【STC15 学习笔记】【转载】51单片机学习教程(简单入门)
转载自:知乎 https://zhuanlan.zhihu.com/p/628407258
作者:rakey (作者主页 https://www.zhihu.com/people/rakey-49)
学习51单片机之前你一定要具备的基础知识。
1、C语言基础。
2、数字电路基础。
3、模拟电路基础。
如果你已经具备这些知识那么我们就可以来学习单片机。
学习单片准备工作:1、51单片机开发板一张。
2、电脑一台。
学习的重点:
1、51单片机的最小系统。
![](https://pic4.zhimg.com/80/v2-36e8fb031f0f5f3fc75b98d0fd99b653_720w.webp)
所谓的最小系统其实就是能够保证单片机正常运行的最小电路,它分为一下三个部分。
①电源系统。
![](https://pic2.zhimg.com/80/v2-848edb0d19ff0a62de0824a8fce204c9_720w.webp)
电源是单片机的能量来源,没有它单片机什么也不能干,我们单片机通常使用的有两种电压一种是5V另外一种是3.3V,现在学的是传统单片机(AT89S52,STC89C52),所以电压就5V,如果以后用增强型单片机一定要去读规格书看看单片机的电压。
②复位系统。
![](https://pic4.zhimg.com/80/v2-3ea68ecf34feb0a8fd147c9ffeb189c7_720w.webp)
如果单片机死机,或者想让单片机恢复到初始状态,就需要复位电路进行复位,复位电路有两种,一种是上电复位,也就是说当你给单片机通电的时候单片机就会自动进入初始状态,另外一种就是按键复位,你可以在任何时候让单片你恢复到初始状态,只需按下该复位按钮。
③时钟系统。
![](https://pic2.zhimg.com/80/v2-df6704c1d527c92568166a142ff08aa1_720w.webp)
通常我们所说的单片机时钟就是我们平时所说的电脑的主频,电脑的主频越高,电脑的性能就越好,运行的速度就越快,那么我们单片机也是一样的时钟越高,单片机的运行速度就越快,但是51单片机的时钟也是有限制的不能高于33MHZ,(STC公司的单片机时钟频率可以高达40MHZ,但他是1T单片机,所谓的1T就是单片机的 机械周期 =1/f ,就是接下来要讲的内容)。
51单片机的时钟、状态、机器周期和指令周期
①时钟周期
时钟周期有称为振荡周期是51单片机的最小时间单位,在一个时钟周期内,CPU仅完成一个最基本的动作。
T=1/F F为晶体振荡器的频率,如果晶体振荡器的频率为12MHZ那么T = 0x0000000833333S
②状态周期
在51单片机中把1个时钟周期定义为一个节拍(用P表示),2个节拍定义为一个状态周期(用S表示)。
③机械周期
一条指令的执行过程可以分为若干个阶段,如取指令、读存储器、写存储器等。完成某一个操作的时间称为一个机器周期。通常情况下,一个机器周期由 12个时钟周期组成。
④指令周期
执行一条指令所需要的时间称为指令周期,一般由若干个机器周期组成。指令不同,所需的机器周期数也不同,一般由1~4个机器周期组成。
如:MOV A, Rn (数据传送指令) 就只需要一个机械周期,如果晶体振荡器为12MHZ那么执行该指令就只需要1us的时间, DIV AB (除法指令)就需要四个机械周期,执行该指令就需要4us的时间。
注意:上面讲的51单片机的汇编指令,后面我们写的程序是用C语言,但是C语言最终是会编译成汇编指令,在keil上的debug中可以看得到,后面讲的内容中延时程序就是通过以上的指令周期来进行计算的。
在上面讲过STC的增强型单片机是1T的单片机,它是机械周期=时钟周期,也就是说执行MOV A,Rn 这条指令只需要0.08us左右,比如STC8H8K64U,STC32(32位51单片机)。
2、点亮一只发光二极管。
①学习单片机的I/O口操作(I/O 是input/output的简写,为输入输出接口)。
![](https://pic2.zhimg.com/80/v2-d677b635e51148ed2b6993bfa1efc461_720w.webp)
51单片机一共有4个并口,分别是P1口,P2口,P3口,P4口,每一个并口有8位,每位引脚可以输入输出1位数据(0/1)。如P1口当中的8位分别表示为(按照从高位到低位排列):P1.7,P1.6,P1.5,P1.4,P1.3,P1.2,P1.1,P1.0。
那我们怎么操作这些I/O口呢 ?就拿P1来举例。
如: P1 = 0X01;那么P1口输出的数据如图:
![](https://pic1.zhimg.com/80/v2-424597d87490fe700ce7e4d7478a574c_720w.webp)
P1.7 输出0,P1.6 输出0,P1.5输出0,P1.4 输出0,P1.3 输出0,P1.2输出0,P1.1输出0,P1.0 输出1,刚好就对应了十六进制0x01的二进制数据00000001B。51单片机I/O口操作是不是就这么简单。
②发光二极管的接法(共阴极还是共阳极)如图所示:
![](https://pic1.zhimg.com/80/v2-8dcde322e25decf42a396168f0671bac_720w.webp)
如共阳接法,那么单片机的I/O口就要输出低电平(也就是逻辑0)该LED就能点亮,如共阴接法,那么单片机I/O口就要输出高电平(也就是逻辑1)该LED就能点亮。
③代码
如图所示LED的阴极接在单片机的P0.0,阳极通过一个限流电阻和一个电流表接在电源的正极,现在需要通过程序点亮这只LED。
![动图封面](https://pic2.zhimg.com/v2-eae569c1ea196ff2941463bb2e0697f1_b.jpg)
#include<reg52.h>//包含一个单片机标准的头文件(里面包含了单片机各个寄存器的定义)
void main()//主函数 一个工程里面有且只能有一个主函数。名字必须为main
{
while(1)//while大循环,只要while(1)小括号里面的内容不为0,那么单片机就会一直循环运行while大括号里面的内容
{
//发光二极管阳极接的是电源的正极,阴极接的是单片机的P0.0。
P0 = 0xfe;//点亮一只发光二极管 P1 (P1.7 P1.6 P1.5 P1.4 P1.3 P1.2 P1.1 P1.0) 十六进制
// 1 1 1 1 1 1 1 0 0xfe
}
}
如果能理解上面的这个点灯程序,那么你已经踏入单片机的大门了,接下来就来强化这个点灯程序程序来让LED以500ms的速度闪烁起来,单片机的晶体振荡器位12MHZ。
分析:从题目可知让LED以500ms的速度闪烁起来,其实就是让LED亮500ms然后在熄灭500ms,然后在亮500ms,然后在熄灭500ms。那么这里的难点就在这500ms,我们要在程序中写出一个延时函数,这个函数的运行时间位500ms,当函数运行完后就改变LED的亮灭状态。
在学习单片机的时钟系统的时候,学习了指令周期,指令周期其实就是运行一条指令的时间,那么我们就可以通过运行一些指令来达到延时的目的。例如下面这一串汇编指令,单片机运行下面这一串汇编指令时间大概就是500ms,其实延时就是CPU执行一串没有意义的代码,唯一的作用就是延时。
这串汇编代码,只需要了解,如果想知道里面指令的作用,可以结合着51单片机的汇编指令来看。
DELAY500MS:
PUSH 30H
PUSH 31H
PUSH 32H
MOV 30H,#4
MOV 31H,#205
MOV 32H,#180
NEXT:
DJNZ 32H,NEXT
DJNZ 31H,NEXT
DJNZ 30H,NEXT
POP 32H
POP 31H
POP 30H
RET
接下来就通过C语言来写延时函数:
/*
*函数名:延时函数
*描 述:当z = 1时,该演示函数大概延时1MS
*注 意:该延时函数为阻塞函数,当运行该函数时CPU只能运行它,其他什么事都干不了,只有CPU把它运行完后才能执行其他代码。
*/
void delay(int z)
{
int x,y;
for(x=z;x>0;x--)
{
for(y=110;y>0;y--)
}
}
delay(int z),括号里面的z,就是延时多少MS的参数入口,当z=1时,该延时函数就位1ms,如果要延时500ms,那么只需让 z = 500,就好了。
接下来就来编写LED闪烁代码:
#include<reg52.h>//包含一个单片机标准的头文件(里面包含了单片机各个寄存器的定义)
/*
*函数名:延时函数
*描 述:当z = 1时,该演示函数大概延时1MS
*注 意:该延时函数为阻塞函数,当运行该函数时CPU只能运行它,其他什么事都干不了,只有CPU把它运行完后才能执行其他代码。
*/
void delay(int z)
{
int x,y;
for(x=z;x>0;x--)
{
for(y=110;y>0;y--)
}
}
void main()//主函数 一个工程里面有且只能有一个主函数。名字必须为main
{
while(1)//while大循环,只要while(1)小括号里面的内容不为0,那么单片机就会一直循环运行while大括号里面的内容
{
P0 = 0xfe;//点亮LED
delay(500);//点亮500ms
P0 = 0xff;//熄灭LED
delay(500);//熄灭500ms
}
}
能看明白以上例子后,我们就可以来做一个流水灯,8只LED分别点亮,间隔时间大概一秒钟,8只LED 接在单片机的P1口,假如该单片机的晶振频率为12MHZ,如图所示。
![动图封面](https://pic4.zhimg.com/v2-4517794b00ebbd3013aedb05402f363b_b.jpg)
从该电路可知,8只LED为共阳极连接,所以点亮LED的数据就应该如下:
![](https://pic4.zhimg.com/80/v2-19374539873ed176c3fbcc49b03a6843_720w.webp)
#include<reg52.h>
/*定义一个数组,用来存放LED的点亮数据*/
char led[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
/*
*函数名:延时函数
*描 述:当z = 1时,该演示函数大概延时1MS
*注 意:该延时函数为阻塞函数,当运行该函数时CPU只能运行它,其他什么事都干不了,只有CPU把它运行完后才能执行其他代码。
*/
void delay(int z)
{
int x,y;
for(x=z;x>0;x--)
{
for(y=110;y>0;y--)
}
}
void main()
{
char i;
while(1)
{
for(i=0;i<8;i++)//一共有8只LED,所以循环8次,分别取出了led[]数组中的数据,送个P1口。
{
P1 = led[i];//分别把LED的点亮数据送入单片机的P1口
delay(1000);//延时1S
}
}
}
3、51单片机的中断系统
在这里一定要理解中断的概念,实际上中断很简单,就是你现在正在做事情,然后你的领导教你帮他做事情,因为这是你领导你不能拒绝所以你只能去帮领导干事情,等你把领导的事情干完了以后,再回来接着你刚刚被领导打断的那里继续做你的事情,这就是中断。中断是单片机当中的一个重要内容,中断具有响应速度快的特点。51单片机(普通)一共有5个中断源,分别是外部中断(INT0)、定时器中断(T0)、外部中断(INT1)、定时器中断(T1)、串口中断(RX TX)。
![](https://pic3.zhimg.com/80/v2-913edc0cde71b8da5a5914fbd7e0c506_720w.webp)
各中断源优先级的排列和中断序号
中断序号 | 中断源 | 优先级 |
0 | 外部中断 INT0 | 最高优先级 |
1 | 定时器/计数器中断 T0 | 第二高优先级 |
2 | 外部中断 INT1 | 第三高优先级 |
3 | 定时器/计数器中断 T1 | 第四高优先级 |
4 | 串口中断 RI/TI | 最低优先级 |
中断系统中一共有三个寄存器,分别是TCON,IE,IP实际上我们可以把这几个寄存器理解为设置开关(这些寄存器没有我们想象中那么复杂,它就是一个开关。为0就断开,为1就闭合),大家从上面的图都可以看出你要使用哪一个中断,你就要把相应的的开关打开。
例如我要使用外部中断(INT0)那么我首先要先择它的触发方式IT0(当IT0=0为电平触发,IT0 = 1为下降沿触发)在这里围殴选择下降沿触发,所以IT0 = 1;接下来就是EX = 1;EA是属于总中断开关所以EA = 1,接下来就是PX0,PX0为优先级选择,为1选择高优先级,为0选择低优先级,在这里我们就选则高优先级,所以PX0 = 1,这样设置先来外部中断(INT0)就可以使用了。
代码:
例子:
如图所示,在单片机P1口上接了4只发光二极管LED,都在单片机P3口的P3.2/INT0和P3.3/INT1上分别接了一个按钮开关(按钮就是你按下去就打开,松手就断开,游戏机手柄上面的按钮就是这样)按钮的另外一端接的是地,当按钮按下后单片机P3.2引脚电平就被拉低,单片机就能检测出引脚的电平变化,从而做出相应的相应。
注意:这里只用外部中断0(INT0),外部中1不用,会操作INT0那么INT1也就会操作了。 要求:当按钮P1按下,全部点亮LED,当按键松开LED做流水显示。
![](https://pic1.zhimg.com/80/v2-16be1805bb529c33777e501eda0403c4_720w.webp)
#include<reg52.h>
/*定义一个数组,用来存放LED的点亮数据,这里只有4个LED所以数组中只存放了4个LED的点亮数据。*/
char led[] = {0xfe,0xfd,0xfb,0xf7};
/*
*函数名:延时函数
*描 述:当z = 1时,该演示函数大概延时1MS
*注 意:该延时函数为阻塞函数,当运行该函数时CPU只能运行它,其他什么事都干不了,只有CPU把它运行完后才能执行其他代码。
*/
void delay(int z)
{
int x,y;
for(x=z;x>0;x--)
{
for(y=110;y>0;y--)
}
}
/*
*函数名:外部中断INT0初始化函数
*描 述:通过设置上述寄存器,使得外部中断INT0能够正常工作。
*/
void int0_init()
{
IT0 = 0;//关闭IT0,INT0为电平触发方式,
EX0 = 1;//打开EX0,INT0响应中断请求
EA = 1;//打开EA,打开总中断
PX0 = 1;//打开PX0,把INT0设置为高优先级,这里只有一个中断,其实可以不设置优先级。
}
/*主函数*/
void main()
{
char i;
int0_init();//初始化函数只运行一次
while(1)
{
for(i=0;i<4;i++)//一共有4只LED,所以循环4次,分别取出了led[]数组中的数据,送个P1口。
{
P1 = led[i];
delay(1000);//延时1S
}
}
}
/*外部中断INT0的中断服务函数*/
void int0()interrupt 0//这里的0表示为中断序号。
{
//4只LED全部点亮
P1 = 0x00;
delay(5000);//点亮大概5S
}
当没有按下按钮,单片机会一直运行while循环里面的内容,当按钮按下后,单片机会立马执行int0()这个函数里面的内容,该函数里面的内容执行完毕后,有会回到主函数中while循环继续执行。
知识点: 在单片相应中断之前,单片机会自动保存当前while循环里面的运行状态(如:现在是点亮的第4只LED,那么单片机就会保存下来),在运行完中断代码后回来就会接着上次被打断的地方继续执行。
4、定时器/计数器
顾名思义就是定时,计数用的,51单片机(普通)一共有两个定时器(定时器T0和定时器T1)
我们要使用51单片机的定时器/计数器,我们就要对定时器/计数器进行设置,设置定时器/计数器一共有两个寄存器,一个是TMOD(模式控制寄存器),一个是TCON控制寄存器。
![](https://pic1.zhimg.com/80/v2-79f857fcdb1a8b47fdd9f198fcc4bf6c_720w.webp)
TMOD的高四位用于控制定时器T1,低四位用于控制定时器T0。在我们后面的举例中我们都是用定时器T0。
GATE:若GATE=0,只需要把TR1/TR0设置位1就可以启动相应的定时器,若GATE = 1,则还需要将外部中断(INT0/INT1)引脚输入为高电平,才可以启动相应的定时器。
C/T:选择定时器还是计数器,C/T = 0定时模式,C/T = 1计数模式。
M1,M0:为定时器/计数器工作方式选择。
![](https://pic4.zhimg.com/80/v2-b00875a064191266e1f67ed04c4a967b_720w.webp)
在后面的举例中都用定时器T0并且都工作在方式1(16位定时/计数器)所以M0 = 1,M1 = 0。由于工作在定时器所以C/T = 0,GATE = 0。
那么在设置TMOD的时候就可以直接设置成:TMOD = 0x01。如下图所示
![](https://pic3.zhimg.com/80/v2-212d17f9fe90d65a170e65e28f35cbca_720w.webp)
![](https://pic2.zhimg.com/80/v2-ce409c554800ca5b1177dbbff313f269_720w.webp)
我们平时用得最多的是定时器/计数器中断,所以中断标志我们现在可以不管他,TCON里面位是可以进行位操作。 在后面的举例当中我们都是用定时器T0。所以要启动定时器T0,就可以直接操作TR0, TR0 = 1;就表示打开定时器T0,这是定时器T0就开始正常计数,当达到一定的计数值是就会产生中断。
要想运用定时器,那么还要了解定时器里面的一个最重要的两个寄存器TL0和TH0(计数寄存器)TL0为低8位,TH0为高8位。你可以把计数寄存器理解位一个水缸,当水缸里面的水装满了过后就会溢出,这时候你听到水声,就会马上跑过去关闭水龙头,这个就是定时器中断。当计数寄存器装满后就会发出中断请求,然后CPU就会相应该中断。
计数寄存器装的最大值位0-65535,当它装满65535就会发出中断请求,如:单片机的晶体振荡器位12MHZ,一个机械周期就为1us,那么计数寄存器就会1us计数一次,以此类推当计数寄存器计数到65535是,就相当于65535us,也就是65535us定时器就会发出一次中断请求。但是在我们做的项目中不需要这么长的时间响应中断,比如50000us相应一次中断,那我们怎么办呢?我们可以装入初值,65535-50000 = 15535,其中50000就为我们需要定时的时间,15535就为我为我们提前转入计数寄存器中的值,就我们有一个18升的水缸,我需要装6升水这个水缸就满了,所以我们提前就要往水缸里装如12升水。上面王计数寄存器装入初值也就是这个意思。这里我们要把15535十进制数转换成十六进制数,其中把高8位装入TH0中,把低8位装入TL0中。15535转换成十六进制为0x3caf,TH0 = 0x3c TL0 = 0xaf。
例子:用定时器中断操作流水灯,间隔时间为1S。
![动图封面](https://pic4.zhimg.com/v2-4517794b00ebbd3013aedb05402f363b_b.jpg)
#include<reg52.h>
/*定义一个数组,用来存放LED的点亮数据,这里只有4个LED所以数组中只存放了4个LED的点亮数据。*/
char led[] = {0xfe,0xfd,0xfb,0xf7,0xef,0xdf,0xbf,0x7f};
char num;//定义一个全局计数变量
char i;//哪一只LED点亮
/*
*函数名:定时器T0初始化函数
*描 述:通过设置上述寄存器,使得定时器T0能够正常工作。
*/
void timer_init()
{
TMOD = 0X01;//设置定时/计数器工作在定时器方式一
TH0 = 0X3C;//装入初值,定时器每过50000us进入中断
TL0 = 0XAF;
ET0 = 1;//打开定时器T0中断
EA = 1;//打开总中断
TR0 = 1;//启动定时器
}
/*主函数*/
void main()
{
timer_init();//初始化函数只运行一次
while(1)
{
//主函数里面什么也不做
}
}
/*函数名:定时器T0中断服务函数
* 描 述:每过50000us运行一次
*/
void timer0()interrupt 1//这里的1表示为中断序号。
{
TH0 = 0X3C;//每次运行中断服务函数都要为定时器从新装入初值
TL0 = 0XAF;
num++;//没中断一次num就自加一次,加一次为50000us(50ms),当num=20时就相当于1S.
if(num==20)
{
num=0;//把num清零,让它从新计数
P1 = led[i];//点亮LED
i++;
if(i==8)
{
i = 0;
}
}
}
学完以上内容,你的单片你就基本入门,接下来你就要对上面内容进行强化练习,然后在学习进阶内容,比如独立按键操作,矩阵键盘操作,液晶显示操作,电机调速(PWM),UART串口通信,IIC总线,SPI总线等等。
写得不是很好,文章中如有错误,还请大家指出,谢谢大家。
1.STC15W408AS单片机硬件资源/命名规则:
https://blog.csdn.net/zhuming3834/category_11156816.html
2.STC15W408AS单片机时钟
https://hgdq10.blog.csdn.net/article/details/118294501
3.STC15W408AS单片机GPIO
https://hgdq10.blog.csdn.net/article/details/118368589
TC15W408AS单片机有14个I/O口。其所有I/O口均可由软件配置成4种类型。4种类型分别为:准双向口/弱上拉(标准8051输出模式)、推挽输出/强上拉、高阻输入(电流既不能流入也不能流出)或开漏输出功能。每个口由2个控制寄存器中的相应位控制每个引脚工作类型。STC15系列单片机的I/O口上电复位后为准双向口/弱上拉(传统8051的I/O口)模式。每个I/O口驱动能力均可达到20mA,但40-pin及40-pin以上单片机的整个芯片最大不要超过120mA,20-pin以上及32-pin以下(包括32-pin)单片机的整个芯片最大不要超过90mA。
4.STC15W408AS单片机点亮第一个LED
https://hgdq10.blog.csdn.net/article/details/118513665
5.STC15W408AS单片机按键输入
https://hgdq10.blog.csdn.net/article/details/118699555