一、过渡模拟二
1 小数点输入
case11://小数点输入if(Seg_Disp_Mode==0&&Point_Flag==0&&Seg_Input[0]!=11)//处于温度采集界面、标志位为0、输入数组第一位有数据{Seg_Point[2+Seg_Input_Index]=1;Point_Wela=Seg_Input_Index;//记录此时指针状态 便于后期数据处理Point_Flag=1;//拉高标志位 保证一次输入周期小数点只能使能一次}break;Temperature=Seg_Input[0]*100+Seg_Input[1]*10+Seg_Input[2]+5;while(3-Point_Wela){Temperature/=10.0;Point_Wela++;}2 长按短按不同功能
数码管的减速时间需要改成10ms(与按键一致),否则会出现间隔加不连贯的情况(按键在加,但数码管没刷新)
3 亮度等级
核心原理:欺骗眼睛的“视觉暂留”
- 如果不控制:LED 只有“全亮”和“全灭”两种状态。
- PWM大法:让 LED 快速地一会儿开、一会儿关。
- 周期长(慢):人眼能看清,就是闪烁。
- 周期短(快):人眼看不清开关过程,觉得它一直在亮,只是亮度变暗了。
关键术语:
- 周期 (Period):比如把时间切成一小块一小块,每一块总长度是固定的(代码里是 10)。
- 占空比 (Duty Cycle):在这一小块时间里,灯“亮”了多久。
- 亮的时间越长 = 占空比越高 = 灯越亮。
- 亮的时间越短 = 占空比越低 = 灯越暗。
把代码想象成一个切蛋糕的过程。我们要把一块蛋糕切成10 份(这就是周期)。
// 设定:我想让灯有 30% 的亮度 (Led_Pwm = 3)// 设定:蛋糕总共切 10 份 (上限是 10)unsignedcharLed_Num;// 现在的刀切到第几份了 (0~9)unsignedcharLed_Pwm;// 决定前几份蛋糕给灯吃 (亮度值)// 每执行一次,切下一份if(++Led_Num==10)Led_Num=0;// 切完10份,这就叫一个“周期”结束,重头开始// 核心判断:只要还没切到第 Led_Pwm 份,灯就亮着if(Led_Num<Led_Pwm)Led_Disp(Led_Pos,ucLed[Led_Pos]);// 【开灯】前 3 份时间亮elseLed_Disp(Led_Pos,0);// 【关灯】后 7 份时间灭附:写程序过程中遇到的问题
数码管没有闪烁,只是先亮,然后一直灭
错误代码及原因:
if(Seg_Input[2]==11)Seg_Input[Seg_Input_Index]=Seg_Flag?Seg_Input[Seg_Input_Index]:10;这里不是对Seg_Input操作,是对Seg_Buf操作
代码实现
略有点小瑕疵,比如亮度等级(由于8ms才扫描一个灯,导致PWM不太对),但总体已实现功能
/* 头文件声明区 */#include<REGX52.H>//单片机寄存器专用头文件#include<Key.h>//按键底层驱动专用头文件#include<Seg.h>//数码管底层驱动专用头文件#include<Led.h>//Led底层驱动专用头文件/* 变量声明区 */unsignedcharKey_Val,Key_Down,Key_Old,Key_Up;//按键专用变量unsignedcharKey_Slow_Down;//按键减速专用变量unsignedcharSeg_Buf[6]={10,10,10,10,10,10};//数码管显示数据存放数组unsignedcharSeg_Pos;//数码管扫描专用变量unsignedcharSeg_Point[6]={0,0,0,0,0,0};//数码管小数点存放数组unsignedintSeg_Slow_Down;//数码管减速专用变量unsignedcharLed_Pos;//Led扫描专用变量unsignedcharLed[8]={0,0,0,0,0,0,0,0};//Led显示数据存放数组unsignedcharSeg_Disp_Mode;//0-温度采集界面 1-数据显示界面 2-参数设置界面unsignedcharSeg_Input[3]={11,11,11};unsignedcharSeg_Input_Index;unsignedintTimer_500ms;unsignedcharPoint_Wela;unsignedcharParameter[2]={30,20};unsignedcharParameter_Ctrol[2]={30,20};unsignedintTimer_Count;unsignedcharLed_Num;unsignedcharLed_PWM;floatTemperature;bit Seg_Flag;bit Point_Flag;bit Parameter_Index;bit Time_Flag;bit Error_Flag;/* 键盘处理函数 */voidKey_Proc(){unsignedchari;if(Key_Slow_Down)return;Key_Slow_Down=1;//键盘减速程序Key_Val=Key_Read();//实时读取键码值Key_Down=Key_Val&(Key_Old^Key_Val);//捕捉按键下降沿Key_Up=~Key_Val&(Key_Old^Key_Val);//捕捉按键上升沿Key_Old=Key_Val;//辅助扫描变量if(Key_Down>=1&&Key_Down<=10){if(Seg_Disp_Mode==0){Seg_Input[Seg_Input_Index]=Key_Down-1;Seg_Input_Index++;}}if(Seg_Disp_Mode==2){if(Key_Down==14)Time_Flag=1;}if(Timer_Count<500){if(Key_Up==14){Time_Flag=0;Timer_Count=0;if(++Parameter_Ctrol[Parameter_Index]>70)Parameter_Ctrol[Parameter_Index]=10;}}else{if(Key_Old==14){if(++Parameter_Ctrol[Parameter_Index]>70)Parameter_Ctrol[Parameter_Index]=10;if(Key_Up==14){Time_Flag=0;Timer_Count=0;}}}if(Seg_Disp_Mode==2){if(Key_Down==15)Time_Flag=1;}if(Timer_Count<500){if(Key_Up==15){Time_Flag=0;Timer_Count=0;if(--Parameter_Ctrol[Parameter_Index]<10)Parameter_Ctrol[Parameter_Index]=70;}}else{if(Key_Old==15){if(--Parameter_Ctrol[Parameter_Index]<10)Parameter_Ctrol[Parameter_Index]=70;if(Key_Up==15){Time_Flag=0;Timer_Count=0;}}}switch(Key_Down){case11:if(Seg_Disp_Mode==0&&Point_Flag==0&&Seg_Input[0]!=11){Seg_Point[2+Seg_Input_Index]=1;Point_Wela=Seg_Input_Index;Point_Flag=1;}break;case12:if(Seg_Disp_Mode!=0){Seg_Disp_Mode++;if(Seg_Disp_Mode>2)Seg_Disp_Mode=1;if(Seg_Disp_Mode==2){Parameter_Index=0;Parameter_Ctrol[0]=Parameter[0];Parameter_Ctrol[1]=Parameter[1];}else{if(Parameter_Ctrol[0]>=Parameter_Ctrol[1]){Error_Flag=1;Parameter[0]=Parameter_Ctrol[0];Parameter[1]=Parameter_Ctrol[1];}elseError_Flag=0;}}break;case13:if(Seg_Disp_Mode==2){Parameter_Index^=1;}break;case16:if(Seg_Disp_Mode==0){if(Point_Flag==0||Seg_Input_Index<3||Seg_Input[0]==0){Seg_Input_Index=0;for(i=0;i<3;i++)Seg_Input[i]=11;Point_Flag=0;Seg_Point[3]=Seg_Point[4]=0;}else{Temperature=Seg_Input[0]*100+Seg_Input[1]*10+Seg_Input[2]+5;if(Point_Wela==1)Temperature/=100.0;elseTemperature/=10.0;if((unsignedchar)Temperature<=85){Seg_Input_Index=0;Point_Flag=0;Seg_Point[3]=Seg_Point[4]=0;Seg_Disp_Mode=1;}else{Seg_Input_Index=0;for(i=0;i<3;i++)Seg_Input[i]=11;Point_Flag=0;Seg_Point[3]=Seg_Point[4]=0;}}}else{Seg_Input_Index=0;for(i=0;i<3;i++)Seg_Input[i]=11;Seg_Point[3]=Seg_Point[4]=0;Point_Flag=0;Seg_Disp_Mode=0;}}}/* 信息处理函数 */voidSeg_Proc(){unsignedchari;if(Seg_Slow_Down)return;Seg_Slow_Down=1;//数码管减速程序switch(Seg_Disp_Mode){case0:Seg_Buf[0]=12;Seg_Buf[1]=Seg_Buf[2]=10;for(i=0;i<3;i++)Seg_Buf[3+i]=Seg_Input[i];if(Seg_Input[2]==11)Seg_Buf[3+Seg_Input_Index]=Seg_Flag?Seg_Input[Seg_Input_Index]:10;break;case1:Seg_Buf[0]=13;Seg_Buf[1]=Seg_Buf[2]=Seg_Buf[3]=10;Seg_Buf[4]=(unsignedchar)Temperature/10%10;Seg_Buf[5]=(unsignedchar)Temperature%10;break;case2:Seg_Buf[0]=14;Seg_Buf[1]=10;Seg_Buf[2]=Parameter_Ctrol[0]/10;Seg_Buf[3]=Parameter_Ctrol[0]%10;Seg_Buf[4]=Parameter_Ctrol[1]/10;Seg_Buf[5]=Parameter_Ctrol[1]%10;if(!Parameter_Index){Seg_Buf[2]=Seg_Flag?Parameter_Ctrol[0]/10:10;Seg_Buf[3]=Seg_Flag?Parameter_Ctrol[0]%10:10;}else{Seg_Buf[4]=Seg_Flag?Parameter_Ctrol[1]/10:10;Seg_Buf[5]=Seg_Flag?Parameter_Ctrol[1]%10:10;}break;}}/* 其他显示函数 */voidLed_Proc(){if(Temperature>Parameter[0])Led_PWM=3;elseif(Temperature<Parameter[0]&&Temperature>Parameter[1])Led_PWM=6;elseLed_PWM=9;Led[0]=(int)Temperature/Parameter[0];Led[1]=(!((int)Temperature/Parameter[0]))&((int)Temperature/Parameter[1]);Led[2]=!((int)Temperature/Parameter[1]);Led[3]=Error_Flag;}/* 定时器0中断初始化函数 */voidTimer0Init(void)//1毫秒@12.000MHz{TMOD&=0xF0;//设置定时器模式TMOD|=0x01;//设置定时器模式TL0=0x18;//设置定时初始值TH0=0xFC;//设置定时初始值TF0=0;//清除TF0标志TR0=1;//定时器0开始计时ET0=1;//定时器0中断打开EA=1;//总中断打开}/* 定时器0中断服务函数 */voidTimer0Server()interrupt1{TL0=0x18;//设置定时初始值TH0=0xFC;//设置定时初始值if(++Key_Slow_Down==10)Key_Slow_Down=0;//键盘减速专用if(++Seg_Slow_Down==10)Seg_Slow_Down=0;//数码管减速专用if(++Seg_Pos==6)Seg_Pos=0;//数码管显示专用if(++Led_Pos==8)Led_Pos=0;//Led显示专用Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);if(++Timer_500ms>=500){Timer_500ms=0;Seg_Flag^=1;}if(Time_Flag){if(++Timer_Count==600)Timer_Count=600;}if(++Led_Num==10)Led_Num=0;if(Led_Num<Led_PWM)Led_Disp(Led_Pos,Led[Led_Pos]);elseLed_Disp(Led_Pos,0);}/* Main */voidmain(){Timer0Init();while(1){Key_Proc();Seg_Proc();Led_Proc();}}二、蓝桥杯LED模块
1 添加芯片头文件
打开烧录工具STC-ISP
进入"Keil仿真设置"
选择单片机型号:
IAP15F2K61S2。点击添加型号和头文件到Keil中
选择Keil的安装路径
2 锁存器
锁存器的作用是保存数据,防止数据在总线上变化时影响输出。
2.1 RS锁存器
Reset(复位) 和 Set(置位)。
- R=1(使能), S=0 -> Q=0(复位)
- R=0, S=1(使能) -> Q=1(置位)
- R=0, S=0 -> 保持不变
真值表
| R | S | Q |
|---|---|---|
| 0 | 0 | Q |
| 0 | 1 | 1 |
| 1 | 0 | 0 |
| 1 | 1 | X |
2.2 D锁存器
EN=0时,输出状态保持不变
EN=1时,输出随输入状态而改变(当D=0,Q=0;当D=1,Q=1)
蓝桥杯板载芯片74HC573
八个锁存器都是D型锁存器
LE (Latch Enable):使能端,高电平有效。
LE = 1 (高电平):Q 跟随 D 变化 (直通模式)。
LE = 0 (低电平):Q 锁存住之前的数据,D 再变化 Q 也不变 (锁存模式)。
3 译码器
将 3 个输入引脚 (A, B, C) 转换成 8 个输出引脚 (Y0-Y7) 的低电平信号,用于选中不同的锁存器(片选)。
- 连接方式:P25 -> A, P26 -> B, P27 -> C (高位对应高位)。
- 输出特性:低电平有效。被选中的通道输出 0,其余输出 1。
蓝桥杯板载芯片74HC138
连接到P2口,P2高位在前(前三位:27、26、25)
真值表(基于8421BCD码)
| 输入 C (P27) | 输入 B (P26) | 输入 A (P25) | 选通输出 (低电平有效) | 功能映射 (蓝桥杯板) |
|---|---|---|---|---|
| 0 | 0 | 0 | Y0 | 未使用 |
| 0 | 0 | 1 | Y1 | 未使用 |
| 0 | 1 | 0 | Y2 | 未使用 |
| 0 | 1 | 1 | Y3 | 未使用 |
| 1 | 0 | 0 | Y4 | LED控制 |
| 1 | 0 | 1 | Y5 | 蜂鸣器/继电器 |
| 1 | 1 | 0 | Y6 | 数码管位选 |
| 1 | 1 | 1 | Y7 | 数码管段选 |
4 原理
先通过 P2 口的高 3 位控制译码器,选中对应的锁存器(LE置1),然后通过 P0 口写入数据,最后关闭锁存器(LE置0)。
5 编写程序
5.1 新建工程
选择芯片时需要先选择STC MCU Database
再选择15F2K60S2 Series
其余步骤与之前相同
5.2 关闭外设
原因:上电时 P0 口状态不确定,蜂鸣器可能会叫,继电器可能会吸合,LED可能会乱亮。初始化时需统一关闭。
init.c
#include<init.h>voidSystem_Init(){P0=0xff;P2=P2&0x1f|0x80;//先屏蔽低五位,再改变高三位P2&=0x1f;P0=0x00;P2=P2&0x1f|0xa0;P2&=0x1f;}注释版init.c
#include<init.h>voidSystem_Init(){// 关闭 LED (Y4)P0=0xFF;// LED是低电平点亮,写入FF为全灭P2=(P2&0x1F)|0x80;// 保留P2低5位,将高3位设为 100 (Y4选中)P2&=0x1F;// 将高3位清零,关闭锁存器(LE=0)// 关闭蜂鸣器和继电器 (Y5)P0=0x00;// 蜂鸣器和继电器是高电平触发,写入00为全关P2=(P2&0x1F)|0xA0;// 保留P2低5位,将高3位设为 101 (Y5选中)P2&=0x1F;// 关闭锁存器}init.h
#include<STC15F2K60S2.H>voidSystem_Init();5.3 Led底层
为了避免在操作 LED 时影响其他 P2 口的设备(虽然在这个板子上基本只用高3位,但养成好习惯很重要),或者避免反复开关锁存器造成干扰。
Led.c
#include<Led.h>voidLed_Disp(unsignedcharaddr,unsignedcharenable){staticunsignedchartemp=0x00;staticunsignedchartemp_old=0xff;if(enable)temp|=0x01<<addr;elsetemp&=~(0x01<<addr);if(temp!=temp_old){P0=~temp;P2=P2&0x1f|0x80;P2&=0x1f;temp_old=temp;}}注意:static局部变量只在第一次初始化,后续调用函数时,它会记住上一次的值,非常适合保存外设状态。
注释版Led.c
#include<Led.h>/* @brief 控制指定位置的LED亮灭 @param addr LED位置 (0-7) @param enable 1-亮, 0-灭 */voidLed_Disp(unsignedcharaddr,unsignedcharenable){staticunsignedchartemp=0x00;// 记录当前的LED状态 (1表示亮)staticunsignedchartemp_old=0xff;// 记录上一次写入P0的值// 更新状态变量if(enable)temp|=(0x01<<addr);// 对应位置1elsetemp&=~(0x01<<addr);// 对应位置0// 只有当状态发生改变时,才操作硬件if(temp!=temp_old){P0=~temp;// 取反,因为LED是低电平点亮 (temp中1表示想让它亮,P0需输出0)// 打开Y4锁存器P2=(P2&0x1F)|0x80;// 关闭Y4锁存器P2&=0x1F;temp_old=temp;// 更新旧值}}Led.h
#include<STC15F2K60S2.H>voidLed_Disp(unsignedcharaddr,unsignedcharenable);5.4 主程序
//头文件声明区#include<Init.h>#include<Led.h>//Mainvoidmain(){System_Init();while(1){Led_Disp(0,1);}}