第二周-笔记

单片机培训笔记(LED、KEY、SEG 综合)


一、开发环境与新建工程

1. 开发软件

  • Keil5:编写、编译程序,生成 HEX 文件。
  • STC-ISP:烧录程序到单片机。

【图片位置:LED.ppt 第2页】
Keil5 图标与 STC-ISP 图标。

2. 新建工程步骤

  1. 养成良好的编程习惯:每一个不同的工程项目对应一个不同的文件夹。
  2. 芯片选择 AT89C52
  3. 点开“品”字工具栏,修改相关信息。
  4. 点开魔术棒(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 端口赋高低电平,实现开与关。
  • 延时函数生成:
    1. 打开 STC-ISP 烧录工具。
    2. 打开“软件延时计算器”。
    3. 系统频率 12MHz,定时长度 500 毫秒,指令集 STC-Y1。
    4. 生成 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

  • 入口参数:数码管位选(哪个数码管亮)+ 数码管段选(亮什么数字)。
  • 程序逻辑:
    1. 消影
    2. 位选赋值
    3. 段选赋值

示例:

void Seg_Disp(unsigned char Wei, unsigned char Duan)
{
    P0 = 0x00;      // 消影
    P2 = Wei;       // 位选
    P0 = Duan;      // 段选
}

4. 动态数码管

  • 每位数码管显示不同的数字,若直接重复调用一个数码管显示函数,会因为程序由上至下依次执行,造成后面的数据影响前面的显示效果。
  • 解决方法:使用定时器中断,在中断内轮流刷新每一位数码管。

5. 定时器生成步骤

  1. 打开 STC-ISP 烧录软件,找到“定时器计算器”。
  2. 系统频率 12MHz,定时长度 1 毫秒,定时器模式 16 位,定时器时钟 12T。
  3. 复制 C 代码到工程中。
  4. 删除 AUXR &= 0x7F;

6. 中断配置方法

  1. 在生成的定时器初始化函数内增加中断打开命令:ET0 = 1; EA = 1;
  2. 书写中断服务函数 Timer0Server()
  3. 在服务函数内初始化计数值。
  4. 在主程序内添加定时器 0 初始化函数。

7. 动态数码管显示逻辑

  1. 定义一个数组,将每一个数码管要显示的数据放到数组对应的位置。
  2. 将每一个数码管显示的函数放入中断内。

8. 项目设计:简易计时器

  • 按键功能
    • 按键 1:计时启动
    • 按键 2:计时暂停
    • 按键 3:复位
    • 按键 4:LED 亮灭功能
  • LED:当系统处于计时状态时,所有 LED 以 200ms/次的速度闪烁。
  • 数码管:实时显示当前计时数值。

五、编程错误点总结

  • 按键的 switch 中,casecase 1 开始,不要从 case 0 开始。
  • switch 后面要加 {}
  • if 语句中应使用 ==,而不是 =
  • 注意变量初始化,避免随机值。
  • 数组下标不要越界。
  • 模式切换时记得重置相关显示数据。