STM32 四輪驅動與 VESC 控制系統 (4WD_RWD 動態切換)

📌 專案架構與規格設定 
 本專案使用 STM32 作為核心控制器，透過 CAN Bus 與 4 顆 VESC 進行通訊，實現四輪獨立驅動與動態扭矩分配。 
 
 電池系統 ：48V 標稱電壓，總輸出功率限制 1000W。 
 前輪配置 ：250W 減速電機 (內建單向離合器，具備滑行優勢)。 
 後輪配置 ：500W 減速電機。 
 輪胎規格 ：20 吋車輪，減速比 1:4.4，馬達額定轉速 390 RPM。 
 控制策略 ：低速 4WD 起步 (1:2 扭矩分配) $\rightarrow$ 高速切換純 RWD 巡航 (前輪斷電滑行，後輪滿功率輸出)。 
 
 
 🛠️ 程式碼核心優化項目 (Bug Fixes & Improvements) 
 
 修復 CAN 發送死迴圈 ：在 VESC_Send_Current 增加 Timeout 機制，防止 VESC 沒開機或斷線時導致 MCU 系統卡死。 
 修復記憶體溢位 (Buffer Overflow) ：在 ENCODE_Step_up 中將 TxData 陣列加大至 8 bytes，並修正 DLC (Data Length Code) 設定。 
 優化 ADC 讀取 ：將 HAL_ADC_Start() 移出 while(1) 迴圈，利用硬體連續轉換模式 (Continuous Mode) 提高穩定性。 
 增加 CAN ID 濾波保護 ：在接收中斷 HAL_CAN_RxFifo0MsgPendingCallback 內加入 StdId 檢查，防止雜訊誤觸發編碼器數值更新。 
 
 
 💻 完整程式碼 ( main.c ) 
 /* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file : main.c
 * @brief : STM32 VESC 4WD/RWD Dynamic Control System
 ******************************************************************************
 */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */

/* USER CODE END Includes */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define POWER_LIMIT_W 1000 // 電池總功率限制 1000W
#define BATTERY_VOLTAGE_NOM 48 // 標稱電壓 48V

// 最大總電流 (mA)
#define MAX_TOTAL_CURRENT_MA ((POWER_LIMIT_W * 1000) / BATTERY_VOLTAGE_NOM)

// 速度切換閾值 (ERPM: Electrical RPM)
// 依據 20吋輪、4.4減速比、390rpm 重新估算切換點
#define ERPM_THRESHOLD_RWD 10000
#define ERPM_HYSTERESIS 500 // 遲滯區間，避免臨界點頻繁切換

// VESC CAN ID 定義 (需與 VESC Tool 設定一致)
#define VESC_ID_FRONT_L 0xB1
#define VESC_ID_FRONT_R 0xB2
#define VESC_ID_REAR_L 0xA3
#define VESC_ID_REAR_R 0xA4

// VESC CAN Packet ID
#define CAN_PACKET_SET_CURRENT 1
#define ENCODER_RESOLUTION 4096.0f
#define STEERING_CENTER_OFFSET 2048.0f
/* USER CODE END PD */

/* Private variables ---------------------------------------------------------*/
ADC_HandleTypeDef hadc1;
ADC_HandleTypeDef hadc2;
CAN_HandleTypeDef hcan1;

/* USER CODE BEGIN PV */
int32_t current_erpm = 0; // 平均轉速 (由 CAN RX 更新)
uint16_t throttle_adc = 0; // 0 - 4095
int32_t target_total_ma = 0; // 總目標電流 (mA)
uint16_t speed_adc = 0; // 模擬速度的 ADC

float current_angle = 0.0f; // 0 ~ 360 度的絕對角度
float steering_angle = 0.0f; // -180 ~ +180 度的方向盤實際轉角
uint32_t raw_encoder_value = 0; // 0 ~ 4095
/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_ADC1_Init(void);
static void MX_CAN1_Init(void);
static void MX_ADC2_Init(void);

/* USER CODE BEGIN PFP */
// 狀態機定義：四驅與後驅模式
typedef enum {
 MODE_4WD,
 MODE_RWD
} DriveMode_t;

DriveMode_t drive_mode = MODE_4WD;

void VESC_Send_Current(uint8_t controller_id, int32_t current_ma);
void Control_Loop_1kHz(void);
void ENCODE_Step_up(void);
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan);
/* USER CODE END PFP */

/**
 * @brief The application entry point.
 */
int main(void)
{
 /* MCU Configuration--------------------------------------------------------*/
 HAL_Init();
 SystemClock_Config();

 /* Initialize all configured peripherals */
 MX_GPIO_Init();
 MX_ADC1_Init();
 MX_CAN1_Init();
 MX_ADC2_Init();

 /* USER CODE BEGIN 2 */
 // 啟動 CAN 總線與中斷
 HAL_CAN_Start(&hcan1);
 HAL_CAN_ActivateNotification(&hcan1, CAN_IT_RX_FIFO0_MSG_PENDING);

 // 【優化】將 ADC 啟動移出 while(1) 迴圈，使用連續轉換模式讓硬體自動更新
 HAL_ADC_Start(&hadc1);
 HAL_ADC_Start(&hadc2);
 /* USER CODE END 2 */

 /* Infinite loop */
 while (1)
 {
 Control_Loop_1kHz();
 }
}

/* USER CODE BEGIN 4 */

/**
 * @brief 主控制迴圈 (使用輪詢方式發送 CAN 避免 Bus 壅塞)
 */
void Control_Loop_1kHz(void)
{
 static uint32_t last_control_tick = 0;
 uint32_t current_tick = HAL_GetTick();
 
 // 控制執行頻率 (大約 500Hz)
 if ((current_tick - last_control_tick) < 2) {
 return;
 }
 last_control_tick = current_tick;

 // 1. 讀取油門與速度模擬 ADC (0 ~ 4095)
 throttle_adc = HAL_ADC_GetValue(&hadc1);
 speed_adc = HAL_ADC_GetValue(&hadc2);
 current_erpm = ((int32_t)speed_adc * 20000) >> 12;

 // 2. 計算總需求電流 (mA)
 target_total_ma = ((int32_t)throttle_adc * MAX_TOTAL_CURRENT_MA) >> 12;

 // 3. 判斷模式 (根據 ERPM 絕對值)
 int32_t abs_erpm = (current_erpm < 0) ? -current_erpm : current_erpm;

 if (drive_mode == MODE_4WD) {
 if (abs_erpm > (ERPM_THRESHOLD_RWD + ERPM_HYSTERESIS)) {
 drive_mode = MODE_RWD;
 }
 } else {
 if (abs_erpm < (ERPM_THRESHOLD_RWD - ERPM_HYSTERESIS)) {
 drive_mode = MODE_4WD;
 }
 }

 // 4. 動態電流分配 (Distribute Current)
 int32_t current_front_axle = 0;
 int32_t current_rear_axle = 0;

 if (drive_mode == MODE_4WD) {
 // --- 4WD 模式 (起步高扭力) ---
 // 前 250W + 後 500W，比例 1:2
 current_front_axle = target_total_ma / 3;
 current_rear_axle = target_total_ma - current_front_axle; 
 } else {
 // --- RWD 模式 (高速巡航) ---
 // 前輪斷電 (利用離合器滑行)，後輪全功率輸出
 current_front_axle = 0;
 current_rear_axle = target_total_ma;
 }

 // 5. 計算單顆電機電流並輪詢發送
 int32_t cmd_front = current_front_axle / 2;
 int32_t cmd_rear = current_rear_axle / 2;

 static uint8_t motor_step = 0;
 switch(motor_step)
 {
 case 0:
 VESC_Send_Current(VESC_ID_FRONT_L, cmd_front);
 motor_step = 1;
 break;
 case 1:
 VESC_Send_Current(VESC_ID_FRONT_R, cmd_front);
 motor_step = 2;
 break;
 case 2:
 VESC_Send_Current(VESC_ID_REAR_L, cmd_rear);
 motor_step = 3;
 break;
 case 3:
 VESC_Send_Current(VESC_ID_REAR_R, cmd_rear);
 motor_step = 0;
 break;
 }
}

/**
 * @brief 發送電流命令給 VESC
 */
void VESC_Send_Current(uint8_t controller_id, int32_t current_ma)
{
 CAN_TxHeaderTypeDef TxHeader;
 uint32_t TxMailbox;
 uint8_t TxData[4];

 TxHeader.ExtId = (CAN_PACKET_SET_CURRENT << 8) | controller_id;
 TxHeader.IDE = CAN_ID_EXT;
 TxHeader.RTR = CAN_RTR_DATA;
 TxHeader.DLC = 4;

 // Big Endian packing
 TxData[0] = (uint8_t)(current_ma >> 24);
 TxData[1] = (uint8_t)(current_ma >> 16);
 TxData[2] = (uint8_t)(current_ma >> 8);
 TxData[3] = (uint8_t)(current_ma);

 // 【安全優化】加入 Timeout 防護，避免 CAN Bus 異常時系統卡死
 uint32_t timeout = 0;
 while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0)
 {
 timeout++;
 if (timeout > 50000) {
 return; // 丟棄此包，保護主程式
 }
 }
 HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);
}

/**
 * @brief 發送編碼器設定命令
 */
void ENCODE_Step_up(void)
{
 CAN_TxHeaderTypeDef TxHeader;
 uint32_t TxMailbox;
 uint8_t TxData[8]; // 【修正】必須開到 8 bytes 以防溢位

 TxHeader.StdId = 0x01;
 TxHeader.IDE = CAN_ID_STD;
 TxHeader.RTR = CAN_RTR_DATA;
 TxHeader.DLC = 4;

 TxData[0] = 0x04;
 TxData[1] = 0x01;
 TxData[2] = 0x04;
 TxData[3] = 0xAA;
 HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);

 // 第二包命令
 TxHeader.DLC = 5; // 【修正】長度改為 5
 TxData[0] = 0x05;
 TxData[1] = 0x01;
 TxData[2] = 0x05;
 TxData[3] = 0x88;
 TxData[4] = 0x13; // 寫入第 5 個 byte 不再越界
 HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);
}

/**
 * @brief CAN 接收中斷回調函式 (處理編碼器回傳角度)
 */
void HAL_CAN_RxFifo0MsgPendingCallback(CAN_HandleTypeDef *hcan)
{
 CAN_RxHeaderTypeDef RxHeader;
 uint8_t RxData[8];

 if (hcan->Instance == CAN1)
 {
 if (HAL_CAN_GetRxMessage(hcan, CAN_RX_FIFO0, &RxHeader, RxData) == HAL_OK)
 {
 // 【安全優化】確認封包來源 ID，防止 VESC 雜訊誤更動數據
 if (RxHeader.IDE == CAN_ID_STD && RxHeader.StdId == 0x01)
 {
 if (RxData[0] == 0x07 && RxData[1] == 0x01 && RxData[2] == 0x01)
 {
 // 小端序解碼
 raw_encoder_value = (uint32_t)((RxData[6] << 24) |
 (RxData[5] << 16) |
 (RxData[4] << 8) |
 RxData[3]);
 }
 }
 }
 }
}
/* USER CODE END 4 */