目录
一、CAN通信简介
二、CAN数据帧类型
三、格式帧
四、位同步
传输数据时可能遇到的问题
最小时间单位
硬同步
再同步
波特率的计算
=============STM32中的CAN外设=============
一、原理图
二、标识符筛选
三、配置单个邮箱(正常模式或自发自收只需要修改模式)
四、如何配置两个邮箱(用于接收两个电机的数据)
一、CAN通信简介
can通信只有两根信号线(CAN High)和CAN_L(CAN Low),所有设备都搭载在这两根信号线上,可以实现多设备之间的高速实时的通信。其中每个设备都可以是主机/从机,都有对can总线的控制权。
can通信使用差分信号,不容易受干扰。当两根通信线的电压相同时,属于隐形电平1,当CANH被拉高而CANL被拉低时,属于显性电平0。在没有进行通信时,can总线表现为隐性电平1,当需要进行数据传输时,有且只有一个设备对can总线进行控制,其他设备选择是否接收。(当出现错误、过载或者应答时,其他设备才可以对总线进行控制)
二、CAN数据帧类型
这里主要了解的是数据帧。
三、格式帧
SOF:帧起始,数据传输开始时,总线从隐性电平1转变成显性电平0;
ID:11位设备ID号,表示设备信息,用于仲裁;
RTR:远程传输请求,用于区分数据帧(0)和遥控帧(1)(数据帧优先级高)
IDE:区分标准格式帧和扩展格式帧(0:为标准格式)
RB0:保留位(默认逻辑0)
DLC:4位,表示数据长度(字节),最多表示8字节,即64位;
CRC:校验位,检测数据是否错误;
CRC界定符:恒为隐性1,表示发送结束;
ACK:应答处理,发送端表现为隐性1,如果有设备接收了数据,那个设备会表现为显性电平1,此时整个can总线表现为显性电平;
ACK界定符:为隐性1,表示应答结束;
EOF:帧结束,为7位的隐性电平1;
SRR:用于代替RTR,真正的RTR在扩展ID后面,由于标准格式优先级高于扩展格式优先级,所以这一位虽然没用,但是必须为隐性1;
RB1,RB0:保留位(总是显性电平)
关于位填充:
当发送数据连续五个位是相同的时,会自动发送一个相反位的数据,接收方接收时,也会自动去除这个位。
关于仲裁段:
如果有两个设备同时开始发送数据,则会进行仲裁,ID号小的会在此获胜,继续数据发送,ID号大的则会退出。如果ID号相同,则标准格式帧优先。
四、位同步
CAN总线没有时钟线,总线上的所有设备通过约定波特率的方式确定每一个数据位的时长;
发送方以约定的位时长每隔固定时间输出一个数据位;
接收方以约定的位时长每隔固定时间采样总线的电平,输入一个数据位;
理想状态下,接收方能依次采样到发送方发出的每个数据位,且采样点位于数据位中心附近。
传输数据时可能遇到的问题
一个是接收设备的采样点可能位于跳变沿周围,导致采样信号不准确;一个是接收设备和发送设备的时钟有一点点偏差,导致采样信号不准确。
为了避免这两个问题,有硬同步和再同步两个方法。
最小时间单位
为了灵活调整每个采样点的位置,使采样点对齐数据位中心附近,CAN总线传播时间段对每一个数据位的时长进行了更细的划分,分为同步段(SS)(PTS)、相位缓冲段1(PBS1)和相位缓冲段2(PBS2)每个段又由若干个最小时间单位(Tq)构成。后面三个数据可以自己指定长度。
其中PTS用于处理网络延迟,PBS1和PBS2长度可以改变,用于再同步。
硬同步
当有设备需要发送数据时,它会选择在自身时钟周期的ss段进行发送,此时其他设备接收到跳变信号,会将自己的时钟之间调整到ss段,实现同步。
再同步
为了解决设备时钟有偏差的问题,接收设备可以对自己的时间进行补偿,通过补偿宽度值(SWJ)加长PBS1或缩短PBS2对时序进行补偿。
波特率的计算
=============STM32中的CAN外设=============
一、原理图
CAN有三个发送邮箱,可以对要发送的消息进行缓存。
CAN有14个过滤器,可以对接收的报文ID进行过滤,通过过滤器的报文才会被存入接收FIFO。
接收FIFO有两个,每个有三级深度,也是缓存的功能,可以配置重要的报文进FIFO0,不重要的进FIFO1,防止重要的消息丢失。
二、标识符筛选
(1)ID表示需要接收的ID号,在掩码模式下,掩码位为1,则只能接收与ID相同的位一致的ID号,掩码位为0,则没有要求
(2)只有匹配两个ID的帧才能被筛选
(3)相当于(1)中ID低16位为ID,高16位为掩码,掩码低16位为ID,高16位为掩码
(4)相同
列表模式相当于所有位都匹配的掩码模式
三、配置单个邮箱(正常模式或自发自收只需要修改模式)
1、在CubeMX中的配置
配置传输速率位1Mbps,(根据自己的时钟来)
设置传输模式
开启接收中断
2、代码(普通模式或回环模式代码相同)
初始化
HAL_CAN_Start(&hcan1);//打开CAN
CAN1_Filter_Init();//接收初始化
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)//开启中断
{
Error_Handler();
}
can.c中配置宏
CANTxMsg_t TxMsg; //定义发送邮件实体
CANRxMsg_t RxMsg; //定义接收邮件实体
uint8_t rcvdFlag=0; //接收标志位
can.c中编写的函数
//筛选器配置
void CAN1_Filter_Init(void)
{
CAN_FilterTypeDef CAN1_FilerConf;
/*筛选器具体的Id要求(配合掩码使用)
比如:下面的掩码为 0x0101,即二进制0000|0001|0000|0001,表示我只关心从右数第一位,以及第9位。
因此当具体Id要求为 0x0000,即二进制0000|0000|0000|0000。
结合掩码实际就是筛选器最终允许通过的Id为
xxxx|xxx0|xxxx|xxx0 (x表示0或者1都可以)
因此具体筛选器允许的Id范围是需要结合掩码来看的
*/
CAN1_FilerConf.FilterIdHigh=0X0000 << 5; //具体Id要求高16位
CAN1_FilerConf.FilterIdLow=0X0000 << 5; //具体Id要求低16位
//高16位左移五位即为标准id号
CAN1_FilerConf.FilterMaskIdHigh=0X0000; //掩码高16位全设置为0,表示对所有位报文Id高16位都不关心
CAN1_FilerConf.FilterMaskIdLow=0X0000; //掩码低16位全设置为0,表示对所有位报文Id低16位都不关心
CAN1_FilerConf.FilterFIFOAssignment=CAN_FILTER_FIFO0; //筛选器接收到的报文放入到FIFO0中,即为接收邮箱0
CAN1_FilerConf.FilterActivation=ENABLE; //筛选器使能(开启)
CAN1_FilerConf.FilterMode=CAN_FILTERMODE_IDMASK; //筛选器掩码模式
CAN1_FilerConf.FilterScale=CAN_FILTERSCALE_32BIT; //掩码用32位表示
/*这里说明一下为什么填0
首先一个筛选器有28个组。一块stm32板子的can外设共用一个can外设
比如F4系列基本都有两个can模块,如果只用一个can的时候,我们FilterBank可选0-13,因此下面就是0,其实填0-13都可以。
如果用两个can,则要分配两个筛选器组。一个是FilterBank
则
can1的筛选器组选择0-13。如CAN1_FilerConf.FilterBank=0;
can2的筛选器组选择14-27。如CAN1_FilerConf.SlaveStartFilterBank=14;
*/
CAN1_FilerConf.FilterBank=0;
CAN1_FilerConf.SlaveStartFilterBank=14;
/* 此处&hcan1指明是can1的筛选器配置,但实际上can1和can2的筛选器都配置好了。因为两个can是共用的。但是接收FIFO每个CAN都有独立的两个。
这是因为STM32的双路CAN共用过滤器组,
而且过滤器组寄存器与CAN1配置寄存器在物理上是挨着的,HAL库将这些寄存器合并在一个结构里访问而已。
下面通过调用 "HAL_CAN_ConfigFilter(&hcan1,&CAN1_FilerConf)" 配置can筛选器即可生效。
无需再调用HAL_CAN_ConfigFilter(&hcan2,&CAN1_FilerConf)
*/
if(HAL_CAN_ConfigFilter(&hcan1,&CAN1_FilerConf)!=HAL_OK)
{
Error_Handler();
}
}
uint8_t CAN1_Send_Msg(CANTxMsg_t *msg,uint16_t mailbox_id,uint8_t *sendbuff)
{
uint8_t id;
msg->TxMessage.StdId=mailbox_id; //邮箱id号
msg->TxMessage.IDE=CAN_ID_STD; //邮件的id格式(标准为CAN_ID_STD | 拓展为CAM_ID_EXT)
msg->TxMessage.DLC=8; //邮件数据长度 此处为8个字节
msg->TxMessage.RTR=CAN_RTR_DATA; //数据帧 一般都是数据帧
msg->TxMessage.TransmitGlobalTime=DISABLE; //默认DISABLE
for(id=0;id<8;id++)
{
msg->payload[id]=sendbuff[id]; //装填数据
}
//发送邮件 注意邮件邮件信息(id号,邮件类型等等)和数据内容是分开发送的,具体看下面这句函数参数
if(HAL_CAN_AddTxMessage(&hcan1,&msg->TxMessage,msg->payload,&msg->mailbox)!=HAL_OK)
return 0;
else
return 1;
}
/*视频第4节有讲为什么要起这样的函数名字,这个叫回调函数
接收到邮件最终会来到这里。由于我们开了RX0中断,因此我们的回调函数名是
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan);
如果我们开的是RX1中断,那么回调函数名是void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan);
*/
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
//这句话是判断对方发来的信息是发给本板子的can1还是can2,如果是can1,那通过接线就知道是哪个设备发来的数据。
if(hcan->Instance==CAN1)
{
/*
这个是获取邮件的函数,既然进了接收中断,那么就说明接收到了数据因此我们调用下方函数获取。
我们提供一个RxMessage以及payload分别接收邮件邮件信息(id号,邮件类型等等)和数据内容。
*/
if(HAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO0,&(RxMsg.RxMessage),(RxMsg.payload))==HAL_OK)
rcvdFlag=1;
else
Error_Handler();
}
//HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING);
//开启中断,在初始化已经调用
}
can.h中声明
#include "stm32f1xx_hal_can.h"
typedef struct
{
uint32_t mailbox;
CAN_TxHeaderTypeDef TxMessage;
uint8_t payload[8];
}CANTxMsg_t; //发送结构体
typedef struct
{
CAN_RxHeaderTypeDef RxMessage;
uint8_t payload[8];
}CANRxMsg_t; //接收结构体
/* USER CODE END Includes */
extern CAN_HandleTypeDef hcan1;
/* USER CODE BEGIN Private defines */
extern uint8_t rcvdFlag;
extern CANTxMsg_t TxMsg;
extern CANRxMsg_t RxMsg;
/* USER CODE END Private defines */
void MX_CAN_Init(void);
/* USER CODE BEGIN Prototypes */
void CAN1_Filter_Init(void);
uint8_t CAN1_Send_Msg(CANTxMsg_t *msg,uint16_t mailbox_id,uint8_t *sendbuff);
void CAN1_Receive_Msg(CANRxMsg_t *msg);
主循环
uint8_t SendMsg[8]={1,2,3,4,5,6,7,8}; //发送的数据
CAN1_Send_Msg(&TxMsg,0X201,SendMsg); //发送数据
while (1)//这部分可以直接在中断回调函数中写
{
if(rcvdFlag==1)//receive msg
{
rcvdFlag=0;
if(RxMsg.RxMessage.StdId==0x201)
//将过滤器配置好,就没必要再判断id号了
//判断接收的ID号,回环模式下,ID相同
{
printf("%d %d %d %d\r\n",RxMsg.payload[0],RxMsg.payload[1],RxMsg.payload[2],RxMsg.payload[3]);
}
//打印接收的数据
}
}
四、如何配置两个邮箱(用于接收两个电机的数据)
打开fifo0和fifo1的中断
HAL_CAN_Start(&hcan1);//打开CAN
CAN1_Filter_Init();//接收初始化
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)//开启FIFO0中断
{
Error_Handler();
}
if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO1_MSG_PENDING) != HAL_OK)//开启FIFO1中断
{
Error_Handler();
}
配置各自的接收结构体、标志位
CANTxMsg_t TxMsg;
CANRxMsg_t RxMsg0,RxMsg1;
uint8_t rcvdFlag1=0,rcvdFlag2=0;
初始化邮箱的筛选器(这里的代码是fifo0接收0x200的id号的数据,fifo1接收0x201id号的数据)
void CAN1_Filter_Init(void)
{
CAN_FilterTypeDef CAN1_FilerConf;
CAN1_FilerConf.FilterIdHigh=0X0200 << 5;//高16位左移五位即为标准id号
CAN1_FilerConf.FilterIdLow=0X0000 << 5;
CAN1_FilerConf.FilterMaskIdHigh=0Xffff;
CAN1_FilerConf.FilterMaskIdLow=0Xffff;//掩码全部为f,表示所有位需要相同
CAN1_FilerConf.FilterFIFOAssignment = CAN_FILTER_FIFO0;
CAN1_FilerConf.FilterActivation=ENABLE;
CAN1_FilerConf.FilterMode=CAN_FILTERMODE_IDMASK;//掩码模式
CAN1_FilerConf.FilterScale=CAN_FILTERSCALE_32BIT;
CAN1_FilerConf.FilterBank=0; //小的先接收
//CAN1_FilerConf.SlaveStartFilterBank=14;
if(HAL_CAN_ConfigFilter(&hcan1,&CAN1_FilerConf)!=HAL_OK)//配置CAN过滤器
{
Error_Handler();
}
CAN1_FilerConf.FilterIdHigh=0X0201 << 5;
CAN1_FilerConf.FilterIdLow=0X0000 << 5;
CAN1_FilerConf.FilterMaskIdHigh=0Xffff;
CAN1_FilerConf.FilterMaskIdLow=0Xffff;//掩码全部为f,表示所有位需要相同
CAN1_FilerConf.FilterFIFOAssignment = CAN_FILTER_FIFO1;
CAN1_FilerConf.FilterActivation=ENABLE;
CAN1_FilerConf.FilterMode=CAN_FILTERMODE_IDMASK;//掩码模式
CAN1_FilerConf.FilterScale=CAN_FILTERSCALE_32BIT;
CAN1_FilerConf.FilterBank=1;
//CAN1_FilerConf.SlaveStartFilterBank=14;
if(HAL_CAN_ConfigFilter(&hcan1,&CAN1_FilerConf)!=HAL_OK)//配置CAN过滤器
{
Error_Handler();
}
}
每个邮箱都有回调函数
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)//FIFO0接收中断回调函数
{
if(hcan->Instance==CAN1)
{
if(HAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO0,&(RxMsg0.RxMessage),(RxMsg0.payload))==HAL_OK)
rcvdFlag1 = 1;
else
Error_Handler();
}
//HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING);
}
void HAL_CAN_RxFifo1MsgPendingCallback(CAN_HandleTypeDef *hcan)//FIFO1接收中断回调函数
{
if(hcan->Instance==CAN1)
{
if(HAL_CAN_GetRxMessage(&hcan1,CAN_RX_FIFO1,&(RxMsg1.RxMessage),(RxMsg1.payload))==HAL_OK)
rcvdFlag2 = 1;
else
Error_Handler();
}
//HAL_CAN_ActivateNotification(&hcan1,CAN_IT_RX_FIFO0_MSG_PENDING);
}
主循环判断标志位
while (1)
{
CAN1_Send_Msg(&TxMsg,0X200,SendMsg);//发送数据
HAL_Delay(50);
if(rcvdFlag1==1)//receive msg
{
rcvdFlag1 = 0;
printf("%d %d %d %d\r\n",RxMsg0.payload[0],RxMsg0.payload[1], RxMsg0.payload[2],RxMsg0.payload[3]);
}
if(rcvdFlag2==1)//receive msg
{
rcvdFlag2 = 0;
printf("%d %d %d %d\r\n",RxMsg1.payload[4],RxMsg1.payload[5],
RxMsg1.payload[6],RxMsg1.payload[7]);
}
}