控制器局域網(wǎng)總線(CAN,Controller Area Network)是一種用于實(shí)時(shí)應(yīng)用的串行通訊協(xié)議總線,它可以使用雙絞線來(lái)傳輸信號(hào),是世界上應(yīng)用最廣泛的現(xiàn)場(chǎng)總線之一。CAN協(xié)議用于汽車中各種不同元件之間的通信,以此取代昂貴而笨重的配電線束。該協(xié)議的健壯性使其用途延伸到其他自動(dòng)化和工業(yè)應(yīng)用。CAN協(xié)議的特性包括完整性的串行數(shù)據(jù)通訊、提供實(shí)時(shí)支持、傳輸速率高達(dá)1Mb/s、同時(shí)具有11位的尋址以及檢錯(cuò)能力。
特別說(shuō)明:關(guān)于CAN總線協(xié)議和硬件電路等問(wèn)題,這里不做介紹,網(wǎng)上的資料非常多,不懂的同學(xué)請(qǐng)自行查閱。
1 軟件編程
1.1 初始化
初始化主要分成三部分:引腳設(shè)置,CAN參數(shù)設(shè)置和CAN濾波器設(shè)置。
1.1.1 引腳設(shè)置
把CAN_H和CAN_L兩個(gè)引腳配置成復(fù)用功能即可。
注:如果CAN控制芯片的S引腳連接到STM32的話,還得初始化這個(gè)引腳,S引腳可以配置成高速模式或靜音模式。
參考代碼:
注:該代碼可以通過(guò)STM32CubeMX生成
/**
* @brief CAN MSP Initialization
* This function configures the hardware resources used in this example
* @param hcan: CAN handle pointer
* @retval None
*/
void HAL_CAN_MspInit(CAN_HandleTypeDef* hcan)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(hcan->Instance==CAN1)
{/* USER CODE END CAN1_MspInit 0 */
/* Peripheral clock enable */
HAL_RCC_CAN1_CLK_ENABLED++;
if(HAL_RCC_CAN1_CLK_ENABLED==1){
__HAL_RCC_CAN1_CLK_ENABLE();
}
__HAL_RCC_GPIOA_CLK_ENABLE();
/**CAN1 GPIO Configuration
PA11 ------> CAN1_RX
PA12 ------> CAN1_TX
*/
GPIO_InitStruct.Pin = GPIO_PIN_11|GPIO_PIN_12;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF9_CAN1;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
/* CAN1 interrupt Init */
HAL_NVIC_SetPriority(CAN1_RX0_IRQn, 0, 0); // CAN接收中斷
HAL_NVIC_EnableIRQ(CAN1_RX0_IRQn);
}
}
1.1.2 CAN參數(shù)設(shè)置
HAL庫(kù)的CAN初始化有幾個(gè)重要參數(shù),都存放在幾個(gè)結(jié)構(gòu)體里面(如:CAN_HandleTypeDef,CAN_InitTypeDef),具體的結(jié)構(gòu)體定義可以在HAL庫(kù)查看。
說(shuō)明:CAN參數(shù)需要根據(jù)自己實(shí)際的需求來(lái)配。
我這里著重講解一下CAN波特率的配置。
CAN波特率 = CAN時(shí)鐘頻率 / 分頻系數(shù) / (TimeSeg1 + TimeSeg2 + 1)。
其中,CAN時(shí)鐘頻率不是固定不變的,它取決于CAN所掛載的總線時(shí)鐘。
比如STM32F1,系統(tǒng)時(shí)鐘最大72M,APB1的總線時(shí)鐘最大36M,而CAN控制器的時(shí)鐘是掛在APB1的,所以CAN的時(shí)鐘頻率也等于APB1的時(shí)鐘。
如果換作其他型號(hào)的MCU,CAN外設(shè)不一定是掛載到APB1上面的,時(shí)鐘也不一定是36M,比如F4系列,APB1的時(shí)鐘是可以配成42M的,因此,這個(gè)要根據(jù)實(shí)際情況來(lái)配置。
參考代碼:
注:該代碼可以通過(guò)STM32CubeMX生成
/**
* @brief CAN1 Initialization Function
* @param None
* @retval None
*/
static void MX_CAN_Init(void)
{
// CAN波特率 = CAN時(shí)鐘頻率 / Prescaler / (TimeSeg1 + TimeSeg2 + 1)
// 例: 500kbps = 36MHz / 9 / (3 + 4 + 1) 36MHz為該例程APB1的總線時(shí)鐘
/* USER CODE END CAN1_Init 1 */
hcan.Instance = CAN1; // 配置CAN1
hcan.Init.Prescaler = 9; // 預(yù)分頻系數(shù)
hcan.Init.Mode = CAN_MODE_NORMAL; // 正常CAN模式
hcan.Init.SyncJumpWidth = CAN_SJW_1TQ; // 重同步跳躍寬度,CAN_SJW_1TQ~CAN_SJW_4TQ
hcan.Init.TimeSeg1 = CAN_BS1_3TQ; // TimeSeg1
hcan.Init.TimeSeg2 = CAN_BS2_4TQ; // TimeSeg2
hcan.Init.TimeTriggeredMode = DISABLE; // 非時(shí)間觸發(fā)通信模式
hcan.Init.AutoBusOff = DISABLE; // 軟件自動(dòng)離線管理
hcan.Init.AutoWakeUp = DISABLE; // 睡眠模式通過(guò)軟件喚醒(清除CAN->MCR的SLEEP位)
hcan.Init.AutoRetransmission = DISABLE; // 禁止報(bào)文自動(dòng)重傳
hcan.Init.ReceiveFifoLocked = DISABLE; // FIFO報(bào)文不鎖定,新的覆蓋舊的
hcan.Init.TransmitFifoPriority = DISABLE; // 優(yōu)先級(jí)由報(bào)文標(biāo)識(shí)符決定
if (HAL_CAN_Init(&hcan) != HAL_OK)
{
Error_Handler();
}
}
1.1.3 CAN濾波器設(shè)置
CAN濾波器的主要作用是篩選CAN接收的數(shù)據(jù),只有滿足設(shè)定規(guī)則的數(shù)據(jù)才會(huì)被接收,否則會(huì)被過(guò)濾掉。
參考代碼:
void CAN_Config(void)
{
CAN_FilterTypeDef sFilterConfig;
/* Configure the CAN Filter */
sFilterConfig.FilterBank = 0; // 過(guò)濾器編號(hào),使用一個(gè)CAN,則可選0-13;使用兩個(gè)CAN可選0-27
sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; // 過(guò)濾器模式,掩碼模式或列表模式
sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; // 過(guò)濾器位寬
sFilterConfig.FilterIdHigh = 0x0000; // 過(guò)濾器驗(yàn)證碼ID高16位,0-0xFFFF
sFilterConfig.FilterIdLow = 0x0000; // 過(guò)濾器驗(yàn)證碼ID低16位,0-0xFFFF
sFilterConfig.FilterMaskIdHigh = 0x0000; // 過(guò)濾器掩碼ID高16位,0-0xFFFF
sFilterConfig.FilterMaskIdLow = 0x0000; // 過(guò)濾器掩碼ID低16位,0-0xFFFF
sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; // FIFOx,0或1
sFilterConfig.FilterActivation = ENABLE; // 使能過(guò)濾器
sFilterConfig.SlaveStartFilterBank = 14; // 從過(guò)濾器編號(hào),0-27,對(duì)于單CAN實(shí)例該參數(shù)沒(méi)有意義
if (HAL_CAN_ConfigFilter(&hcan, &sFilterConfig) != HAL_OK)
{
/* Filter configuration Error */
}
/* Start the CAN peripheral */
if (HAL_CAN_Start(&hcan) != HAL_OK)
{
/* Start Error */
}
/* Activate CAN RX notification */
if (HAL_CAN_ActivateNotification(&hcan, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK)
{
/* Notification Error */
}
}
1.2 CAN發(fā)送
CAN發(fā)送需要先配置發(fā)送參數(shù),我這里為了方便測(cè)試,直接固定發(fā)送標(biāo)準(zhǔn)幀,ID也是固定的。
實(shí)際使用時(shí)可以再增加一個(gè)ID的入?yún)ⅲ@樣會(huì)更靈活一點(diǎn)。
參考代碼:
/****************************************************************************
* 名 稱: uint8_t CAN_Send_Msg(uint8_t* msg, uint8_t len)
* 功 能:can發(fā)送一組數(shù)據(jù)(固定格式:ID為0X12,標(biāo)準(zhǔn)幀,數(shù)據(jù)幀)
* 入口參數(shù):len:數(shù)據(jù)長(zhǎng)度(最大為8)
msg:數(shù)據(jù)指針,最大為8個(gè)字節(jié).
* 返回參數(shù):0,成功;
其他,失敗;
* 說(shuō) 明:
****************************************************************************/
uint8_t CAN_Send_Msg(uint8_t* msg, uint8_t len)
{
uint8_t i=0;
uint8_t message[8];
uint32_t TxMailbox;
CAN_TxHeaderTypeDef CAN_TxHeader;
// 設(shè)置發(fā)送參數(shù)
CAN_TxHeader.StdId = 0x12; // 標(biāo)準(zhǔn)標(biāo)識(shí)符(12bit)
CAN_TxHeader.ExtId = 0x12; // 擴(kuò)展標(biāo)識(shí)符(29bit)
CAN_TxHeader.IDE = CAN_ID_STD; // 使用標(biāo)準(zhǔn)幀
CAN_TxHeader.RTR = CAN_RTR_DATA; // 數(shù)據(jù)幀
CAN_TxHeader.DLC = len; // 發(fā)送長(zhǎng)度
CAN_TxHeader.TransmitGlobalTime = DISABLE;
// 裝載數(shù)據(jù)
for(i = 0; i < len; i++)
{
message[i] = msg[i];
}
// 發(fā)送CAN消息
if(HAL_CAN_AddTxMessage(&hcan, &CAN_TxHeader, message, &TxMailbox) != HAL_OK)
{
return 1;
}
while(HAL_CAN_GetTxMailboxesFreeLevel(&hcan) != 3)
{
}
return 0;
}
1.3 CAN接收
接收部分只要開(kāi)啟了Rx中斷,在CAN控制器收到消息時(shí)會(huì)調(diào)用RxFifo的回調(diào)函數(shù),此時(shí)我們?cè)谶@里讀取數(shù)據(jù)并根據(jù)實(shí)際情況做相應(yīng)的處理即可。
參考代碼:
/*******************************************************************************
* Function Name : HAL_CAN_RxFifo0MsgPendingCallback
* Description : 消息接收回調(diào)函數(shù)
* Input : hcan
* Output : None
* Return : None
****************************************************************************** */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{// 收到CAN數(shù)據(jù)會(huì)觸發(fā)接收中斷,進(jìn)入該回調(diào)函數(shù)
uint32_t i;
uint8_t RxData[8];
CAN_RxHeaderTypeDef CAN_RxHeader;
if(HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &CAN_RxHeader, RxData) == HAL_OK)
{
// 串口打印接收結(jié)果
printf("GetRxMessage, CANID:0x%0X, Data:", CAN_RxHeader.StdId);
for(i = 0;i < CAN_RxHeader.DLC; i++)
{
printf("%02x ", RxData[i]);
}
// 把接收的數(shù)據(jù)用CAN再發(fā)回去
CAN_Send_Msg(RxData, CAN_RxHeader.DLC);
}
}
2 運(yùn)行測(cè)試
使用USB-CAN工具測(cè)試發(fā)送:
消息窗口如下:
可以看到,上位機(jī)發(fā)送了一條CAN數(shù)據(jù),CANID為0x01,接著就收到了STM32回的一條數(shù)據(jù),CANID為0x12(因?yàn)槲掖a固定寫(xiě)死了ID為0x12)。
同樣的,通過(guò)串口也能看到STM32收到的CAN數(shù)據(jù),如下圖所示:
還有一些其他測(cè)試,比如收發(fā)不同長(zhǎng)度,切換擴(kuò)展幀等等,這里就不展示了,感興趣的同學(xué)可以自己改參數(shù)試試。
結(jié)論:CAN收發(fā)正常。
結(jié)束語(yǔ)
好了,關(guān)于如何通過(guò)STM32如何配置和使用CAN就講到這里,如果你有什么問(wèn)題或者有更好的方法,歡迎在評(píng)論區(qū)留言。