hi3863(ws63)智能小车 (三)PWM驱动马达

我的小车采用的驱动电机是普通的玩具小马达,价格是真的便宜啊。无法想象厂家是怎么做到这么低的价格的。这种小马达非常适合做实验项目,因为你要不断实验各种情况,积累数据,这其中会有消耗。用这种小东西买一堆也不心疼。我买了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,下一步写一个 指南针模块。

相关推荐
xingzhemengyou12 小时前
STM32 内存空间中的选项字节
stm32·单片机
v先v关v住v获v取3 小时前
椰子采摘机械臂设计cad9张+三维图+设计说明书
科技·单片机·51单片机
就是蠢啊4 小时前
51单片机——TFTLCD显示器(一)
嵌入式硬件·计算机外设·51单片机
qq_448011165 小时前
嵌入式中IO、GPIO、专用IO
单片机·嵌入式硬件
xingzhemengyou16 小时前
STM32 Boot0 与 Boot1 [当 Flash 被误操作锁死或 SWD/JTAG 无法连接时,很有用]
stm32·单片机·嵌入式硬件
恒锐丰小吕6 小时前
屹晶微 EG27710 600V耐压、高性能、快速开关的半桥驱动芯片技术解析
嵌入式硬件·性能优化·硬件工程
一路往蓝-Anbo6 小时前
【第05期】数据的微观世界 (五) —— 浮点数 vs 定点数:MCU的数学课
linux·stm32·单片机·嵌入式硬件·物联网
polarislove02146 小时前
9.2 自制延迟函数-嵌入式铁头山羊STM32笔记
笔记·stm32·嵌入式硬件
Cincoze-Johnny6 小时前
Windows系统-应用问题全面剖析Ⅳ:德承工控机DV-1000在Windows操作系统下[桌面图标消失]的解决方法
单片机·嵌入式硬件