【51系列】【转载】51单片机学习教程(简单入门)

news/2024/9/19 9:31:06/文章来源:https://www.cnblogs.com/FBsharl/p/18327191

转载自:知乎 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	31HPUSH	32HMOV	30H,#4MOV	31H,#205MOV	32H,#180
NEXT:DJNZ	32H,NEXTDJNZ	31H,NEXTDJNZ	30H,NEXTPOP	32HPOP	31HPOP	30HRET

接下来就通过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;//点亮LEDdelay(500);//点亮500msP0 = 0xff;//熄灭LEDdelay(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];//点亮LEDi++;if(i==8){i = 0;}}
}

学完以上内容,你的单片你就基本入门,接下来你就要对上面内容进行强化练习,然后在学习进阶内容,比如独立按键操作,矩阵键盘操作,液晶显示操作,电机调速(PWM),UART串口通信,IIC总线,SPI总线等等。

写得不是很好,文章中如有错误,还请大家指出,谢谢大家。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/772679.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024暑假集训测试13

前言比赛链接。从来没见过交互题,T1 狂 CE 不止心态炸了,后面的题也没打好,T2、T3 简单题都不会了,所以为啥 T4 又放黑题。 T1 大众点评原题:AT_joisc2014_d。难点主要在交互,赛时琢磨了半场比赛终于搞明白是啥玩意儿了,可以将给定库当成压缩的一部分代码,可以调用里面…

51nod-3976-最长序列

https://class.51nod.com/Html/Textbook/ChapterIndex.html#textbookId=126&chapterId=338 https://class.51nod.com/Html/Textbook/Problem.html#problemId=3976&textbookChapterId=725 LIS是符号只有大于或小于,所以这道题就是LIS问题。 状态设计同LIS,由于答案就是…

Spring 常用的三种拦截器详解

在开发过程中,我们常常使用到拦截器来处理一些逻辑。最常用的三种拦截器分别是 AOP、 Interceptor 、 Filter,但其实很多人并不知道什么时候用AOP,什么时候用Interceptor,什么时候用Filter,也不知道其拦截顺序,内部原理。今天我们详细介绍一下这三种拦截器。前言 在开发过…

Lambda、LINQ

1.Lambda表达式的使用示例如下:2.Linq示例:

并发容器

Java 的并发集合容器提供了在多线程环境中高效访问和操作的数据结构。这些容器通过内部的同步机制实现了线程安全,使得开发者无需显式同步代码就能在并发环境下安全使用,比如说:ConcurrentHashMap、阻塞队列和 CopyOnWrite 容器等。 java.util 包下提供了一些容器类(集合框…

win10运行交互题

这是题目给的 题目要求输入的编译命令g++ -O2 -o grader grader.cpp ramen.cpp 在\(DEV\)下是运行不了的 系统的\(CMD\)也是不行的 但我们可以用\(DEV\)中的\(system\)函数运行 int main() {system("g++ -O2 -o grader grader.cpp ramen.cpp"); return 0; }运行前在\…

15、flask-模型-models-表的操作-分页paginate()

paginate()的属性属性名 说明items 返回当前页的内容列表has_next 是否还有下一页has_prev 是否还有上一页next(error_out=False) 返回下一页的pagination对象prev(error_out=False) 返回上一页的Pagination对象page 当前页的页码pages 总页数per_page 每页显示的数量prev_num …

01-从WordCount程序理解Spark术语及术语间的关系

1. 应用程序(Application) 通过下面的代码设置应用程序名称,设置后再UI中可以看到相应的名称。 //1.设置Application的名称 val conf = new SparkConf() conf.setAppName("WordCount") conf.setMaster("local")2. Job Job由scala的执行算子生成,每个执…

leetcode-5

题目: 给你一个字符串 s,找到 s 中最长的 回文子串 示例 1:输入:s = "babad"  输出:"bab"  解释:"aba" 同样是符合题意的答案。 示例 2:输入:s = "cbbd"   输出:"bb" 提示: 1 <= s.length <= 1000…

Uniapp 之手写签名

一、效果图二、代码示例 qianming.jsexport const qianming = {data() {return {windowWidth: 0,pixelRatio: 0,context: null,points: [],oldPoints: [],qm_width: 280,qm_height: 120,qm_img: }},methods: {qm_start() {const systemInfo = uni.getSystemInfoSync()this.wind…

代码随想录day11 || 150 逆表达式求值 239 滑动窗口最大值 347 前k最高频元素

150 逆波兰表达式计算func evalRPN(tokens []string) int {// 自己想是真的想不出来,看了视频之后有了思路// 本质上逻辑就是遇到数字入栈,遇到运算符号 出栈两个元素然后计算再入栈,最终就是计算结果stack := Constructor()for _, val := range tokens{// 如果数字入栈if i…

计算机组成与体系结构-校验码

奇偶校验码 奇偶校验是一种简单有效的校验方法,这种方法通过在编码中增加一位校验位来使编码中1的个数为奇数(奇校验)或者为偶数(偶校验),只能发现奇数个数据位 出错的情况.循环冗余校验码 CRC(Cyclic RedundancyCheck)循环冗余校验是一种常用的错误检测技术,用于在数据传输…