1. 项目概述
在嵌入式硬件开发领域,电机控制是最基础也是最核心的应用之一。今天我要分享的是基于CW32L012/F030单片机的智能小车电机调速控制方案。这个项目看似简单,但其中涉及到的PWM(脉宽调制)技术却是嵌入式开发中的一项重要基本功。
我选择CW32系列单片机作为控制核心,主要考虑到它性价比高、外设丰富,特别适合初学者和中小型项目开发。在实际操作中,我发现很多新手虽然能让电机转起来,但往往卡在速度调节这个环节。传统的GPIO控制只能实现电机的启停和方向控制,无法实现速度的无级调节,这在智能小车这类需要精确控制的应用中显然不够用。
通过这个项目,我们将深入探讨如何利用CW32的定时器和PWM功能,实现对直流电机的精确调速。我会从硬件连接、寄存器配置到软件编程,一步步拆解整个实现过程。在这个过程中,你不仅能学会PWM的基本原理,还能掌握嵌入式开发中常见的外设配置技巧。
2. PWM技术基础解析
2.1 PWM工作原理
PWM(Pulse Width Modulation)即脉宽调制,是一种通过调节脉冲宽度来控制模拟信号电平的数字技术。它的核心思想是利用数字信号(只有高电平和低电平)来模拟连续变化的模拟信号。
想象一下水龙头:如果你快速开关水龙头,通过调节每次打开的时间长短(脉冲宽度)和开关的频率,就能控制出水的平均流量。PWM控制电机速度的原理与此类似 - 我们通过调节高电平在整个周期中所占的比例(占空比)来控制电机的平均电压,从而实现调速。
2.2 关键参数解析
PWM有两个关键参数需要特别关注:
-
频率:指PWM信号每秒钟完成的周期数。对于电机控制,通常选择几千赫兹到几十千赫兹的频率范围。频率太低会导致电机运转不平稳(能听到明显的"嗡嗡"声),太高则可能超出电机驱动电路的响应能力。
-
占空比:指一个周期内高电平时间占总周期的比例。占空比越大,等效输出电压越高,电机转速越快。计算公式为:
code复制占空比 = (高电平时间 / 周期时间) × 100%
在实际应用中,我们通常将占空比量化为0-100%或者0-1000(对应0%-100%)的整数值,便于程序控制。
2.3 PWM在电机控制中的优势
相比传统的模拟电压控制,PWM调速有以下显著优势:
- 效率高:功率器件(如MOSFET)只在开关状态工作,几乎不消耗功率
- 控制精确:数字信号抗干扰能力强,调节精度高
- 发热小:避免了线性调节方式的大功率损耗
- 灵活性:同一硬件可通过软件实现多种控制策略
3. 硬件设计与连接
3.1 开发板选型与资源分析
我们使用的是基于CW32L012/F030的开发板,这款MCU具有以下关键特性:
- 32位ARM Cortex-M0+内核
- 工作频率最高64MHz
- 多达4个通用定时器(GTIM)
- 丰富的GPIO复用功能
- 内置PWM输出功能
根据开发板原理图,两个直流电机通过RZ7899电机驱动芯片与MCU连接:
- 左电机:PB3(方向控制)和PB4(PWM速度控制)
- 右电机:PB1(方向控制)和PB5(PWM速度控制)
3.2 电机驱动电路解析
RZ7899是一款双H桥电机驱动芯片,具有以下特点:
- 工作电压:2.7V-16V
- 峰值输出电流:1.5A
- 内置防反接和过流保护
- 支持PWM调速和方向控制
其典型连接方式如下:
code复制MCU PWM引脚 -> RZ7899 PWM输入
MCU GPIO引脚 -> RZ7899方向控制输入
RZ7899输出 -> 直流电机
这种设计将大电流的电机驱动与MCU隔离,既保护了MCU,又提供了足够的驱动能力。
3.3 引脚复用配置
CW32的GPIO引脚通常具有多种复用功能。要实现PWM输出,我们需要:
- 确认哪些定时器通道可以映射到目标引脚
- 配置引脚为复用功能模式
- 选择正确的复用功能选项
查阅数据手册可知:
- PB4可复用为GTIM1通道1(CH1)
- PB5可复用为GTIM1通道2(CH2)
这种灵活的映射关系让我们在PCB布局时有更多选择余地。
4. 软件实现详解
4.1 系统初始化流程
完整的电机控制初始化包括以下步骤:
- 开启相关外设时钟(GPIOB和GTIM1)
- 配置GPIO引脚为复用功能模式
- 初始化定时器基础参数
- 配置PWM输出模式
- 使能定时器
以下是关键代码解析:
c复制void Motor_Init(void)
{
// 1. 开启时钟
RCC_AHBPeriphClk_Enable(RCC_AHB_PERIPH_GPIOB, ENABLE);
RCC_APBPeriphClk_Enable1(RCC_APB1_PERIPH_GTIM1, ENABLE);
// 2. 配置引脚复用
PB04_AFx_GTIM1CH1(); // PB4作为GTIM1 CH1
PB05_AFx_GTIM1CH2(); // PB5作为GTIM1 CH2
// 3. GPIO初始化
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.IT = GPIO_IT_NONE;
GPIO_InitStructure.Mode = GPIO_MODE_OUTPUT_PP; // 推挽输出
GPIO_InitStructure.Pins = GPIO_PIN_4 | GPIO_PIN_5 | GPIO_PIN_3 | GPIO_PIN_1;
GPIO_InitStructure.Speed = GPIO_SPEED_HIGH;
GPIO_Init(CW_GPIOB, &GPIO_InitStructure);
// 4. 定时器基础配置
GTIM_InitTypeDef GTIM_InitStructure;
GTIM_InitStructure.Mode = GTIM_MODE_TIME; // 定时器模式
GTIM_InitStructure.OneShotMode = GTIM_COUNT_CONTINUE; // 连续计数
GTIM_InitStructure.Prescaler = GTIM_PRESCALER_DIV1024; // 预分频
GTIM_InitStructure.ReloadValue = 1000; // 自动重装载值
GTIM_InitStructure.ToggleOutState = DISABLE; // 关闭输出翻转
GTIM_TimeBaseInit(CW_GTIM1, >IM_InitStructure);
// 5. PWM输出配置
GTIM_OCInit(CW_GTIM1, GTIM_CHANNEL1, GTIM_OC_OUTPUT_PWM_HIGH);
GTIM_OCInit(CW_GTIM1, GTIM_CHANNEL2, GTIM_OC_OUTPUT_PWM_HIGH);
// 6. 初始化比较寄存器
CW_GTIM1->CCR1 = 0;
CW_GTIM1->CCR2 = 0;
// 7. 使能定时器
GTIM_Cmd(CW_GTIM1, ENABLE);
}
4.2 PWM参数计算
理解PWM参数的计算方法对调试至关重要。在我们的配置中:
- 系统时钟:64MHz
- 预分频值:1024
- 定时器时钟 = 系统时钟 / 预分频 = 64MHz / 1024 = 62.5kHz
- 重装载值:1000
- PWM频率 = 定时器时钟 / 重装载值 = 62.5kHz / 1000 = 62.5Hz
这个频率对于电机控制来说偏低,实际应用中建议提高频率到1kHz以上。可以通过减小预分频值或重装载值来实现:
例如,将预分频改为64:
- 定时器时钟 = 64MHz / 64 = 1MHz
- 重装载值保持1000
- 新PWM频率 = 1MHz / 1000 = 1kHz
4.3 电机控制函数实现
我们封装了一系列函数来简化电机控制:
c复制// 设置PWM占空比(0-1000对应0%-100%)
void GTIM1_SetCompare1(uint16_t value) {
GTIM_SetCompare1(CW_GTIM1, value);
}
// 左电机正转
void Motor_Left_Run(uint16_t speed) {
GTIM1_SetCompare1(speed); // 设置PWM占空比
PB03_SETHIGH(); // 设置方向
}
// 右电机反转
void Motor_Right_Retreat(uint16_t speed) {
GTIM1_SetCompare2(1000 - speed); // 注意反转时占空比取反
PB12_SETLOW();
}
// 小车前进
void Car_Run(uint16_t speed) {
Motor_Left_Run(speed);
Motor_Right_Run(speed);
}
注意:在实现反转控制时,我们使用了"1000 - speed"来计算占空比。这是因为在H桥驱动中,反转通常是通过交换电机两端电压极性实现的,PWM占空比也需要相应调整。
4.4 主程序逻辑
主程序通过按键调整电机速度,并在OLED上显示当前速度:
c复制int main(void)
{
OLED_Init();
LED_Init();
Key_Init();
Motor_Init();
int16_t keynum, speed = 0;
while(1) {
keynum = Key_Get(); // 获取按键值
if(keynum == 1) { // 加速
speed += 100;
if(speed > 1000) speed = 1000;
}
if(keynum == 2) { // 减速
speed -= 100;
if(speed < 0) speed = 0;
}
Motor_Left_Run(speed);
Motor_Right_Run(speed);
OLED_ShowString(1, 1, "speed=");
OLED_ShowNum(1, 7, speed, 4);
}
}
5. 调试技巧与常见问题
5.1 PWM输出验证
在连接电机前,建议先用示波器或逻辑分析仪验证PWM输出:
- 确保引脚配置正确
- 检查PWM频率是否符合预期
- 验证占空比是否随参数变化
如果没有测试仪器,可以用LED简单测试:PWM输出接LED,改变占空比应能看到亮度变化。
5.2 电机不转的排查步骤
如果电机不转,按照以下步骤排查:
- 检查电源:电机驱动和MCU供电是否正常
- 检查信号通路:用万用表测量PWM引脚是否有电压变化
- 检查方向控制:方向引脚电平是否正确
- 检查软件配置:定时器和PWM初始化代码是否正确
5.3 电机抖动或异响处理
这通常是PWM频率不合适导致的:
- 频率太低:尝试提高PWM频率(1kHz-20kHz)
- 占空比极端:避免使用接近0%或100%的占空比
- 电源不足:检查电源是否能提供足够电流
5.4 性能优化建议
- 提高PWM频率:一般直流电机控制在1kHz-20kHz为宜
- 加入加速减速曲线:避免速度突变导致电机失步
- 增加电流检测:防止电机堵转烧毁驱动
- 使用编码器反馈:实现闭环控制提高精度
6. 项目扩展与进阶
6.1 闭环速度控制
当前实现是开环控制,实际应用中建议加入编码器实现闭环控制:
- 安装编码器测量实际转速
- 使用PID算法调节PWM占空比
- 实现精确的速度控制
6.2 多电机同步控制
对于需要多电机协同的应用(如机械臂):
- 使用多个定时器通道控制不同电机
- 实现同步启动/停止
- 加入速度同步算法
6.3 无线遥控集成
通过蓝牙或2.4G模块实现无线控制:
- 添加无线通信模块
- 解析遥控指令
- 实现远程速度调节和方向控制
6.4 能量回收设计
在减速/刹车时实现能量回收:
- 检测电机反电动势
- 切换电路模式为发电状态
- 将能量回充到电源
7. 关键参数配置表
为方便参考,以下是PWM配置的关键参数总结:
| 参数 | 推荐值 | 说明 |
|---|---|---|
| 系统时钟 | 64MHz | CW32默认工作频率 |
| 预分频值 | 64-1024 | 根据所需PWM频率调整 |
| 重装载值 | 500-1000 | 影响PWM分辨率和频率 |
| PWM频率 | 1kHz-20kHz | 电机控制理想范围 |
| 占空比范围 | 10%-90% | 避免极端值提高寿命 |
8. 代码优化技巧
在实际开发中,我总结了以下优化经验:
-
使用硬件抽象层:将电机控制函数封装成独立模块,提高代码复用性
-
加入参数检查:在设置PWM占空比前检查范围,防止越界
-
优化中断处理:如果需要实时性,可以使用定时器中断更新PWM参数
-
实现软启动:开机时逐步增加占空比,减小冲击电流
c复制// 示例:带参数检查的PWM设置函数
void Safe_SetPWM(uint16_t value) {
if(value > 1000) value = 1000;
GTIM_SetCompare1(CW_GTIM1, value);
}
9. 实测效果与验证
完成代码编写后,烧录到开发板可以观察到:
- 按下加速键,电机转速逐步提高
- 按下减速键,电机转速逐步降低
- OLED实时显示当前速度值
- 电机运行平稳,无明显噪音
通过这个项目,我们不仅实现了电机调速的基本功能,更重要的是掌握了嵌入式开发中PWM的应用方法。这套方案稍作修改即可应用于其他需要模拟量输出的场景,如LED调光、舵机控制等。