1、硬件结构图
以STM32F4为例,他有2个can控制器,分别为 CAN1 CAN2。
每个CAN控制器,都有3个发送邮箱、2个接收fifo,每个接收fifo又由3个接收邮箱组成。也即每个CAN控制器都有9个邮箱,其中3个供发送用,3个供接收fifo0用,3个供接收fifo1用。
STM32F4有2个CAN,也即有18个邮箱。
注意:
下图来自数据手册,可以清楚的看到单片机内部的2个CAN控制器的硬件结构。
这个结构图对于编写收发驱动程序至关重要。
2、发送数据
我们由stm32提供的库函数来看一下发送过程,我加了几条中文注释,发送;流程应该很清楚了
uint8_t CAN_Transmit(CAN_TypeDef* CANx, CanTxMsg* TxMessage)
{
uint8_t transmit_mailbox = 0;
/* Check the parameters */
assert_param(IS_CAN_ALL_PERIPH(CANx));
assert_param(IS_CAN_IDTYPE(TxMessage->IDE));
assert_param(IS_CAN_RTR(TxMessage->RTR));
assert_param(IS_CAN_DLC(TxMessage->DLC));
/* Select one empty transmit mailbox */
if ((CANx->TSR&CAN_TSR_TME0) == CAN_TSR_TME0)
{//如果发送邮箱[0]空闲,则记下该邮箱的编号0,退出if
transmit_mailbox = 0;
}
else if ((CANx->TSR&CAN_TSR_TME1) == CAN_TSR_TME1)
{//如果发送邮箱[1]空闲,则记下该邮箱的编号1,退出if
transmit_mailbox = 1;
}
else if ((CANx->TSR&CAN_TSR_TME2) == CAN_TSR_TME2)
{//如果发送邮箱[2]空闲,则记下该邮箱的编号2,退出if
transmit_mailbox = 2;
}
else
{//如果发送邮箱全不空闲,则标记为全不可用
transmit_mailbox = CAN_TxStatus_NoMailBox;
}
if (transmit_mailbox != CAN_TxStatus_NoMailBox)//至少有一个发送邮箱空闲
{
/* Set up the Id */
CANx->sTxMailBox[transmit_mailbox].TIR &= TMIDxR_TXRQ;
if (TxMessage->IDE == CAN_Id_Standard)//记录CAN地址(std标准地址、或ext扩展地址)
{
assert_param(IS_CAN_STDID(TxMessage->StdId));
CANx->sTxMailBox[transmit_mailbox].TIR |= ((TxMessage->StdId << 21) | \
TxMessage->RTR);
}
else
{
assert_param(IS_CAN_EXTID(TxMessage->ExtId));
CANx->sTxMailBox[transmit_mailbox].TIR |= ((TxMessage->ExtId << 3) | \
TxMessage->IDE | \
TxMessage->RTR);
}
/* Set up the DLC */
TxMessage->DLC &= (uint8_t)0x0000000F;
CANx->sTxMailBox[transmit_mailbox].TDTR &= (uint32_t)0xFFFFFFF0;
CANx->sTxMailBox[transmit_mailbox].TDTR |= TxMessage->DLC;//记录要发送的字节数
/* Set up the data field */ //填充要发送的字节内容
CANx->sTxMailBox[transmit_mailbox].TDLR = (((uint32_t)TxMessage->Data[3] << 24) |
((uint32_t)TxMessage->Data[2] << 16) |
((uint32_t)TxMessage->Data[1] << 8) |
((uint32_t)TxMessage->Data[0]));
CANx->sTxMailBox[transmit_mailbox].TDHR = (((uint32_t)TxMessage->Data[7] << 24) |
((uint32_t)TxMessage->Data[6] << 16) |
((uint32_t)TxMessage->Data[5] << 8) |
((uint32_t)TxMessage->Data[4]));
/* Request transmission */ //启动发送
CANx->sTxMailBox[transmit_mailbox].TIR |= TMIDxR_TXRQ;
}
return transmit_mailbox;
}
根据数据手册目录可见,3个发送邮箱均有4个相关寄存器:
分别是TIxR、TDTxR、TDLxR、TDHxR,其中x为0、1、2,分别对应3个邮箱,例如TDT0R代表邮箱[0]的TDTxR寄存器。
在硬件上,每个邮箱的这4个寄存器是按顺序的,也即依次为:TI0R、TDT0R、TDL0R、TDH0R、TI1R、TDT1R、TDL1R、TDH1R、TI2R、TDT2R、TDL2R、TDH2R。
如果我们把以上12个寄存器地址都宏定义为以上名称,当然没问题,但是使用起来不是很方便,尤其不方便使用循环。在ST库函数中,是这样定义的:
他把每个邮箱的4个寄存器,都作为结构体的一个成员,这样3个邮箱就可以用结构体数组来描述了 ,而且与硬件顺序一致,如下所示:
这样CAN1的TI0R寄存器,就可以用CAN1->sTxMailBox[0].TIR来引用了。
3、接收数据
以CAN1为例,CAN1在初始化时,就要先给CAN1的两个FIFO绑定过滤器,当CAN总线收到消息后,会先由过滤器进行筛选,筛选通过的报文,会进入FIFO的3个邮箱中空闲的一个中,进行保存,供CPU来调取。如果3个邮箱都
下面 根据ST的官方库函数,看一下接收过程:
第一步,先查询某FIFO中消息的数量(显然,最多有3条,也即该FIFO的3个邮箱都有数据时)
uint8_t CAN_MessagePending(CAN_TypeDef* CANx, uint8_t FIFONumber)
{
uint8_t message_pending=0;
/* Check the parameters */
assert_param(IS_CAN_ALL_PERIPH(CANx));
assert_param(IS_CAN_FIFO(FIFONumber));
if (FIFONumber == CAN_FIFO0)//查询FIFO0的3个邮箱中中,有几个有数据
{ //0x03就是寄存器RFxR低2 bits(FMPx)的掩码,见下图RFxR寄存器描述
message_pending = (uint8_t)(CANx->RF0R&(uint32_t)0x03);
}
else if (FIFONumber == CAN_FIFO1)//查询FIFO1的3个邮箱中中,有几个有数据
{
message_pending = (uint8_t)(CANx->RF1R&(uint32_t)0x03);
}
else
{
message_pending = 0;
}
return message_pending;//返回消息的数量
}
void CAN_Receive(CAN_TypeDef* CANx, uint8_t FIFONumber, CanRxMsg* RxMessage)
{
/* Check the parameters */
assert_param(IS_CAN_ALL_PERIPH(CANx));
assert_param(IS_CAN_FIFO(FIFONumber));
/* Get the Id */
RxMessage->IDE = (uint8_t)0x04 & CANx->sFIFOMailBox[FIFONumber].RIR;//提取消息的ID类型
if (RxMessage->IDE == CAN_Id_Standard)//如果消息类型是std标准帧
{
RxMessage->StdId = (uint32_t)0x000007FF & (CANx->sFIFOMailBox[FIFONumber].RIR >> 21);
}
else//如果消息类型是ext扩展帧
{
RxMessage->ExtId = (uint32_t)0x1FFFFFFF & (CANx->sFIFOMailBox[FIFONumber].RIR >> 3);
}
//提取RTR比特,是1远程帧还是0数据帧
RxMessage->RTR = (uint8_t)0x02 & CANx->sFIFOMailBox[FIFONumber].RIR;
/* Get the DLC *///提取字节数
RxMessage->DLC = (uint8_t)0x0F & CANx->sFIFOMailBox[FIFONumber].RDTR;
/* Get the FMI */
RxMessage->FMI = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDTR >> 8);
/* Get the data field */
RxMessage->Data[0] = (uint8_t)0xFF & CANx->sFIFOMailBox[FIFONumber].RDLR;
RxMessage->Data[1] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDLR >> 8);
RxMessage->Data[2] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDLR >> 16);
RxMessage->Data[3] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDLR >> 24);
RxMessage->Data[4] = (uint8_t)0xFF & CANx->sFIFOMailBox[FIFONumber].RDHR;
RxMessage->Data[5] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDHR >> 8);
RxMessage->Data[6] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDHR >> 16);
RxMessage->Data[7] = (uint8_t)0xFF & (CANx->sFIFOMailBox[FIFONumber].RDHR >> 24);
/* Release the FIFO */
/* Release FIFO0 */
if (FIFONumber == CAN_FIFO0)//清除邮箱占用标志
{
CANx->RF0R |= CAN_RF0R_RFOM0;
}
/* Release FIFO1 */
else /* FIFONumber == CAN_FIFO1 */
{
CANx->RF1R |= CAN_RF1R_RFOM1;
}
}
3.1 筛选器
接收时 ,可以设置者筛选器(手册或库函数中称为filter),说白了就是根据CAN的ID来设置白名单或黑名单,直接丢弃我们不想要的报文,以便减轻CPU负担。
列表模式(我管他叫做白名单):只有报文的ID出现在该列表中,该报文才会进入接收邮箱,否则直接被丢弃。
屏蔽模式(我管他叫做黑名单):如果报文携带的ID复合黑名单规则,则该报文直接被丢弃,否则才允许进入邮箱。
STM32F4 共有28个筛选器组,每个筛选器都有2个u32的寄存器,分别是CAN_FxR0和CAN_FxR1,其中x为0~27共28组。
筛选器的配置
1、尺度配置:筛选器可配置为双16位或单32位,以应对不同需求。通过寄存器FS1R的28个bit的0或1分别对应这28个筛选器组的尺度配置。0=双16位1=单32位
2、模式配置。CAN_FM1R寄存器的32个bit中的28个,用于指定28个筛选器组工作于哪种模式,0=屏蔽模式,1=列表模式
3、给FIFO分配筛选器。CAN_FFA1R寄存器32个bit中的28个,用于指定28个筛选器要分配给哪个FIFO。如果bit5=0代表筛选器5分配给FIFO0,如果bit5=1代表筛选器5分配给FIFO1,其他bit雷同。
4、筛选器内容配置:
3.2 FIFO0和FIFO1
两个CAN各有2个FIFO,每个FIFO又各有3个邮箱,但是与发送邮箱不同的,接收FIFO的这6个附属邮箱,无法被直接访问。