单片机培训笔记(LED、KEY、SEG 综合)
一、开发环境与新建工程
1. 开发软件
- Keil5:编写、编译程序,生成 HEX 文件。
- STC-ISP:烧录程序到单片机。
【图片位置:LED.ppt 第2页】
Keil5 图标与 STC-ISP 图标。
2. 新建工程步骤
- 养成良好的编程习惯:每一个不同的工程项目对应一个不同的文件夹。
- 芯片选择 AT89C52。
- 点开“品”字工具栏,修改相关信息。
- 点开魔术棒(Options for Target),勾选 生成 HEX 文件。
二、LED 控制
1. LED 原理图
- LED 接在 P1 口,经 74HC573 驱动。
- 低电平点亮,高电平熄灭。
【图片位置:LED.ppt 第4页】
P1 口经 74HC573 连接 8 个 LED(D1~D8)。
【图片位置:LED.ppt 第5页】
89S52 引脚图,P1.0~P1.7 对应 LED。
2. 编写程序
- 方法一:对 P1 整体赋值,控制所有 LED。
例:P1 = 0xFE;点亮 LED1。 - 方法二:对 P1 中八个 IO 口单独赋值,控制单个 LED。
例:P1_0 = 0;点亮 LED1。
3. LED 闪烁
- 原理:不断给 P1 端口赋高低电平,实现开与关。
- 延时函数生成:
- 打开 STC-ISP 烧录工具。
- 打开“软件延时计算器”。
- 系统频率 12MHz,定时长度 500 毫秒,指令集 STC-Y1。
- 生成 C 代码,粘贴到工程中,并删除
nop语句。
4. LED 流水灯
- 原理:不断给 P1 中不同的 IO 口置低电平。
- 方法一:给 P1 赋不同的值。
- 方法二:使用内置函数库
intrins.h:_crol_:循环左移_cror_:循环右移
流水灯示例代码:
/* 头文件声明区域 */
#include <REGX52.H>
#include <intrins.h>
// 延时函数
void Delay(unsigned char xms) //@12.000MHz
{
unsigned char data i, j;
while (xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
// 变量声明区域
unsigned char ucled = 0xfe;
void main()
{
while (1)
{
ucled = _crol_(ucled, 1);
P1 = ucled;
Delay(100);
}
}
5. 自定义延时
- 通过 STC-ISP 生成 1 毫秒延时函数,使用
while循环调用,即可实现自定义延时。
三、按键检测
1. KEY 原理图
- 独立按键:S2~S5 分别接 LCDEN、RS、WR、RD,另一端接地。
- 矩阵键盘:4×4 矩阵接在 P3 口。
【图片位置:KEY.ppt 第3页】
独立按键 S2~S5 接 LCDEN、RS、WR、RD。
【图片位置:KEY.ppt 第4页】
P3 口接 Header,P3.4~P3.7 可作独立按键输入。
【图片位置:KEY.ppt 第7页】
4×4 矩阵键盘原理图,S6~S21。
【图片位置:KEY.ppt 第8页】
矩阵键盘与 8051 连接图。
2. 模块化函数
-
好处:便于程序移植、理解思路、局内调用。
-
函数定义:将常用的整体实现某个功能的代码块封装起来,用到时直接调用。
-
声明格式:
函数类型 函数名(入口参数) { 函数体 }
3. 按键扫描四行代码(死记)
Key_Val = Key_Read(); // 读取键码值
Key_Down = Key_Val & (Key_Val ^ Key_Old); // 检测下降沿
Key_Up = ~Key_Val & (Key_Val ^ Key_Old); // 检测上升沿
Key_Old = Key_Val; // 扫描辅助变量
4. 独立按键读取函数
unsigned char Key_Read()
{
unsigned char temp = 0;
if (P3_4 == 0) temp = 1;
if (P3_5 == 0) temp = 2;
if (P3_6 == 0) temp = 3;
if (P3_7 == 0) temp = 4;
return temp;
}
5. 矩阵键盘
- 将 4×4 矩阵键盘看成四个不同行的独立按键,逐行逐列依次扫描。
- 范例程序:密码锁(后续培训讲解)。
矩阵键盘扫描函数示例:
unsigned char Key_Read()
{
unsigned char temp = 0;
P3_0 = 0; P3_1 = 1; P3_2 = 1; P3_3 = 1;
if (P3_4 == 0) temp = 1;
if (P3_5 == 0) temp = 2;
if (P3_6 == 0) temp = 3;
if (P3_7 == 0) temp = 4;
P3_0 = 1; P3_1 = 0; P3_2 = 1; P3_3 = 1;
if (P3_4 == 0) temp = 5;
if (P3_5 == 0) temp = 6;
if (P3_6 == 0) temp = 7;
if (P3_7 == 0) temp = 8;
P3_0 = 1; P3_1 = 1; P3_2 = 0; P3_3 = 1;
if (P3_4 == 0) temp = 9;
if (P3_5 == 0) temp = 10;
if (P3_6 == 0) temp = 11;
if (P3_7 == 0) temp = 12;
P3_0 = 1; P3_1 = 1; P3_2 = 1; P3_3 = 0;
if (P3_4 == 0) temp = 13;
if (P3_5 == 0) temp = 14;
if (P3_6 == 0) temp = 15;
if (P3_7 == 0) temp = 16;
return temp;
}
6. 彩灯控制系统(综合按键与 LED)
/* 头文件声明区域 */
#include <REGX52.H>
#include <intrins.h>
// 延时函数
void Delay(unsigned char xms) //@12.000MHz
{
unsigned char data i, j;
while (xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
// 变量声明区
unsigned char Key_Val, Key_Down, Key_Old;
unsigned char Led_Data = 0xfe;
unsigned char Led_34_Data[] = {0x7e, 0xbd, 0xdb, 0xe7};
unsigned char Seg_Mode = 0;
unsigned char Led_34_Data_Index;
bit Led_Flag;
// 按键区
unsigned char Key_Read()
{
unsigned char temp = 0;
if (P3_4 == 0) temp = 1;
if (P3_5 == 0) temp = 2;
if (P3_6 == 0) temp = 3;
if (P3_7 == 0) temp = 4;
return temp;
}
void main()
{
while (1)
{
Key_Val = Key_Read(); // 读取键码值
Key_Down = Key_Val & (Key_Val ^ Key_Old); // 检测下降沿
Key_Old = Key_Val; // 扫描辅助变量
switch (Key_Down)
{
case 1:
Led_Flag = 1;
break;
case 2:
Led_Flag = 0;
break;
case 3:
Seg_Mode++;
if (Seg_Mode == 4) Seg_Mode = 0;
break;
case 4:
Seg_Mode--;
if (Seg_Mode == 255) Seg_Mode = 3;
break;
}
if (Led_Flag == 1)
{
switch (Seg_Mode)
{
case 0:
P1 = Led_Data;
Delay(500);
Led_Data = _crol_(Led_Data, 1);
break;
case 1:
P1 = Led_Data;
Delay(500);
Led_Data = _cror_(Led_Data, 1);
break;
case 2:
P1 = Led_34_Data[Led_34_Data_Index];
Delay(500);
Led_34_Data_Index++;
if (Led_34_Data_Index == 4) Led_34_Data_Index = 0;
break;
case 3:
P1 = Led_34_Data[Led_34_Data_Index];
Delay(500);
Led_34_Data_Index--;
if (Led_34_Data_Index == 255) Led_34_Data_Index = 3;
break;
}
}
}
}
四、数码管显示(SEG)
1. SEG 原理图
- 数码管位选、段选分别控制。
- 数据存放在 P0 口,段码和位码给 0 是灭。
【图片位置:SEG.ppt 第3页】
两个数码管 LED1、LED2,段选 A~DP,位选 DS1、DS2。
【图片位置:SEG.ppt 第4页】
P2.0~P2.7 连接数码管位选、段选等。
2. 位码与段码
// 位码:选择哪一个数码管亮
unsigned char code Wei_Ma[] = {0xfe, 0xfd, 0xfb, 0xf7, 0xef, 0xdf};
// 段码:显示什么数字
unsigned char code Duan_Ma[] = {0x3f, 0x06, 0x5b, 0x4f, 0x66, 0x6d, 0x7d, 0x07, 0x7f, 0x6f, 0x00};
3. 数码管显示函数 Seg_Disp
- 入口参数:数码管位选(哪个数码管亮)+ 数码管段选(亮什么数字)。
- 程序逻辑:
- 消影
- 位选赋值
- 段选赋值
示例:
void Seg_Disp(unsigned char Wei, unsigned char Duan)
{
P0 = 0x00; // 消影
P2 = Wei; // 位选
P0 = Duan; // 段选
}
4. 动态数码管
- 每位数码管显示不同的数字,若直接重复调用一个数码管显示函数,会因为程序由上至下依次执行,造成后面的数据影响前面的显示效果。
- 解决方法:使用定时器中断,在中断内轮流刷新每一位数码管。
5. 定时器生成步骤
- 打开 STC-ISP 烧录软件,找到“定时器计算器”。
- 系统频率 12MHz,定时长度 1 毫秒,定时器模式 16 位,定时器时钟 12T。
- 复制 C 代码到工程中。
- 删除
AUXR &= 0x7F;。
6. 中断配置方法
- 在生成的定时器初始化函数内增加中断打开命令:
ET0 = 1; EA = 1; - 书写中断服务函数
Timer0Server()。 - 在服务函数内初始化计数值。
- 在主程序内添加定时器 0 初始化函数。
7. 动态数码管显示逻辑
- 定义一个数组,将每一个数码管要显示的数据放到数组对应的位置。
- 将每一个数码管显示的函数放入中断内。
8. 项目设计:简易计时器
- 按键功能:
- 按键 1:计时启动
- 按键 2:计时暂停
- 按键 3:复位
- 按键 4:LED 亮灭功能
- LED:当系统处于计时状态时,所有 LED 以 200ms/次的速度闪烁。
- 数码管:实时显示当前计时数值。
五、编程错误点总结
- 按键的
switch中,case从case 1开始,不要从case 0开始。 switch后面要加{}。if语句中应使用==,而不是=。- 注意变量初始化,避免随机值。
- 数组下标不要越界。
- 模式切换时记得重置相关显示数据。