PC通过串口控制51单片机输出PWM波控制直流电机转速
1 简单介绍
最近在做单片机的课程设计,把51单片机的相关知识简单的回顾了一遍,今天给大家分享一个简单的小实验,就是PC机通过串口给51单片机发送直流电机速度数据,51单片机对数据进行识别后,利用定时器输出对应占空比的PWM波形,从而实现PC对直流电机转速的控制。
2 详细介绍
本次使用的单片机是市面上非常常见的51单片机,也非常适合小白入门使用,本次实验采用的单片机型号是STC89C52RC,也是从淘宝上买的一块普中科技的51开发板。
2.1 本次实验所用到的硬件
- STC89C52RC
- PC机
- 直流电机
- 数码管
在这里插入图片描述
2.2 简单介绍一下直流电机、PWM、串口、数码管
2.2.1 直流电机
直流电机相对来说算是一种原理比较简单的电机,这里在百度上面找了一张原理图。
实际上,直流电机就是一种将电能转化位机械能的装置,其内部含有磁铁,由于电流会产生磁场,且产生的磁场方向与电流的方向相关,当电流方向相反时,导线受到直流电机内部磁铁的吸引力方向就会相反,从而使得直流电机的转向相反。
话句话说,直流电机的一脚接VCC,一脚接GND,当两脚反过来接线时,直流电机的转向就会相反。
2.2.2 PWM
PWM(Pulse Width Moduation)中文翻译是脉冲宽度调制。假设此时有一个方波信号,其示意图如下所示:
其中, T h T_h Th是一个周期 T T T内高电平所维持的时间。
该方波的占空比计算公式为:
占空比
=
T
h
T
×
100
%
占空比=\frac{T_h}{T}\times 100\%
占空比=TTh×100%
由占空比计算公式可知,占空比其实就是高电平保持时间占整个周期的比例,而PWM(即脉冲宽度调制)就是改变方波的占空比来实现直流电机的转速调节。那么为什么PWM波能够调节电机的转速呢?
高中物理中我们学过,直流电机内部是有电感器件的,有这种储能元件存在意味着电机内部的电流不能发生突变,而只能缓慢变化。
试想将直流电机的两个引线端一端接VCC,一端接GND,那么此时直流电机中通过的电流将是最大的,电机的转速与通过的电流大小成正比关系,因此此时直流电机的转速也是最大的。如果将直流电机的两个引线端都接GND,那么直流电机中将没有电流通过,因此直流电机不转。那么如果我将直流电机的一个引脚固定接GND,另一个引脚先接0.05msVCC,然后再接0.05msGND呢?会出现什么现象?
前面讲了直流电机中的电流是连续变化的,因此当直流电机的引脚从VCC变为GND之后,其内部电流没有立马变为0,而是向0的方向缓慢变化(这里所说的缓慢变化只是相对于立即变为0而言,实际上也是很快的)。那么当电流还在减小时,下一个高电平又来了,电流又开始慢慢增大,如此往复,最终使得电机中的有效电流维持在一个常值,可以理解为有效值。
如果高电平维持的时间越长,那么该有效值就会越大,通过直流电机的等效恒定电流就会越大,那么直流电机转速就会越快,这就是PWM控制直流电机转速比较浅显的理解方式。
2.2.3 串口
51单片机中含有一对串口,个人感觉串口资源还是比较少的,比如本次课程设计我想做一个指纹模块,我需要用到51单片机的串口,但是我用电脑烧录程序需要用到CH340将TTL转为串口,也需要用到串口,这时候就会发生冲突。我一开始的想法是电脑只用烧写程序,不用于给单片机供电,自己用2节18650的锂电池(3.7V)然后通过一个LM7805稳压芯片输出5V的稳压信号,给单片机供电。这样当程序烧录进去之后,就可以将PC和单片机之间的USB线断开,这也意味着51单片机的串口此时处于空闲状态了,因此我的指纹模块就可以用了。后来询问了老师的建议,老师说用一个跳线帽和3脚排针就能解决,这里也简单讲讲为什么可以这样。
可以看到CH340和AS06模块都需要与单片机的串口连接,但是串口又不能脚踏两只船(开个玩笑~),因此就需要做出选择,这个选择功能就可以通过插针和跳线帽实现,从上面两张图也可以看出我在电路中已经将CH340的TXD引脚与51单片机的RXD/P3.0相连接,AS06的TX也与单片机的RXD/P3.0相连接。而CH340和AS06的RXD均接到了插针上,而没有直接与单片机的TXD/P3.1连接。当我们需要烧录程序的时候,就将插针的1脚和2脚用跳线帽短接;当我们需要使用指纹模块时,就将插针的2脚和3脚用跳线帽短接(前提是已经将程序烧录完成)。
好了,现在言归正传,这里我只讲解51单片机的工作方式1(最近时间比较紧,等放假了我再做详细介绍),也是比较常用的一种工作方式。
51单片机串口工作方式1是8位串行接收发方式,对串口的操作其实就是对几种寄存器的操作,串口涉及到的寄存器主要有SCON、PCON、SUBF以及与定时器1有关的寄存器TMOD和TCON。
SCON寄存器:
SM0 | SM1 | SM2 | REN | TB8 | RB8 | TI | RI | |
---|---|---|---|---|---|---|---|---|
位地址 | 9FH | 9EH | 9DH | 9CH | 9BH | 9AH | 99H | 98H |
其中SM0和SM1是用来选择串口工作方式的,串口一共有四种工作方式。
SM0 | SM1 | 方式 | 功能说明 |
---|---|---|---|
0 | 0 | 0 | 同步移位寄存器方式(用于扩展I/O口) |
0 | 1 | 1 | 8位异步收发,波特率可变(由定时器控制) |
1 | 0 | 2 | 9位异步收发,波特率为 f o s c / 64 f_{osc}/64 fosc/64或 f o s c / 32 f_{osc}/32 fosc/32 |
1 | 1 | 3 | 9位异步收发,波特率可变(由定时器控制) |
SM2是多机通信控制位。SM2一般只有在用于多机通信时才会置为1,其余时候都是0,而多机通信一般采用方式2或方式3.
REN是允许串行接收位。REN=1,允许串行口接收数据;REN=0,禁止串行口接收数据。该位在方式1接收数据时需要用到。
TB8和RB8都是多机通信是需要用到的,工作方式1时这两个位置为0即可,不用管。
TI和RI分别是发送中断标志位和接收中断标志位。当51单片机通过串口发送完一帧数据(8位数据)时,TI会被硬件置为1;当51单片机通过串口接收完一帧数据(8位数据)时,RI会被硬件置为1。这两个中断标志位与外部中断和定时器的不同点在于,它们需要用软件清零,硬件不会自动清零,因此在终端服务程序中通常以这样的格式写:
void Uart(void) interrupt 4 //串口中断服务程序,串口的中断号为4
{
if(TI==1) //如果一帧数据发送完,TI被硬件置为1
{
......
TI=0;
}
if(RI==1)
{
......
RI=0;
}
}
另外还有一个点需要注意,就是在串口的中断服务程序中需要对TI和RI进行判断,因为串口触发中断可能是因为接收到一帧数据也有可能是因为发送完一帧数据,这两种情况下需要进行的操作往往是不同的,因此需要作判断。
PCON寄存器:
SMOD | - | - | - | GF1 | GF0 | PD | IDL |
---|
串口只与PCON寄存器的SMOD位有关系,低四位是与单片机节能等有关的位,这里不需要考虑。SMOD位是决定串口波特率的一个因素,当SMOD等于1时串口波特率是SMOD等于0时的两倍,因此也常常被称为波特率倍增位。
波特率串口1秒所能发送或接收的位数。
51单片机void 串口工作在方式1时的波特率计算公式如下:
波特率
=
2
S
M
O
D
32
×
定时器
T
1
的溢出率
波特率=\frac{2^{SMOD}}{32}\times 定时器T1的溢出率
波特率=322SMOD×定时器T1的溢出率
定时器T1的溢出率是指1秒内定时器T1溢出的次数,其等于定时器T1定时时间的倒数。
定时器常用的工作方式有方式1和方式2。方式1和方式2的区别在于方式2为自动重装载模式,不需要在中断服务程序中重新给THx和TLx赋初值,精度也更高,但是最大计时时间更小。假设定时器1工作在方式2,单片机的晶振频率为11.0592MHz,那么定时器T1的溢出率计算公式为:
定时器
T
1
的溢出率
=
11.0592
12
×
(
256
−
X
)
M
H
z
定时器T1的溢出率=\frac{11.0592}{12\times(256-X)}MHz
定时器T1的溢出率=12×(256−X)11.0592MHz
因此51单片机串口工作在方式1时的波特率用下式计算:
波特率
=
2
S
M
O
D
32
×
11.0592
12
×
(
256
−
X
)
M
b
i
t
s
波特率=\frac{2^{SMOD}}{32}\times\frac{11.0592}{12\times (256-X)}Mbits
波特率=322SMOD×12×(256−X)11.0592Mbits
其中,
X
X
X是定时器的初值。
实际上,常用的波特率是一些定值,可以通过查表获取,而不需要自己计算,下面是波特率用表。
波特率 | f o s c f_{osc} fosc | SMOD位 | 定时器1方式 | 初值X |
---|---|---|---|---|
62.5kbit/s | 12MHz | 1 | 2 | FFH |
19.2kbit/s | 11.0592MHz | 1 | 2 | FDH |
9.6kbit/s | 11.0592MHz | 0 | 2 | FDH |
4.8kbit/s | 11.0592MHz | 0 | 2 | FAH |
2.4kbit/s | 11.0592MHz | 0 | 2 | F4H |
1.2kbit/s | 11.0592MHz | 0 | 2 | F8H |
比如我的51单片机所用的晶振是11.0592MHz的晶振,我想将串口波特率设置为9600bit/s,那么我就可以通过以下代码对串口进行初始化:
void Uart_SendInit(void) //串口9600波特率发送初始化
{
SCON=0x40; //SM0=0,SM1=1,REN=0,方式1,只发送不接收
SMOD=0x00; //SMOD=0,查表得到
TMOD=0x20; //定时器1工作在方式2
TH1=0xfd;
TL1=0xfd; //方式2时TH1和TL1的值相同,其值通过查表得到
TR1=1; //启动定时器1
EA=1; //打开总中断允许开关
ES=1; //打开串口中断允许开关
}
void Uart_ReceiveInit(void) //串口9600波特率接收初始化
{
SCON=0x50; //SM0=0,SM1=1,REN=1,方式1,能发送能接收
SMOD=0x00; //SMOD=0,查表得到
TMOD=0x20; //定时器1工作在方式2
TH1=0xfd;
TL1=0xfd; //方式2时TH1和TL1的值相同,其值通过查表得到
TR1=1; //启动定时器1
EA=1; //打开总中断允许开关
ES=1; //打开串口中断允许开关
}
串口需要发送数据时,对SBUF寄存器写入一帧数据,串口就开始将SBUF中的数据逐位发送出去,先发送低位,后发送高位,当发送完一帧数据后,TI被硬件置为1。当串口需要接收数据时,将SCON寄存器中的REN位置为1,然后串口开始从低位到高位接收外部传来的一帧数据,当一帧数据接收完毕后,RI被硬件置为1。
还有一点需要注意的是,接收两方的波特率需要保持一致。串口就先说这么多吧,具体细节见后面代码讲解。
2.2.4 数码管
数码管是一种日常生活中常见的显示器件。其内部是由很多LED组成的,数码管通常被分为共阳极数码管和共阴极数码管,从其内部结构解释这种分类,实际上就是所有LED的正极接在一起(共阳极数码管)或所有LED的负极接在一起(共阴极数码管)。共阳极数码管通常也简写为CA,共阴极数码管CC。
数码管显示又分为静态数码管显示和动态数码管显示,静态数码管显示需要占用大量的IO口但是操作起来比较方便。动他数码管显示能够节省大量的IO口,但是操作稍微复杂一点,其显示操作主要分为两部分,一个是位选,一个是段选,最后利用人眼的余辉效应(一般频率高于50Hz左右的变化人眼就无法捕捉到),实现数码管的动态显示,即实际上是分别点亮,但是人眼看起来确实同时亮,通过手机摄像头拍摄就可以发现问题,因为收集摄像头的分辨率比人眼高。
3 主要原理图
4 代码
4.1 main.c
#include <REGX52.H>
#include "Key.h"
#include "Nixie.h"
unsigned char Speed=0,Compare=0,Count=0;
sbit Motor=P1^0;
unsigned char Temp=0;
/*Function Comment
Brief:定时器0初始化函数
Input:无
Return:无
*/
void Timer0_Init(void) //定时器0工作在方式2定时10us
{
TMOD|=0x02; //定时器0工作在方式2
TH0=0x09;
TL0=0x09; //定时器0定时10us
EA=1; //打开总中断允许开关
ET0=1; //打开定时器0开关
TR0=1; //启动定时器0
PT0=0; //定时器0中断低优先级
}
/*Function Comment
Brief:串口发送初始化函数
Input:无
Return:无
*/
void Uart_SendInit(void) //串口方式2发送初始化,波特率为9600
{
TMOD|=0x20; //定时器1工作在方式2
TH1=0xfd;
TL1=0xfd;
SCON=0x40; //串口初始化方式1发送,不接收
PCON=0x00; //SMOD=0
TR1=1; //启动定时器1
EA=1; //打开中断总允许开关
ES=1; //打开串口中断开关
PS=1; //串口中断高优先级
}
/*Function Comment
Brief:串口接收初始化函数
Input:无
Return:无
*/
void Uart_RecInit(void) //串口方式2接收初始化,波特率为9600
{
TMOD|=0x20; //定时器1工作在方式2
TH1=0xfd;
TL1=0xfd;
SCON=0x50; //串口初始化方式1发送且接收,REN=1
PCON=0x00; //SMOD=0
TR1=1; //启动定时器1
EA=1; //打开中断总允许开关
ES=1; //打开串口中断开关
PS=0; //串口中断高优先级
}
/*Function Comment
Brief:主函数
Input:无
Return:无
*/
void main(void)
{
Uart_RecInit(); //串口方式2接收初始化,波特率为9600
Timer0_Init(); //定时器0初始化
while(1)
{
Nixie_Disp(1,Speed);
}
}
/*Function Comment
Brief:串口中断函数
Input:无
Return:无
*/
void Uart(void) interrupt 4 //串口中断函数
{
if(RI==1) //接收到一帧数据
{
Temp=SBUF; //将SBUF中的数据读取走
if(Temp==0){Speed=0;Compare=0;}
if(Temp==1){Speed=1;Compare=50;}
if(Temp==2){Speed=2;Compare=70;}
if(Temp==3){Speed=3;Compare=90;}
SBUF=Temp; //写SBUF,将单片机接收到的一帧数据返回给PC
RI=0; //接收标志位手动清零
}
if(TI==1) //发送一帧数据
{
TI=0; //发送标志位手动清零
}
}
/*Function Comment
Brief:定时器0初始化函数
Input:无
Return:无
*/
void Timer0(void) interrupt 1 //定时器0中断函数
{
Count++;
if(Count==100)
{
Count=0;
}
if(Count<Compare)
{
Motor=1;
}
if(Count>=Compare)
{
Motor=0;
}
}
4.2 Delayms.c
#include "Delayms.h"
void Delayms(unsigned int xms) //延迟xms毫秒函数,晶振11.0592MHz
{
while(xms--)
{
unsigned char i, j;
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
}
}
4.3 Nixie.C
#include <REGX52.H>
unsigned char Nixie_CC[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x77,0x7c,0xc6,0xa1,0x86,0x8e}; //共阴极数码管段码
void Nixie_Disp(unsigned char Location,unsigned char Num)
{
switch(Location)
{
case 1:
{
P2_2=0;
P2_3=0;
P2_4=0;
break;
}
case 2:
{
P2_2=1;
P2_3=0;
P2_4=0;
break;
}
case 3:
{
P2_2=0;
P2_3=1;
P2_4=0;
break;
}
case 4:
{
P2_2=1;
P2_3=1;
P2_4=0;
break;
}
case 5:
{
P2_2=0;
P2_3=0;
P2_4=1;
break;
}
case 6:
{
P2_2=1;
P2_3=0;
P2_4=1;
break;
}
case 7:
{
P2_2=0;
P2_3=1;
P2_4=1;
break;
}
case 8:
{
P2_2=1;
P2_3=1;
P2_4=1;
break;
}
}
P0=Nixie_CC[Num];
}
5 总结
以上便是本次分享的全部内容,本次也是本人第一次写博客,由于最近时间比较紧张,因此介绍的可能不够详细,逻辑也比较混乱,等我时间充裕了我也会不间断的更新博客,希望大家能够支持一下,希望能够在和大家分享技术的同时能够有所进步,也能结交到更多志同道合的朋友,大家如果有什么问题可以发在评论区。
需要原工程文件的小伙伴看这里。
链接:https://pan.baidu.com/s/18Hkl13b0aZXkkzN928uhDA
提取码:1234