
我的小车采用的驱动电机是普通的玩具小马达,价格是真的便宜啊。无法想象厂家是怎么做到这么低的价格的。这种小马达非常适合做实验项目,因为你要不断实验各种情况,积累数据,这其中会有消耗。用这种小东西买一堆也不心疼。我买了10几个,做个实验台,把每个电机跑一遍,因为太便宜确实没法保障每个都良好运行。

这种电机驱动板很适合这种小马,马达精度就不高,输出精度也不要求太高。
控制驱动板用hi3863上的PWM脉冲控制。
马达驱动接收的是 20KHz - 40KHz 频率的脉冲信号。
// 获取PWM工作频率。
uint32_t uapi_pwm_get_frequency(uint8_t channel);
通过这个函数获取端口的脉冲时钟,获得的结果是 40MHz 拆开就是 40 * 1000 * 1000 Hz
通过计算倍频,得到我们想要输出的频率。
10KHz = 40MHz / 4 * 1000
20KHz = 40MHz / 2 * 1000
30KHz = 40MHz / 1333
40KHz = 40MHz / 1 * 1000
50KHz = 40MHz / 800
我通过试验,最后觉得 40KHz对小车的控制比较清晰。
然后设定频率参数
static uint32_t pwm_time = 1000; // 脉冲 周期 时间
static uint32_t lef_time = 500; // 左 高频 时间
static uint32_t rig_time = 500; // 右 高频 时间
这种小车马达精度一般,很难有2个一样的马达,所以为了不跑偏,需要单独对2个马达设置工作频率。
驱动2个小马达,需要4个脉冲信号。

先声明 4 组 PWM 参数。
static pwm_config_t LA_PWM_Config = {0, 0, 0, 0, true}; // 左 a 脉冲 设置
static pwm_config_t LB_PWM_Config = {0, 0, 0, 0, true}; // 左 b 脉冲 设置
static pwm_config_t RA_PWM_Config = {0, 0, 0, 0, true}; // 右 a 脉冲 设置
static pwm_config_t RB_PWM_Config = {0, 0, 0, 0, true}; // 右 b 脉冲 设置
先看一下参数的含
// PWM配置类型定义。
typedef struct pwm_config {
uint32_t low_time; PWM工作时钟周期计数个数低电平部分
uint32_t high_time; PWM工作时钟周期计数个数高电平部分
uint32_t offset_time; PWM相位
uint16_t cycles; PWM重复周期,范围:0~32767 (15bit)
bool repeat; 指示PWM应连续输出的标志
} pwm_config_t;
参数定义不仅包含了,高频和低频,还可以设置周期,设定脉冲输出次数。
对于高精度脉冲控制,这个功能非常有用,大大简化了控制复杂度。
因为我的小车根据运行状况决定,不需要设置输出周期,设置一直输出。
然后对PWM脉冲做个初始设置
cpp
// 马达 脉冲 初始化
void Car_Init(void)
{
uapi_pin_set_mode(LA_PWM_PIN, PIN_MODE_1);
uapi_pin_set_mode(LB_PWM_PIN, PIN_MODE_1);
uapi_pin_set_mode(RA_PWM_PIN, PIN_MODE_1);
uapi_pin_set_mode(RB_PWM_PIN, PIN_MODE_1);
uapi_pwm_deinit();
uapi_pwm_init();
uapi_pwm_open(LA_PWM_CHANNEL, &LA_PWM_Config);
uapi_pwm_open(LB_PWM_CHANNEL, &LB_PWM_Config);
uapi_pwm_open(RA_PWM_CHANNEL, &RA_PWM_Config);
uapi_pwm_open(RB_PWM_CHANNEL, &RB_PWM_Config);
uint8_t channel_id[4] = { LA_PWM_CHANNEL, LB_PWM_CHANNEL, RA_PWM_CHANNEL, RB_PWM_CHANNEL };
uapi_pwm_set_group(MOTOR_PWM_GROUP, channel_id, 4);
osal_printk(" [Car_Init] \r\n");
}
uint8_t channel_id[4] = { LA_PWM_CHANNEL, LB_PWM_CHANNEL, RA_PWM_CHANNEL, RB_PWM_CHANNEL };
uapi_pwm_set_group(MOTOR_PWM_GROUP, channel_id, 4);
这2行代码,我将所有的脉冲控制放到一个数组里,然后声明在1个组内运行。
这跟以往的一个端口一个端口的输出控制不一样,比如我做一个机械臂的控制,每时每刻各个关节同步协调一致,如果我单独控制,那样代码非常复杂,控制时间也非常难。
现在我只需要写一个算法,一堆三角函数,结果写入参数,最后扔到运行里就行了,大大简单了,而且准确性也提高了。
高级! 点赞!
剩下的就简单了,如何控制2个电机正转、反转、转速调节......
每次改变小车状态,先停止脉冲输出,直接改参数值,然后继续直接运行就行了。好简单啊。
就不过多赘述了,放上代码。
motor.c
cpp
/**
*
* 马达驱动板
*
* 2025 12 20
*
* motor.c
*
**/
#include "common_def.h"
#include "pinctrl.h"
#include "pwm.h"
#include "tcxo.h"
#include "soc_osal.h"
#include "app_init.h"
#include "pwm_porting.h"
#include "motor.h"
// 40 * 1000 * 1000 / 2 * 1000 = 20 KHz
static uint32_t pwm_time = 1000; // 脉冲 周期 时间
static uint32_t lef_time = 500; // 左 高频 时间
static uint32_t rig_time = 500; // 右 高频 时间
static pwm_config_t LA_PWM_Config = {0, 0, 0, 0, true}; // 左 a 脉冲 设置
static pwm_config_t LB_PWM_Config = {0, 0, 0, 0, true}; // 左 b 脉冲 设置
static pwm_config_t RA_PWM_Config = {0, 0, 0, 0, true}; // 右 a 脉冲 设置
static pwm_config_t RB_PWM_Config = {0, 0, 0, 0, true}; // 右 b 脉冲 设置
static uint8_t pwm_stat = 0; // 脉冲 状态
// 马达 脉冲 初始化
void Motor_Init(void)
{
uapi_pin_set_mode(LA_PWM_PIN, PIN_MODE_1);
uapi_pin_set_mode(LB_PWM_PIN, PIN_MODE_1);
uapi_pin_set_mode(RA_PWM_PIN, PIN_MODE_1);
uapi_pin_set_mode(RB_PWM_PIN, PIN_MODE_1);
uapi_pwm_deinit();
uapi_pwm_init();
uapi_pwm_open(LA_PWM_CHANNEL, &LA_PWM_Config);
uapi_pwm_open(LB_PWM_CHANNEL, &LB_PWM_Config);
uapi_pwm_open(RA_PWM_CHANNEL, &RA_PWM_Config);
uapi_pwm_open(RB_PWM_CHANNEL, &RB_PWM_Config);
uint8_t channel_id[4] = { LA_PWM_CHANNEL, LB_PWM_CHANNEL, RA_PWM_CHANNEL, RB_PWM_CHANNEL };
uapi_pwm_set_group(MOTOR_PWM_GROUP, channel_id, 4);
osal_printk(" [Motor_Init] \r\n");
}
// 马达 脉冲 注销
void Motor_PWM_Deinit(void)
{
uapi_pwm_close(LA_PWM_CHANNEL);
uapi_pwm_close(LB_PWM_CHANNEL);
uapi_pwm_close(RA_PWM_CHANNEL);
uapi_pwm_close(RB_PWM_CHANNEL);
uapi_pwm_deinit();
uapi_pwm_stop_group(MOTOR_PWM_GROUP);
osal_printk(" [Motor_PWM_Deinit] \r\n");
}
// 马达 脉冲 停止
void Motor_PWM_Stop(void)
{
if(pwm_stat == 0)
{
return;
}
uapi_pwm_close(LA_PWM_CHANNEL);
uapi_pwm_close(LB_PWM_CHANNEL);
uapi_pwm_close(RA_PWM_CHANNEL);
uapi_pwm_close(RB_PWM_CHANNEL);
uapi_pwm_deinit();
pwm_stat = 0;
osal_printk(" [Motor_PWM_Stop] \r\n");
}
// 马达 脉冲 启动
void Motor_PWM_Start(uint32_t la_time, uint32_t lb_time, uint32_t ra_time, uint32_t rb_time)
{
if(pwm_stat == 1)
{
Motor_PWM_Stop();
}
uapi_pwm_deinit();
uapi_pwm_init();
LA_PWM_Config.low_time = pwm_time - la_time;
LA_PWM_Config.high_time = la_time;
LB_PWM_Config.low_time = pwm_time - lb_time;
LB_PWM_Config.high_time = lb_time;
RA_PWM_Config.low_time = pwm_time - ra_time;
RA_PWM_Config.high_time = ra_time;
RB_PWM_Config.low_time = pwm_time - rb_time;
RB_PWM_Config.high_time = rb_time;
uapi_pwm_open(LA_PWM_CHANNEL, &LA_PWM_Config);
uapi_pwm_open(LB_PWM_CHANNEL, &LB_PWM_Config);
uapi_pwm_open(RA_PWM_CHANNEL, &RA_PWM_Config);
uapi_pwm_open(RB_PWM_CHANNEL, &RB_PWM_Config);
osal_printk(" la_time = %ld, lb_time = %ld \r\n", la_time, lb_time );
osal_printk(" ra_time = %ld, rb_time = %ld \r\n", ra_time, rb_time );
uapi_pwm_start_group(MOTOR_PWM_GROUP);
pwm_stat = 1;
osal_printk(" [Motor_PWM_Start] \r\n");
}
// 小车 空挡
void Car_Neutral(void)
{
Motor_PWM_Start(0, 0, 0, 0);
osal_printk(" [Car_Neutral] \r\n");
}
// 小车 抱死
void Car_Brake(void)
{
Motor_PWM_Start(pwm_time, pwm_time, pwm_time, pwm_time);
osal_printk(" [Car_Brake] \r\n");
}
// 小车 前进
void Car_Forward(void)
{
Motor_PWM_Start(0, lef_time, 0, rig_time);
osal_printk(" [Car_Forward] \r\n");
}
// 小车 后退
void Car_Backward(void)
{
Motor_PWM_Start(lef_time, 0, rig_time, 0);
osal_printk(" [Car_Backward] \r\n");
}
// 小车 左转
void Car_Left_Turn(void)
{
Motor_PWM_Start(lef_time, 0, 0, rig_time);
osal_printk(" [Car_Left_Turn] \r\n");
}
// 小车 右转
void Car_Right_Turn(void)
{
Motor_PWM_Start(0, lef_time, rig_time, 0);
osal_printk(" [Car_Right_Turn] \r\n");
}
// 设置 左 时间
void Set_Left_Time(uint32_t t)
{
if(t >= 0 && t <= pwm_time)
{
lef_time = t;
}
osal_printk(" [Set_Left_Time] : %ld \r\n", lef_time);
}
// 设置 右 时间
void Set_Right_Time(uint32_t t)
{
if(t >= 0 && t <= pwm_time)
{
rig_time = t;
}
osal_printk(" [Set_Right_Time] : %ld \r\n", rig_time);
}
// 设置 脉冲 时间
void Set_PWM_Time(uint32_t t)
{
pwm_time = t;
osal_printk(" [Set_PWM_Time] : %ld \r\n", pwm_time);
}
// 读取 脉冲 时间
uint32_t Get_PWM_Time(void)
{
return pwm_time;
osal_printk(" [Get_PWM_Time] : %ld \r\n", pwm_time);
}
// 读取 左 时间
uint32_t Get_Left_Time(void)
{
return lef_time;
osal_printk(" [Get_Left_Time] : %ld \r\n", lef_time);
}
// 读取 右 时间
uint32_t Get_Right_Time(void)
{
return rig_time;
osal_printk(" [Get_Right_Time] : %ld \r\n", rig_time);
}
motor.h
cpp
/**
*
* 马达驱动板
*
* 2025 12 21
*
* motor.h
*
**/
#ifndef __MOTOR_H__
#define __MOTOR_H__
#define LA_PWM_PIN GPIO_08
#define LB_PWM_PIN GPIO_09
#define LA_PWM_CHANNEL PWM_0
#define LB_PWM_CHANNEL PWM_1
#define RA_PWM_PIN GPIO_10
#define RB_PWM_PIN GPIO_11
#define RA_PWM_CHANNEL PWM_2
#define RB_PWM_CHANNEL PWM_3
#define MOTOR_PWM_GROUP 0
// 马达 脉冲 初始化
void Motor_Init(void)
// 马达 脉冲 注销
void Motor_PWM_Deinit(void);
// 马达 脉冲 停止
void Motor_PWM_Stop(void);
// 马达 脉冲 启动
void Motor_PWM_Start(uint32_t la_time, uint32_t lb_time, uint32_t ra_time, uint32_t rb_time);
// 小车 空挡
void Car_Neutral(void);
// 小车 抱死
void Car_Brake(void);
// 小车 前进
void Car_Forward(void);
// 小车 后退
void Car_Backward(void);
// 小车 左转
void Car_Left_Turn(void);
// 小车 右转
void Car_Right_Turn(void);
// 设置 左 时间
void Set_Left_Time(uint32_t t);
// 设置 右 时间
void Set_Right_Time(uint32_t t);
// 设置 脉冲 时间
void Set_PWM_Time(uint32_t t);
// 读取 脉冲 时间
uint32_t Get_PWM_Time(void);
// 读取 左 时间
uint32_t Get_Left_Time(void);
// 读取 右 时间
uint32_t Get_Right_Time(void);
#endif
我觉得我的代码移植性还是可以的,每个都是完全独立的功能。
所有必要的参数声明都放在.h文件里,重用的时候,只需要将这2个文件放到新代码里,如果修改参数就修改.h文件里的内容,比如改个端口。都是最基本功能,如果需要更复杂的操作,建个新文件,引入一下,然后套上新功能就行了。
OK,下一步写一个 指南针模块。