STM32 教學系列 第 10 節:CANbus 通訊(兩板間通訊) 🎯 學習目標 理解 CAN 協定基礎 - 掌握識別符、優先級仲裁與訊息過濾 配置 TJA1050 收發器 - 實現 Nucleo 與 Nucleo 的 CAN 通訊 實現雙板訊息收發 - 一片做發送方,另一片做接收方 🎓 CAN 協定基礎 CAN 是什麼? CAN (Controller Area Network) 是車用工業級網路協定。特點: 強抗干擾 :差分信號設計 實時性好 :優先級仲裁確保關鍵訊息先傳 多節點 :最多 127 個節點共享一條總線 長距離 :可達 40km(低速)或 1km(高速) CAN 消息結構 欄位 長度 說明 ID 11 bit(標準)/ 29 bit(擴展) 訊息識別符,用於優先級仲裁 DLC 4 bit 資料長度碼(0-8 bytes) DATA 0-8 bytes 實際負載資料 CRC 15 bit 循環冗餘碼(硬體自動計算) CAN 的 2 條信號線 訊號 說明 CAN_H CAN 高位線 CAN_L CAN 低位線 GND 共同接地 STM32F446 CAN 資源 特性 規格 CAN 模組 2 個(CAN1、CAN2) 最高速度 1 Mbps 訊息濾波器 14 個過濾器組 接收 FIFO 2 個(每個 3 訊息) 傳輸郵箱 3 個 🛠️ 硬體接線 所需元件 元件 數量 說明 Nucleo-F446RC 2 一發一收 TJA1050 CAN 收發器 2 轉換 CAN 信號 120Ω 終端電阻 2 CAN 總線兩端各 1 跳線 多條 連接所有元件 CAN 總線接線 Nucleo 1 (發送方) TJA1050 收發器 CAN 總線 TJA1050 收發器 Nucleo 2 (接收方) ───────────────── ───────────────── ────────── ───────────────── ───────────────── PB8 (CAN1_RX) ───────→ RXD ← RXD ─── PB8 (CAN1_RX) PB9 (CAN1_TX) ←────── TXD ──→ TXD ─── PB9 (CAN1_TX) 3.3V ──────────────→ VCC ← VCC ──────── 3.3V GND ───────────────→ GND ← GND ──────── GND ┌──────→ CANH ═════════════════════════════════════════════════ CANH ────┐ │ │ │ [120Ω] ← 終端電阻 [120Ω] ← 終端電阻 │ │ │ │ │ │ GND GND │ │ │ └──────→ CANL ═════════════════════════════════════════════════ CANL ────┘ 接線表 | Nucleo PB8 | → | TJA1050 RXD | | Nucleo PB9 | ← | TJA1050 TXD | | Nucleo 3.3V | → | TJA1050 VCC | | Nucleo GND | → | TJA1050 GND | | TJA1050 CANH | ═ | CAN 總線 H | | TJA1050 CANL | ═ | CAN 總線 L | ⚙️ CubeMX 配置步驟 步驟 1:啟用 CAN1(兩片板都設定相同) 開啟 CubeMX 在 Pinout 圖中選擇: PB8 :配置為 CAN1_RX PB9 :配置為 CAN1_TX 步驟 2:配置 CAN 參數 在左側選擇 Connectivity → CAN1 Activated :勾選啟用 Parameter Settings 中設定: Mode :Normal Mode Prescaler :8(波特率 = 168MHz / 8 / 13 ≈ 1.615 Mbps,調整至 1 Mbps) Time Quanta in Bit Segment 1 :11 Time Quanta in Bit Segment 2 :2 Resynchronization Jump Width :1 Filter Settings :配置過濾器 Number of Master Filters :14 Filters Configuration : Filter ID :0x000(接收所有 ID) Filter Mask :0x000 Filter FIFO Assignment :FIFO0 Filter Activation :Enable 步驟 3:啟用中斷 NVIC Settings 中勾選: CAN1 RX0 interrupt CAN1 TX interrupt 步驟 4:生成程式碼 點擊 Generate Code 💻 完整程式碼 發送方 (Nucleo 1):main.c /* STM32 Lesson 10 - CAN Bus (Sender) * 功能:發送 CAN 訊息到另一片 Nucleo * 難度:中級 */ #include "main.h" #include "can.h" #include "usart.h" #include #include #define CAN_ID_TEST 0x123 /* CAN 訊息 ID */ void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_CAN1_Init(void); static void MX_USART1_UART_Init(void); void UART_Print(const char *format, ...); CAN_HandleTypeDef hcan1; UART_HandleTypeDef huart1; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN1_Init(); MX_USART1_UART_Init(); UART_Print("\r\n=== CAN Bus Sender ===\r\n"); CAN_TxHeaderTypeDef TxHeader; uint8_t TxData[8]; uint32_t TxMailbox; /* 配置發送訊息頭 */ TxHeader.StdId = CAN_ID_TEST; TxHeader.IDE = CAN_ID_STD; /* 標準 ID */ TxHeader.RTR = CAN_RTR_DATA; /* 資料訊息(非遠程) */ TxHeader.DLC = 8; /* 資料長度 8 bytes */ uint32_t counter = 0; while (1) { /* 準備發送資料 */ TxData[0] = (counter >> 24) & 0xFF; TxData[1] = (counter >> 16) & 0xFF; TxData[2] = (counter >> 8) & 0xFF; TxData[3] = counter & 0xFF; TxData[4] = 0xAA; TxData[5] = 0xBB; TxData[6] = 0xCC; TxData[7] = 0xDD; /* 發送訊息 */ if (HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox) == HAL_OK) { UART_Print("CAN Sent: ID=0x%03X, Data=[%02X %02X %02X %02X %02X %02X %02X %02X]\r\n", CAN_ID_TEST, TxData[0], TxData[1], TxData[2], TxData[3], TxData[4], TxData[5], TxData[6], TxData[7]); counter++; } else { UART_Print("CAN Send Failed!\r\n"); } HAL_Delay(1000); } } void UART_Print(const char *format, ...) { char buffer[100]; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY); } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 336; RCC_OscInitStruct.PLL.PLLP = 2; RCC_OscInitStruct.PLL.PLLQ = 7; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) Error_Handler(); RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) Error_Handler(); } static void MX_GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); } static void MX_CAN1_Init(void) { hcan1.Instance = CAN1; hcan1.Init.Prescaler = 6; hcan1.Init.Mode = CAN_MODE_NORMAL; hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; hcan1.Init.TimeSeg1 = CAN_BS1_11TQ; hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; hcan1.Init.TimeTriggeredMode = DISABLE; hcan1.Init.AutoBusOff = DISABLE; hcan1.Init.AutoWakeUp = DISABLE; hcan1.Init.AutoRetransmission = ENABLE; hcan1.Init.ReceiveFifoLocked = DISABLE; hcan1.Init.TransmitFifoPriority = DISABLE; if (HAL_CAN_Init(&hcan1) != HAL_OK) Error_Handler(); HAL_CAN_Start(&hcan1); } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; if (HAL_UART_Init(&huart1) != HAL_OK) Error_Handler(); } void Error_Handler(void) { while(1); } 接收方 (Nucleo 2):main.c /* STM32 Lesson 10 - CAN Bus (Receiver) * 功能:接收另一片 Nucleo 的 CAN 訊息 * 難度:中級 */ #include "main.h" #include "can.h" #include "usart.h" #include #include #define CAN_ID_TEST 0x123 void SystemClock_Config(void); static void MX_GPIO_Init(void); static void MX_CAN1_Init(void); static void MX_USART1_UART_Init(void); void UART_Print(const char *format, ...); CAN_HandleTypeDef hcan1; UART_HandleTypeDef huart1; int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_CAN1_Init(); MX_USART1_UART_Init(); UART_Print("\r\n=== CAN Bus Receiver ===\r\n"); CAN_FilterTypeDef sFilterConfig; CAN_RxHeaderTypeDef RxHeader; uint8_t RxData[8]; /* 配置接收過濾器 */ sFilterConfig.FilterBank = 0; sFilterConfig.FilterMode = CAN_FILTERMODE_IDMASK; sFilterConfig.FilterScale = CAN_FILTERSCALE_32BIT; sFilterConfig.FilterIdHigh = (CAN_ID_TEST << 5) >> 16; sFilterConfig.FilterIdLow = (CAN_ID_TEST << 5) & 0xFFFF; sFilterConfig.FilterMaskIdHigh = 0xFFFF; sFilterConfig.FilterMaskIdLow = 0xFFFF; sFilterConfig.FilterFIFOAssignment = CAN_RX_FIFO0; sFilterConfig.FilterActivation = ENABLE; if (HAL_CAN_ConfigFilter(&hcan1, &sFilterConfig) != HAL_OK) Error_Handler(); if (HAL_CAN_Start(&hcan1) != HAL_OK) Error_Handler(); /* 啟用接收中斷 */ if (HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING) != HAL_OK) Error_Handler(); UART_Print("Waiting for CAN messages...\r\n"); while (1) { if (HAL_CAN_GetRxFifoFillLevel(&hcan1, CAN_RX_FIFO0) > 0) { if (HAL_CAN_GetRxMessage(&hcan1, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK) { UART_Print("CAN Received: ID=0x%03X, DLC=%d, Data=[%02X %02X %02X %02X %02X %02X %02X %02X]\r\n", RxHeader.StdId, RxHeader.DLC, RxData[0], RxData[1], RxData[2], RxData[3], RxData[4], RxData[5], RxData[6], RxData[7]); } } HAL_Delay(100); } } void UART_Print(const char *format, ...) { char buffer[100]; va_list args; va_start(args, format); vsnprintf(buffer, sizeof(buffer), format, args); va_end(args); HAL_UART_Transmit(&huart1, (uint8_t *)buffer, strlen(buffer), HAL_MAX_DELAY); } void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; __HAL_RCC_PWR_CLK_ENABLE(); __HAL_PWR_VOLTAGESCALING_CONFIG(PWR_REGULATOR_VOLTAGE_SCALE1); RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLM = 8; RCC_OscInitStruct.PLL.PLLN = 336; RCC_OscInitStruct.PLL.PLLP = 2; RCC_OscInitStruct.PLL.PLLQ = 7; if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK) Error_Handler(); RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2; if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5) != HAL_OK) Error_Handler(); } static void MX_GPIO_Init(void) { __HAL_RCC_GPIOA_CLK_ENABLE(); } static void MX_CAN1_Init(void) { hcan1.Instance = CAN1; hcan1.Init.Prescaler = 6; hcan1.Init.Mode = CAN_MODE_NORMAL; hcan1.Init.SyncJumpWidth = CAN_SJW_1TQ; hcan1.Init.TimeSeg1 = CAN_BS1_11TQ; hcan1.Init.TimeSeg2 = CAN_BS2_2TQ; hcan1.Init.TimeTriggeredMode = DISABLE; hcan1.Init.AutoBusOff = DISABLE; hcan1.Init.AutoWakeUp = DISABLE; hcan1.Init.AutoRetransmission = ENABLE; hcan1.Init.ReceiveFifoLocked = DISABLE; hcan1.Init.TransmitFifoPriority = DISABLE; if (HAL_CAN_Init(&hcan1) != HAL_OK) Error_Handler(); } static void MX_USART1_UART_Init(void) { huart1.Instance = USART1; huart1.Init.BaudRate = 115200; huart1.Init.WordLength = UART_WORDLENGTH_8B; huart1.Init.StopBits = UART_STOPBITS_1; huart1.Init.Parity = UART_PARITY_NONE; huart1.Init.Mode = UART_MODE_TX_RX; if (HAL_UART_Init(&huart1) != HAL_OK) Error_Handler(); } void Error_Handler(void) { while(1); } 🔍 測試與除錯 預期結果 ✅ 發送方 UART 輸出 : === CAN Bus Sender === CAN Sent: ID=0x123, Data=[00 00 00 00 AA BB CC DD] CAN Sent: ID=0x123, Data=[00 00 00 01 AA BB CC DD] CAN Sent: ID=0x123, Data=[00 00 00 02 AA BB CC DD] ✅ 接收方 UART 輸出 : === CAN Bus Receiver === Waiting for CAN messages... CAN Received: ID=0x123, DLC=8, Data=[00 00 00 00 AA BB CC DD] CAN Received: ID=0x123, DLC=8, Data=[00 00 00 01 AA BB CC DD] CAN Received: ID=0x123, DLC=8, Data=[00 00 00 02 AA BB CC DD] 常見問題 問題 原因 解決 無法接收 波特率配置錯誤 重新計算 Prescaler/TimeSeg 訊息丟失 CAN 總線終端電阻缺少 確保兩端各有 120Ω 電阻 發送失敗 郵箱滿 檢查 HAL_CAN_GetTxMailboxesFreeLevel() 🔗 延伸學習 下節預覽 - 第 11 節:RS-485 通訊 第 11 節將實現: RS-485 差分信號 多設備半雙工通訊 收發模式自動切換 ✨ CAN 雙板通訊完成!🚀