第十五届蓝桥杯单片机省赛第一场 — 刷题总结

第十五届蓝桥杯单片机省赛 — 刷题总结

一、题目概要

  • 芯片:STC IAP15F2K61S2,12MHz 晶振
  • 外设:NE555方波输入、数码管×8、LED×8、矩阵键盘、DS1302实时时钟、PCF8591 DAC
  • 核心功能:频率测量 + 参数调节 + DAC线性输出 + 历史最大值记录

二、系统架构

Timer0(计数模式)←── NE555方波 (P3.4)
Timer1(1ms中断)
    ├── 系统节拍 uwTick
    ├── 数码管动态扫描
    ├── L1/L2 LED控制(每200ms)
    ├── 1秒门控:读取Timer0 → Freq
    └── freq_last / 历史最大值更新

主循环(时间片调度器)
    ├── Key_Proc   10ms
    ├── Seg_Proc   20ms
    ├── Get_Time   100ms(读DS1302)
    └── AD_DA      150ms(DAC输出)

三、数码管显示模式(S4切换)

Seg_Show_Mod 界面 格式示例
0 F(频率) F _ _ _ 2 3 5 0(右对齐,前置空)
1 P(参数) P1: P 1 _ _ 2 0 0 0 / P2: P 2 _ _ - 3 0 0
2 时钟 1 3 - 4 5 - 0 5
3 H(历史) HF: H F _ 2 3 5 0 / HT: H A 1 3 4 5 0 4
  • S5:在 P界面切换 P1/P2;在 H界面切换 HF/HT
  • S8/S9:参数界面调节数值(P1步进1000,P2步进100)

四、DAC 输出规则

freq_last 范围 DAC输出 da_val
< 0(校准后频率为负) 0V 0
0 ~ 500 Hz 1V 51
500 ~ pf Hz(线性) 1V ~ 5V 51 + 4×51×(f-500)/(pf-500)
≥ pf Hz 5V 255

五、LED 行为规则

条件 L1 L2
Seg_Show_Mod == 0(F界面) 每200ms翻转
其他界面 熄灭
freq_last > pf 每200ms翻转
freq_last < 0 常亮
正常范围 熄灭

六、本题 Bug 汇总与修正

Bug 1 — 中间量类型溢出

位置:全局变量声明区

错误代码 修正代码
问题 idata unsigned char freq_delta_pf; idata unsigned int freq_delta_pf;
问题 idata unsigned char freq_delta; idata unsigned int freq_delta;

原因pf 最大 9000,pf - 500 = 8500,超出 unsigned char(0~255)范围,溢出后 DAC 计算完全错误。


Bug 2 — DAC 线性段公式错误

位置AD_DA() 函数

// 错误:分子分母相同,结果永远是 4×51+51 = 255(5V)
da_val = 4*51*(freq_delta)/(freq_delta) + 51;

// 修正:除以 pf 与 500 的差值
da_val = 4*51*(freq_delta)/(freq_delta_pf) + 51;

原因:复制变量名时笔误,/freq_delta 应为 /freq_delta_pf


Bug 3 — DAC 0Hz 条件判断遗漏

位置AD_DA() 函数

// 错误:freq_last > 0 排除了 0Hz,导致 0Hz 时 da_val 不更新
}else if((freq_last <= 500) && (freq_last > 0)){

// 修正:包含 0Hz,0Hz 也输出 1V(题目要求 0~500Hz 均为 1V)
}else if((freq_last <= 500) && (freq_last >= 0)){

Bug 4 — S4 退出参数界面只保存一个参数

位置Key_Proc() 函数

// 错误:只保存当前显示的那个参数
if(Seg_Show_Mod == 2)
{
    if(!parameter_show_mod)  pf = pf_temp;
    else                     sta_val = sta_val_temp;
}

// 修正:同时保存两个参数
if(Seg_Show_Mod == 2)
{
    pf = pf_temp;
    sta_val = sta_val_temp;
}

原因:若在 P1 界面修改了 pf,切到 P2 后按 S4 退出,pf 的修改会丢失。


Bug 5 — HT 时间显示个位写成了十位

位置Seg_Proc() case 3,back_show_mod == 1 分支

// 错误:/10 取的是十位,导致分钟、秒的个位显示错误
Seg_Buf[5] = uchft[1]/10;
Seg_Buf[7] = uchft[2]/10;

// 修正:%10 取个位
Seg_Buf[5] = uchft[1]%10;
Seg_Buf[7] = uchft[2]%10;

Bug 6 — DAC 线性段乘法溢出

位置AD_DA() 函数

// 错误:204 × freq_delta,freq_delta 最大 9499
// 204 × 9499 = 1,937,796 > 65535,unsigned int 溢出,DAC 输出完全错误
da_val = 4*51*(freq_delta)/(freq_delta_pf) + 51;

// 修正:强制转 unsigned long 再做乘法,避免中间值溢出
da_val = (unsigned int)((unsigned long)204 * freq_delta / freq_delta_pf) + 51;

验证(freq_last=1000, pf=2000):

计算过程 结果
错误版 204×500=102000 → 溢出为 3646436464/1500+51=75 约 1.47V(错误)
修正版 (ulong)204×500=102000102000/1500+51=119 约 2.33V(正确)

原因:C51 中 unsigned int 为 16 位,乘法结果超过 65535 时高位被截断,必须在乘法前先强制转为 32 位 unsigned long


Bug 7 — 中断 ISR 中全局变量 i 未初始化

位置Timer1_Isr() 中断服务函数

// 错误:i 是全局变量,进入中断时值不确定(可能 >= 3,循环体永不执行)
for(i;i<3;i++)
{
    uchft[i] = ucRtc[i];
}

// 修正:循环前显式初始化
for(i=0;i<3;i++)
{
    uchft[i] = ucRtc[i];
}

原因i 定义为全局变量(idata unsigned char i),在中断 ISR 里复用时必须手动赋初值,否则历史最大频率出现时间永远不会被记录



七、完整代码

main.c

#include <STC15F2K60S2.H>
#include "LED.h"
#include "init.h"
#include "Seg.h"
#include "Key.h"
#include "ds1302.h"
#include "iic.h"

/* ==================== 全局变量定义 ==================== */
idata unsigned long int uwTick;
pdata unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};
pdata unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10};
idata unsigned char Seg_Pos = 0;
idata unsigned char Key_Val, Key_Old, Key_Up, Key_Down;
idata unsigned char ucRtc[3] = {12,23,45};
idata unsigned int Temperature_10x;
idata unsigned int Freq;
idata unsigned int Time_1s;

idata unsigned char Seg_Show_Mod;
bit parameter_show_mod;
idata int pf = 2000;
idata int pf_temp = 2000;
idata int sta_val = 0;
idata int sta_val_temp = 0;
idata unsigned int sta_val_abs = 0;
idata unsigned int sta_val_temp_abs = 0;
idata unsigned int hf;
bit back_show_mod;
idata unsigned char uchft[3] = {0,0,0};
idata int freq_last;
idata int freq_last_max;
idata unsigned int da_val;
idata unsigned int freq_delta_pf;      // [修正Bug1] unsigned int,防止溢出
idata unsigned char da_val_uc;
idata unsigned int freq_delta;         // [修正Bug1] unsigned int,防止溢出
idata unsigned int time200;
idata unsigned char i = 0;

/* ==================== 按键处理 ==================== */
void Key_Proc()
{
    Key_Val = Key_Read();
    Key_Down = Key_Val & (Key_Val ^ Key_Old);
    Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
    Key_Old = Key_Val;

    if(Key_Down == 4)
    {
        Seg_Show_Mod++;
        if(Seg_Show_Mod == 4) Seg_Show_Mod = 0;
        if(Seg_Show_Mod == 2)           // [修正Bug4] S4退出参数界面同时保存两个参数
        {
            pf = pf_temp;
            sta_val = sta_val_temp;
        }
        if(Seg_Show_Mod == 1) parameter_show_mod = 0;
        if(Seg_Show_Mod == 3) back_show_mod = 0;
    }

    if(Seg_Show_Mod == 1)
    {
        if(Key_Down == 5) parameter_show_mod ^= 1;

        if(!parameter_show_mod)
        {
            if(Key_Down == 8) { pf_temp += 1000; if(pf_temp > 9000) pf_temp = 9000; }
            if(Key_Down == 9) { pf_temp -= 1000; if(pf_temp < 1000) pf_temp = 1000; }
        }
        else
        {
            if(Key_Down == 8) { sta_val_temp += 100; if(sta_val_temp > 900) sta_val_temp = 900; }
            if(Key_Down == 9) { sta_val_temp -= 100; if(sta_val_temp < -900) sta_val_temp = -900; }
            if(sta_val_temp < 0) sta_val_temp_abs = (-1) * sta_val_temp;
            else sta_val_temp_abs = sta_val_temp;
        }
    }

    if(Seg_Show_Mod == 3)
    {
        if(Key_Down == 5) back_show_mod ^= 1;
    }
}

/* ==================== 数码管显示 ==================== */
void Seg_Proc()
{
    switch(Seg_Show_Mod)
    {
        case 0: // F界面:频率显示
            if(freq_last < 0)
            {
                Seg_Buf[0] = 11; // F
                Seg_Buf[1] = 10; Seg_Buf[2] = 10; Seg_Buf[3] = 10;
                Seg_Buf[4] = 10; Seg_Buf[5] = 10;
                Seg_Buf[6] = 16; Seg_Buf[7] = 16; // 无效显示
            }
            else
            {
                Seg_Buf[0] = 11;
                Seg_Buf[1] = 10;
                Seg_Buf[2] = 10;
                Seg_Buf[3] = (freq_last/10000) ? (freq_last/10000%10) : 10;
                Seg_Buf[4] = (freq_last/1000)  ? (freq_last/1000%10)  : 10;
                Seg_Buf[5] = (freq_last/100)   ? (freq_last/100%10)   : 10;
                Seg_Buf[6] = (freq_last/10)    ? (freq_last/10%10)    : 10;
                Seg_Buf[7] = freq_last % 10;
            }
        break;

        case 1: // P界面:参数调节
            if(!parameter_show_mod)
            {
                Seg_Buf[0] = 12; Seg_Buf[1] = 1;  // P1
                Seg_Buf[2] = 10; Seg_Buf[3] = 10;
                Seg_Buf[4] = (pf_temp/1000) ? (pf_temp/1000%10) : 10;
                Seg_Buf[5] = (pf_temp/100)  ? (pf_temp/100%10)  : 10;
                Seg_Buf[6] = (pf_temp/10)   ? (pf_temp/10%10)   : 10;
                Seg_Buf[7] = pf_temp % 10;
            }
            else
            {
                Seg_Buf[0] = 12; Seg_Buf[1] = 2;  // P2
                Seg_Buf[2] = 10; Seg_Buf[3] = 10;
                Seg_Buf[4] = (sta_val_temp >= 0) ? 10 : 15;  // 负号
                Seg_Buf[5] = (sta_val_temp_abs/100) ? (sta_val_temp_abs/100%10) : 10;
                Seg_Buf[6] = (sta_val_temp_abs/10)  ? (sta_val_temp_abs/10%10)  : 10;
                Seg_Buf[7] = sta_val_temp_abs % 10;
            }
        break;

        case 2: // 时钟界面
            Seg_Buf[0] = ucRtc[0]/10%10;
            Seg_Buf[1] = ucRtc[0]%10;
            Seg_Buf[2] = 15; // -
            Seg_Buf[3] = ucRtc[1]/10%10;
            Seg_Buf[4] = ucRtc[1]%10;
            Seg_Buf[5] = 15; // -
            Seg_Buf[6] = ucRtc[2]/10%10;
            Seg_Buf[7] = ucRtc[2]%10;
        break;

        case 3: // H界面:历史最大值
            if(!back_show_mod)
            {
                Seg_Buf[0] = 13; Seg_Buf[1] = 11; // HF
                Seg_Buf[2] = 10;
                Seg_Buf[3] = (freq_last_max/10000) ? (freq_last_max/10000%10) : 10;
                Seg_Buf[4] = (freq_last_max/1000)  ? (freq_last_max/1000%10)  : 10;
                Seg_Buf[5] = (freq_last_max/100)   ? (freq_last_max/100%10)   : 10;
                Seg_Buf[6] = (freq_last_max/10)    ? (freq_last_max/10%10)    : 10;
                Seg_Buf[7] = freq_last_max % 10;
            }
            else
            {
                Seg_Buf[0] = 13; Seg_Buf[1] = 14; // HA(历史时刻)
                Seg_Buf[2] = uchft[0]/10%10;
                Seg_Buf[3] = uchft[0]%10;
                Seg_Buf[4] = uchft[1]/10%10;
                Seg_Buf[5] = uchft[1]%10;          // [修正Bug5] %10 取个位
                Seg_Buf[6] = uchft[2]/10%10;
                Seg_Buf[7] = uchft[2]%10;          // [修正Bug5] %10 取个位
            }
        break;
    }
}

void Led_Proc() { }

void Get_Time() { Read_Rtc(ucRtc); }

/* ==================== DAC 输出 ==================== */
void AD_DA()
{
    if(freq_last < 0)
    {
        da_val = 0;
    }
    else if((freq_last <= 500) && (freq_last >= 0))  // [修正Bug3] >= 0 包含0Hz
    {
        da_val = 51;
    }
    else if((freq_last > 500) && (freq_last < pf))
    {
        freq_delta_pf = pf - 500;
        freq_delta = freq_last - 500;
        da_val = (unsigned int)((unsigned long)204 * freq_delta / freq_delta_pf) + 51;  // [修正Bug2+Bug6] 防溢出
    }
    else if(freq_last >= pf)
    {
        da_val = 5*51;
    }
    da_val_uc = (unsigned char)da_val;
    Da_Write(da_val_uc);
}

/* ==================== 定时器初始化 ==================== */
void Timer1_Init(void)
{
    AUXR &= 0xBF;
    TMOD &= 0x0F;
    TL1 = 0x18; TH1 = 0xFC;
    TF1 = 0; TR1 = 1; ET1 = 1; EA = 1;
}

void Timer0_Init(void)
{
    AUXR &= 0x7F;
    TMOD &= 0xF0;
    TMOD |= 0x05;
    TL0 = 0x00; TH0 = 0x00;
    TF0 = 0; TR0 = 1;
}

/* ==================== 中断服务函数 ==================== */
void Timer1_Isr(void) interrupt 3
{
    uwTick++;

    Seg_Pos = (++Seg_Pos) % 8;
    if(Seg_Buf[Seg_Pos] > 20)
        Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos] - ',', 1);
    else
        Seg_Disp(Seg_Pos, Seg_Buf[Seg_Pos], 0);

    if(++time200 == 200)
    {
        time200 = 0;
        if(Seg_Show_Mod == 0) ucLed[0] ^= 1;
        else ucLed[0] = 0;

        if(freq_last > pf)       ucLed[1] ^= 1;
        else if(freq_last < 0)   ucLed[1] = 1;
        else                     ucLed[1] = 0;
    }

    if(++Time_1s == 1000)
    {
        Time_1s = 0;
        Freq = TH0 << 8 | TL0;
        TH0 = TL0 = 0;
    }

    freq_last = Freq + sta_val;
    if(freq_last_max < freq_last) freq_last_max = freq_last;
    if(hf ^ freq_last_max)
    {
        for(i=0; i<3; i++)       // [修正Bug6] i=0 显式初始化
        {
            uchft[i] = ucRtc[i];
        }
    }
    hf = freq_last_max;
    Led_Disp(ucLed);
}

/* ==================== 调度器 ==================== */
typedef struct
{
    void(*task_func)(void);
    unsigned long int rate_ms;
    unsigned long int last_ms;
} task_t;

idata task_t Scheduler_Task[] = {
    {Led_Proc,  1,   0},
    {Key_Proc,  10,  0},
    {Seg_Proc,  20,  0},
    {Get_Time,  100, 0},
    {AD_DA,     150, 0}
};

idata unsigned char task_num;

void Scheduler_Init()
{
    task_num = sizeof(Scheduler_Task) / sizeof(task_t);
}

void Scheduler_Run()
{
    unsigned char i;
    for(i=0; i<task_num; i++)
    {
        unsigned long int now_time = uwTick;
        if(now_time >= (Scheduler_Task[i].rate_ms + Scheduler_Task[i].last_ms))
        {
            Scheduler_Task[i].last_ms = now_time;
            Scheduler_Task[i].task_func();
        }
    }
}

/* ==================== 主函数 ==================== */
void main()
{
    System_Init();
    Scheduler_Init();
    Set_Rtc(ucRtc);
    Timer0_Init();
    Timer1_Init();
    while(1)
    {
        Scheduler_Run();
    }
}

八、考点总结

考点 关键点
类型溢出 中间计算量若超过 255,必须用 unsigned int,不能用 unsigned char
全局变量在 ISR 中复用 中断里使用全局循环变量,循环前必须 i=0 显式赋初值
DAC 线性映射公式 da_val = 基准 + 斜率×(x-x0)/(x1-x0),分母变量名不能写成分子
乘法中间值溢出 C51 中 unsigned int 仅 16 位,乘法前需强制转 (unsigned long) 再做运算,最后转回结果类型
参数保存时机 参数界面有多个子页时,退出时应保存所有参数,不只保存当前页
数位提取 十位用 /10%10,个位用 %10,两者不可混用
频率测量原理 Timer0 计数模式(TMOD|=0x05),Timer1 提供1s门控,读完立即清零
hf ^ freq_last_max 利用 XOR 检测值是否发生变化,变化则记录时间戳