怎麼用方向盤開遙控車?
用 ESP32-S3 讀取 USB 方向盤,再透過 ELRS 高頻頭送出 CRSF 訊號,就能用方向盤開遙控車。下面分三步說明。
STEP 1
ESP32 硬體連接
STEP 2
ESP32-S3 燒入程式碼
#include <Arduino.h>
#include "driver/gptimer.h"
// ==========================================
// 【硬體腳位定義:ESP32-S3 雙線雙震動完整版】
// ==========================================
const int PEDAL_PIN = 1; // 油門/煞車踏板輸入 (GPIO 1)
const int STEERING_PIN = 2; // 方向盤轉向輸入 (GPIO 2)
#define PPM_PIN 18 // PPM 訊號輸出腳 (GPIO 18)
// 左震動馬達 (A側)
const byte LEFT_PWM = 13; // ENA -> 控制左馬達強弱
const byte LEFT_IN = 16; // IN2 -> 數位輸出 (建立電位差)
// 右震動馬達 (B側)
const byte RIGHT_PWM = 4; // ENB -> 控制右馬達強弱
const byte RIGHT_IN = 5; // IN4 -> 數位輸出 (建立電位差)
// ==========================================
// 【操控參數與死區校正】
// ==========================================
const int PEDAL_MIN = 0;
const int PEDAL_CENTER = 1870; // 油門實測中立點
const int PEDAL_MAX = 4095;
const int PEDAL_DEADZONE = 40;
const int STEERING_MIN = 0;
const int STEERING_MAX = 4095;
const int STEERING_DEADZONE_LOW = 1930; // 方向盤中立點下限
const int STEERING_DEADZONE_HIGH = 1970; // 方向盤中立點上限
// ==========================================
// 【PPM 參數與極性設定】
// ==========================================
#define PPM_POLARITY_NEGATIVE
#ifdef PPM_POLARITY_NEGATIVE
#define SIGNAL_ON LOW
#define SIGNAL_OFF HIGH
#else
#define SIGNAL_ON HIGH
#define SIGNAL_OFF LOW
#endif
#define NUM_CHANNELS 8
#define PPM_FRAME_LEN 22500
#define PPM_PULSE_LEN 300
#define PPM_MIN 1000
#define PPM_MID 1500
#define PPM_MAX 2000
volatile uint16_t ppm_channels[NUM_CHANNELS];
gptimer_handle_t ppm_timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
// ==========================================
// 【gptimer 硬體中斷函數】
// ==========================================
static bool IRAM_ATTR on_timer_alarm(gptimer_handle_t timer, const gptimer_alarm_event_data_t *edata, void *user_ctx) {
static bool is_pulse = true;
static uint8_t current_channel = 0;
static uint32_t total_frame_time = 0;
portENTER_CRITICAL_ISR(&timerMux);
uint32_t next_alarm_val = 0;
if (is_pulse) {
digitalWrite(PPM_PIN, SIGNAL_ON);
next_alarm_val = PPM_PULSE_LEN;
total_frame_time += PPM_PULSE_LEN;
is_pulse = false;
} else {
digitalWrite(PPM_PIN, SIGNAL_OFF);
if (current_channel < NUM_CHANNELS) {
uint32_t data_len = ppm_channels[current_channel] - PPM_PULSE_LEN;
next_alarm_val = data_len;
total_frame_time += data_len;
current_channel++;
is_pulse = true;
} else {
uint32_t sync_len = PPM_FRAME_LEN - total_frame_time;
if (sync_len < 4000) {
sync_len = 4000;
}
next_alarm_val = sync_len;
current_channel = 0;
total_frame_time = 0;
is_pulse = true;
}
}
gptimer_alarm_config_t alarm_config = {
.alarm_count = next_alarm_val,
.reload_count = 0,
.flags = { .auto_reload_on_alarm = true }
};
gptimer_set_alarm_action(timer, &alarm_config);
portEXIT_CRITICAL_ISR(&timerMux);
return true;
}
// ==========================================
// 【初始化設定】
// ==========================================
void setup() {
Serial.begin(115200);
analogReadResolution(12);
analogSetAttenuation(ADC_11db);
pinMode(PPM_PIN, OUTPUT);
digitalWrite(PPM_PIN, SIGNAL_OFF);
// 初始化震動馬達腳位
pinMode(LEFT_PWM, OUTPUT);
pinMode(LEFT_IN, OUTPUT);
pinMode(RIGHT_PWM, OUTPUT);
pinMode(RIGHT_IN, OUTPUT);
digitalWrite(LEFT_IN, LOW);
digitalWrite(RIGHT_IN, LOW);
for (int i = 0; i < NUM_CHANNELS; i++) {
ppm_channels[i] = PPM_MID;
}
gptimer_config_t timer_config = {
.clk_src = GPTIMER_CLK_SRC_DEFAULT,
.direction = GPTIMER_COUNT_UP,
.resolution_hz = 1000000,
.intr_priority = 0,
.flags = { .intr_shared = false }
};
gptimer_new_timer(&timer_config, &ppm_timer);
gptimer_event_callbacks_t cbs = { .on_alarm = on_timer_alarm };
gptimer_register_event_callbacks(ppm_timer, &cbs, NULL);
gptimer_alarm_config_t alarm_config = {
.alarm_count = PPM_PULSE_LEN,
.reload_count = 0,
.flags = { .auto_reload_on_alarm = true }
};
gptimer_set_alarm_action(ppm_timer, &alarm_config);
gptimer_enable(ppm_timer);
gptimer_start(ppm_timer);
Serial.println("==================================================");
Serial.println(" ESP32-S3 觸覺回饋完美對稱版:立體震動系統啟動! ");
Serial.println("==================================================");
}
// ==========================================
// 【主程式循環】
// ==========================================
void loop() {
int raw_pedal = analogRead(PEDAL_PIN);
int raw_steering = analogRead(STEERING_PIN);
// 油門/煞車踏板映射
int pedal_ppm = PPM_MID;
if (raw_pedal > (PEDAL_CENTER + PEDAL_DEADZONE)) {
ppm_channels[1] = map(raw_pedal, PEDAL_CENTER, PEDAL_MAX, PPM_MID, PPM_MAX);
pedal_ppm = ppm_channels[1];
} else if (raw_pedal < (PEDAL_CENTER - PEDAL_DEADZONE)) {
ppm_channels[1] = map(raw_pedal, PEDAL_MIN, PEDAL_CENTER, PPM_MIN, PPM_MID);
pedal_ppm = ppm_channels[1];
} else {
ppm_channels[1] = PPM_MID;
pedal_ppm = PPM_MID;
}
// 方向盤轉向映射
int steer_ppm = PPM_MID;
if (raw_steering >= STEERING_DEADZONE_LOW && raw_steering <= STEERING_DEADZONE_HIGH) {
steer_ppm = PPM_MID;
} else if (raw_steering > STEERING_DEADZONE_HIGH) {
steer_ppm = map(raw_steering, STEERING_DEADZONE_HIGH, STEERING_MAX, PPM_MID, PPM_MIN);
} else if (raw_steering < STEERING_DEADZONE_LOW) {
steer_ppm = map(raw_steering, STEERING_DEADZONE_LOW, STEERING_MIN, PPM_MID, PPM_MAX);
}
steer_ppm = constrain(steer_ppm, PPM_MIN, PPM_MAX);
// 計算雙側震動強度
int left_out_pwm = 0;
int right_out_pwm = 0;
if (pedal_ppm >= PPM_MID) {
int base_vib = map(pedal_ppm, PPM_MID, PPM_MAX, 80, 255);
digitalWrite(LEFT_IN, HIGH);
digitalWrite(RIGHT_IN, HIGH);
if (steer_ppm < PPM_MID) {
// 左轉增強
int bias = map(steer_ppm, PPM_MIN, PPM_MID, 50, 0);
left_out_pwm = constrain(base_vib + bias, 80, 255);
right_out_pwm = constrain(base_vib - bias, 0, 255);
} else if (steer_ppm > PPM_MID) {
// 右轉增強
int bias = map(steer_ppm, PPM_MID, PPM_MAX, 0, 50);
left_out_pwm = constrain(base_vib - bias, 0, 255);
right_out_pwm = constrain(base_vib + bias, 80, 255);
} else {
left_out_pwm = base_vib;
right_out_pwm = base_vib;
}
} else {
left_out_pwm = 0;
right_out_pwm = 0;
digitalWrite(LEFT_IN, LOW);
digitalWrite(RIGHT_IN, LOW);
}
analogWrite(LEFT_PWM, left_out_pwm);
analogWrite(RIGHT_PWM, right_out_pwm);
// 安全寫入中斷共享變數
portENTER_CRITICAL(&timerMux);
ppm_channels[0] = steer_ppm; // CH1 轉向
ppm_channels[1] = pedal_ppm; // CH2 油門
ppm_channels[2] = PPM_MID; // CH3
ppm_channels[3] = PPM_MID; // CH4
portEXIT_CRITICAL(&timerMux);
Serial.printf("[監控] 1.油門(PPM): %4dus | 2.轉向(PPM): %4dus | 3.左震動(PWM): %3d | 4.右震動(PWM): %3d\n",
pedal_ppm, steer_ppm, left_out_pwm, right_out_pwm);
delay(10);
}
準備清單
硬體
- ESP32-S3
- ELRS 高頻頭
- 遙控車
- 電調
- ELRS 轉 PWM 接收器
- USB 方向盤
程式庫
- AlPlC_PCM
開發版驅動
- Arduino ESP32 Board
請問要如何操作以上步驟
先將step1接線圖如上接線,接下來將程式代碼貼上Arduino燒錄器,點擊上傳便可
相關文章
沒有直接連結的,依相同標籤補上。
虛線框是尚未整理成文章的原始筆記,點進去可以看到完整的思路脈絡。
還沒有留言,來當第一個吧。