Hanuman嵌入式机器人控制库:电机闭环与传感器融合实战指南
嵌入式机器人运动控制系统依赖于底层硬件抽象、实时闭环控制与多源传感器融合等关键技术。其核心原理在于通过PID算法实现电机速度/位置的误差反馈调节,并结合编码器与IMU数据进行姿态估计和里程计计算,从而支撑自主导航与精准执行。该类系统在教育机器人、智能小车、ROS底层驱动等场景中具有广泛工程价值。Hanuman库作为面向ARM Cortex-M平台的轻量级固件框架,深度整合双电机驱动、正交编码器解码
1. Hanuman 库概述
Hanuman 库是一个面向嵌入式机器人控制的专用软件框架,由 Artron Academy 开发并维护,专为 Hanuman 机器人控制板 (以下简称“Hanuman 板”)定制设计。该库并非通用型外设驱动集合,而是以“机器人运动控制中枢”为定位,将底层硬件抽象、实时任务调度、传感器融合、电机闭环控制与上层通信协议有机整合,形成一套可裁剪、可扩展、面向实际控制场景的固件支撑平台。
Hanuman 板本身基于 ARM Cortex-M 系列微控制器(具体型号依硬件版本而定,常见为 STM32F407VG 或 STM32H743VI),集成双路 H 桥电机驱动(支持四象限运行)、多通道 ADC(用于电流/电压/电位器采样)、多组 PWM 输出(用于舵机或有刷电机调速)、标准 I²C/SPI/UART 接口(用于连接 IMU、编码器、激光测距、WiFi/BLE 模块等),并预留 JTAG/SWD 调试接口与 USB DFU 升级能力。Hanuman 库的设计哲学是: 让开发者聚焦于机器人行为逻辑,而非寄存器配置与中断服务细节 。
该库采用模块化分层架构,严格遵循嵌入式实时系统开发规范:
- 硬件抽象层(HAL) :封装芯片外设操作,屏蔽不同 MCU 型号差异,提供统一的
HAL_*接口(如HAL_TIM_PWM_Start,HAL_ADC_Start_IT)。此层直接对接 STM32 HAL 库(或 LL 库),确保可移植性。 - 设备驱动层(Driver) :实现对板载关键外设的驱动,包括:
motor_driver.c/h:双路直流电机驱动,支持正反转、PWM 调速、过流保护、堵转检测;encoder_driver.c/h:正交编码器信号解码(支持 x2/x4 模式),提供位置、速度、方向信息;imu_driver.c/h:通过 I²C 读取 MPU6050/MPU9250 等惯性测量单元原始数据,并提供姿态解算(互补滤波或简易卡尔曼);servo_driver.c/h:16 路 PWM 舵机控制,支持脉宽微调与死区补偿;adc_sensor.c/h:多通道模拟量采集(电池电压、电机电流、电位器角度等),带软件滤波与标定接口。
- 控制算法层(Control) :提供核心运动控制功能:
pid_controller.c/h:可配置的 PID 控制器,支持位置式与增量式,具备抗积分饱和、输出限幅、微分先行等工业级特性;odometry.c/h:基于编码器与 IMU 的里程计计算,支持差速轮式底盘运动学建模(v = (vl + vr)/2,ω = (vr - vl)/L);motion_planner.c/h:简易路径规划与轨迹生成,支持直线、圆弧插补及 S 曲线加减速。
- 应用服务层(App Service) :构建机器人运行时环境:
comm_protocol.c/h:定义串口(UART)通信协议,支持 ASCII 命令(如M1:127设置左轮 PWM)与二进制帧(含 CRC16 校验);task_manager.c/h:基于 FreeRTOS 的任务管理,预定义motor_control_task,sensor_fusion_task,comm_handler_task等,各任务间通过队列与信号量同步;system_monitor.c/h:看门狗喂狗、温度/电压监控、错误日志记录(环形缓冲区)。
整个库以 C 语言编写,无 C++ 依赖,编译目标为裸机(Bare-metal)或 FreeRTOS 环境,不依赖任何高级操作系统服务。其源码结构清晰,符合 CMSIS 标准,便于在 STM32CubeIDE、Keil MDK 或 PlatformIO 等主流工具链中集成。
2. 核心功能与工程价值
Hanuman 库的核心价值在于将机器人开发中重复度高、易出错的底层工作标准化,使工程师能快速构建稳定可靠的运动控制系统。其关键功能并非孤立存在,而是围绕“闭环控制”这一核心目标深度耦合。
2.1 双电机协同控制与状态反馈闭环
Hanuman 板的双路 H 桥驱动电路支持独立 PWM 输入与电流检测。库中 motor_driver 模块不仅提供 Motor_SetSpeed(MOTOR_LEFT, 80) 这样的基础接口,更内置了完整的状态机与保护逻辑:
// motor_driver.h 关键 API 定义
typedef enum {
MOTOR_STOP = 0,
MOTOR_FORWARD,
MOTOR_BACKWARD,
MOTOR_BRAKE
} MotorDir_TypeDef;
typedef struct {
int16_t target_speed; // 目标 PWM 占空比 (-100 ~ +100)
int16_t actual_speed; // 实际测得速度(来自编码器)
uint16_t current_ma; // 实时电流采样值(mV → mA)
MotorDir_TypeDef dir; // 当前方向
uint8_t fault_flag; // 故障标志:0x01=过流, 0x02=过热, 0x04=堵转
} MotorState_t;
void Motor_Init(void);
void Motor_SetTargetSpeed(MotorId_TypeDef id, int16_t speed);
void Motor_UpdateState(MotorId_TypeDef id); // 在定时器中断中周期调用
uint8_t Motor_GetFaultFlag(MotorId_TypeDef id);
Motor_UpdateState() 是闭环控制的关键入口。它在 1ms 定时器中断(由 TIM6 或 TIM7 驱动)中执行,完成以下原子操作:
- 读取当前编码器计数值,计算瞬时转速(单位:RPM);
- 读取 ADC 通道获取霍尔电流传感器输出,经标定公式
I = (Vadc * Vref / 4096) / Rshunt计算实际电流; - 将实测速度与
target_speed比较,送入 PID 控制器计算新的 PWM 输出值; - 检查电流是否超过阈值(如 2A),若超限则自动置
fault_flag并强制Motor_Stop(); - 更新
MotorState_t结构体,供上层任务查询。
这种设计将“设定目标→感知状态→计算误差→调整输出→安全保护”的完整闭环压缩在一个确定性中断内,保证了控制周期的严格性(1ms),这是实现平稳启停与精准定位的基础。
2.2 多源传感器融合与自主定位
机器人导航的前提是可靠的位置与姿态估计。Hanuman 库通过 imu_driver 与 encoder_driver 的协同,构建低成本但实用的定位方案。
-
IMU 数据处理 :
IMU_ReadRawData()返回加速度计(ax, ay, az)与陀螺仪(gx, gy, gz)原始值(16-bit)。库内置IMU_AttitudeEstimate()函数,采用互补滤波算法:// 互补滤波核心逻辑(简化示意) float dt = 0.001f; // 1ms 采样间隔 float acc_pitch = atan2(-ax, sqrt(ay*ay + az*az)) * 180.0f / PI; float gyro_pitch = pitch_last + gy * dt * 180.0f / PI; // 积分得到角速度 pitch = 0.98f * gyro_pitch + 0.02f * acc_pitch; // 加权融合此算法在动态响应(陀螺仪)与长期稳定性(加速度计)间取得平衡,避免纯积分漂移,适用于中低速移动场景。
-
里程计融合 :
Odometry_Update()函数在每次Motor_UpdateState()后被调用,结合左右轮编码器脉冲数cnt_l,cnt_r与轮距L、轮径D,按差速模型更新机器人位姿(x, y, theta):float delta_s = (delta_l + delta_r) * PI * D / (2.0f * ENCODER_PPR); float delta_theta = (delta_r - delta_l) * D / L; x += delta_s * cosf(theta + delta_theta/2.0f); y += delta_s * sinf(theta + delta_theta/2.0f); theta += delta_theta;其中
ENCODER_PPR为编码器每转脉冲数。该模型虽忽略滑移,但在硬质地面、中低速下精度足够支撑基础避障与循迹。
2.3 实时通信协议与远程交互
Hanuman 库定义了一套轻量、鲁棒的 UART 通信协议,使上位机(PC、树莓派、手机 App)能实时监控与控制机器人。协议分为两种模式:
-
ASCII 命令模式(默认) :人类可读,便于调试。
- 查询状态:
GET:ALL\r\n→ 返回STAT:OK,M1:85,M2:-42,ENC_L:1245,ENC_R:1238,ACC_X:-123,GYR_Z:45,VBAT:11.8\r\n - 设置参数:
SET:M1=60,M2=-60\r\n→ 执行后返回ACK:OK\r\n - 错误响应:
ERR:INVALID_CMD\r\n
- 查询状态:
-
二进制帧模式(高性能) :固定长度 16 字节帧,含命令 ID、数据域、CRC16 校验,吞吐率更高,适合闭环控制指令流。
协议栈在 comm_handler_task 中实现,该任务优先级设为 osPriorityAboveNormal ,使用 osMessageQueue 接收 UART ISR 收到的数据,并通过 switch-case 解析命令。所有响应均通过 HAL_UART_Transmit_IT() 异步发送,避免阻塞主控循环。
3. 关键 API 详解与典型应用示例
3.1 主要 API 分类与参数说明
| 模块 | API 函数 | 参数说明 | 返回值 | 典型用途 |
|---|---|---|---|---|
| Motor | Motor_Init(void) |
无 | void |
初始化 GPIO、TIM、ADC,配置 H 桥引脚与电流采样通道 |
Motor_SetTargetSpeed(MotorId_TypeDef id, int16_t speed) |
id : MOTOR_LEFT / MOTOR_RIGHT ; speed : -100~+100(占空比百分比) |
HAL_StatusTypeDef |
设定目标速度,触发 PID 计算 | |
Motor_GetState(MotorId_TypeDef id, MotorState_t *state) |
id : 电机编号; state : 指向状态结构体的指针 |
void |
获取当前速度、电流、故障标志等实时状态 | |
| Encoder | Encoder_Init(void) |
无 | void |
初始化 TIMx 编码器接口模式,配置输入捕获通道 |
Encoder_GetPulseCount(MotorId_TypeDef id) |
id : 对应电机 |
int32_t |
获取自初始化以来的总脉冲数(有符号,含方向) | |
Encoder_ResetCounter(MotorId_TypeDef id) |
id : 对应电机 |
void |
清零计数器,常用于归零操作 | |
| IMU | IMU_Init(void) |
无 | HAL_StatusTypeDef |
初始化 I²C,配置 MPU 寄存器(陀螺仪量程±2000dps,加速度±16g) |
IMU_ReadRawData(int16_t *acc, int16_t *gyro) |
acc[3] : 存储 ax/ay/az; gyro[3] : 存储 gx/gy/gz |
HAL_StatusTypeDef |
读取原始传感器数据 | |
IMU_GetAttitude(float *pitch, float *roll, float *yaw) |
pitch/roll/yaw : 指向浮点变量的指针 |
void |
获取融合后的欧拉角(单位:度) | |
| Comm | Comm_Init(void) |
无 | void |
初始化 UART(115200bps, 8N1),创建消息队列与互斥锁 |
Comm_SendResponse(const char *str) |
str : 待发送字符串(自动添加 \r\n ) |
void |
发送 ASCII 响应 | |
Comm_SendBinaryFrame(uint8_t cmd_id, uint8_t *data, uint8_t len) |
cmd_id : 命令ID; data : 数据指针; len : 数据长度(≤10) |
void |
发送二进制帧 |
3.2 FreeRTOS 集成示例:构建多任务控制循环
Hanuman 库默认与 FreeRTOS 协同工作。以下是一个典型的 main.c 初始化与任务创建代码片段,展示了如何将库模块纳入实时操作系统环境:
#include "main.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
// 声明任务句柄与队列
osThreadId_t motorTaskHandle;
osThreadId_t sensorTaskHandle;
osMessageQueueId_t cmdQueueHandle;
// 电机控制任务:执行 PID 闭环
void MotorControlTask(void *argument) {
MotorState_t left_state, right_state;
while (1) {
// 从队列获取上位机下发的速度指令(假设已解析为结构体)
if (osMessageQueueReceive(cmdQueueHandle, &cmd, NULL, 10) == osOK) {
Motor_SetTargetSpeed(MOTOR_LEFT, cmd.left_speed);
Motor_SetTargetSpeed(MOTOR_RIGHT, cmd.right_speed);
}
// 更新电机状态(含 PID 计算)
Motor_UpdateState(MOTOR_LEFT);
Motor_UpdateState(MOTOR_RIGHT);
// 获取最新状态用于日志或反馈
Motor_GetState(MOTOR_LEFT, &left_state);
Motor_GetState(MOTOR_RIGHT, &right_state);
osDelay(1); // 保持 1ms 控制周期
}
}
// 传感器融合任务:周期读取并融合
void SensorFusionTask(void *argument) {
int16_t acc[3], gyro[3];
float pitch, roll, yaw;
while (1) {
// 读取原始 IMU 数据
if (IMU_ReadRawData(acc, gyro) == HAL_OK) {
// 执行姿态解算
IMU_GetAttitude(&pitch, &roll, &yaw);
// 更新里程计(需先获取编码器值)
Encoder_GetPulseCount(MOTOR_LEFT);
Encoder_GetPulseCount(MOTOR_RIGHT);
Odometry_Update();
}
osDelay(10); // 10ms 更新一次姿态与里程计
}
}
// 主函数
int main(void) {
HAL_Init();
SystemClock_Config();
// 初始化所有硬件外设
MX_GPIO_Init();
MX_TIM_Init(); // PWM, Encoder, SysTick
MX_ADC_Init(); // 电流/电压采样
MX_I2C_Init(); // IMU
MX_USART_Init(); // UART 通信
// 初始化 Hanuman 库各模块
Motor_Init();
Encoder_Init();
IMU_Init();
Comm_Init();
// 创建 FreeRTOS 对象
cmdQueueHandle = osMessageQueueNew(10, sizeof(Command_t), NULL);
motorTaskHandle = osThreadNew(MotorControlTask, NULL, &motorTask_attr);
sensorTaskHandle = osThreadNew(SensorFusionTask, NULL, &sensorTask_attr);
// 启动调度器
osKernelStart();
while (1) { }
}
此示例清晰体现了 Hanuman 库的工程化设计:每个物理功能(电机控制、传感器处理)被封装为独立、可调度的任务,通过消息队列解耦,避免了传统裸机 while(1) 中复杂的轮询与状态判断,极大提升了代码可维护性与系统健壮性。
4. 硬件配置与参数标定指南
Hanuman 库的稳定运行高度依赖于对板载硬件特性的精确配置与标定。这些参数通常定义在 hanuman_config.h 头文件中,工程师必须根据实际硬件进行修改。
4.1 关键硬件参数配置表
| 参数宏定义 | 默认值 | 物理含义 | 配置依据 | 修改建议 |
|---|---|---|---|---|
MOTOR_PWM_PERIOD |
1000 |
TIMx 自动重装载值(ARR) | 决定 PWM 基频。 f_pwm = f_tim / (ARR+1) 。例: f_tim=1MHz , ARR=999 → f_pwm=1kHz |
若电机噪声大,可降至 500Hz( ARR=1999 );若需精细调速,可升至 2kHz( ARR=499 ) |
ENCODER_PPR |
1024 |
编码器每转脉冲数(Pulses Per Revolution) | 查阅所用编码器规格书 | 必须准确,否则里程计距离计算错误。实测可用示波器抓取 A/B 相边沿计数验证 |
WHEEL_DIAMETER_MM |
100 |
车轮直径(毫米) | 用卡尺实测车轮滚动圆直径 | 影响 delta_s 计算,误差直接导致定位偏差。建议多次测量取平均 |
WHEEL_BASE_MM |
180 |
左右轮中心距(毫米) | 测量两轮轴心水平距离 | 影响转向角 delta_theta 计算,决定转弯半径精度 |
CURRENT_SHUNT_OHM |
0.01 |
电流采样电阻阻值(欧姆) | 查阅原理图中 Rshunt 器件值 |
标定电流值 I = Vadc * Vref / (4096 * Rshunt) 的核心参数,必须精确 |
VBAT_DIV_RATIO |
3.0f |
电池电压分压比( R1+R2 / R2 ) |
原理图中分压电阻网络计算值 | 用于将 ADC 读数换算为真实电池电压,影响低电量告警准确性 |
4.2 电流与电压标定实操步骤
由于 ADC 读数受参考电压 Vref 和运放增益影响,仅靠理论计算难以保证精度,必须进行两点标定:
- 空载标定 :断开电机,给采样电路施加已知小电流(如 100mA),记录 ADC 值
adc_100。 - 满载标定 :接入电机,施加已知大电流(如 1.5A,可用电子负载或大功率电阻模拟),记录 ADC 值
adc_1500。 - 计算斜率与偏移 :
float slope = (1500.0f - 100.0f) / (adc_1500 - adc_100); float offset = 100.0f - slope * adc_100; - 在
motor_driver.c中替换电流计算公式 :// 原始线性公式(仅适用于理想情况) // current_ma = (adc_val * VREF_MV) / 4096 / CURRENT_SHUNT_OHM; // 标定后公式 current_ma = slope * adc_val + offset;
电压标定同理,使用万用表实测分压点电压,与 ADC 读数建立映射关系。此过程是确保过流保护、电池管理功能可靠的必要步骤。
5. 故障诊断与调试技巧
在机器人开发中,硬件与软件的耦合性极强,故障现象往往表里不一。Hanuman 库内置了丰富的调试支持,工程师应善加利用。
5.1 常见故障现象与排查路径
| 现象 | 可能原因 | 诊断方法 | 解决方案 |
|---|---|---|---|
| 电机完全不转 | 1. Motor_Init() 未正确配置 GPIO/TIM 2. H 桥供电异常( VMOT 未上电) 3. 电流采样电路短路触发保护 |
1. 用示波器测 TIMx_CHy 是否有 PWM 波形 2. 万用表测 VMOT 引脚电压 3. Motor_GetFaultFlag() 返回非零值 |
1. 检查 MX_TIM_Init() 生成代码 2. 确认电源输入与保险丝 3. 断开电机,测 Rshunt 两端电阻是否为 0.01Ω |
| 电机抖动/无法稳速 | 1. PID 参数 Kp/Ki/Kd 不匹配 2. 编码器信号干扰(A/B 相噪声大) 3. 供电纹波过大(影响 ADC 采样) |
1. 临时将 Ki=0 , Kd=0 ,仅用 Kp 观察响应 2. 示波器观察编码器 A/B 相波形质量 3. 示波器测 VDDA 纹波 |
1. 从小到大调节 Kp ,再引入 Ki 消除静差 2. 加磁环、缩短走线、增加 RC 滤波 3. 加大 VDDA 旁路电容(10μF+100nF) |
| 里程计累计误差大 | 1. WHEEL_DIAMETER_MM 或 WHEEL_BASE_MM 配置错误 2. 地面打滑严重 3. 编码器丢脉冲(机械松动或电气干扰) |
1. 手动推动机器人走 1 米,对比 Odometry_GetDistance() 返回值 2. 在光滑地面测试,观察是否误差加剧 |
1. 根据实测误差反推修正轮径/轮距值 2. 增加摩擦系数或改用全向轮 3. 检查编码器安装紧固性与信号线屏蔽 |
5.2 利用库内建调试功能
- 实时日志输出 :在
system_monitor.c中启用DEBUG_LOG_ENABLE,所有Motor_GetState(),IMU_GetAttitude()的结果会通过printf重定向到 UART,方便上位机抓取分析。 - LED 指示灯编码 :
SystemMonitor_Task控制板载 LED,以不同闪烁模式指示系统状态(如长亮=正常,快闪=过流,慢闪=低电量),无需串口即可快速判断。 - 内存占用监控 :
osMemoryUsageGet()可查询各任务栈使用峰值,防止因栈溢出导致的随机故障。Hanuman 库推荐任务栈大小:MotorControlTask≥ 512 字节,SensorFusionTask≥ 256 字节。
一名经验丰富的嵌入式工程师,在首次点亮 Hanuman 板后,必做的三件事是:用示波器确认 PWM 波形干净、用万用表验证所有电源轨电压稳定、用串口助手发送 GET:ALL 命令查看所有传感器读数是否在合理范围内。这三步,是跨越从“代码烧录成功”到“系统真正可用”之间最坚实的桥梁。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐
所有评论(0)