当前位置:   article > 正文

蓝桥杯单片机第十四届国赛注意点_蓝桥杯单片机14届国赛记录功能

蓝桥杯单片机14届国赛记录功能

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档


前言

提示:这里可以添加本文要记录的大概内容:

考察部分:除了三大金刚(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` 可以用来避免全局变量和函数的命名冲突。

代码部分展示

  1. void Beep_Disp(unsigned char pos,enable)
  2. {
  3. static unsigned char temp = 0x00;
  4. static unsigned char temp_old = 0xff;
  5. if(enable)
  6. {
  7. temp |= (0x01<<pos);
  8. }else{
  9. temp &= ~(0x01<<pos);
  10. }
  11. if(temp != temp_old)
  12. {
  13. P0 = temp;
  14. P2 = P2&0x1f | 0xa0;
  15. P2 &= 0x1f;
  16. temp_old = temp;
  17. }
  18. }

分析

在这个 `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引脚发射出去

  1. void Ul_Wave_Init()
  2. {
  3. unsigned char i;
  4. for(i=0;i<8;i++)
  5. {
  6. Tx = 1;
  7. Delay12us();
  8. Tx = 0;
  9. Delay12us();
  10. }
  11. }

在这之前,先生成一个12us的延时,为什么是12us?

1s = 1000ms = 1000 000us

T = 1/f ;   f = 40Khz = 40 000;

所以T = 25us ,以25秒为一个周期他的一半是12或13,所以我们可以生成一个12us或13us的延时

  1. void Delay12us() //@12.000MHz
  2. {
  3. unsigned char i;
  4. _nop_(); //注意:用到了这个函数,要添加相应的头文件 --> #include <intrins.h>
  5. _nop_();
  6. i = 33;
  7. while (--i);
  8. }

2.发送后,开启定时器,计算脉冲

3.等待超声波信号返回,如果接收到反射回来的信号,Rx引脚变为高电平

4.停止定时器,读取脉冲个数,即获得时间T

5.根据公式算出距离

  1. unsigned char Wave_Dat()
  2. {
  3. unsigned int time;
  4. CMOD = 0;
  5. CH =CL = 0;
  6. Wave_Init();
  7. CR = 1;
  8. while((Rx == 1)&&(CF == 0));
  9. CR = 0;
  10. if(CF == 0)
  11. {
  12. time = (CH<<8|CL)*0.017;//*0.017 等价于*340(声速)/2/10000;
  13. return time;
  14. }else{
  15. CF = 0;
  16. return 0;
  17. }
  18. }

为什么上面的*0.017等价于我上面写的呢?

因为由公式L = V * T/2;其中,V的单位为m/s,而T的单位为us,然后我们计算出来的距离的单位是厘米,所以涉及了单位的转化。为什么要将这些,

因为如果是一般的超声波,他的声速我们默认是340m/s,而在本题中,声速是可以改变的,所以我们这里要用一个变量

而且我们这个变量除了在超声波文件中可以用,还要在main.c文件中可以使用,这里就需要了解另一个关键字extern

在超声波相关函数Ultrasound.c中,

  1. #include <STC15F2K60S2.H>
  2. #include "Ultrasound.h"
  3. #include <intrins.h>
  4. sbit Tx = P1^0;
  5. sbit Rx = P1^1;
  6. extern unsigned int Speed_Num;//传播速度
  7. void Delay12us() //@12.000MHz
  8. {
  9. unsigned char i;
  10. _nop_();
  11. _nop_();
  12. i = 33;
  13. while (--i);
  14. }
  15. void Ul_Wave_Init()
  16. {
  17. unsigned char i;
  18. for(i=0;i<8;i++)
  19. {
  20. Tx = 1;
  21. Delay12us();
  22. Tx = 0;
  23. Delay12us();
  24. }
  25. }
  26. unsigned char Ul_Wave_Data()
  27. {
  28. unsigned int time;
  29. CMOD = 0;
  30. CH = CL = 0;
  31. Ul_Wave_Init();
  32. CR = 1;
  33. while((Rx == 1)&& (CF == 0));
  34. CR = 0;
  35. if(CF == 0)
  36. {
  37. time = (CH <<8| CL)*(Speed_Num/20000.0);
  38. return time;
  39. }else{
  40. CF = 0;
  41. return 0;
  42. }
  43. }

在这里用extern关键字定义一个变量

extern unsigned int Speed_Num;//传播速度    

注意:这里只定义,不要赋值,赋值在main.c文件中声明的时候进行。

不然会报错!!!

main.c文件中

uint Speed_Num = 340;//传播速度    */定义在了Ultrasound.c文件中

extern关键之关于多次声明,但只能定义一次。

以上,我们完成了基础外设的配置,接下来我们来解决题目中的一些坑。

二、本套题中的阴谋 \(^o^)/~

1.关于校准值的相关设置

本题中校准值的设置是-90~90;因为我觉得有负数不好处理,所以我操作了一下

我将范围设置为0~180,这样我这里的90,就相当于题目中指的0;

大于等于90,就显示                   x - 90 

而小于90的部分就显示           - (90 - x)

  1. uchar Aux_values = 90;/* 关于校准值既然范围是-90到90,有负数有点麻烦,
  2. 所以我们不放将范围等价到0-180,以90为初始值*/
  3. bit Aux_Flag;//当Aux_values >=90,为 0   当Aux_values<90,为 1
  4. smg[0] = 17;//F
  5.                         smg[1] = Factory_Son_mode + 1;
  6.                         smg[2] = smg[3] = smg[4] = 10;
  7.                         if(Aux_values >= 90)
  8.                         {
  9.                             smg[5] = 10;
  10.                             smg[6] = (Aux_values - 90) > 9? ((Aux_values - 90)/10) : 10;
  11.                             smg[7] = (Aux_values - 90)%10;
  12.                         }else{
  13.                            if((90 - Aux_values) > 9)
  14.                            {
  15.                              smg[5] = 11;//-
  16.                              smg[6] = (90 - Aux_values) > 9? ((90 - Aux_values)/10) : 10;
  17.                              smg[7] = (90 - Aux_values)%10;
  18.                            }else {
  19.                                 smg[5] = 10;
  20.                                 smg[6] = 11;//-
  21.                                 smg[7] = (90 - Aux_values)%10;
  22.                             }
  23.                         }
  24.                         smg_point[6] = 0;

为了防止大家看不懂我的数码管显示

这里附上数码管相关底层

  1. #include <STC15F2K60S2.H>
  2. #include "Smg.h"
  3. code smg_weixuan[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
  4. code unsigned char smg_duanxuan[] =
  5. {
  6. 0xc0, //0
  7. 0xf9, //1
  8. 0xa4, //2
  9. 0xb0, //3
  10. 0x99, //4
  11. 0x92, //5
  12. 0x82, //6
  13. 0xf8, //7
  14. 0x80, //8
  15. 0x90, //9
  16. 0xff, //(空)10
  17. 0xbf, //"-" 11
  18. 0x88, //A12
  19. 0x83, //b13
  20. 0xc6, //C14
  21. 0xa1, //d15
  22. 0x86, //E16
  23. 0x8e, //F17
  24. 0x8c //P18
  25. };
  26. void Smg_Disp(unsigned char x,y,point)
  27. {
  28. P0 = 0xff;
  29. P2 = P2&0x1f|0xe0;
  30. P2 &= 0x1f;
  31. P0 = smg_weixuan[x];
  32. P0 = 0xff;
  33. P2 = P2&0x1f|0xc0;
  34. P2 &= 0x1f;
  35. P0 = smg_duanxuan[y];
  36. if(point)
  37. P0 &= 0x7f;
  38. P2 = P2&0x1f|0xe0;
  39. P2 &= 0x1f;
  40. }

//数码管
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)
二进制00001010
十进制0* 2^00* 2^10* 2^20* 2^3 1* 2^40* 2^51* 2^60*2^7

结果 = 1* 2^4 + 1* 2 ^6 = 16 + 64 = 80;

所以我们只需用二进制表示相关LED亮灭即可,

为防止小伙伴们看不懂我写的LED,下面先展示我的LED相关底层

  1. #include <STC15F2K60S2.H>
  2. #include "Led.h"
  3. void Led_Disp(unsigned char pos,enable)
  4. {
  5. static unsigned char temp = 0x00;
  6. static unsigned char temp_old = 0xff;
  7. if(enable)
  8. {
  9. temp |= (0x01<<pos);
  10. }else{
  11. temp &= ~(0x01<<pos);
  12. }
  13. if(temp != temp_old)
  14. {
  15. P0 = ~temp;
  16. P2 = P2&0x1f | 0x80;
  17. P2 &= 0x1f;
  18. temp_old = temp;
  19. }
  20. }

//LED
led[8] = {0,0,0,0,0,0,0,0};

生成一个1ms的定时器0;将数码管,按键,LED的减速放在定时器中,我们尽量不要使用delay延时,因为这个很呆,当执行delay时我们的单片机是不能干别的事情的,这就可能导致数据执行时,数据刷新不到位,可能会有一系列Bug等着你。

  1. void Timer0Init(void) //1毫秒@12.000MHz
  2. {
  3. AUXR &= 0x7F; //定时器时钟12T模式
  4. TMOD &= 0xF0; //设置定时器模式
  5. TL0 = 0x18; //设置定时初值
  6. TH0 = 0xFC; //设置定时初值
  7. TF0 = 0; //清除TF0标志
  8. TR0 = 1; //定时器0开始计时
  9. ET0 = 1;
  10. EA = 1;
  11. }
  12. //*******中断服务函数
  13. void Timer0() interrupt 1
  14. {
  15. static uchar a;
  16. if(++Key_Slow_Down == 10) Key_Slow_Down = 0;
  17. if(++Led_Slow_Down == 200) Led_Slow_Down = 0;
  18. if(++Smg_Slow_Down == 500) Smg_Slow_Down = 0;
  19. Smg_Disp(a,smg[a],smg_point[a]);
  20. Led_Disp(a,led[a]);
  21. if(++a == 8) a = 0;
  22. .....
  23. }

不要忘记将Timer0Init()放在程序的开始

  1. //******主程序
  2. void main()
  3. {
  4. AllStart();
  5. while(Temperature_Read() == 85);
  6. *****Timer0Init();*******
  7. while(1)
  8. {
  9. SMG_Process();
  10. KEY_Process();
  11. Led_Process();
  12. Other_Process();
  13. }
  14. }

然后就是在测距模式下Led相关的亮灭了

代码展示

  1. void Led_Process()
  2. {
  3. uchar i;
  4. if(Mode == 2){
  5. for(i=1;i<8;i++)
  6. led[i] = 0;
  7. led[0] = L1_Flag?1:0;
  8. }
  9. if(Led_Slow_Down) return;
  10. Led_Slow_Down = 1;
  11. if(Mode == 0)
  12. {
  13. if((Ultrasound_old - Ultrasound < 10) || (Ultrasound - Ultrasound_old <10))
  14. {
  15. led[0] = Ultrasound%2;
  16. led[1] = Ultrasound/2%2;
  17. led[2] = Ultrasound/4%2;
  18. led[3] = Ultrasound/8%2;
  19. led[4] = Ultrasound/16%2;
  20. led[5] = Ultrasound/32%2;
  21. led[6] = Ultrasound/64%2;
  22. led[7] = Ultrasound/128;
  23. }
  24. }else if(Mode == 1)
  25. {
  26. for(i=0;i<7;i++)
  27. led[i] = 0;
  28. led[7] = 1;
  29. }
  30. }

上述代码中if((Ultrasound_old - Ultrasound < 10) || (Ultrasound - Ultrasound_old <10))

 这个是为了防止我们数码管上显示的距离突然跳变的,其实就是为了好看一点,也可以不加。

2.双按键

新考点,两个按键都按下。

关于按键,我采用的是三行按键法,有人可能会问,为什么不用状态机,实不相瞒,其实我以前会状态机法,但是自从学了三行按键法,因为三行按键法只有三行,比较简介易上手(其实就是我懒,不想多敲)渐渐地就忘记了。

关于三行按键法

在使用前要先定义几个变量

  1. //按键
  2. uchar Key_Val,Key_Down,Key_Up,Key_Old;
  3. Key_Val = Key_Scan();
  4. //三行按键法
  5. Key_Down = Key_Val & (Key_Val ^ Key_Old);
  6. Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
  7. Key_Old = Key_Val;

这里的Key_Down和Key_Up都是瞬时值

而Key_Old的值比Key_Val延后10ms

为什么是10ms呢?

因为我们在定时器中断中,按键的减速变量设置的是10ms

按键之所以要减速是因为要去除抖动,相信大家都知道了。

其中,

Key_Val = Key_Scan();  //获取被按下的按键值

那么我们有要求看Key_Scan()这个函数

  1. //矩阵按键
  2. unsigned char Key_Scan()
  3. {
  4. unsigned char temp = 0;
  5. //第一行
  6. P44 = 0;P42 = 1;P35 = 1;P34 = 1;
  7. if(P33 == 0) temp = 4;
  8. if(P32 == 0) temp = 5;
  9. if(P31 == 0) temp = 6;
  10. if(P30 == 0) temp = 7;
  11. //第二行
  12. P44 = 1;P42 = 0;P35 = 1;P34 = 1;
  13. if(P33 == 0) temp = 8;
  14. if(P32 == 0) temp = 9;
  15. if(P31 == 0) temp = 10;
  16. if(P30 == 0) temp = 11;
  17. if(P33 == 0 && P32 == 0) temp = 89;
  18. //第三行
  19. P44 = 1;P42 = 1;P35 = 0;P34 = 1;
  20. if(P33 == 0) temp = 12;
  21. if(P32 == 0) temp = 13;
  22. if(P31 == 0) temp = 14;
  23. if(P30 == 0) temp = 15;
  24. //第四行
  25. P44 = 1;P42 = 1;P35 = 1;P34 = 0;
  26. if(P33 == 0) temp = 16;
  27. if(P32 == 0) temp = 17;
  28. if(P31 == 0) temp = 18;
  29. if(P30 == 0) temp = 19;
  30. return temp;
  31. }

有人可能发现了扫描第二行时的if(P33 == 0 && P32 == 0) temp = 89;

后面会讲解

   注意这里的 temp变量一定要赋初始值  ,不然按键会有问题。

   unsigned char temp = 0; 

下面关于三行按键法

这⾥以S4(0100)为例,来进⾏说明

键盘状态Key_ValKey_OldKey_Val^Key_OldKey_DownKey_Up
未按下000000000000^0000 = 000000000000
按下过程中(10ms)010000000100^0000 = 010001000000
按下稳定(10ms后)010001000100^0100= 000000000000
抬起过程(10ms)000001000000^0100 = 010000000100

双按键相关代码展示

  1. void KEY_Process()
  2. {
  3. if(Key_Slow_Down) return;
  4. Key_Slow_Down = 1;
  5. Key_Val = Key_Scan();
  6. //三行按键法
  7. Key_Down = Key_Val & (Key_Val ^ Key_Old);
  8. Key_Up = ~Key_Val & (Key_Val ^ Key_Old);
  9. Key_Old = Key_Val;
  10. if(Key_Old == 89)
  11. {
  12. S8S9_Flag = 1;
  13. Key_Clock = 1;
  14. }
  15. if(Key_Clock && Key_Old) return;//还在同时按下
  16. Key_Clock = 0;
  17. .......
  18. }

上述代码中的Key_Clock 变量其实是给双按键上来一个锁,因为本套题目中,S8与S9单独按下时均有功能,所以为了防止误触发。

而当我同时按下时我让我的一个标志位置1  --> S8S9_Flag = 1;  

在Other_Process()函数中执行相关功能:

  1. void Other_Process()
  2. {
  3. if(Reset)
  4. {
  5. Distance_Para = 40;
  6. T_Para_10x = 300;
  7. Aux_values = 90;
  8. Speed_Num = 340;
  9. DA_Voltage = 1;
  10. Reset = 0;
  11. }
  12. }


总结

提示:这里对文章进行总结:
本套题看起来不难,但其实还是有强度,需要对外设的使用了如指掌,还有新颖的考点,我没有参加过第十四届蓝桥杯,但是我觉得要想在考场上把功能都实现,还真是不简单!!!

希望大家看完我的这篇文章能有所收获。

谢谢你的观看。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小小林熬夜学编程/article/detail/642878
推荐阅读
相关标签
  

闽ICP备14008679号