一、前言
电机控制是嵌入式系统中最经典也最实用的课题之一。无论是智能小车、机械臂还是各类自动化设备,都离不开对电机的精确控制。本文基于STC89C52单片机(51系列),结合实际的代码和硬件电路,详细讲解三种最常用的电机------直流电机 、舵机(SG90) 和步进电机(28BYJ-48) 的控制原理与实现方法。
代码中涉及的模块包括:定时器中断、PWM(脉宽调制)、按键扫描、OLED显示等。我会在讲解电机控制的同时,把这些配套模块的原理也一并说清楚。
二、预备知识:PWM(脉宽调制)
在深入三种电机之前,必须先理解一个核心概念------PWM(Pulse Width Modulation,脉宽调制) 。
PWM是一种利用数字信号输出来控制模拟电路的技术。简单来说,就是在一个固定周期(频率)内,通过改变高电平持续的时间(占空比)来等效地调节输出电压。

上图很好地解释了PWM的核心思想:
-
周期(频率) :一个完整脉冲的时间长度。
-
占空比:高电平时间占整个周期的比例。
| 占空比 | 效果 |
|---|---|
| 10% | 大部分时间输出低电平,等效电压低 |
| 50% | 高低电平各占一半,等效电压居中 |
| 90% | 大部分时间输出高电平,等效电压高 |
对于直流电机,占空比越高,电机两端的平均电压越高,转速就越快。这就是PWM调速的根本原理。
标准51单片机(如STC89C52)本身没有硬件PWM模块,因此需要用定时器中断来软件模拟PWM信号。
定时器初始化代码分析
cpp
void Timer0_Init(void)
{
TMOD &= 0xF0; // 低四位清0,保留高四位
TMOD |= 0x01; // 设置定时器0为模式1(16位定时器)
TH0 = 0XDC; // 定时初值高8位
TL0 = 0X00; // 定时初值低8位 → 10ms中断一次
TF0 = 0; // 清除中断标志
ET0 = 1; // 使能定时器0中断
EA = 1; // 打开总中断
TR0 = 1; // 启动定时器0
}
这个初始化函数配置定时器0每10ms产生一次中断。在中断服务函数中,通过累加计数器pwm_count并对比level值,来决定PWM输出引脚的电平高低。
需要特别注意的是,51单片机定时器的工作频率是晶振频率的12分频 (12T模式)。使用11.0592MHz晶振时,定时器计数一次的时间为12/11.0592MHz ≈ 1.085μs。代码中TH0=0xDC, TL0=0x00对应的是10ms中断周期------这个值是经过精确计算的,不是随意填写的。
三、直流电机控制
3.1 直流电机的工作原理
直流电机是将电能转换为机械能的装置。其核心结构包括:
-
定子(永磁体):提供固定磁场
-
转子(线圈):在磁场中受力旋转
-
换向器:确保转子始终朝一个方向旋转
原理很简单:给电机两端施加电压,电机就转动;电压越高,转速越快;改变电压极性,电机反转。

3.2 为什么需要电机驱动?
很多人会问:为什么不能直接用单片机IO口驱动电机?
答案很简单:电流不够。
51单片机的IO口只能输出几毫安的电流,而直流电机工作时需要几百毫安甚至安培级的电流。直接连接会烧坏单片机IO口,电机也转不起来。
因此,我们需要电机驱动电路来做"电流放大"------用单片机的小信号去控制驱动芯片的大电流开关。
3.3 驱动方案:H桥电路
常用的直流电机驱动方案是H桥电路。如下图所示:

H桥由4个开关(可以是三极管、MOS管或集成芯片内部的开关管)组成,形状像字母"H"。电机连接在中间横杠上。
工作原理:
-
正转:Q1和Q4导通,电流从电源正极→Q1→电机→Q4→地
-
反转:Q2和Q3导通,电流从电源正极→Q2→电机→Q3→地
-
停止:所有开关断开
-
刹车:Q1和Q2同时导通(或Q3和Q4同时导通),电机两端短路,产生制动效果

这块驱动板就是基于H桥原理设计的,可以独立控制两路直流电机:
-
IN1、IN2 控制电机A
-
IN3、IN4 控制电机B
控制逻辑如下表:
| 电机 | 旋转方式 | IN1 | IN2 | 说明 |
|---|---|---|---|---|
| MOTOR-A | 正转(调速) | PWM | 0 | 占空比越高越快 |
| MOTOR-A | 反转(调速) | 0 | PWM | 占空比越高越快 |
| MOTOR-A | 停止 | 0 | 0 | 电机自由停止 |
| MOTOR-A | 刹车 | 1 | 1 | 电机快速制动 |
3.4 代码实现分析
cpp
sbit PWM_IO = P1^0; // PWM输出引脚,接驱动板的IN1
void Timer0_Routine(void) interrupt 1
{
TH0 = 0XDC;
TL0 = 0X00; // 10ms中断
pwm_count++;
pwm_count = pwm_count % 10; // 10次中断为一个PWM周期 → 周期100ms
if(pwm_count < level) // level范围0~10
{
PWM_IO = 1; // 高电平
}
else
{
PWM_IO = 0; // 低电平
}
}
代码解读:
-
每10ms进入一次中断,
pwm_count从0累加到9后归零 -
一个完整的PWM周期 = 10 × 10ms = 100ms(频率10Hz)
-
level的值(0~10)决定了高电平在10个周期中的占比,即占空比 -
例如
level=7,则每个周期中有7个10ms(70ms)输出高电平,3个10ms(30ms)输出低电平,占空比70%
按键调节逻辑:
cpp
if(1 == key_num) { level++; if(level > 10) level = 10; } // 加速
if(2 == key_num) { if(level > 0) level--; } // 减速
KEY1加速,KEY2减速,level值在0~10之间变化,对应占空比0%~100%。
工程提示:实际使用时,PWM频率不宜过低(否则电机抖动明显),也不宜过高(超出驱动芯片的开关响应能力)。一般直流电机PWM频率在1kHz~20kHz之间较为合适。代码中10Hz是演示用途,实际项目建议提高定时器中断频率(如将中断周期设为100μs,计数100次,周期10ms,频率100Hz)。
四、舵机(SG90)控制
4.1 什么是舵机?
舵机是一种位置伺服电机,它能精确地旋转到指定的角度并保持住。
SG90是最常见的微型舵机,具有以下特点:
-
工作电压:4.8V~6V
-
转动角度:0°~180°(或0°~360°,不同型号有差异)
-
重量仅9克
-
三根线:红色(电源+) 、棕色(电源-) 、橙色(信号线)
舵机的内部结构包括:一个直流电机、一组减速齿轮、一个电位器(位置反馈)和控制电路。其本质是一个闭环控制系统------控制电路不断比较目标角度(PWM信号)和实际角度(电位器反馈),驱动电机转动直到两者一致。
4.2 舵机的PWM控制信号
舵机的控制信号与直流电机有很大不同------它不控制转速,而是控制目标角度。
SG90舵机的控制信号标准如下:
| 高电平时间 | 对应角度 |
|---|---|
| 0.5ms | 0° |
| 1.0ms | 45° |
| 1.5ms | 90° |
| 2.0ms | 135° |
| 2.5ms | 180° |
关键点:
-
PWM周期固定为 20ms(50Hz)
-
通过改变高电平的宽度(0.5ms~2.5ms)来控制角度
-
舵机收到信号后会转到对应角度并锁定在该位置
4.3 代码实现分析
cpp
sbit SG_IO = P2^0; // 舵机信号线
void Timer0_Routine(void) interrupt 1
{
TH0 = 0XFE;
TL0 = 0X33; // 0.5ms中断一次(11.0592MHz @12T)
pwm_count++;
pwm_count = pwm_count % 40; // 40次中断 = 20ms周期
if(pwm_count <= level) // level范围0~4
{
SG_IO = 1; // 高电平
}
else
{
SG_IO = 0; // 低电平
}
}
代码解读:
定时器初值TH0=0xFE, TL0=0x33对应约0.5ms的中断周期。
-
40次中断 = 40 × 0.5ms = 20ms(一个完整的舵机控制周期)
-
level的值(0~4)决定了高电平持续的0.5ms个数 -
level=0→ 高电平0ms → 0° -
level=1→ 高电平0.5ms → 0° -
level=2→ 高电平1.0ms → 45° -
level=3→ 高电平1.5ms → 90° -
level=4→ 高电平2.0ms → 135°
按键控制逻辑与直流电机类似,但level范围被限制在0~4之间:
cpp
if(1 == key_num) { level++; if(level > 4) level = 4; }
if(2 == key_num) { if(level > 0) level--; }
工程提示:
-
舵机对电源质量要求较高,大电流瞬间可能导致电压跌落,建议单独供电或使用大电容滤波。
-
代码中的
level=0对应0°、level=4对应135°,如果需要完整的0°~180°范围,需要将level范围扩展到0~5(2.5ms对应180°),但需要确保中断周期精确为0.5ms。 -
舵机控制信号必须持续发送(每个20ms周期都要发送),否则舵机可能因信号丢失而抖动或复位。
五、步进电机(28BYJ-48)控制
5.1 什么是步进电机?
步进电机是一种将电脉冲信号转换为角位移的执行元件。与直流电机和舵机都不同:
-
每输入一个脉冲,转子转动一个固定的角度(步距角)
-
转动角度与脉冲数成正比
-
转速与脉冲频率成正比
-
可以精确控制位置,不需要反馈(开环控制)
28BYJ-48是学习中最常见的步进电机型号,型号含义:
-
28:电机直径28mm
-
B:步进电机
-
Y:永磁式
-
J:减速型(带减速箱)
-
48:四相八拍
5.2 内部结构与工作原理
28BYJ-48的内部结构如下:
-
转子(内圈):6个齿,每个齿是永磁体
-
定子(外圈):8个齿,每个齿上绕有线圈,正对的2个齿串联成一相
-
共四相(A、B、C、D),每相由正对的两个线圈组成
工作原理:依次给四相通电,产生的磁场会吸引转子的齿对齐,从而驱动转子一步一步转动。

5.3 驱动方案:ULN2003
与直流电机类似,步进电机也需要驱动芯片。28BYJ-48通常搭配ULN2003驱动模块。
ULN2003是一个达林顿管阵列,内部包含7个NPN达林顿管,可以放大单片机IO口的微弱信号来驱动步进电机线圈。
5.4 控制时序:八拍工作方式
28BYJ-48采用四相八拍的控制方式。所谓"八拍",就是8个不同的通电状态循环一次:
| 步序 | A相 | B相 | C相 | D相 | 对应代码case |
|---|---|---|---|---|---|
| 0 | 1 | 0 | 0 | 0 | case 0 |
| 1 | 1 | 1 | 0 | 0 | case 1 |
| 2 | 0 | 1 | 0 | 0 | case 2 |
| 3 | 0 | 1 | 1 | 0 | case 3 |
| 4 | 0 | 0 | 1 | 0 | case 4 |
| 5 | 0 | 0 | 1 | 1 | case 5 |
| 6 | 0 | 0 | 0 | 1 | case 6 |
| 7 | 1 | 0 | 0 | 1 | case 7 |
这种"单相-双相-单相-双相......"的交替通电方式,相比四拍(单相四拍或双相四拍)具有步距角更小、运行更平稳的优点。
5.5 代码实现分析
引脚定义:
cpp
sbit IN1_A = P2^0; // A相
sbit IN2_B = P2^1; // B相
sbit IN3_C = P2^2; // C相
sbit IN4_D = P2^3; // D相
步进电机控制函数:
cpp
void step_motor_conctrl(unsigned char step, unsigned char dir)
{
unsigned char temp = step;
if(dir == 0) // 反向旋转
temp = 7 - step; // 时序反序
switch(temp) // 8个节拍
{
case 0: IN1_A=1; IN2_B=0; IN3_C=0; IN4_D=0; break;
case 1: IN1_A=1; IN2_B=1; IN3_C=0; IN4_D=0; break;
case 2: IN1_A=0; IN2_B=1; IN3_C=0; IN4_D=0; break;
case 3: IN1_A=0; IN2_B=1; IN3_C=1; IN4_D=0; break;
case 4: IN1_A=0; IN2_B=0; IN3_C=1; IN4_D=0; break;
case 5: IN1_A=0; IN2_B=0; IN3_C=1; IN4_D=1; break;
case 6: IN1_A=0; IN2_B=0; IN3_C=0; IN4_D=1; break;
case 7: IN1_A=1; IN2_B=0; IN3_C=0; IN4_D=1; break;
default: IN1_A=0; IN2_B=0; IN3_C=0; IN4_D=0; break;
}
}
主循环:
cpp
while(1)
{
key_num = Key_Scan();
if(1 == key_num) { dir = !dir; } // KEY1切换方向
if(2 == key_num) { speed = speed%5 + 1; } // KEY2切换速度档位
step_motor_conctrl(step++, dir); // 走一步
step = step % 8; // 0~7循环
Delay_xms(6 - speed); // 延时控制速度
}
代码解读:
-
step从0递增到7再归零,对应八拍的循环 -
dir=0时,temp=7-step,时序反向,电机反转 -
step_motor_conctrl()每次调用只走一步(一个节拍) -
Delay_xms(6-speed)控制每步之间的间隔时间:-
speed=1→ 延时5ms → 最快 -
speed=5→ 延时1ms → 最慢 -
注意:延时值越小,步进电机转动越快
-
工程提示:
28BYJ-48内部有减速齿轮 ,实际输出轴转一圈需要转子转动约64圈。这意味着步距角经过减速后变得更小,精度更高,但转速也相应降低。
步进电机的转速不能太快,否则会因力矩不足而"失步"(转子跟不上脉冲频率)。
如果发现电机只振动不转动,通常是相序不对 或驱动电压不足。
步进电机在停止时不需要持续通电来保持位置(与舵机不同),断电后仍有一定的保持力矩。
六、硬件连接总览
根据代码和图片,各模块的引脚连接如下:
| 模块 | 引脚 | 说明 |
|---|---|---|
| 直流电机驱动 | P1^0 (PWM_IO) | 接驱动板IN1,PWM调速信号 |
| 舵机 SG90 | P2^0 (SG_IO) | 接舵机橙色信号线 |
| 步进电机驱动 | P2^0~P2^3 | 接ULN2003的IN1~IN4 |
| KEY1 | P3^2 | 功能切换/加速/正转 |
| KEY2 | P3^3 | 减速/反转 |
| OLED I2C | P1^4(SDA), P1^5(SCL) | 显示状态信息 |
注意 :直流电机和步进电机的引脚有复用(都使用了P2^0),实际使用时需要根据当前测试的电机类型选择对应的代码版本,不能同时运行。
七、总结
通过本文的分析,我们可以总结三种电机的核心区别:
| 特性 | 直流电机 | 舵机(SG90) | 步进电机(28BYJ-48) |
|---|---|---|---|
| 控制方式 | PWM占空比 | PWM脉宽 | 脉冲时序 |
| 控制目标 | 转速 | 角度 | 角度/位置 |
| 反馈方式 | 开环(无反馈) | 闭环(内部电位器) | 开环(无反馈) |
| 持续转动 | 可以 | 不可以(限位) | 可以 |
| 保持力矩 | 无(断电自由) | 有(通电锁定) | 有(断电保持) |
| 驱动芯片 | L298N/H桥 | 无需(直接驱动) | ULN2003 |
| 典型应用 | 风扇、车轮 | 舵机、云台 | 3D打印机、CNC |
掌握这三种电机的控制原理,就掌握了绝大多数嵌入式电机控制场景的核心技术。希望这篇文章能帮助你理解代码背后的硬件原理,不仅"知其然",更"知其所以然"。
文中所用代码均来自实际可运行的项目,图片为实验过程中的实物拍摄和原理图截图。如有疑问,欢迎在评论区交流讨论。