大家好,如果您还对小说网站源码分享tp不太了解,没有关系,今天就由本站为大家分享小说网站源码分享tp的知识,包括小说源码php分享的问题都会给大家分析到,还望可以解决大家的问题,下面我们就开始吧!
一、CAN协议概念
1.1CAN协议简介
CAN是控制器局域网络(ControllerAreaNetwork)的简称,它是由研发和生产汽车电子产品著称的德国BOSCH公司开发的,并最终成为国际标准(ISO11519以及ISO11898),是国际上应用最广泛的现场总线之一。差异点如下:
CAN总线协议已经成为汽车计算机控制系统和嵌入式工业控制局域网的标准总线,并且拥有以CAN为底层协议专为大型货车和重工机械车辆设计的J1939协议。近年来,它具有的高可靠性和良好的错误检测能力受到重视,被广泛应用于汽车计算机控制系统和环境温度恶劣、电磁辐射强及振动大的工业环境。
我们来贴图一个车载网络构想图
1.2CAN物理层
与I2C、SPI等具有时钟信号的同步通讯方式不同,CAN通讯并不是以时钟信号来进行同步的,它是一种异步通讯,只具有CAN_High和CAN_Low两条信号线,共同构成一组差分信号线,以差分信号的形式进行通讯。我们来看一个示意图
1.2.1闭环总线网络
CAN物理层的形式主要有两种,图中的CAN通讯网络是一种遵循ISO11898标准的高速、短距离“闭环网络”,它的总线最大长度为40m,通信速度最高为1Mbps,总线的两端各要求有一个“120欧”的电阻。
1.2.2开环总线网络
图中的是遵循ISO11519-2标准的低速、远距离“开环网络”,它的最大传输距离为1km,最高通讯速率为125kbps,两根总线是独立的、不形成闭环,要求每根总线上各串联有一个“2.2千欧”的电阻。
1.2.3通讯节点
从CAN通讯网络图可了解到,CAN总线上可以挂载多个通讯节点,节点之间的信号经过总线传输,实现节点间通讯。由于CAN通讯协议不对节点进行地址编码,而是对数据内容进行编码的,所以网络中的节点个数理论上不受限制,只要总线的负载足够即可,可以通过中继器增强负载。
CAN通讯节点由一个CAN控制器及CAN收发器组成,控制器与收发器之间通过CAN_Tx及CAN_Rx信号线相连,收发器与CAN总线之间使用CAN_High及CAN_Low信号线相连。其中CAN_Tx及CAN_Rx使用普通的类似TTL逻辑信号,而CAN_High及CAN_Low是一对差分信号线,使用比较特别的差分信号,下一小节再详细说明。
当CAN节点需要发送数据时,控制器把要发送的二进制编码通过CAN_Tx线发送到收发器,然后由收发器把这个普通的逻辑电平信号转化成差分信号,通过差分线CAN_High和CAN_Low线输出到CAN总线网络。而通过收发器接收总线上的数据到控制器时,则是相反的过程,收发器把总线上收到的CAN_High及CAN_Low信号转化成普通的逻辑电平信号,通过CAN_Rx输出到控制器中。
例如,STM32的CAN片上外设就是通讯节点中的控制器,为了构成完整的节点,还要给它外接一个收发器,在我们实验板中使用型号为TJA1050的芯片作为CAN收发器。CAN控制器与CAN收发器的关系如同TTL串口与MAX3232电平转换芯片的关系,MAX3232芯片把TTL电平的串口信号转换成RS-232电平的串口信号,CAN收发器的作用则是把CAN控制器的TTL电平信号转换成差分信号(或者相反)。
目前有以下CAN电平转换芯片(不全)
我们来用TJA1050来看下原理图:
1.2.4差分信号
差分信号又称差模信号,与传统使用单根信号线电压表示逻辑的方式有区别,使用差分信号传输时,需要两根信号线,这两个信号线的振幅相等,相位相反,通过两根信号线的电压差值来表示
逻辑0和逻辑1。见图,它使用了V+与V-信号的差值表达出了图下方的信号。
相对于单信号线传输的方式,使用差分信号传输具有如下优点:
?抗干扰能力强,当外界存在噪声干扰时,几乎会同时耦合到两条信号线上,而接收端只关心两个信号的差值,所以外界的共模噪声可以被完全抵消。
举一个例子,正常的单线假设逻辑1是3.3V,逻辑0假设是0V,但是如果有噪声,把3.3V弄成了0V(极端),把0V弄成了-3.3V,此时就逻辑错误,但是有Can高/Can低一般都作用于两根线,所以两个虽然都有噪声影响,但是差值还是不变的
?能有效抑制它对外部的电磁干扰,同样的道理,由于两根信号的极性相反,他们对外辐射的电磁场可以相互抵消,耦合的越紧密,泄放到外界的电磁能量越少。
举一个例子,假设一根是10V,一根是-10V,单跟都会对外部造成电磁干扰,但是CAN可以把线拧在一起,跟编麻花一样,可以互相抵消电子干扰
?时序定位精确,由于差分信号的开关变化是位于两个信号的交点,而不像普通单端信号依靠高低两个阈值电压判断,因而受工艺,温度的影响小,能降低时序上的误差,同时也更适合于低幅度信号的电路。
由于差分信号线具有这些优点,所以在USB协议、485协议、以太网协议及CAN协议的物理层中,都使用了差分信号传输。
1.2.5CAN协议中的差分信号
CAN协议中对它使用的CAN_High及CAN_Low表示的差分信号做了规定,见表及图。以高速CAN协议为例,当表示逻辑1时(隐性电平),CAN_High和CAN_Low线上的电压均为2.5v,即它们的电压差VH-V:sub:L=0V;而表示逻辑0时(显性电平),CAN_High的电平为3.5V,CAN_Low线的电平为1.5V,即它们的电压差为VH-V:sub:L=2V。例如,当CAN收发器从CAN_Tx线接收到来自CAN控制器的低电平信号时(逻辑0),它会使CAN_High输出3.5V,同时CAN_Low输出1.5V,从而输出显性电平表示逻辑0。
在CAN总线中,必须使它处于隐性电平(逻辑1)或显性电平(逻辑0)中的其中一个状态。假如有两个CAN通讯节点,在同一时间,一个输出隐性电平,另一个输出显性电平,类似I2C总线的“线与”特性将使它处于显性电平状态,显性电平的名字就是这样来的,即可以认为显性具有优先的意味。
由于CAN总线协议的物理层只有1对差分线,在一个时刻只能表示一个信号,所以对通讯节点来说,CAN通讯是半双工的,收发数据需要分时进行。在CAN的通讯网络中,因为共用总线,在整个网络中同一时刻只能有一个通讯节点发送信号,其余的节点在该时刻都只能接收。
1.3CAN协议层
1.3.1CAN的波特率及位同步
由于CAN属于异步通讯,没有时钟信号线,连接在同一个总线网络中的各个节点会像串口异步通讯那样,节点间使用约定好的波特率进行通讯,特别地,CAN还会使用“位同步”的方式来抗干扰、吸收误差,实现对总线电平信号进行正确的采样,确保通讯正常。
1.3.2位时序分解
为了实现位同步,CAN协议把每一个数据位的时序分解成如图所示的SS段、PTS段、PBS1段、PBS2段,这四段的长度加起来即为一个CAN数据位的长度。分解后最小的时间单位是Tq,而一个完整的位由8~25个Tq组成。为方便表示,图中的高低电平直接代表信号逻辑0或逻辑1(不是差分信号)。
该图中表示的CAN通讯信号每一个数据位的长度为19Tq,其中SS段占1Tq,PTS段占6Tq,PBS1段占5Tq,PBS2段占7Tq。信号的采样点位于PBS1段与PBS2段之间,通过控制各段的长度,可以对采样点的位置进行偏移,以便准确地采样。
各段的作用如介绍下:
?SS段(SYNCSEG)
SS译为同步段,若通讯节点检测到总线上信号的跳变沿被包含在SS段的范围之内,则表示节点与总线的时序是同步的,当节点与总线同步时,采样点采集到的总线电平即可被确定为该位的电平。SS段的大小固定为1Tq。
?PTS段(PROPSEG)
PTS译为传播时间段,这个时间段是用于补偿网络的物理延时时间。是总线上输入比较器延时和输出驱动器延时总和的两倍。PTS段的大小可以为1~8Tq。
?PBS1段(PHASESEG1),
PBS1译为相位缓冲段,主要用来补偿边沿阶段的误差,它的时间长度在重新同步的时候可以加长。PBS1段的初始大小可以为1~8Tq。
?PBS2段(PHASESEG2)
PBS2这是另一个相位缓冲段,也是用来补偿边沿阶段误差的,它的时间长度在重新同步时可以缩短。PBS2段的初始大小可以为2~8Tq。
1.3.3通讯的波特率
总线上的各个通讯节点只要约定好1个Tq的时间长度以及每一个数据位占据多少个Tq,就可以确定CAN通讯的波特率。
例如,假设上图中的1Tq=1us,而每个数据位由19个Tq组成,则传输一位数据需要时间T1bit=19us,从而每秒可以传输的数据位个数为:1×10次方/19=52631.6(bps)
这个每秒可传输的数据位的个数即为通讯中的波特率。
1.3.4同步过程分析
波特率只是约定了每个数据位的长度,数据同步还涉及到相位的细节,这个时候就需要用到数据位内的SS、PTS、PBS1及PBS2段了。根据对段的应用方式差异,CAN的数据同步分为硬同步和重新同步。其中硬同步只是当存在“帧起始信号”时起作用,无法确保后续一连串的位时序都是同步的,而重新同步方式可解决该问题,这两种方式具体介绍如下:
(1)硬同步
若某个CAN节点通过总线发送数据时,它会发送一个表示通讯起始的信号(即下一小节介绍的帧起始信号),该信号是一个由高变低的下降沿。而挂载到CAN总线上的通讯节点在不发送数据时,会时刻检测总线上的信号。见图,可以看到当总线出现帧起始信号时,某节点检测到总线的帧起始信号不在节点内部时序的SS段范围,所以判断它自己的内部时序与总线不同步,因而这个状态的采样点采集得的数据是不正确的。所以节点以硬同步的方式调整,把自己的位时序中的SS段平移至总线出现下降沿的部分,获得同步,同步后采样点就可以采集得正确数据了。
(2)重新同步
前面的硬同步只是当存在帧起始信号时才起作用,如果在一帧很长的数据内,节点信号与总线信号相位有偏移时,这种同步方式就无能为力了。因而需要引入重新同步方式,它利用普通数据位的高至低电平的跳变沿来同步(帧起始信号是特殊的跳变沿)。重新同步与硬同步方式相似的地方是它们都使用SS段来进行检测,同步的目的都是使节点内的SS段把跳变沿包含起来。重新同步的方式分为超前和滞后两种情况,以总线跳变沿与SS段的相对位置进行区分。第一种相位超前的情况如图,节点从总线的边沿跳变中,检测到它内部的时序比总线的时序相对超前2Tq,这时控制器在下一个位时序中的PBS1段增加2Tq的时间长度,使得节点与总线时序重新同步。
第二种相位滞后的情况如图,节点从总线的边沿跳变中,检测到它的时序比总线的时序相对滞后2Tq,这时控制器在前一个位时序中的PBS2段减少2Tq的时间长度,获得同步。
在重新同步的时候,PBS1和PBS2中增加或减少的这段时间长度被定义为“重新同步补偿宽度SJW*(reSynchronizationJumpWidth)”。一般来说CAN控制器会限定SJW的最大值,如限定了最大SJW=3Tq时,单次同步调整的时候不能增加或减少超过3Tq的时间长度,若有需要,控制器会通过多次小幅度调整来实现同步。当控制器设置的SJW极限值较大时,可以吸收的误差加大,但通讯的速度会下降
1.3.5CAN的报文种类及结构
在SPI通讯中,片选、时钟信号、数据输入及数据输出这4个信号都有单独的信号线,I2C协议包含有时钟信号及数据信号2条信号线,异步串口包含接收与发送2条信号线,这些协议包含的信号都比CAN协议要丰富,它们能轻易进行数据同步或区分数据传输方向。而CAN使用的是两条差分信号线,只能表达一个信号,简洁的物理层决定了CAN必然要配上一套更复杂的协议,如何用一个信号通道实现同样、甚至更强大的功能呢?CAN协议给出的解决方案是对数据、操作命令(如读/写)以及同步信号进行打包,打包后的这些内容称为报文。
1.3.5.1报文的种类
在原始数据段的前面加上传输起始标签、片选(识别)标签和控制标签,在数据的尾段加上CRC校验标签、应答标签和传输结束标签,把这些内容按特定的格式打包好,就可以用一个通道表达各种信号了,各种各样的标签就如同SPI中各种通道上的信号,起到了协同传输的作用。当整个数据包被传输到其它设备时,只要这些设备按格式去解读,就能还原出原始数据,这样的报文就被称为CAN的“数据帧”。
为了更有效地控制通讯,CAN一共规定了5种类型的帧,它们的类型及用途说明如表
1.3.5.2数据帧的结构
数据帧是在CAN通讯中最主要、最复杂的报文,我们来了解它的结构,见图
数据帧以一个显性位(逻辑0)开始,以7个连续的隐性位(逻辑1)结束,在它们之间,分别有仲裁段、控制段、数据段、CRC段和ACK段。
?帧起始
SOF段(StartOfFrame),译为帧起始,帧起始信号只有一个数据位,是一个显性电平,它用于通知各个节点将有数据传输,其它节点通过帧起始信号的电平跳变沿来进行硬同步。
?仲裁段
当同时有两个报文被发送时,总线会根据仲裁段的内容决定哪个数据包能被传输,这也是它名称的由来。
仲裁段的内容主要为本数据帧的ID信息(标识符),数据帧具有标准格式和扩展格式两种,区别就在于ID信息的长度,标准格式的ID为11位,扩展格式的ID为29位,它在标准ID的基础上多出18位。在CAN协议中,ID起着重要的作用,它决定着数据帧发送的优先级,也决定着其它节点是否会接收这个数据帧。
CAN协议不对挂载在它之上的节点分配优先级和地址,对总线的占有权是由信息的重要性决定的,即对于重要的信息,我们会给它打包上一个优先级高的ID,使它能够及时地发送出去。也正因为它这样的优先级分配原则,使得CAN的扩展性大大加强,在总线上增加或减少节点并不影响其它设备。报文的优先级,是通过对ID的仲裁来确定的。根据前面对物理层的分析我们知道如果总线上同时出现显性电平和隐性电平,总线的状态会被置为显性电平,CAN正是利用这个特性进行仲裁。
若两个节点同时竞争CAN总线的占有权,当它们发送报文时,若首先出现隐性电平,则会失去对总线的占有权,进入接收状态。见图,在开始阶段,两个设备发送的电平一样,所以它们一直继续发送数据。到了图中箭头所指的时序处,节点单元1发送的为隐性电平,而此时节点单元2发送的为显性电平,由于总线的“线与”特性使它表达出显示电平,因此单元2竞争总线成功,这个报文得以被继续发送出去。
仲裁段ID的优先级也影响着接收设备对报文的反应。因为在CAN总线上数据是以广播的形式发送的,所有连接在CAN总线的节点都会收到所有其它节点发出的有效数据,因而我们的CAN
控制器大多具有根据ID过滤报文的功能,它可以控制自己只接收某些ID的报文。回看数据帧格式,可看到仲裁段除了报文ID外,还有RTR、IDE和SRR位。
(1)RTR位(RemoteTransmissionRequestBit),译作远程传输请求位,它是用于区分数据帧和遥控帧的,当它为显性电平时表示数据帧,隐性电平时表示遥控帧。
(2)IDE位(IdentifierExtensionBit),译作标识符扩展位,它是用于区分标准格式与扩展格式,当它为显性电平时表示标准格式,隐性电平时表示扩展格式。
(3)SRR位(SubstituteRemoteRequestBit),只存在于扩展格式,它用于替代标准格式中的RTR位。由于扩展帧中的SRR位为隐性位,RTR在数据帧为显性位,所以在两个ID相同的标准格式报文与扩展格式报文中,标准格式的优先级较高。
?控制段
在控制段中的r1和r0为保留位,默认设置为显性位。它最主要的是DLC段(DataLengthCode),译为数据长度码,它由4个数据位组成,用于表示本报文中的数据段含有多少个字节,DLC段表示的数字为0~8。
?数据段
数据段为数据帧的核心内容,它是节点要发送的原始信息,由0~8个字节组成,MSB先行。
?CRC段
为了保证报文的正确传输,CAN的报文包含了一段15位的CRC校验码,一旦接收节点算出的CRC码跟接收到的CRC码不同,则它会向发送节点反馈出错信息,利用错误帧请求它重新发送。CRC部分的计算一般由CAN控制器硬件完成,出错时的处理则由软件控制最大重发数。在CRC校验码之后,有一个CRC界定符,它为隐性位,主要作用是把CRC校验码与后面的ACK段间隔起来。
?ACK段
ACK段包括一个ACK槽位,和ACK界定符位。类似I2C总线,在ACK槽位中,发送节点发送的是隐性位,而接收节点则在这一位中发送显性位以示应答。在ACK槽和帧结束之间由ACK界定符间隔开。
?帧结束
EOF段(EndOfFrame),译为帧结束,帧结束段由发送节点发送的7个隐性位表示结束。
1.3.5.3其它报文的结构
二、STM32CAN控制器介绍
STM32的芯片中具有bxCAN控制器(BasicExtendedCAN),它支持CAN协议2.0A和2.0B标准。该CAN控制器支持最高的通讯速率为1Mb/s;可以自动地接收和发送CAN报文,支持使用标准ID和扩展ID的报文;外设中具有3个发送邮箱,发送报文的优先级可以使用软件控制,还可以记录发送的时间;具有2个3级深度的接收FIFO,可使用过滤功能只接收或不接收某些ID号的报文;可配置成自动重发;不支持使用DMA进行数据收发。框架示意图如下:
STM32的有两组CAN控制器,其中CAN1是主设备,框图中的“存储访问控制器”是由CAN1控制的,CAN2无法直接访问存储区域,所以使用CAN2的时候必须使能CAN1外设的时钟。框图中主要包含CAN控制内核、发送邮箱、接收FIFO以及验收筛选器,下面对框图中的各个部分进行介绍。
2.1CAN控制内核
框图中标号处的CAN控制内核包含了各种控制寄存器及状态寄存器,我们主要讲解其中的主控制寄存器CAN_MCR及位时序寄存器CAN_BTR。
2.1.1主控制寄存器CAN_MCR
主控制寄存器CAN_MCR负责管理CAN的工作模式,它使用以下寄存器位实现控制。
(1)DBF调试冻结功能
DBF(Debugfreeze)调试冻结,使用它可设置CAN处于工作状态或禁止收发的状态,禁止收发时仍可访问接收FIFO中的数据。这两种状态是当STM32芯片处于程序调试模式时才使用的,平时使用并不影响。
(2)TTCM时间触发模式
TTCM(Timetriggeredcommunicationmode)时间触发模式,它用于配置CAN的时间触发通信模式,在此模式下,CAN使用它内部定时器产生时间戳,并把它保存在CAN_RDTxR、CAN_TDTxR寄存器中。内部定时器在每个CAN位时间累加,在接收和发送的帧起始位被采样,并生成时间戳。利用它可以实现ISO11898-4CAN标准的分时同步通信功能。
(3)ABOM自动离线管理
ABOM(Automaticbus-offmanagement)自动离线管理,它用于设置是否使用自动离线管理功能。当节点检测到它发送错误或接收错误超过一定值时,会自动进入离线状态,在离线状态中,CAN不能接收或发送报文。处于离线状态的时候,可以软件控制恢复或者直接使用这个自动离线管理功能,它会在适当的时候自动恢复。
(4)AWUM自动唤醒
AWUM(Automaticbus-offmanagement),自动唤醒功能,CAN外设可以使用软件进入低功耗的睡眠模式,如果使能了这个自动唤醒功能,当CAN检测到总线活动的时候,会自动唤醒。
(5)NART自动重传
NART(Noautomaticretransmission)报文自动重传功能,设置这个功能后,当报文发送失败时会自动重传至成功为止。若不使用这个功能,无论发送结果如何,消息只发送一次。
(6)RFLM锁定模式
RFLM(ReceiveFIFOlockedmode)FIFO锁定模式,该功能用于锁定接收FIFO。锁定后,当接收FIFO溢出时,会丢弃下一个接收的报文。若不锁定,则下一个接收到的报文会覆盖原报文。
(7)TXFP报文发送优先级的判定方法
TXFP(TransmitFIFOpriority)报文发送优先级的判定方法,当CAN外设的发送邮箱中有多个待发送报文时,本功能可以控制它是根据报文的ID优先级还是报文存进邮箱的顺序来发送。
2.1.2位时序寄存器(CAN_BTR)及波特率
CAN外设中的位时序寄存器CAN_BTR用于配置测试模式、波特率以及各种位内的段参数。
2.1.2.1模式
位31SILM:静默模式(调试)(Silentmode(debug))
0:正常工作
1:静默模式
位30LBKM:环回模式(调试)(Loopbackmode(debug))
0:禁止环回模式
1:使能环回模式
为方便调试,STM32的CAN提供了测试模式,配置位时序寄存器CAN_BTR的SILM及LBKM寄存器位可以控制使用正常模式、静默模式、回环模式及静默回环模式,见图。
各个工作模式介绍如下:
?正常模式
正常模式下就是一个正常的CAN节点,可以向总线发送数据和接收数据。
?静默模式
静默模式下,它自己的输出端的逻辑0数据会直接传输到它自己的输入端,逻辑1可以被发送到总线,所以它不能向总线发送显性位(逻辑0),只能发送隐性位(逻辑1)。输入端可以从总线接收内容。由于它只可发送的隐性位不会强制影响总线的状态,所以把它称为静默模式。这种模式一般用于监测,它可以用于分析总线上的流量,但又不会因为发送显性位而影响总线。
?回环模式
回环模式下,它自己的输出端的所有内容都直接传输到自己的输入端,输出端的内容同时也会被传输到总线上,即也可使用总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。使用回环模式可以进行自检。
?回环静默模式
回环静默模式是以上两种模式的结合,自己的输出端的所有内容都直接传输到自己的输入端,并且不会向总线发送显性位影响总线,不能通过总线监测它的发送内容。输入端只接收自己发送端的内容,不接收来自总线上的内容。这种方式可以在“热自检”时使用,即自我检查的时候,不会干扰总线。
以上说的各个模式,是不需要修改硬件接线的,例如,当输出直接连输入时,它是在STM32芯片内部连接的,传输路径不经过STM32的CAN_Tx/Rx引脚,更不经过外部连接的CAN收发器,只有输出数据到总线或从总线接收的情况下才会经过CAN_Tx/Rx引脚和收发器
2.1.2.2位时序及波特率
STM32外设定义的位时序与我们前面解释的CAN标准时序有一点区别,见图
STM32的CAN外设位时序中只包含3段,分别是同步段SYNC_SEG、位段BS1及位段BS2,采样点位于BS1及BS2段的交界处。其中SYNC_SEG段固定长度为1Tq,而BS1及BS2段可以
在位时序寄存器CAN_BTR设置它们的时间长度,它们可以在重新同步期间增长或缩短,该长度SJW也可在位时序寄存器中配置。
理解STM32的CAN外设的位时序时,可以把它的BS1段理解为是由前面介绍的CAN标准协议中PTS段与PBS1段合在一起的,而BS2段就相当于PBS2段。
了解位时序后,我们就可以配置波特率了。通过配置位时序寄存器CAN_BTR的TS1[3:0]及
TS2[2:0]寄存器位设定BS1及BS2段的长度后,我们就可以确定每个CAN数据位的时间:
BS1段时间:TS1=Tqx(TS1[3:0]+1),
BS2段时间:TS2=Tqx(TS2[2:0]+1),
一个数据位的时间:T1bit=1Tq+TS1+TS2=1+(TS1[3:0]+1)+(TS2[2:0]+1)=NTq
其中单个时间片的长度Tq与CAN外设的所挂载的时钟总线及分频器配置有关,CAN1和CAN2外设都是挂载在APB1总线上的,而位时序寄存器CAN_BTR中的BRP[9:0]寄存器位可以设置
CAN波特率=Fpclk1/((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)
其中clk为42M!
推荐一个CAN波特率计算器
CAN波特率计算f103AHP1_36Mf407AHP1_42M采样点软件有说明.rar
2.2CAN发送邮箱
回到图中的CAN外设框图,在标号处的是CAN外设的发送邮箱,它一共有3个发送邮箱,即最多可以缓存3个待发送的报文。每个发送邮箱中包含有标识符寄存器CAN_TIxR、数据长度控制寄存器CAN_TDTxR及2个数据寄存器CAN_TDLxR、CAN_TDHxR,它们的功能见表
当我们要使用CAN外设发送报文时,把报文的各个段分解,按位置写入到这些寄存器中,并对标识符寄存器CAN_TIxR中的发送请求寄存器位TMIDxR_TXRQ置1,即可把数据发送出去。其中标识符寄存器CAN_TIxR中的STDID寄存器位比较特别。我们知道CAN的标准标识符的总位数为11位,而扩展标识符的总位数为29位的。当报文使用扩展标识符的时候,标识符寄存器CAN_TIxR中的STDID[10:0]等效于EXTID[18:28]位,它与EXTID[17:0]共同组成完整的29位扩展标识符。
2.3CAN接收FIFO
图中的CAN外设框图,在标号处的是CAN外设的接收FIFO,它一共有2个接收FIFO,每个FIFO中有3个邮箱,即最多可以缓存6个接收到的报文。当接收到报文时,FIFO的报文计数器会自增,而STM32内部读取FIFO数据之后,报文计数器会自减,我们通过状态寄存器可获知报文计数器的值,而通过前面主控制寄存器的RFLM位,可设置锁定模式,锁定模式下FIFO溢出时会丢弃新报文,非锁定模式下FIFO溢出时新报文会覆盖旧报文。跟发送邮箱类似,每个接收FIFO中包含有标识符寄存器CAN_RIxR、数据长度控制寄存器CAN_RDTxR及2个数据寄存器CAN_RDLxR、CAN_RDHxR,它们的功能见表。
通过中断或状态寄存器知道接收FIFO有数据后,我们再读取这些寄存器的值即可把接收到的报文加载到STM32的内存中
2.4验收筛选器
图中的CAN外设框图,在标号处的是CAN外设的验收筛选器,一共有28个筛选器组,每个筛选器组有2个寄存器,CAN1和CAN2共用的筛选器的。在CAN协议中,消息的标识符与节点地址无关,但与消息内容有关。因此,发送节点将报文广播给所有接收器时,接收节点会根据报文标识符的值来确定软件是否需要该消息,为了简化软件的工作,STM32的CAN外设接收报文前会先使用验收筛选器检查,只接收需要的报文到FIFO中。
筛选器工作的时候,可以调整筛选ID的长度及过滤模式。根据筛选ID长度来分类有有以下两种:
(1)检查STDID[10:0]、EXTID[17:0]、IDE和RTR位,一共31位。
(2)检查STDID[10:0]、RTR、IDE和EXTID[17:15],一共16位。
通过配置筛选尺度寄存器CAN_FS1R的FSCx位可以设置筛选器工作在哪个尺度。而根据过滤的方法分为以下两种模式:
(1)标识符列表模式,它把要接收报文的ID列成一个表,要求报文ID与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理。
(2)掩码模式,它把可接收报文ID的某几位作为列表,这几位被称为掩码,可以把它理解成关键字搜索,只要掩码(关键字)相同,就符合要求,报文就会被保存到接收FIFO。通过配置筛选模式寄存器CAN_FM1R的FBMx位可以设置筛选器工作在哪个模式。不同的尺度和不同的过滤方法可使筛选器工作在图的4种状态。
每组筛选器包含2个32位的寄存器,分别为CAN_FxR1和CAN_FxR2,它们用来存储要筛选的ID或掩码,各个寄存器位代表的意义与图中两个寄存器下面“映射”的一栏一致,各个模式的说明见表。
例如下面的表格所示,在掩码模式时,第一个寄存器存储要筛选的ID,第二个寄存器存储掩码,掩码为1的部分表示该位必须与ID中的内容一致,筛选的结果为表中第三行的ID值,它是一组包含多个的ID值,其中x表示该位可以为1可以为0。
而工作在标识符模式时,2个寄存器存储的都是要筛选的ID,它只包含2个要筛选的ID值(32位模式时)。
如果使能了筛选器,且报文的ID与所有筛选器的配置都不匹配,CAN外设会丢弃该报文,不存入接收FIFO。
2.5整体控制逻辑
回到图结构框图,图中的标号处表示的是CAN2外设的结构,它与CAN1外设是一样的,他们共用筛选器且由于存储访问控制器由CAN1控制,所以要使用CAN2的时候必须要使能CAN1的时钟。其中STM32F103系列芯片不具有CAN2控制器。
2.6STM32HAL库代码逻辑
2.6.1初始化
注意:网络上基本上用的很久的HAL库,我们采用很新的1.25.2,最新的库还是差异挺大的!
从STM32的CAN外设我们了解到它的功能非常多,控制涉及的寄存器也非常丰富,而使用STM32HAL库提供的各种结构体及库函数可以简化这些控制过程。跟其它外设一样,STM32
HAL库提供了CAN初始化结构体及初始化函数来控制CAN的工作方式,提供了收发报文使用的结构体及收发函数,还有配置控制筛选器模式及ID的结构体。这些内容都定义在库文件“STM32F4xx_hal_can.h”及“STM32F4xx_hal_can.c”中,编程时我们可以结合这两个文件内的注释使用或参考库帮助文档。首先我们来学习初始化结构体的内容,见代码清单1。代码清单CAN初始化结构
typedefstruct\n{\nuint32_tPrescaler;/*配置CAN外设的时钟分频,可设置为1-1024*/\nuint32_tMode;/*配置CAN的工作模式,回环或正常模式*/\nuint32_tSyncJumpWidth;/*配置SJW极限值*/\nuint32_tTimeSeg1;/*配置BS1段长度*/\nuint32_tTimeSeg2;/*配置BS2段长度*/\nFunctionalStateTimeTriggeredMode;/*是否使能TTCM时间触发功能*/\nFunctionalStateAutoBusOff;/*是否使能ABOM自动离线管理功能*/\nFunctionalStateAutoWakeUp;/*是否使能AWUM自动唤醒功能*/\nFunctionalStateAutoRetransmission;/*是否使能NART自动重传功能*/\nFunctionalStateReceiveFifoLocked;/*是否使能RFLM锁定FIFO功能*/\nFunctionalStateTransmitFifoPriority;/*配置TXFP报文优先级的判定方法*/\n}CAN_InitTypeDef;\n
体这些结构体成员说明如下,其中括号内的文字是对应参数在STM32HAL库中定义的宏
(1)Prescaler
本成员设置CAN外设的时钟分频,它可控制时间片Tq的时间长度,这里设置的值最终会减1后再写入BRP寄存器位,即前面介绍的Tq计算公式:
Tq=(BRP[9:0]+1)xTPCLK
等效于:Tq=CAN_PrescalerxTPCLK
(2)Mode
本成员设置CAN的工作模式,可设置为正常模式(CAN_MODE_NORMAL)、回环模式(CAN_MODE_LOOPBACK)、静默模式(CAN_MODE_SILENT)以及回环静默模式(CAN_MODE_SILENT_LOOPBACK)。
(3)SyncJumpWidth
本成员可以配置SJW的极限长度,即CAN重新同步时单次可增加或缩短的最大长度,它可以被配置为1-4Tq(CAN_SJW_1/2/3/4tq)。
(4)TimeSeg1
本成员用于设置CAN位时序中的BS1段的长度,它可以被配置为1-16个Tq长度(CAN_BS1_1/2/3…16tq)。
(5)TimeSeg2
本成员用于设置CAN位时序中的BS2段的长度,它可以被配置为1-8个Tq长度(CAN_BS2_1/2/3…8tq)。SYNC_SEG、BS1段及BS2段的长度加起来即一个数据位的长度,即前面介绍的原来
计算公式:T1bit=1Tq+TS1+TS2=1+(TS1[3:0]+1)+(TS2[2:0]+1)
等效于:T1bit=1Tq+CAN_BS1+CAN_BS2
(6)TimeTriggeredMode
本成员用于设置是否使用时间触发功能(ENABLE/DISABLE),时间触发功能在某些CAN标准中会使用到。
(7)AutoBusOff
本成员用于设置是否使用自动离线管理(ENABLE/DISABLE),使用自动离线管理可以在节点出错离线后适时自动恢复,不需要软件干预。
(8)AutoWakeUp
本成员用于设置是否使用自动唤醒功能(ENABLE/DISABLE),使能自动唤醒功能后它会在监测到总线活动后自动唤醒。
(9)AutoWakeUp
本成员用于设置是否使用自动离线管理功能(ENABLE/DISABLE),使用自动离线管理可以在出错时离线后适时自动恢复,不需要软件干预。
(10)AutoRetransmission
本成员用于设置是否使用自动重传功能(ENABLE/DISABLE),使用自动重传功能时,会一直发送报文直到成功为止。
(11)ReceiveFifoLocked
本成员用于设置是否使用锁定接收FIFO(ENABLE/DISABLE),锁定接收FIFO后,若FIFO溢出时会丢弃新数据,否则在FIFO溢出时以新数据覆盖旧数据。
(12)TransmitFifoPriority
本成员用于设置发送报文的优先级判定方法(ENABLE/DISABLE),使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文ID的优先级来发送。配置完这些结构体成员后,我们调用库函数HAL_CAN_Init即可把这些参数写入到CAN控制寄存器中,实现CAN的初始化
2.6.2CAN发送及接收结构体
在发送或接收报文时,需要往发送邮箱中写入报文信息或从接收FIFO中读取报文信息,利用STM32HAL库的发送及接收结构体可以方便地完成这样的工作,它们的定义见代码清单。代码清单39?2CAN发送及接收结构体
typedefstruct\n{\nuint32_tStdId;/*存储报文的标准标识符11位,0-0x7FF.*/\nuint32_tExtId;/*存储报文的扩展标识符29位,0-0x1FFFFFFF.*/\nuint32_tIDE;/*存储IDE扩展标志*/\nuint32_tRTR;/*存储RTR远程帧标志*/\nuint32_tDLC;/*存储报文数据段的长度,0-8*/\nFunctionalStateTransmitGlobalTime;\n}CAN_TxHeaderTypeDef;\ntypedefstruct\n{\nuint32_tStdId;/*存储报文的标准标识符11位,0-0x7FF.*/\nuint32_tExtId;/*存储报文的扩展标识符29位,0-0x1FFFFFFF.*/\nuint32_tIDE;/*存储IDE扩展标志*/\nuint32_tRTR;/*存储RTR远程帧标志*/\nuint32_tDLC;/*存储报文数据段的长度,0-8*/\nuint32_tTimestamp;\nuint32_tFilterMatchIndex;\n}CAN_RxHeaderTypeDef;\n
这些结构体成员,说明如下:
(1)StdId
本成员存储的是报文的11位标准标识符,范围是0-0x7FF。
(2)ExtId
本成员存储的是报文的29位扩展标识符,范围是0-0x1FFFFFFF。ExtId与StdId这两个成员根据下面的IDE位配置,只有一个是有效的。
(3)IDE
本成员存储的是扩展标志IDE位,当它的值为宏CAN_ID_STD时表示本报文是标准帧,使用StdId成员存储报文ID;当它的值为宏CAN_ID_EXT时表示本报文是扩展帧,使用ExtId成员存储报文ID。
(4)RTR
本成员存储的是报文类型标志RTR位,当它的值为宏CAN_RTR_Data时表示本报文是数据帧;当它的值为宏CAN_RTR_Remote时表示本报文是遥控帧,由于遥控帧没有数据段,所以当报文是遥控帧时,数据是无效的
(5)DLC
本成员存储的是数据帧数据段的长度,它的值的范围是0-8,当报文是遥控帧时DLC值为0。
2.6.3CAN筛选器结构体
CAN的筛选器有多种工作模式,利用筛选器结构体可方便配置,它的定义见代码清单。代码清单CAN筛选器结构体
typedefstruct\n{\nuint32_tFilterIdHigh;/*CAN_FxR1寄存器的高16位*/\nuint32_tFilterIdLow;/*CAN_FxR1寄存器的低16位*/\nuint32_tFilterMaskIdHigh;/*CAN_FxR2寄存器的高16位*/\nuint32_tFilterMaskIdLow;/*CAN_FxR2寄存器的低16位*/\nuint32_tFilterFIFOAssignment;/*设置经过筛选后数据存储到哪个接收FIFO*/\nuint32_tFilterBank;/*筛选器编号,范围0-27,数据手册上说0-27是CAN1/CAN2共享,但是实测发现并不是这样,CAN1是0-13,CAN2是14-27*/\nuint32_tFilterMode;/*筛选器模式*/\nuint32_tFilterScale;/*设置筛选器的尺度*/\nuint32_tFilterActivation;/*是否使能本筛选器*/\nuint32_tSlaveStartFilterBank;\n}CAN_FilterTypeDef;\n
这些结构体成员都是“41.2.14验收筛选器”小节介绍的内容,可对比阅读,各个结构体成员的介绍如下:
(1)FilterIdHigh
FilterIdHigh成员用于存储要筛选的ID,若筛选器工作在32位模式,它存储的是所筛选ID的高16位;若筛选器工作在16位模式,它存储的就是一个完整的要筛选的ID。
(2)FilterIdLow
类似地,FilterIdLow成员也是用于存储要筛选的ID,若筛选器工作在32位模式,它存储的是所筛选ID的低16位;若筛选器工作在16位模式,它存储的就是一个完整的要筛选的ID。
(3)FilterMaskIdHigh
FilterMaskIdHigh存储的内容分两种情况,当筛选器工作在标识符列表模式时,它的功能与FilterIdHigh相同,都是存储要筛选的ID;而当筛选器工作在掩码模式时,它存储的是FilterIdHigh成员对应的掩码,与FilterIdLow组成一组筛选器。
(4)FilterMaskIdLow
类似地,FilterMaskIdLow存储的内容也分两种情况,当筛选器工作在标识符列表模式时,它的功能与FilterIdLow相同,都是存储要筛选的ID;而当筛选器工作在掩码模式时,它存储的是FilterIdLow成员对应的掩码,与FilterIdLow组成一组筛选器。上面四个结构体的存储的内容很容易让人糊涂,请结合前面的图39_0_15和下面的表39?7理解,如果还搞不清楚,再结合库函数FilterInit的源码来分析。
表不同模式下各结构体成员的内容
对这些结构体成员赋值的时候,还要注意寄存器位的映射,即注意哪部分代表STID,哪部分代表EXID以及IDE、RTR位。
(5)FilterFIFOAssignment
本成员用于设置当报文通过筛选器的匹配后,该报文会被存储到哪一个接收FIFO,它的可选值为FIFO0或FIFO1(宏CAN_FILTER_FIFO0/1)。
(6)FilterBank
本成员用于设置筛选器的编号,即本过滤器结构体配置的是哪一组筛选器,CAN一共有28个筛选器,所以它的可输入参数范围为0-27。
(7)FilterMode
本成员用于设置筛选器的工作模式,可以设置为列表模式(宏CAN_FILTERMODE_IDLIST)及掩码模式(宏CAN_FILTERMODE_IDMASK)。
(8)FilterScale
本成员用于设置筛选器的尺度,可以设置为32位长(宏CAN_FILTERSCALE_32BIT)及16位长(宏CAN_FILTERSCALE_16BIT)。
(9)FilterActivation
本成员用于设置是否激活这个筛选器(宏ENABLE/DISABLE)。
三、CANCubemx配置
我们通过问题来熟悉下cubemx配置,你熟悉了这些问题基本就知道怎么配置了!
问题:ParameterSettings分别都是设置什么的?答案:如图
问题:怎么配置波特率呢?
答案:用我上面贴的工具(CAN波特率计算f103AHP1_36Mf407AHP1_42M采样点软件有说明.rar)直接配置,举两个个例子
例子1:我们要配置成500KHz,那么我们这样配置
我们用采集点为80%,所以BS1为4tq,BS2为2tq,分频系数为12,代进公式Fpclk1/((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)=42M/(4+2+1)/12=500kHz
例子2:我们要配置成1MHz,那么我们这样配置
我们用采集点为75%,所以BS1为3tq,BS2为2tq,分频系数为7,代进公式Fpclk1/((CAN_BS1+CAN_BS2+1)*CAN_Prescaler)=42M/(3+2+1)/7=1MHz
问题:BasicParameter分别是啥意思呢?
TimerTriggeredCommunicationMode:否使用时间触发功能(ENABLE/DISABLE),时间触发功能在某些CAN标准中会使用到。
AutomaticBus-OffManagement:用于设置是否使用自动离线管理功能(ENABLE/DISABLE),使用自动离线管理可以在出错时离线后适时自动恢复,不需要软件干预。
AutomaticWake-UpMode:用于设置是否使用自动唤醒功能(ENABLE/DISABLE),使能自动唤醒功能后它会在监测到总线活动后自动唤醒。
AutomaticRetransmission:用于设置是否使用自动重传功能(ENABLE/DISABLE),使用自动重传功能时,会一直发送报文直到成功为止。
ReceiveFifoLockedMode:用于设置是否使用锁定接收FIFO(ENABLE/DISABLE),锁定接收FIFO后,若FIFO溢出时会丢弃新数据,否则在FIFO溢出时以新数据覆盖旧数据。
TransmitFifoPriority:用于设置发送报文的优先级判定方法(ENABLE/DISABLE),使能时,以报文存入发送邮箱的先后顺序来发送,否则按照报文ID的优先级来发送。配置完这些结构体成员后,我们调用库函数HAL_CAN_Init即可把这些参数写入到CAN控制寄存器中,实现CAN的初始化
问题:为啥CAN分为RX0,RX1中断呢?
答案:STM32有2个3级深度的接收缓冲区:FIFO0和FIFO1,每个FIFO都可以存放3个完整的报文,它们完全由硬件来管理。如果是来自FIFO0的接收中断,则用CAN1_RX0_IRQn中断来处理。如果是来自FIFO1的接收中断,则用CAN1_RX1_IRQn中断来处理,如图:
问题:CANSCE中断时什么?
答案:statuschanegeerror,错误和状态变化中断!
四、CAN分析工具的使用
下面我们会用到CAN分析工具,还是比较好用的,此部分使用作为自己使用
https://www.zhcxgd.com/h-col-112.html
五、实验
1.Normal模式测试500K波特率(定时发送,轮询接收)
1.1CubeMx配置
1.2设置Filter过滤,我们只使能FIFO0,并且不过滤任何消息
uint8_tbsp_can1_filter_config(void)\n{\nCAN_FilterTypeDeffilter={0};\nfilter.FilterActivation=ENABLE;\nfilter.FilterMode=CAN_FILTERMODE_IDMASK;\nfilter.FilterScale=CAN_FILTERSCALE_32BIT;\nfilter.FilterBank=0;\nfilter.FilterFIFOAssignment=CAN_FILTER_FIFO0;\nfilter.FilterIdLow=0;\nfilter.FilterIdHigh=0;\nfilter.FilterMaskIdLow=0;\nfilter.FilterMaskIdHigh=0;\nHAL_CAN_ConfigFilter(&hcan1,&filter);\nreturnBSP_CAN_OK;\n}\n
1.3开启CAN(注意,默认Cubemx生成的代码并没有canstart)
HAL_CAN_Start(&hcan1);\n
1.4编写发送函数
我们开出了几个参数,id_type是扩展帧还是标准帧,basic_id标准帧ID(在标准帧中有效),ex_id扩展帧ID(在扩展帧中有效),data要发送的数据,data_len要发送的数据长度
uint8_tbsp_can1_send_msg(uint32_tid_type,uint32_tbasic_id,uint32_tex_id,uint8_t*data,uint32_tdata_len)\n{\nuint8_tindex=0;\nuint32_t*msg_box;\nuint8_tsend_buf[8]={0};\nCAN_TxHeaderTypeDefsend_msg_hdr;\nsend_msg_hdr.StdId=basic_id;\nsend_msg_hdr.ExtId=ex_id;\nsend_msg_hdr.IDE=id_type;\nsend_msg_hdr.RTR=CAN_RTR_DATA;\nsend_msg_hdr.DLC=data_len;\nsend_msg_hdr.TransmitGlobalTime=DISABLE;\nfor(index=0;index<data_len;index++)\nsend_buf[index]=data[index];\n\nHAL_CAN_AddTxMessage(&hcan1,&send_msg_hdr,send_buf,msg_box);\nreturnBSP_CAN_OK;\n}\n
我们在main函数中1s发送一帧,标准帧跟扩展帧交叉调用,代码如下:
send_data[0]++;\nsend_data[1]++;\nsend_data[2]++;\nsend_data[3]++;\nsend_data[4]++;\nsend_data[5]++;\nsend_data[6]++;\nsend_data[7]++;\nif(id_type_std==1)\n{\nbsp_can1_send_msg(CAN_ID_STD,1,2,send_data,8);\nid_type_std=0;\n}\nelse\n{\nbsp_can1_send_msg(CAN_ID_EXT,1,2,send_data,8);\nid_type_std=1;\n}\nHAL_Delay(1000);\n
我们通过CAN协议分析仪来抓下结果
1.5编写轮询接收函数
uint8_tbsp_can1_polling_recv_msg(uint32_t*basic_id,uint32_t*ex_id,uint8_t*data,uint32_t*data_len)\n{\nuint8_tindex=0;\nuint8_trecv_data[8];\nCAN_RxHeaderTypeDefheader;\n\nwhile(HAL_CAN_GetRxFifoFillLevel(&hcan1,CAN_RX_FIFO0)!=0)\n{\nif(__HAL_CAN_GET_FLAG(&hcan1,CAN_FLAG_FOV0)!=RESET)\nprintf(&34;);\n\nHAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO0,&header,recv_data);\nif(header.IDE==CAN_ID_STD)\n{\nprintf(&34;,header.StdId);\n}\nelse\n{\nprintf(&34;,header.ExtId);\n}\nprintf(&34;,header.IDE);\nprintf(&34;,header.RTR);\nprintf(&34;,header.DLC);\nprintf(&34;);\nfor(index=0;index<header.DLC;index++)\n{\nprintf(&34;,recv_data[index]);\n}\nprintf(&34;);\n}\n}\n
实验一总结:
1.没用调用HAL_CAN_Start(&hcan1);使能CAN
2.没有编写Filter函数,我开始自认为不设置就默认不过滤,现在看来是我想多了,其实想想也合理,你如果不过滤分配FIFO,STM32怎么决定把收到的放到哪个FIFO中
待提升:
1.目前只用到FIFO0,待把FIFO1使用起来2.Normal模式测试500K波特率(定时发送,中断接收)
2.1CubeMx配置
步骤2,3,4跟polling完全一致,我们来直接说下中断怎么用(主要是使能notifity就行了)
staticvoidMX_CAN1_Init(void)\n{\n/*USERCODEBEGINCAN1_Init0*/\n/*USERCODEENDCAN1_Init0*/\n/*USERCODEBEGINCAN1_Init1*/\n/*USERCODEENDCAN1_Init1*/\nhcan1.Instance=CAN1;\nhcan1.Init.Prescaler=12;\nhcan1.Init.Mode=CAN_MODE_NORMAL;\nhcan1.Init.SyncJumpWidth=CAN_SJW_1TQ;\nhcan1.Init.TimeSeg1=CAN_BS1_4TQ;\nhcan1.Init.TimeSeg2=CAN_BS2_2TQ;\nhcan1.Init.TimeTriggeredMode=DISABLE;\nhcan1.Init.AutoBusOff=ENABLE;\nhcan1.Init.AutoWakeUp=ENABLE;\nhcan1.Init.AutoRetransmission=DISABLE;\nhcan1.Init.ReceiveFifoLocked=DISABLE;\nhcan1.Init.TransmitFifoPriority=DISABLE;\nif(HAL_CAN_Init(&hcan1)!=HAL_OK)\n{\nError_Handler();\n}\n/*USERCODEBEGINCAN1_Init2*/\nbsp_can1_filter_config();\nHAL_CAN_Start(&hcan1);\nHAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING);\n/*USERCODEENDCAN1_Init2*/\n}\n
下面我们来编写下中断函数
voidHAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef*hcan)\n{\nuint8_tindex=0;\nuint8_trecv_data[8];\nCAN_RxHeaderTypeDefheader;\n\nHAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO0,&header,recv_data);\nif(header.IDE==CAN_ID_STD)\n{\nprintf(&34;,header.StdId);\n}\nelse\n{\nprintf(&34;,header.ExtId);\n}\nprintf(&34;,header.IDE);\nprintf(&34;,header.RTR);\nprintf(&34;,header.DLC);\nprintf(&34;);\nfor(index=0;index<header.DLC;index++)\n{\nprintf(&34;,recv_data[index]);\n}\nprintf(&34;);\n}
另外,整理了一些电子工程类的资料,分享给大家,目前有模拟电路、单片机、C语言、PCB设计、电源相关、FPGA、EMC、物联网、Linux相关学习资料,还有针对大学生的资料包,后续还会有更多资料分享给大家,助力大家学习,成就梦想~
博主福利:点击链接免费获取电子工程类学习资料「链接」
关于小说网站源码分享tp的内容到此结束,希望对大家有所帮助。
