控制器局域網總線(CAN,Controller Area Network)是一種用于實時應用的串行通訊協(xié)議總線,它可以使用雙絞線來傳輸信號,是世界上應用最廣泛的現場總線之一。CAN協(xié)議用于汽車中各種不同元件之間的通信,以此取代昂貴而笨重的配電線束。該協(xié)議的健壯性使其用途延伸到其他自動化和工業(yè)應用。CAN協(xié)議的特性包括完整性的串行數據通訊、提供實時支持、傳輸速率高達1Mb/s、同時具有11位的尋址以及檢錯能力。
特別說明:關于CAN總線協(xié)議和硬件電路,CANFD和CAN的區(qū)別等問題,這里不做介紹,網上的資料非常多,不懂的同學請自行查閱。
1 軟件編程
1.1 建立工程
要建立一個HAL庫版本的工程可以直接使用其他項目的HAL工程,也可以通過移植HAL固件庫或者用STM32CubeMX生成。
我這里以STM32G0為例講解一下如何用STM32CubeMX生成一個工程。
特別說明:如果不使用STM32CubeMX工具,可以跳過以下步驟,直接從1.2開始,把CANFD相關代碼加入其他HAL工程即可。
1、配置時鐘
我這里使用外部晶振時鐘(HSE),8M晶振倍頻到64M時鐘。
2、配置引腳
選擇自己實際使用的引腳作為CAN_TX和CAN_RX。
3、配置CAN參數
我這里用CAN1作為CANFD,CAN2作為普通CAN。
CAN1配置參考如下:
特別說明:以下數據僅供參考,請根據實際情況配置。
CAN2配置參考如下:
特別說明:以下數據僅供參考,請根據實際情況配置。
最后使能中斷(注:CAN1和CAN2可以共用一個中斷接口)。
4、生成工程
1.2 初始化
初始化主要分成三部分:引腳設置,CAN參數設置和CAN濾波器設置。
1.2.1 引腳設置
把CAN_H和CAN_L兩個引腳配置成復用功能即可。
注:如果CAN控制芯片的S引腳連接到STM32的話,還得初始化這個引腳,S引腳可以配置成高速模式或靜音模式。
參考代碼:
注:該代碼可以通過STM32CubeMX生成
static uint32_t HAL_RCC_FDCAN_CLK_ENABLED=0;
void HAL_FDCAN_MspInit(FDCAN_HandleTypeDef* fdcanHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
RCC_PeriphCLKInitTypeDef PeriphClkInit = {0};
if(fdcanHandle->Instance==FDCAN1)
{
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* FDCAN1 clock enable */
HAL_RCC_FDCAN_CLK_ENABLED++;
if(HAL_RCC_FDCAN_CLK_ENABLED==1){
__HAL_RCC_FDCAN_CLK_ENABLE();
}
__HAL_RCC_GPIOB_CLK_ENABLE();
/**FDCAN1 GPIO Configuration
PB8 ------> FDCAN1_RX
PB9 ------> FDCAN1_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_8|GPIO_PIN_9;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF3_FDCAN1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* FDCAN1 interrupt Init */
HAL_NVIC_SetPriority(TIM16_FDCAN_IT0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM16_FDCAN_IT0_IRQn);
}
else if(fdcanHandle->Instance==FDCAN2)
{
PeriphClkInit.PeriphClockSelection = RCC_PERIPHCLK_FDCAN;
PeriphClkInit.FdcanClockSelection = RCC_FDCANCLKSOURCE_PCLK1;
if (HAL_RCCEx_PeriphCLKConfig(&PeriphClkInit) != HAL_OK)
{
Error_Handler();
}
/* FDCAN2 clock enable */
HAL_RCC_FDCAN_CLK_ENABLED++;
if(HAL_RCC_FDCAN_CLK_ENABLED==1){
__HAL_RCC_FDCAN_CLK_ENABLE();
}
__HAL_RCC_GPIOB_CLK_ENABLE();
/**FDCAN2 GPIO Configuration
PB5 ------> FDCAN2_RX
PB6 ------> FDCAN2_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_5|GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF3_FDCAN2;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* FDCAN2 interrupt Init */
HAL_NVIC_SetPriority(TIM16_FDCAN_IT0_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM16_FDCAN_IT0_IRQn);
}
}
1.2.2 CAN基本參數設置
HAL庫的CAN初始化有幾個重要參數,都存放在幾個結構體里面(如:CAN_HandleTypeDef,CAN_InitTypeDef),具體的結構體定義可以在HAL庫查看。
說明:CAN參數需要根據自己實際的需求來配。
我這里著重講解一下CAN波特率的配置。
CAN波特率 = CAN時鐘頻率 / 時鐘分頻 / 預分頻系數 / (TimeSeg1 + TimeSeg2 + 1)。
其中,CAN時鐘頻率不是固定不變的,它取決于CAN所掛載的總線時鐘。
比如STM32F1,系統(tǒng)時鐘最大72M,APB1的總線時鐘最大36M,而CAN控制器的時鐘是掛在APB1的,所以CAN的時鐘頻率也等于APB1的時鐘。
如果換作其他型號的MCU,CAN外設不一定是掛載到APB1上面的,時鐘也不一定是36M,比如F4系列,APB1的時鐘是可以配成42M的,因此,這個要根據實際情況來配置。
本例用的是STM32G0,CAN時鐘頻率配置為64MHz。
參考代碼:
注:該代碼可以通過STM32CubeMX生成
FDCAN_HandleTypeDef hfdcan1;
FDCAN_HandleTypeDef hfdcan2;
/* FDCAN1 init function */
void MX_FDCAN1_Init(void)
{
/* USER CODE END FDCAN1_Init 1 */
// CAN波特率 = 時鐘頻率 / 時鐘分頻 / 預分頻系數 / (1 + TSG1 + TSG2)
// 仲裁段波特率 = 64M / 1 / 8 / (1 + 10 + 5) = 500k
// 數據段波特率 = 64M / 1 / 2 / (1 + 10 + 5) = 2M
hfdcan1.Instance = FDCAN1; // FDCAN1
hfdcan1.Init.ClockDivider = FDCAN_CLOCK_DIV1; // 時鐘分頻
hfdcan1.Init.FrameFormat = FDCAN_FRAME_FD_BRS; // 配置為CANFD
hfdcan1.Init.Mode = FDCAN_MODE_NORMAL; // 正常工作狀態(tài)
hfdcan1.Init.AutoRetransmission = DISABLE; // 關閉自動重傳, 傳統(tǒng)模式下需關閉
hfdcan1.Init.TransmitPause = DISABLE; // 關閉傳輸暫停
hfdcan1.Init.ProtocolException = DISABLE; // 關閉協(xié)議異常處理
hfdcan1.Init.NominalPrescaler = 8; // 仲裁段時鐘分頻,傳統(tǒng)CAN模式時只設置這一段即可
hfdcan1.Init.NominalSyncJumpWidth = 1; // 仲裁段同步跳轉段的寬度
hfdcan1.Init.NominalTimeSeg1 = 10; // 仲裁段時間段1
hfdcan1.Init.NominalTimeSeg2 = 5; // 仲裁段時間段2
hfdcan1.Init.DataPrescaler = 2; // 數據段時鐘分頻,若工作于傳統(tǒng)模式,不需要設置數據段參數
hfdcan1.Init.DataSyncJumpWidth = 1; // 數據段同步跳轉段的寬度
hfdcan1.Init.DataTimeSeg1 = 10; // 數據段時間段1
hfdcan1.Init.DataTimeSeg2 = 5; // 數據段時間段2
hfdcan1.Init.StdFiltersNbr = 28; // 標準幀濾波器數量
hfdcan1.Init.ExtFiltersNbr = 8; // 擴展幀濾波器數量
hfdcan1.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // 發(fā)送模式:先入先出
if (HAL_FDCAN_Init(&hfdcan1) != HAL_OK)
{
Error_Handler();
}
}
/* FDCAN2 init function */
void MX_FDCAN2_Init(void)
{
/* USER CODE END FDCAN2_Init 1 */
// CAN波特率 = 時鐘頻率 / 時鐘分頻 / 預分頻系數 / (1 + TSG1 + TSG2) = 64M / 1 / 8 / (1 + 10 + 5) = 500k
hfdcan2.Instance = FDCAN2; // FDCAN2
hfdcan2.Init.ClockDivider = FDCAN_CLOCK_DIV1; // 時鐘分頻
hfdcan2.Init.FrameFormat = FDCAN_FRAME_CLASSIC; // 配置為傳統(tǒng)模式
hfdcan2.Init.Mode = FDCAN_MODE_NORMAL; // 正常工作狀態(tài)
hfdcan2.Init.AutoRetransmission = DISABLE; // 關閉自動重傳, 傳統(tǒng)模式下需關閉
hfdcan2.Init.TransmitPause = DISABLE; // 關閉傳輸暫停
hfdcan2.Init.ProtocolException = DISABLE; // 關閉協(xié)議異常處理
hfdcan2.Init.NominalPrescaler = 8; // 仲裁段時鐘分頻,傳統(tǒng)CAN模式時只設置這一段即可
hfdcan2.Init.NominalSyncJumpWidth = 1; // 仲裁段同步跳轉段的寬度
hfdcan2.Init.NominalTimeSeg1 = 10; // 仲裁段時間段1
hfdcan2.Init.NominalTimeSeg2 = 5; // 仲裁段時間段2
hfdcan2.Init.DataPrescaler = 8; // 數據段時鐘分頻,若工作于傳統(tǒng)模式,不需要設置數據段參數
hfdcan2.Init.DataSyncJumpWidth = 1; // 數據段同步跳轉段的寬度
hfdcan2.Init.DataTimeSeg1 = 10; // 數據段時間段1
hfdcan2.Init.DataTimeSeg2 = 5; // 數據段時間段2
hfdcan2.Init.StdFiltersNbr = 28; // 標準幀濾波器數量
hfdcan2.Init.ExtFiltersNbr = 8; // 擴展幀濾波器數量
hfdcan2.Init.TxFifoQueueMode = FDCAN_TX_FIFO_OPERATION; // 發(fā)送模式:先入先出
if (HAL_FDCAN_Init(&hfdcan2) != HAL_OK)
{
Error_Handler();
}
}
1.2.3 CAN收發(fā)初始化設置
CAN接收需要設置CAN濾波器,它的主要作用是篩選CAN接收的數據,只有滿足設定規(guī)則的數據才會被接收,否則會被過濾掉。
而CAN發(fā)送則需要在發(fā)送一條數據前,先配置好這條數據的信息,如CANID,發(fā)送長度,數據類型(CAN/CANFD)等等。
參考代碼:
FDCAN_TxHeaderTypeDef TxHeader1;
FDCAN_RxHeaderTypeDef RxHeader1;
FDCAN_TxHeaderTypeDef TxHeader2;
FDCAN_RxHeaderTypeDef RxHeader2;
void FDCAN1_Config(void)
{
FDCAN_FilterTypeDef sFilterConfig;
/* Configure Rx filter */
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 1;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x00000000;
sFilterConfig.FilterID2 = 0x000007FF;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
sFilterConfig.IdType = FDCAN_EXTENDED_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x00000000;
sFilterConfig.FilterID2 = 0x1FFFFFFF;
if (HAL_FDCAN_ConfigFilter(&hfdcan1, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/* Configure global filter:
Filter all remote frames with STD and EXT ID
Reject non matching frames with STD ID and EXT ID */
if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan1, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
Error_Handler();
}
/* Activate Rx FIFO 0 new message notification on both FDCAN instances */
if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
{
Error_Handler();
}
// if (HAL_FDCAN_ActivateNotification(&hfdcan1, FDCAN_IT_BUS_OFF, 0) != HAL_OK)
// {
// Error_Handler();
// }
/* Tx Config*/
TxHeader1.Identifier = 0x000000000; // CAN ID
TxHeader1.IdType = FDCAN_STANDARD_ID; // 標準ID
TxHeader1.TxFrameType = FDCAN_DATA_FRAME;
TxHeader1.DataLength = FDCAN_DLC_BYTES_8;
TxHeader1.ErrorStateIndicator = FDCAN_ESI_PASSIVE;
TxHeader1.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader1.FDFormat = FDCAN_FD_CAN; // CANFD
TxHeader1.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader1.MessageMarker = 0;
/* Configure and enable Tx Delay Compensation, required for BRS mode.
TdcOffset default recommended value: DataTimeSeg1 * DataPrescaler
TdcFilter default recommended value: 0 */
HAL_FDCAN_ConfigTxDelayCompensation(&hfdcan1, hfdcan1.Init.DataPrescaler * hfdcan1.Init.DataTimeSeg1, 0);
HAL_FDCAN_EnableTxDelayCompensation(&hfdcan1);
/* Start the FDCAN module */
if (HAL_FDCAN_Start(&hfdcan1) != HAL_OK)
{
Error_Handler();
}
}
void FDCAN2_Config(void)
{
FDCAN_FilterTypeDef sFilterConfig;
/* Configure Rx filter */
sFilterConfig.IdType = FDCAN_STANDARD_ID;
sFilterConfig.FilterIndex = 1;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x00000000;
sFilterConfig.FilterID2 = 0x000007FF;
if (HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
sFilterConfig.IdType = FDCAN_EXTENDED_ID;
sFilterConfig.FilterIndex = 0;
sFilterConfig.FilterType = FDCAN_FILTER_RANGE;
sFilterConfig.FilterConfig = FDCAN_FILTER_TO_RXFIFO0;
sFilterConfig.FilterID1 = 0x00000000;
sFilterConfig.FilterID2 = 0x1FFFFFFF;
if (HAL_FDCAN_ConfigFilter(&hfdcan2, &sFilterConfig) != HAL_OK)
{
Error_Handler();
}
/* Configure global filter:
Filter all remote frames with STD and EXT ID
Reject non matching frames with STD ID and EXT ID */
if (HAL_FDCAN_ConfigGlobalFilter(&hfdcan2, FDCAN_REJECT, FDCAN_REJECT, FDCAN_FILTER_REMOTE, FDCAN_FILTER_REMOTE) != HAL_OK)
{
Error_Handler();
}
/* Activate Rx FIFO 0 new message notification on both FDCAN instances */
if (HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_RX_FIFO0_NEW_MESSAGE, 0) != HAL_OK)
{
Error_Handler();
}
// if (HAL_FDCAN_ActivateNotification(&hfdcan2, FDCAN_IT_BUS_OFF, 0) != HAL_OK)
// {
// Error_Handler();
// }
/* Tx Config*/
TxHeader2.Identifier = 0x000000000; // CAN ID
TxHeader2.IdType = FDCAN_STANDARD_ID; // 標準ID
TxHeader2.TxFrameType = FDCAN_DATA_FRAME;
TxHeader2.DataLength = FDCAN_DLC_BYTES_8;
TxHeader2.ErrorStateIndicator = FDCAN_ESI_PASSIVE;
TxHeader2.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader2.FDFormat = FDCAN_CLASSIC_CAN; // CAN
TxHeader2.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader2.MessageMarker = 0;
/* Configure and enable Tx Delay Compensation, required for BRS mode.
TdcOffset default recommended value: DataTimeSeg1 * DataPrescaler
TdcFilter default recommended value: 0 */
HAL_FDCAN_ConfigTxDelayCompensation(&hfdcan2, hfdcan2.Init.DataPrescaler * hfdcan2.Init.DataTimeSeg1, 0);
HAL_FDCAN_EnableTxDelayCompensation(&hfdcan2);
/* Start the FDCAN module */
if (HAL_FDCAN_Start(&hfdcan2) != HAL_OK)
{
Error_Handler();
}
}
1.2.4 中斷設置
如果使用CAN接收,需要配置中斷服務函數。
參考代碼:
/**
* @brief This function handles TIM16, FDCAN1_IT0 and FDCAN2_IT0 Interrupt.
*/
void TIM16_FDCAN_IT0_IRQHandler(void)
{
HAL_FDCAN_IRQHandler(&hfdcan1);
HAL_FDCAN_IRQHandler(&hfdcan2);
}
1.3 CAN發(fā)送
CAN發(fā)送需要先配置發(fā)送參數,我這里為了方便測試,直接固定發(fā)送標準幀,ID也是固定的。
實際使用時可以把CANID,數據長度等參數作為函數入參,這樣會更靈活一點。
參考代碼:
void canfd_tx_test(void)
{
TxHeader1.Identifier = 0x123; // CAN ID
TxHeader1.IdType = FDCAN_STANDARD_ID; // 標準ID
TxHeader1.TxFrameType = FDCAN_DATA_FRAME;
TxHeader1.DataLength = FDCAN_DLC_BYTES_8; // 發(fā)送長度:8byte
TxHeader1.ErrorStateIndicator = FDCAN_ESI_PASSIVE;
TxHeader1.BitRateSwitch = FDCAN_BRS_OFF;
TxHeader1.FDFormat = FDCAN_FD_CAN; // CANFD
TxHeader1.TxEventFifoControl = FDCAN_NO_TX_EVENTS;
TxHeader1.MessageMarker = 0;
if(HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader1, can1_txbuf) != HAL_OK)
{
Error_Handler();
}
}
1.4 CAN接收
接收部分只要開啟了Rx中斷,在CAN控制器收到消息時會調用RxFifo的回調函數,此時在這里讀取數據并根據實際情況做相應的處理即可。我這里把收到的數據又發(fā)回去了,這樣可以同時驗證接收和發(fā)送。
參考代碼:
void HAL_FDCAN_RxFifo0Callback(FDCAN_HandleTypeDef *hfdcan, uint32_t RxFifo0ITs)
{
if((RxFifo0ITs & FDCAN_IT_RX_FIFO0_NEW_MESSAGE) != RESET)
{
if(hfdcan->Instance == FDCAN1)
{
/* Retrieve Rx messages from RX FIFO0 */
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader1, can1_rxbuf) != HAL_OK)
{
Error_Handler();
}
/* 把接收到的數據以CANFD發(fā)送回去 */
TxHeader1.DataLength = RxHeader1.DataLength;
TxHeader1.Identifier = RxHeader1.Identifier;
if(HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan1, &TxHeader1, can1_rxbuf) != HAL_OK)
{
Error_Handler();
}
}
if(hfdcan->Instance == FDCAN2)
{
/* Retrieve Rx messages from RX FIFO0 */
if (HAL_FDCAN_GetRxMessage(hfdcan, FDCAN_RX_FIFO0, &RxHeader2, can2_rxbuf) != HAL_OK)
{
Error_Handler();
}
/* 把接收到的數據以CAN發(fā)送回去 */
TxHeader2.DataLength = RxHeader2.DataLength;
TxHeader2.Identifier = RxHeader2.Identifier;
if(HAL_FDCAN_AddMessageToTxFifoQ(&hfdcan2, &TxHeader2, can2_rxbuf) != HAL_OK)
{
Error_Handler();
}
}
}
}
2 運行測試
使用USB-CAN工具測試。
CAN1使用CANFD(仲裁域波特率500k,數據域波特率2M),上位機每發(fā)送一條數據,STM32收到后同樣回一條一樣的數據,CANID、數據長度和數據都是一致,說明STM32收到的數據沒有問題。
CAN2使用普通CAN(仲裁域和數據域波特率均為500k),上位機每發(fā)送一條數據,STM32收到后同樣回一條一樣的數據,CANID、數據長度和數據都是一致,說明STM32收到的數據沒有問題。
還有一些其他測試,比如切換擴展幀,遠程幀等等,這里就不展示了,感興趣的同學可以自己改參數試試。
結論:CAN收發(fā)正常。
結束語
好了,關于如何通過STM32如何配置和使用CANFD就講到這里,如果你有什么問題或者有更好的方法,歡迎在評論區(qū)留言。
需要源碼工程的同學可以自行下載:點擊跳轉下載鏈接