51单片机电机控制全攻略:直流电机、舵机与步进电机

一、前言

电机控制是嵌入式系统中最经典也最实用的课题之一。无论是智能小车、机械臂还是各类自动化设备,都离不开对电机的精确控制。本文基于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
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--; }

工程提示

  1. 舵机对电源质量要求较高,大电流瞬间可能导致电压跌落,建议单独供电或使用大电容滤波。

  2. 代码中的level=0对应0°、level=4对应135°,如果需要完整的0°~180°范围,需要将level范围扩展到0~5(2.5ms对应180°),但需要确保中断周期精确为0.5ms。

  3. 舵机控制信号必须持续发送(每个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);             // 延时控制速度
}

代码解读

  1. step从0递增到7再归零,对应八拍的循环

  2. dir=0时,temp=7-step,时序反向,电机反转

  3. step_motor_conctrl()每次调用只走一步(一个节拍)

  4. Delay_xms(6-speed)控制每步之间的间隔时间:

    • speed=1 → 延时5ms → 最快

    • speed=5 → 延时1ms → 最慢

    • 注意:延时值越小,步进电机转动越快

工程提示

  1. 28BYJ-48内部有减速齿轮 ,实际输出轴转一圈需要转子转动约64圈。这意味着步距角经过减速后变得更小,精度更高,但转速也相应降低。

  2. 步进电机的转速不能太快,否则会因力矩不足而"失步"(转子跟不上脉冲频率)。

  3. 如果发现电机只振动不转动,通常是相序不对驱动电压不足

  4. 步进电机在停止时不需要持续通电来保持位置(与舵机不同),断电后仍有一定的保持力矩。

六、硬件连接总览

根据代码和图片,各模块的引脚连接如下:

模块 引脚 说明
直流电机驱动 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

掌握这三种电机的控制原理,就掌握了绝大多数嵌入式电机控制场景的核心技术。希望这篇文章能帮助你理解代码背后的硬件原理,不仅"知其然",更"知其所以然"。

文中所用代码均来自实际可运行的项目,图片为实验过程中的实物拍摄和原理图截图。如有疑问,欢迎在评论区交流讨论。

相关推荐
king_linlin1 小时前
数据结构——顺序表(附图文讲解|超详细)
c语言·数据结构·算法
xuxie9911 小时前
start9 霍尔双相编码器
单片机
兆龙电子单片机设计11 小时前
【STM32项目开源】STM32单片机语音分类垃圾桶-蓝牙APP
stm32·单片机·嵌入式硬件·物联网·开源·自动化·毕业设计
2601_9545267511 小时前
【工业传感与算法实战】温漂补偿与零点抗漂破局:基于二阶多项式拟合的 C/C++ 边缘校准算法,深度拆解“压力变送器什么牌子好”的技术硬指标
c语言·c++·算法
qq_4480111612 小时前
C语言中的变量和函数的定义与声明
android·c语言·开发语言
零涂毕业设计12 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
阿龙的工作记录13 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
ye1501277745513 小时前
220V转5V1A隔离芯片WT5602
单片机·嵌入式硬件·其他·硬件工程