CAN通信
目前(2021年6月)我用到的CAN电路原理图:
- 判断电路有没有接好,其中一个标准就是,测量接入的电阻阻值是不是60Ω。
- 上图两个60.4Ω之间,可以接一个电容到GND。
1. Bit Timing(位时序)
- 用于确定波特率。
CAN 具有 处理长总线长度(与比特率相比)中发现的时间延迟 和 处理总线上节点的时钟晶体频率差异的高级功能。
位时序的选择非常重要,因为它决定了位速率、采样点和重新同步的能力。 - 位段(博世标准)
每个比特分为四段——同步段(synchronisation)、传播段(propagation)和相位段一和二(phase1,2)。
每一段由一个或多个时间量组成。时间量是从 CAN 控制器时钟导出的固定时间量,具有预定标因子。 - 同步段(Synch_Seg)
同步段用于同步总线上的各个节点。当在总线上发送一个比特时,预计前沿在该段内。 该段固定为 一个时间量程长。 - 传播段(Prop_Seg)
需要传播段来补偿总线中的延迟。段大小在 1 到 8 个时间量程之间可编程。 - 相位段1(Phase_Seg1),相位段2(Phase_Seg2)
可以通过重新同步来延长或缩短这些段。 - 采样点
通常每一位只有一个采样点,在这种情况下,采样点在TSEG1和TSEG2之间的边缘。
(但是,有些CAN控制器也可以对每一位采样3次。这种情况下,该位将被采样3次 在一行中,最后一个样本取自 TSEG1 和 TSEG2 之间的边缘。三个样本应该只用于相对较慢的波特率。)
1.1 波特率和采样点的计算
-
波特率
总线的波特率可以通过下式计算:
-
采样点
样品前量子=TSEG1+1
样品后量子=TSEG2
采样点通常以位时间的百分比给出。是:(TSEG1+1)/(TSEG1+1+TSEG2)
2. CAN波特率配置
20220727新增补充。
2.1 stm32f107vc
- 配置为 1000K波特率。
- 配置为 500K波特率。
CAN_HandleTypeDef hcan1;
CAN_HandleTypeDef hcan2;
void CAN1_Init(void)
{
hcan1.Instance = CAN1;
hcan1.Init.Prescaler = 6;//6--1000K 12--500K
hcan1.Init.Mode = CAN_MODE_NORMAL;
hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan1.Init.TimeSeg1 = CAN_BS1_3TQ;
hcan1.Init.TimeSeg2 = CAN_BS2_2TQ;
hcan1.Init.TimeTriggeredMode = DISABLE;
hcan1.Init.AutoBusOff = DISABLE;
hcan1.Init.AutoWakeUp = DISABLE;
hcan1.Init.AutoRetransmission = DISABLE;
hcan1.Init.ReceiveFifoLocked = DISABLE;
hcan1.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan1) != HAL_OK)
{
Error_Handler();
}
}
void CAN2_Init(void)
{
hcan2.Instance = CAN2;
hcan2.Init.Prescaler = 6;//6--1000K 12--500K
hcan2.Init.Mode = CAN_MODE_NORMAL;
hcan2.Init.SyncJumpWidth = CAN_SJW_1TQ;
hcan2.Init.TimeSeg1 = CAN_BS1_3TQ;
hcan2.Init.TimeSeg2 = CAN_BS2_2TQ;
hcan2.Init.TimeTriggeredMode = DISABLE;
hcan2.Init.AutoBusOff = DISABLE;
hcan2.Init.AutoWakeUp = DISABLE;
hcan2.Init.AutoRetransmission = DISABLE;
hcan2.Init.ReceiveFifoLocked = DISABLE;
hcan2.Init.TransmitFifoPriority = DISABLE;
if (HAL_CAN_Init(&hcan2) != HAL_OK)
{
Error_Handler();
}
}
2.2 HHD32f107
//CAN_BAUD_1M CAN_BAUD_500K CAN_BAUD_250K
#define PROJ_CAN_SRC_NODE (0x01)
can_init(CAN1, CAN_BAUD_1M, PROJ_CAN_SRC_NODE, 0x0000000);// can_init(CAN1, CAN_BAUD_1M, 0x7ff, 0x00);
can_init(CAN2, CAN_BAUD_1M, PROJ_CAN_SRC_NODE, 0x0000000);
3. CAN数据接收设置
针对两种MCU的过滤器设置。
stm32f105
网上全是参考代码,这里直接贴上我的过滤器代码
- 列表模式,CAN1 32位ID过滤,CAN2 16位ID过滤
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDLIST; //设为列表模式
//CAN1----------------------------------------------
sFilterConfig.FilterFIFOAssignment = CAN1FIFO; //接收到的报文放入到FIFO0中
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.SlaveStartFilterBank = 14;
//过滤器0
sFilterConfig.FilterBank = 0;
sFilterConfig.FilterIdHigh = ((ID1<<3)>>16)&0xffff;
sFilterConfig.FilterIdLow = ((ID1<<3)&0xffff)|CAN_ID_EXT;
sFilterConfig.FilterMaskIdHigh =((ID2<<3)>>16)&0xffff;
sFilterConfig.FilterMaskIdLow =((ID2<<3)&0xffff)|CAN_ID_EXT;
HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
//过滤器1
sFilterConfig.FilterBank = 1;
sFilterConfig.FilterIdHigh = ((ID3<<3)>>16)&0xffff;
sFilterConfig.FilterIdLow = ((ID3<<3)&0xffff)|CAN_ID_EXT;
// sFilterConfig.FilterMaskIdHigh = ((ID4<<3)>>16)&0xffff;
// sFilterConfig.FilterMaskIdLow = ((ID4<<3)&0xffff)|CAN_ID_EXT;
// HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
//CAN2----------------------------------------
sFilterConfig.FilterFIFOAssignment = CAN2FIFO;
sFilterConfig.FilterScale = CAN_FILTERSCALE_16BIT;
sFilterConfig.FilterBank = 14;
sFilterConfig.FilterIdHigh = 0x020 <<5;
sFilterConfig.FilterIdLow = 0x021 <<5;
sFilterConfig.FilterMaskIdHigh = 0x022<<5;
sFilterConfig.FilterMaskIdLow = 0x023 <<5;
HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig);
//过滤器15
sFilterConfig.FilterBank = 15;
sFilterConfig.FilterIdHigh = 0x030 <<5;
sFilterConfig.FilterIdLow = 0x031 <<5;
sFilterConfig.FilterMaskIdHigh = 0x032<<5;
sFilterConfig.FilterMaskIdLow = 0x033 <<5;
HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig);
HAL_CAN_Start(&hcan1);
HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);
HAL_CAN_Start(&hcan2);
HAL_CAN_ActivateNotification(&hcan2, CAN_IT_RX_FIFO1_MSG_PENDING);
- 如果需要接受全部帧,不进行硬件过滤。如下 所示:
void CAN1_recvALL(void)
{
CAN_FilterTypeDef sFilterConfig;
TxMeg.IDE=CAN_ID_EXT; //扩展帧
TxMeg.RTR=CAN_RTR_DATA;//数据帧
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; //设为mask模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
//sFilterConfig.FilterFIFOAssignment = CAN1FIFO; //接收到的报文放入到FIFO0中
sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO0; //接收到的报文放入到FIFO0中
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.FilterBank = 0; //过滤器0
sFilterConfig.FilterIdHigh = 0x0000; //基本ID放入到STID中
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0;//这里设置接收任意ID的CAN数据
sFilterConfig.FilterMaskIdLow = 0;//0表示接收来自发送端任意ID的数据
sFilterConfig.SlaveStartFilterBank = 14;
HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig);
}
void CAN2_recvALL(void)
{
CAN_FilterTypeDef sFilterConfig;
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; //设为mask模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT;
sFilterConfig.FilterFIFOAssignment = CAN_FILTER_FIFO1;
sFilterConfig.FilterActivation = ENABLE;
sFilterConfig.FilterBank = 14;
sFilterConfig.FilterIdHigh = 0x0000; //基本ID放入到STID中
sFilterConfig.FilterIdLow = 0x0000;
sFilterConfig.FilterMaskIdHigh = 0;//这里设置接收任意ID的CAN数据
sFilterConfig.FilterMaskIdLow = 0;//0表示接收来自发送端任意ID的数据
//CAN2 过滤器设置
HAL_CAN_ConfigFilter(&hcan2, &sFilterConfig);
}
国产芯片HHD32107
- 国产芯片文件资料给的不全,且有一些不严谨的地方。只能够按照对stm32 can的理解,来理解国产芯片。
- 资料中显示,该国产芯片,具有双CAN。但是未具体介绍过滤器是如何使用的。实际使用中发现,can2不能正常的接收数据和发送数据,因而只能对can1进行调试。
- can1 看起来仅有一个过滤器,实验发现,过滤器实际处于mask模式下,因为项目需要用到的ID比较简单,没有统一的ID特征位,不需要再用mask过滤一遍,索性mask寄存器AMR全部设置为无效位,硬件不过滤。全部接收。最终实行软件代码过滤。
- 能正常工作,就不要去动他了。
// 设置滤波器
can->MOD.bit.AFM = 0x01; //1 数据接受采用 4-byte 过滤器(32位过滤器)
//0 数据接受采用 2 个 shoter 过滤器(2个16位过滤器)
// 设置接收帧过滤,可以接收任何标识符
/*
ACR/AMR屏蔽字属性:
ACR用于指定CAN ID二进制中关注位和忽略位;
AMR用于规定ACR中那些二进制位生效,哪些无效.AMR中0代表有效位,1代表无效位;
*/
// Acceptance Code Register (注册两个ID,实际不需要)
can->DATAINFO.FILTER.ACR[0].all = ((filterID << 5) >> 8 ) & 0xFF; //Identifier
can->DATAINFO.FILTER.ACR[1].all = (filterID << 5) &0xE0 ; //Identifier
can->DATAINFO.FILTER.ACR[2].all = 0xFF;
can->DATAINFO.FILTER.ACR[3].all = 0xFF;
//Acceptance Mask Register (mask设置,不对任何位进行判定)
can->DATAINFO.FILTER.AMR[0].all = 0xFF;//((mask << 5) >> 8 ) & 0xFF;
can->DATAINFO.FILTER.AMR[1].all = 0xFF;//(mask << 5) &0xE0 ;
can->DATAINFO.FILTER.AMR[2].all = 0xff;
can->DATAINFO.FILTER.AMR[3].all = 0xff;
CAN的两种标准
CAN官网入口
我的阿里云参考资料(文件已上传,当前阿里云盘没有分享功能)
- 2.0A 标准。标识符ID 长度为11bit 。
- 2.0B 标准。标识符ID 长度支持11位和29bit。
注意:在编写MCU程序时会发现,如果CAN收发芯片,没有接在一个总线中,can驱动代码会卡在等待的死循环中。关于这一点,我们在添加了实时系统之后,在can等待循环中,需要添加系统的delay函数,用以自动切换线程任务。
实际数据帧波形图解析
- 以2.0A标准帧为例,发送数据,11bit的ID数值为1,发送8个字节的数据(0x12,0x23,0x34,0x45,0x56,0x67,0x78,0x89)。
- 电表笔 高接CANH,低接CANL,得到的高电平表示0,低电平表示1。
- 关于速率,当前采用的是500Kbit的速率,在示波器上看,也就是2us一个bit。下图中记录了原始的高低电平。
- 通过示波器观察每次can总线上的数据,会发现“**位填充”**现象,即总线上,5bit持续同样的电平,发送方会在后面补充1bit的反向电平。