蓝桥杯第五周之第一讲

蓝桥杯单片机备赛指南 - 第十讲:进阶驱动与系统模板化

一、 硬件配置 (必读)

  • J5 跳线帽:必须接 KBD 端 (左侧)。
  • 资源占用
  • 矩阵键盘:占用 P30-P33 (列), P34, P35, P42, P44 (行)。
  • 数码管:占用 P0 (数据), P2.6, P2.7 (通过138译码器控制 Y6C, Y7C)。

二、 进阶模块 I:矩阵键盘底层驱动 (Key.c)

核心逻辑:逐行扫描法
通过轮流拉低 P44, P42, P35, P34 四根行线,读取 P30-P33 四根列线的状态,从而确定按键坐标。

代码实现

#include "Key.h"

unsigned char Key_Read(){
    unsigned char Temp=0; // 默认返回0

    // === 第一行扫描 (S4-S7) ===
    P44=0; P42=1; P35=1; P34=1; // 拉低第一行
    if(P33==0) Temp=4;          // 检测 S4
    if(P32==0) Temp=5;          // 检测 S5
    if(P31==0) Temp=6;          // 检测 S6
    if(P30==0) Temp=7;          // 检测 S7

    // === 第二行扫描 (S8-S11) ===
    P44=1; P42=0; P35=1; P34=1; // 拉低第二行
    if(P33==0) Temp=8;
    if(P32==0) Temp=9;
    if(P31==0) Temp=10;
    if(P30==0) Temp=11;

    // === 第三行扫描 (S12-S15) ===
    P44=1; P42=1; P35=0; P34=1; // 拉低第三行
    if(P33==0) Temp=12;
    if(P32==0) Temp=13;
    if(P31==0) Temp=14;
    if(P30==0) Temp=15;

    // === 第四行扫描 (S16-S19) ===
    P44=1; P42=1; P35=1; P34=0; // 拉低第四行
    if(P33==0) Temp=16;
    if(P32==0) Temp=17;
    if(P31==0) Temp=18;
    if(P30==0) Temp=19;

    return Temp; // 返回键值 4-19
}


三、 进阶模块 II:数码管通用驱动 (Seg.c)

核心逻辑:查表法与小数点控制
将段码 (Seg_Dula) 和位码 (Seg_Wela) 封装在数组中,通过索引直接调用。并增加 Point 参数灵活控制小数点。

代码实现

#include "Seg.h"

// 0-F 及 熄灭(FF) 的共阳极段码表
unsigned char Seg_Dula[] = {
    0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 
    0x80, 0x90, 0xFF, 0x88, 0x83, 0xc6, 0xa1, 0x86, 0x8e
};

// 8个位置的位选表
unsigned char Seg_Wela[] = {
    0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80
};

// Wela: 位置(0-7), Dula: 数字索引(0-16), Point: 小数点(1亮 0灭)
void Seg_Disp(unsigned char Wela, unsigned char Dula, unsigned char Point){
    // 1. 消影 (位选/段选操作前先关闭显示,防止鬼影)
    P0=0xFF; 
    P2=P2&0x1F|0xE0; P2&=0x1F; // 打开并关闭 Y7C (段选)

    // 2. 位选 (选择哪一位亮)
    P0=Seg_Wela[Wela];
    P2=P2&0x1F|0xC0; P2&=0x1F; // 打开并关闭 Y6C (位选)

    // 3. 段选 (显示什么数字)
    P0=Seg_Dula[Dula];
    if(Point){
        P0&=0x7F; // 如果需要小数点,将最高位(dp)置0 (共阳极低电平亮)
    } 
    P2=P2&0x1F|0xE0; P2&=0x1F; // 打开并关闭 Y7C (段选)
}


四、 核心系统模板:分时轮询架构 (main.c)

这是蓝桥杯最标准的**“三层架构”**:

  1. 中断层 (Timer0_Isr):负责显示刷新和提供时间基准(减速变量)。
  2. 驱动层 (Key_Proc, Seg_Proc):负责处理具体的硬件逻辑和数据更新。
  3. 业务层 (main):负责调度和初始化。

1. 变量声明区

#include <STC15F2K60S2.H>
#include "LED.h"
#include "Init.h"
#include "Seg.h"
#include "Key.h"

// 常用类型定义
typedef unsigned char u8;
typedef unsigned int u16;

// 减速变量 (时间片)
u8 Key_Slow_Down=0;  // 键盘扫描间隔
u16 Seg_Slow_Down=0; // 数码管数据更新间隔

// 扫描索引
u8 Seg_Pos=0; // 当前扫描到的数码管位置
u8 LED_Pos=0; // 当前扫描到的LED位置

// 显存数组 (Display Buffer)
u8 Seg_Buf[8]={11,10,10,10,10,10,10,10}; // 默认为熄灭(10)或无显示
u8 Seg_Point[8]={0,0,0,0,0,0,0,0};       // 小数点控制
u8 LED_Buf[8]={0,0,0,0,0,0,0,0};         // LED状态

// 按键状态机变量
u8 Key_Val, Key_Old, Key_Down, Key_Up;

2. 键盘处理进程 (三行代码消抖法)

void Key_Proc(){
    // 1. 软件减速 (10ms执行一次,实现消抖)
    if(Key_Slow_Down) return;
    Key_Slow_Down=1;
    
    // 2. 读取物理状态
    Key_Val = Key_Read();
    
    // 3. 状态机逻辑运算
    Key_Down = Key_Val & (Key_Val ^ Key_Old);  // 下降沿 (按下瞬间)
    Key_Up   = ~Key_Val & (Key_Val ^ Key_Old); // 上升沿 (松手瞬间)
    Key_Old  = Key_Val;                        // 更新旧值
    
    // ===在此处编写按键业务逻辑===
    // if(Key_Down == 4) { ... }
}

3. 显示信息处理进程

void Seg_Proc(){
    // 减速 (例如500ms更新一次数据,避免刷新过快看不清)
    if(Seg_Slow_Down) return;
    Seg_Slow_Down=1;
    
    // ===在此处更新 Seg_Buf 和 Seg_Point===
    // Seg_Buf[0] = ...
}

4. 定时器中断 (系统心跳)

void Timer0_Init(void)      // 1毫秒@12.000MHz
{
    AUXR &= 0x7F;           // 定时器时钟12T模式
    TMOD &= 0xF0;           // 设置定时器模式
    TL0 = 0x18;             // 设置定时初始值
    TH0 = 0xFC;             // 设置定时初始值
    TF0 = 0; 
    TR0 = 1; 
    ET0 = 1; 
    EA=1; // 开启中断
}

void Timer0_Isr(void) interrupt 1
{
    // 1. 时间切片处理 (提供给主循环使用)
    if(++Key_Slow_Down == 10) Key_Slow_Down = 0;    // 10ms 周期
    if(++Seg_Slow_Down == 500) Seg_Slow_Down = 0;   // 500ms 周期
    
    // 2. 数码管扫描 (硬件驱动)
    if(++Seg_Pos == 8) Seg_Pos = 0;
    Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], Seg_Point[Seg_Pos]);
    
    // 3. LED扫描 (硬件驱动)
    if(++LED_Pos == 8) LED_Pos = 0;
    LED_Disp(LED_Pos, LED_Buf[LED_Pos]);
}

5. 主函数 (调度中心)

void main(){
    System_Init(); // 系统初始化 (关闭蜂鸣器/继电器)
    Timer0_Init(); // 启动定时器
    
    while(1){
        Key_Proc(); // 处理按键输入
        Seg_Proc(); // 处理显示数据
        LED_Proc(); // 处理LED逻辑 (如果有)
    }
}

五、 模板使用总结

  1. 文件结构Key.c, Seg.c, LED.c 负责底层 IO 操作;main.c 负责逻辑控制。
  2. 非阻塞设计:所有 Delay 全部移除,依靠 Timer0_Isr 里的计数器 (Slow_Down) 控制频率。
  3. 按键使用
  • 单次触发用 if(Key_Down == x)
  • 长按触发用 if(Key_Old == x)
  • 松手触发用 if(Key_Up == x)
  1. 显示使用
  • 只需在 Seg_Proc 中修改 Seg_Buf[] 数组的内容,数码管就会自动更新显示,无需在主循环里手动扫描。