基于 STM32 的标准遥控器架构与源码

基于 STM32 + 霍尔摇杆 + TFT屏幕 + 2.4GHz射频 的标准遥控器工程架构与核心源码解析。


一、 典型硬件架构与工程结构

1. 硬件选型建议
  • 主控: STM32F103C8T6(性价比)或 STM32F405(高性能,支持USB HID)。
  • 摇杆: 霍尔传感器(非电位器,寿命长,精度高)+ ADS7953(SPI ADC采集)。
  • 射频: 内置 ELRS (ExpressLRS) 模块(当前穿越机/竞速圈最火,超低延迟)或 SI24R1(兼容NRF24L01)。
  • 屏幕: 1.3寸 IPS (240x240),SPI接口。
2. 软件工程目录 (STM32CubeIDE)
text 复制代码
RadioController/
├── Core/
│   ├── Src/
│   │   ├── main.c              # 主循环:状态机切换
│   │   ├── adc_task.c          # 摇杆/电位器采样(DMA方式)
│   │   ├── sbus_output.c       # SBUS协议打包与串口发送
│   │   ├── elrs_bind.c         # ELRS对频逻辑
│   │   └── lcd_menu.c          # 菜单UI(参数设置、电压显示)
├── Drivers/
├── Modules/
│   ├── Protocol/              # 通信协议层
│   │   ├── sbus.c/h           # FrSky SBUS (18通道,负逻辑)
│   │   └── crsf.c/h           # TBS Crossfire / ELRS 协议
│   └── Mixer/                 # 混控器(可选)
└── User/

二、 核心源码解析

1. 摇杆数据采集 (ADC + DMA)

遥控器最忌讳卡顿,必须使用 DMA + 定时器触发 进行无阻塞采样。

c 复制代码
// adc_task.c
#include "adc.h"

#define CHANNEL_NUM 6 // 4个摇杆 + 2个滑块
uint16_t adc_raw[CHANNEL_NUM];

void ADC_Init_DMA(void)
{
    // CubeMX中配置:ADC连续转换,DMA循环模式
    HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_raw, CHANNEL_NUM);
}

// 获取经过校准的摇杆值 (-1000 ~ +1000)
int16_t Get_StickValue(uint8_t ch)
{
    uint16_t raw = adc_raw[ch];
    // 去死区 & 映射 (假设中位是2048)
    int16_t val = raw - 2048; 
    if(abs(val) < 20) val = 0; // 死区
    return val * 1000 / 2048;  // 缩放到 -1000~1000
}
2. SBUS 协议输出 (最通用的飞控协议)

SBUS 使用 100kbps 反相串口,每帧 25字节,间隔 7ms。

c 复制代码
// sbus_output.c
#include "usart.h"

static uint8_t sbus_frame[25];

void SBUS_BuildFrame(int16_t ch[16])
{
    uint8_t *p = sbus_frame;
    *p++ = 0x0F; // Header

    // 将16个通道的11位数据打包进22个字节
    uint32_t bit_index = 0;
    for(int i=0; i<16; i++){
        uint16_t val = (ch[i] + 1000) * 2048 / 2000; // 映射到 0-2048
        for(int b=0; b<11; b++){
            if(val & (1<<b)){
                sbus_frame[1 + bit_index/8] |= (1 << (bit_index%8));
            }
            bit_index++;
        }
    }
    *p++ = 0x00; // Flags (数字开关)
    *p = 0x00;   // End byte
}

// 定时器回调中发送 (100Hz)
void TIM_SBUS_Handler(void)
{
    HAL_UART_Transmit(&huart2, sbus_frame, 25, 10);
}
3. ExpressLRS (ELRS) 协议集成

ELRS 是目前最先进的遥控协议,通常使用现成的 TX Module ,遥控器主控通过串口发送 CRSF 协议 与之通信。

c 复制代码
// crsf_protocol.c
typedef struct {
    uint8_t device_addr;
    uint8_t frame_size;
    uint8_t type;
    uint8_t payload[8]; // 包含通道数据
} CRSF_Frame;

void CRSF_SendChannels(int16_t ch1, int16_t ch2, int16_t ch3, int16_t ch4)
{
    uint8_t buf[26];
    buf[0] = 0xC8; // Sync Byte
    buf[1] = 24;  // Frame Length
    buf[2] = 0x16; // Type: RC Channels Packed

    // CRSF 使用混合位域打包 16 个通道 (10bit/11bit)
    // 此处简化示意
    uint32_t ch_packed = 0;
    ch_packed |= ((ch1 + 1024) & 0x7FF); // Ch1 11bit
    // ... 继续打包其他通道

    HAL_UART_Transmit(&huart3, buf, sizeof(buf), 10);
}
4. 失控保护 (Failsafe) 逻辑

这是遥控器最重要的安全代码。当信号丢失或电压过低时,自动切回安全状态。

c 复制代码
// failsafe.c
void Failsafe_Check(void)
{
    static uint32_t last_packet_time = 0;
    
    if(HAL_GetTick() - last_packet_time > 500) // 500ms无数据
    {
        // 执行失控保护
        Set_AllChannels(1000); // 所有通道拉到最低(或预设的安全位置)
        LED_Warning_On();
    }
}

参考代码 飞控遥控器原理图、PCB、源程序 www.youwenfan.com/contentcsv/103422.html

三、 开发避坑指南

  1. 串口反相问题: SBUS 协议是反相逻辑 (Inverted Logic)。STM32 的普通 USART 不能直接接接收机,需要:
    • 硬件方案:加一个 MAX3232反相器电路
    • 软件方案:使用 STM32F3/F4 的高级定时器(TIM)的串口功能,开启硬件反相。
  2. 电源抖动: 摇杆采样必须做 均值滤波中值滤波,否则飞机会"抽搐"。
  3. 射频干扰: 遥控器主板必须做好屏蔽,2.4G天线远离电机和电池线,否则会出现远距离断连。
  4. 低电量报警: 必须在代码中实现 多级报警(3.8V闪烁提示,3.6V强制关机),防止锂电池过放鼓包。
相关推荐
ZhengEnCi20 小时前
Q01-高并发点赞系统架构设计
架构
笨鸟飞不快1 天前
从 MVC 到 DDD:一次真实的渐进式迁移实录
后端·架构
这个DBA有点耶2 天前
GROUP BY优化全解:如何写出既不丢数据又飞快的分组查询
数据库·mysql·架构
锋行天下2 天前
我试图优化 Vite 的拆包,结果首屏慢了 10 倍
前端·vue.js·架构
小鼻子的猫2 天前
独立开发 30 天:2.5 万行代码,23 个 Bug,5 次重构——一个 AI 社区的诞生
架构
咖啡八杯2 天前
GoF设计模式——命令模式
java·设计模式·架构
candyTong2 天前
阿里开源 AI Code Review 工具:ocr review 的执行链路解析
javascript·后端·架构
doiito3 天前
【Agent Harness】TPS的“自工程完结”教会了我一件事:别把Bug留给下一道工序
架构·rust
烬羽3 天前
中英文 token 数量差一倍?两段 JS 代码搞懂 LLM 底层是怎么"读"文字的
javascript·程序员·架构