RIOT 直流电机驱动测试指南:motor_driver 测试应用的原理与运行验证
【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT
导读
本文围绕 RIOT 操作系统中的tests/drivers/motor_driver测试应用展开,深入剖析其背后的高层直流电机驱动模块motor_driver(源码、头文件)。你将了解到该测试程序如何驱动两个电机完成「半速顺时针 → 全速顺时针 → 制动 → 半速逆时针 → 全速逆时针 → 制动」的无限循环,以及motor_set、motor_brake、motor_enable/motor_disable等 API 的底层实现原理、参数配置方法和在真实板卡上的运行验证方式。
一、测试目标与预期结果
本测试用于验证 RIOT 高层motor_driver驱动(High-level driver for DC motors)的核心功能。测试主程序位于 tests/drivers/motor_driver/main.c,其逻辑是一个无限循环,按顺序执行以下六个阶段:
- 两电机以50% PWM 占空比顺时针(CW)转动;
- 两电机以100% PWM 占空比顺时针(CW)转动;
- 两电机制动(brake);
- 两电机以50% PWM 占空比逆时针(CCW)转动;
- 两电机以100% PWM 占空比逆时针(CCW)转动;
- 两电机制动(brake)。
从 main.c 的motion_control()主循环可以看出,除了上述六个阶段外,该循环还额外加入了motor_disable()/motor_enable()的禁用与使能测试环节:在 50% 与 100% 占空比之间,程序会先禁用两个电机、等待 3 秒后再重新使能,用于验证驱动板的 enable 功能(前提是驱动板具备该特性)。
每个阶段的间隔由INTERVAL宏控制,定义为3 * MS_PER_SEC,即 3 秒:
/* Set interval to 3 seconds */ #define INTERVAL (3 * MS_PER_SEC) #define MOTOR_0_ID ((uint8_t)0) #define MOTOR_1_ID ((uint8_t)1)完整循环时序
结合 main.c,一次循环的完整时序为:
| 阶段 | 动作 | 占空比 | 方向 | 持续 |
|---|---|---|---|---|
| 1 | 制动 | — | — | 3 s |
| 2 | 转动 | 50% | CW | 3 s |
| 3 | 禁用电机 | — | — | 3 s |
| 4 | 使能电机 | — | — | 3 s |
| 5 | 转动 | 100% | CW | 3 s |
| 6 | 制动 | — | — | 3 s |
| 7 | 转动 | 50% | CCW | 3 s |
| 8 | 转动 | 100% | CCW | 3 s |
| 9 | 制动 | — | — | 3 s |
方向切换通过dir *= -1实现(main.c),而方向语义由motor_set对pwm_duty_cycle符号的解析决定:正值对应顺时针(CW),负值对应逆时针(CCW)。
二、测试程序结构拆解
2.1 顶层 API 调用封装
测试程序将驱动 API 封装为两个辅助函数,便于阅读与复用:
motors_control(int32_t duty_cycle)(main.c)——对两个电机同时施加指定的带符号 PWM 占空比,并打印方向标识(CW 或 CCW):
void motors_control(int32_t duty_cycle) { char str[4]; if (duty_cycle >= 0) { strncpy(str, "CW", 3); } else { strncpy(str, "CCW", 4); } puts("\nActuate Motors"); if (motor_set(&motor_driver, MOTOR_0_ID, duty_cycle)) { printf("Cannot set PWM duty cycle for motor %" PRIu8 "\n", MOTOR_0_ID); } if (motor_set(&motor_driver, MOTOR_1_ID, duty_cycle)) { printf("Cannot set PWM duty cycle for motor %" PRIu8 "\n", MOTOR_1_ID); } }motors_brake(void)(main.c)——同时制动两个电机:
void motors_brake(void) { puts("\nBrake motors"); if (motor_brake(&motor_driver, MOTOR_0_ID)) { printf("Cannot brake motor %" PRIu32 "\n", (uint32_t)MOTOR_0_ID); } if (motor_brake(&motor_driver, MOTOR_1_ID)) { printf("Cannot brake motor %" PRIu32 "\n", (uint32_t)MOTOR_1_ID); } }两个函数均以非零返回值作为调用失败的判定依据,这与驱动 API 返回负 errno 的约定一致(见下文第三节)。
2.2 初始化流程
motion_control()开头首先完成驱动初始化(main.c):
int32_t pwm_res = motor_driver_params->pwm_resolution; ret = motor_driver_init(&motor_driver, &motor_driver_params[0]); if (ret) { LOG_ERROR("motor_driver_init failed with error code %d\n", ret); } expect(ret == 0);这里使用test_utils/expect.h提供的expect()宏断言初始化必须成功;同时从全局参数数组motor_driver_params[0]中取出pwm_resolution(默认 100)作为后续占空比计算的分母,于是:
dir * pwm_res / 2→ 50% 占空比;dir * pwm_res→ 100% 占空比。
2.3 回调函数示例
init_dev.h 定义了motor_driver_callback_example回调,并通过MOTOR_DRIVER_PARAM_MOTOR_SET_POST_CALLBACK宏将其注册为默认的motor_set()后置回调:
#ifndef MOTOR_DRIVER_PARAM_MOTOR_SET_POST_CALLBACK /** Default callback called at end of motor_set() */ # define MOTOR_DRIVER_PARAM_MOTOR_SET_POST_CALLBACK motor_driver_callback_example #endif回调实现(main.c)在每次motor_set()成功后打印电机驱动实例地址、电机 ID 与最终施加的 PWM 值:
void motor_driver_callback_example( const motor_driver_t *motor_driver, uint8_t motor_id, int32_t pwm_duty_cycle) { LOG_DEBUG("MOTOR-DRIVER=%p" \ " MOTOR_ID = %"PRIu8 \ " PWM_VALUE = %"PRIi32"\n", \ (void*)motor_driver, motor_id, pwm_duty_cycle); }由于测试程序的 Makefile 中设置了CFLAGS += -DLOG_LEVEL=LOG_DEBUG(Makefile),该调试信息会在串口输出中实时可见,便于观察每一次 PWM 写入。
三、motor_driver 驱动的底层实现原理
3.1 驱动初始化motor_driver_init
motor_driver_init()(motor_driver.c)负责三板斧:
- PWM 设备初始化:调用
pwm_init(params->pwm_dev, params->pwm_mode, params->pwm_frequency, params->pwm_resolution),若返回 0 则判定失败并返回-EINVAL; - GPIO 初始化:对每个电机的
gpio_dir0、gpio_dir1(或gpio_brake)与gpio_enable引脚逐个执行gpio_init(..., GPIO_OUT),任一失败即返回-EIO,且该电机视为未配置成功。初始化前会先通过gpio_is_valid()判断引脚是否有效(GPIO_UNDEF视为无效),因此未使用的引脚可以安全地保持未定义; - 保存参数指针:将
params存入motor_driver->params。
初始化过程中pwm_init返回的是实际取得的 PWM 分辨率值,测试程序中将其取出用作占空比计算基准,正是利用了这一点。
3.2 三种驱动模式
驱动支持三种硬件接线模式,定义于 drivers/include/motor_driver.h:
| 模式 | 取值 | 说明 |
|---|---|---|
MOTOR_DRIVER_2_DIRS | 0 | 两个方向 GPIO,可处理制动(H 桥典型接法) |
MOTOR_DRIVER_1_DIR | 1 | 单个方向 GPIO,无制动能力(默认模式) |
MOTOR_DRIVER_1_DIR_BRAKE | 2 | 单个方向 GPIO + 单个制动 GPIO |
方向状态定义于同一头文件(L118-L121):
typedef enum { MOTOR_CW = 0, /**< clockwise */ MOTOR_CCW = 1, /**< counter clockwise */ } motor_direction_t;默认模式为MOTOR_DRIVER_1_DIR(单方向引脚、无制动引脚)。注意:在这种模式下调用motor_brake()无法真正制动,驱动只会在 DEBUG 日志中提示「cannot brake with only one direction pin」并将 PWM 清零(见 motor_driver.c)。
3.3 速度与方向控制motor_set
motor_set()(motor_driver.c)是本驱动的核心 API,完整实现「符号决定方向、绝对值决定速度」的语义:
- ID 校验:
motor_id >= nb_motors时返回-EINVAL; - 方向解析:
pwm_duty_cycle < 0判为MOTOR_CCW,否则为MOTOR_CW,再与gpio_dir_reverse异或以适配电机实际接线方向(L129-L130);若异或后方向值不合法则强制将占空比清零; - 取绝对值:
pwm_duty_cycle_abs = |pwm_duty_cycle|,作为实际写入 PWM 的占空比; - 临界区保护:
irq_disable()/irq_restore()包裹方向引脚与 PWM 的写入序列,保证「换向 + 调速」原子完成,避免电机短暂误动作; - 按模式写方向:2 方向模式调用
_motor_set_two_dirs(gpio_dir0写方向值、gpio_dir1写方向值取反),1 方向模式调用_motor_set_one_dir; - 写 PWM:
pwm_set(params->pwm_dev, motor->pwm_channel, (uint16_t)pwm_duty_cycle_abs); - 解除制动:仅
MOTOR_DRIVER_1_DIR_BRAKE模式需要,根据brake_inverted决定制动引脚电平; - 后置回调:若配置了
motor_set_post_cb,则调用回调并传入驱动指针、电机 ID 与带符号占空比。
3.4 制动motor_brake
motor_brake()(motor_driver.c)同样先做 ID 校验,然后在临界区内:
MOTOR_DRIVER_2_DIRS:两个方向引脚同时写!brake_inverted电平,形成短路制动(L233-L234);MOTOR_DRIVER_1_DIR_BRAKE:写制动引脚!brake_inverted;MOTOR_DRIVER_1_DIR:仅打印 DEBUG 提示,无法硬件制动;- 无论哪种模式,最后都执行
pwm_set(..., 0)将 PWM 占空比清零。
3.5 使能与禁用motor_enable/motor_disable
这两个 API(motor_driver.c)控制gpio_enable引脚,电平极性由enable_inverted决定(默认false,即高电平使能)。若gpio_enable为GPIO_UNDEF,则打印 WARNING 并跳过操作——这正是测试循环中「Disable/Enable motors」环节的底层行为。
四、配置参数详解
4.1 默认参数表
驱动的默认参数定义在 drivers/motor_driver/include/motor_driver_params.h,均为可被应用或板级配置覆盖的宏:
| 宏 | 默认值 | 说明 |
|---|---|---|
MOTOR_DRIVER_PARAM_MODE | MOTOR_DRIVER_1_DIR | 驱动模式 |
MOTOR_DRIVER_PARAM_BRAKE_INVERTED | false | 制动引脚电平是否反相 |
MOTOR_DRIVER_PARAM_ENABLE_INVERTED | false | 使能引脚电平是否反相 |
MOTOR_DRIVER_PARAM_PWM | 0 | PWM 设备号 |
MOTOR_DRIVER_PARAM_PWM_MODE | PWM_LEFT | PWM 对齐模式 |
MOTOR_DRIVER_PARAM_PWM_FREQUENCY | 20000U | PWM 频率(20 kHz,高于人耳听阈) |
MOTOR_DRIVER_PARAM_PWM_RESOLUTION | 100U | PWM 分辨率(即 100 级占空比) |
MOTOR_DRIVER_PARAM_NB_MOTORS | 2U | 电机数量 |
MOTOR_DRIVER_PARAM_MOTOR_SET_POST_CALLBACK | NULL | motor_set后置回调 |
MOTOR_DRIVER_PARAM_MOTOR1_PWM_CHANNEL | 1U | 电机 1 的 PWM 通道 |
MOTOR_DRIVER_PARAM_MOTOR2_PWM_CHANNEL | 2U | 电机 2 的 PWM 通道 |
MOTOR_DRIVER_PARAM_MOTOR{1,2}_GPIO_ENABLE | GPIO_UNDEF | 使能引脚(未定义则跳过) |
MOTOR_DRIVER_PARAM_MOTOR{1,2}_GPIO_DIR0 | GPIO_UNDEF | 方向引脚 |
MOTOR_DRIVER_PARAM_MOTOR{1,2}_GPIO_DIR1_OR_BRAKE | GPIO_UNDEF | 第二方向或制动引脚 |
MOTOR_DRIVER_PARAM_MOTOR{1,2}_GPIO_DIR_REVERSE | 0 | 方向取反标志 |
这些宏最终聚合为MOTOR_DRIVER_PARAMS,构成静态的motor_driver_params[]数组(motor_driver_params.h),并同时提供motor_driver_saul_info[]以接入 SAUL 注册表。
4.2 板级覆盖方式
默认参数中方向与使能引脚均为GPIO_UNDEF,因此在真实板卡上运行前必须通过板级或应用级宏覆盖,例如在板级头文件中:
#define MOTOR_DRIVER_PARAM_MODE MOTOR_DRIVER_2_DIRS #define MOTOR_DRIVER_PARAM_PWM 0 #define MOTOR_DRIVER_PARAM_PWM_FREQUENCY 20000U #define MOTOR_DRIVER_PARAM_PWM_RESOLUTION 100U #define MOTOR_DRIVER_PARAM_MOTOR1_PWM_CHANNEL 0U #define MOTOR_DRIVER_PARAM_MOTOR1_GPIO_DIR0 GPIO_PIN(0, 0) #define MOTOR_DRIVER_PARAM_MOTOR1_GPIO_DIR1_OR_BRAKE GPIO_PIN(0, 1) #define MOTOR_DRIVER_PARAM_MOTOR1_GPIO_ENABLE GPIO_PIN(0, 2) #define MOTOR_DRIVER_PARAM_MOTOR2_PWM_CHANNEL 1U #define MOTOR_DRIVER_PARAM_MOTOR2_GPIO_DIR0 GPIO_PIN(0, 3) #define MOTOR_DRIVER_PARAM_MOTOR2_GPIO_DIR1_OR_BRAKE GPIO_PIN(0, 4) #define MOTOR_DRIVER_PARAM_MOTOR2_GPIO_ENABLE GPIO_PIN(0, 5)若使用 Kconfig 配置,最大电机数由MOTOR_DRIVER_MAX(默认 2)控制,其帮助文本提示该值取决于 H 桥能力,且不应超过 PWM 通道数(见 drivers/motor_driver/Kconfig)。
4.3 数据结构关系
从 drivers/include/motor_driver.h 可以看到:
motor_t(L126-L135)描述单个电机,含pwm_channel、gpio_enable、gpio_dir0以及共用体gpio_dir1/gpio_brake(取决于模式)、gpio_dir_reverse;motor_driver_params_t(L164-L175)描述整块驱动,含模式、PWM 设备参数、极性标志、电机数组与后置回调;motor_driver_t(L180-L182)仅持有const motor_driver_params_t *params指针,是轻量的运行期句柄。
五、构建与运行
5.1 模块依赖
测试应用的 Makefile 声明了所需模块:
BOARD ?= native INCLUDES += -I$(APPDIR) include ../Makefile.drivers_common USEMODULE += motor_driver USEMODULE += shell_cmds_default USEMODULE += ztimer USEMODULE += ztimer_msec CFLAGS += -DLOG_LEVEL=LOG_DEBUG CFLAGS += -DDEBUG_ASSERT_VERBOSE其中:
motor_driver为被测驱动模块;ztimer/ztimer_msec提供ztimer_sleep(ZTIMER_MSEC, INTERVAL)的毫秒级睡眠(见 main.c 等处的循环节拍);shell_cmds_default提供默认 shell 命令集;CFLAGS += -DLOG_LEVEL=LOG_DEBUG使能调试日志,从而在串口上输出回调中的MOTOR-DRIVER=...信息。
5.2 编译与烧录
默认目标板为native,也可指定其他具备 PWM 与 GPIO 能力的板卡:
# 使用默认板(native)编译 make -C tests/drivers/motor_driver # 指定真实板卡编译并烧录,例如 nucleo-f401re make -C tests/drivers/motor_driver BOARD=nucleo-f401re flash注意:部分内存较小的板卡(如 arduino-uno、atmega328p、atmega8 等)由于内存不足无法运行本测试,完整清单见 tests/drivers/motor_driver/Makefile.ci 中的
BOARD_INSUFFICIENT_MEMORY。
5.3 运行观察
程序启动后立即进入motion_control()无限循环,无需人工干预。在串口终端上应能看到按固定 3 秒节奏交替打印的动作信息,并结合调试日志观察PWM_VALUE的变化:
Actuate Motors伴随MOTOR_ID=0/1、PWM_VALUE=50(50% CW);Disable motors/Enable motors(验证 enable 功能);Actuate Motors伴随PWM_VALUE=100(100% CW);Brake motors;Actuate Motors伴随PWM_VALUE=-50(50% CCW,负号表示逆时针);Actuate Motors伴随PWM_VALUE=-100(100% CCW);Brake motors。
如果配置为MOTOR_DRIVER_1_DIR模式(默认),注意制动阶段只会将 PWM 清零,无硬件制动效果;要验证真正的制动能力,应配置为MOTOR_DRIVER_2_DIRS或MOTOR_DRIVER_1_DIR_BRAKE模式。
六、测试应用设计要点总结
- API 覆盖完整:测试循环覆盖了
motor_driver_init、motor_set(正负占空比、50%/100% 两档)、motor_brake、motor_enable/motor_disable全部公开 API; - 方向语义验证:通过带符号占空比同时验证速度与方向,负值 → CCW、正值 → CW;
- 时序可观察:3 秒间隔使每个阶段都能被肉眼与串口日志清晰分辨;
- 回调解耦:
motor_set_post_cb回调机制让上层应用可以在每次调速后执行自定义逻辑,测试程序用其打印调试信息,是驱动扩展性的直观体现。
通过本测试应用,开发者可以在实际 H 桥 + 直流电机硬件上快速验证电机接线、PWM 通道配置与驱动逻辑的正确性,是 RIOT 下进行机器人底盘、风扇等直流电机控制的实用起点。
【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考