赞
踩
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
提示:这里可以添加本文要记录的大概内容:
考察部分:除了三大金刚(LED、按键、数码管)外,本套题还考察到了Ds18b20、DAC、继电器,超声波。考察的不是特别多,还可以介绍。
关于新鲜点:
1.在工厂模式下的校准值的设置,要注意有个符号位 -
2.在测距界面下,Led1~Led8亮灭的方式
3.考察到了S8和S9同时按下的双按键
两个注意点:
一:当在测距界面按下S8时开启记录功能,连续记录6s,但是被记录的数据是最近一次的数据,也就是说,当只需要在开启记录功能后的第六秒,测量一次距离,并将值传给记录变量;
二:真实距离值等于测量的距离值加上校准的距离值
提示:以下是本篇文章正文内容,下面案例可供参考
Ds18b20,DA输出这两个外设的配置相信大家早已烂熟于心,这里就不做展示了,有需要的小伙伴可以自行搜索。
下面来讲讲继电器、超声波,这两个外设的配置。
1.继电器
让我们先来了解一下什么是static关键字
在C语言中,`static` 关键字有多种用途,主要包括声明静态变量、静态函数以及定义静态成员变量和函数。以下是 `static` 关键字的一些常见用法:
1. **静态变量**:
- 在函数内部,使用 `static` 声明的变量在函数调用结束后不会消失,而是保留其值,直到程序结束。
- 这些变量的初始化只在第一次调用函数时进行,之后的函数调用会保留上一次的值。2. **全局静态变量**:
- 使用 `static` 声明的全局变量具有内部链接,这意味着它们只能在定义它们的文件中访问,而不能在其他文件中访问。3. **静态函数**:
- 使用 `static` 声明的函数只能在定义它们的文件中调用,这有助于避免命名冲突。4. **静态成员变量和函数**:
- 在结构体或类中,使用 `static` 声明的成员变量或函数是静态的,属于类型本身而不是类型的实例。5. **编译器优化**:
- `static` 关键字还可以帮助编译器进行优化,因为编译器知道 `static` 变量的生命周期,可以做出更好的优化决策。6. **存储类说明符**:
- `static` 是一种存储类说明符,它控制变量或函数的生命周期和可见性。7. **初始化**:
- 静态变量可以初始化,如果没有显式初始化,全局和静态变量会被自动初始化为0。使用 `static` 关键字可以提高程序的可读性、可维护性,并有助于管理程序中变量和函数的生命周期。在多文件项目中,`static` 可以用来避免全局变量和函数的命名冲突。
代码部分展示
- void Beep_Disp(unsigned char pos,enable)
- {
- static unsigned char temp = 0x00;
- static unsigned char temp_old = 0xff;
- if(enable)
- {
- temp |= (0x01<<pos);
- }else{
- temp &= ~(0x01<<pos);
- }
-
- if(temp != temp_old)
- {
- P0 = temp;
- P2 = P2&0x1f | 0xa0;
- P2 &= 0x1f;
- temp_old = temp;
- }
- }
分析:
在这个 `Beep_Disp` 函数中,`static` 关键字用于声明两个局部变量 `temp` 和 `temp_old`,使它们在函数调用之间保持其值。这是 `static` 关键字在函数内部的一个常见用法,它允许这些变量在程序的整个生命周期内存在,而不仅仅是在函数调用的持续时间内。
让我们逐步分析这个函数:
1. **静态变量初始化**:
- static unsigned char temp = 0x00;声明了一个静态变量 temp,并初始化为 0x00。
这意味着每次程序运行开始时,temp`都会被设置为 0x00。
- static unsigned char temp_old = 0xff;声明了另一个静态变量 temp_old,并初始化为 0xff。
这同样意味着每次程序开始时,temp_old 都会被设置为 0xff。
2. **根据 enable 参数更新 temp**:
- 如果 enable 参数为非零值(通常这意味着它应该启用),则 temp 通过位运算 `|=`(按位或赋值)与 `(0x01<<pos)` 结合,这会在 `temp` 的第 `pos` 位设置一个 `1`。
- 如果 `enable` 参数为零,`temp` 通过位运算 `&=`(按位与赋值)与 `(~(0x01<<pos))` 结合,这会在 `temp` 的第 `pos` 位清零。
3. **检查 `temp` 是否发生变化**:
- 如果 `temp` 的值与 `temp_old` 不同,这意味着输出状态已经改变。
4. **更新硬件状态**:
- 如果 `temp` 发生变化,函数会通过 `P0 = temp;` 将 `temp` 的值赋给端口 `P0`,这可能连接到一些LED或其他输出设备。
- 然后,根据:
P2 = P2 & 0x1f | 0xa0;
P2 &= 0x1f;
这两行代码来选择HC573锁存器的特定通道,这可能是为了更新与 `P0` 端口连接的设备的状态。
5. **更新 `temp_old`**:
- 无论 `temp` 是否变化,`temp_old` 都会被更新为当前的 `temp` 值,以便在下一次函数调用时检测变化。
通过使用 `static` 关键字,这个函数能够在每次调用时记住 `temp` 和 `temp_old` 的值,从而实现对输出设备状态的跟踪和更新。如果没有使用 `static`,`temp` 和 `temp_old` 将在每次函数调用结束后重置为其初始值,这将导致无法正确跟踪状态变化。
2.超声波
这里给还不了解的PCA的小伙伴们提供一个新思路,用PCA定时来配置。
其实在蓝桥杯单片机的这个开发板STC15f2k60s2中,有3(定时器0、定时器1、定时器2)+1个定时器(PCA)
PCA(Programmable Counter Array,可编程计数器阵列)是8051微控制器中的一个多功能硬件模块,它在蓝桥杯单片机竞赛中使用的单片机上也可能存在。PCA能够执行多种定时器和计数器相关的任务。
使用超声波测量距离的原理:
超声波相关硬件发送一个信号,当遇到障碍是就返回,相关硬件根据两次的时间差,从而计算出距离。
公式 : 距离 = 声速 * 发出超声波到接收返回的时间 / 2;
实现步骤:
1.产生8个40KHz的超声波,通过Tx引脚发射出去
- void Ul_Wave_Init()
- {
- unsigned char i;
- for(i=0;i<8;i++)
- {
- Tx = 1;
- Delay12us();
- Tx = 0;
- Delay12us();
- }
- }
在这之前,先生成一个12us的延时,为什么是12us?
1s = 1000ms = 1000 000us
T = 1/f ; f = 40Khz = 40 000;
所以T = 25us ,以25秒为一个周期他的一半是12或13,所以我们可以生成一个12us或13us的延时
- void Delay12us() //@12.000MHz
- {
- unsigned char i;
-
- _nop_(); //注意:用到了这个函数,要添加相应的头文件 --> #include <intrins.h>
- _nop_();
- i = 33;
- while (--i);
- }
2.发送后,开启定时器,计算脉冲
3.等待超声波信号返回,如果接收到反射回来的信号,Rx引脚变为高电平
4.停止定时器,读取脉冲个数,即获得时间T
5.根据公式算出距离
- unsigned char Wave_Dat()
- {
- unsigned int time;
- CMOD = 0;
- CH =CL = 0;
- Wave_Init();
- CR = 1;
- while((Rx == 1)&&(CF == 0));
- CR = 0;
- if(CF == 0)
- {
- time = (CH<<8|CL)*0.017;//*0.017 等价于*340(声速)/2/10000;
- return time;
- }else{
- CF = 0;
- return 0;
- }
- }
为什么上面的*0.017等价于我上面写的呢?
因为由公式L = V * T/2;其中,V的单位为m/s,而T的单位为us,然后我们计算出来的距离的单位是厘米,所以涉及了单位的转化。为什么要将这些,
因为如果是一般的超声波,他的声速我们默认是340m/s,而在本题中,声速是可以改变的,所以我们这里要用一个变量
而且我们这个变量除了在超声波文件中可以用,还要在main.c文件中可以使用,这里就需要了解另一个关键字extern;
在超声波相关函数Ultrasound.c中,
#include <STC15F2K60S2.H> #include "Ultrasound.h" #include <intrins.h> sbit Tx = P1^0; sbit Rx = P1^1; extern unsigned int Speed_Num;//传播速度 void Delay12us() //@12.000MHz { unsigned char i; _nop_(); _nop_(); i = 33; while (--i); } void Ul_Wave_Init() { unsigned char i; for(i=0;i<8;i++) { Tx = 1; Delay12us(); Tx = 0; Delay12us(); } } unsigned char Ul_Wave_Data() { unsigned int time; CMOD = 0; CH = CL = 0; Ul_Wave_Init(); CR = 1; while((Rx == 1)&& (CF == 0)); CR = 0; if(CF == 0) { time = (CH <<8| CL)*(Speed_Num/20000.0); return time; }else{ CF = 0; return 0; } }在这里用extern关键字定义一个变量
extern unsigned int Speed_Num;//传播速度
注意:这里只定义,不要赋值,赋值在main.c文件中声明的时候进行。
不然会报错!!!
main.c文件中
uint Speed_Num = 340;//传播速度 */定义在了Ultrasound.c文件中
extern关键之关于多次声明,但只能定义一次。
以上,我们完成了基础外设的配置,接下来我们来解决题目中的一些坑。
本题中校准值的设置是-90~90;因为我觉得有负数不好处理,所以我操作了一下
我将范围设置为0~180,这样我这里的90,就相当于题目中指的0;
大于等于90,就显示 x - 90
而小于90的部分就显示 - (90 - x)
- uchar Aux_values = 90;/* 关于校准值既然范围是-90到90,有负数有点麻烦,
- 所以我们不放将范围等价到0-180,以90为初始值*/
-
- bit Aux_Flag;//当Aux_values >=90,为 0 当Aux_values<90,为 1
-
- smg[0] = 17;//F
- smg[1] = Factory_Son_mode + 1;
- smg[2] = smg[3] = smg[4] = 10;
- if(Aux_values >= 90)
- {
- smg[5] = 10;
- smg[6] = (Aux_values - 90) > 9? ((Aux_values - 90)/10) : 10;
- smg[7] = (Aux_values - 90)%10;
- }else{
- if((90 - Aux_values) > 9)
- {
- smg[5] = 11;//-
- smg[6] = (90 - Aux_values) > 9? ((90 - Aux_values)/10) : 10;
- smg[7] = (90 - Aux_values)%10;
- }else {
- smg[5] = 10;
- smg[6] = 11;//-
- smg[7] = (90 - Aux_values)%10;
- }
- }
- smg_point[6] = 0;
为了防止大家看不懂我的数码管显示
这里附上数码管相关底层
#include <STC15F2K60S2.H> #include "Smg.h" code smg_weixuan[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; code unsigned char smg_duanxuan[] = { 0xc0, //0 0xf9, //1 0xa4, //2 0xb0, //3 0x99, //4 0x92, //5 0x82, //6 0xf8, //7 0x80, //8 0x90, //9 0xff, //(空)10 0xbf, //"-" 11 0x88, //A12 0x83, //b13 0xc6, //C14 0xa1, //d15 0x86, //E16 0x8e, //F17 0x8c //P18 }; void Smg_Disp(unsigned char x,y,point) { P0 = 0xff; P2 = P2&0x1f|0xe0; P2 &= 0x1f; P0 = smg_weixuan[x]; P0 = 0xff; P2 = P2&0x1f|0xc0; P2 &= 0x1f; P0 = smg_duanxuan[y]; if(point) P0 &= 0x7f; P2 = P2&0x1f|0xe0; P2 &= 0x1f; }//数码管
smg[8] = {10,10,10,10,10,10,10,10};
smg_point[8] = {0,0,0,0,0,0,0,0};
关于测距界面LED的亮灭,有点想计算机中2进制的转10进制
L1(P00) | L2(P01) | L3(P02) | L4(P03) | L5(P04) | L6(P05) | L7(P06) | L8(P07) | |
---|---|---|---|---|---|---|---|---|
二进制 | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 |
十进制 | 0* 2^0 | 0* 2^1 | 0* 2^2 | 0* 2^3 | 1* 2^4 | 0* 2^5 | 1* 2^6 | 0*2^7 |
结果 = 1* 2^4 + 1* 2 ^6 = 16 + 64 = 80;
所以我们只需用二进制表示相关LED亮灭即可,
为防止小伙伴们看不懂我写的LED,下面先展示我的LED相关底层
#include <STC15F2K60S2.H> #include "Led.h" void Led_Disp(unsigned char pos,enable) { static unsigned char temp = 0x00; static unsigned char temp_old = 0xff; if(enable) { temp |= (0x01<<pos); }else{ temp &= ~(0x01<<pos); } if(temp != temp_old) { P0 = ~temp; P2 = P2&0x1f | 0x80; P2 &= 0x1f; temp_old = temp; } }//LED
led[8] = {0,0,0,0,0,0,0,0};
生成一个1ms的定时器0;将数码管,按键,LED的减速放在定时器中,我们尽量不要使用delay延时,因为这个很呆,当执行delay时我们的单片机是不能干别的事情的,这就可能导致数据执行时,数据刷新不到位,可能会有一系列Bug等着你。
void Timer0Init(void) //1毫秒@12.000MHz { AUXR &= 0x7F; //定时器时钟12T模式 TMOD &= 0xF0; //设置定时器模式 TL0 = 0x18; //设置定时初值 TH0 = 0xFC; //设置定时初值 TF0 = 0; //清除TF0标志 TR0 = 1; //定时器0开始计时 ET0 = 1; EA = 1; } //*******中断服务函数 void Timer0() interrupt 1 { static uchar a; if(++Key_Slow_Down == 10) Key_Slow_Down = 0; if(++Led_Slow_Down == 200) Led_Slow_Down = 0; if(++Smg_Slow_Down == 500) Smg_Slow_Down = 0; Smg_Disp(a,smg[a],smg_point[a]); Led_Disp(a,led[a]); if(++a == 8) a = 0; ..... }不要忘记将Timer0Init()放在程序的开始
//******主程序 void main() { AllStart(); while(Temperature_Read() == 85); *****Timer0Init();******* while(1) { SMG_Process(); KEY_Process(); Led_Process(); Other_Process(); } }
然后就是在测距模式下Led相关的亮灭了
代码展示
- void Led_Process()
- {
- uchar i;
-
-
- if(Mode == 2){
- for(i=1;i<8;i++)
- led[i] = 0;
- led[0] = L1_Flag?1:0;
- }
-
-
- if(Led_Slow_Down) return;
- Led_Slow_Down = 1;
-
- if(Mode == 0)
- {
- if((Ultrasound_old - Ultrasound < 10) || (Ultrasound - Ultrasound_old <10))
- {
- led[0] = Ultrasound%2;
- led[1] = Ultrasound/2%2;
- led[2] = Ultrasound/4%2;
- led[3] = Ultrasound/8%2;
- led[4] = Ultrasound/16%2;
- led[5] = Ultrasound/32%2;
- led[6] = Ultrasound/64%2;
- led[7] = Ultrasound/128;
- }
- }else if(Mode == 1)
- {
- for(i=0;i<7;i++)
- led[i] = 0;
- led[7] = 1;
- }
- }
上述代码中if((Ultrasound_old - Ultrasound < 10) || (Ultrasound - Ultrasound_old <10))
这个是为了防止我们数码管上显示的距离突然跳变的,其实就是为了好看一点,也可以不加。
新考点,两个按键都按下。
关于按键,我采用的是三行按键法,有人可能会问,为什么不用状态机,实不相瞒,其实我以前会状态机法,但是自从学了三行按键法,因为三行按键法只有三行,比较简介易上手(其实就是我懒,不想多敲)渐渐地就忘记了。
关于三行按键法
在使用前要先定义几个变量
- //按键
- uchar Key_Val,Key_Down,Key_Up,Key_Old;
-
-
- Key_Val = Key_Scan();
- //三行按键法
- Key_Down = Key_Val & (Key_Val ^ Key_Old);
- Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
- Key_Old = Key_Val;
这里的Key_Down和Key_Up都是瞬时值
而Key_Old的值比Key_Val延后10ms
为什么是10ms呢?
因为我们在定时器中断中,按键的减速变量设置的是10ms
按键之所以要减速是因为要去除抖动,相信大家都知道了。
其中,
Key_Val = Key_Scan(); //获取被按下的按键值
那么我们有要求看Key_Scan()这个函数
- //矩阵按键
- unsigned char Key_Scan()
- {
- unsigned char temp = 0;
-
- //第一行
- P44 = 0;P42 = 1;P35 = 1;P34 = 1;
- if(P33 == 0) temp = 4;
- if(P32 == 0) temp = 5;
- if(P31 == 0) temp = 6;
- if(P30 == 0) temp = 7;
- //第二行
- 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;
- if(P33 == 0 && P32 == 0) temp = 89;
- //第三行
- 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;
- //第四行
- 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;
- }
有人可能发现了扫描第二行时的if(P33 == 0 && P32 == 0) temp = 89;
后面会讲解
注意这里的 temp变量一定要赋初始值 ,不然按键会有问题。
unsigned char temp = 0;
下面关于三行按键法
这⾥以S4(0100)为例,来进⾏说明
键盘状态 | Key_Val | Key_Old | Key_Val^Key_Old | Key_Down | Key_Up |
---|---|---|---|---|---|
未按下 | 0000 | 0000 | 0000^0000 = 0000 | 0000 | 0000 |
按下过程中(10ms) | 0100 | 0000 | 0100^0000 = 0100 | 0100 | 0000 |
按下稳定(10ms后) | 0100 | 0100 | 0100^0100= 0000 | 0000 | 0000 |
抬起过程(10ms) | 0000 | 0100 | 0000^0100 = 0100 | 0000 | 0100 |
双按键相关代码展示
void KEY_Process() { if(Key_Slow_Down) return; Key_Slow_Down = 1; Key_Val = Key_Scan(); //三行按键法 Key_Down = Key_Val & (Key_Val ^ Key_Old); Key_Up = ~Key_Val & (Key_Val ^ Key_Old); Key_Old = Key_Val; if(Key_Old == 89) { S8S9_Flag = 1; Key_Clock = 1; } if(Key_Clock && Key_Old) return;//还在同时按下 Key_Clock = 0; ....... }上述代码中的Key_Clock 变量其实是给双按键上来一个锁,因为本套题目中,S8与S9单独按下时均有功能,所以为了防止误触发。
而当我同时按下时我让我的一个标志位置1 --> S8S9_Flag = 1;
在Other_Process()函数中执行相关功能:
void Other_Process() { if(Reset) { Distance_Para = 40; T_Para_10x = 300; Aux_values = 90; Speed_Num = 340; DA_Voltage = 1; Reset = 0; } }
提示:这里对文章进行总结:
本套题看起来不难,但其实还是有强度,需要对外设的使用了如指掌,还有新颖的考点,我没有参加过第十四届蓝桥杯,但是我觉得要想在考场上把功能都实现,还真是不简单!!!
希望大家看完我的这篇文章能有所收获。
谢谢你的观看。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。