CAN协议
CAN是由:微控制器,CAN 控制器,CAN收发器组成。STM32自带CAN控制器。
由于can 协议的复杂,因此can 协议是需要can辅助硬件辅助实现的,因此我们只需要了解协议内容的大概就行,不要求完全掌握已经完全又代码实现。
can是半双工,使用回环模式可自发自收,可用于只有一个单片机的调试使用
认识CAN
几个概念
仲裁:硬件实现仲裁,硬件自动根据ID序列排列优先级,在总线上只能高优先级的信号传输。
筛选器(滤波器):软件配置、硬件实现筛选器。筛选器就是可以用户配置ID的掩码,实现对感兴趣的ID数据包的筛选。
邮箱:抽象概念,硬件实现。其实就是本地是发送缓存与接收缓存,单位大小就是一个CAN数据包。
STM32有发送邮箱三个(三级缓存),接收邮箱由两个FIFO队列,每个队列深度为 3 个邮箱(各三级缓存)组成。
邮局:抽象概念,其实就是接收方的筛选器。
发送方会检测接收方是否收到
接收方可以对发送方纠错
位填充机制,就是当连续发送5个0或者5个1时,硬件自动填充一个位的补码数据,即1或0。
每次跳变沿就是一次信号同步。
回读是由硬件内部实现,在发送端口会有线连接到接收端口。
位同步是由每一帧的帧头,同步段实现的。以此解决CAN没时钟线时需要的同步。
从应用了解CAN
ECU报文
报文名称 | ID | 发送周期 | 长度DLC | 报文起始字节 | 信号名 | 数据起始位 | 数据长度 | 系数 | 偏置数 | 最小值 | 最大值 | Codin示意 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
EMS_Control | 100 | 10ms | 8Byte | 0 | EMS_RPM | 7 | 8bit | 30 | 0 | 0 | 7650 | - |
100 | 10ms | 8Byte | 1 | EMS_FAIL_F | 0 | 1bit | - | - | - | - | 0b:nornaml 1b:abnormal |
实际代表数值 = 字节数据 x 系数 + 偏置数
一、CAN的通讯层结构
1、物理层
电平:隐性电平,逻辑1,无电压差(2.5v);显性电平,逻辑0,有电压差(canH 3.5v canL 1.5v)。
显性是指有强的控制性,CAN总线是线与操作,任何与0都为0,因此逻辑0为显性。
终端电阻:120Ω ,双绞线阻抗120Ω
波特率:
CAN波特率范围在 0.5M - 1Mbps ,一般配置最高速率1Mbps。
汽车常用512bps。
CAN不能直接像UART一样设置波特率,但是通过配置时间周期即可得出CAN波特率。时间速率是由 起始位段(SJY) + 位段1(TBS1) + 位段2(TBS2)
三者来实现的。
CAN的时间定义:同步段(同步时间,占用1脉冲时间),传播段(补偿线路延迟,1-8脉冲时间),相位缓冲段1(补偿相位误差,1-8脉冲时间),采样点,相位缓冲段2(1-8脉冲时间)
如STM32 由APB1总线、CAN的预分频(Prescaler)、起始段(TBS1)、位段1(对应相位缓冲段1)、位段2(对应相位缓冲段2),几个位时间的定义。
$ CAN_{波特率} = \frac{APB1_{PCLK}总线频率}{分频系数 * TBS1+TBS2+SJY}$
配置波特率就是凑等式,若为了配置1M的波特率
$ TBS1+TBS2+SJY = \frac{APB1_{PCLK}总线频率(如F1的36M)}{分频系数(Prescaler)} = 1M$
TBS1 尽量等于也可稍微大于 TBS2,SJY 可以配置为 1。
如:
Prescaler (for Time Quantum) :分频系数配置为 2 (36/2=18;9+8+1=18 )
Time Quanta in Bit Segment 1 :9
Time Quanta in Bit Segment 2 :8
ReSynchronization Jump Width:1
同步时间用来仲裁,
位段1/2用来滤波跳变沿。位段1/2之间是采样点。
(需要软件配置)
挂载节点:理论上一条CAN总线可挂载几百个节点,但是在实际应用中可能会遇到挂载几十个节点,如 16 个,在125khz 的频率下就会奔溃,这是由于总线上的寄生容和等效电阻的存在导致的波形失真。
解决方法,当总线不断增加后要调整终端电阻,使电平快速进入隐形状态,抑制信号反射,一般要确保整条总线的终端电阻为60Ω ,当总线雨来越多时,总线的终端电阻要增加,即总的终端电阻要减小。
通讯距离:
通信距离 = 50000 / 波特率
2、链路层
1、帧格式
1.1 数据帧
仲裁场 | ------------| | 控制场 | ------------ | ------------| | 数据场 | 校验场 | – | 应答场 | |
---|---|---|---|---|---|---|---|---|---|
帧起始SOF | ID段 | RTR(远程请求发送帧) | IDE(ID扩展标志) | r0 | DLC | Data | CRC | CRC界定符 | ACK |
1b(0) | 11b(x) | 1b(0) | 1b(0) | 1b(0) | 4b | 0-64 (最多8Byte) | 15Bit | 1b(1) | 2b ACK DEL |
0为数据 1为请求数据 | 0:标准帧 1:扩展帧 | 保留位(0) | 数据长度代码 | 数据 |
标准帧11 位ID,扩展帧29位ID(11+18)。
1.2 远程帧
远程帧就是没有数据的数据帧,并且RTR=1。用于请求其他节点发送响应数据的。
1.3 错误帧
1.4 过载帧
2、CAN的错误
CAN有五种错误机制:1、位错误,2、填充错误,3、格式错误,4、应答错误,5、CRC错误
-
位错误 (Bit Error):
定义:当一个节点发送的位与接收到的位不匹配时发生位错误。位错误可能是由于信号干扰或总线上的其他问题引起的。
处理:当节点检测到位错误时,它会立即发送一个错误帧(Error Frame),并且增加发送错误计数器。所有节点都会看到这个错误帧,并根据协议做出响应。 -
填充错误 (Stuffing Error):
定义:CAN协议要求在数据帧的每个位之间插入额外的“填充位”以保持同步。如果发送的数据中的连续相同的位超过了允许的数量(通常是五个位),则需要插入一个反向位。填充错误发生在填充规则被违反时。
处理:节点检测到填充错误时,会发送一个错误帧,并增加错误计数器。所有节点将看到这个错误帧并采取适当的措施。 -
格式错误 (Form Error):
定义:格式错误发生在CAN帧的字段不符合规定的格式时,例如在帧的标识符、控制字段或数据字段中出现无效的位模式。
处理:当检测到格式错误时,节点会发送错误帧,并增加错误计数器。 -
应答错误 (Acknowledgment Error):
定义:应答错误发生在发送节点发送数据后,未收到任何应答信号时。CAN协议要求接收节点在数据帧传输完成后发回应答位以确认接收到数据。
处理:如果没有应答信号,发送节点会重新发送数据帧,并且错误计数器会增加。如果应答错误持续发生,节点可能会进入错误被动或总线关闭状态。 -
CRC 错误 (CRC Error):
定义:CRC错误发生在接收到的数据帧的循环冗余检验 (CRC) 结果与计算值不匹配时。CRC用于检查数据在传输过程中是否发生了错误。
处理:当检测到CRC错误时,节点会发送错误帧,并增加错误计数器。其他节点将看到错误帧并根据协议做出响应。
出现错误后总线节点要通知其他节点时就要发送错误帧
发送错误计数 TEC 和 接收错误计数 REC
错误状态机
主动错误(计数警告)、被动错误(禁言错误设备)、总线关闭(直接踢开掉线)
二、认识HAL CAN及其使用
发送数据包头结构体
typedef struct
{
uint32_t StdId; /*!< 标准帧的地址 Specifies the standard identifier.
This parameter must be a number between Min_Data = 0 and Max_Data = 0x7FF. */
uint32_t ExtId; /*!< 扩展帧的地址 Specifies the extended identifier.
This parameter must be a number between Min_Data = 0 and Max_Data = 0x1FFFFFFF. */
uint32_t IDE; /*!< 标准帧和拓展帧的标志位 Specifies the type of identifier for the message that will be transmitted.
This parameter can be a value of @ref CAN_identifier_type */
uint32_t RTR; /*!< 远程传输请求位 Specifies the type of frame for the message that will be transmitted.
This parameter can be a value of @ref CAN_remote_transmission_request */
uint32_t DLC; /*!< 数据长度 Specifies the length of the frame that will be transmitted.
This parameter must be a number between Min_Data = 0 and Max_Data = 8. */
FunctionalState TransmitGlobalTime; /*!< 消息发送时的时间戳,用于数据同步 Specifies whether the timestamp counter value captured on start
of frame transmission, is sent in DATA6 and DATA7 replacing pData[6] and pData[7].
@note: Time Triggered Communication Mode must be enabled.
@note: DLC must be programmed as 8 bytes, in order these 2 bytes are sent.
This parameter can be set to ENABLE or DISABLE. */
} CAN_TxHeaderTypeDef;
接收数据包头的结构体
CubeMx 配置
1、打开CAN外设
2、配置波特率(F1:2、9、8、1)
3、开接收中断 CAN RX0 (FIFO1的中断)
软件使用
发送
1、CAN Init 向邮局申请将发送(发送)消息
2、配Message数据包(发送)
接收
1、配筛选器 (接收特有)
准备好数据存放容器
需要在FIFO0或1拿数据?
实例
1、需要自己实现滤波器的配置CAN1_Fliter_Init();
,HAL库没该函数。
2、配置CAN FIFO0 中断回调函数 HAL_CAN_RxFifo0MsgPendingCallback();
//can.h
CAN_TxHeaderTypeDef TxMessage;
CAN_RxHeaderTypeDef RxHeader;
uint8_t rxbuf[8];//接收数据的全局变量
uint8_t rcvdFlag;//中断接收到数据的标志位 1 为接收到数据
void CAN1_Fliter_Init(void)
{
CAN_FilterTypeDef CAN1_FilterConf;
CAN1_FilterConf.FilterIdHigh=0x0000;
CAN1_FilterConf.FilterIdLow=0x0000;
CAN1_FilterConf.FilterMaskIdHigh = 0x0000;
CAN1_FilterConf.FilterMaskIdLow = 0x0000;
CAN1_FilterConf.FilterFIFOAssignment = CAN_FilterFIFO0;//将过滤器关联到FIFO0
CAN1_FilterConf.FilterActivation = ENABLE;//激活过滤器
CAN1_FilterConf.FilterMode = CAN_FILTERMODE_IDMASK;
CAN1_FilterConf.FilterScale = CAN_FILTERSCALE_32BIT;
CAN1_FilterConf.FilterBank = 1;//过滤器1
CAN1_FilterConf.SlaveStartFilterBank = 14;
if( HAL_CAN_ConfigFilter(&hcan,&CAN1_FilterConf) != HAL_OK )
{
Error_Handler();
}
}
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
HAL_StatusTypeDef status;
if(hcan ->Instance == CAN1 )
{
status = HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0,&RxHeader, rxbuf );
if( status != HAL_OK)
{
Error_Handler();
}
else if(status == HAL_OK)
{
rcvdFlag = 1;
}
}
}
3、执行一次开启CAN,打开CAN接收中断。
HAL_CAN_Start(&hcan);
CAN1_Fliter_Init();
if( HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK){
Error_Handler();
}
4、编写发射函数和轮询标志位的接收函数
/*发送任务*/
void CANTransmitTask(void const * argument)
{
srand(HAL_GetTick()); // 用系统时间或其他种子初始化随机数生成敿
// uint8_t sendMsg[8] = {"hellCAN"};
CAN_TxHeaderTypeDef canTx;
uint8_t txdata[8] = {'o','b','c','d','1','2','3','4',};
uint32_t pTxMailbox = 0;
canTx.StdId = 0x123;
canTx.ExtId = 0x123;
canTx.IDE = 0;
canTx.RTR = 0;
canTx.DLC = 8;
while(1)
{
taskENTER_CRITICAL();
printf("Put msg: ");
taskEXIT_CRITICAL();
for(int i=0;i<8;i++)
{
int randnum = 0;
while(1){
randnum = rand()%255+1;
if( (randnum >= 'A' && randnum <= 'Z') ||
(randnum >= 'a' && randnum <= 'z') ||
(randnum >= '0' && randnum <= '9')){
break;
}
}
// randnum = rand()%26 + 'a';
txdata[i] = randnum;
taskENTER_CRITICAL();
printf("%c ", txdata[i]);
taskEXIT_CRITICAL();
}
printf("\r\n");
HAL_CAN_AddTxMessage(&hcan,&canTx,txdata,&pTxMailbox);
HAL_Delay(500);
}
}
/* 轮询标志位接收 */
__weak void StartTask(void const * argument)
{
/* USER CODE BEGIN StartTask */
taskENTER_CRITICAL();//进入临界保护区
printf("startTask running\r\n");
taskEXIT_CRITICAL();//退出出临界保护区
// vTaskDelete(NULL);//板掉自巿
/* Infinite loop */
for(;;)
{
if(rcvdFlag==1){ //在中断那里通过此全局变量告诉我该数据可用
rcvdFlag=0;
taskENTER_CRITICAL();
if( rxbuf[0] != 0){
printf("Get Msg: ");
for(int i=0;i<8;i++){
printf("%c ", rxbuf[i]);
}
printf("\r\n");
printf("\r\n");
}
taskEXIT_CRITICAL();
}
osDelay(200);
}
/* USER CODE END StartTask */
}