RIOT 直流电机驱动测试指南:motor_driver 测试应用的原理与运行验证
2026/9/20 3:40:27 网站建设 项目流程

RIOT 直流电机驱动测试指南:motor_driver 测试应用的原理与运行验证

【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT

导读

本文围绕 RIOT 操作系统中的tests/drivers/motor_driver测试应用展开,深入剖析其背后的高层直流电机驱动模块motor_driver(源码、头文件)。你将了解到该测试程序如何驱动两个电机完成「半速顺时针 → 全速顺时针 → 制动 → 半速逆时针 → 全速逆时针 → 制动」的无限循环,以及motor_setmotor_brakemotor_enable/motor_disable等 API 的底层实现原理、参数配置方法和在真实板卡上的运行验证方式。


一、测试目标与预期结果

本测试用于验证 RIOT 高层motor_driver驱动(High-level driver for DC motors)的核心功能。测试主程序位于 tests/drivers/motor_driver/main.c,其逻辑是一个无限循环,按顺序执行以下六个阶段:

  1. 两电机以50% PWM 占空比顺时针(CW)转动;
  2. 两电机以100% PWM 占空比顺时针(CW)转动;
  3. 两电机制动(brake);
  4. 两电机以50% PWM 占空比逆时针(CCW)转动;
  5. 两电机以100% PWM 占空比逆时针(CCW)转动;
  6. 两电机制动(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%CW3 s
3禁用电机3 s
4使能电机3 s
5转动100%CW3 s
6制动3 s
7转动50%CCW3 s
8转动100%CCW3 s
9制动3 s

方向切换通过dir *= -1实现(main.c),而方向语义由motor_setpwm_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)负责三板斧:

  1. PWM 设备初始化:调用pwm_init(params->pwm_dev, params->pwm_mode, params->pwm_frequency, params->pwm_resolution),若返回 0 则判定失败并返回-EINVAL
  2. GPIO 初始化:对每个电机的gpio_dir0gpio_dir1(或gpio_brake)与gpio_enable引脚逐个执行gpio_init(..., GPIO_OUT),任一失败即返回-EIO,且该电机视为未配置成功。初始化前会先通过gpio_is_valid()判断引脚是否有效(GPIO_UNDEF视为无效),因此未使用的引脚可以安全地保持未定义;
  3. 保存参数指针:将params存入motor_driver->params

初始化过程中pwm_init返回的是实际取得的 PWM 分辨率值,测试程序中将其取出用作占空比计算基准,正是利用了这一点。

3.2 三种驱动模式

驱动支持三种硬件接线模式,定义于 drivers/include/motor_driver.h:

模式取值说明
MOTOR_DRIVER_2_DIRS0两个方向 GPIO,可处理制动(H 桥典型接法)
MOTOR_DRIVER_1_DIR1单个方向 GPIO,无制动能力(默认模式)
MOTOR_DRIVER_1_DIR_BRAKE2单个方向 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,完整实现「符号决定方向、绝对值决定速度」的语义:

  1. ID 校验motor_id >= nb_motors时返回-EINVAL
  2. 方向解析pwm_duty_cycle < 0判为MOTOR_CCW,否则为MOTOR_CW,再与gpio_dir_reverse异或以适配电机实际接线方向(L129-L130);若异或后方向值不合法则强制将占空比清零;
  3. 取绝对值pwm_duty_cycle_abs = |pwm_duty_cycle|,作为实际写入 PWM 的占空比;
  4. 临界区保护irq_disable()/irq_restore()包裹方向引脚与 PWM 的写入序列,保证「换向 + 调速」原子完成,避免电机短暂误动作;
  5. 按模式写方向:2 方向模式调用_motor_set_two_dirsgpio_dir0写方向值、gpio_dir1写方向值取反),1 方向模式调用_motor_set_one_dir
  6. 写 PWMpwm_set(params->pwm_dev, motor->pwm_channel, (uint16_t)pwm_duty_cycle_abs)
  7. 解除制动:仅MOTOR_DRIVER_1_DIR_BRAKE模式需要,根据brake_inverted决定制动引脚电平;
  8. 后置回调:若配置了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_enableGPIO_UNDEF,则打印 WARNING 并跳过操作——这正是测试循环中「Disable/Enable motors」环节的底层行为。


四、配置参数详解

4.1 默认参数表

驱动的默认参数定义在 drivers/motor_driver/include/motor_driver_params.h,均为可被应用或板级配置覆盖的宏:

默认值说明
MOTOR_DRIVER_PARAM_MODEMOTOR_DRIVER_1_DIR驱动模式
MOTOR_DRIVER_PARAM_BRAKE_INVERTEDfalse制动引脚电平是否反相
MOTOR_DRIVER_PARAM_ENABLE_INVERTEDfalse使能引脚电平是否反相
MOTOR_DRIVER_PARAM_PWM0PWM 设备号
MOTOR_DRIVER_PARAM_PWM_MODEPWM_LEFTPWM 对齐模式
MOTOR_DRIVER_PARAM_PWM_FREQUENCY20000UPWM 频率(20 kHz,高于人耳听阈)
MOTOR_DRIVER_PARAM_PWM_RESOLUTION100UPWM 分辨率(即 100 级占空比)
MOTOR_DRIVER_PARAM_NB_MOTORS2U电机数量
MOTOR_DRIVER_PARAM_MOTOR_SET_POST_CALLBACKNULLmotor_set后置回调
MOTOR_DRIVER_PARAM_MOTOR1_PWM_CHANNEL1U电机 1 的 PWM 通道
MOTOR_DRIVER_PARAM_MOTOR2_PWM_CHANNEL2U电机 2 的 PWM 通道
MOTOR_DRIVER_PARAM_MOTOR{1,2}_GPIO_ENABLEGPIO_UNDEF使能引脚(未定义则跳过)
MOTOR_DRIVER_PARAM_MOTOR{1,2}_GPIO_DIR0GPIO_UNDEF方向引脚
MOTOR_DRIVER_PARAM_MOTOR{1,2}_GPIO_DIR1_OR_BRAKEGPIO_UNDEF第二方向或制动引脚
MOTOR_DRIVER_PARAM_MOTOR{1,2}_GPIO_DIR_REVERSE0方向取反标志

这些宏最终聚合为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_channelgpio_enablegpio_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/1PWM_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_DIRSMOTOR_DRIVER_1_DIR_BRAKE模式。


六、测试应用设计要点总结

  1. API 覆盖完整:测试循环覆盖了motor_driver_initmotor_set(正负占空比、50%/100% 两档)、motor_brakemotor_enable/motor_disable全部公开 API;
  2. 方向语义验证:通过带符号占空比同时验证速度与方向,负值 → CCW、正值 → CW;
  3. 时序可观察:3 秒间隔使每个阶段都能被肉眼与串口日志清晰分辨;
  4. 回调解耦motor_set_post_cb回调机制让上层应用可以在每次调速后执行自定义逻辑,测试程序用其打印调试信息,是驱动扩展性的直观体现。

通过本测试应用,开发者可以在实际 H 桥 + 直流电机硬件上快速验证电机接线、PWM 通道配置与驱动逻辑的正确性,是 RIOT 下进行机器人底盘、风扇等直流电机控制的实用起点。

【免费下载链接】RIOTRIOT - The friendly OS for IoT项目地址: https://gitcode.com/GitHub_Trending/riot/RIOT

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询