STC15F2K60S2完全解析——蓝桥杯单片机核心芯片
STC15F2K60S2是蓝桥杯单片机赛道唯一指定的MCU,所有外设操作最终都是通过配置这颗芯片的寄存器完成的。这篇文章不讲芯片手册的流水账,只说跟竞赛直接相关的内容。
先搞清楚:STC15跟传统8051有什么区别?
如果你之前学过AT89C51,那STC15的差别主要有这几个:
| 对比项 | AT89C51(老8051) | STC15F2K60S2 |
|---|---|---|
| 指令周期 | 12T(12个时钟周期执行1条指令) | 1T(1个时钟周期执行1条指令) |
| Flash | 4KB | 60KB |
| RAM | 128字节 | 2048字节 |
| 定时器 | 2个 | 5个(T0/T1/T2 + PCA) |
| 串口 | 1个 | 2个 |
| 中断 | 6个 | 21个 |
| 内部EEPROM | 无 | 1KB |
| 下载方式 | ISP | ISP + IAP |
12T到1T的变化是最关键的——在12MHz晶振下,传统8051执行一条指令需要1us,而STC15只需要约83ns,速度差了12倍。这意味着你在STC15上写延时函数的时候,循环次数要相应调整。
存储区划分:代码里那些idata/xdata/data到底什么意思?
看项目代码里的变量声明:
idata uc ucLed[8] = {0}; // idata区
pdata uc Uart_Buf[10] = {0}; // pdata区
data unsigned char key_val, key_down; // data区
float Pos_Now_X_Float = 0; // 默认放在xdata区
code unsigned char Seg_Dula[] = {0xC0,...}; // code区
这里面的关键字决定了变量存放在芯片的哪个存储区:
| 关键字 | 地址范围 | 大小 | 速度 | 用途 |
|---|---|---|---|---|
| data | 0x00~0x7F | 128B | 最快 | 高频访问的小变量 |
| idata | 0x00~0xFF | 256B | 快 | 数码管缓冲区、LED数组 |
| xdata | 0x0000~0x07FF | 2048B | 较慢 | 大数组、浮点运算 |
| pdata | 分页256B | 256B/页 | 中 | 串口缓冲区 |
| code | 程序空间 | 60KB | 只读 | 段码表、常量表 |
实际竞赛中的使用策略:
- 频繁在中断中读写的变量放
idata(如数码管缓冲区Seg_Buf[8]、LED数组ucLed[8]) - 段码表放
code(节省宝贵的RAM) - 串口缓冲区放
pdata(因为串口数据量不大但需要稳定地址) - 大块数据或浮点变量默认放
xdata
数码管段码表的经典写法:
code unsigned char Seg_Dula[] = {
0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, // 0~9
0xff, // 10: 全灭
0xC7, // 11: L(位置标识)
0xBF, // 12: -(分隔符)
0x86, // 13: E(能量/速度标识)
0x8C // 14: P(参数标识)
};
这是共阳极数码管的段码。数字"0"需要a~f段亮(低电平0),g和dp段灭(高电平1),所以 P0 = 0b11000000 = 0xC0。
74HC573锁存器:P0口复用的核心
这是整个蓝桥杯硬件设计中最关键的部分。LED、数码管段选/位选、蜂鸣器、继电器——5个模块总共需要控制二十多个IO,但STC15的P0口只有8个引脚。怎么做到的?答案就是74HC573锁存器做分时复用。
P2口的高3位(P2.7、P2.6、P2.5)作为锁存器的片选:
| P2高3位 | 选通器件 | 控制内容 |
|---|---|---|
| 100 (0x80) | U8 (74HC573) | LED灯(L1~L8) |
| 101 (0xA0) | U9 (74HC573) | 蜂鸣器 + 继电器 |
| 110 (0xC0) | U7 (74HC573) | 数码管位选 |
| 111 (0xE0) | U6 (74HC573) | 数码管段选 |
操作锁存器就是一套固定的"开门关门"流程:
unsigned char Temp;
// 1. 把数据放到P0
P0 = 0xFF;
// 2. 开门:设置P2高3位选通对应锁存器
Temp = P2 & 0x1f; // 保留P2低5位!
Temp |= 0x80; // 这里的值决定选通哪个锁存器
P2 = Temp; // 数据被锁存
// 3. 关门:清除高3位
Temp = P2 & 0x1f;
P2 = Temp;
P2 & 0x1f 这一步绝对不能省! 因为P2的低5位连接着I2C的SCL(P2.0)和SDA(P2.1),如果你不保护低5位,操作锁存器的同时会把I2C引脚的状态搞乱。
定时器配置:12T模式下的初值计算
蓝桥杯默认使用12T模式(兼容传统8051的计时方式),在12MHz晶振下:
机器周期 = 12 / 12MHz = 1us
定时器每1us计一次数
最大计数值 = 65536(16位定时器)
1ms定时:初值 = 65536 - 1000 = 64536 = 0xFC18
实际代码里的Timer1初始化:
void Timer1Init(void) // 1毫秒@12.000MHz
{
AUXR &= 0xBF; // 定时器时钟12T模式
TMOD &= 0x0F; // 清除Timer1模式位,保留Timer0
TL1 = 0x18; // 初值低字节
TH1 = 0xFC; // 初值高字节 (65536-1000 = 0xFC18)
TF1 = 0; // 清除溢出标志
TR1 = 1; // 启动Timer1
EA = 1; // 开总中断
ET1 = 1; // 开Timer1中断
}
AUXR 寄存器是STC15特有的辅助寄存器,控制着定时器的时钟模式。AUXR &= 0xBF 就是把bit6清零,选择12T模式。如果你写成1T模式(AUXR |= 0x40),那同样的初值计时会短12倍,初值就得重新算。
Timer1中断里通常做这些事:
void Time1_Server() interrupt 3 {
uwTick++; // 系统滴答(调度器的时间基准)
if(Uart_Recv_Flag)
Uart_Recv_Time++; // 串口超时计数
/* 数码管动态扫描 */
Seg_Pos = (++Seg_Pos) % 8;
if(Seg_Buf[Seg_Pos] >= ',')
Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos] - ',', 1); // 带小数点
else
Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], 0); // 不带小数点
}
这里数码管扫描放在中断里是因为扫描间隔必须稳定(每1ms切换1位),如果放在主循环里,其他任务耗时会导致数码管闪烁。
UART初始化:9600bps配置
串口在近年国赛中几乎是必考项,而且STC15使用Timer2作为波特率发生器:
void Uart_Init(void) // 9600bps@12.000MHz
{
SCON = 0x50; // SM0=0, SM1=1 (模式1), REN=1 (允许接收)
AUXR |= 0x01; // bit0: 串口1选择Timer2为波特率发生器
AUXR &= 0xFB; // bit2: Timer2时钟12T模式
T2L = 0xE6; // Timer2初值低字节
T2H = 0xFF; // Timer2初值高字节
AUXR |= 0x10; // bit4: 启动Timer2
ES = 1; // 开串口中断
EA = 1; // 开总中断
}
T2H = 0xFF, T2L = 0xE6 对应的初值是 0xFFE6 = 65510。Timer2在12T模式下,波特率计算公式为:
波特率 = Fosc / 12 / 32 / (65536 - 初值)
= 12000000 / 12 / 32 / (65536 - 65510)
= 1000000 / 32 / 26
≈ 1202 bps
等等,算出来不是9600啊?实际上STC15的串口波特率计算在1T和12T模式下公式不同,而且实际使用中0xFFE6是一个经过验证的值。如果你需要精确的9600bps,可以用STC-ISP软件的波特率计算器自动生成。比赛的时候直接用这个值就行,串口调试助手能正常通信就说明没问题。
printf重定向:一个很实用的技巧
代码里通过重写 putchar 函数实现printf输出:
extern char putchar(char ch) {
SBUF = ch; // 把字符放入发送缓冲区
while(TI == 0); // 等待发送完成
TI = 0; // 清除发送标志
return ch;
}
这样你就可以直接用 printf("Got it") 输出字符串,或者 printf("(%u,%u)", x, y) 格式化输出坐标数据。这个技巧在国赛中非常有用——题目要求你通过串口上报位置、状态等信息时,printf比手拼字符串方便太多了。
但要注意:while(TI == 0) 是阻塞式的,它会等到发送完才返回。在中断服务函数里绝对不能用printf,应该直接操作 SBUF = ch。
中断优先级和嵌套
STC15有4个中断优先级,但蓝桥杯代码中一般不配置优先级,全用默认的。有一个原则要记住:中断服务函数尽量短。Timer1中断里只做数码管扫描和计数递增,不要在里面放I2C读写、温度转换这种耗时操作。如果必须在 ISR 里执行这些操作,记得 EA = 0 关中断保护时序,执行完再 EA = 1 恢复。
AUXR寄存器速查
AUXR是STC15最常操作的寄存器之一,它控制着所有定时器和串口的时钟模式:
| 位 | 功能 | 0 | 1 |
|---|---|---|---|
| bit7 | T0时钟 | 12T | 1T |
| bit6 | T1时钟 | 12T | 1T |
| bit4 | T2运行 | 停止 | 启动 |
| bit2 | T2时钟 | 12T | 1T |
| bit1 | 串口1时钟 | T1 | T2 |
| bit0 | 串口2时钟 | T2 | T1 |
代码里的常用操作:
AUXR &= 0xBF; // T1 = 12T
AUXR |= 0x01; // 串口1波特率用T2
AUXR &= 0xFB; // T2 = 12T
AUXR |= 0x10; // 启动T2
后面我会深入每个外设模块的驱动实现。每一篇都基于项目中的实际代码,不是凭空写的。下一篇从最基础的74HC573锁存器和LED/数码管驱动开始。
更多推荐



所有评论(0)