面向机器人与云台的双模蓝牙遥控器协议实现
基于 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 平台。
更多推荐


所有评论(0)