控制器局域网(Controller Area Network,CAN),是由德国BOSCH(博世)公司开发,是目前国际上应用最为广泛的现场总线之一。其特点是可拓展性好,可承受大量数据的高速通信,高度稳定可靠,因此常应用于汽车电子领域、工业自动化、医疗设备等高要求环境。
CAN总线有两个ISO国际标准:ISO11519 和ISO11898。
高速CAN主要应用在发动机、变速箱等对实时性、传输速度要求高的场景。低速CAN主要应用在车身控制系统等可靠性要求高的场景,低速CAN在断掉其任一导线后,仍可以继续接收数据,因此在汽车发生交通事故时,使用低速CAN能更大提高设备正常接收数据工作的可能性,提高安全性。
如图 23.1.1 所示,是低速CAN的拓扑结构图,如图 23.1.2 是高速CAN的拓扑结构图。低速CAN总线为开环,高速CAN总线为闭环,总线由CAN_H和CAN_L两根线组成,总线上可以挂多个节点设备。每个节点设备由CAN控制器和CAN收发器组成,CAN控制器通常作为外设集成在MPU/MCU上,而CAN收发器则需要外围添加芯片电路。
从两个网络拓扑结构可以看出,基于ISO11519标准的低速CAN,是一个“开环网络”,每根总线上个串联一个2.2KΩ的电阻;基于ISO11898标准的高速CAN,是一个“闭环网络”,总线的两端各需串联一个120Ω的电阻用于阻抗匹配,以减少回波反射。
类似RS485,CAN也使用差分信号传输数据。CAN总线使用CAN_H和CAN_L的电位差来表示数据电平。电位差分为显性电平和隐性电平,分别表示逻辑0和1。如图 23.1.3 所示,是低速CAN(ISO11519标准)的电平定义,如图 23.1.4 是高速CAN(ISO11898标准)的电平定义,两者物理层电气特性不一样,因此不能将它们连接在一起。可以看到当CAN_H和CAN_L电压相近,则表示隐性电平,对应逻辑1,当两个电压相差较大,表示显性电平,对应逻辑0。
CAN是一种基于消息广播模式的串行通信总线,即在同一时刻网络上所有节点监测到的数据是一致的,各节点根据报文ID来甄别是否是发给自己的报文。
CAN总线以“帧”(Frame)的形式进行通信。CAN 总线协议规定了5种帧,分别是数据帧、远程帧、错误帧、超载帧以及帧间隔,其中数据帧最常用,表 23.1.2 是各个帧的用途。
数据帧由七段组成,如图 23.1.5 所示。数据帧又分为标准帧(CAN2.0A)和扩展帧(CAN2.0B),主要体现在在仲裁段和控制段上。
当CAN总线网络中有多个CAN节点设备时,某一CAN设备发出数据帧,总线上所有设备(无过滤时)都获取该数据帧中仲裁段中的ID,如果是自己关注ID的数据,则获取数据段的内容,完成数据的传输。
STM32F103系列的CAN控制器(Basic Extended CAN,bxCAN),支持CAN 2.0A和CAN 2.0B Active版本协议。CAN 2.0A只能处理标准数据帧,扩展帧的内容会识别为错误;CAN 2.0B Active可以处理标准数据帧和扩展数据帧;CAN 2.0B Passive只能处理标准数据帧,扩展帧的内容会忽略。
STM32F103系列只有一个CAN控制器,STM32F105/STM32F107互联型有两个CAN控制器,互联型内部CAN控制器结构如图 23.1.6 所示,⑤是CAN2,STM32F103系列没有,先忽略。
①CAN1内核:包含各种控制、状态、配置寄存器。其中比较重要的是主控制寄存器(CAN_MCR)和位时序寄存器(CAN_BTR)。主控制寄存器主要控制CAN的工作模式,在后面设置CAN协议初始化时,实现对该寄存器的修改。位时序寄存器主要涉及CAN的工作速率,由于CAN是异步信号,同串口类似,需要收发双方提前统一通信速率。除此之外,为保证通信稳定,CAN采用“位同步”机制,实现对电平的正确采样。传输中的每位数据由四段组成:同步段(Synchronization Segment,SS)、传播时间段(Propagation TimeSegment,PTS)、相位缓冲段1(Phase Buffer Segment 1,PBS1)和相位缓冲段2(Phase Buffer Segment 2, PBS2)。每段又由多个位时序(Time Quantum,Tq)组成,如图 23.1.6 所示,为各段组成示意图。
假设CAN对应逻辑电平持续的时间为9Tq,即一位数据持续的时间为9Tq。其中SS段长度为1Tq(只能为1Tq),PTS段长度为2Tq(范围为18Tq),PSB1段长度为3Tq(范围为18Tq),PSB2段长度为3Tq(范围为2~8Tq)。采样点在PSB1和PSB2之间,调整各段的长度,即可对采样点位置进行调整,实现补偿准确采样。
如图 23.1.8 所示,为STM32F103系列的CAN控制器位时序,和标准CAN协议的位时序略有不同。STM32只有三段,同步段长度为1Tq(只能为1Tq),标准CAN协议中的PTS段和PSB1合并为位段1(范围为1-16Tq),标准CAN协议中的PSB2段对应位段2(范围为1-8Tq)。
当知道CAN控制器的工作时钟频率、tBS1和tBS2的长度时,即可计算出CAN传输的波特率,关系如下:
② 发送邮箱:STM32F103的CAN控制器有三个发送邮箱,可最多缓存三个待发送的报文。由传输调度负责决定邮箱报文的发送顺序。
③接收FIFO:STM32F103的CAN控制器有两个个接收FIFO来存储传入的数据,每个FIFO由三个邮箱存储三个接收到报文。
④:验收筛选器:STM32F105/STM32F107互联型有28个筛选器,STM32F103系列只有14个筛选器(编号0~13)。前面介绍CAN协议介绍到,在CAN总线网络中,总线上的所有设备都获取总线数据帧中ID,如果是自己关注的ID,则继续获取数据段的内容。当总线上报文过多时,每个CAN设备将频繁获取报文,消耗比较大。因此,提供筛选器实现选择性的获取报文,降低系统负担。
每个筛选器组由两个32位寄存器CAN_FxR1和CAN_FxR2组成。根据不同的实际需求,筛选器支持设置筛选范围和筛选模式。
筛选范围可设置为32位和16位,两种方式筛选的范围有所差异:
举个例子,如表 23.1.3 所示,ID为0xF,掩码为0x7FC。掩码位为1表示必须与ID一致,掩码位为0表示可不与ID一致,因此结果bit[1:0]为任意值,其它都需要与ID一致,则最后结果为11XX,即0xC~0xF之间的ID都可经过筛选器存入FIFO,其它则无法通过;
如图 23.1.9 所示,通过设置筛选范围和筛选模式进行组合,每个筛选器有四种情况。
①FSCx=1,FBMx=0:处于32位掩码模式,此时两个32位寄存器CAN_FxR1和CAN_FxR2,一个存放ID,一个存放掩码;
②FSCx=1,FBMx=1:处于32位列表模式,此时两个32位寄存器CAN_FxR1和CAN_FxR2,两个都存放ID,组成列表;
③FSCx=0,FBMx=0:处于16位掩码模式,此时两个32位寄存器CAN_FxR1和CAN_FxR2,它们各自低16位存放ID,高16位存放掩码;
②FSCx=0,FBMx=1:处于16位列表模式,此时两个32位寄存器CAN_FxR1和CAN_FxR2,它们各自低16位和高16位都存放ID,组成列表;
举个例子,假设CAN总线上有ID为0至99的100个报文,现在只需要ID为0-5的报文,筛选器该如何设置?首先设置筛选器组0处于32位掩码模式,ID为0x0,掩码为0x7FC,结果将筛选出0x0-0x3。接着设置筛选器组1处于32位列表模式,列表两个ID分别设为0x04和0x05。最后ID为0x0~0x05的报文将通过筛
关于CAN的基础介绍就到这里,读者重点理解CAN电气特性、数据帧结构、STM32的CAN控制器波特率和筛选器。
如图 23.2.1 为开发板CAN部分的原理图。U17为CAN收发器TJA1042芯片,该芯片将MCU的CAN控制器的逻辑电平转换成差分信号,发送到CAN总线中。
U17的1脚接MCU的CAN发送引脚(PB9),2脚接MCU的CAN接收引脚(PB8), 7脚、8脚为CAN输出引脚,上面挂了一个120欧的终端电阻,工作在高速CAN模式。
另外,CAN和RS485都是半双工的差分信号,需要两个设备连接测试。百问网制作了一个CAN/RS485互转模块,可以直接连接到100ASK系列开发板上,实现RS485的CAN的透传,同时验证、学习两个接口,该模块外形如图 23.2.2 所示。
该模块左边是RS485接口(波特率9600bps,无校验,8位数据位,1位停止位),右边是CAN接口(波特率500Kbps,屏蔽位模式,允许所有帧接收),中间是转换芯片,实现协议转换。由于CAN协议中包含有ID, 而RS485不存在ID。因此,当RS485转CAN时,模块会自动加上0x00的ID,当CAN转RS485时,RS485只会收到数据部分,扔掉ID部分。
实验目的:本实验通过RS485_CAN互转模块,实现RS485和CAN互传数据,让读者熟悉如何使用CAN。开发板的RS485首先发送数据,经过RS485_CAN互转模块变为CAN信号,传入开发板CAN接口;开发板CAN接口接到数据后,CAN再发送数据,经过RS485_CAN互转模块变为RS485信号,传入开发板R485接口。
代码段 23.3.1 CAN 协议初始化(driver_can.c)
/*
* 函数名:void CAN_Init(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:初始化 CAN1
*/
void CAN_Init(void) {
CAN_FilterTypeDef sFilterConfig;
hcan.Instance = CAN1;
/* 配置 CAN 的基本参数 */
hcan.Init.Prescaler = 8; // 预分频,范围(1~1024)
hcan.Init.Mode = CAN_MODE_NORMAL; // 正常工作模式
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; // 再次同步跳跃宽度为 1Tq
hcan.Init.TimeSeg1 = CAN_BS1_6TQ; // 位段 1(BS1)的长度为 6Tq
hcan.Init.TimeSeg2 = CAN_BS2_2TQ; // 位段 2(BS2)的长度为 2Tq
hcan.Init.TimeTriggeredMode = DISABLE; // 禁止时间触发通信模式
hcan.Init.AutoBusOff = DISABLE; // 禁止总线自动关闭
hcan.Init.AutoWakeUp = DISABLE; // 禁止自动唤醒
hcan.Init.AutoRetransmission = ENABLE; // 使能自动重传
hcan.Init.ReceiveFifoLocked = DISABLE; // 禁止接收 FIFO 锁定
hcan.Init.TransmitFifoPriority = DISABLE; // 禁止传输 FIFO 优先级
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
Error_Handler(); }
/* 配置 CAN 的筛选器,此处全部接收,不做过滤 */
sFilterConfig.FilterBank = 0; // 选择筛选器组 0
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 设置为掩码模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32 位长度
sFilterConfig.FilterIdHigh = 0x0000; // ID 高字节
sFilterConfig.FilterIdLow = 0x0000; // ID 低字节
sFilterConfig.FilterMaskIdHigh = 0x0000; // 掩码高字节
sFilterConfig.FilterMaskIdLow = 0x0000; // 掩码低字节
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 过滤器关联 FIFO
sFilterConfig.FilterActivation = ENABLE; // 使能筛选器
if(HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
{
Error_Handler(); }
// 启用 CAN
if(HAL_CAN_Start(&hcan) != HAL_OK)
{
Error_Handler(); }
// 使能 CAN 接收 FIFO0 中断
if(HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
Error_Handler(); } }
12~25行:设置CAN协议参数;
27~30行:初始化前面设置的CAN参数,同时会调用CAN硬件相关初始化函数“HAL_CAN_MspInit()”;
32~41行:设置CAN筛选器;
43~46行:配置前面设置的筛选器;
48~52行:启动CAN;
54~58行:使能CAN接收FIFO0的中断;注意这里使能的是FIFO0,需要和前面设置筛选器的FIFO保持一致;这里可以设置三种类型FIFO中断,分别为“CAN_IT_RX_FIFO0_MSG_PENDING”FIFO接收到数据就
产生中断、“CAN_IT_RX_FIFO0_FULL”FIFO接收满后产生中断“CAN_IT_RX_FIFO0_OVERRUN”FIFO溢出产生中断;
前面“HAL_CAN_Init()”会调用CAN硬件相关初始化函数“HAL_CAN_MspInit()”,该函数需要我们自己完善,如代码段 23.3.2 所示。
代码段 23.3.2 CAN 硬件初始化(driver_can.c)
/*
* 函数名:void HAL_CAN_MspInit(CAN_HandleTypeDef *hcanhandle)
* 输入参数:hcanhandle-CAN 句柄
* 输出参数:无
* 返回值:无
* 函数作用:初始化 CAN 硬件相关
*/
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcanhandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_CAN1_CLK_ENABLE();
__HAL_RCC_AFIO_CLK_ENABLE(); // 重映射时钟使能
__HAL_AFIO_REMAP_CAN1_2(); // 重映射
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //PB9 CAN TX
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //PB8 CAN RX
// HAL_Init()里默认优先级分组 4
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0); // 设置 CAN 接收中断的优先级和使能
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
HAL_NVIC_SetPriority(CAN1_TX_IRQn, 1, 0); // 设置 CAN 发送中断的优先级和使能
HAL_NVIC_EnableIRQ(CAN1_TX_IRQn); }
需要注意,PB8、PB9如果用于CAN引脚,不是引脚典型复用,而是需要引脚复用重映射,如图 23.3.1所示为《数据手册》部分引脚描述截图。因此代码段 23.3.2 中14~15行需要先使能重映射时钟,再重映射CAN功能。
代码段 23.3.3 CAN 发送函数(driver_can.c)
/*
* 函数名:void CAN_Transmit(uint16_t ID, uint8_t *pdata, uint8_t length)
* 输入参数:ID -> CAN 发送报文的 ID
* pdata -> 发送报文的首地址
* length-> 发送报文的个数,最大 8 个字节
* 输出参数:无
* 返回值:无
* 函数作用:CAN1 发送函数
*/
void CAN_Transmit(uint16_t ID, uint8_t *pdata, uint8_t length)
{
uint32_t tx_mailbox = 0;
if(length>8) {
return ; }
TxHeader.StdId = ID; // 标准标识符
TxHeader.ExtId = 0; // 扩展标识符
TxHeader.IDE = CAN_ID_STD; // 帧模式(标准帧或扩展帧)
TxHeader.RTR = CAN_RTR_DATA; // 帧类型(数据帧或远程帧)
TxHeader.DLC = length; // 数据长度
TxHeader.TransmitGlobalTime = DISABLE; // 不发送时间标记
if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, pdata, &tx_mailbox) != HAL_OK)
{
Error_Handler(); } }
12~23行:准备发送报文;
代码段 23.3.1 CAN 协议初始化(driver_can.c)
/*
* 函数名:void CAN_Init(void)
* 输入参数:无
* 输出参数:无
* 返回值:无
* 函数作用:初始化 CAN1
*/
void CAN_Init(void) {
CAN_FilterTypeDef sFilterConfig;
hcan.Instance = CAN1;
/* 配置 CAN 的基本参数 */
hcan.Init.Prescaler = 8; // 预分频,范围(1~1024)
hcan.Init.Mode = CAN_MODE_NORMAL; // 正常工作模式
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; // 再次同步跳跃宽度为 1Tq
hcan.Init.TimeSeg1 = CAN_BS1_6TQ; // 位段 1(BS1)的长度为 6Tq
hcan.Init.TimeSeg2 = CAN_BS2_2TQ; // 位段 2(BS2)的长度为 2Tq
hcan.Init.TimeTriggeredMode = DISABLE; // 禁止时间触发通信模式
hcan.Init.AutoBusOff = DISABLE; // 禁止总线自动关闭
hcan.Init.AutoWakeUp = DISABLE; // 禁止自动唤醒
hcan.Init.AutoRetransmission = ENABLE; // 使能自动重传
hcan.Init.ReceiveFifoLocked = DISABLE; // 禁止接收 FIFO 锁定
hcan.Init.TransmitFifoPriority = DISABLE; // 禁止传输 FIFO 优先级
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
Error_Handler(); }
/* 配置 CAN 的筛选器,此处全部接收,不做过滤 */
sFilterConfig.FilterBank = 0; // 选择筛选器组 0
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 设置为掩码模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 32 位长度
sFilterConfig.FilterIdHigh = 0x0000; // ID 高字节
sFilterConfig.FilterIdLow = 0x0000; // ID 低字节
sFilterConfig.FilterMaskIdHigh = 0x0000; // 掩码高字节
sFilterConfig.FilterMaskIdLow = 0x0000; // 掩码低字节
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 过滤器关联 FIFO
sFilterConfig.FilterActivation = ENABLE; // 使能筛选器
if(HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
{
Error_Handler(); }
// 启用 CAN
if(HAL_CAN_Start(&hcan) != HAL_OK)
{
Error_Handler(); }
// 使能 CAN 接收 FIFO0 中断
if(HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
Error_Handler(); } }
12~25行:设置CAN协议参数;
27~30行:初始化前面设置的CAN参数,同时会调用CAN硬件相关初始化函数“HAL_CAN_MspInit()”;
32~41行:设置CAN筛选器;
43~46行:配置前面设置的筛选器;
48~52行:启动CAN;
54~58行:使能CAN接收FIFO0的中断;注意这里使能的是FIFO0,需要和前面设置筛选器的FIFO保持一致;这里可以设置三种类型FIFO中断,分别为:“CAN_IT_RX_FIFO0_MSG_PENDING”FIFO接收到数据就产生中断、“CAN_IT_RX_FIFO0_FULL”FIFO接收满后产生中断、“CAN_IT_RX_FIFO0_OVERRUN”FIFO溢出产生中断;
前面“HAL_CAN_Init()”会调用CAN硬件相关初始化函数“HAL_CAN_MspInit()”,该函数需要我们自己完善,如代码段 23.3.2 所示。
代码段 23.3.2 CAN 硬件初始化(driver_can.c)
/*
* 函数名:void HAL_CAN_MspInit(CAN_HandleTypeDef *hcanhandle)
* 输入参数:hcanhandle-CAN 句柄
* 输出参数:无
* 返回值:无
* 函数作用:初始化 CAN 硬件相关
*/
void HAL_CAN_MspInit(CAN_HandleTypeDef *hcanhandle)
{
GPIO_InitTypeDef GPIO_InitStruct;
__HAL_RCC_GPIOB_CLK_ENABLE();
__HAL_RCC_CAN1_CLK_ENABLE();
__HAL_RCC_AFIO_CLK_ENABLE(); // 重映射时钟使能
__HAL_AFIO_REMAP_CAN1_2(); // 重映射
GPIO_InitStruct.Pin = GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //PB9 CAN TX
GPIO_InitStruct.Pin = GPIO_PIN_8;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //PB8 CAN RX
// HAL_Init()里默认优先级分组 4
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 1, 0); // 设置 CAN 接收中断的优先级和使能
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
HAL_NVIC_SetPriority(CAN1_TX_IRQn, 1, 0); // 设置 CAN 发送中断的优先级和使能
HAL_NVIC_EnableIRQ(CAN1_TX_IRQn); }
需要注意,PB8、PB9如果用于CAN引脚,不是引脚典型复用,而是需要引脚复用重映射,如图 23.3.1所示为《数据手册》部分引脚描述截图。因此代码段 23.3.2 中14~15行需要先使能重映射时钟,再重映射CAN功能。
代码段 23.3.3 CAN 发送函数(driver_can.c)
/*
* 函数名:void CAN_Transmit(uint16_t ID, uint8_t *pdata, uint8_t length)
* 输入参数:ID -> CAN 发送报文的 ID
* pdata -> 发送报文的首地址
* length-> 发送报文的个数,最大 8 个字节
* 输出参数:无
* 返回值:无
* 函数作用:CAN1 发送函数
*/
void CAN_Transmit(uint16_t ID, uint8_t *pdata, uint8_t length)
{
uint32_t tx_mailbox = 0;
if(length>8) {
return ; }
TxHeader.StdId = ID; // 标准标识符
TxHeader.ExtId = 0; // 扩展标识符
TxHeader.IDE = CAN_ID_STD; // 帧模式(标准帧或扩展帧)
TxHeader.RTR = CAN_RTR_DATA; // 帧类型(数据帧或远程帧)
TxHeader.DLC = length; // 数据长度
TxHeader.TransmitGlobalTime = DISABLE; // 不发送时间标记
if (HAL_CAN_AddTxMessage(&hcan, &TxHeader, pdata, &tx_mailbox) != HAL_OK)
{
Error_Handler(); } }
12~23行:准备发送报文;
25~28行:调用“HAL_CAN_AddTxMessage()”发送报文;
前面设置了CAN中断和中断优先级,当CAN控制器,发送完成或接收完成时,都将跳转到中断向量表的对应位置,执行中断处理函数,如代码段 23.3.4 所示。
代码段 23.3.4 CAN 中断处理函数(driver_can.c)
//CAN1 发送完成后,进入该中断
void CAN1_TX0_IRQHandler(void) {
HAL_CAN_IRQHandler(&hcan); }
//CAN1 接收完成后,进入该中断
void CAN1_RX0_IRQHandler(void) {
HAL_CAN_IRQHandler(&hcan); // 回调 HAL_CAN_RxFifo0MsgPendingCallback()
}
/*
* 函数名:void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
* 输入参数:CanHandle -> CAN 句柄
* 输出参数:无
* 返回值:无
* 函数作用:CAN1 接收回调函数
*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *CanHandle)
{
/* Get RX message */
if (HAL_CAN_GetRxMessage(CanHandle, CAN_RX_FIFO0, &RxHeader, can_msg.rx_msg) != HAL_OK)
{
Error_Handler(); }
can_msg.ID = RxHeader.StdId; // 获取标准标识符
can_msg.length = RxHeader.DLC; // 获取数据长度
CAN_SetRxFlag(1); // 设置接收标志位 }
本实验中涉及的RS485、串口打印、按键等实现,参考之前相关章节的讲解。
代码段 23.3.5 主函数控制逻辑(main.c)
// 初始化 CAN
CAN_Init();
// 初始化 USART1,设置波特率为 115200 bps
DEBUG_USART_Init(115200);
// 初始化 USART2,设置波特率为 9600 bps
RS485_Init(9600);
// 初始化按键
KeyInit();
// 在 windows 下字符串\n\r 表示回车
// 如果工程在编译下面这句中文的时候报错,请在“Option for target”->"C/C++"->"Misc Controls"添加“ --locale=english”
printf("**********************************************\n\r");
printf("-->百问科技 www.100ask.net\n\r");
printf("-->CAN 收发实验\n\r");
printf("**********************************************\n\r");
while(1) {
// RS485 开始发送数据给 CAN
if(step==0) {
// 初始化 RS485 发送信息
RS485_Msg.length = 8;
for(i=0; i<8; i++)
RS485_Msg.tx_data[i] = i;
RS485_Tx((uint8_t*)&RS485_Msg.tx_data[0], 8);
//如果 CAN 收到了帧报文,即 RS485 的数据帧
if(CAN_GetRxFlag()) {
CAN_SetRxFlag(0);
step = 1;
printf("\n\r");
printf("* RS485 发送数据: \n\r");
printf("Tx Data: ");
for(i=0; i<can_msg.length; i++) {
printf("0x%x ", RS485_Msg.tx_data[i]); }
printf("\n\r");
printf("* CAN 接收报文内容: \n\r");
printf("ID: 0x%x \n\r", can_msg.ID);
printf("Rx Data: ");
for(i=0; i<can_msg.length; i++) {
printf("0x%x ", can_msg.rx_msg[i]); }
printf("\n\r"); } }
// CAN 发送帧 ID+帧数据给 RS485
if(step==1) {
// 初始化 CAN 发送信息
can_msg.ID = 0x306;
can_msg.length = 8;
for(i=0; i<8; i++)
can_msg.tx_msg[i] = i|0xF0;
CAN_Transmit(can_msg.ID, can_msg.tx_msg, can_msg.length);
if(RS485_Rx(RS485_Msg.rx_data, RS485_Msg.length)) {
step = 0xFF;
printf("\n\r");
printf("* CAN 发送报文内容:\n\r");
printf("ID: 0x%x \n\r",can_msg.ID);
printf("Tx Data: ");
for(i=0; i<can_msg.length; i++) {
printf("0x%x ", can_msg.tx_msg[i]); }
printf("\n\r");
printf("* RS485 接收数据:\n\r");
printf("Rx Data: ");
for(i=0; i<RS485_Msg.length; i++) {
printf("0x%x ", RS485_Msg.rx_data[i]); }
printf("\n\r");
} } }
本实验对应配套资料的“5_程序源码\15_通信—CAN\”。打开工程后,编译,下载。按图 23.4.1 所示,将开发板、RS485_CAN互转模块连接,开发板的CAN接模块的CAN,开发板的RS485接模块的RS485,且线都不交叉,注意此时的J11(蓝色拨码开关)的1脚拨为ON。
按下按键KEY1(KEY_U),即可看到串口如图 23.4.2 所示。首先RS485发送数据,CAN接收到相同的数据,RS485_CAN互换模块为CAN添加了0x0的ID。随便CAN发送数据,RS485接收到相同的数据,CAN报文的ID被RS485_CAN互换模块省略。
百问网技术论坛:
http://bbs.100ask.net/
百问网嵌入式视频官网:
https://www.100ask.net/index
百问网开发板:
淘宝:https://100ask.taobao.com/
天猫:https://weidongshan.tmall.com/
技术交流群2(鸿蒙开发/Linux/嵌入式/驱动/资料下载)
QQ群:752871361
单片机-嵌入式Linux交流群:
QQ群:536785813