STM32 初次發車測試程式 (Basic Throttle Test)

📌 測試目的 
 驗證底層硬體與通訊是否正常工作： 
 
 ADC 油門訊號讀取是否平順。 
 CAN Bus 傳送是否正常，沒有卡死。 
 4 顆 VESC 是否都能正確接收指令並轉動。 
 確認 4 顆馬達的正反轉方向是否一致 （若方向反了，請直接在 VESC Tool 裡將該馬達反轉，不要在程式裡改，保持程式單純）。 
 
 
 🚗 測試步驟建議 
 
 將車輛四輪確實架空。 
 燒錄這份程式碼。 
 輕踩油門，觀察四個輪子是否「 同時轉動 」且「 方向一致往前 」。 
 放開油門，觀察輪子是否能正常停止。 
 如果發現有輪子往後轉，請將筆電接上該輪的 VESC，打開 VESC Tool，將 Motor Configuration -> General -> Invert Motor Direction 打勾（設為 True），寫入後再試一次。 
 
 
 💻 基礎測試程式碼 ( main.c ) 
 /* USER CODE BEGIN Header */
/**
 ******************************************************************************
 * @file : main.c
 * @brief : 最基礎的油門直通測試 (四輪同等輸出)
 ******************************************************************************
 */
/* USER CODE END Header */

#include "main.h"

/* USER CODE BEGIN PD */
// --- 測試用安全參數 ---
// 單顆馬達最大電流限制 (測試階段建議設小一點，例如 5A = 5000mA)
#define TEST_MAX_CURRENT_MA 5000 

// 油門死區 (0~4095 之間，小於此值馬達不輸出，防止雜訊抖動)
#define THROTTLE_DEADZONE 150 

// VESC CAN ID 定義
#define VESC_ID_FRONT_L 0xB1
#define VESC_ID_FRONT_R 0xB2
#define VESC_ID_REAR_L 0xA3
#define VESC_ID_REAR_R 0xA4

#define CAN_PACKET_SET_CURRENT 1
/* USER CODE END PD */

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

/* USER CODE BEGIN PV */
uint16_t throttle_adc = 0; 
int32_t target_current = 0; 
/* 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);

/* USER CODE BEGIN PFP */
void VESC_Send_Current(uint8_t controller_id, int32_t current_ma);
void Basic_Drive_Loop(void);
/* USER CODE END PFP */

int main(void)
{
 HAL_Init();
 SystemClock_Config();

 MX_GPIO_Init();
 MX_ADC1_Init();
 MX_CAN1_Init();

 /* USER CODE BEGIN 2 */
 // 啟動 CAN 總線
 HAL_CAN_Start(&hcan1);

 // 啟動 ADC 連續轉換
 HAL_ADC_Start(&hadc1);
 /* USER CODE END 2 */

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

/* USER CODE BEGIN 4 */

/**
 * @brief 最基礎的直通驅動迴圈 (100Hz)
 */
void Basic_Drive_Loop(void)
{
 static uint32_t last_tick = 0;
 uint32_t current_tick = HAL_GetTick();
 
 // 限制執行頻率為 100Hz (每 10ms 執行一次即可，測試階段不需太快)
 if ((current_tick - last_tick) < 10) {
 return;
 }
 last_tick = current_tick;

 // 1. 讀取油門 ADC
 throttle_adc = HAL_ADC_GetValue(&hadc1);

 // 2. 加入死區與計算目標電流
 if (throttle_adc < THROTTLE_DEADZONE) {
 target_current = 0;
 } else {
 // 將 ADC 值 (扣除死區後) 映射到目標電流
 // 公式: (當前ADC - 死區) * 最大電流 / (4095 - 死區)
 uint32_t active_adc = throttle_adc - THROTTLE_DEADZONE;
 target_current = (active_adc * TEST_MAX_CURRENT_MA) / (4095 - THROTTLE_DEADZONE);
 }

 // 3. 將相同的電流指令，依序發送給 4 顆馬達
 // 這裡加上微小的延遲(1ms)避免 CAN Bus 瞬間塞滿 4 個封包
 VESC_Send_Current(VESC_ID_FRONT_L, target_current);
 HAL_Delay(1);
 
 VESC_Send_Current(VESC_ID_FRONT_R, target_current);
 HAL_Delay(1);
 
 VESC_Send_Current(VESC_ID_REAR_L, target_current);
 HAL_Delay(1);
 
 VESC_Send_Current(VESC_ID_REAR_R, target_current);
}

/**
 * @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;

 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);

 uint32_t timeout = 0;
 // 等待 Mailbox 有空位
 while (HAL_CAN_GetTxMailboxesFreeLevel(&hcan1) == 0)
 {
 timeout++;
 if (timeout > 50000) return; // 超時放棄，防止當機
 }
 HAL_CAN_AddTxMessage(&hcan1, &TxHeader, TxData, &TxMailbox);
}

/* USER CODE END 4 */