STM32------CAN通讯协议
CAN协议简述
- CAN—Controller Area Network(控制器局域网),由Bosch开发的一种面向汽车的通信协议,这是目前应用最广泛的通信协议,更是尤其是在汽车电子行业。CAN的开发不仅极大增强了汽车电子系统的安全性和稳定性,也减少了汽车电子系统内线束数量
- 下面我们仅重点叙述CAN在物理层和数据链路层的规定。
物理层
-
CAN协议经过ISO标准化后有ISO11898和ISO11519-2两种标准。其区别仅于物理层不同,ISO11898是CAN闭环高速通信标准,而ISO11519-2是开环低速通信标准,下面仅重点叙述IS011898物理层特性。 -
为了增强总线抗干扰能力和安全性,我们来看下CAN总线在物理层上做的工作。 -
在物理层上,CAN总线定义了CAN_High和CAN_Low两条差分信号线,CAN总线的逻辑电平为两条线的电位差(CAN_High - CAN_Low)。那么这样定义会带来什么好处呢?在工业现场中,很多干扰都是共模干扰,如电磁干扰、机械振动等。当外界环境的共模干扰施加到CAN总线上时,CAN采用差分信号线,使得共模干扰得以抵消,不会影响CAN总线逻辑。这就从物理层上提高了总线的稳定性和安全性。 下图为ISO11898标准CAN协议拓扑图 -
在CAN中采用CAN_High和CAN_Low两线的电位差来表示逻辑0和1,但与一般正逻辑不同,在CAN中采用负逻辑表示"显性电平"和“隐性电平”,总线必须处于二者之中,显性电平为“0”,隐性电平为“1”。在总线上使用“线与"逻辑,即当连接在总线上的n个单元中,只要有一个单元为表现为显性电平0,整个总线上表现为显性电平0;所有的单元表现为隐性电平1时,总线采表现为隐性电平1。这就是“显性电平”和“隐性电平”。 -
ISO11898标准规定CAN总线拓扑结构为闭环,所有单元以并联的形式连接到网络中,并规定总线两段串联两个85-130Ω电阻。 此外,CAN总线同样为正确逻辑电平的表达提供了一定的容许误差,下表即为ISO11898标准两差分线电位和总线电平关系: -
从上表可以看出,电位差为0V时,表现为隐形电平1;电位差为2V时,为显性电平1。 -
由于ISO11519-2通信标准与ISO11898通信标准类似,就不再赘述,下面提供一个表格表示两种通信标准物理层差异:
物理层 | ISO11898 | ISO11519-2 |
---|
通信速度 | 125Kbps-1Mbps | 125Kbps | 总线长度 | 40m以下 | 1KM以下 | 连接单元数 | 不超30 | 不超20 | 阻抗 | 120Ω | 120Ω | 总线形式 | 闭环 | 开环 | 终端电阻 | 120Ω | 2.2kΩ |
位时序
同样为了提高数据传输的稳定性和安全性,CAN协议在位时序上也做了相应工作,CAN协议规定每一位由下面四段组成:
- 同步段(SS:Synchronization Segment),多个连接在总线上的单元通过此段实现时序调整,达到同步收发的目的。起始信号边沿变化正常情况下会出现在此段中。
- 传播时间段(PTS:Propagation Time Segment),用于平衡网络上的物理延迟,即发送单元输出延迟、总线信号传输延迟。该段时间为延时时间和的二倍。
- 相位缓冲段1(PBS1:Phase Buffer Segment 1),当信号边沿即电平变化的那个时刻不是出现在同步段中时,总线上各单元信号因为细微累计误差导致不同步,可以在该段进行补偿,具体如何补偿将在后面叙述。在PSB1段结束时刻即为采样时刻,采样时刻电平即为该位的电平。
- 相位缓冲段2(PBS2:Phase Buffer Segment 2),与PBS1段一起补偿信号误差。
- 再同步补偿宽度(SJW: Resynchronization Jump Width),该段不是位时序组成段,它是信号同步补偿限度,即当进行同步补偿时,每次补偿不能超过这个限度。
上面的SS、PTS、PSB1、PBS2、四段共同组成一个位,而这四段又由若干个最小时间单位Time Quantum(Tq)组成,CAN协议正是通过设置Tq大小,和每段Tq数量来设置通信速率和采样点。下表给出了每段Tq的数量范围:
段名称 | Tq数 |
---|
SS | 1 | PTS | 1~8 | PBS1 | 1~8 | PBS2 | 2~8 | SJW | 1~4 | 总段 | 8~25 |
由于波特率是每秒传输的位数,所以:波特率=1/(8~25)Tq。下图是10Tq的位时序。
位时序同步方法
CAN协议为了保证通信的稳定性和可靠性,在时序上也做了很多工作保证收发端时序相同。CAN协议采用硬件同步和再同步来对信号进行同步调整。所谓信号同步,就是保证发送单元的时序和接收单元的时序相同,从而保证采样时刻位置不发生变动,进而保证采样的准确性。
硬件同步
硬件同步是接收单元在总线空闲时检测出起始信号(电平跳变)时进行的调整,在该段认为是SS段,即位同步段。注意:硬件同步只是进行了一个起始信号检测并将此段认为是SS段,并没有做同步补偿。硬件同步过程如图所示。 上方的时序为发送单元时序,下方为接收单元时序,接收单元检测到电平变化时,就将此刻作为SS段,从中可见这时由于延时等误差已经造成发送单元时序和接收单元时序有所不同了。
再同步
- 再同步就是在接收过程中检测总线上的电平变化时进行的同步调整。每当检测出边沿时,通过加长PBS1段或缩短PBS2段来同步调整,但如果误差超过了SJW时,最大调整量为SJW。下图是增加PBS1段做的同步调整。
- 蓝色线表示的是发送端的时序,红色表示的是接收端的时序变化,由于接收端提前进入SS段,而发送端延迟2Tq才进入SS段,所以对于接收端来说,如果不进行同步,其采样点在黑色箭头处,但发送端延迟2Tq,导致发送端的采样点也延迟2Tq,其采样点在绿色箭头处。所以,接收端为了保证时序同步,在PBS1处插入2Tq的时间点,保证收发端采样点相同。这是电平跳变发生在接收端的SS-PB1段的同步方式,下面来分析一下电平跳变发生在PBS2-SS段的同步方式。
上图中蓝色表示发送端时序,红色表示接收端时序,由于延迟等因素,导致发送端起始信号有2Tq的延迟,绿色为发送端时序对应的采样点,黑色为接收端时序不做调整下对应的采样点。接收端为了保证时序同步,在PBS2段减去2Tq时间段,从而使收发端采样点在同一时刻。
数据格式
若干个最小时间量子Tq构成了1个位时序,若干个位时序组合又构成了消息帧。 CAN协议规定了以下四种帧
帧 | 作用 |
---|
数据帧 | 发送单元向接收单元传送的数据 | 遥控帧 | 接收单元向发送单元请求数据 | 错误帧 | 检测出错误时向其他单元通知错误 | 过载帧 | 接收单元通知发送单元尚未做好接收准备 |
数据帧
在CAN协议中有标准格式和扩展格式两种数据帧格式,扩展格式在标准格式前增加了一些位来扩展ID,其他区别不大。下图是标准格式时序图。
- 在标准格式中,以一个显性位表示帧起始(SOF)。
- 12位表示仲裁段。其中11位表示ID,RTR用于区分数据帧和遥控帧,当RTR为显性电平时为数据帧,反之为遥控帧。注意ID仅表示的是该帧的优先级,并不是帧对应发送的地址,因为在CAN中,数据是以广播的形式发送的,即挂载在总线上所有单位均能接收到这条消息,后续会说明接收单位如何分辨这些消息到是有用还是无用的。这里只需要知道ID表示的是优先级,而且ID小的优先级高。
+6位的控制段,IDE用于区分标准格式和扩展格式,如果ID显性电平表示标准格式,隐性电平表示扩展格式。DLC四位用于表示后面数据段的数据长度,数据长度范围为0-8byte。 - 64位的数据内容,即每个消息帧最多可以发送8byte的数据。
- 16位的CRC校验段,15位用于CRC校验,最后一位用以界定CRC段和ACK段
- 2位的ACK段,用以回应是否接收到正确数据。
- EOF为帧结束段
在扩展格式在标准格式的基础上将11位ID扩大到29位,其余二者区别不大,在此不再赘述。
遥控帧
遥控帧同样有标准格式和扩展格式两种,与数据帧相比,遥控帧仅仅是将RTR位设置为隐性电平,并将数据帧去掉,其余并无差异。下图为遥控帧标准格式时序图,遥控帧的时序不再分析。
错误帧
错误帧由6位的错误标志和8位的错误界定符组成,错误标志分为主动错误标志和被动错误标志两种,当为主动错误标志时,这6个位均为显性位;反之,均为隐性位。错误界定符由8个隐性位组成。下图为错误帧示意图。
下面来介绍一下主动错误和被动错误和总线关闭。 单元必定处于主动错误、被动错误和总线关闭三种状态之一。
主动错误
主动错误就是单元可以正常参加总线通信的状态,处于主动错误状态的单元检测到错误时输出主动错误标志。主动错误状态相当于单元不会出错或偶尔出错,即是偶尔出错也能容忍,所以拥有发言权和参会权。
被动错误
被动错误状态是容易引起错误的状态。处于这种状态的单元也能照常参加总线通信,但为了不妨碍总线正常通信,该单元不能发送错误通知,并且即是检测出错误,但如果其他处于主动错误状态的单元没有检测到错误,仍旧认为整个总线没有错误。也就是说该单元经常性出错,但仍旧能工作,所以为了保证其他单元正常通信,把该单元发言权给剥夺了,仍有参会权。
总线
由于单元一直出错,则达到一定次数后,该单元连参会权都没有,自闭了。
三种状态切换条件
下图是单元三种状态切换流程图
- TEC<=127 && REC<=127,为主动错误状态
- (TEC>127 &&TEC<=255)||(REC>127 &&REC<=255),为被动错误状态
- TEC>255,为总线关闭状态
过载帧
由6位过载标志位和8位过载界定符组成,当过载标志位均为显性位时表示过载,过载界定符由8个隐性位构成。
数据链路层
数据链路层将物理层的信号组织为有一定格式的数据,完成控制数据传输,错误控制等流程,如消息的帧化、仲裁、错误检测、错误报告、应答等。 因为数据链路层的很多东西都已经在前面介绍过了,所以就不再赘述,给出数据链路层的基本参照模型。 要注意的是,CAN协议是一种半双工通信协议,所有单元按照并联方式接入总线,没有主机和从机之分,所有单元均可以竞争总线,总线的竞争结果通过仲裁器仲裁,按照ID号的优先级决定,并且当有一个单元竞争总线成功后,则其他单元均变为接收模式,准备接收数据。
到这里就把CAN协议主要内容就叙述完了,接下来我们叙述一下STM32-bxCAN外设。
STM32-bxCAN简述
bxCAN是基本扩展CAN(Basic Extended CAN)缩写,其设计目标是以最小的CPU负荷来高效处理大量报文,支持报文发送的优先级要求。
bxCAN特点
- 最高波特率为1Mbps
- 支持时间出发通信功能
- 具有3个发送邮箱
- 2个3级深度的FIFO,最多可以存放6个消息帧
- f103具有14个可变过滤器组,并且每组过滤器可配置为列表模式和掩码模式
下图是bxCAN框架
发送信箱及接收FIFO
发送邮箱
由上面的bxCAN框架可知,bxCAN有3个发送邮箱,bxCAN在发送报文时,只需要设置邮箱格式(标识符,数据长度,待发送数据),然后将寄存器CAN_TIxR中的TXRQ位置1,来请求发送。之后该邮箱进入挂号状态,直到成为最高优先级的邮箱后,待总线空闲,报文则马上就会被发送,发送完毕后则该邮箱变为空邮箱。下图是发送邮箱状态图。
- TXRQ=1,表示请求发送报文
- ABRQ=1,表示报文发送完成
- IDLE 表示空闲状态
- TXOK=1,表示发送成功
接收FIFO
bxCAN有两个接收FIFO,分别为FIFO1和FIFO2,每个FIFO最多可以存放3个报文,报文的存放由硬件自动管理。当接收到第一个报文时,报文会暂存在FIFO中,FIFO变为挂号_1等待软件读取报文,释放邮箱。如果此时又有新的报文到来,则FIFO变为挂号_2。下图为接收FIFO状态转换图。 如果FIFO变为挂号_3状态,则如果有新的报文到来,就会导致该报文或原来报文丢失。究竟哪个报文会丢失取决于CAN_MCR寄存器的RFLM位。
- RFLM=1,最后收到的报文会被新的报文覆盖,新的报文被保留下来
- RFLM=0,已经接收到的报文不变,新报文被丢弃。
过滤器
我们前面说过,CAN协议里没有地址区分,也没有主从设备区别,各单元以并联形式连接到总线上,没有高低之分的,信息通过广播的形式发送给总线上的所有接收设备。那么,接收设备如何判断该报文是有效信息还是无效信息呢? 其实,在CAN中,ID既表示了优先级,同样也表示信息身份。在软件上,我们可以通过判断ID来决定是否接收该消息,但这样就会占用CPU,bxCAN设计目的就是用最小的CPU负荷来处理大量数据,所以STM32为我们提供了标识符过滤器。在stm32f103系列中共有14个可配置的过滤器组,每个过滤器由2个32位寄存器组成,分别是CAN_FxR1和CAN_FxR2,而这两个32位的寄存器又可以配置其位宽为16位还是32位,因此每个过滤器可以提供1个32位过滤器或2个16位过滤器。除此之外,每组过滤器工作模式可以配置为掩码模式或标识符列表模式。 所谓“掩码模式”,其实就相当于关键字检索,把带有符合条件的关键字的报文存入FIFO中,其余的过滤掉。“标识符列表模式”就相当于建立了一个数据库,如果该报文在建立的数据库中能全字匹配找到,则接收报文存入FIFO中,否则舍弃。 具体过滤器位宽配置和过滤器组工作模式关系看下图
- FSCx位是过滤器位宽设置位,FSCx=1为32位过滤器,FSCx=0为16位过滤器。
- FBMx为是过滤器模式设置位,FBMx=0为掩码模式(标识符屏蔽),FBMx=1为标识符列表模式
- STID为标准标识符,EXID为扩展标识符。IDE为标识符选择,IDE=0,使用标准标识符;IDE=1,使用扩展标识符。
那么上面这个表格什么意思呢?下面以FSCx=1,FBMx=0为例介绍掩码模式的筛选方法。 当设置为掩码模式时,CAN_FxR1表示标识符,CAN_FxR2用来表示哪些位可以屏蔽哪些位不能屏蔽,当某一位为1时,则该位不能屏蔽,否则该位被屏蔽,举个例子。
ID(CAN_FxR1) | 1 0 1 1 1 1 0 0 1······(32位) |
---|
掩码(CAN_FxR2) | 1 0 1 0 0 1 1 1 1······(32位) | 要筛选的ID | 1 x 1 x x 1 0 0 1······(32位) |
FSCx=0,FBMx=1为例介绍标识符列表模式
ID(CAN_FxR1) | 1 0 |
---|
ID(CAN_FxR1) | 0x1234 | ID(CAN_FxR2) | 0x2345 | ID(CAN_FxR3) | 0x3456 | ID(CAN_FxR4) | 0x4567 | 要筛选的ID | 须是0x1234、0x2345、0x3456、0x4567中的一个 |
bxCAN测试模式
bxCAN有四种测试模式,分别为静默模式、回环模式、回环静默模式和调试模式。一般在调试的时候,先用CAN的测试模式调试,调试完毕后,在更改为正常模式。
静默模式
模式框图 静默模式就是CAN能够接收总线消息,但是不能向总线发送消息,因为bxCAN要发送显性位时,显性位可以被CAN内部自己检测到,但不会传输到总线上,也就相当于CAN“哑巴”了。这种模式常被用来分析CAN总线活动。
回环模式
回环模式框图 回环模式可用于自测,在该模式下bxCAN在内部把Tx输出回馈到Rx上。相当于“聋了”
静默回环模式
模式框图 这种模式用于热自测,即完全与总线断开通信,但可以接收到自己发送的信号。
bxCAN时序
bxCAN的位时序与CAN协议规定的位时序稍有不同,下图为bxCAN位时序。 bxCAN将CAN标准的4段位时序简化为3段,bxCAN把PTS段和PBS1段合并为BS1段,其余没有变化。 则
- 位时间=Tq+Tbs1+Tbs2
- Tq = Tpclk*分频因子,Tpclk=APB1时钟频率
- 波特率=1/位时间
bxCAN部分寄存器
寄存器我们需要注意的一点是关于位时序配置的,stm32给出的时间计算公式为如下所示 注意这里TS1、TS2、BRP这些位均在后面有加一操作,在利用库函数配置时需要注意。
STM32-CAN代码编写要点
实验平台:野火stm32f103霸道开发板(同系列最小系统板也可以,因为是自测模式) 实验目的:利用回环测试模式实现CAN收发,使用CAN中断进行数据的读取
GPIO配置(GPIO不配置同样可以做CAN测试实验)
GPIO部分配置较简单,CAN引脚配置根据官方手册,CAN_Tx引脚配置为复用推挽输出,CAN_Rx引脚配置为浮空输入。这里对引脚进行了重映射,将其映射到PB8和PB9上。注意开启相应时钟。代码如下
void CAN_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_APB2PeriphClockCmd (RCC_APB2Periph_GPIOB,ENABLE);
RCC_APB2PeriphClockCmd (RCC_APB2Periph_AFIO,ENABLE);
RCC_APB1PeriphClockCmd (RCC_APB1Periph_CAN1,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING ;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_Init (GPIOB,&GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP ;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9;
GPIO_Init (GPIOB,&GPIO_InitStruct);
GPIO_PinRemapConfig (GPIO_Remap1_CAN1,ENABLE);
}
CAN模式配置
CAN初始化结构体:
typedef struct
{
uint8_t CAN_Mode;
FunctionalState CAN_TTCM;
FunctionalState CAN_ABOM;
FunctionalState CAN_AWUM;
FunctionalState CAN_NART;
FunctionalState CAN_RFLM;
FunctionalState CAN_TXFP;
uint8_t CAN_BS1;
uint8_t CAN_BS2;
uint8_t CAN_SJW;
uint16_t CAN_Prescaler;
} CAN_InitTypeDef;
- CAN_Mode 用于配置CAN模式,可选参数如下:
- CAN_Mode_Normal ,正常模式,回环自测结束后可使用该模式即可与其他设备通信
- CAN_Mode_LoopBack,回环模式,用于自测
- CAN_Mode_Silent,静默模式,用于分析CAN总线活动
- CAN_Mode_Silent_LoopBack,回环静默,可用于自测
- CAN_TTCM 用于设置是否使用时间触发功能,ENABLE 或 DISABLE
- CAN_ABOM 用于设置是否启用自动离线管理功能,单元出错后适时自动恢复,一般使能
- CAN_AWUM 用于设置是否启动自动唤醒功能,一般使能
- CAN_NART 用于设置是否自动重传,即报文发送失败后一直发送。
- CAN_RFLM 用于设置是否锁定接收FIFO,锁定FIFO后,若FIFO填满则丢弃新报文。不锁定FIFO,则丢弃最后接收的报文。
- CAN_TXFP 用于设置报文优先级判断方法,使能则按照报文存放邮箱的先后顺序发送报文,否则按照ID优先级发送报文,一般不使能。
- CAN_BS1 用以设置bxCAN位时序中的BS1段,可选参数有CAN_BS1_1tq~CAN_BS1_16tq
- CAN_BS2 用以设置bxCAN位时序中的BS2段,可选参数有CAN_BS2_1tq~CAN_BS2_8tq
- CAN_SJW 用以设置同步补偿限度,可选参数有CAN_SJW_1tq~CAN_SJW_4tq
- CAN_Prescaler 用以设置CAN时序时钟分频因子
波特率配置(1Mbps):
- BS1段设置为4tq ,
- BS2段设置为3tq,
- 位时间=1tq+tBS1+tBS2=9tq;
- 设置分频因子CAN_Prescaler =4;
- CAN时钟频率=36M/4=9M, tq=1/(9M),9tq=1/M
- 波特率 = 1/(9tq) = 1Mbps
经过上述分析,该部分代码如下:
void CAN_Mode_Config(void)
{
CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.CAN_ABOM = ENABLE;
CAN_InitStruct.CAN_AWUM = ENABLE;
CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack;
CAN_InitStruct.CAN_NART = DISABLE;
CAN_InitStruct.CAN_RFLM = ENABLE;
CAN_InitStruct.CAN_TTCM = DISABLE;
CAN_InitStruct.CAN_TXFP = DISABLE;
CAN_InitStruct.CAN_BS1 = CAN_BS1_5tq;
CAN_InitStruct.CAN_BS2 = CAN_BS2_3tq;
CAN_InitStruct.CAN_SJW = CAN_SJW_2tq;
CAN_InitStruct.CAN_Prescaler = 4;
CAN_Init (CAN1,&CAN_InitStruct);
}
过滤器配置
typedef struct
{
FunctionalState CAN_FilterActivation;
uint8_t CAN_FilterMode;
uint8_t CAN_FilterScale;
uint8_t CAN_FilterNumber;
uint16_t CAN_FilterFIFOAssignment;
uint16_t CAN_FilterIdHigh;
uint16_t CAN_FilterIdLow;
uint16_t CAN_FilterMaskIdHigh;
uint16_t CAN_FilterMaskIdLow;
} CAN_FilterInitTypeDef;
- CAN_FilterActivation 使能过滤器,可配置为ENABLE或DISABLE
- CAN_FilterMode 用于配置过滤器工作模式,有以下参数
- CAN_FilterMode_IdMask 过滤器工作在掩码模式
- CAN_FilterMode_IdList 过滤器工作在标识符列表模式
- CAN_FilterScale 用于配置过滤器位宽,可选以下参数
- CAN_FilterScale_16bit, 位宽为16位
- CAN_FilterScale_32bit, 位宽为32位
- CAN_FilterNumber 用于选择过滤器组,可选0~13组
- CAN_FilterFIFOAssignment 选择过滤后存放于那个FIFO中,可选参数如下
- CAN_Filter_FIFO0 存放于FIFO0中
- CAN_Filter_FIFO1 存放于FIFO1中
- CAN_FilterIdHigh 用以配置CAN_FxR1中高16位
- CAN_FilterIdLow 用以配置CAN_FxR1中低16位
- CAN_FilterMaskIdHigh 用以配置CAN_FxR2中高16位
- CAN_FilterMaskIdLow 用以配置CAN_FxR2中低16位
过滤器FxRx配置方法: 以FxR1为例,假设过滤器配置为32位,ID为扩展模式,FxR1映像如下图所示: 所以对于扩展ID模式而言,FxR1[31:3]与ID[28:0]一 一对应,所要过滤的ID需要先向左移3位,即
ID=ID<<3.
IDE用于选择ID是标准格式还是扩展格式,RTR用于区别信息是遥控帧还是数据帧。这里直接使用官方定义的宏。 所以
FxR1 = (ID<<3)|CAN_ID_EXT|CAN_RTR_DATA
因为FxR1由FilterIdHigh和FilterIdLow两个16位寄存器组成,所以
FilterIdHigh= ( ( (ID<<3)|CAN_ID_EXT|CAN_RTR_DATA ) & 0xFFFF0000 ) >> 16
FilterIdLow= ( ( (ID<<3)|CAN_ID_EXT|CAN_RTR_DATA ) & 0x0000FFFF)
这样FxR1就配置完成了,要想配置FxR2则按照同样的方法配置CAN_FilterMaskIdHigh和CAN_FilterMaskIdLow即可。其他模式的配置同样如此
CAN中断
当CAN接收到一个消息帧存入FIFO中的时候,产生中断,在中断中读取FIFO中的数据。当满足下列条件时可以产生中断:
- CAN_IT_TME ,发送邮箱空中断
- CAN_IT_FMP0,FIFO0消息挂号中断
- CAN_IT_FMP1,FIFO1消息挂号中断
- CAN_IT_FF0 ,FIFO0满中断
- CAN_IT_FF1 ,FIFO1满中断
- CAN_IT_FOV0,FIFO0溢出中断
- CAN_IT_FOV1 ,FIFO1溢出中断
- CAN_IT_EWG ,错误警告中断
- CAN_IT_EPV ,错误被动中断
- CAN_IT_BOF,离线中断
- CAN_IT_LEC ,上次错误信号中断
- CAN_IT_ERR,错误中断
- CAN_IT_WKU ,唤醒中断
- CAN_IT_SLK ,睡眠中断
则因为当消息存入FIFO中时,FIFO将会处于挂号状态,等待消息被读取,所以这里我们使用的中断产生条件为CAN_IT_FMP0。 在官方提供的库函数中,定义接收数据结构体和发送数据结构体,这两个结构体均是按照CAN标准帧格式定义的。
typedef struct
{
uint32_t StdId;
uint32_t ExtId;
uint8_t IDE;
uint8_t RTR;
uint8_t DLC;
uint8_t Data[8];
uint8_t FMI;
} CanRxMsg;
typedef struct
{
uint32_t StdId;
uint32_t ExtId;
uint8_t IDE;
uint8_t RTR;
uint8_t DLC;
uint8_t Data[8];
} CanTxMsg;
在发送数据时只需要调用函数 CAN_Transmit (),就能完成自动发送,并将数据定义的结构体变量地址传入。 同样接收数据调CAN_Receive()即可。 其余配置较为简单不再赘述。附代码如下:
void CAN_NVIC_Config(void)
{
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = USB_LP_CAN1_RX0_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStruct.NVIC_IRQChannelSubPriority =2;
CAN_ITConfig (CAN1,CAN_IT_FMP0,ENABLE);
NVIC_Init (&NVIC_InitStruct);
}
void USB_LP_CAN1_RX0_IRQHandler(void)
{
CAN_Receive (CAN1,CAN_FIFO0,&CAN_Recivee_Buffer);
CAN_Flag = 1;
CAN_ClearFlag (CAN1,CAN_FLAG_FMP0);
}
主函数
主函数完成的功能是,初始化按键和CAN,当按下KEY1时,发送一帧数据,并打印第一位数据,按下KEY2时发送另一帧数据,并打印第一位数据。代码逻辑较简单,不在赘述,附代码如下
int main(void)
{
u8 box;
SystemInit();
delay_init();
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
LED_GPIO_Config();
Key_GPIO_Config();
uart_init(115200);
CAN1_Init();
while(1)
{
if (Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == KEY_ON)
{
CAN_Transmit_Buffer.StdId = 0;
CAN_Transmit_Buffer.ExtId = PASS_ID;
CAN_Transmit_Buffer.Data[0] = 111;
CAN_Transmit_Buffer.IDE = CAN_Id_Extended;
CAN_Transmit_Buffer.RTR = CAN_RTR_Data;
CAN_Transmit_Buffer.DLC = 1;
box = CAN_Transmit (CAN1,&CAN_Transmit_Buffer);
while(CAN_TransmitStatus (CAN1,box) != CAN_TxStatus_Ok);
}
if (Key_Scan(KEY2_GPIO_PORT,KEY2_GPIO_PIN) == KEY_ON)
{
CAN_Transmit_Buffer.StdId = 0;
CAN_Transmit_Buffer.ExtId = 121;
CAN_Transmit_Buffer.Data[0] = 121;
CAN_Transmit_Buffer.IDE = CAN_Id_Extended;
CAN_Transmit_Buffer.RTR = CAN_RTR_Data;
CAN_Transmit_Buffer.DLC = 1;
box = CAN_Transmit (CAN1,&CAN_Transmit_Buffer);
while(CAN_TransmitStatus (CAN1,box) != CAN_TxStatus_Ok);
}
if (CAN_Flag == 1)
{
printf("CAN_Receive = %d\n",CAN_Recivee_Buffer.Data[0]);
CAN_Flag = 0;
}
}
}
附:can完整代码
https://download.csdn.net/download/dhejsb/21410062?spm=1001.2014.3001.5501
|