一、简介
1.1 简介
CAN总线(Controller Area Network Bus)控制器局域网总线,由BOSCH公司开发的一种简介易用、传输速度快、易扩展、可靠性高的通信总线,广泛应用于汽车、嵌入式、工业控制等领域
特点:
<1>两根通信线(CAN_H,CAN_L),线路少,无需共地;
<2>差分信号通信,抗干扰能力强
<3>高速CAN(ISO11898):1125k~1Mbps,<40m
<4>低速CAN(ISO11519):10k~125kbps,<1km
<5>异步(无需时钟线,通信速率设备各自约定),串行,半双工(可挂在多设备,多设备同时发送数据时通过仲裁判断先后顺序)
<6>11位(标准格式)/29位(扩展格式)报文ID,用于区分消息功能,同时决定优先级
<7>可配置1~8字节的有效荷载
<8>可实现广播式(最常用)和请求式两种传输方式
<9>应答、CRC校验、位填充、位同步、错误处理等特征
注意:虽然差分信号各设备不共地可以使用,但是有可能使收发器承受较高的共模电压,为了安全与稳定,CAN通信一般还是会共地,完整的CAN通信线路需要VCC,GND,CAN_H,CAN_L
1.2 高低速CAN
每个设备通过CAN收发器接收挂载在CAN总线网络上
高速CAN使用闭环网络,CAN_H,CAN_L两端添加120Ω的终端电阻
120Ω作用:
① 减少回波反射
②当总线空闲时,终端电阻可以将CAN_H和CAN_L电平保持一致,即电压差为0V
低速CAN使用开环网络,CAN_H,CAN_L其中一端添加2.2KΩ的终端电阻
传输速度越快,传输距离越短
传输速度越慢,传出距离越长
总线的传输速率:
又称为总线的通信速率,指的是位速率/比特率(和波特率不是一回事),
表示的是:单位时间内,通信线路上传输的二进制位的数量,其基本单位是 bps 或 b/s (bit per second
1.3 电平规约
CAN总线采用差分信号,即两线电压差(Vcan_h-Vcan_l)传输数据位
高速CAN规定:
电压差为0V时表示逻辑1(隐性电平)
电压差为2V时表示逻辑0(显性电平)两线收紧,没有电压差,是默认状态,所以为隐性
两线张开,产生电压差,是需要设备干预的状态,所以为显性
低速CAN规定
电压差为-1.5V时表示逻辑1(隐性电平)
电压差为3V时表示逻辑0(显性电平)
CAN总线网络中,CAN总线上的一个终端设备称为一个节点(Node),在CAN网络中,没有主设备和从设备的区别。
一个CAN节点的硬件部分一般由CAN控制器和CAN收发器两个部分组成。
CAN控制器负责CAN总线的逻辑控制,实现CAN传输协议;
CAN收发器主要负责MCU逻辑电平与CAN总线电平之间的转换
二、帧格式
2.1 帧种类
数据帧和遥控帧有标准格式和扩展格式两种格式
2.1.1 数据帧
在CRC界定符时发送放必须置隐性电平1,后跟ACK槽,若为显性0,说明有接收方应答,该位有接收方控制
2.1.2 遥控帧
遥控帧的RTR位为1,却没有数据段,其他与数据帧相同
当设备想要接收数据时,会广播出一个带有ID报文的遥控帧,若某个设备有这个ID的数据,这个设备将会广播出这个ID数据,接收方就会及时获取这个数据
请求式传输,没传输一次数据需要一来一回两个过程,适用于使用频率低的数据
2.1.3 错误帧
总线上所有设备都会监督总线上的数据,一旦发现了“位错误”或“填充错误”或“CRC错误”或“格式错误”或“应答错误”,这些设备便会发出错误帧来破坏数据,同时终止当前的发送设备
错误分为主动错误与被动错误,设备默认处于主动错误状态
处于主动错误状态的设备检测出错误时,这个设备会连续发六个显性电平来破坏正常传输的数据,其他设备检测到错误标志就会抛弃这个数据,若主动错误产生太频繁,这个设备就会进入被动错误状态
处于被动错误状态的设备检测出错误时,会连续发六个隐性位,表示不去碰总线,不碰总线就不会破坏总线别人的数据,但会破坏自己发的数据(6个相同的电平)
2.1.4 过载帧
当接收方收到大量数据而无法处理时,其可以发出过载帧,延缓发送方的数据发送,以平衡总线负载,避免数据丢失 (6个相同的电平)
2.1.5 帧间隔
帧间隔是用于分隔数据帧和遥控帧的帧。数据帧和遥控帧可通过插入帧间隔将本帧与前面的任何帧(数据帧、遥控帧、错误帧、过载帧)分开,过载帧和错误帧前不能插入帧间隔。
主动错误状态的帧间隔是3个隐性位
被动错误状态的帧间隔在3个隐性位的帧间隔后再加8个隐性位的延迟传送
2.2 位填充
防止突发错误而设定的功能。当同样的电平持续5位时则添加一个位的反型数据
<1>增加波形的定时信息,利于接收方执行“再同步”,防止波形长时间无变化,导致接收方不能精确掌握数据采样时机
<2>将正常数据流与“错误帧”,“过载帧”区分
<3>保持CAN总线在发送正常数据流时的活跃状态,防止被误认为总线空闲
发送单元:
在发送数据帧和遥控帧时,SOF~CRC段间的数据,相同电平如果持续5位,在下一个位(第6个位)则要插入1位与前5位反型的电平。
接收单元:
在接收数据帧和遥控帧时,SOF~CRC段间的数据,相同电平如果持续5位,需要删除下一个位(第6个位)再接收。
如果这个第6个位的电平与前5位相同,将被视为错误并发送错误帧。CRC校验时需要剔除填充位
2.3 波形分析
波形一

S0F=0;
11位ID:0x555
RTR=0;表示数据帧
IDE=0;表示标准格式
r0=0; 保留位
DLC=0x1;表示负载为1个字节 有1个位填充
Data=0xAA
15位CRC 有1个填充位
CRC界定符=1(发送方释放总线)
ACK应答槽,用于接收方应答
ACK界定符=1(接收方释放总线)
波形二

S0F=0;
11位ID:0x666
RTR=0;表示数据帧
IDE=0;表示标准格式
r0=0; 保留位
DLC=0x2;表示负载为2个字节 有1个位填充
Data=0x12,0x34
15位CRC
CRC界定符=1(发送方释放总线)
ACK应答槽,用于接收方应答
ACK界定符=1(接收方释放总线)
波形三

S0F=0;起始
11位ID
SRR位,无意义,必须给1
IDE=1;表示扩展格式
扩展的18位ID 加上前面的11位ID,一共29位 ID范围:0x00000000~0x1FFFFFFF
RTR=0;表示数据帧
r0=0,r1=0; 保留位,默认给0
DLC=0x1;表示负载为1个字节
Data=0x56
15位CRC
CRC界定符=1(发送方释放总线)
ACK应答槽,用于接收方应答
ACK界定符=1(接收方释放总线)
波形四

S0F=0;
11位ID:0x666
RTR=1;遥控帧,不再有内容数据
IDE=0;表示标准格式
r0=0; 保留位
DLC=0x1;意义不大
15位CRC
CRC界定符=1(发送方释放总线)
ACK应答槽,用于接收方应答
ACK界定符=1(接收方释放总线)
三、接收方数据采样
<1>CAN总线没有时钟线,总线上的所有设备通过约定比特率的方式确定每一个数据位的时长
<2>发送方以约定的位时长每隔固定时间输出一位数据
<3>发送方以约定的位时长每隔固定时间采样总线上的电平,输入一位数据位
<4>理想情况下,接收方能依次采样到发送方发出的每个数据位,且采样点位于数据位中心附近
遇到的采样问题:
①接收方以约定的位时长进行采样,但是采样点没有对齐数据位中心附近
②接收方刚开始采样正确,但是时钟有误差,随着误差累积,采样点逐渐偏离
3.1 位时序
为了灵活调整每个采样点的位置,使采样点对齐数据中心附近,CAN总线对每一个数据位的时长进行了更细致的划分,分为同步段(SS)、传播时间段(PTS),相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每个段有由若干个最小时间单位(Tq)构成,最小时间单位可以在程序中指定

同步段(SS):(固定1Tq),跳变沿应该出现的地方,如果出现在SS段之后,需要利用硬同步和再同步使得跳变沿再迟出现在SS段
传播时间段(PTS):用于吸收网络上的物理延迟->发送单元输出延迟,总线信号传播延迟,接收单元输入延迟
相位缓冲段1(PBS1)和相位缓冲段2(PBS2):确定采样点的位置,采样点在PBS1,PBS2中间
除了SS固定为1Tq,其他段可以自己设定
3.2 硬同步
每个设备都有一个位时序计数周期,当发送方开始发报文,接收方收到SOF的下降沿时,接收方会将自己的位时序计时器周期拨到SS段的位置,与发送方的位时序计时周期保持同步
硬件同步只有在帧的第一个下降沿(SOF下降沿)有效
经过硬件同步后,若发送方和接收方的时钟没有误差,则后续所有数据的采样时点必然都会对齐数据中心位置附近
3.3 再同步
若接收方或发送方的时钟有误差,随着误差累积,数据位边沿逐渐偏离SS段,则此时接收方根据再同步补偿宽度值(SJW:1~4Tq)通过加长PBS1,或者缩短PBS2段,以调整同步
再同步可以发生在第一个下降沿之后的每个数据位跳变边沿

注意:并不是SJW=2就一定补偿2Tq;若误差为1Tq,而SJW=2,则会补偿1Tq,最大调整量不能超过SJW值
①1个位中只进行一次同步调整。
②只有当上次采样点的总线值和边沿后的总线值不同时,该边沿才能用于调整同步
③在总线空闲且存在隐性电平到显性电平的边沿时,则一定要进行硬件同步
④在总线非空闲时检测到的隐性电平到显性电平的边沿如果满足条件(1)和(2),将进行再同步。但 还要满足下面条件
·发送单元观测到自身输出的显性电平有延迟时不进行再同步。
·发送单元在帧起始到仲裁段有多个单元同时发送的情况下,对延迟边沿不进行再同步
3.4 比特率计算
四、仲裁
规则1:
<1>若当前已经有设备正在操作总线发送数据帧或遥控帧,则其他任何设备不能再同时发送数据帧/遥控帧(可以发送错误帧/过载帧破坏当前数据)
<2>任何设备检测到连续11个隐性电平,即认为总线空闲,只有在总线空闲时,设备才能发送数据帧/遥控帧
<3>若总线活跃状态时其他设备由发送需求,则需要等待总线变为空闲,才能执行发送需求
一旦有设备正在发送数据帧/遥控帧,总线就会变为活跃状态,必然不会出现连续11个隐性电平(1个隐性位的ACK界定符+7个隐性位的EOF+3个隐性位的帧间隔),其他设备自然也不会破坏当前发送
规则2
若对个设备的发送需求同时到来或者因等待而同时到来,则CAN总线协议会根据ID号(总裁段ID+RTR)进行非破坏性仲裁:ID号小的优先级高,取得总线控制权;ID号大的优先级低,仲裁失利后将进入接收状态,等待下一次总线空闲时再尝试发送
实现非破坏性仲裁需要两个要求:
① 线与特性:
②回读机制:每个设备发出一个数据位后,都会回读总线当前的电平状态,以确认自己发出的电平是否被真正发送出去了,根据线与特性,发出0回读必然为0,发出1回读不一定为1注意:在仲裁段会进行位填充,填充的位会进行仲裁,但位填充不会导致仲裁优先级的变化
4.1 数据帧与遥控帧
具有相同 ID 的数据帧和遥控帧在总线上竞争时, 仲裁段的最后一位(RTR) 为显性位的数据帧具有优先权,可继续发送

4.2 标准格式和扩展格式
标准格式 ID 与具有相同 ID 的遥控帧或者扩展格式的数据帧在总线上竞争时,标准格式的 RTR 位为显性位的具有优先权,可继续发送 (SRR必须始终为1)

五、错误处理

5.1 错误状态
主动错误状态的设备正常参与通信并检测到错误时发出主动错误
被动错误状态的设备正常参与通信但检测到错误时只能发出被动错误帧
总线关闭状态的设备不能参与通信
每个设备内部管理一个发送错误计数值( TEC)和接收错误计数值( REC)
刚开始时设备为主动错误状态,会发出主动错误帧,发送单元在输出错误标志时,TEC将增加8,加到16次以后TEC=128,这时发送就进入了被动错误状态;
若设备处在被动错误状态,若是再次出现同样的错误时,则该设备输出6个隐性电平的错误标志+8个隐性位的错误界定符+3个隐性位帧间隔+8个延迟传送
一个主动错误的设备和一个被动错误的设备都想发数据,在同时检测到11个隐性位,那么主动错误状态将直接取得总线的控制权,不会和被动错误设备进行仲裁,因为被动错误设备有八位延迟传送

六、STM32 CAN外设
资源:
波特率最高可达1兆位/秒
3个可配置优先级的发送邮箱
2个3级深度接收FIFO
14个过滤组
特色功能:时间触发通信、自动离线恢复、自动唤醒、禁止自动重传、接收FIFO溢出处理方式可配置,发送优先级可配置、双CAN模式
6.1 框图
发送部分:
①当想要发出一个报文时,需要将报文的各个参数,比如ID,Data,IDE,RTR写入到一个发送邮箱;
②为防止总线繁忙造成发送拥堵,STM32设计了3个发送邮箱
③若是发送邮箱有两个及以上的报文需要等待发送,可以配置发送策略(先请求先发送或者ID号优先级发送)
接收部分:
①接收过滤器(共有14个):可以根据ID号对报文进行过滤
②两个接收FIFO,在过滤器中可以配置报文进入哪个FIFO邮箱
③当FIFO满以后,可以配置新的报文该怎么存(若配置了FIFO锁定,满以后新的报文直接丢弃;若配置FIFO不锁定,满以后会覆盖邮箱2的数据)
发送:


当读取或者接收一个有效报文时,就会释放放一个邮箱
注意:当使用CAN_IT_FMP0中断标志位进入中断后,无需手动清除标志位,读取就相当于清除了;读取时注意避免双重读取
6.2 标识符过滤器
每个过滤器的核心由两个32位寄存器组成:R1[31:0],R2[31:0]
FFAx:关联设置 | FACTx:激活设置 |
0:经过该过滤器的报文经过FIFO 0 1:经过该过滤器的报文经过FIFO 1 |
0:表示该过滤器禁能 1:表示该过滤器使能 |
x:0~13 | FSCx=0 位宽16 |
FSCx=1 位宽32 |
FBMx=0 屏蔽模式 |
11位的ID地址范围:0~0x7FF | 29位的ID地址范围:0~0x1FFF FFFF |
FBMx=0 列表模式 |
CAN_FilterIdHigh对应CAN_FilterMaskIdHigh CAN_FilterIdLow对应CAN_FilterMaskIdLow |
6.3 测试模式
<1>静默模式:用于分析CAN总线的活动,不会对总线造成影响
<2>环回模式:用于自测,同时发送的报文可以在CAN_TX引脚上检测到
<3>环回静默模式:用于热自测,自测的同时不影响CAN总线

6.4 工作模式
<1>初始化模式:用于配置CAN外设,禁止报文的接收与发送
<2>正常模式:配置CAN外设后进入正常模式,以便正常接收和发送报文
<3>睡眠模式:低功耗,CAN外设时钟停止,可以软件唤醒或者硬件自动唤醒
<4>AWUM:
置1,自动唤醒,一旦检测到CAN总线活动,硬件就会自动清零SLEEP,唤醒 CAN外设;
置0,手动唤醒,软件清零SLEEP,唤醒CAN外设
6.5 波特率
6.6 中断
CAN外设占用4个专用的中断向量
<1>发送中断:发送邮箱空时中断
<2>FIFO 0中断:接收到一个报文/FIFO 0满/FIFO 0溢出时产生
<3>FIFO 1中断:接收到一个报文/FIFO 1满/FIFO 1溢出时产生
状态改变错误中断:出错/唤醒/进入睡眠时产生
6.7 错误处理与离线恢复
本章与5.1很相似,不过不同的是,从离线状态到错误主动状态,有一个使能位ABOM
ABOM=1:开启离线自动恢复,进入离线状态后,就自动开启恢复过程(与CAN协议一致)
ABOM=0:关闭离线自动恢复,软件必须先请求进入然后再退出初始化模式,随后恢复过程才被开启
七、CAN环回通信
初始化代码
/****************************
*@brief CAN总线初始化
*@param void
*@return void
*@note PA11-CAN_RX-推挽复用
PA12-CAN_TX-浮空输入
*@time 2025-7-7
******************************/
void MyCAN_Init(void)
{
/*1.开启GPIOA,CAN1时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);//开启CAN外设时钟
/*2.配置pin11引脚*/
GPIO_InitTypeDef GPIO_InitStruct ;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD ;//推挽复用输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11 ;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct) ;
/*3.配置pin12引脚*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING ;//浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 ;
GPIO_Init(GPIOA,&GPIO_InitStruct) ;
/*4.配置CAN1*/
CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack ;//测试模式为环回模式,此模式仅仅用于环回测试
CAN_InitStruct.CAN_Prescaler = 48 ;//预分频;波特率:36MHz/48/(1+2+3)=125KHz
CAN_InitStruct.CAN_BS1 = CAN_BS1_2tq ;//时间段BS1=2tq
CAN_InitStruct.CAN_BS2 = CAN_BS2_3tq ;//时间段BS2=3tq
CAN_InitStruct.CAN_SJW = CAN_SJW_2tq ;
CAN_InitStruct.CAN_NART = DISABLE ;//开启自动重传->发送失败的报文直接进入预定状态再次发送
CAN_InitStruct.CAN_TXFP = DISABLE ;//按照ID进行仲裁->ID号低的优先发送
CAN_InitStruct.CAN_RFLM = DISABLE ;//不锁定FIFO->FIFO满以后将覆盖FIFO中的最后一个邮箱
CAN_InitStruct.CAN_AWUM = DISABLE ;//手动唤醒->软件清零SLEEP,唤醒CAN外设
CAN_InitStruct.CAN_TTCM = DISABLE ;//不使能时间触发
CAN_InitStruct.CAN_ABOM = DISABLE ;//关闭离线自动恢复
CAN_Init (CAN1,&CAN_InitStruct) ;
CAN_FilterInitTypeDef CAN_FilterInitStruct ;
CAN_FilterInitStruct.CAN_FilterNumber = 0 ;//过滤器0
CAN_FilterInitStruct.CAN_FilterActivation = ENABLE ;//使能过滤器
CAN_FilterInitStruct.CAN_FilterMode = CAN_FilterMode_IdMask ;//屏蔽模式
CAN_FilterInitStruct.CAN_FilterScale = CAN_FilterScale_32bit ;//位宽32
CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0 ;//报文进入FIFO0
CAN_FilterInitStruct.CAN_FilterIdHigh = 0x0000;//0x123<<5:可替换
CAN_FilterInitStruct.CAN_FilterIdLow = 0x0000;
CAN_FilterInitStruct.CAN_FilterMaskIdHigh = 0x0000;//0x1FF<<5:可替换
CAN_FilterInitStruct.CAN_FilterMaskIdLow = 0x0000;
CAN_FilterInit (&CAN_FilterInitStruct) ;
}
在初始化代码中,设定了仲裁条件:根据ID优先级;以及FIFO满以后的报文该怎么处理;设定了FIFO的测试模式为环回模式;并设定了过滤器0的过滤规则;使用掩码,32位ID;设定将数据放到FIFO0中
发送代码
/****************************
*@brief CAN总线发送
*@param void
*@return void
*@note void
*@time 2025-7-7
******************************/
void MyCAN_Transmit(Message*Transmit)
{
CanTxMsg TxMessage ;
TxMessage.IDE = CAN_Id_Standard ;//设定发送标准ID
TxMessage.RTR = CAN_RTR_Data ;//设定发送帧类型
TxMessage.StdId = Transmit->Std_ID ;//发送的ID号
TxMessage.ExtId = Transmit->Ext_ID ;//发送的扩展ID号,本实验位使用
TxMessage.DLC = Transmit->Length ;//发送的数据长度
for(uint8_t i=0;i<Transmit->Length;i++)
{
TxMessage.Data[i]=Transmit->Data[i] ;
}
uint8_t numbermailbox = CAN_Transmit(CAN1,&TxMessage) ;//发送数据邮箱并返回发送的邮箱号
uint32_t Timeout;
while(CAN_TransmitStatus(CAN1,numbermailbox)!=CAN_TxStatus_Ok)//等待发送成功
{
Timeout++ ;
if(Timeout>100000){break;}
}
}
发送代码中设定了发送的帧的类型,以及是否为标准ID号
判断FIFO0中 是否有报文
/****************************
*@brief 判断FIFO0里有没有报文
*@param void
*@return 1:有报文,0:无报文
*@note void
*@time 2025-7-7
******************************/
uint8_t MyCAN_ReceiveFlag(void)
{
if(CAN_MessagePending(CAN1,CAN_FIFO0)) //返回挂起报文的数目。也就是位FMP的值
return 1;
else
return 0;
}
接收代码:
/****************************
*@brief CAN总线接收
*@param void
*@return void
*@note 从FIFO0邮箱CanRxMsg中获得数据
*@time 2025-7-7
******************************/
void MyCAN_Receive(Message*Receive)
{
CanRxMsg RxMessage ;//定义用于接收数据的结构体
CAN_Receive(CAN1,CAN_FIFO0,&RxMessage) ;//接收数据
if(RxMessage.IDE==CAN_Id_Standard ) //判断接收到的是否为标准ID
{ Receive->Std_ID=RxMessage.StdId;}
else
{Receive->Ext_ID=RxMessage.ExtId;}
if(RxMessage.RTR==CAN_RTR_Data) //判断接收的是否为数据帧
{
Receive->Length=RxMessage.DLC; //接收数据的长度
for(uint8_t i=0;i<Receive->Length;i++)
{
Receive->Data[i]=RxMessage.Data[i];//如果是数据帧,将数据给接收数据的结构体
}
}
else
{
//遥控帧
}
}
八、过滤器的用法
/****************************
*@brief CAN总线初始化
*@param void
*@return void
*@note PA11-CAN_RX-推挽复用
PA12-CAN_TX-浮空输入
*@time 2025-7-7
******************************/
void MyCAN_Init(void)
{
/*1.开启GPIOA,CAN1时钟*/
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);//开启GPIOA的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_CAN1,ENABLE);//开启CAN外设时钟
/*2.配置pin11引脚*/
GPIO_InitTypeDef GPIO_InitStruct ;
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_OD ;//推挽复用输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11 ;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct) ;
/*3.配置pin12引脚*/
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING ;//浮空输入
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12 ;
GPIO_Init(GPIOA,&GPIO_InitStruct) ;
/*4.配置CAN1*/
CAN_InitTypeDef CAN_InitStruct;
CAN_InitStruct.CAN_Mode = CAN_Mode_LoopBack ;//测试模式为环回模式,此模式仅仅用于环回测试
CAN_InitStruct.CAN_Prescaler = 48 ;//预分频;波特率:36MHz/48/(1+2+3)=125KHz
CAN_InitStruct.CAN_BS1 = CAN_BS1_2tq ;//时间段BS1=2tq
CAN_InitStruct.CAN_BS2 = CAN_BS2_3tq ;//时间段BS2=3tq
CAN_InitStruct.CAN_SJW = CAN_SJW_2tq ;
CAN_InitStruct.CAN_NART = DISABLE ;//开启自动重传->发送失败的报文直接进入预定状态再次发送
CAN_InitStruct.CAN_TXFP = DISABLE ;//按照ID进行仲裁->ID号低的优先发送
CAN_InitStruct.CAN_RFLM = DISABLE ;//不锁定FIFO->FIFO满以后将覆盖FIFO中的最后一个邮箱
CAN_InitStruct.CAN_AWUM = DISABLE ;//手动唤醒->软件清零SLEEP,唤醒CAN外设
CAN_InitStruct.CAN_TTCM = DISABLE ;//不使能时间触发
CAN_InitStruct.CAN_ABOM = DISABLE ;//关闭离线自动恢复
CAN_Init (CAN1,&CAN_InitStruct) ;
CAN_FilterInitTypeDef CAN_FilterInitStruct ;
CAN_FilterInitStruct.CAN_FilterNumber = 0 ;//过滤器0
CAN_FilterInitStruct.CAN_FilterActivation = ENABLE ;//使能过滤器
CAN_FilterInitStruct.CAN_FilterMode = CAN_FilterMode_IdMask ;//屏蔽模式
CAN_FilterInitStruct.CAN_FilterScale = CAN_FilterScale_16bit ;//位宽16
CAN_FilterInitStruct.CAN_FilterFIFOAssignment = CAN_Filter_FIFO0 ;//报文进入FIFO0
CAN_FilterInitStruct.CAN_FilterIdHigh = 0x120<<5;//0x123<<5:可替换
CAN_FilterInitStruct.CAN_FilterIdLow = 0x000<<5;
CAN_FilterInitStruct.CAN_FilterMaskIdHigh = 0x1F0<<5;//屏蔽模式,ID为0x120~0x12F
CAN_FilterInitStruct.CAN_FilterMaskIdLow = 0x1FF<<5;
CAN_FilterInit (&CAN_FilterInitStruct) ;
}
/****************************
*@brief CAN总线发送
*@param void
*@return void
*@note void
*@time 2025-7-7
******************************/
void MyCAN_Transmit(CanTxMsg*TxMessage)
{
uint8_t numbermailbox = CAN_Transmit(CAN1,TxMessage) ;//发送数据邮箱并返回发送的邮箱号
uint32_t Timeout;
while(CAN_TransmitStatus(CAN1,numbermailbox)!=CAN_TxStatus_Ok)//等待发送成功
{
Timeout++ ;
if(Timeout>100000){break;}
}
}
/****************************
*@brief 判断FIFO0里有没有报文
*@param void
*@return 1:有报文,0:无报文
*@note void
*@time 2025-7-7
******************************/
uint8_t MyCAN_ReceiveFlag(void)
{
if(CAN_MessagePending(CAN1,CAN_FIFO0)) //返回挂起报文的数目。也就是位FMP的值
return 1;
else
return 0;
}
/****************************
*@brief CAN总线接收
*@param void
*@return void
*@note 从FIFO0邮箱CanRxMsg中获得数据
*@time 2025-7-7
******************************/
void MyCAN_Receive(CanRxMsg*RxMessage)
{
CAN_Receive(CAN1,CAN_FIFO0,RxMessage) ;
}
上述代码为CAN通信的初始化,发送,判断接收邮箱是否为空以及接收数据,可以利用掩码对报文进行过滤,对这CAN_FilterIdHigh,CAN_FilterIdLow,CAN_FilterMaskIdHigh,CAN_FilterMaskIdLow 进行设置,就可以实现想要接收什么样的报文了
uint8_t KeyNum;
uint8_t cnt=0;
CanTxMsg TransmitDataArray[]={
{.StdId =0x123 ,.ExtId =0x00,.IDE =CAN_Id_Standard,.RTR=CAN_RTR_Data ,.DLC=6,.Data={0x11,0x22,0x33,0x44,0x55,0x66}},
{.StdId =0x124 ,.ExtId =0x00,.IDE =CAN_Id_Standard,.RTR=CAN_RTR_Data ,.DLC=6,.Data={0x11,0x22,0x33,0x44,0x55,0x66}},
{.StdId =0x131 ,.ExtId =0x00,.IDE =CAN_Id_Standard,.RTR=CAN_RTR_Data ,.DLC=6,.Data={0x11,0x22,0x33,0x44,0x55,0x66}},
{.StdId =0x129 ,.ExtId =0x00,.IDE =CAN_Id_Standard,.RTR=CAN_RTR_Data ,.DLC=6,.Data={0x11,0x22,0x33,0x44,0x55,0x66}},
};
CanRxMsg ReceiveData;
int main()
{
usart1_Init(115200);
MyCAN_Init();
Key_Init();
while(1)
{
KeyNum=KEY_Scanf();
if(KeyNum==1)
{
MyCAN_Transmit(&TransmitDataArray[cnt++]);
if(cnt>=sizeof(TransmitDataArray)/sizeof(CanTxMsg)){cnt=0;}
}
if(MyCAN_ReceiveFlag())
{
MyCAN_Receive(&ReceiveData);
if(ReceiveData.IDE==CAN_Id_Standard)
{
printf("接收到标准ID=%#X\r\n",ReceiveData.StdId);
}
else
{
printf("接收到扩展ID=%#X\r\n",ReceiveData.ExtId);
}
if(ReceiveData.RTR==CAN_RTR_Data)
{
printf("接收到数据帧:\r\n");
printf("RxDLC =%#X\r\n",ReceiveData.DLC);
for(uint8_t i=0;i<ReceiveData.DLC;i++)
{
printf("RxData[%d]=%#X\r\n",i,ReceiveData.Data[i]);
}
printf("\r\n");
}
else
{
printf("接收到遥控帧:\r\n");
for(uint8_t i=0;i<ReceiveData.DLC;i++)
{
printf("RxData[%d]=%#X\r\n",i,ReceiveData.Data[i]);
}
printf("\r\n");
}
}
}
}
主程序理由库函数中自带的结构体变量定义发送结构体数组
九、个人理解
对于CAN总线,它是半双工的,发送数据的时候不能接收,CAN总线上可以挂在很多设备,这些设备并没有主从之分
对于发送:每个设备在发送数据的时候都会带有数据的ID号(可以设定ID号是标准ID还是扩展ID),如果多个设备同一时刻往总线上发送数据,那么这些想要发送的设备就会根据自己发送数据的ID号进行仲裁,ID号小的优先发送,发送的数据可以进入三个发送邮箱的其中一个邮箱
对于接收:当数据发送到CAN总线上时,每个设备都可以感受到总线数据的变化,想要接收数据的设备可以设定过滤器的过滤规则,比如说过滤想要的ID号或者想要的ID号范围,经过过滤器的数据可以进入FIFO0或者FIFO1中,若FIFO满了,也可以设定是否锁定FIFO