1、仅有 CAN_L 和 CAN_H 两条总线
2、显性电平为逻辑值0,隐性电平为逻辑值1
帧可以看做是用于传输信息的邮件单位,帧由段组成,段由二进制位组成
实验室主要用到数据帧
上图中灰体为显性电平0
名称 | 描述 |
---|---|
帧起始 | 表示帧的开始,产生一个bit的显性电平。 |
仲裁段 | 表示帧的优先级, 由标识符(ID)和传送帧类型(RTR)组成。 |
控制端 | 表示数据的字节数,由6个bit构成 |
数据段 | 数据的具体内容,可发送0~8 个字节的数据。 |
CRC段 | 用于校验传输是否正确。 |
ACK段 | 表示确认是否正常接收。 |
帧结束 | 表示此帧结束。 |
△仲裁段:
ID为报文的名字,可通过CAN的过滤器筛选
RTR表示为数据帧还是远程帧(0表示数据帧)
△控制段:
IDE表示为标准格式还是扩展格式(0表示为标准格式)
DLC表示数据部分的长度,前面的r0、r1编程时不用管,默认为0(显性电平)即可
△数据段:
可以用来存放0~8个字节的数据
(二)CAN总线的传输速率
由APB1 peripheral clocks开始分频
(三)CAN总线的过滤机制
1、屏蔽位模式:报文ID符合目标ID的指定部分便允许通过(屏蔽码)
2、标识符列表模式:一模一样才能通过(与期望ID比较)
(四)CAN总线的发送和接收
1、CAN接收:FIFO相当于接收邮箱(开启接收中断0或1即开启FIFO0或FIFO1)
每个FIFO有三级深度,相当于可以储存3个CAN报文
2、CAN发送:通过发送邮箱Tx Mailboxes实现
准备完成的CAN报文将被填入到空闲的发送邮箱中,等待上一个邮箱中存储的报文发送完成,之后进行发送。
(五)CAN在cubemx上配置
1、使能CAN
2、调与分频系数、时间片1、时间片2和SWJ(通常为1),调节至波特率与外设要求的一致
3、打开CAN接收和发送中断
(六)CAN接收:过滤器组配置
参考函数:
/* 定义CAN过滤器寄存器位宽类型 */
typedef union
{
__IO uint32_t value; //32位的value变量,存储一个寄存器的值,而下面的结构体是对其进行细分
struct
{
uint8_t REV : 1; //< [0] :未使用,1bit
uint8_t RTR : 1; //< [1] : RTR(数据帧或远程帧标志位),1bit
uint8_t IDE : 1; //< [2] : IDE(标准帧或扩展帧标志位),1bit
uint32_t EXID : 18; //< [21:3] : 存放扩展帧ID,18bits
uint16_t STID : 11; //< [31:22]: 存放标准帧ID,11bits
} Sub;
} CAN_FilterRegTypeDef;
/**
* @brief CAN过滤器配置(屏蔽位模式)若要使用列表模式,则可自行修改注释多余语句
* @parma _id:目标ID号,标准ID
* @parma _filter_num:当前配置的过滤器组号,0-27
* @return NULL
* @author Lingzi_Xie
* @note _id配置为全0时,表示全通
*/
void CAN_Filter_Config(uint32_t _id, uint16_t _filter_num)
{
CAN_FilterTypeDef sFilterConfig;
CAN_FilterRegTypeDef IDH = {0};
CAN_FilterRegTypeDef IDL = {0};
IDH.Sub.STID = (_id >> 16) & 0xFFFF; //把标准ID高16位放在寄存器1的STID域
IDL.Sub.STID = (_id & 0xFFFF); //把标准ID低16位放在寄存器2的STID域
/* 过滤器配置,参照HAL库CAN_FilterTypeDef的相关注释完成
配置模式、位宽、ID、MaskID、关联FIFO、使
能激活 */
//加载过滤器配置
if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
}
之后调用 HAL_CAN_Start(&hcan);
使能CAN外设。
△FilterMode:过滤器模式,分列表模式(CAN_FILTERMODE_IDLIST
)和掩码模式(CAN_FILTERMODE_IDMASK
)
列表模式下,scale为32时,每个过滤器的列表只能写入两个报文ID,若scale为16时,每个过滤器的列表最多可写入4个CAN ID;而掩码模式则无需担心容量,完全取决于屏蔽码。
32位宽的列表模式下,FilterIdHigh与FilterIdLow一起用来存放一个CAN ID,FilterMaskIdHigh与FilterMaskIdLow用来存放另一个CAN ID,不再表示其字面所示的mask含义;16位宽的列表模式下,FilterIdHigh,FilterIdLow,FilterMaskIdHigh,FilterMaskIdLow这4个16位变量都是用来存储一个标准CAN ID;32位掩码模式下,CAN_FxR1(FilterIdHigh与FilterIdLow)用做32位宽的验证码,而CAN_FxR2(FilterMaskIdHigh与FilterMaskIdLow)则用作32位宽的屏蔽码。
△FilterScale:位宽(CAN_FILTERSCALE_32BIT
和CAN_FILTERSCALE_16BIT
)
△FilterFIFOAssignment:FIFO的名称(0或1U)
△FilterNumber:过滤器编号,一共有0~27号过滤器
△FilterActivation:使能 ENABLE 即可
△BankNumber:单CAN设备通常设为14,无需理会;双CAN设备则填开启的过滤器编号
关于过滤器的ID配置,使用标准数据帧通信时:
1、想配置成全通,则32位的屏蔽位模式下,ID和MaskID直接全0就行;
2、想配置成过滤特定ID,则32位的屏蔽位模式下,ID和MaskID都配置成目标ID就行啦;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dhvtpHUO-1668156680313)(D:\Temp\WeChat Files\26f95c41a6d1a7d3001729c1359cedf.png)]
(七)CAN接收:中断回调函数编写
报文存入FIFO后会触发CAN外设的Pending中断,故程序可通过配置此中断的 回调函数实现对报文的取出:
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *canHandle)
{
//CAN数据接收
if (canHandle->Instance == CAN1)
{
CAN_RxHeaderTypeDef RxHeader; //接受句柄
//获得接收到的数据头和数据
if (HAL_CAN_GetRxMessage(canHandle, CAN_RX_FIFO0, &RxHeader, packet.payload) == HAL_OK)
{
//判断接收到的报文ID号,并作出对应的解包处理
if(packet.hdr.StdId == 0x205)
{
/* 解包处理 */
}
}
}
}
(八)CAN发送:发送报文的配置
对于发送报文,我们需要先准备好发送报文的头部(ID、RTR、IDE和DLC等配置)和数据部分, 后直接调用 HAL_CAN_AddTxMessage() 即可,下面给出两种报文发送函数的写法
/**
* @brief Send an communication frame by CAN.
* @param hcan :CAN bus used to send.
* @param ID :ID of frame.
* @param *pData:Data to send.(pData可以换成函数内的局部变量)
* @param Len :Length of data.(通常取8)
* @return CAN_SUCCESS: Operation success.
* @return CAN_LINE_BUSY:CAN line busy.
* @要调的参数需要手动添加
*/
uint8_t CANx_SendData(uint16_t ID,uint8_t *pData,uint16_t Len)
{
static CAN_TxHeaderTypeDef Tx_Header;
uint32_t used_mailbox;
/* 发送报文的Header配置,参照HAL库CAN_TxHeaderTypeDef的相关注释完成
Header配置ID、RTR、IDE、DLC
若发送标准格式的报文,则扩展ID填充全0即可;扩展格式同理 */
//对发送的数据进行打包处理
if(HAL_CAN_AddTxMessage(&hcanx,&Tx_Header,pData,&used_mailbox)!= HAL_OK)
{
return CAN_LINE_BUSY;
}
else{}
return CAN_SUCCESS;
}
/**
* @brief 发送标准ID的数据帧
* @param hcan CAN的句柄
* @param ID 数据帧ID
* @param pData 数组指针
* @param Len 字节数0~8(通常取8)
* @要调的参数需要手动添加
*/
uint8_t CANx_SendStdData(uint16_t ID,uint16_t Len)
{
static CAN_TxHeaderTypeDef Tx_Header;
uint8_t pData[8];
Tx_Header.StdId=ID;
Tx_Header.ExtId=0;
Tx_Header.IDE=CAN_ID_STD;
Tx_Header.RTR=CAN_RTR_DATA;
Tx_Header.DLC=Len;
//对发送的数据进行打包处理
/*找到空的发送邮箱,把数据发送出去*/
if(HAL_CAN_AddTxMessage(&hcanx, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX0) != HAL_OK) //
{
if(HAL_CAN_AddTxMessage(&hcanx, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX1) != HAL_OK)
{
HAL_CAN_AddTxMessage(&hcanx, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX2);
}
}
}
/**
* @brief 发送扩展ID的数据帧
* @param hcan CAN的句柄
* @param ID 数据帧ID
* @param pData 数组指针
* @param Len 数据长度0~8(通常取8)
* @要调的参数需要手动添加
*/
uint8_t CANx_SendExtData(uint32_t ID,uint8_t *pData,uint16_t Len)
{
static CAN_TxHeaderTypeDef Tx_Header;
uint8_t pData[8];
Tx_Header.StdId=0;
Tx_Header.ExtId=ID;
Tx_Header.IDE=CAN_ID_EXT;
Tx_Header.RTR=CAN_RTR_DATA;
Tx_Header.DLC=Len;
//对发送的数据进行打包处理
/*找到空的发送邮箱,把数据发送出去*/
if(HAL_CAN_AddTxMessage(&hcanx, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX0) != HAL_OK) //
{
if(HAL_CAN_AddTxMessage(&hcanx, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX1) != HAL_OK)
{
HAL_CAN_AddTxMessage(&hcanx, &Tx_Header, pData, (uint32_t*)CAN_TX_MAILBOX2);
}
}
}
△在CAN总线中,只要有一个CAN设备发出了显性电平,那么整条总线均表现为显性电平。
△CAN设备在进行报文发送时,会同时监听总线上的状态( 回读机制 ,通过收发器的Rx引脚回传 读取到的状态到控制器)。
△若在发送仲裁段时,本设备对比到其发出的二进制位和总线上当前的二进制位 不一致 ,说明此时 有 优先级更高(由第一条机制可知显然显性电平(二进制位1)优先级高于为0的) 的CAN设备在发送报文,则本设备停止报文发送( 退出竞争 )。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pMfARZxo-1668156680313)(C:\Users\殇之哈士奇\AppData\Roaming\Typora\typora-user-images\image-20211005165626885.png)]
上图中ID 25处Node_A的显性电平覆盖了Node_B的隐性电平,则Node_B暂时停止发送报文,而Node_A仲裁胜出可以继续发送(优先级更高)
器的Rx引脚回传 读取到的状态到控制器)。
△若在发送仲裁段时,本设备对比到其发出的二进制位和总线上当前的二进制位 不一致 ,说明此时 有 优先级更高(由第一条机制可知显然显性电平(二进制位1)优先级高于为0的) 的CAN设备在发送报文,则本设备停止报文发送( 退出竞争 )。
[外链图片转存中…(img-pMfARZxo-1668156680313)]
上图中ID 25处Node_A的显性电平覆盖了Node_B的隐性电平,则Node_B暂时停止发送报文,而Node_A仲裁胜出可以继续发送(优先级更高)