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,退出iftransmit_mailbox = 0;}else if ((CANx->TSR&CAN_TSR_TME1) == CAN_TSR_TME1){//如果發送郵箱[1]空閑,則記下該郵箱的編號1,退出iftransmit_mailbox = 1;}else if ((CANx->TSR&CAN_TSR_TME2) == CAN_TSR_TME2){//如果發送郵箱[2]空閑,則記下該郵箱的編號2,退出iftransmit_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綁定過濾器,當CAN1的總線收到消息后,會先由CAN1的FIFO0過濾器進行篩選,篩選通過的報文,會分別進入FIFO0的3個郵箱中空閑的1個,供CPU來調取,接收結束;如果未通過篩選,則進入FIFO1的過濾器進行篩選,如果通過篩選,則會進入FIFO1 3個空閑郵箱中的1個,供CPU來調取。
一般簡單的使用場景中,只給fifo0激活過濾器就幾乎能滿足日常需求了,fifo1的過濾器直接設置為全失能就行了,這樣就假裝fifo1不存在就好了。
下面 根據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個附屬郵箱,無法被直接訪問。