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 驱动)中执行,完成以下原子操作:

  1. 读取当前编码器计数值,计算瞬时转速(单位:RPM);
  2. 读取 ADC 通道获取霍尔电流传感器输出,经标定公式 I = (Vadc * Vref / 4096) / Rshunt 计算实际电流;
  3. 将实测速度与 target_speed 比较,送入 PID 控制器计算新的 PWM 输出值;
  4. 检查电流是否超过阈值(如 2A),若超限则自动置 fault_flag 并强制 Motor_Stop()
  5. 更新 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 和运放增益影响,仅靠理论计算难以保证精度,必须进行两点标定:

  1. 空载标定 :断开电机,给采样电路施加已知小电流(如 100mA),记录 ADC 值 adc_100
  2. 满载标定 :接入电机,施加已知大电流(如 1.5A,可用电子负载或大功率电阻模拟),记录 ADC 值 adc_1500
  3. 计算斜率与偏移
    float slope = (1500.0f - 100.0f) / (adc_1500 - adc_100);
    float offset = 100.0f - slope * adc_100;
    
  4. 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 命令查看所有传感器读数是否在合理范围内。这三步,是跨越从“代码烧录成功”到“系统真正可用”之间最坚实的桥梁。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐