【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单片机的最小系统。

51单片机的最小系统

所谓的最小系统其实就是能够保证单片机正常运行的最小电路,它分为一下三个部分。

①电源系统。

51单片机5V供电电路

电源是单片机的能量来源,没有它单片机什么也不能干,我们单片机通常使用的有两种电压一种是5V另外一种是3.3V,现在学的是传统单片机(AT89S52,STC89C52),所以电压就5V,如果以后用增强型单片机一定要去读规格书看看单片机的电压。

②复位系统。

51单片机复位电路

如果单片机死机,或者想让单片机恢复到初始状态,就需要复位电路进行复位,复位电路有两种,一种是上电复位,也就是说当你给单片机通电的时候单片机就会自动进入初始状态,另外一种就是按键复位,你可以在任何时候让单片你恢复到初始状态,只需按下该复位按钮。

③时钟系统。

51单片机时钟电路

通常我们所说的单片机时钟就是我们平时所说的电脑的主频,电脑的主频越高,电脑的性能就越好,运行的速度就越快,那么我们单片机也是一样的时钟越高,单片机的运行速度就越快,但是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的简写,为输入输出接口)。

51单片机引脚图

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口输出的数据如图:

51单片你P1口每位输出的数据

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口操作是不是就这么简单。

②发光二极管的接法(共阴极还是共阳极)如图所示:

多个LED的接法

如共阳接法,那么单片机的I/O口就要输出低电平(也就是逻辑0)该LED就能点亮,如共阴接法,那么单片机I/O口就要输出高电平(也就是逻辑1)该LED就能点亮。

③代码

如图所示LED的阴极接在单片机的P0.0,阳极通过一个限流电阻和一个电流表接在电源的正极,现在需要通过程序点亮这只LED。

动图封面
 
51单片机驱动一只LED
#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,如图所示。

动图封面
 
如图所示

从该电路可知,8只LED为共阳极连接,所以点亮LED的数据就应该如下:

8只发光二极管点亮数据

 

#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)。

中断

各中断源优先级的排列和中断序号

中断序号 中断源 优先级
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做流水显示。

51单片机中断实验
#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控制寄存器。

TMOD的位名称及功能

TMOD的高四位用于控制定时器T1,低四位用于控制定时器T0。在我们后面的举例中我们都是用定时器T0。

GATE:若GATE=0,只需要把TR1/TR0设置位1就可以启动相应的定时器,若GATE = 1,则还需要将外部中断(INT0/INT1)引脚输入为高电平,才可以启动相应的定时器。

C/T:选择定时器还是计数器,C/T = 0定时模式,C/T = 1计数模式。

M1,M0:为定时器/计数器工作方式选择。

定时/计数器工作方式

在后面的举例中都用定时器T0并且都工作在方式1(16位定时/计数器)所以M0 = 1,M1 = 0。由于工作在定时器所以C/T = 0,GATE = 0。

那么在设置TMOD的时候就可以直接设置成:TMOD = 0x01。如下图所示

TMOD = 0x01,TMOD当中的各位数据
TCON的结构,位名称及功能

我们平时用得最多的是定时器/计数器中断,所以中断标志我们现在可以不管他,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。

动图封面
 
LED流水灯电路
#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

 

posted @ 2024-07-27 17:13  FBshark  阅读(4)  评论(0编辑  收藏  举报