CAN基础知识介绍
CAN介绍
什么是CAN
CAN(Controller Area Network),是ISO国际标准化的串行通信协议。为了满足汽车产业的“减少线束的数量”、“通过多个LAN,进行大量数据的高速通信”的需求。
低速CAN(ISO11519)通信速率10~125Kbps,总线长度可达1000米。
高速CAN(经典CAN)(ISO11898)通信速率125Kbps~1Mbps,总线长度≤40米。
CAN FD通信速率可达5Mbps,并且兼容经典CAN,遵循ISO11898-1 做数据收发。
CAN总线通讯网络
一般CAN总线包含CAN控制器(开发板内CAN外设)、CAN收发器(CAH收发器芯片:TAJ1050、TAJ1042、SIT1050T)、CAN_High和CAN_Low
CAN总线由两根线(CAN_High、CAN_Low)组成的,允许挂载多个设备节点(一般低速CAN20个,高速CAN30个,一般取决于CAN收发器的芯片)。
闭环总线网络(高速CAN)
开环总线网络(低速CAN)
CAN总线特点
多主控制:每个设备都可以主动发送数据。
系统的柔软性:没有类似地址的信息,添加设备不改变原来总线的状态。
通信速度:速度快,距离远。
具有错误检测和错误通知和错误恢复功能。
故障封闭:判断故障类型,并且进行隔离。
连接节点多:CAN协议中,我们可以挂载多个节点(每个节点由CAN控制器和CAN收发器组成) ,通过总线来实现节点通讯,与其他协议不同的是,CAN协议不对节点的地址进行编码,而是对节点的数据内容进行编码。
CAN应用场景
CAN总线协议已经广泛应用在汽车电子、工业自动化、船舶、医疗设备、工业设备等方面。
CAN物理层
CAN物理层特性
CAN使用差分信号(CAN_High - CAN_Low)进行数据传输,根据CAN_High和CAN_Low上的电位差来判断总线电平。
总线电平分为显性电平(逻辑0)和隐性电平(逻辑1)。
显性电平具有优先权(即当某个发送方发送一个显性电平,那么总线都是逻辑0)。发送方通过使总线电平发生变化,将消息发给接收方。
CAN总线电平与CAN_High和CAN_Low的关系:
CAN数据传输
发送数据:控制器发送一个逻辑信号,收发器将这个逻辑信号变成差分信号传送到总线中。
接收数据:收发器将差分信号转化为逻辑信号,即0或1的二进制编码;
CAN协议层
CAN帧种类介绍
CAN总线以“帧”形式进行通信。CAN协议定义了5种类型的帧,分别是数据帧、遥控帧、错误帧、过载帧和间隔帧,其中数据帧最常用。
帧类型 |
帧作用 |
数据帧 |
用于发送单元向接收单元传输数据的帧 |
遥控帧 |
用于接收单元向具有相同ID的发送单元请求数据的帧 |
错误帧 |
用于当检测出错误时向其他单元通知错误的帧 |
过载帧 |
用于接收单元通知其他尚未做好接收准备的帧 |
间隔帧 |
用于将数据帧以及遥控帧与前面的帧分离开来的帧 |
CAN的其他帧的分析方法与数据帧基本一样,我们只要搞懂对数据帧就基本可以搞懂CAN的其他帧了。
CAN数据帧介绍
数据帧由7段组成,分为标准帧(CAN2.0A)和扩展帧(CAN2.0B),这两者的区别主要体现在仲裁段和控制段。
帧起始:表示数据帧开始的段,一位显性信号(逻辑0)。
仲裁段:表示该帧优先级的段,优先级。
控制段:表示数据的字节数即保留位的段。
数据段:数据的内容,一帧可发送0~8字节数。
CRC段:检查帧的传输错误的段。
ACK段:表示确认正常接收的段。
帧结束:表示数据帧结束的段,7位隐性信号(逻辑1)。
标准数据帧
ID(标识符位):优先级。
RTR(远程发送请求位):0是数据帧,1是遥控帧。
IDE(扩展标识符位):用于区别是标准帧还是扩展帧,1是扩展帧,0是标准帧。
DLC(数据长度编码位):告知该数据帧的长度。
Data Field:数据长度,0~8字节。
CRC:循环校验序列。
DEL:界定符。
ACK:发送单元发送数据时将该位拉高,其他接收单元接收数据时将该位拉低。
扩展数据帧
ID(标识符位):优先级。
RTR(远程发送请求位):0是数据帧,1是遥控帧。
SRR:用在扩展格式,替代RTR,无实质作用。
IDE(扩展标识符位):用于区别是标准帧还是扩展帧,1是扩展帧,0是标准帧。
DLC(数据长度编码位):告知该数据帧的长度。
Data Field:数据长度,0~8字节。
CRC:循环校验序列。
DEL:界定符。
ACK:发送单元发送数据时将该位拉高,其他接收单元接收数据时将该位拉低。
CAN时序介绍
CAN总线以“位同步”机制实现对电平的正确采样。位数据都由四段组成:同步段(SS)、传播时间段(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2),每段又由多个位时序Tq组成。
同步段(SS):1Tq
传播时间段(PTS):1~8Tq
相位缓冲段1(PBS1):1~8Tq
相位缓冲段2(PBS2):2~8q
采样点:读取总线电平,并将读取到的电平作为位值的点,如该位数据的采样点读取的电平是显性电平。
注意:当节点监测到总线上信号的跳变在SS段范围内,表示节点与总线的时序是同步,此时采样点的电平即该位的电平。
根据位时序,就可以计算CAN通信的波特率?。
数据同步过程
CAN为了实现对总线电平信号的正确采样,会进行数据同步,数据同步分为硬件同步和软件同步。
硬件同步
节点通过CAN总线发送数据,一开始发送帧起始信号。总线上其他节点会检测帧起始信号在不在位数据的SS段内,来判断内部时序是否与总线同步。
如果不在SS段内,这种情况下,采样点获得的电平状态时不正确的。所以节点会使用硬件同步方式来进行调整,把自己的SS段平移到检测到边沿(帧起始信号是从隐性电平到显性电平的,具有一个下降沿)的地方,从而获得同步,同步情况下,采样点获得的电平状态才是正确的。
再同步
再同步利用普通数据位的边沿信号(帧起始信号时特殊的边沿信号)进行同步。再同步的方式分为两种情况:超前和滞后,即边沿信号与SS段的相对位置。
再同步时,PSB1和PSB2增加或者减少的时间称为PJW(再同步补偿宽度),一般是1~4个Tq。限定了SJW值后,再同步时,不能增加(修改)限定长度的SJW值。SJW值较大时,吸收误差能力更强,但是通讯速度会下降。
CAN总线仲裁(优先级决定)
CAN总线处于空闲状态时,最先开始发送消息的单元获得发送权。当多个单元同时开始发送时,从仲裁段(报文ID)的第一位开始仲裁,连续输出显性电平最多的单元可继续发送,即首先出现隐性电平的单元失去对总线的占有权而变为接收单元。(即报文ID越小的单元,它的优先级越高)
在总线仲裁中失利的单元(即由发送转变成接收的单元)会自动检测总线空闲,在第一时间再次尝试发送。
STM32的CAN控制器介绍
CAN控制器介绍
STM32的CAN控制器(bxCAN)支持CAN2.0A和CAN2.0B Active版本协议。CAN2.0A只能处理标准数据帧且扩展帧的内容会识别错误,而CAN2.0B Active可以处理标准数据帧和扩展数据帧。CAN2.0B Passive只能处理标准数据帧且扩展数据帧的内容会忽略。
CAN控制器主要特点
波特率最高可达1Mbps。
支持时间出发通信(CAN的硬件内部定时器可以在TX/RX的帧起始位的采样点位置产生时间戳)。
具有3级发送邮箱。
具有3级深度的2个接收FIFO。
可变的过滤器组(最多28个),STM32F103xxxx只有14个。
CAN控制器工作模式
CAN控制器的工作模式有三种:初始化模式、正常模式和睡眠模式。
CAN控制器的测试模式
CAN控制器的测试模式有三种:静默模式、环回模式和环回静默模式。(在初始化模式下可以配置CAN控制器的测试模式)
CAN控制器的框图
STM32F103xxxx没有CAN2(从)。
CAN内核(①)
包含各种控制/状态/配置寄存器,可以配置模式、波特率灯。
发送邮箱(②)
用来缓存待发送的报文,最多可以缓存3个报文,并且包含发送调度,可以决定哪个报文(报文ID)先发送。
发送处理
发送优先级由邮箱中报文的标识符(ID)决定,标识符数值越低有最高优先级,如果标识符的值相同,邮箱小的先发送。
接收FIFO(③)
当有效报文通过接收过滤器后就进入FIFO,可以在FIFO里读取报文。
接收处理
有效报文指的是(数据帧知道EOF段的最后一位都没有错误),且通过过滤器组队标识符过滤。
接收过滤器(④)
当总线上报文数据量很大时,总线上的设备会频繁获取报文,占用CPU。过滤器的作用是选择性接收有效报文,减轻系统的负担,根据滤波器的配置将报文分别送往FIFO0或FIFO1。
过滤器的作用
每个过滤器组都有两个32位寄存器CAN_FxR1和CAN_FxR2。过滤器组的功能(与寄存器的位宽和寄存器的选择模式有关)不同,寄存器的作用不尽相同。
位宽
寄存器位宽可以设置32位或者16位,寄存器存储的内容就有所区别。(STDID:标准ID,EXTID:扩展ID)
过滤器组寄存器 |
32位 |
16位(寄存器由两部分组成) |
CAN_FxR1 |
STDID[10:0]、EXTID[17:0]、IDE、RTR |
STDID[10:0]、EXTID[17:15]、IDE、RTR |
CAN_FxR2 |
STDID[10:0]、EXTID[17:0]、IDE、RTR |
STDID[10:0]、EXTID[17:15]、IDE、RTR |
工作模式
工作模式可以设置标识符屏蔽模式或标识符列表模式,寄存器内容的功能就有所区别。
标识符屏蔽模式:它把可接收报文 ID 的某几位作为列表,这几位被称为屏蔽位,可以把它理解成关键字搜索,只要屏蔽位(关键字)相同,就符合要求,报文就会被保存到接收 FIFO。
标识符列表模式:它把要接收报文的 ID 列成一个表,要求报文 ID 与列表中的某一个标识符完全相同才可以接收,可以理解为白名单管理。
作用
通常32位用来筛选扩展数据帧,16位用来筛选标准数据帧。
模式 |
说明 |
32位标识符屏蔽模式 |
CAN_FxR1 存储 ID,CAN_FxR2 存储哪个位必须要与 CAN_FxR1 中的ID 一致。(过滤出1组符合条件的报文) |
32 位标识符列表模式 |
CAN_FxR1和 CAN_FxR2 各存储 1个ID(过滤出2个符合条件的报文) |
16 位标识符屏蔽模式 |
CAN_FxR1低 16 位存储 ID,高 16 位存储哪个位必须要与低 16 位的ID一致; CAN_FxR2低 16 位存储 ID,高 16 位存储哪个位必须要与低 16 位的ID一致。(过滤出2组符合条件的报文) |
16 位标识符模式 |
CAN_FxR1和 CAN_FxR2 各存储2个ID(过滤出4个符合条件的报文) |
举例:使用32位掩码模式过滤出一组符合条件的报文
设置过滤器组0为32位屏蔽位模式(过滤出一组符合条件的报文) |
||||
bit |
31~24 |
23~16 |
15~8 |
7~0 |
ID CAN_F0R1 (0xFFFF0000) |
1111 1111 |
1111 1111 |
0000 0000 |
0000 0000 |
屏蔽位 CAN_F0R1 (0xFF00FF00) |
1111 1111 |
0000 0000 |
1111 1111 |
0000 0000 |
映像 |
STID[10:3] |
STID[2:0]、EXID[17:13] |
EXID[12:5] |
EXID[4:0]、IDE、RTR、0 |
过滤出ID |
1111 1111 |
xxxx xxxx |
0000 0000 |
xxxx xxxx |
屏蔽位寄存器中位值为1,表示ID要必须匹配;位值为0则不关心。
在使能过滤器的情况下,总线上广播的报文ID与过滤器的配置都不匹配,CAN控制器会丢弃该报文,不会进入到接收FIFO中。
注意:标识符选择位IDE和帧类型RTR需要一致,不同过滤器组的工作模式可以设置不同。
CAN控制器的位时序
STM32的CAN控制器的位时序分为三段:同步段(SYNC_SEG)、时间段1(BS1 = PTS + PBS1)、时间段2(BS2)。(主要用来计算波特率,CAN通信双方的波特率保持一致才能通信成功)
CAN相关寄存器介绍
寄存器 |
名称 |
作用 |
CAN_MCR |
CAN主控制寄存器 |
主要负责CAN工作模式的配置 |
CAN_MSR |
CAN主状态寄存器 |
主要用来设置CAN的当前状态 |
CAN_TSR |
CAN发送状态寄存器 |
主要用来确定报文是否发送出去 |
CAN_RF0R |
CAN接收FIFO 0寄存器 |
主要用来获取FIFO 0的报文数目 |
CAN_RF1R |
CAN接收FIFO 1寄存器 |
主要用来获取FIFO 1的报文数目 |
CAN_IER |
CAN中断使能寄存器 |
主要用来使能CAN的某些中断位 |
CAN_ESR |
CAN错误状态寄存器 |
主要用来接收一些错误信息 |
CAN_BTR |
位时序寄存器 |
用来设置分频/TBS1/TBS2/TSWJ 等参数,设置测试模式 |
CAN_(T/R)IxR |
标识符寄存器 |
存放(待发送/接收)的报文ID、扩展ID、IDE位及RTR位 |
CAN_(T/R)DTxR |
数据长度和时间戳寄存器 |
存放(待发送/接收)报文的DLC段 |
CAN_(T/R)DIxR |
低位数据寄存器 |
存放(待发送/接收)报文数据段的Data0~Data3的内容 |
CAN_(T/R)DHxR |
高位数据寄存器 |
存放 (待发送/接收)报文数据段的Data4~Data7的内容 |
CAN_FM1R |
过滤器模式寄存器 |
用于设置各过滤器组的选择模式 |
CAN_FS1R |
过滤器位宽寄存器 |
用于设置各过滤器组的位宽 |
CAN_FFA1R |
FIFO关联寄存器 |
用于设置报文通过过滤器后,被存入的FIFO |
CAN_FA1R |
过滤器激活寄存器 |
用于开启对应的过滤器组 |
CAN_FxR(1/2) |
过滤器组x寄存器 |
根据位宽和模式设置不同,CAN FXR1和FXR2功能不同 |
CAN相关HAL库驱动介绍
STM32的hal库关于CAN的结构体
CAN_InitTypeDef
CAN初始化结构体
typedef struct
{
uint32_t Prescaler; /* 预分频 */
uint32_t Mode; /* 工作模式 */
uint32_t SyncJumpWidth; /* 再次同步跳跃宽度 */
uint32_t TimeSeg1; /* 时间段1(BS1)长度 */
uint32_t TimeSeg2; /* 时间段2(BS2)长度 */
FunctionalState TimeTriggeredMode; /* 时间触发通信模式 */
FunctionalState AutoBusOff; /* 总线自动关闭模式 */
FunctionalState AutoWakeUp; /* 自动唤醒 */
FunctionalState AutoRetransmission; /* 自动重传 */
FunctionalState ReceiveFifoLocked; /* 接收FIFO锁定 */
FunctionalState TransmitFifoPriority; /* 传输FIFO优先级 */
} CAN_InitTypeDef;
CAN_FilterTypeDef
CAN过滤器配置结构体。
typedef struct
{
uint32_t FilterIdHigh; /* ID高字节 */
uint32_t FilterIdLow; /* ID低字节 */
uint32_t FilterMaskIdHigh; /* 屏蔽位高字节 */
uint32_t FilterMaskIdLow; /*屏蔽位低字节 */
uint32_t FilterFIFOAssignment; /* 过滤器关联FIFO */
uint32_t FilterBank; /* 选择过滤器组 */
uint32_t FilterMode; /* 过滤器模式 */
uint32_t FilterScale; /* 过滤器位宽 */
uint32_t FilterActivation; /* 过滤器使能 */
uint32_t SlaveStartFilterBank; /* 从CAN选择启动过滤器组(单CAN情况下,该成员没有意义) */
} CAN_FilterTypeDef;
过滤器配置模式 |
CAN_FxR1[31:16] |
CAN_FxR1[15:0] |
CAN_FxR2[31:16] |
CAN_FxR2[15:.0] |
32位标识符屏蔽模式 |
FilterIdHigh |
FilterIdLow |
FilterMaskIdHigh |
FilterMaskIdLow |
32 位标识符列表模式 |
FilterIdHigh |
FilterIdLow |
FilterMaskIdHigh |
FilterMaskIdLow |
16 位标识符屏蔽模式 |
FilterMaskIdLow |
FilterIdLow |
FilterMaskIdHigh |
FilterIdHigh |
16 位标识符模式 |
FilterMaskIdLow |
FilterIdLow |
FilterMaskIdHigh |
FilterIdHigh |
需要结合印象来赋值:
32位位宽:STID[10:3]、STID[2:0]、EXID[17:13]、EXID[12:5]、EXID[4:0]、IDE、ETR、0
16位位宽:STID[10:3]、STID[2:0]、RTR、IDE、EXID[17:15]
32位标识符列表模式过滤器配置代码
32位标识符屏蔽模式过滤器配置代码
CAN_TxHeaderTypeDef
CAN发送消息结构体。
typedef struct
{
uint32_t StdId; /* 标准标识符(当CAN数据帧是扩展数据帧时,该成员无意义) */
uint32_t ExtId; /* 扩展标识符(当CAN数据帧是标准数据帧时,该成员无意义) */
uint32_t IDE; /* 帧格式(该成员决定标准数据帧或扩展数据帧) */
uint32_t RTR; /* 帧类型(该成员决定数据帧或遥控帧) */
uint32_t DLC; /* 数据长度 */
FunctionalState TransmitGlobalTime; /* 发送时间标记(时间戳) */
} CAN_TxHeaderTypeDef;
CAN_RxHeaderTypeDef
CAN接收消息结构体。
typedef struct
{
uint32_t StdId; /* 标准标识符(当CAN数据帧是扩展帧时,该成员无意义) */
uint32_t ExtId; /* 扩展标识符(当CAN数据帧是标准帧时,该成员无意义) */
uint32_t IDE; /* 帧格式(该成员决定标准帧或扩展帧) */
uint32_t RTR; /* 帧类型(该成员决定数据帧或遥控帧) */
uint32_t DLC; /* 数据长度 */
uint32_t Timestamp; /* 时间戳 */
uint32_t FilterMatchIndex; /* 过滤器号 */
} CAN_RxHeaderTypeDef;
STM32的hal库关于CAN的函数
__HAL_RCC_CAN1_CLK_ENABLE()
使能CAN时钟。(使用STM32CubeMX会自动配置)
HAL_CAN_Init()
初始化CAN。(使用STM32CubeMX会自动配置)
HAL_CAN_ConfigFilter()
配置CAN接收过滤器。(将配置好的接收过滤器结构体与CAN外设连接起来)
原型:
HAL_StatusTypeDef HAL_CAN_ConfigFilter(CAN_HandleTypeDef *hcan, CAN_FilterTypeDef *sFilterConfig)
参数:
CAN_HandleTypeDef *hcan:CAN句柄
CAN_FilterTypeDef *sFilterConfig:接收过滤器结构体地址
实例:
HAL_CAN_ConfigFilter(&hcan,&myCan_filter); /* 配置CAN外设的接收过滤器 */
HAL_CAN_Start()
启动CAN外设。
原型:
HAL_StatusTypeDef HAL_CAN_Start(CAN_HandleTypeDef *hcan)
参数:
CAN_HandleTypeDef *hcan:CAN句柄
实例:
HAL_CAN_Start(&hcan); /* 启动CAN外设 */
HAL_CAN_ActivateNotification()
使能CAN中断。(该函数会调用__HAL_CAN_ENABLE_IT())
HAL_CAN_AddTxMessage()
添加数据到某个发送邮箱。
原型:
HAL_StatusTypeDef HAL_CAN_AddTxMessage(CAN_HandleTypeDef *hcan, CAN_TxHeaderTypeDef *pHeader, uint8_t aData[], uint32_t *pTxMailbox)
参数:
CAN_HandleTypeDef *hcan:CAN句柄
CAN_TxHeaderTypeDef *pHeader:CAN发送结构体地址
uint8_t aData[]:待发送数据
uint32_t *pTxMailbo:发送邮箱
实例:
CAN_TxHeaderTypeDef myCan_tx; /* CAN发送结构体 */
uint32_t tx_mail = CAN_TX_MAILBOX0; /* 数据发送邮箱为邮箱0 */
uint8_t buf[8] = "zhu_tou";
HAL_CAN_AddTxMessage(&hcan,&myCan_tx,buf,&tx_mail); /* 从buf中发送数据到邮箱0 */
HAL_CAN_GetTxMailboxesFreeLevel()
获取发送邮箱的状态。
原型:
uint32_t HAL_CAN_GetTxMailboxesFreeLevel(CAN_HandleTypeDef *hcan)
参数:
CAN_HandleTypeDef *hcan:CAN句柄
实例:
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3); /* 等待数据发送完毕:当发送邮箱的3个邮箱都为空时,返回值为3 */
HAL_CAN_GetRxMessage()
从接收FIFO接收数据。
原型:
HAL_StatusTypeDef HAL_CAN_GetRxMessage(CAN_HandleTypeDef *hcan, uint32_t RxFifo, CAN_RxHeaderTypeDef *pHeader, uint8_t aData[])
参数:
CAN_HandleTypeDef *hcan:CAN句柄
uint32_t RxFifo:接收FIFO
CAN_RxHeaderTypeDef *pHeader:CAN接收结构体地址
uint8_t aData[]:接收数据存储地址
实例:
CAN_RxHeaderTypeDef myCan_rx; /* CAN接收结构体 */
uint8_t rcvbuf[8] = {0};
HAL_CAN_GetRxMessage(&hcan,CAN_RX_FIFO0,&myCan_rx,buf); /* 从接收FIFO 0接收消息到buf中 */
HAL_CAN_GetRxFifoFillLevel()
获取接收FIFO的状态。
原型:
uint32_t HAL_CAN_GetRxFifoFillLevel(CAN_HandleTypeDef *hcan, uint32_t RxFifo)
参数:
CAN_HandleTypeDef *hcan:CAN句柄
uint32_t RxFifo:接收FIFO
实例:
/* 判断接收FIFO 0是否为空 */
if(HAL_CAN_GetRxFifoFillLevel(&hcan,CAN_RX_FIFO0) == 0){
return 0; /*为空返回0*/
}
CAN基本使用步骤
CAN外设初始化MX_CAN_Init()
配置波特率、CAN功能、接收过滤器等等。
使能CAN时钟和初始化CAN相关引脚HAL_CAN_MspInit()
使用STM32CubeMX会自动配置。
CAN数据的接收和发送
CAN数据的接收:HAL_CAN_AddTxMessage()
获取发送邮箱的状态:HAL_CAN_GetTxMailboxesFreeLevel()
CAN数据的发送:HAL_CAN_GetRxMessage()
获取接收FIFO的状态:HAL_CAN_GetRxFifoFillLevel()
使用CAN的中断(可选)
使能CAN的相关中断:__HAL_CAN_ENABLE_IT()
配置NVIC
编写中断服务函数
CAN实验1(使用回环模式实现自发自收)
通过CAN总线将数据自发自收,每按下一次按键就发送一次数据,并将接收的数据显示在串口上。(接收过滤器接收所有报文,不进行筛选)
使用STM32CubeMX创建工程
配置SYS
配置RCC
配置GPIO
PA0(按键)配置成输入引脚。
配置串口信息(UART1)
配置CAN
配置工程名称、工程路径
选择固件库
生成工程
使用MicroLIB库
can.c文件编写
CAN外设初始化:主要配置波特率、CAN功能、接收过滤器
CAN发送数据函数
CAN接收数据函数
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file can.c
* @brief This file provides code for the configuration
* of the CAN instances.
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "can.h"
#include <stdio.h>
/* USER CODE BEGIN 0 */
CAN_TxHeaderTypeDef myCan_tx; /* CAN发送结构体 */
CAN_RxHeaderTypeDef myCan_rx; /* CAN接收结构体 */
/* USER CODE END 0 */
CAN_HandleTypeDef hcan;
/* CAN外设初始化 */
void MX_CAN_Init(void)
{
/* USER CODE BEGIN CAN_Init 0 */
/* USER CODE END CAN_Init 0 */
/* USER CODE BEGIN CAN_Init 1 */
/* USER CODE END CAN_Init 1 */
hcan.Instance = CAN1;
hcan.Init.Mode = CAN_MODE_LOOPBACK; /* 环回模式:自发自收 */
/* 波特率相关 */
hcan.Init.Prescaler = 4; /* 分频系数 */
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; /* 重新同步跳跃宽度 */
hcan.Init.TimeSeg1 = CAN_BS1_9TQ; /* 时间段1 */
hcan.Init.TimeSeg2 = CAN_BS2_8TQ; /* 时间段2 */
/* CAN功能设置 */
hcan.Init.TimeTriggeredMode = DISABLE; /* 禁止时间触发通信 */
hcan.Init.AutoBusOff = DISABLE; /* 禁止自动离线管理 */
hcan.Init.AutoWakeUp = DISABLE; /* 禁止自动唤醒 */
hcan.Init.AutoRetransmission = DISABLE; /* 禁止自动重发 */
hcan.Init.ReceiveFifoLocked = DISABLE; /* 禁止接收FIFO锁定 */
hcan.Init.TransmitFifoPriority = DISABLE; /* 禁止发送FIFO优先级 */
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN CAN_Init 2 */
/* 配置接收过滤器 */
CAN_FilterTypeDef myCan_filter; /* CAN过滤器结构体 */
myCan_filter.FilterMode = CAN_FILTERMODE_IDMASK; /* 工作模式:屏蔽位模式 */
myCan_filter.FilterScale = CAN_FILTERSCALE_32BIT; /* 32位位宽 */
/* 过滤器接收所有报文,不进行筛选 */
myCan_filter.FilterIdHigh = 0;
myCan_filter.FilterIdLow = 0;
myCan_filter.FilterMaskIdHigh = 0;
myCan_filter.FilterMaskIdLow = 0;
myCan_filter.FilterBank = 0; /* 选择过滤器组 */
myCan_filter.FilterFIFOAssignment = CAN_FilterFIFO0; /* 过滤器关联FIFO 0 */
myCan_filter.FilterActivation = CAN_FILTER_ENABLE; /* 使能过滤器 */
HAL_CAN_ConfigFilter(&hcan,&myCan_filter); /* 配置CAN外设的接收过滤器 */
/* USER CODE END CAN_Init 2 */
}
/* 使能CAN时钟和初始化相关引脚 */
void HAL_CAN_MspInit(CAN_HandleTypeDef* canHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(canHandle->Instance==CAN1)
{
/* USER CODE BEGIN CAN1_MspInit 0 */
/* USER CODE END CAN1_MspInit 0 */
/* CAN1 clock enable */
__HAL_RCC_CAN1_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
/**CAN GPIO Configuration
PA11 ------> CAN_RX
PA12 ------> CAN_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_11;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
GPIO_InitStruct.Pin = GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* USER CODE BEGIN CAN1_MspInit 1 */
/* USER CODE END CAN1_MspInit 1 */
}
}
void HAL_CAN_MspDeInit(CAN_HandleTypeDef* canHandle)
{
if(canHandle->Instance==CAN1)
{
/* USER CODE BEGIN CAN1_MspDeInit 0 */
/* USER CODE END CAN1_MspDeInit 0 */
/* Peripheral clock disable */
__HAL_RCC_CAN1_CLK_DISABLE();
/**CAN GPIO Configuration
PA11 ------> CAN_RX
PA12 ------> CAN_TX
*/
HAL_GPIO_DeInit(GPIOA, GPIO_PIN_11|GPIO_PIN_12);
/* USER CODE BEGIN CAN1_MspDeInit 1 */
/* USER CODE END CAN1_MspDeInit 1 */
}
}
/* USER CODE BEGIN 1 */
/* 发送消息数据函数 */
uint8_t can_send_message(uint32_t id,uint8_t *buf,uint8_t len)
{
uint32_t tx_mail; /* 数据发送邮箱 */
myCan_tx.RTR = CAN_RTR_DATA; /* 帧类型为数据帧 */
myCan_tx.IDE = CAN_ID_EXT; /* 帧格式为扩展数据帧 */
myCan_tx.ExtId = id; /* 扩展数据帧ID */
myCan_tx.DLC = len; /* 数据长度 */
/*如果三个发送邮箱都阻塞了就一直等待直到其中某个邮箱空闲*/
while((HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0)){
printf("没有邮箱空闲\r\n");
HAL_Delay(1000);
}
/* 哪个邮箱空闲,就选哪个邮箱作为发送邮箱 */
if ((hcan.Instance->TSR & CAN_TSR_TME0) != 0U){
tx_mail = CAN_TX_MAILBOX0;
} else if ((hcan.Instance->TSR & CAN_TSR_TME1) != 0U) {
tx_mail = CAN_TX_MAILBOX1;
} else if ((hcan.Instance->TSR & CAN_TSR_TME2) != RESET) {
tx_mail = CAN_TX_MAILBOX2;
}
/* 从buf中发送消息 */
if(HAL_CAN_AddTxMessage(&hcan,&myCan_tx,buf,&tx_mail) != HAL_OK){
return 1;
}
/* 等待数据发送完毕:当发送邮箱的3个邮箱都为空时,返回值为3 */
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3);
printf("邮箱为空\r\n");
return 0;
}
/*接收数据函数*/
uint8_t can_receive_message(uint8_t *buf)
{
/* 判断接收FIFO 0是否为空 */
if(HAL_CAN_GetRxFifoFillLevel(&hcan,CAN_RX_FIFO0) == 0){
return 0; /*为空返回0*/
}
HAL_CAN_GetRxMessage(&hcan,CAN_RX_FIFO0,&myCan_rx,buf); /* 接收消息到buf中 */
return myCan_rx.DLC; /*不为空返回数据长度*/
}
/* USER CODE END 1 */
can.h文件编写
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file can.h
* @brief This file contains all the function prototypes for
* the can.c file
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __CAN_H__
#define __CAN_H__
#ifdef __cplusplus
extern "C" {
#endif
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* USER CODE BEGIN Includes */
/* USER CODE END Includes */
extern CAN_HandleTypeDef hcan;
/* USER CODE BEGIN Private defines */
/* USER CODE END Private defines */
void MX_CAN_Init(void);
/* USER CODE BEGIN Prototypes */
/* 发送消息数据函数 */
uint8_t can_send_message(uint32_t id,uint8_t *buf,uint8_t len);
/*接收数据函数*/
uint8_t can_receive_message(uint8_t *buf);
/* USER CODE END Prototypes */
#ifdef __cplusplus
}
#endif
#endif /* __CAN_H__ */
main.c文件编写
启动CAN外设
发送数据
接收数据
/* USER CODE BEGIN Header */
/**
******************************************************************************
* @file : main.c
* @brief : Main program body
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "can.h"
#include "usart.h"
#include "gpio.h"
/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
/* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
/* USER CODE END PTD */
/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
/* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
/* USER CODE END PM */
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
/* USER CODE END PV */
/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */
/* USER CODE END PFP */
/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
uint8_t rcvlen = 0;
uint8_t rcvbuf[8] = {0};
uint8_t can = 0;
uint8_t sendbuf[8] = "zhu_tou";
/* 重写stdio.h文件中的prinft()里的fputc()函数 */
int fputc(int my_data,FILE *p)
{
unsigned char temp = my_data;
/* 改写后,使用printf()函数会将数据通过串口一发送出去 */
HAL_UART_Transmit(&huart1,&temp,1,0xffff); /* 0xfffff为最大超时时间 */
return my_data;
}
/* USER CODE END 0 */
/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_CAN_Init();
MX_USART1_UART_Init();
/* USER CODE BEGIN 2 */
printf("haozige\r\n"); /* 确认串口是否启动 */
HAL_CAN_Start(&hcan); /* 启动CAN外设 */
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){ /* 当按键被按下*/
HAL_Delay(50); //按键防抖
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_RESET){
//HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_8);
if(can_send_message(0x12345678,sendbuf,8)){
printf("发送失败\r\n");
} /* CAN发送数据,一次发送8字节 */
}
}
rcvlen = can_receive_message(rcvbuf); /* CAN接收数据 */
if(rcvlen){
printf("%s\r\n",rcvbuf); /* 把接收到的数据打印到串口上 */
}
HAL_Delay(100);
}
/* USER CODE END 3 */
}
/**
* @brief System Clock Configuration
* @retval None
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** Initializes the RCC Oscillators according to the specified parameters
* in the RCC_OscInitTypeDef structure.
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** Initializes the CPU, AHB and APB buses clocks
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/* USER CODE BEGIN 4 */
/* USER CODE END 4 */
/**
* @brief This function is executed in case of error occurrence.
* @retval None
*/
void Error_Handler(void)
{
/* USER CODE BEGIN Error_Handler_Debug */
/* User can add his own implementation to report the HAL error return state */
__disable_irq();
while (1)
{
}
/* USER CODE END Error_Handler_Debug */
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t *file, uint32_t line)
{
/* USER CODE BEGIN 6 */
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */