STM32编程方法:通过函数指针来实现PWM电机速度设置,嵌入式。

2025博客之星年度评选已开启 10w+人浏览 1.1k人参与

例子 void (*set_speed_left)(int16_t speed); 它完美体现了硬件抽象层的思想。让我为你详细拆解,并用控制小车上两个不同电机的例子,一步一步解释。

一 这个例子是干什么的?

void (*set_speed_left)(int16_t speed);
这行代码的意思是:声明了一个名为 set_speed_left 的“遥控器”。这个遥控器专门用来“遥控”一个功能,这个功能要接收一个 int16_t 类型的速度值,并且不返回任何东西 (void)

它的核心目的是:将 “设置左轮速度”这个操作,与具体如何实现这个操作的代码分离开

二 典型应用场景:控制两个不同的电机

假设你的STM32小车有两个轮子,但左边电机由定时器1的PWM通道1驱动,右边电机由定时器2的PWM通道3驱动。如果不使用函数指针,你的代码可能是这样的:

// 传统方式,代码与硬件高度绑定
void SetMotorLeft_Speed(int16_t speed) {
    // 一大堆设置TIM1->CCR1寄存器的代码...
}
void SetMotorRight_Speed(int16_t speed) {
    // 一大堆设置TIM2->CCR3寄存器的代码...
}
void Car_SetSpeed(int16_t left, int16_t right) {
    SetMotorLeft_Speed(left);  // 这里固定死了必须用左电机函数
    SetMotorRight_Speed(right); // 这里固定死了必须用右电机函数
}

这种方式的问题在于,Car_SetSpeed 这个上层控制函数必须知道下面有两个具体函数,并且知道谁管左、谁管右。如果电机换了或者驱动方式变了,上层代码也得跟着改。

使用函数指针后,代码会变得非常灵活和清晰:

三 完整代码示例:用函数指针抽象电机控制

/* 步骤1: 定义统一的“遥控器”类型 */
// 定义一种函数指针类型,它代表“设置速度”这个操作
typedef void (*MotorSpeedSetter_t)(int16_t speed);

/* 步骤2: 实现具体的底层驱动函数(准备可以被遥控的“设备”) */
// 具体控制左电机的函数
void PWM_Set_TIM1_CCR1(int16_t speed) {
    if(speed >= 0) {
        TIM1->CCR1 = speed; // 正转,设置PWM占空比
    } else {
        TIM1->CCR1 = -speed; // 反转
    }
    // 可能还有设置方向引脚电平的代码...
}

// 具体控制右电机的函数
void PWM_Set_TIM2_CCR3(int16_t speed) {
    // 这是另一个定时器的另一个通道,硬件操作完全不同
    if(speed >= 0) {
        TIM2->CCR3 = speed;
    } else {
        TIM2->CCR3 = -speed;
    }
    // 设置方向引脚...
}

/* 步骤3: 创建“遥控器”并绑定“设备” */
// 声明两个“速度设置遥控器”
MotorSpeedSetter_t set_left_motor_speed = NULL;
MotorSpeedSetter_t set_right_motor_speed = NULL;

// 初始化函数,在这里把“遥控器”绑定到具体的“设备”(函数)
void Motor_Driver_Init(void) {
    set_left_motor_speed = PWM_Set_TIM1_CCR1;  // 遥控器1绑定左电机驱动
    set_right_motor_speed = PWM_Set_TIM2_CCR3; // 遥控器2绑定右电机驱动
}

/* 步骤4: 上层应用代码(使用“遥控器”来统一控制) */
// 小车控制模块。它完全不知道下面具体是怎么控制电机的!
void Car_SetSpeed(int16_t left_speed, int16_t right_speed) {
    // 关键:这里只是按下“遥控器”按钮,而不管遥控的是谁
    if(set_left_motor_speed != NULL) {
        set_left_motor_speed(left_speed); // 遥控左电机
    }
    if(set_right_motor_speed != NULL) {
        set_right_motor_speed(right_speed); // 遥控右电机
    }
}

// 更高级的控制逻辑:让小车原地旋转
void Car_Spin(int16_t speed) {
    Car_SetSpeed(speed, -speed); // 左轮正转,右轮反转
}

int main(void) {
    // 初始化硬件和驱动
    HAL_Init();
    MX_TIM1_Init(); // 初始化定时器1
    MX_TIM2_Init(); // 初始化定时器2
    Motor_Driver_Init(); // 关键:绑定函数指针!
    
    while(1) {
        // 示例:让小车前进、旋转、后退
        Car_SetSpeed(300, 300); // 前进
        HAL_Delay(1000);
        Car_Spin(200);          // 原地旋转
        HAL_Delay(1000);
        Car_SetSpeed(-200, -200); // 后退
        HAL_Delay(1000);
        Car_SetSpeed(0, 0);     // 停止
        HAL_Delay(1000);
    }
}

四 这样做带来的巨大好处

  1. 上层代码(Car_SetSpeed)变得极其干净:它只知道要“设置速度”,完全不知道速度是通过PWM、DAC还是直接给IO口发脉冲实现的。这实现了硬件隔离

  2. 更换硬件极其方便:如果明天左电机坏了,你换了一个需要不同驱动方式的新电机,你只需要做一件事

    // 实现新的驱动函数
    void NewMotor_Driver_Left(int16_t speed) {
        // 新电机的驱动代码...
    }
    // 然后在初始化时重新绑定
    void Motor_Driver_Init(void) {
        set_left_motor_speed = NewMotor_Driver_Left; // 只改这一行!
        set_right_motor_speed = PWM_Set_TIM2_CCR3;
    }

    所有上层代码(Car_SetSpeedCar_Spin 等)一行都不用改!

  3. 便于测试和模拟:在电脑上仿真时,你可以绑定一个“模拟电机”函数,这个函数只是把速度值打印到屏幕上,而不需要真实的硬件。

五 使用中的关键点(针对这个例子)

  1. 为什么用 int16_t
    因为电机速度通常有正负(表示方向)。int16_t(16位有符号整数)的范围是 -32768 到 32767,足够表示速度值,且能明确表达方向。

  2. 调用前的安全检查

    if(set_left_motor_speed != NULL) {
        set_left_motor_speed(100);
    }

    这个检查至关重要。如果指针是 NULL(比如你忘了调用 Motor_Driver_Init),程序会立即崩溃。

  3. 实际工程中的优化
    实际项目中,我们通常会把电机相关的所有操作封装成一个结构体

    typedef struct {
        MotorSpeedSetter_t set_speed;
        void (*brake)(void);     // 刹车函数指针
        void (*coast)(void);     // 滑行函数指针
        int16_t current_speed;   // 当前速度
    } MotorController_t;
    
    MotorController_t left_motor, right_motor;
    // 这样管理起来更系统化

六 总结:例子的精髓

void (*set_speed_left)(int16_t speed); 这个声明,实际上是在建立一个契约接口

  • 契约内容:“我承诺会提供一个方法,你给它一个速度值,它就能让左轮转起来。”

  • 具体如何实现:“可能是PWM,可能是模拟电压,也可能只是打印一条日志——这是我的自由,你(上层调用者)不用管。”

这就是面向接口编程的思想在C语言中的体现,也是写出高质量、可维护的嵌入式代码的关键技巧之一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值