赞
踩
51单片机的最小系统由晶振电路、复位电路和电源电路组成
下面对片内各部件做简单介绍:
PDIP封装的引脚分布:
40 引脚我们 按其功能类别可分为四类:
①电源引脚。如 VCC、GND
②时钟引脚。如 XTAL1、XTAL2
③控制引脚。如 RST、PSEN(29号引脚)、ALE/PROG(30号引脚)、EA/Vpp(31号引脚)
④I/O 口引脚。如 P0、P1、P2、P3,4 组 8 位并行 I/O 口
注:单片机的内部 有程序存储器(ROM),它的作用是用来存放用户需要执行的程序,那么我们怎 样才能将写好的程序存入这个 ROM 中呢?实际上,我们是通过编程脉冲输入才写 进去的,这个脉冲的输入端口就是 PROG。现在绝大多数单片机都已经不需要编 程脉冲引脚往内部写程序了,比如我们使用的 STC 单片机,它可以直接通过串口 往里面写程序
首先,先简单了解一下各部分
P0口的工作原理
(1)作为I/O端口使用
P0 口作为 通用I/O输出口使用时,多路开关的控制信号为 0(低电平),与门输出为0,V1 管就截止;
多路开关是与锁存器的 Q 非端相接的(即 P0 口作为 I/O 口线使用)。来自CPU的“写”脉冲家在CLK(CP)端,内部总线上的数据写入D锁存器并由引脚P0.x输出。当D锁存器为1时,Q非为0,V2截止,输出为漏极开路输出,此时必须外接上拉电阻才能有高电平输出;当D锁存器为0时,Q非为1,V2导通,输出为低电平
P0口作为通用I/O输入口时,有两种方式:读锁存器和读引脚
当CPU发出“读锁存器”指令时,锁存器的状态由Q端经过上方的输入缓冲器进入内部总线;
当CPU发出“读引脚”指令时,锁存器的输出状态为1(Q非端为0),使V2截止,端口线已处于高阻态,引脚的状态经过下方的输入缓冲器进入内部总线;
(2)作为系统的地址/数据总线
当P0口作为地址/数据总线使用,“控制”信号为1,多路开关接向上面
当“地址/数据”信息为1,非门输出为0,与门输出为1,V1导通,V2截止,P0.x引脚输出1
当“地址/数据”信息为0,非门输出为1,与门输出为0,V2导通,V1截止,P0.x引脚输出0
可见:P0.x引脚的输出状态随着“地址/数据”控制信号的变化而变化
当P0口作为数据输入时,仅从外部存储器读入信息,对应的控制信号为0,多路开关接到Q非端。由于P0口作为“地址/数据”复用方式访问外部存储器时,CPU自动向P0口写入FFH,使V2截止;同时控制信号为0,V1也截止,从而保证数据信息的高阻抗输入,直接由P0.x引脚通过下方的输入缓冲器进入内部总线
总结:
P1口的工作原理:
只能作为I/O端口使用
P0 口作为 通用I/O输出口使用时,当D锁存器为1时,Q非为0,V2截止,P1.x输出高电平;当D锁存器为0时,Q非为1,V2导通,P1.x输出为低电平
P0口作为通用I/O输入口时,有两种方式:读锁存器和读引脚
当CPU发出“读锁存器”指令时,锁存器的状态由Q端经过上方的输入缓冲器进入内部总线;
当CPU发出“读引脚”指令时,先向锁存器写入1(Q非端为0),使V2截止,引脚的状态经过下方的输入缓冲器进入内部总线;
总结:
P2口的工作原理
(1)作为I/O端口使用
P2 口作为 通用I/O输出口使用时,多路开关的控制信号为 0(低电平),多路开关是与锁存器的 Q 端相接的。当D锁存器为1时,Q为1,V2截止,输出高电平;当D锁存器为0时,Q为0,V2导通,输出为低电平
P2口作为通用I/O输入口时,有两种方式:读锁存器和读引脚
当CPU发出“读锁存器”指令时,锁存器的状态由Q端经过上方的输入缓冲器进入内部总线;
当CPU发出“读引脚”指令时,先向锁存器写入1(Q端为1),使V2截止,引脚的状态经过下方的输入缓冲器进入内部总线;
(2)作为系统的地址总线
当P0口作为地址/数据总线使用,“控制”信号为1,多路开关接向上面
当“地址”信息为0,V2导通,P2.x引脚输出0
当“地址”信息为1,V2截止,P0.x引脚输出1
总结:
P3口的工作原理:
(1)第一功能:通用I/O口
总结:
(1)运算器:
(2)控制器:
STC89C52片内拥有8K程序存储器和512B数据存储器
程序存储器,又称为只读存储器(Read Only Memory,ROM),掉电不丢失,用于存放用户程序、数据和表格等信息.STC89C52的片内程序存储器为8KB的Flash存储器,地址范围为:0000H~1FFFH
当若EA非引脚接高电平,单片机首先从片内程序存储器的0000H单元开始执行程序,当PC的内容超过1FFFH时系统自动转到片外程序存储器中取指令
中断服务程序的入口地址,又称中断向量,也位于程序存储器单元。在程序存储器中,每个中断都有一个固定的入口地址,当中断发生并得到响应后,单片机就会自动跳转到相应的中断入口地址去执行程序。
由于相邻中断入口地址的间隔区间(8个字节)有限,一般情况下无法保存完整的中断服务程序,因此,一般在中断响应的地址区域存放一条无条件转移指令,指向真正存放中断服务程序的空间去执行。
STC89C52单片机的RAM分两部分,一部分是内部RAM(256字节),一部分是内部扩展RAM(256字节)
256字节的RAM又分低128字节内部RAM(0x00–0x7F)和高128字节内部RAM(0x80–0xFF);
数据存储器,又称为随机访问存储器(Random Access Memory,RAM),掉电丢失,通常存储程序中的变量
特殊功能寄存器(SFR)的单元地址映射在片内RAM区的80H~FFH区域中
凡是可以进行位寻址的SFR,其字节地址的末位只能是0H或8H
堆栈指针主要是为了子程序调用和中断操作而设立的,具体功能:保护断点和现场保护(暂存数据和地址)
①保护断点:无论是子程序调用或中断服务子程序调用,主程序都会被“打断”。需要把主程序的断点在堆栈中保护起来,为程序的正确返回做准备
②现场保护:在执行子程序或中断服务子程序,可能会用到一些寄存器,这就会破坏主程序运行时这些寄存器的原有内容。所在在执行子程序或中断服务程序之前,先把有关寄存器的内容保存起来,送入堆栈
两种低功耗节电模式:空闲模式和掉电保持模式
#include <REGX52.H> //P2寄存器的字节地址0xA0,可以进行位寻址 //将P2.0引脚定义为LED1 sbit LED1 = P2^0; void main() { while(1) { //P2 = 0xFE; //1111 1110 LED1 = 0; //P2_0=0 } }
答:其任务是不间断地执行特定的功能或任务,例如控制外部设备 、采集数据、处理输入等。 通过将主 程序设计为死循环,可以确保程序在完成一轮任务后立即开始下一轮,不会自行终止。目的是为了让程序一直保持在需要的运行的情况下
不加while(1)的情况下,当main()函数执行完毕之后,会继续跳转到main()函数的首行继续执行。如果程序指针PC在程序结束到跳转到main()函数之前,会读取不确定的指令,从而出现意想不到的结果
其次,在嵌入式C程序中,main()函数是不能返回的
我们在单片机中使用while(1),大部分还是为了防止程序跑飞,因为很多时候执行完某段程序后单片机的程序指针PC(就是程序指针)并不会停止,仍然会继续从ROM中读取指令并执行,这样一来可能会出现程序跑飞的情况,进而出现不确定的结果,我们加个while(1)就能让程序在执行完后在原地循环,相当于停在原地,防止跑飞。
while(1);
意义:这是一个死循环,代码不再向下执行。
(1)STC-ISP中软件延时器生成的延时函数(修改版)
void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
注:此函数是在STC-ISP中生成的延时1ms的函数,然后对其增加一个参数,进行修改而来
(2)利用循环语句
void Delay(uint i)
{
uchar t;
while(i--)
{
for(t = 0;t < 120;t++);
}
}
注:上述两种延时函数,均不精确
#include <REGX52.H> #include "intrins.h" typedef unsigned int uint; typedef unsigned char uchar; sbit LED1 = P2^0; /* void Delay(uint i) { uchar t; while(i--) { for(t = 0;t < 120;t++); } } */ /* 函数功能:延时 参数类型:无符号的整型 参数说明:xms:延时的毫秒 */ void Delay(uint xms) //@12.000MHz { uchar i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } } void main() { while(1) { //P2 = 0xFE; //1111 1110 LED1 = 0; //P2_0=0 Delay(500); LED1 = 1; //P2_0=1 Delay(500); } }
根据前两个知识点的学习,所谓流水灯,即需要8个数码管在同一时间只亮一个,之后延时;以固定方向移动,使相邻的LED点亮,之后延时
#include <REGX52.H> void Delay(unsigned int xms) //@12.000MHz { unsigned char i, j; while(xms--) { i = 12; j = 169; do { while (--j); } while (--i); } } void main() { while(1) { P2=0xFE; Delay(100); P2=0xFD; Delay(100); P2=0xFB; Delay(10); P2=0xF7; Delay(100); P2=0xEF; Delay(100); P2=0xDF; Delay(100); P2=0xBF; Delay(100); P2=0x7F; Delay(100); } }
#include <REGX52.H> #include "intrins.h" #define LED_PORT P2 typedef unsigned int uint; typedef unsigned char uchar; /* 函数功能:延时 参数类型:无符号的整型 参数说明:xms:延时的毫秒 */ void Delay(uint xms) //@12.000MHz { uchar i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); xms--; } } void main() { LED_PORT = 0xFE; //1111 1110 while(1) { Delay(500); LED_PORT = _crol_(LED_PORT,1); } }
#include <REGX52.H> #define LED_PORT P2 //将P2定义为LED_PORT typedef unsigned int uint; typedef unsigned char uchar; /* 函数功能:延时 参数类型:无符号的整型 参数说明:xms:延时的毫秒 */ void Delay(uint xms) //@12.000MHz { uchar i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); xms--; } } void main() { uchar i,temp; while(1) { temp = 0x01; //0000 0001 for(i = 0;i < 8;i++) { LED_PORT = ~temp; Delay(500); temp = temp<<1; } } } /* void main() { uchar i; while(1) { for(i = 0;i < 8;i++) { LED_PORT = ~(0x01<<i); Delay(500); } } } */
该程序中为什么要使用temp中间变量呢?
(1)假如不使用临时变量,由于移位运算符的特性,回出现同一时间多个LED点亮,达不到流水灯的效果
(2)假如temp = 0xFE;会发现和第一种情况如出一辙
利用移位运算符的特性,高位或低位丢失,另一端补0
将temp = 0x01;之后进行取反,送入P2口中,延时一段时间后,对临时变量进行左移1位(让1移位,达到移位的目的,补的0对1毫无影响,取反之后,不会出现多个0的情况)
通常的按键所用开关为机械弹性开关,当机械触点断开、闭合时,电压信号 如下图所示:
按键抖动会引起按键被误读多次,为了确保 CPU 对按键的一次闭合仅作一次处理,必须进行消抖。 按键消抖有两种方式,一种是硬件消抖,另一种是软件消抖。为了使电路更 加简单,通常采用软件消抖。
#include <REGX52.H> void Delay(unsigned int xms) //@12.000MHz { unsigned char i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } } void main() { while(1) { if(0 == P3_1) { Delay(20); while(0 == P3_1); Delay(20); } } }
当然,软件消抖也有一定的弊端,通过延时来达到目的,但在延时过程中,CPU不会进行其他工作,非常占耗CPU资源
软件思想:先判断按键是否按下,如果按下,进行按键消抖;继续判断按键是否松开,如果松开,进行按键消抖;之后进行相应的操作
#include <REGX52.H> void Delay(unsigned int xms) //@12.000MHz { unsigned char i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } } void main() { while(1) { if(0 == P3_1) { Delay(20); while(0 == P3_1); Delay(20); P2_0 = ~P2_0; //LED1进行点亮、熄灭交替进行 } } }
#include <REGX52.H> void Delay(unsigned int xms) //@12.000MHz { unsigned char i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } } void main() { unsigned char LED_Num = 0; while(1) { if(0 == P3_1) { Delay(20); //消抖 while(0 == P3_1); Delay(20); //消抖 //P2++; //LED熄灭的状态表示的二进制 //P2++˙ //0000 0000 //P2 = ~P2; //1111 1111 LED_Num++; P2 = ~LED_Num; } } }
分析:
(1)如果按键按下之后,操作语句仅仅为P2++,上电之后P2端口默认为高电平,P2++导致该端口的8个引脚全部为0,即LED全亮,每次加1,则对应二进制的LED为熄灭状态,恰恰与其相反
(2)如果按键按下之后,操作语句为P2++;P2=~P2,则会发现无论按键按下多少次,LED均处于熄灭状态。其原因为:P2端口上电后为高电平,加1之后为低电平,取反之后又回到高电平,一直在循环
(3)为想要达到既定功能,需要增加一个临时变量,对临时变量进行操作,将操作后的临时变量送入P2端口中,即可
当多个引脚作为输入端时,大多数情况下不直接操作作为输入端的引脚,而是增加临时变量,对其进行操作之后,再送入引脚中
#include <REGX52.H> void Delay(unsigned int xms) //@12.000MHz { unsigned char i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } } void main() { unsigned char LED_Num = 0; P2 = ~0x01; while(1) { if(0 == P3_1) //K1控制LED左移 { Delay(20); //消抖 while(0 == P3_1); Delay(20); //消抖 LED_Num++; /* 0000 0001 0x01<<0 0000 0010 0x01<<1 0000 0100 0x01<<2 0000 1000 0x01<<3 0001 0000 0x01<<4 0010 0000 0x01<<5 0100 0000 0x01<<6 1000 0000 0x01<<7 */ if(LED_Num > 7) { LED_Num = 0; } P2 = ~(0x01<<LED_Num); } if(0 == P3_0) //K2控制LED右移 { Delay(20); //消抖 while(0 == P3_0); Delay(20); //消抖 LED_Num--; //该变量为无符号变量,当0-1后,变为最大值 if(LED_Num > 7) { LED_Num = 7; } /* if(0 == LED_Num) //LED1处于点亮状态,当再次按下K2,需要使LED8点亮 { LED_Num = 7; } else //LED1处于熄灭状态,变量就正常递减 { LED_Num--; } */ P2 = ~(0x01<<LED_Num); } } }
#include <REGX52.H> void Delay(unsigned int xms) //@12.000MHz { unsigned char i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } } void main() { unsigned char temp = 0; while(1) { P1 = 0xFF; //P1口置为高电平 temp = P1 & 0xF0; //屏蔽低四位,获取高四位 P1 = (temp>>4); //将temp的数据右移4位,之后送入P1口中 } }
main.c
#include <REGX52.H> #include "MatrixKey.h" #include "LCD1602.h" void main() { unsigned char Key_Num; LCD_Init(); LCD_ShowString(1,1,"KeyNum:"); while(1) { Key_Num = MatrixKey_Num(); if(Key_Num) { LCD_ShowNum(1,8,Key_Num,2); } } }
MatrixKey.c
#include <REGX52.H> #include "Delay.h" /** * @brief 列扫描,获取矩阵键码 * @param 无 * @retval Key_Num:按下的矩阵键码,范围:1~16 */ unsigned char MatrixKey_Num() { unsigned char Key_Num = 0; P1 = 0xFF; //P1端口全置为高电平 P1_3 = 0; //将第一列置为低电平 if(0 == P1_7) { Delay(20); while(0 == P1_7); Delay(20); Key_Num = 1; } if(0 == P1_6) { Delay(20); while(0 == P1_6); Delay(20); Key_Num = 5; } if(0 == P1_5) { Delay(20); while(0 == P1_5); Delay(20); Key_Num = 9; } if(0 == P1_4) { Delay(20); while(0 == P1_4); Delay(20); Key_Num = 13; } P1 = 0xFF; //P1端口全置为高电平 P1_2 = 0; //将第二列置为低电平 if(0 == P1_7) { Delay(20); while(0 == P1_7); Delay(20); Key_Num = 2; } if(0 == P1_6) { Delay(20); while(0 == P1_6); Delay(20); Key_Num = 6; } if(0 == P1_5) { Delay(20); while(0 == P1_5); Delay(20); Key_Num = 10; } if(0 == P1_4) { Delay(20); while(0 == P1_4); Delay(20); Key_Num = 14; } P1 = 0xFF; //P1端口全置为高电平 P1_1 = 0; //将第三列置为低电平 if(0 == P1_7) { Delay(20); while(0 == P1_7); Delay(20); Key_Num = 3; } if(0 == P1_6) { Delay(20); while(0 == P1_6); Delay(20); Key_Num = 7; } if(0 == P1_5) { Delay(20); while(0 == P1_5); Delay(20); Key_Num = 11; } if(0 == P1_4) { Delay(20); while(0 == P1_4); Delay(20); Key_Num = 15; } P1 = 0xFF; //P1端口全置为高电平 P1_0 = 0; //将第四列置为低电平 if(0 == P1_7) { Delay(20); while(0 == P1_7); Delay(20); Key_Num = 4; } if(0 == P1_6) { Delay(20); while(0 == P1_6); Delay(20); Key_Num = 8; } if(0 == P1_5) { Delay(20); while(0 == P1_5); Delay(20); Key_Num = 12; } if(0 == P1_4) { Delay(20); while(0 == P1_4); Delay(20); Key_Num = 16; } return Key_Num; }
Delay.c
void Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
LCD1602.c
#include <REGX52.H> //引脚配置: sbit LCD_RS=P2^6; sbit LCD_RW=P2^5; sbit LCD_EN=P2^7; #define LCD_DataPort P0 //函数定义: /** * @brief LCD1602延时函数,12MHz调用可延时1ms * @param 无 * @retval 无 */ void LCD_Delay() { unsigned char i, j; i = 2; j = 239; do { while (--j); } while (--i); } /** * @brief LCD1602写命令 * @param Command 要写入的命令 * @retval 无 */ void LCD_WriteCommand(unsigned char Command) { LCD_RS=0; LCD_RW=0; LCD_DataPort=Command; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602写数据 * @param Data 要写入的数据 * @retval 无 */ void LCD_WriteData(unsigned char Data) { LCD_RS=1; LCD_RW=0; LCD_DataPort=Data; LCD_EN=1; LCD_Delay(); LCD_EN=0; LCD_Delay(); } /** * @brief LCD1602设置光标位置 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @retval 无 */ void LCD_SetCursor(unsigned char Line,unsigned char Column) { if(Line==1) { LCD_WriteCommand(0x80|(Column-1)); } else if(Line==2) { LCD_WriteCommand(0x80|(Column-1+0x40)); } } /** * @brief LCD1602初始化函数 * @param 无 * @retval 无 */ void LCD_Init() { LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵 LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关 LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动 LCD_WriteCommand(0x01);//光标复位,清屏 } /** * @brief 在LCD1602指定位置上显示一个字符 * @param Line 行位置,范围:1~2 * @param Column 列位置,范围:1~16 * @param Char 要显示的字符 * @retval 无 */ void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char) { LCD_SetCursor(Line,Column); LCD_WriteData(Char); } /** * @brief 在LCD1602指定位置开始显示所给字符串 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param String 要显示的字符串 * @retval 无 */ void LCD_ShowString(unsigned char Line,unsigned char Column,char *String) { unsigned char i; LCD_SetCursor(Line,Column); for(i=0;String[i]!='\0';i++) { LCD_WriteData(String[i]); } } /** * @brief 返回值=X的Y次方 */ int LCD_Pow(int X,int Y) { unsigned char i; int Result=1; for(i=0;i<Y;i++) { Result*=X; } return Result; } /** * @brief 在LCD1602指定位置开始显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~65535 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以有符号十进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:-32768~32767 * @param Length 要显示数字的长度,范围:1~5 * @retval 无 */ void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length) { unsigned char i; unsigned int Number1; LCD_SetCursor(Line,Column); if(Number>=0) { LCD_WriteData('+'); Number1=Number; } else { LCD_WriteData('-'); Number1=-Number; } for(i=Length;i>0;i--) { LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0'); } } /** * @brief 在LCD1602指定位置开始以十六进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~0xFFFF * @param Length 要显示数字的长度,范围:1~4 * @retval 无 */ void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i,SingleNumber; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { SingleNumber=Number/LCD_Pow(16,i-1)%16; if(SingleNumber<10) { LCD_WriteData(SingleNumber+'0'); } else { LCD_WriteData(SingleNumber-10+'A'); } } } /** * @brief 在LCD1602指定位置开始以二进制显示所给数字 * @param Line 起始行位置,范围:1~2 * @param Column 起始列位置,范围:1~16 * @param Number 要显示的数字,范围:0~1111 1111 1111 1111 * @param Length 要显示数字的长度,范围:1~16 * @retval 无 */ void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length) { unsigned char i; LCD_SetCursor(Line,Column); for(i=Length;i>0;i--) { LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0'); } }
#include <REGX52.H> #include "MatrixKey.h" #include "LCD1602.h" #define PASSWORD 1123 //初始密码 void main() { unsigned char key_num = 0,count = 0; unsigned int password = 0; LCD_Init(); LCD_ShowString(1,1,"PassWord:"); while(1) { key_num = MatrixKey_Num(); if(key_num) { if(key_num <= 10) //按键S1~S10,密码输入,期中S10定义为0 { if(count < 4) { password *= 10; //左移一位 password += (key_num % 10); //个位的数字 count++; //每按下一次,计次加1 } LCD_ShowNum(1,10,password,4); LCD_ShowString(2,1," "); } } if(12 == key_num) //按键S12作为确认键 { if(password == PASSWORD) //密码正确,显示OK,并且清零,为下次输入做准备 { LCD_ShowString(2,1,"OK"); password = 0; count = 0; } else //密码错误,需要按下取消键,重新输入 { LCD_ShowString(2,1,"ERROR"); } } if(11 == key_num) //按键S11作为撤销键 { password = 0; count = 0; LCD_ShowNum(1,10,password,4); LCD_ShowString(2,1," "); } } }
数码管的电路原理图:
C | B | C | Y |
---|---|---|---|
0 | 0 | 0 | 0 |
0 | 0 | 1 | 1 |
0 | 1 | 0 | 2 |
0 | 1 | 1 | 3 |
1 | 0 | 0 | 4 |
1 | 0 | 1 | 5 |
1 | 1 | 0 | 6 |
1 | 1 | 1 | 7 |
#include <REGX52.H> typedef unsigned int uint; typedef unsigned char uchar; //共阴极段码表 uchar LED_Num[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; /* 功能:选中某一位数码管,显示字符 参数类型:无符号字符型 参数: Location:从右至左,依次为1~8 Number:显示相应的数字字符 */ void Nixie_Tube(uchar Location,Number) { switch(Location) { case 1:P2_4 = 0;P2_3 = 0;P2_2 = 0;break; case 2:P2_4 = 0;P2_3 = 0;P2_2 = 1;break; case 3:P2_4 = 0;P2_3 = 1;P2_2 = 0;break; case 4:P2_4 = 0;P2_3 = 1;P2_2 = 1;break; case 5:P2_4 = 1;P2_3 = 0;P2_2 = 0;break; case 6:P2_4 = 1;P2_3 = 0;P2_2 = 1;break; case 7:P2_4 = 1;P2_3 = 1;P2_2 = 0;break; case 8:P2_4 = 1;P2_3 = 1;P2_2 = 1;break; } P0 = LED_Num[Number]; } void main() { while(1) { Nixie_Tube(6,6); } }
#include <REGX52.H> typedef unsigned int uint; typedef unsigned char uchar; uchar LED_Num[] = {0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F}; void Delay(uint xms) //@12.000MHz { unsigned char i, j; while(xms--) { i = 2; j = 239; do { while (--j); } while (--i); } } void Nixie_Tube(uchar Location,Number) { switch(Location) { case 1:P2_4 = 0;P2_3 = 0;P2_2 = 0;break; case 2:P2_4 = 0;P2_3 = 0;P2_2 = 1;break; case 3:P2_4 = 0;P2_3 = 1;P2_2 = 0;break; case 4:P2_4 = 0;P2_3 = 1;P2_2 = 1;break; case 5:P2_4 = 1;P2_3 = 0;P2_2 = 0;break; case 6:P2_4 = 1;P2_3 = 0;P2_2 = 1;break; case 7:P2_4 = 1;P2_3 = 1;P2_2 = 0;break; case 8:P2_4 = 1;P2_3 = 1;P2_2 = 1;break; } P0 = LED_Num[Number]; Delay(1); P0 = 0x00; //消影 } void main() { while(1) { Nixie_Tube(8,1); Nixie_Tube(7,2); Nixie_Tube(6,3); } }
(1)TF1:片内定时器/计数器T1的溢出中断请求标志位
当启动T1计数后,定时器/计数器T1从初值开始加1计数,当计数溢出时,由硬件自动置1,向CPU申请中断。CPU响应TF1中断时,TF1标志位由硬件自动清零,TF1也可由软件清零
(2)TF0:片内定时器/计数器T0的溢出中断请求标志位
(3)IE1:外部中断请求1的中断请求标志位
(4)IE0:外部中断请求0的中断请求标志位
(5)IT1:选择外部中断请求1位负跳变触发方式还是电平触发方式
IT1 = 0,低电平触发方式;IT1 = 1,负跳变触发方式。当CPU检测到 P3.3引脚上出现有效的中断信号时,中断请求标志IE1置 1,向 CPU 申请中断。
(6) IT0:选择外部中断请求0位负跳变触发方式还是电平触发方式
IE的字节地址为A8H,可进行位寻址
中断允许寄存器IE对中断的允许对中断的允许和禁止实现两级控制:一个中断允许总开关EA和各中断源的中断允许控制位
1——中断请求被允许,0——中断请求被屏蔽
(1)EA:中断允许总开关控制位
(2)ES:串行口中断允许控制位
(3)ET1:定时器/计数器1的溢出中断允许控制位
(4)EX1:外部中断1中断允许控制位
(5)ET0:定时器/计数器0的溢出中断允许控制位
(6)EX0:外部中断0中断允许控制位
单片机复位后,IE被清零,所有中断请求都被禁止
中断优先级寄存器IP,其字节地址为B8H,可位寻址
1——高优先级,0——低优先级
(1)PS:串行口中断优先级控制位
(3)PT1:定时器/计数器1的溢出中断优先级控制位
(4)PX1:外部中断1中断优先级控制位
(5)PT0:定时器/计数器0的溢出中断优先级控制位
(6)PX0:外部中断0中断优先级控制位
单片机复位后,IP被清零,各个中断源均为低优先级中断
当同时收到多个同一优先级的中断请求时,中断请求的响应取决于内部的查询顺序:
中断响应还遵循以下规则:
一个中断源的中断请求被响应,必须满足以下条件
中断响应的过程:首先将程序计数器PC的内容压入堆栈中,以保护断点,再将中断入口地址装入PC计数器,使程序转向响应中断请求的中断入口地址
可以看出两个中断入口只相隔8字节,无法存放一个完整的中断服务程序。因此,通常在中断入口地址放置了一条无条件转移指令,使程序执行转向在其他地址存放的中断服务程序入口
CPU定期在每个机械周期都要查询各中断源的中断请求标志,并不是查询到的所有中断都能立即被响应,当遇到以下情况,中断被封锁
(1)CPU正在处理同级或更高优先级的中断
(2)查询出中断请求的机械周期不是当前正在执行指令的最后一个机械周期。只有指令执行完成后,才能响应中断
由于C51编译器在编译时对中断服务函数自动添加了现场保护、阻断其他中断、返回时自动恢复现场等处理的程序段,编写中断服务程序不必考虑这些问题
一般形式:
void 函数名() interrupt n using n
(1)关键字interrupt后面的n是中断号,n取值0~4,编译器从8Xn+3处产生中断向量
(2)using后面的n用来选择工作寄存区。
在中断函数的入口处将当前的工作寄存器区的内容保护到堆栈中,函数返回之前将被保护的寄存器区的内容从堆栈中恢复。如果不用该关键字,中断函数中的所有工作寄存器的内容将被保护到堆栈中
51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机的内部完成
定时器/计数器和单片机的 CPU 是相互独立的。定时器/计数器工作的过程 是自动完成的,不需要 CPU 的参与
51 单片机中的定时器/计数器是根据机器内部的时钟或者是外部的脉冲信 号对寄存器中的数据加 1
M1 M0 | 工作方式 |
---|---|
0 0 | 方式0,13位(TLx的低五位和THx的整个8位)定时器/计数器 |
0 1 | 方式1,16位定时器/计数器 |
1 0 | 方式2,8位自动重新装载的定时器/计数器,低8位存放的值自动存放在高8位 |
1 1 | 方式3,T0分成2个8位计数器/定时器,T1停止计数 |
方式1除了使用整个THx和TLx之外,其他的全都和方式0相同
TL0的溢出不仅置位TF0,而且将TH0内容重新装入TL0,TH0内容由软件预置,重装时TH1内容不变
STC89C52列单片机内部集成有一个全双工通用异步收发串行通信口
并行通信适合短距离的数据传输
2. 异步串行通信
串口有两个物理上独立的接收、发送缓冲器SBUF(特殊功能寄存器),可以同时发送、接收数据。发送缓冲器只能写入不能读出,接收缓冲器只能读出不能写入,两者公用同一个字节地址99H
字节地址98H,可位寻址
波特率:每秒传输二进制位数,单位是bps
定时器溢出率:定时器溢出的频率,即1/溢出一次所用的时间
对于12分频而言:
定时器频率:f / 12,计数一次所需要的时间:12 / f
溢出一次所需要的时间:(最大值 - 初值)x (12 / f)
字节地址:87H,不可位寻址
LED点阵屏由若干个独立的LED组成,以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等
分类:
(1)按颜色:单色、双色、全彩(红绿蓝)
(2)按色素:88、1616等(大规模的LED点阵通常由很多个小点阵拼接而成)
LED点阵电路图
74HC595电路
开发板上引脚对应关系
串行数据引脚SER将数据放在左边的移位寄存器中,串行时钟引脚SECLK每来一个上升沿,数据往下移一位。当寄存器时钟引脚RCLK来一个上升沿,会将移位寄存器的数据同时移动到输出端,进行并行输出
DS1302 是 DALLAS 公司推出的涓流充电时钟芯片,内含有一个实时时钟/日历和 31字节静态 RAM,通过简单的串行接口与单片机进行通信。实时时钟/日 历电路提供秒、分、时、日、周、月、年的信息,每月的天数和闰年的天数可自动调整。
当单片机掉电后,实时时钟芯片会自动切换到备用电池,实现掉电也会继续工作
注:实时时钟的寄存器起始地址以0开始,即秒寄存器地址为0,分寄存器地址1为1
通过命令字,可实现对寄存器实现读写操作
上述RTC寄存器图中,前两列已经给出命令字
I2C(Inter-Integrated Circuit)总线是由 PHILIPS 公司开发的两线式 串行总线,用于连接微控制器及其外围设备。它是同步通信的一种特殊形式,具有接口线少,控制方式简单, 器件封装形式小,通信速率较高等优点。I2C 总线只有两根双向信号线。一根是数据线 SDA,另一根是时钟线 SCL。由于其管脚少,硬件实现简单,可扩展性强 等特点,因此被广泛的使用在各大集成芯片内。
特点:同步、半双工、带数据应答
IIC电路规范:
时序结构:
起始与终止条件:
发送一字节数据:
接受一字节数据:
当接收一个字节时,主机要释放对SDA线的控制权,将控制权交给从机,将SDA置为1即可
数据应答:
数据帧格式:
1.发送数据帧
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。