文章目录
前言
经过一学期对单片机的学习,打算将所学知识进行罗列整理以便帮助后续复习,另外本篇为小编第一次编写博客,如有错误请务必指出。本文所使用的图片皆来源于网络,若有侵犯请私信删除。
一、了解C51单片机
C51单片机是一种由英特尔(Intel)公司开发的经典的8位微控制器(MCU)。它基于MCS-51体系结构,具有高度集成的功能,包括处理器核心、存储器、输入/输出接口、定时器/计数器、串行通信接口等。C51单片机被广泛应用于嵌入式系统开发、工业控制、汽车电子、家电等领域。因为其便宜,简单的特点,在许多学习和实际项目中仍然享有较高的流行度。
对于初学者来说,单片机和诸多元器件都是很难以理解的东西,但是事实上来讲,我们并不需要对那些东西理解的很透彻,这点可以类比成数学,物理公式。当然如果想要进行深造的话,进阶的话还是需要相当一部分理解的。需要注意的是,单片机编程需要一定的电子基础知识和编程经验。建议先了解一些基本的电路和编程概念,再逐步学习和实践单片机的使用。同时,参考相关的教程、书籍和在线资源,结合具体的项目和实践经验,可以更好地掌握单片机的应用和编程技巧。
二、如何使用单片机
C51单片机是一种使用C语言进行编程的单片机。与以前编写C语言程序不同的是,我们现在要将芯片上的引脚当作变量来控制。不论是51还是32等单片机,本质上都是通过对引脚的控制来实现各种功能。
在C51单片机中,引脚的状态仅有两种可能性:0表示低电平,1表示高电平。通过设置引脚的状态,我们可以控制外部电路的行为。例如,将引脚的状态设置为0可以让LED点亮,而将引脚的状态设置为1可以让LED熄灭(以普中的开发板为例)。
为了进行C语言编程,我们可以使用开发软件Keil 5。Keil 5提供了C99的库和函数支持,使得我们可以方便地进行单片机的控制和编程。同时用STC-ISP进行烧录,但是为了方便,本文将进行仿真操作,仿真软件为proteus 8。
需要注意的是,单片机的具体引脚布局和控制方式会因不同型号而异。在开始进行编程之前,请先查阅相关的文档和数据手册,了解特定单片机的引脚定义和引脚控制方式,确保正确地控制和使用引脚,本文所使用的型号为AT89C51(AT89C52同)。
三、使用示例
0.proteus 仿真的使用 关于proteus仿真的安装与汉化以及使用方法可以参考以下文章:(https://blog.csdn.net/weixin_43772810/article/details/121993865)。
本文仅说明一个常用的使仿真图看起来更简洁的方法,以下图为例
注为契合主流,本文将以普中的51单片机为基础进行仿真。
1.流水灯
流水灯本质上就是将最基本的点灯延申,通过延迟函数控制每个引脚的状态,接下来将以数组法和位移符进行演示。
Proteus 仿真图如下
数组法:
#include <REGX52.H>
typedef unsigned int u16;
typedef unsigned char u8;
void delay_ms(u16 ms)//延迟函数
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
while(1)
{
P2=0XFE;//1111 1110 当引脚为低电平时LED灯亮起
delay_ms(50);
P2=0XFD;//1111 1101
delay_ms(50);
P2=0XFB;//1111 1011
delay_ms(50);
P2=0XF7;//1111 0111
delay_ms(50);
P2=0XEF;//1110 1111
delay_ms(50);
P2=0XDF;//1101 1111
delay_ms(50);
P2=0XBF;//1011 1111
delay_ms(50);
P2=0X7F;//0111 1111
delay_ms(50);
}
}
由上我们可以看出直接对P2进行赋值操作,其实就是总的控制P2^0~ P2^1,再通过将16进制转变为2进制可以看出,流水灯其实就是每隔一段时间只让一个引脚为低电平(亮起),其他引脚为高电平(熄灭)。在此基础上我们再引入位移符的概念———<< (左移)、>> (带符号右移)和>>> (无符号右移),位移操作符可以向左或向右移动二进制数,并在移动过程中用0进行补位。 如0x01(0000 0001)<<2,即向左补俩个0,结果为0x04(0000 0100)。将其综合,位移符法如下,仿真图不变
/**********16进制
* a&b a和b相同位数的值相同取1否取0
* a|b a∪b
* a^b a和b相同位数的值不同取1相同取0
* ~a 取反符
* a<<b a的值向左移动b个位置。末尾补0
* a>>b a的值向右移动b个位置。补0
* a&=B,a|=b,a^=b, a=a&,|,^=b
***************/
#include <REGX52.H>
typedef unsigned int u16;
typedef unsigned char u8;
int i;
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
for( i=0;i<8;i++)
{
P2=~(0x01<<i);//取反符,将 0 1互换,目的是为了方便计算和观看
delay_ms(50);
}
}
2.独立按键 长摁 短摁
按键是一种电子开关,主要分为俩种类型—— 轻触式按键和自锁式按键,让我们来看一下二者的区别
(注:本文仅使用轻触式按键做示例)
1.轻触式按键
主要特点是摁下之后可自回弹,只要轻轻的按下按键就可以是开关接通,松开时是开关就断开连接,实现原理主要是通过轻触按键内部的金属弹片受力弹动来实现接通和断开的。
2.自锁式按键
主要特点是摁下之后不会自回弹,需要再次摁下才可以弹起,在开关按钮第一次按时,开关接通并保持,即自锁,在开关按钮第二次按时,开关断开,同时开关按钮弹出来。
不论那种按键,刚开始都是断开状态,当按键按下之后才会变成导通状态,这一点在单片机上的体现就是,当按键未按下时引脚上的电平为高电平1,按下之后为低电平0。通过这点可以画出按键按下过程的理想波形,然而因为硬件的问题,按键状态变化产生的实际波形曲线会有一段变化剧烈的抖动(通常为10-20ms),而这些变化对单片机而言时非常明显的,此时如果不进行消抖操作那么单片机就会误判,并且由图可以看出除去摁下去和松手的俩段时间具有抖动外,摁着的闭合时间段并没有抖动而且此段时间是由使用者决定的,因此就有下列流程图
既然原理和思路都有了那么就开始进入实战环节,Proteus仿真如下(就是再第一问的基础上多加了按键)
接下来我们程序要实现的就是当按键短摁时,LED灯逐个亮起;按键长摁时,LED灯以流水灯的形式持续亮起,代码如下
#include <regx52.h>
sbit key1=P3^0;
#include <REGX52.H>
typedef unsigned int u16;
typedef unsigned char u8;
int i=0;
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
void main()
{
while(1)
{
if(key1==0)
{
delay_ms(150);//实际物体而言延迟不会这么大
P2=~(0x01<<i);
i++;
if(i==8){i=0;}
delay_ms(150);
}
}
}
3.动态数码管显示
数码管分为共阳极和共阴极俩种,根据原理图可以看出来共阳极和共阴极就是接电源和接地的区别,同时细心的小伙伴可能就发现了,无论那种数码管,它的原理图和第一问的流水灯及其相似,事实上数码管的显示也及其相似,无非就是让a~e的灯个别亮起,然后组成我们想要的字,在这一点上共阳极和共阴极的区别就是控制数码管亮起的的时候使用高电平还是低电平亮起。
共阳极表示0~9的段码{ ~0x3f, ~0x06, ~0x5b, ~0x4f, ~0x66, ~0x6d, ~0x7d, ~0x07, ~0x7f, ~0x6f }
共阴极表示0~9的段码{ 0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f}
这下我们知道怎么让数码管显示数字,那么该怎么选择数码管显示那?这时候我们就要涉及到数码管的位选,在说明之前我们先来了解一下 74HC138
由于单片机引脚IO口稀缺的原因,通常开发板会选择用一些元器件让少的IO口去控制更多的IO口,74HC138就是一个很典型的高速CMOS器件,它可以仅用3根线选择8根线(8选1)的功能,只需要将选中的引脚链接图中ABC,然后再将Y0到Y7的8根线接到想控制的IO口即可,它可以根据ABC的引脚的电平去选择Y0到Y7中的任意一个,但是没有办法同时选择,举个例子:如果A,B口是低电平0,C口为高电平1,那么就可以看成001,选择的是Y0;如果BC为低电平,A为高电平即100,选择的是Y4;从这里可以看出它巧妙地把二进制和选择8根线联系在一起,真的非常令人感叹。这里放出连接8位数码管的仿真图:
这里注意一下一定要选择共阴极的数码管,同时我们也从这个图中看出只要合理的控制ABC3个引脚就可以选择出想要显示的数码管了,但是我们也提过既然不能同时选择多个,那么那些显示那么多的数码管是如何实现的?那就要归功于人眼的暂留了,事实上许多东西都是依靠人眼的暂留实现的。接下来我们就利用此让数码管的8位都显示数字,代码如下:
#include <REGX52.H>
int y;
// 0 1 2 3 4 5 6 7
char smg[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07};
typedef unsigned int u16;
typedef unsigned char u8;
int i=0;
void delay_ms(u16 ms)
{
u16 i,j;
for(i=ms;i>0;i--)
for(j=110;j>0;j--);
}
void nixie(unsigned int i)
{
switch (i)
{
case 0:P2_4=0;P2_3=0;P2_2=0;break;//0
case 1:P2_4=0;P2_3=0;P2_2=1;break;//1
case 2:P2_4=0;P2_3=1;P2_2=0;break;//2
case 3:P2_4=0;P2_3=1;P2_2=1;break;//3
case 4:P2_4=1;P2_3=0;P2_2=0;break;//4
case 5:P2_4=1;P2_3=0;P2_2=1;break;//5
case 6:P2_4=1;P2_3=1;P2_2=0;break;//6
case 7:P2_4=1;P2_3=1;P2_2=1;break;//7
}
}
void main()
{
while(1)
{
for (y=0;y<=8;y++)
{ nixie(y);
P0=smg[y];
delay_ms(5);
}
}
}
其实总而言之就是8个引脚去进行控制数码管的灯,然后再用3个引脚去控制数码管的位置。最终成果如下:
4.8*8LED点阵 播放一段动画
8*8点阵在实际使用上很少用得到,平时做项目通常使用OLED屏,本文主要想记录一下74HC595的使用,74HC595属于序列输入/并行输出的8位移位寄存器。它可以将8个数据位按照一定的顺序输入,然后以并行的形式输出到外部设备。它与74HC138都具有扩展IO口的特点,但是74HC595可以简化控制逻辑:通过使用74HC595,可以将驱动LED矩阵的复杂控制逻辑转移至74HC595上,单片机只需负责通过串行方式发送数据即可。这样可以简化程序的编写和控制逻辑的设计,使代码更加清晰和易于维护。以下为原理图
简而言之就是74HC595
控制8个IO口需要对8个IO口都输入数据(通常为一个16进制数),但是此数据并不是一次都对8个IO口输入,而是一次输入1 0,然后通过移位操作将它们依次加载到寄存器中。最后,同时将加载后的数据并行输出到对应的IO口上。这样就可以实现对8个IO口的控制。以控制8*8LED矩阵为例
开始
初始化IO口和74HC595引脚
循环:
清零SRCLK引脚
清零SER引脚
逐行扫描:
更新数据:
通过SER引脚输入要显示的数据(如字模或字符编码)
数据锁存:
按下SRCLK引脚一次,将输入的数据移位到移位寄存器中
数据加载:
按下RCLK引脚,将移位寄存器中的数据加载到输出寄存器中
逐列显示:
对每一列的LED灯进行控制:
通过输出寄存器的引脚(QA-QH)控制LED灯的亮灭状态
等待一段时间(用于控制刷新频率)
结束
Proteus仿真图如下
注意8*8点阵别接错了。代码如下(记得父亲节母亲节给个惊喜哦)
#include <REGX52.H>
#include "Delay.h"//为了方便提前打包的函数
unsigned char i;
unsigned char n;
unsigned char j=0;
unsigned char y=0;
unsigned char offset=0;
unsigned char count=0;
unsigned char jie[]={
0x00,0x50,0xF0,0x5F,0x50,0xF2,0x5E,0x00,
0x00,0x00,0xFE,0x92,0x92,0x92,0xFE,0x00,
0x10,0xFF,0x11,0x2A,0xFC,0x2A,0x39,0x08,
0x00,0x32,0x54,0x91,0xBF,0x90,0x14,0x02,
0x30,0x48,0x84,0x42,0x42,0x84,0x48,0x30,
0x30,0x48,0x84,0x42,0x42,0x84,0x48,0x30,
};//段码,类似数码管
sbit RCK=P3^5;//搬运.
sbit SCK=P3^6;//把SER输出的值向上移位
sbit SER=P3^4;//输出数值 ser一次一位 74hc595一次8位,高位在先
void _74HC595(unsigned char byte)
{
for(n=0;n<8;n++)
{
SER=byte&(0X80>>n);//取出高位到低位的数
SCK=1;
SCK=0;
}
RCK=1;
RCK=0;
}
void ShowColumn(unsigned char Column,Data)
{
_74HC595(Data);
P0=~(0X80>>Column);//0亮1灭
Delay(1);
P0=0XFF;//消影
}
void main()
{
SCK=0;
RCK=0;
while(1)
{
for(i=0;i<8;i++)
{
ShowColumn(i,jie[i+offset]);
}
count++;
if(count>20)//模拟定时器
{
count=0;
offset=offset+1;//控制帧数
if(offset>40)
{
offset=0;
}
}
}
}
5.各类中断以及利用定时器中断制作秒表
听学长经常说:单片机学的好不好,看一看中断用的怎么样。中断可以说是单片机的精髓所在,那么什么是中断?中断(Interrupt)允许硬件设备、外部信号或软件事件打断CPU正在执行的正常程序流程,使得CPU能够及时处理发生的特定事件或处理紧急任务。 比如在上水课摸鱼时,如果老师下来检查,都会停下手中的活去听老师讲课( •̀ ω •́ )✧,这里把摸鱼是正常程序流程,老师的巡视看成外部信号,是不是就很理解了?
由于C51的中断是通过配置寄存器的方法控制的,为了方便起见我将这些中断寄存器整理出来以便复习与学习
51:5个中断源,3个中断类型 外部中断 定时器中断 串口中断
中断允许寄存器IE:CPU对中断源的开放或中断屏蔽的控制是通过中断允许寄存器IE设置的
IE 位 | D7 D6 D5 D4 D3 D2 D1 D0 |
---|---|
位符号 | EA 0 ET2 ES ET1 EX1 ET0 EX0 |
D7: | EA(总中断使能位):设置为1时,表示允许所有中断源,CPU对中断允许的最高级控制。 |
D6: | 无(保留位) |
D5: | ET2(定时器2中断允许位):用于设置定时器2的中断允许。 |
D4: | ES(串口中断允许位):用于设置串口中断的允许。 |
D3: | ET1(定时器1中断允许位):用于设置定时器1的中断允许。 |
D2: | EX1(外部中断1允许位):用于控制CPU是否对外部引脚P3.3上的中断信号产生响应。 |
D1: | ET0(定时器0中断允许位):用于设置定时器0的中断允许。 |
D0: | EX0(外部中断0允许位):用于控制CPU是否对外部引脚P3.2上的中断信号产生响应。 |
使用方法(所有中断寄存器都可这么配置)
(1)整体赋值:IE=0x81;(开启全局中断,打开外部中断0 )。
(2)单独赋值:EA=1;EX0=1;(开启全局中断,打开外部中断0 )。
配置寄存器就像开开关一样,通过设置特定的位值来控制硬件设备或外围设备的行为和功能,使其适应特定的需求和应用场景。
TCON (定时器/计数器控制寄存器) 用于控制定时器和外部中断的启动和中断标志位的状态。
IE 位 | D7 D6 D5 D4 D3 D2 D1 D0 |
---|---|
位符号 | EA 0 ET2 ES ET1 EX1 ET0 EX0 |
D7: | TF1(定时器1溢出标志位):当定时器1计数溢出时,TF1 置 1,表示定时器1已经溢出。 |
D6: | TR1(定时器1启动控制位):当 TR1=1 时,定时器1启动运行。当 TR1=0 时,定时器1停止运行。 |
D5: | TF0(定时器0溢出标志位):当定时器0计数溢出时,TF0 置 1,表示定时器0已经溢出。 |
D4: | TR0(定时器0启动控制位):当 TR0=1 时,定时器0启动运行。当 TR0=0 时,定时器0停止运行。 |
D3: | IE1(外部中断1中断标志位):当外部中断1引脚 INT1 发生中断时,IE1 置 1,表示有外部中断1发生。 |
D2: | IT1(外部中断1触发方式):当 IT1=0 时,外部中断1使用电平触发方式。当 IT1=1 时,外部中断1使用边沿触发方式(跳变触发方式)。 |
D1: | IE0(外部中断0中断标志位):当外部中断0引脚 INT0 发生中断时,IE0 置 1,表示有外部中断0发生。 |
D0: | IT0(外部中断0触发方式): 当 IT0=0 时,外部中断0使用电平触发方式。当 IT0=1 时,外部中断0使用边沿触发方式(负跳变触发方式)。 |
注:TF0 TF1 当启动T1计数后,定时器/计数器T1从初值开始加1计数,当计数溢出时,由硬件自动为TF1置“1”,向CPU申请中断。CPU响应TF1中断时,TF1标志位由硬件自动清零,TF1也可由软件清零。
SCON (串口控制寄存器) 用于配置和控制单片机的串口通信功能。
scon | D7 D6 D5 D4 D3 D2 D1 D0 |
---|---|
位符号 | 0 0 0 0 0 T1 R1 |
D7~D2 | 无(0) |
D1 | TI:串行口发送中断请求标志位。 |
D0 | RI:串行口接收中断请求标志位。 |
TMOD (定时器模式寄存器) 是用于配置定时器工作模式和工作方式的寄存器。
由于C51具有俩个定时器,所以配置TMOD寄存器首先要选择定时器0或者定时器1(即GATE0或者1)然后根据选择的定时器配置前四位(定时器0)或第四位(定时器1)
TMOD | D7 D6 D5 D4 D3 D2 D1 D0 |
---|---|
位符号 | GATE1 CT1 T1M1 T1M0 GATE1 CT0 T0M1 T0M0 |
D7(定时器1选择位) | GATE1;选择定时器1的开启方式,下文详列 |
D6 | CT1 :CT1=0:定时器1工作在定时器模式,对外部时钟信号进行计数。CT1=1:定时器1工作在计数器模式,对外部时钟信号进行计数。 |
D5 | T1M1 可置零,置一; |
D4 | T1M0 可置零,置一;通过与T1M1联动可控制定时器1的工作模式位,下文列出 |
D3(定时器0选择位) | GATE0;选择定时器0的开启方式,下文详列 |
D2 | CT0 :CT0=0:定时器1工作在定时器模式,对外部时钟信号进行计数。CT0=1:定时器1工作在计数器模式,对外部时钟信号进行计数。 |
D1 | T0M1,作用同上 |
D0 | T0M0,作用同上 |
GATE1 0(定时器1 0启动控制位)用于设置定时器1 0的启动控制方式。具体说明如下(以定时器1为列):
GATE1=0:定时器1由软件控制启动。这意味着定时器1的启动不依赖于外部信号,而是由程序中的相应控制语句触发。
GATE1=1:定时器1由外部信号 INT1(P3.3) 控制启动。在该模式下,定时器1的启动依赖于外部中断引脚 INT1 的状态变化。当外部信号 INT1 发生相应的边沿(上升/下降)变化时,定时器1开始计时
T1M1-T1M0(定时器1工作模式位):
00:定时器1工作在模式0(13位计数器模式)。
01:定时器1工作在模式1(16位计数器模式)。
10:定时器1工作在模式2(8位自动重载定时器模式)。
11:定时器1工作在模式3(自动重载的方式2定时器模式)。
T0M1-T0M0(定时器0工作模式位):
00:定时器0工作在模式0(13位计数器模式)。
01:定时器0工作在模式1(16位计数器模式)。
10:定时器0工作在模式2(8位自动重载定时器模式)。
11:定时器0工作在模式3(自动重载的方式2定时器模式)。
interrupt 0 INT0外部中断0
1 T0定时器/计数器0中断
2 INT1外部中断1
3 T1定时器/计数器1中断
4 TI/RI-串行口中断
这里因为篇幅原因仅用定时器中断做一个秒表示范,仿真图如下
所使用的仿真元器件
代码如下:Nixie(数码管)
#include "Public.h"
void Nixie(unsigned int i)
{
switch (i)
{
case 1:P2_4=1;P2_3=1;P2_2=1;break;
case 2:P2_4=1;P2_3=1;P2_2=0;break;
case 3:P2_4=1;P2_3=0;P2_2=1;break;
case 4:P2_4=1;P2_3=0;P2_2=0;break;
case 5:P2_4=0;P2_3=1;P2_2=1;break;
case 6:P2_4=0;P2_3=1;P2_2=0;break;
case 7:P2_4=0;P2_3=0;P2_2=1;break;
case 8:P2_4=0;P2_3=0;P2_2=0;break;
}
}
void smg_display(u8 dat[],u8 pos)
{
u8 i=0;
u8 pos_temp=pos-1;//???0??
u8 gsmg_code[17]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};
for(i=pos_temp;i<8;i++)
{
switch(i)//??
{
case 0: SMG_LSC=1;SMG_LSB=1;SMG_LSA=1;break;
case 1: SMG_LSC=1;SMG_LSB=1;SMG_LSA=0;break;
case 2: SMG_LSC=1;SMG_LSB=0;SMG_LSA=1;break;
case 3: SMG_LSC=1;SMG_LSB=0;SMG_LSA=0;break;
case 4: SMG_LSC=0;SMG_LSB=1;SMG_LSA=1;break;
case 5: SMG_LSC=0;SMG_LSB=1;SMG_LSA=0;break;
case 6: SMG_LSC=0;SMG_LSB=0;SMG_LSA=1;break;
case 7: SMG_LSC=0;SMG_LSB=0;SMG_LSA=0;break;
}
P0=gsmg_code[dat[i-pos_temp]];
delay_us(100);
P0=0x00;
}
}
Time(定时器)
#include "Public.h"
void time0_init(void)
{
TMOD |= 0x01; //选择定时及工作模式
TH0=0XFC;
TL0=0X18;
ET0=1;
EA=1;
TR0=0;//防止秒表在未启动开始计时,使初值不对.
}
延迟函数(Delay)
void Delay(unsigned int xms)
{
unsigned char i,j;
while(xms--)
{
i = 2;
j = 239;
do
{
while(--j);
} while(--i);
}
}
Public.h
#ifndef _public_H
#define _public_H
#include "regx52.h"
typedef unsigned int u16; //对系统默认数据类型进行重定义
typedef unsigned char u8;
sbit SMG_LSC=P2^4;//数码管
sbit SMG_LSB=P2^3;
sbit SMG_LSA=P2^2;
//74hc595
sbit RCK=P3^5;
sbit SCK=P3^6;
sbit SER=P3^4;
//key
sbit key_1=P3^1;
sbit key_2=P3^0;
sbit key_3=P3^3;
sbit key_4=P3^4;
//EEPROM
sbit IIC_SCL=P2^1;
sbit IIC_SDA=P2^0;
void delay_us(u16 ten_us);
void delay_ms(u16 ms);
#endif
main(主函数)
#include "Nixie.h"
#include "Time.h"
#include "Delay.h"
#include "Public.h"
static int n;
static int sec=0;
static int min=0;
static int ms=0;
sbit key1=P3^1;
sbit key2=P3^0;
sbit key3=P3^2;
sbit key4=P3^3;
sbit led1=P2^0;
unsigned char led[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f};//从0到9的断码。
unsigned char num[8];
unsigned char zero[]={0x3f,0x3f,0x40,0x3f,0x3f,0x40,0x3f,0x3f,};
void Time0() interrupt 1
{
TH0=0xfc;//设置初始值,1ms溢出
TL0=0x18;
ms++;
if(ms==1000)//判断是否为1秒。
{
ms=0;
sec++;
led1=~led1;//检测定时器运行
if(sec==59)
{
sec=0;
min++;
}
}
}
void datapros()//进位换算。
{
num[0]=led[min/10];
num[1]=led[min%10];
num[2]=0x40;
num[3]=led[sec/10];
num[4]=led[sec%10];
num[5]=0x40;
num[6]=led[ms/100];
num[7]=led[ms%100];
}
void Display()
{
for(n=0;n<=8;n++)
{
Nixie(n);
P0=num[n-1];
Delay(1);
P0=0x00;
}
}
void main()
{
time0_init();
while (1)
{
for(n=0;n<=8;n++)//令秒表在未进入循环显示00-00-00。
{
Nixie(n);
P0=zero[n-1];
Delay(1);
P0=0x00;
}
if(key1==0)//启动秒表
{
Delay (20);
while(key1==0);//消抖
Delay (20);
while(1)
{
TR0=1;//启动定时器
datapros();
Display();
if(key2==0)//停止,继续
{
Delay (20);
while(key2==0);
Delay (20);
EA=~EA;//取反符第一次终止定时器,第二次启动定时器。
}
if(key3==0)
{
Delay (20);
while(key3==0);
Delay (20);
ms=0;
sec=0;
min=0;//初始化数据
EA=1;
TR0=0;//关闭定时器防止偷跑造成秒表有初值.
break;//跳出循环以便再次进入
}
}
}
}
}
6.各类寄存器
注,由于使用的历程都是从网上找的原因,此章不做项目
让我们先了解一下RAM,ROM以及Flash三类寄存器:
RAM(Random Access Memory)是一种易失性存储器,特点是运算速度很快,用于存储临时数据和变量。它可以随机读写数据,但断电后数据会丢失。
ROM(Read-Only Memory)是一种只读存储器,用于存储程序的固定和不可修改的部分。它包含了主程序的指令和固定的数据。
Flash是一种非易失性存储器,用于存储可重新编程的程序和数据。它类似于ROM,但可以进行擦除和编程操作,同时也具有较高的运算。
这三种存储器在计算机和嵌入式系统中起着不同的作用:
RAM用于暂时存储运行时数据、堆栈和其他临时存储需求。
ROM存储器用于存储主程序的不可修改的指令和数据,一般包括启动代码和固定的配置。
Flash存储器用于存储可重新编程的程序和数据,可在系统运行期间进行更新和修改。它常用于嵌入式系统中的代码存储、应用程序存储和配置数据存储等。
C51 单片机是一种常见的 8051 微控制器系列,它具有内置的 RAM(易失性存储器)、ROM(只读存储器) 和 Flash存储器。合理使用寄存器是做项目的基本要求,举一个例子:通过一段时间的学习,尤其是秒表的项目,我们可以看出来单片机的数据会在重启之后重置,这是因为我们平常使用的寄存器是易失性存储器的一种,比如静态随机存储器(SRAM)。
它的存储内容在断电或重启后会丢失。单片机中的寄存器通常采用 SRAM 存储结构,因此在重启之后,寄存器中存储的数据会被清空,都会被重置为初始值。
这也是为什么在单片机开发中,我们通常需要在代码中进行初始化,即在系统启动时为寄存器设置初始值。这样,即使单片机重启或断电后再次启动,寄存器中的数据也会被正确地初始化,确保系统正常运行。
对于一些需要持久存储的数据,例如计时器的计时值或其他系统状态,我们可以使用非易失性存储器(Non-Volatile Memory)来保存这些数据。例如,可以使用闪存存储器(Flash Memory)来存储这些重要数据,闪存具有持久性,即使断电或重启后,数据仍然保持不变。
因此,在设计单片机项目时,需要根据具体需求选择适当的存储器类型来保存关键数据和配置信息,以确保数据的持久性和系统的正常运行。这里为了方便,我简单做了一些笔记以便分辨各类寄存器:
RAM(随机存取存储器):
特点:RAM 是一种存储器,具有快速的读写能力和随机存取的特点。它用于存储正在运行的程序、数据和临时信息。RAM 的数据在断电后会丢失,因此需要持续供电以保持数据的存储。RAM 可以通过读取和写入操作来存取数据,存取速度较快。
分类:
SRAM(静态随机存取存储器):SRAM 使用触发器来存储数据位,由于内部电路的复杂性,它的存储密度较低,但具有较快的访问速度和较低的功耗。SRAM 通常用于高速缓存等需要快速访问的应用。
DRAM(动态随机存取存储器):DRAM 使用电容来存储数据位,由于电容的特性,它需要周期性地进行刷新操作来保持数据的存储。DRAM 的存储密度较高,但相对于 SRAM,其访问速度较慢。
ROM(只读存储器):
特点:ROM 是一种存储器,用于存储程序、数据或固定的信息。与 RAM 不同,ROM 的数据是在制造过程中被写入的,并且在断电后仍然保持不变。因此,ROM 是一种非易失性存储器,不需要持续供电来保持数据。
分类:
PROM(可编程只读存储器):PROM 是一种可以被一次性编程的 ROM,一旦编程后,数据将无法修改。编程通常通过提供特定的电压或光照进行。
EPROM(可擦可编程只读存储器):EPROM 是一种可以被多次擦除和编程的 ROM。它使用特定的设备(如紫外线擦除器)来擦除数据,并使用编程电压进行重写。擦除后的 EPROM 可以重新编程多次。
EEPROM(电可擦可编程只读存储器):EEPROM 是一种电子擦除和可编程的 ROM。它通过电子擦除和编程操作来存储和修改数据。EEPROM 将数据以字节为单位进行擦除和编程,可以多次进行擦写和编程操作。
RAM(随机存取存储器):
特点:RAM 是一种存储器,具有快速的读写能力和随机存取的特点。它用于存储正在运行的程序、数据和临时信息。RAM 的数据在断电后会丢失,因此需要持续供电以保持数据的存储。RAM 可以通过读取和写入操作来存取数据,存取速度较快。
分类:
SRAM(静态随机存取存储器):SRAM 使用触发器来存储数据位,由于内部电路的复杂性,它的存储密度较低,但具有较快的访问速度和较低的功耗。SRAM 通常用于高速缓存等需要快速访问的应用。
DRAM(动态随机存取存储器):DRAM 使用电容来存储数据位,由于电容的特性,它需要周期性地进行刷新操作来保持数据的存储。DRAM 的存储密度较高,但相对于 SRAM,其访问速度较慢。
ROM(只读存储器):
特点:ROM 是一种存储器,用于存储程序、数据或固定的信息。与 RAM 不同,ROM 的数据是在制造过程中被写入的,并且在断电后仍然保持不变。因此,ROM 是一种非易失性存储器,不需要持续供电来保持数据。
分类:
PROM(可编程只读存储器):PROM 是一种可以被一次性编程的 ROM,一旦编程后,数据将无法修改。编程通常通过提供特定的电压或光照进行。
EPROM(可擦可编程只读存储器):EPROM 是一种可以被多次擦除和编程的 ROM。它使用特定的设备(如紫外线擦除器)来擦除数据,并使用编程电压进行重写。擦除后的 EPROM 可以重新编程多次。
EEPROM(电可擦可编程只读存储器):EEPROM 是一种电子擦除和可编程的 ROM。它通过电子擦除和编程操作来存储和修改数据。EEPROM 将数据以字节为单位进行擦除和编程,可以多次进行擦写和编程操作。
Flash 存储器:
特点:Flash 是一种非易失性存储器,可以随机读取和写入数据。它通常由一系列的存储单元组成,每个存储单元可以存储一个二进制位,也被称为 “闪存单元”。与 RAM 不同,Flash 存储器的数据在断电后仍然保持不变。
关系:Flash 存储器在某种程度上结合了 RAM 和 ROM 的特性。它可以像 RAM 那样被随机读取和写入,但与 ROM 类似,它的数据是非易失性的,在断电后仍然保持。Flash 存储器还具有可擦除和可编程的特性,使其能够多次更新和修改存储的数据。