目錄
介紹
協議層
CAN的 幀/報文 種類
數據幀
遠程幀(遙控幀)
錯誤幀
過載幀
幀間隔
總線仲裁
stm32的CAN外設
工作模式
測試模式
功能框圖
時序
標準時序
例子??環回靜默模式測試
寄存器代碼
HAL版本
介紹
一種功能豐富的車用總線標準。被設計用于在不需要主機(Host)的情況下,允許網絡上的單片機和儀器相互通信
它基于消息傳遞協議,設計之初在車輛上復用通信線纜,以降低銅線使用量,后來也被其他行業所使用
CAN擁有了良好的彈性調整能力,可以在現有網絡中增加節點而不用在軟、硬件上做出調整。除此之外,消息的傳遞不基于特殊種類的節點,增加了升級網絡的便利性
物理層
- 一個CAN控制器
- 一般MCU提供,stm32內部提供了一個can控制器
- 一個CAN收發器
- 收發器一般需要專門芯片提供,例如PD1050S收發器芯片
can?
協議層
CAN的 幀/報文 種類
特點:
- ?CAN總線是廣播類型的總線
- 這意味著所有節點都可以偵聽到所有傳輸的報文
- 無法將報文單獨發送給指定節點
- 所有節點都將始終捕獲所有報文
- 但是CAN硬件能夠提供本地過濾功能,讓每個節點對報文有選擇性地做出響應
- CAN使用短報文 – 最大實用負載是94位
- 可以認為報文是通過內容尋址,也就是說,報文的內容隱式地確定其地址
- CAN總線上有5種不同的報文類型
- 數據幀,遠程幀,錯誤幀,過載幀,幀間隔
數據幀
- 數據幀是最常見的報文類型,用于發送單元向接收單元發送數據
- 有標準格式與擴展格式。標準格式有11位標識符,擴展格式有29位標識符
遠程幀(遙控幀)
- 遠程幀用于接收單元向具有相同id的發送單元請求發送數據
- 有標準格式與擴展格式。標準格式有11位標識符,擴展格式有29位標識符
- 與數據幀相比沒有數據段
錯誤幀
- 錯誤幀當檢測出錯誤時向其他單元通知錯誤的幀
- 由硬件自動完成的,沒有辦法用軟件來控制
過載幀
- 過載幀并不常用,因為當今的CAN控制器會非常智能化地避免使用過載幀
- 由硬件自動完成的,沒有辦法用軟件來控制
幀間隔
- 用于將數據幀及遙控幀與前面的幀分離開來的幀
- 由硬件自動完成的,沒有辦法用軟件來控制
總線仲裁
發送接收特點:
- CAN總線處于空閑狀態的時候,最先發送消息的單元獲得發送權
- 多個單元同時開始發送時,從仲裁段(報文id)的第一位開始進行仲裁
- 連續輸出顯性電平最多的單元可以繼續發送,即首先出現隱形電平的單元失去最總線的占有權變為接收。(即報文id小的優先級高)
- 競爭失敗,會自動檢測總線空閑,在第一時間再次嘗試發送
stm32的CAN外設
STM32的芯片中具有bxCAN控制器,它支持CAN協議2.0A 和2.0B Active標準。
- CAN2.0A只能處理標準數據幀且擴展幀的內容會織別錯誤。
- 而CAN2.0 B Active可以處理標準數據幀和擴展數據幀。
- CAN2.0 B Passive只能處理標準數據幀而擴展幀的內容會被忽略
- 該CAN控制器支持最高的通訊速率為1Mb/s
- 可以自動地接收和發送CAN報文
- 外設中具有3個發送郵箱,發送報文的優先級可以使用軟件控制,還可以記錄發送的時間
- 具有2個3級深度的接收FIFO,可使用過濾功能只接收或不接收某些ID號的報文
- 可配置成自動重發;不支持使用DMA進行數據收發
工作模式
CAN控制器有3種工作模式:
- 初始化模式
- 正常模式
- 睡眠模式
上電復位后CAN控制器默認會進入睡眠模式,作用是降低功耗。當需要將進行初始的時候(配置寄存器),會進入初始化模式。當需要通訊的時候,就進入正常模式
測試模式
有3種測試模式:
- 靜默模式
- 環回模式
- 環回靜默模式
當控制器進入初始化模式的時候才可以配置測試模式
- 靜默模式可以用于檢測總線的數據流量
- 環回模式可以用于自檢(影響總線)
- 環回靜默也是用于自檢,不會影響到總線
功能框圖
- 主動內核
- 含各種控制/狀態/配置寄存器,可以配置模式、波特率等。在STM32CubeMx中可以非常方便的配置
- 發送郵件
- 用來緩存待發送的報文,最多可以緩存3個報文。發送調度決定報文的發送順序
- 接收FIFO
- 共有2個接收FIFO,每個FIFO都可以存放3個完整的報文。它們完全由硬件來管理。從而節省了CPU的處理負荷,簡化了軟件并保證了數據的一致性。應用程序只能通過讀取FIFO輸出郵箱,來讀取FIFO中最先收到的報文
- 接收濾波器
- 做用:對接到的報文進行過濾。最后放入FIFO 0或FIFO 1
- 當總線上報文數據量很大時,總線上的設備會頻繁獲取報文,占用CPU。過濾器的存在,選擇性接收有效報文,減輕系統負擔
- 有2種過濾模式
- 標識符列表模式
- 它把要接收報文的ID列成一個表,要求報文ID與列表中的某一個標識符完全相同才可以接收,可以理解為白名單管理
- 掩碼模式(屏蔽位模式)
- 它把可接收報文ID的某幾位作為列表,這幾位被稱為掩碼,可以把它理解成關鍵字搜索,只要掩碼(關鍵字)相同,就符合要求,報文就會被保存到接收FIFO
- 標識符列表模式
時序
標準時序
- 與我們前面解釋的 CAN 標準時序有一點區別
- STM32的位時序:把傳播時間段和相位緩沖段1做了合并
例子??環回靜默模式測試
需求:我們使用環回靜默模式測試CAN能否正常工作。把接收到的報文數據發送到串口輸出,看是否可以正常工作
寄存器代碼
main.c
int main(void)
{usart1_init();printf("尚硅谷 CAN 通訊實驗: 靜默回環 寄存器版\r\n");CAN_Init();printf("CAN 初始化配置完成...\r\n");uint16_t stdId = 0x066;uint8_t *tData = "abcdefg";CAN_SendMsg(stdId, tData, strlen((char *)tData));printf("發送完畢...\r\n");tData = "123";CAN_SendMsg(stdId, tData, strlen((char *)tData));printf("發送完畢...\r\n");stdId = 0x067;tData = "xyz";CAN_SendMsg(stdId, tData, strlen((char *)tData));printf("發送完畢...\r\n");/* 1. 接收數據 */RxDataStruct rxDataStruct[8];uint8_t rxMsgCount;CAN_ReceiveMsg(rxDataStruct, &rxMsgCount);printf("接收完畢 rxMsgCount = %d...\r\n", rxMsgCount);/* 2. 輸出消息 */uint8_t i;for (i = 0; i < rxMsgCount; i++){RxDataStruct msg = rxDataStruct[i];printf("stdId = %d, length = %d, msgData = %s\r\n", msg.stdId, msg.length, msg.data);}while (1){}
}
can.h
#ifndef __CAN_H
#define __CAN_H
#include "stm32f10x.h"
#include "usart.h"
#include "string.h"/*** @description: 存儲接收到的數據* @return {*}*/
typedef struct
{uint16_t stdId;uint8_t data[8];uint8_t length;
} RxDataStruct;void CAN_Init(void);
void CAN_SendMsg(uint16_t stdId, uint8_t *data, uint8_t length);
void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount);#endif
can.c
#include "can.h"/*** @description: CAN 通訊初始化*/
void CAN_Init(void)
{/* 1. 開啟時鐘 CAN時鐘和GPIO時鐘 */RCC->APB1ENR |= RCC_APB1ENR_CAN1EN;RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;RCC->APB2ENR |= RCC_APB2ENR_AFIOEN;/* 2. 重定向PB8和PB9引腳 10:CAN_RX映像到PB8,CAN_TX映像到PB9 */AFIO->MAPR |= AFIO_MAPR_CAN_REMAP_1;AFIO->MAPR &= ~AFIO_MAPR_CAN_REMAP_0;/* 3. 初始化GPIO:PB9(CAN_Tx):復用推挽輸出 mode=11 cnf=10PB8(CAN_Rx): 浮空輸入 mode=00 cnf=01*/GPIOB->CRH |= GPIO_CRH_MODE9; /* mode = 11 */GPIOB->CRH |= GPIO_CRH_CNF9_1; /* cnf = 10 */GPIOB->CRH &= ~GPIO_CRH_CNF9_0;GPIOB->CRH &= ~GPIO_CRH_MODE8; /* mode = 00 */GPIOB->CRH &= ~GPIO_CRH_CNF8_1; /* cnf = 01 */GPIOB->CRH |= GPIO_CRH_CNF8_0;/* 4. 初始化 CAN *//* 4.1 進入初始化模式 */CAN1->MCR |= CAN_MCR_INRQ;while ((CAN1->MSR & CAN_MSR_INAK) == 0) /* 等待進入初始化模式 */;/* 4.2 退出睡眠模式 */CAN1->MCR &= ~CAN_MCR_SLEEP;while ((CAN1->MSR & CAN_MSR_SLAK) != 0) /* 等待退出睡眠模式 */;/* 4.3 自動離線管理。 允許自動退出離線狀態 */CAN1->MCR |= CAN_MCR_ABOM;/* 4.4 自動喚醒管理。 檢測到有報文,可以從睡眠模式由硬件自動喚醒。 */CAN1->MCR |= CAN_MCR_AWUM;/* 4.5 配置位時序寄存器 *//* 4.5.1 靜默模式 用于調試 */CAN1->BTR |= CAN_BTR_SILM;/* 4.5.2 回環模式 用于調試 */CAN1->BTR |= CAN_BTR_LBKM;/* 4.5.3 波特率分頻器,定義Tq的長度。配置35表示36分頻,則產生波特率的時鐘位1MHz。Tq = 1us*/CAN1->BTR &= ~CAN_BTR_BRP; /* 相應的位均置0 (9:0) */CAN1->BTR |= 35 << 0;/* 4.5.4 時間段1(3*Tq)和時間段2(6*Tq) */CAN1->BTR &= ~CAN_BTR_TS1;CAN1->BTR &= ~CAN_BTR_TS2;CAN1->BTR |= (3 << 16);CAN1->BTR |= (6 << 20);/* 4.5.5 再同步跳躍寬度 2*Tq*/CAN1->BTR &= ~CAN_BTR_SJW;CAN1->BTR |= (2 << 24);/* 4.6 退出初始化模式 */CAN1->MCR &= ~CAN_MCR_INRQ;while ((CAN1->MSR & CAN_MSR_INAK) != 0) /* 等待退出初始化模式 */;/* 4.7 配置過濾器: 接收所有消息 *//* 4.7.1 進入過濾器初始化模式 */CAN1->FMR |= CAN_FMR_FINIT;/* 4.7.2 過濾器組0工作模式: 掩碼模式 0:掩碼模式 1:標識符模式 */CAN1->FM1R &= ~CAN_FM1R_FBM0;/* 4.7.2 過濾器組0為單個32位配置 0:2給16位 1:單個32位*/CAN1->FS1R |= CAN_FS1R_FSC0;/* 4.7.3 給過濾器組0分配FIFO 0:FIFO0 1:FIFO1. 通過后的報文會放入這個FIFO中*/CAN1->FFA1R &= ~CAN_FFA1R_FFA0;/* 4.7.4 設置過濾器組0 標識符寄存器FR1 */CAN1->sFilterRegister[0].FR1 = 0x00000000; /* id每位都是0 *//* 4.7.5 設置過濾器組0 屏蔽位寄存器FR2 */CAN1->sFilterRegister[0].FR2 = 0x00000000; /* 屏蔽位是0,表示不關心ID對應的位。都是0,表示接收所有消息 *//* 4.7.6 激活過濾器組0 */CAN1->FA1R |= CAN_FA1R_FACT0;/* 4.7.7 退出過濾器初始化模式 */CAN1->FMR &= ~CAN_FMR_FINIT;
}/*** @description: 發送消息* @param {uint16_t} stdId 標準幀id* @param {uint8_t} *data 要發送的數據* @param {uint8_t} length 發送的數據的字節數*/
void CAN_SendMsg(uint16_t stdId,uint8_t *data,uint8_t length)
{if (length > 8){printf("數據長度不能超過8個字節\r\n");return;}/* 1. 等待郵箱0為空 (也可以判斷其他郵箱) 0:非空 1:空*/while ((CAN1->TSR & CAN_TSR_TME0) == 0);/* 2. 使用標準標識符 0:標準標識符 1:擴展標識符 */CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_IDE;/* 3. 0:數據幀 or 1:遠程幀 */CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_RTR;/* 4. 設置標準標識符 */CAN1->sTxMailBox[0].TIR &= ~CAN_TI0R_STID;CAN1->sTxMailBox[0].TIR |= (stdId << 21);/* 5. 設置數據長度 */CAN1->sTxMailBox[0].TDTR &= ~CAN_TDT0R_DLC;CAN1->sTxMailBox[0].TDTR |= (length << 0);/* 6. 設置數據 */uint8_t i;CAN1->sTxMailBox[0].TDLR = 0; /* 低位寄存器 */CAN1->sTxMailBox[0].TDHR = 0; /* 高位寄存器 */for (i = 0; i < length; i++){if (i < 4){CAN1->sTxMailBox[0].TDLR |= (data[i] << (8 * i));}else{CAN1->sTxMailBox[0].TDHR |= (data[i] << (8 * (i - 4)));}}/* 7. 請求發送數據 */CAN1->sTxMailBox[0].TIR |= CAN_TI0R_TXRQ;
}/*** @description:* @param {uint16_t} *stdId 讀取數據的標準id* @param {uint8_t} *data 讀取到的數據* @param {uint8_t} *length 讀取到的數據的長度*/
void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount)
{/* 1. 獲取 FIFO0 中的報文數 */*msgCount = (CAN1->RF0R & CAN_RF0R_FMP0) >> 0;uint8_t i, j;for (i = 0; i < *msgCount; i++){RxDataStruct *msg = &rxDataStruct[i];/* 2. 讀取標準標識符id */msg->stdId = (CAN1->sFIFOMailBox[0].RIR >> 21) & 0x7FF;/* 3. 讀取數據長度 */msg->length = (CAN1->sFIFOMailBox[0].RDTR >> 0) & 0x0F;/* 4. 讀取數據 */memset(msg->data, 0, sizeof((char *)msg->data));uint32_t low = CAN1->sFIFOMailBox[0].RDLR;uint32_t high = CAN1->sFIFOMailBox[0].RDHR;for (j = 0; j < msg->length; j++){if (j < 4){msg->data[j] = (low >> (8 * j)) & 0xFF;}else{msg->data[j] = (high >> (8 * (j - 4))) & 0xFF;}}/* 5. 釋放 FIFO 0. 則報文數減1*/CAN1->RF0R |= CAN_RF0R_RFOM0;}
}
HAL版本
?
main.c
int main(void)
{HAL_Init();SystemClock_Config();MX_GPIO_Init();MX_CAN_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 *//* 1. 配置過濾器 */CAN_Filter_Config();/* 2. 啟動CAN總線 */HAL_CAN_Start(&hcan);/* 3. 發送數據 */uint16_t stdId = 0x011;uint8_t *tData = "abcdefg";CAN_SendMsg(stdId, tData, strlen((char *)tData));printf("發送完畢...\r\n");tData = "123";CAN_SendMsg(stdId, tData, strlen((char *)tData));printf("發送完畢...\r\n");/* 4. 接收數據 */RxDataStruct rxDataStruct[8];uint8_t rxMsgCount;CAN_ReceiveMsg(rxDataStruct, &rxMsgCount);printf("接收完畢 rxMsgCount = %d...\r\n", rxMsgCount);/* 5. 輸出消息 */uint8_t i;for (i = 0; i < rxMsgCount; i++){RxDataStruct msg = rxDataStruct[i];printf("stdId = %d, length = %d, msgData = %s\r\n", msg.stdId, msg.length, msg.data);}while (1){}
}
can.h中添加
/* USER CODE BEGIN Prototypes */typedef struct{uint16_t stdId;uint8_t data[8];uint8_t length;} RxDataStruct;void CAN_Filter_Config(void);void CAN_SendMsg(uint16_t stdId, uint8_t *data, uint8_t length);void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount);/* USER CODE END Prototypes */
can.c中添加
/* USER CODE BEGIN 1 */
/*** @description: 配置過濾器*/
void CAN_Filter_Config()
{CAN_FilterTypeDef sFilterConfig;sFilterConfig.FilterBank = 0; // 過濾器編號, CAN1是0-13, CAN2是14-27sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 采用掩碼模式sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 設置篩選器的尺度, 采用32位sFilterConfig.FilterIdHigh = 0X0000; // 過濾器ID高16位,即CAN_FxR1寄存器的高16位sFilterConfig.FilterIdLow = 0X0000; // 過濾器ID低16位,即CAN_FxR1寄存器的低16位sFilterConfig.FilterMaskIdHigh = 0X0000; // 過濾器掩碼高16位,即CAN_FxR2寄存器的高16位sFilterConfig.FilterMaskIdLow = 0X0000; // 過濾器掩碼低16位,即CAN_FxR2寄存器的低16位sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // 設置經過篩選后數據存儲到哪個接收FIFOsFilterConfig.FilterActivation = ENABLE; // 是否使能本篩選器sFilterConfig.SlaveStartFilterBank = 14; // 指定為CAN1分配多少個濾波器組HAL_CAN_ConfigFilter(&hcan, &sFilterConfig);
}/*** @description: 發送信息* @param {uint16_t} stdId* @param {uint8_t} *data* @param {uint8_t} length*/
void CAN_SendMsg(uint16_t stdId,uint8_t *data,uint8_t length)
{/* 1. 檢測發送郵箱是否可用 */while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan) == 0);CAN_TxHeaderTypeDef txHeader;txHeader.IDE = CAN_ID_STD; // 標準幀還是擴展幀txHeader.RTR = CAN_RTR_DATA; // 幀的類型: 數據幀還是遠程幀txHeader.StdId = stdId; // 標準幀的idtxHeader.DLC = length; // 發送的數據長度 單位字節uint32_t txMailBox; // 會把這次使用的郵箱存入到這個變量/* 2. 發送消息 */HAL_CAN_AddTxMessage(&hcan, &txHeader, data, &txMailBox);
}/*** @description: 接收消息* @param {RxDataType} **/
void CAN_ReceiveMsg(RxDataStruct rxDataStruct[], uint8_t *msgCount)
{/* 1. 檢測FIFO0收到的報文個數 */*msgCount = HAL_CAN_GetRxFifoFillLevel(&hcan, CAN_RX_FIFO0);/* 2. 遍歷出所有消息 */uint8_t i;CAN_RxHeaderTypeDef rxHeader;for (i = 0; i < *msgCount; i++){HAL_CAN_GetRxMessage(&hcan, CAN_RX_FIFO0, &rxHeader, rxDataStruct[i].data);rxDataStruct[i].stdId = rxHeader.StdId;rxDataStruct[i].length = rxHeader.DLC;}
}
/* USER CODE END 1 */