一、单片机介绍
- 单片机,英文Micro Controller Unit,简称MCU
- 内部集成了CPU、RAM、ROM、定时器、中断系统、通讯接口等一系列电脑的常用硬件功能
- 单片机的任务是信息采集(依靠传感器)、处理(依靠CPU)和硬件设备(例如电机,LED等)的控制
- 单片机跟计算机相比,单片机算是一个袖珍版计算机,一个芯片就能构成完整的计算机系统。但在性能上,与计算机相差甚远,但单片机成本低、体积小、结构简单,在生活和工业控制领域大有所用
1.1 STC89C52单片机
- 系列:C51单片机系列
- 位数:8位
- RAM(随机存取存储器):512字节,断电后数据消失
- ROM(只读存储器):8K(闪存Flash),存储其中的数据断电后不会消失
- 频率:12MHz(本开发板使用)
1.2 STC89C52系列单片机命名规则
- STC:公司
- 89:系列
- C:工作电压(5.5V~3.3V)
- 52:8K字节程序空间及512字节RAM空间
1.3 STC89C52内部结构图
这是总线结构,所有外部设备都挂接在总线上,CPU访问这个总线,就可以访问到所有的外部设备。
1.4 STC89C52系列单片机最小系统
1.5 LED
发光二极管
1.6 电阻
1.6.1 电阻的作用
- 限制电流:通过电阻可以减少电路中通过特定部位的电流的大小。
- 分压:和电容一样,在电路中用于分压反馈时,在分压网络中,电阻通过减少电压降来工作。
- 耗能:部分电路需要将电能转化为热能,而电阻是简单而普遍的实现方式。
- 保护电路:在过流的情况下,电阻可以承受更大的电流而不损伤,可用于保护电路免受电流突增的损害。
- 热监测:如温度传感器可以利用电阻的随温度变化的特性来测量环境温度。
- 测量仪表: 断电路中的电阻可以用来与用户测量设备的指示相连,方便测试和调整。
- 电感调整:可以和电容器配合作为RC滤波器来调整LC谐振频率。
- 电压稳定性:和稳压管一起配合,利用电阻可以达到部分稳压效果。
- 减少噪音:电阻可以和电容器一起减少电路中的噪声。
- 可变电阻: 电位器等可变电阻或精密旋钮可以帮助微调设备某个属性。
1.6.2 电阻的读数方式
102 = 10 00 = 1000 = 1K
473 = 47 * 10^3 = 47000
1.7 电容
电容的读数方法和电阻一样。
电容单位换算
1F = 1000mF
1mF = 1000uF
1uF = 1000nF
1nF = 1000pF
1.8 进制转换
十进制 | 二进制 | 十六进制 |
0 | 0000 | 0 |
1 | 0001 | 1 |
2 | 0010 | 2 |
3 | 0011 | 3 |
4 | 0100 | 4 |
5 | 0101 | 5 |
6 | 0110 | 6 |
7 | 0111 | 7 |
8 | 1000 | 8 |
9 | 1001 | 9 |
10 | 1010 | A |
11 | 1011 | B |
12 | 1100 | C |
13 | 1101 | D |
14 | 1110 | E |
15 | 1111 | F |
1.9 数据运算
类别 | 运算符 | 意义 |
算数 | + | 加 |
- | 减 | |
* | 乘 | |
/ | 除 | |
% | 取余 | |
= | 赋值 | |
判断 | > | 大于 |
>= | 大于等于 | |
< | 小于 | |
<= | 小于等于 | |
== | 等于 | |
!= | 不等于 | |
逻辑 | && | 逻辑与 |
|| | 逻辑或 | |
! | 逻辑非 | |
位运算 | << | 按位左移 |
>> | 按位右移 | |
& | 按位与 | |
| | 按位或 | |
^ | 按位异或 | |
~ | 按位取反 |
按位左移
0011 1100<<1 -> 0111 1000
按位右移
0011 1100>>2 -> 0000 1111
按位与
0011 1100 & 1010 0011 -> 0010 0000
按位或
0011 1100 & 1010 0011 -> 1011 1111
按位异或
0011 1100 & 1010 0011 -> 1001 1111
按位取反
~0011 1100 -> 1100 0011
1.10 数码管
LED数码管:数码管是一种简单、廉价的显示器,是由多个发光二极管封装在一起组成“8”字型的器件。
![](https://i-blog.csdnimg.cn/direct/07c18f702a28450aac1148f782318557.png)
![](https://i-blog.csdnimg.cn/direct/31fe0b5ad00f42b0be3788e396ef4523.png)
![](https://i-blog.csdnimg.cn/direct/284ea1d26d814054b3fd80250fff66f9.png)
![](https://i-blog.csdnimg.cn/direct/85b187ec16294133b58b5f1c1363ce5f.png)
![](https://i-blog.csdnimg.cn/direct/a1f182a9a04f4f08afebc405a285fab6.png)
![](https://i-blog.csdnimg.cn/direct/5bc2e5f5a82b4be79b7eab63abffe160.png)
![](https://i-blog.csdnimg.cn/direct/50e2ecd3449341b490d985381696049b.png)
1.10.1 位选
本单片机的数码管为共阴极。公共端接低电平,要想使某一数码管点亮,就选中该数码管并置0,其它数码管置1,也称为位选。比如:在第三个数码管上显示数字1,位选为1101。(位选在公共端)
1.10.2 段选
根据显示的字符把某些段置为高电平,其它段置低电平,即点亮某些段,也称为段选。比如:在第三个数码管上显示数字1,段选为0110 0000。(段选在另一端)
1.11 头文件
#include <REGX52.H>//在安装目录下找文件
#include "delay.h"//在本工程下找文件
1.12 LCD1602液晶屏
1.12.1 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');
}
}
1.12.2 LCD1602.h文件
#ifndef __LCD1602_H__
#define __LCD1602_H__
//用户调用函数:
void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
#endif
1.13 矩阵键盘
- 数码管扫描(输出扫描) 原理:显示第1位→显示第2位→显示第3位→……,然后快速循环这个过程,最终实现所有数码管同时显示的效果。
- 矩阵键盘扫描(输入扫描) 原理:读取第1行(列)→读取第2行(列) →读取第3行(列) → ……,然后快速循环这个过程,最终实现所有按键同时检测的效果。
- 以上两种扫描方式的共性:节省I/O口。
1.13.1 MatrixKey.c文件
#include <REGX52.H>
#include "Delay.h"
/**
* @brief 矩阵键盘读取按键键码
* @param 无
* @retval KeyNumber 按下按键的键码值
如果按键按下不放,程序会停留在此函数,松手的一瞬间,返回按键键码,没有按键按下时,返回0
*/
unsigned char MatrixKey()
{
unsigned char KeyNumber=0;
P1=0xFF;
P1_3=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=1;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=5;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=9;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=13;}
P1=0xFF;
P1_2=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=2;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=6;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=10;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=14;}
P1=0xFF;
P1_1=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=3;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=7;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=11;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=15;}
P1=0xFF;
P1_0=0;
if(P1_7==0){Delay(20);while(P1_7==0);Delay(20);KeyNumber=4;}
if(P1_6==0){Delay(20);while(P1_6==0);Delay(20);KeyNumber=8;}
if(P1_5==0){Delay(20);while(P1_5==0);Delay(20);KeyNumber=12;}
if(P1_4==0){Delay(20);while(P1_4==0);Delay(20);KeyNumber=16;}
return KeyNumber;
}
1.13.2 MatrixKey.h文件
#ifndef __MATRIXKEY_H__
#define __MATRIXKEY_H__
unsigned char MatrixKey();
#endif
1.14 定时器
在国际单位制中,时间的换算单位如下:
- 1小时= 60分钟
- 1分钟= 60秒
- 1秒= 1000毫秒
- 1毫秒= 1000微秒
- 1微秒= 1000纳秒
总结一下换算关系:
- 1分 = 60秒
- 1秒 = 1000毫秒 = 1,000,000微秒 = 1,000,000,000纳秒
定时器介绍:51单片机的定时器属于单片机的内部资源,其电路的连接和运转均在单片机内部完成。
定时器作用:
- 用于计时系统,可实现软件计时,或者使程序每隔一固定时间完成一项操作。
- 替代长时间的Delay,提高CPU的运行效率和处理速度。(假设正在执行delay_ms(1000),在这1秒内CPU被占用,无法做其他事,定时器不占用CPU,若换成定时器,在这1秒内CPU可以做其他事)
定时器个数:3个(T0、T1、T2),T0和T1与传统的51单片机兼容,T2是此型号单片机增加的资源。
注意:定时器的资源和单片机的型号是关联在一起的,不同的型号可能会有不同的定时器个数和操作方式,但一般来说,T0和T1的操作方式是所有51单片机所共有的。
1.14.1 定时器框图
定时器在单片机内部就像一个小闹钟一样,根据时钟的输出信号,每隔“一秒”,计数单元的数值就增加一,当计数单元数值增加到“设定的闹钟提醒时间”时,计数单元就会向中断系统发出中断申请,产生“响铃提醒”,使程序跳转到中断服务函数中执行。
1.14.2 定时器工作模式
STC89C52的T0和T1均有四种工作模式:
- 模式0:13位定时器/计数器
- 模式1:16位定时器/计数器(常用)
- 模式2:8位自动重装模式
- 模式3:两个8位计数器
工作模式1框图:
工作方式寄存器 TMOD
TMOD用于选择工作模式和工作方式,不可位寻址
GATE:门控位
- GATE=0:是否计数仅由TRx来控制
- GATE=1:是否计数,由T外部中断引脚(INT0/INT1)与Rx来控制
C/T:选择工作模式
- C/T=0:定时器,对系统时钟12分频后的内部脉冲计数
- C/T=1:计数器,对P3.4和P3.5引脚上的外部脉冲进行计数
M1、M0:选择工作方式
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停止计数 |
控制寄存器 TCON
TCON用于控制T0、T1的启动和停止计数,可位寻址
- TF1、TF0:计数溢出标志位
- TR1、TR0:计数启动控制位
- TR1、TR0=1,启动
- TR1、TR0=0,停止
1.14.3 中断系统
Timer0.c文件
#include <REGX52.H>
/**
* @brief 定时器0初始化,1毫秒@12.000MHz
* @param 无
* @retval 无
*/
void Timer0Init(void)
{
TMOD &= 0xF0; //把TMOD的低四位清零,高四位保持不变
TMOD |= 0x01; //把TMOD的最低位置1,高四位保持不变
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0=1;//定时器/计数器0的溢出中断允许控制位
EA=1;//中断允许总开关控制位
PT0=0;//定时器/计数器0的溢出中断优先级控制位
}
/*定时器中断函数模板,在main函数中使用
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;//static确保每次执行中断子函数后变量T0Count的值不被清零,保留原有值
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000)//每隔1秒执行定时程序
{
T0Count=0;
}
}
*/
Timer0.h文件
#ifndef __TIMER0_H__
#define __TIMER0_H__
void Timer0Init(void);
#endif
二、实验
2.1 点亮一个LED灯
代码
#include <REGX52.H>
void main()
{
P2=OxFE;//1111 1110
//P2是一个寄存器的名称
//Ox是前缀,代表后面的是十六进制数
}
2.2 LED灯闪烁
代码
#include <REGX52.H>
#include <INTRINS.H>//调用_nop_()引入头文件
void Delay500ms()
{
unsigned char i,j,k;
_nop_();
//它是用于生成一个空操作指令的宏。与STM32一样,它可以用在汇编操作中来执行一个不改变任何CPU寄存器状态的指令,占用至少一个CPU周期
i=4;
j=205;
k=187;
do
{
do
{
while(--k);
}while(--j);
}while(--i);
}
void main()
{
P2=OxFE;//1111 1110 亮
Delay500ms();//延时0.5秒
P2=OxFF;//1111 1111 灭
Delay500ms();
}
2.3 流水灯
代码
#include <REGX52.H>
void Delay_ms(unsigned int xms)
{
unsigned char i,j;
while(xms)
{
i=2;
j=239;
do
{
while(--j);
}while(--i);
xms--;
}
}
void main()
{
P2=OxFE;//1111 1110
Delay_ms(500);
P2=OxFD;//1111 1101
Delay_ms(500);
P2=OxFB;//1111 1011
Delay_ms(500);
P2=OxF7;//1111 0111
Delay_ms(500);
P2=OxEF;//1110 1111
Delay_ms(500);
P2=OxDF;//1101 1111
Delay_ms(500);
P2=OxBF;//1011 1111
Delay_ms(500);
P2=Ox7F;//0111 1111
Delay_ms(500);
}
2.4 独立按键控制LED灯亮灭
代码
#include <REGX52.H>
void Delay_ms(unsigned int xms)
{
unsigned char i,j;
while(xms)
{
i=2;
j=239;
do
{
while(--j);
}while(--i);
xms--;
}
}
void main()
{
while(1)
{
if(P3_1 == 0)//按下按键
{
Delay_ms(20);//消抖
while(P3_1 == 0);//松开按键
Delay_ms(20);//消抖
P2_0=~P2_0;//状态取反(初始为高电平,灯不亮)
}
}
}
2.5 独立按键控制LED灯显示二进制(从0加到255)
代码
#include <REGX52.H>
void Delay_ms(unsigned int xms)
{
unsigned char i,j;
while(xms)
{
i=2;
j=239;
do
{
while(--j);
}while(--i);
xms--;
}
}
void main()
{
//LEDNum 是 unsigned char 类型的,它的范围是0到255(在8位系统中)
//当值增加至0xFF(即255),再继续递增就会变成0x00(即0),这是一个循环行为
unsigned char LEDNum=0;
while(1)
{
if(P3_1 == 0)//按下按键
{
Delay_ms(20);//消抖
while(P3_1 == 0);//松开按键
Delay_ms(20);//消抖
/*
//初始P2为1111 1111
P2++;//加一溢出,1 0000 0000,舍1,即0000 0000
*/
LEDNum++;
P2=~LEDNum;//第一次状态取反1111 1110只有一个灯亮
}
}
}
2.6 独立按键控制LED灯移位
第一个按键每次左移一位,第二个按键每次右移一位
0000 0001 0x01<<0 0000 0001 0x01>>0
0000 0010 0x01<<1 1000 0000 0x01>>7
0000 0100 0x01<<2 0100 0000 0x01>>6
0000 1000 0x01<<3 0010 0000 0x01>>5
0001 0000 0x01<<4 0001 0000 0x01>>4
0010 0000 0x01<<5 0000 1000 0x01>>3
0100 0000 0x01<<6 0000 0100 0x01>>2
1000 0000 0x01<<7 0000 0010 0x01>>1
代码
#include <REGX52.H>
void Delay_ms(unsigned int xms)
{
unsigned char i,j;
while(xms)
{
i=2;
j=239;
do
{
while(--j);
}while(--i);
xms--;
}
}
void main()
{
//LEDNum 是 unsigned char 类型的,它的范围是0到255(在8位系统中)
//当值增加至0xFF(即255),再继续递增就会变成0x00(即0),这是一个循环行为
unsigned char LEDNum=0;
while(1)
{
P2=~0x01;
if(P3_1 == 0)//按下按键
{
Delay_ms(20);//消抖
while(P3_1 == 0);//松开按键
Delay_ms(20);//消抖
LEDNum++;
if(LEDNum>=8)
LEDNum=0;
P2=~(0x01<<LEDNum);
}
if(P3_0 == 0)//按下按键
{
Delay_ms(20);//消抖
while(P3_1 == 0);//松开按键
Delay_ms(20);//消抖
if(LEDNum == 0)
LEDNum=7;
else
LEDNum--;
P2=~(0x01<<LEDNum);
}
}
}
2.7 静态数码管显示
从左往右第三个数码管上显示数字6。
代码一
#include <REGX52.H>
void main()
{
//位选,找138译码器(错一位),从下往上读,即CBA
P2_4=1;
P2_3=0;
P2_2=1;
//段选,找74HC245,从下往上读,即0x7D
P0=0x7D;
while(1)
{
}
}
代码二
#include <REGX52.H>
unsigned char NiXieTable[]=
{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
void NiXie(unsigned char Location,Number)
{
switch(Location)//位选,总共8个数码管,选择用哪一个
{
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;
}
P0=NiXieTable[Number];//段选,显示数字number
}
void main()
{
//调用子函数实现
NiXie(3,6);
while(1)
{
}
}
2.8 动态数码管显示
数码管显示数字123 。
代码
#include <REGX52.H>
unsigned char NiXieTable[]=
{0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71};
//0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F
void Delay_ms(unsigned int xms)
{
unsigned char i,j;
while(xms)
{
i=2;
j=239;
do
{
while(--j);
}while(--i);
xms--;
}
}
void NiXie(unsigned char Location,Number)
{
switch(Location)//位选,总共8个数码管,选择用哪一个
{
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;
}
P0=NiXieTable[Number];//段选,显示数字number
//优化
Delay_ms(1);//让它稳定的显示,不然它会比较暗
P0=0x00;//清零
}
void main()
{
while(1)
{
NiXie(1,1);
Delay_ms(100);
NiXie(2,2);
Delay_ms(100);
NiXie(3,3);
Delay_ms(100);
}
}
去掉延时函数,数码管可能显示串位,为避免这一现象,要消影。
消影
位选 段选 零 位选 段选 零 位选 段选 零 位选 段选 零
上述代码用的是单片机直接扫描。
单片机直接扫描:硬件设备简单,但会耗费大量的单片机CPU时间。
专用驱动芯片:内部自带显存、扫描电路,单片机只需告诉它显示什么即可 。比如TM1640、74HC595。
2.9 LCD1602调试
代码
#include <REGX52.H>
#include "LCD1602.h"
void main()
{
LCD_Init();//初始化
LCD_ShowChar(1,1,'A');// 显示字符:A
LCD_ShowString(1,3,"hello");// 显示字符串:hello
LCD_ShowNum(1,9,123,3);// 显示无符号数字:123 ,长度3 ,不够高位补0
LCD_ShowSignedNum(1,13,-66,2);// 显示有符号数字:-66 ,长度2,不够高位补0
LCD_ShowHexNum(2,1,0xA8,2);// 显示十六进制数:A8 ,长度2 ,不够高位补0
LCD_ShowBinNum(2,4,0XAA,8);// 显示二进制数:1010 1010 ,长度8 ,不够高位补0
while(1)
{
}
}
2.10 读取矩阵键盘的键码值(用LCD1602调试)
代码
#include <REGX52.H>
#include "Delay.h" //包含Delay头文件
#include "LCD1602.h" //包含LCD1602头文件
#include "MatrixKey.h" //包含矩阵键盘头文件
unsigned char KeyNum;
void main()
{
LCD_Init(); //LCD初始化
LCD_ShowString(1,1,"MatrixKey:"); //LCD显示字符串
while(1)
{
KeyNum=MatrixKey(); //获取矩阵键盘键码
if(KeyNum) //如果有按键按下
{
LCD_ShowNum(2,1,KeyNum,2); //LCD显示键码
}
}
}
2.11 矩阵键盘密码锁
代码
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "MatrixKey.h"
unsigned char KeyNum;
unsigned int Password,Count;
void main()
{
LCD_Init();
LCD_ShowString(1,1,"Password:");
while(1)
{
KeyNum=MatrixKey();
if(KeyNum)
{
if(KeyNum<=10) //如果S1~S10按键按下,输入密码
{
if(Count<4) //如果输入次数小于4
{
Password*=10; //密码左移一位
Password+=KeyNum%10; //获取一位密码
Count++; //计次加一
}
LCD_ShowNum(2,1,Password,4); //更新显示
}
if(KeyNum==11) //如果S11按键按下,确认
{
if(Password==2345) //如果密码等于正确密码
{
LCD_ShowString(1,14,"OK "); //显示OK
Password=0; //密码清零
Count=0; //计次清零
LCD_ShowNum(2,1,Password,4); //更新显示
}
else //否则
{
LCD_ShowString(1,14,"ERR"); //显示ERR
Password=0; //密码清零
Count=0; //计次清零
LCD_ShowNum(2,1,Password,4); //更新显示
}
}
if(KeyNum==12) //如果S12按键按下,取消
{
Password=0; //密码清零
Count=0; //计次清零
LCD_ShowNum(2,1,Password,4); //更新显示
}
}
}
}
2.12 按键控制LED流水灯模式
0~65535
每隔1us计数加一
总共定时时间65535us
假设定时器定初值64535离计数器溢出差值1000,所以计时时间为1ms
1010 0011&0xF0
1010 0011 & 1111 0000 -> 1010 0011
unsigned char a=0x01
a=_crol_(a,1);//循环左移 0x02 0x04 0x08 0x01...,右参数是所移位数
//和左移<<不一样
//头文件#include <INTRINS.H>
//_cror_()循环右移
代码
#include <REGX52.H>
#include "Timer0.h"
#include "Key.h"
#include <INTRINS.H>
unsigned char KeyNum,LEDMode;
void main()
{
P2=0xFE;
Timer0Init();
while(1)
{
KeyNum=Key(); //获取独立按键键码
if(KeyNum) //如果按键按下
{
if(KeyNum==1) //如果K1按键按下
{
LEDMode++; //模式切换
if(LEDMode>=2)LEDMode=0;
}
}
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++; //T0Count计次,对中断频率进行分频
if(T0Count>=500)//分频500次,500ms
{
T0Count=0;
if(LEDMode==0) //模式判断
P2=_crol_(P2,1); //LED输出
if(LEDMode==1)
P2=_cror_(P2,1);
}
}
key.c
#include <REGX52.H>
#include "Delay.h"
/**
* @brief 获取独立按键键码
* @param 无
* @retval 按下按键的键码,范围:0~4,无按键按下时返回值为0
*/
unsigned char Key()
{
unsigned char KeyNumber=0;
if(P3_1==0){Delay(20);while(P3_1==0);Delay(20);KeyNumber=1;}
if(P3_0==0){Delay(20);while(P3_0==0);Delay(20);KeyNumber=2;}
if(P3_2==0){Delay(20);while(P3_2==0);Delay(20);KeyNumber=3;}
if(P3_3==0){Delay(20);while(P3_3==0);Delay(20);KeyNumber=4;}
return KeyNumber;
}
key.h
#ifndef __KEY_H__
#define __KEY_H__
unsigned char Key();
#endif
2.13 定时器时钟
代码
main.c
#include <REGX52.H>
#include "Delay.h"
#include "LCD1602.h"
#include "Timer0.h"
unsigned char Sec=55,Min=59,Hour=23;
void main()
{
LCD_Init();
Timer0Init();
LCD_ShowString(1,1,"Clock:"); //上电显示静态字符串
LCD_ShowString(2,1," : :");
while(1)
{
LCD_ShowNum(2,1,Hour,2); //显示时分秒
LCD_ShowNum(2,4,Min,2);
LCD_ShowNum(2,7,Sec,2);
}
}
void Timer0_Routine() interrupt 1
{
static unsigned int T0Count;
TL0 = 0x18; //设置定时初值
TH0 = 0xFC; //设置定时初值
T0Count++;
if(T0Count>=1000) //定时器分频,1s
{
T0Count=0;
Sec++; //1秒到,Sec自增
if(Sec>=60)
{
Sec=0; //60秒到,Sec清0,Min自增
Min++;
if(Min>=60)
{
Min=0; //60分钟到,Min清0,Hour自增
Hour++;
if(Hour>=24)
{
Hour=0; //24小时到,Hour清0
}
}
}
}
}