蓝桥杯单片机入门
先搞清楚:蓝桥杯单片机到底考什么?
很多新手上来就开始啃芯片手册,其实方向不对。蓝桥杯单片机赛道的核心是CT107D开发板,主控芯片是STC15F2K60S2。这个板子上集成了以下外设:
| 外设 | 接口 | 重要程度 |
|---|---|---|
| 8个LED灯 | P0口+74HC573锁存器 | 必考 |
| 8位数码管 | P0口+74HC573锁存器 | 必考 |
| 4x4矩阵键盘 | P3/P4口 | 必考 |
| 蜂鸣器/继电器 | P0口+74HC573锁存器 | 高频 |
| DS18B20温度传感器 | P1.4(单总线) | 常考 |
| DS1302实时时钟 | P1.3/P1.7/P2.3 | 常考 |
| PCF8591(ADC/DAC) | P2.0/P2.1(I2C) | 常考 |
| AT24C02(EEPROM) | P2.0/P2.1(I2C) | 常考 |
| 超声波测距 | P1.0/P1.1(PCA) | 近年必考 |
| NE555频率发生器 | P3.4(T0计数) | 国赛常见 |
| UART串口通信 | P3.0/P3.1 | 国赛必考 |
仔细看这个表你会发现,基础外设(LED、数码管、按键、I2C)每年必考,传感器(超声波、温度、RTC)是核心考点,而串口通信和综合应用才是拉开分差的关键。从第15届开始,国赛的难度明显上了一个台阶,串口成了必考项。
代码架构:为什么"西风模板"这么好用?
我分析了手里的满分代码,发现不管是省赛还是国赛,几乎都基于同一个架构——"西风模板"。这个模板的核心思想其实很朴素:
前台(定时器中断):数码管扫描 + 系统计时 + 必要的精确定时
后台(主循环):通过调度器按周期调用各任务函数
看一下样板代码里的调度器定义:
/* 定义调度器结构体 */
typedef struct {
void (*task_func)(void); // 任务函数指针
unsigned long int rate_ms; // 运行周期(毫秒)
unsigned long int last_run; // 上一次运行的时间戳
} task_c;
/* 配置调度器 —— 这就是你的"任务清单" */
idata task_c Scheduler_Task[] = {
{Led_Proc, 1, 0}, // LED刷新 1ms
{Key_Proc, 10, 0}, // 按键扫描 10ms
{Seg_Proc, 100, 0}, // 数码管刷新 100ms
{Uart_Proc, 10, 0}, // 串口处理 10ms
{AD_DA, 160, 0}, // ADC读取 160ms
{Read_Tem, 100, 0}, // 温度读取 100ms
{Read_Time, 500, 0}, // 时间读取 500ms
};
主循环就一句话:
while(1) {
Scheduler_Run();
}
调度器内部会检查每个任务的 rate_ms 和 last_run,时间到了就执行对应函数。这样你完全不需要在主循环里写一堆 if 判断,加减任务只需要改一行配置。比赛的时候,你只需要根据题目要求增删任务行、修改业务逻辑,框架本身完全不用动。
我在对比第15届国赛和第16届省赛的代码时发现,它们都直接复用了这个调度器,只是任务数量和内容不同:
/* 第15届国赛 —— 8个任务 */
{Led_Proc, 1, 0},
{Key_Proc, 10, 0},
{Seg_Proc, 100, 0},
{Uart_Proc, 10, 0},
{AD_DA, 160, 0},
{Read_Freq, 1000, 0}, // 新增:频率读取(NE555)
{Get_Distance, 300, 0}, // 新增:超声波测距
{Run_Calc, 500, 0} // 新增:运动轨迹计算
你看,基础任务完全没变,新增的三个任务直接追加就行。这就是模板的价值——你在比赛前就把基础框架调通,比赛时只写业务代码。
按键处理的那个公式,理解了就很简单
西风模板里有一个看起来很巧妙的按键处理方式,一开始我看了好几遍才明白:
Key_Down = Key_Val & (Key_Val ^ Key_Old); // 按下检测
Key_Up = ~Key_Val & (Key_Val ^ Key_Old); // 释放检测
Key_Old = Key_Val; // 状态更新
拿一个具体例子走一遍就清楚了。假设按键S4按下(键值=4):
时间点1: Key_Val=0, Key_Old=0(无按键)
Key_Down = 0 & (0^0) = 0 → 没有按下
Key_Up = ~0 & (0^0) = 0 → 没有释放
时间点2: Key_Val=4, Key_Old=0(S4刚按下!)
Key_Down = 4 & (4^0) = 4 & 4 = 4 → 检测到S4按下 ✓
Key_Up = ~4 & (4^0) = ~4 & 4 = 0 → 无释放
时间点3: Key_Val=4, Key_Old=4(S4保持按下)
Key_Down = 4 & (4^4) = 4 & 0 = 0 → 不会重复触发 ✓
Key_Up = ~4 & (4^4) = ~4 & 0 = 0 → 无释放
时间点4: Key_Val=0, Key_Old=4(S4刚释放)
Key_Down = 0 & (0^4) = 0 & 4 = 0 → 无按下
Key_Up = ~0 & (0^4) = 0xFF & 4 = 4 → 检测到S4释放 ✓
核心逻辑就是:Key_Val ^ Key_Old 提取出"变化的部分",然后通过与运算判断是按下(当前值非零)还是释放(当前值为零)。这个公式配合10ms的调度周期,天然就实现了按键消抖——因为调度器保证每10ms才扫描一次。
main函数的标准骨架
这里直接给一个经过验证的main函数模板:
void main()
{
System_Init(); // 第一步:初始化硬件(LED熄灭、蜂鸣器关闭)
Uart_Init(); // 第二步:串口初始化(如果题目需要)
Scheduler_Init(); // 第三步:计算任务数量(必须在Timer1之前!)
Timer1Init(); // 第四步:启动系统时基(1ms中断)
// Set_Rtc(ucRtc); // 可选:设置RTC时间
while(1) {
Scheduler_Run(); // 主循环:交给调度器
}
}
有一个细节很多人不注意:Scheduler_Init 必须放在 Timer1Init 之前。因为一旦启动了定时器中断,uwTick 就开始递增了,而调度器的 last_run 初始值为0,如果此时还没初始化任务数量,可能出问题。虽然大多数情况下不会报错,但养成好的初始化顺序习惯很重要。
项目目录结构
一个规范的蓝桥杯工程应该长这样:
Project/
├── Driver/ # 驱动层(比赛前写好,比赛中不改动)
│ ├── Init.c/h # 系统初始化
│ ├── Led.c/h # LED/继电器/蜂鸣器
│ ├── Seg.c/h # 数码管
│ ├── Key.c/h # 按键扫描
│ ├── iic.c/h # I2C(PCF8591 + AT24C02)
│ ├── onewire.c/h # 单总线(DS18B20)
│ ├── ds1302.c/h # RTC时钟
│ ├── Uart.c/h # 串口通信
│ ├── U_Wave.c/h # 超声波测距
│ └── filtering.c/h# 滤波算法
└── User/
└── main.c # 业务逻辑(比赛时主要改这个文件)
这个分层的好处是,驱动层代码在备赛期间反复验证,比赛中完全不用动。你只需要在 main.c 里写业务逻辑,出了问题也只需要排查 main.c。
开发环境说明
- Keil C251:官方推荐IDE,编译STC15代码
- STC-ISP:下载程序到芯片
- CH340驱动:USB转串口
- 芯片选 STC15F2K60S2,晶振 12.000MHz
后面我会按模块逐个拆解驱动代码,每一篇都会配上实际的完整代码(从项目源码中直接提取),而不是伪代码。建议按照文章顺序阅读,先理解架构,再深入每个外设的实现细节。
更多推荐



所有评论(0)