基于 STM32F103 的双模蓝牙遥控器设计:阈值指令与原始 ADC 混合架构实现,使用深圳大夏龙雀科技蓝牙模块DX-BT36,通信稳定性和可靠性具有充分保障。

摘要:本文介绍一款基于 STM32F103C8T6 的可编程蓝牙遥控器,创新性地采用阈值指令模式原始 ADC 模式双模混合架构,通过 DMA 非阻塞发送、滑动平均滤波、状态机优先级仲裁等技术,解决了传统遥控器指令冲突、精度不足、串口阻塞等痛点。硬件采用 DX-BT36 主从一体蓝牙模块,支持 10 路独立按键 + 4 路摇杆 ADC。


一、项目背景与设计目标

1.1 传统遥控器的痛点

在机器人、无人机、智能小车等场景中,蓝牙遥控器普遍存在以下问题:

痛点 具体表现 影响
指令冲突 双摇杆同时推动时,多路数据并发导致接收端解析混乱 控制失效或误动作
精度不足 摇杆仅输出高中低三档,无法精细控制速度/角度 用户体验差
串口阻塞 软件循环发送占用 CPU,高频率时影响按键响应 实时性下降
模式单一 固定协议无法适配不同场景(如小车需要档位,云台需要连续值) 兼容性差

1.2 设计目标

  • 双模切换:一键切换"阈值档位模式"和"原始 ADC 模式"

  • 零冲突仲裁:多路摇杆通过优先级状态机,任一时刻只发一条有效指令

  • 非阻塞传输:DMA 发送释放 CPU,主循环仅做状态判断

  • 可编程按键:10 路独立按键,支持自定义指令映射


二、系统架构

2.1 硬件框图

2.2 引脚分配

功能 引脚 模式 备注
摇杆1 X PA0 模拟输入 ADC12_IN0
摇杆1 Y PA1 模拟输入 ADC12_IN1
摇杆2 X PA2 模拟输入 ADC12_IN2
摇杆2 Y PA3 模拟输入 ADC12_IN3
按键 K1~K3 PB12~PB14 上拉输入 JTAG 引脚
按键 K4~K6 PB7~PB9 上拉输入 通用 GPIO
按键 K7 PA12 上拉输入 未启用 USB
按键 K8 PA15 上拉输入 禁用JTAG后可用
模式键1 PA11 上拉输入 切换阈值模式
模式键2 PB3 上拉输入 切换原始 ADC 模式
蓝牙 TX PA9 复用推挽 USART1_TX
蓝牙 RX PA10 浮空输入 USART1_RX

关键设计:PA11、PA12在本项目中仅作 GPIO 使用;PA15、PB3、PB4 通过 GPIO_Remap_SWJ_JTAGDisable 释放 JTAG 后作为普通 IO使用。


三、双模架构详解

3.1 模式定义

typedef enum {
    MODE_THRESHOLD = 0,  // 阈值指令模式:摇杆分三档,单字节指令
    MODE_RAW_ADC   = 1   // 原始 ADC 模式:连续值输出,固定帧格式
} RemoteMode;

3.2 阈值指令模式

适用场景:控制小车、继电器等只需档位控制的设备。

工作原理

  • 将 12 位 ADC 值(0~4095)映射为 低/中/高三档

  • 通过优先级状态机,任一时刻只输出一路摇杆的指令

  • 状态变化才触发发送,降低串口负载

#define VOLTAGE_LOW_THRESHOLD   1000    // < 0.8V → 低档位
#define VOLTAGE_HIGH_THRESHOLD  3100    // > 2.5V → 高档位
// 中间区域 → 中位时 不发送指令

优先级仲裁

优先级 通道 引脚 低电平指令 高电平指令
1 右摇杆 Y PA1 0x25 0x26
2 右摇杆 X PA0 0x27 0x28
3 左摇杆 X PA2 0x24 0x23
4 左摇杆 Y PA3 0x22 0x21

仲裁逻辑:高优先级通道非中位时,屏蔽所有低优先级通道。只有当高优先级回到中位,才检查下一级。

// 状态机优先级仲裁
if(adc_states[1] != adc_prev[1]) {           // 优先级1:PA1(右Y)
    // 处理右Y状态变化
} else if(adc_states[0] != adc_prev[0] &&   // 优先级2:PA0(右X)
          adc_states[1] == VOL_MID) {       // 仅当右Y为中位时
    // 处理右X状态变化
} else if(adc_states[2] != adc_prev[2] &&   // 优先级3:PA2(左X)
          adc_states[0] == VOL_MID && 
          adc_states[1] == VOL_MID) {       // 当右X、右Y均为中位
    // 处理左X状态变化
}
// ... 以此类推

3.3 原始 ADC 模式

适用场景:控制云台、机械臂、无人机等需要精细角度/速度控制的设备。

工作原理

  • 直接输出 12 位 ADC 的滤波后原始值(0~4095)

  • 固定频率发送(默认 10Hz),帧格式统一便于解析

  • 四路摇杆同时输出,无优先级屏蔽

帧格式

设计考量

  • # 作为帧头,\r\n 作为帧尾,便于接收端用中断 + 状态机解析

  • 固定 4 位数字 + 固定位置字母,解析时无需分隔符搜索

  • 10Hz 发送频率平衡实时性与串口带宽(每帧约 22 字节,10Hz 仅 220B/s,远低于 9600bps 的 960B/s 有效带宽)


四、关键技术

4.1 DMA 非阻塞发送

传统 USART_SendData() 阻塞等待 TXE 标志,高频率发送时 CPU 空转严重。本设计采用 DMA1 通道 4(USART1_TX) 实现非阻塞发送:

// DMA 配置核心
DMA_InitTypeDef dma;
dma.DMA_PeripheralBaseAddr = (uint32_t)&USART1->DR;  // 外设:USART1数据寄存器
dma.DMA_MemoryBaseAddr     = (uint32_t)usart_dma_buf; // 内存:发送缓冲区
dma.DMA_DIR                = DMA_DIR_PeripheralDST;   // 方向:内存→外设
dma.DMA_Mode               = DMA_Mode_Normal;          // 单次模式
DMA_Init(DMA1_Channel4, &dma);

// 使能 USART 的 DMA 发送请求
USART_DMACmd(USART1, USART_DMAReq_Tx, ENABLE);

发送流程

忙标志保护usart_send_busy 防止帧叠加,若 DMA 正在发送则跳过本次,避免数据混乱。

4.2 滑动平均滤波

摇杆电位器存在机械抖动和电源噪声,直接采样会导致数值跳变。采用 4 点滑动平均滤波

#define ADC_FILTER_DEPTH  4

uint16_t adc_filter_buf[4][ADC_FILTER_DEPTH] = {0};  // 4通道×4点缓存
uint8_t  adc_filter_idx = 0;

// 主循环每轮采样
for(uint8_t ch = 0; ch < 4; ch++) {
    adc_filter_buf[ch][adc_filter_idx] = Get_ADC_Single(ch);
}
adc_filter_idx = (adc_filter_idx + 1) % ADC_FILTER_DEPTH;

// 计算平均值
for(uint8_t ch = 0; ch < 4; ch++) {
    uint32_t sum = 0;
    for(uint8_t i = 0; i < ADC_FILTER_DEPTH; i++) {
        sum += adc_filter_buf[ch][i];
    }
    adc_raw[ch] = sum / ADC_FILTER_DEPTH;
}

实际效果对比

滤波方式 噪声幅度 响应延迟 适用场景
无滤波 ±50 LSB 0ms 不适用
单次采样 ±30 LSB 0ms 低要求
4点滑动平均 ±10 LSB ~4ms 本设计
8点滑动平均 ±5 LSB ~8ms 高稳定要求
卡尔曼滤波 ±3 LSB ~2ms 复杂系统

4.3 精准时钟配置

放弃默认的 SystemInit(),手动配置 RCC 确保 72MHz 精准时钟:

void SysClock_Config(void)
{
    RCC_DeInit();
    
    // 外部 8MHz HSE → PLL ×9 → 72MHz
    RCC_HSEConfig(RCC_HSE_ON);
    while(RCC_GetFlagStatus(RCC_FLAG_HSERDY) == RESET);
    
    RCC_PLLConfig(RCC_PLLSource_HSE_Div1, RCC_PLLMul_9);
    RCC_PLLCmd(ENABLE);
    while(RCC_GetFlagStatus(RCC_FLAG_PLLRDY) == RESET);
    
    // 总线分频:AHB=72MHz, APB1=36MHz, APB2=72MHz
    RCC_HCLKConfig(RCC_SYSCLK_Div1);
    RCC_PCLK1Config(RCC_HCLK_Div2);   // APB1 ≤ 36MHz(TIM2~7, USART2~3, I2C, SPI2)
    RCC_PCLK2Config(RCC_HCLK_Div1);   // APB2 = 72MHz(ADC, USART1, TIM1, SPI1)
    
    RCC_SYSCLKConfig(RCC_SYSCLKSource_PLLCLK);
}

关键:APB1 分频为 2 后,挂载在 APB1 上的 TIM2~7 时钟为 72MHz(因分频系数 ≥2 时 TIM 时钟 ×2),确保 PWM/定时器精度。


五、代码解析

5.1 主循环架构

int main(void)
{
    // 1. 初始化
    SysClock_Config();      // 必须先配时钟
    SysTick_Init();         // 1ms 时基
    NVIC_Config();          // 中断优先级分组
    
    // 2. 外设初始化
    GPIO_Buttons_Init();    // 释放 JTAG,配置按键
    USART1_Init();          // DMA 模式串口
    ADC1_Init();            // 单次采样 ADC
    
    // 3. 滤波缓存预热
    for(uint8_t ch = 0; ch < 4; ch++) {
        for(uint8_t i = 0; i < ADC_FILTER_DEPTH; i++) {
            adc_filter_buf[ch][i] = Get_ADC_Single(ch);
        }
    }
    
    // 4. 主循环
    while(1) {
        //  ADC 采样 + 滑动滤波
        //  模式判断:阈值模式 原始ADC模式
        //  按键检测
        //  轻量级延时
    }
}

5.2 模式切换机制

两个专用按键负责模式切换:

按键 引脚 功能 实现
K9 PA11 切阈值模式 current_mode = MODE_THRESHOLD
K10 PB3 切原始 ADC 模式 current_mode = MODE_RAW_ADC

切换后立即生效,无需重启。阈值模式下发送单字节指令,原始模式下发送固定帧。

5.3 按键消抖与防重复

void Check_Buttons(void)
{
    for(uint8_t i = 0; i < BUTTON_COUNT; i++) {
        uint8_t curr = GPIO_ReadInputDataBit(buttons[i].GPIOx, buttons[i].GPIO_Pin);
        
        // 下降沿检测(按下)+ 50ms 防抖
        if(curr == Bit_RESET && buttons[i].last_state == Bit_SET) {
            if((now - buttons[i].last_time) > DEBOUNCE_TIME_MS) {
                // 执行按键动作
                buttons[i].last_state = curr;
                buttons[i].last_time = now;
            }
        }
        // 上升沿检测(释放)
        else if(curr == Bit_SET && buttons[i].last_state == Bit_RESET) {
            buttons[i].last_state = curr;
        }
    }
}

六、性能测试

测试项 参数 结果
系统时钟 72MHz HSE+PLL 实测 72.00MHz
ADC 采样率 12MHz ADCCLK,239.5 cycles ~50kHz 单通道
滤波后有效分辨率 12-bit → 等效 10-bit ±10 LSB 噪声
串口发送带宽 9600bps,10Hz 帧频 占用率 ~23%
DMA 发送耗时 22 字节 @ 9600bps ~23ms,CPU 零占用
主循环周期 含 ADC×4 + 滤波 + 按键扫描 ~5ms(200Hz)
按键响应延迟 消抖 50ms + 扫描周期 < 60ms

七、应用场景

7.1 典型应用

受控设备 推荐模式 配置
智能小车 阈值模式 右Y=前进/后退,右X=转向
二轴云台 原始 ADC 模式 两路摇杆直接映射角度
机械臂 原始 ADC 模式 四路分别控制 XYZ+夹爪
继电器模组 阈值模式 按键直接控制开关
无人机 混合模式 原始 ADC 控制姿态,按键控制模式切换

八、开源与复刻

项目 链接
立创开源硬件 OSHWHUB - 基于 STM32 可编程多功能蓝牙遥控器
完整代码 见上文,可直接复制到 Keil、VSCode 编译
硬件 BOM 见立创工程

编译环境:Keil MDK-ARM 5.38 + STM32F10x Standard Peripheral Library v3.5.0


九、总结

本文设计的双模蓝牙遥控器通过阈值、原始 ADC 混合架构,已经通过实际打样验证,兼顾了简单设备的兼容性和复杂设备的精细控制需求。DMA 非阻塞发送释放 CPU 资源,滑动平均滤波提升摇杆稳定性,优先级状态机彻底解决多路指令冲突。可直接移植到 STM32F103 全系或其他 Cortex-M3 平台。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐