蓝桥杯单片机备赛指南 - 第十讲:进阶驱动与系统模板化
一、 硬件配置 (必读)
- 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)
这是蓝桥杯最标准的**“三层架构”**:
- 中断层 (
Timer0_Isr):负责显示刷新和提供时间基准(减速变量)。 - 驱动层 (
Key_Proc,Seg_Proc):负责处理具体的硬件逻辑和数据更新。 - 业务层 (
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逻辑 (如果有)
}
}
五、 模板使用总结
- 文件结构:
Key.c,Seg.c,LED.c负责底层 IO 操作;main.c负责逻辑控制。 - 非阻塞设计:所有
Delay全部移除,依靠Timer0_Isr里的计数器 (Slow_Down) 控制频率。 - 按键使用:
- 单次触发用
if(Key_Down == x)。 - 长按触发用
if(Key_Old == x)。 - 松手触发用
if(Key_Up == x)。
- 显示使用:
- 只需在
Seg_Proc中修改Seg_Buf[]数组的内容,数码管就会自动更新显示,无需在主循环里手动扫描。