蓝桥杯第十四届电子类单片机组决赛程序设计

目录

前言

单片机资源数据包_2023(点击下载)

一、第十四届比赛题目

1.比赛题目

2.题目解读

1)任务要求

2)注意事项

二、显示功能实现

1.关于高位为0时数码管熄灭功能的实现

2.关于显示小数位的处理

3.关于“校准值”的正负数据的处理

三、温度传感器小数部分的处理

四、两个按键长按2s功能的实现

五、LED灯功能的实现

1.LED灯显示距离功能的实现

2.其他LED灯功能

六、代码实现

main.c

onewire.h

iic.c

iic.h

前言

关于决赛的题,这也是我头一次自己去做,真心感觉好难啊,而且有许多“套路”都不能用了,这里来剖析一下我写的第十四届决赛代码,也是对前边提到的许多代码,关于“套路”不能用时,该如何去处理。

此外,决赛的题目官网上没有,也没链接可放了,我直接截图把题目放出来,决赛和省赛的资源数据包好像是一样的,第十四届比赛也就是在2023年,今年是第十五届比赛。

单片机资源数据包_2023(点击下载)

一、第十四届比赛题目

1.比赛题目

2.题目解读

1)任务要求

  • 数码管显示菜单,分别为测距界面,参数界面和工厂模式界面,其中参数界面有两个子菜单,分别为距离参数和温度参数,工厂模式界面有三个子菜单,分别距离校准、超声波传播速度和DAC输出下限设置
  • 测距界面下,前三位显示温度,保留小数点后一位,第四位显示“-”,后四位显示距离(距离的单位可以切换)
  • 距离参数界面,数码管前两位显示“P1”,最后两位显示距离参数(单位:CM)
  • 温度参数界面,数码管前两位显示“P2”,最后两位显示温度参数
  • 距离校准界面,数码管前两位显示“F1”,最后三位显示校准值,校准值有正负号
  • 超声波速度设置界面,数码管前两位显示“F2”,最后四位显示超声波速度
  • DAC输出下限界面,数码管前两位显示“F3”,后两位显示DAC下限,精确到小数点后一位
  • 按键S4定义为菜单切换,可以在测距界面、参数设置界面、工厂模式之间切换(在各个菜单的子菜单下也可切换,默认切换到下一个界面的第一个子菜单)
  • 按键S5定义为子菜单切换,在测距界面下,按下S5,在切换超声波数据的单位,在cm和m之间切换。在参数设置界面,或者工厂界面下,按下S5可以在对应的子菜单内切换
  • 按键S8和S9没啥介绍的,除了两个特殊功能之外,其他都是简单的加加减减。直接上图
  • DAC输出,根据“记录的距离”以及距离的范围和DAC下限输出对应的电压
  • 测距功能与一般的超声波一致,距离=超声波速度*来回的时间/2+超声波距离校准值,其中超声波速度和距离校准值都是可以手动设置的
  • LED灯:在距离界面下,LED显示当前距离;在参数界面下,L8点亮;在工厂模式下,L1以0.1s闪烁
  • 继电器:当距离参数-5<=测距结果<=距离参数+5,并且采集到的温度<=温度参数,继电器闭合,否则断开。

2)注意事项

  • 上电初始状态
  • 性能要求
  • 数据参数调整范围
  • 在菜单界面中,大部分都要求比如四位数码管显示一个数据,如果这个数据不够四位,则高位的数码管熄灭
  • 在S8的功能6,6s内记录距离的过程中,所有按键失效(包括同时按下S8和S9)
  • S8和S9长按超过2s则复位,其触发时机是“长按了2s”而非“长按2s之后松开按键”

二、显示功能实现

1.关于高位为0时数码管熄灭功能的实现

这个的意思就是,如果使用四位数码管显示一个数码,但是待显示的数据不足四位,比如只有三位,这个数是340,则只用三个数码管显示数据,四个数码管显示的结果应该是“熄灭”“3”“4”“0”,而非“0”“3”“4”“0”。之前写数码管时,都是直接让第一个数码管显示数据的千位,第二个显示百位,第三个显示十位,第四个显示个位。比如像下边这样

Nixie_num[0]=value/1000%10;
Nixie_num[1]=value/100%10;
Nixie_num[2]=value/10%10;
Nixie_num[3]=value/1%10;

然后再在定时器里在数码管对应的位置显示Nixie_num数组内的数据(如果是按照我之前写的代码的话)。

code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
};

unsigned char location=0;

void Timer0_Isr(void) interrupt 1
{
    P0=0x01<<location;NIXIE_CHECK();
    P0=Seg_Table[Nixie_num[location]];NIXIE_ON();
    
    if(++location>=8)
        location=0;

}

但是如果改为是0的话,直接这样处理,高位就不是熄灭,而是显示0了。显然不符合要求。其实到这里,大家至少应该能想到最最笨的处理方法了——判断数据的位数,在依次显示需要显示的位数,或者熄灭不需要显示的位。也就是这样:

unsigned char Wei_shu=0;
if(value/1000>0)Wei_shu=4;
else if(value/100>0)Wei_shu=3;
else if(value/10>0)Wei_shu=2;
else if(value/1>0)Wei_shu=1;

if(Wei_shu==4)//四位数据,四个数码管都显示数据
{
    Nixie_num[0]=value/1000%10;
    Nixie_num[1]=value/100%10;
    Nixie_num[2]=value/10%10;
    Nixie_num[3]=value/1%10;
}
else if(Wei_shu==3)//三位数据,第一个数码管熄灭,后三个显示数据
{
    Nixie_num[0]=10;//假设Nixie_num=10时对应该位熄灭,下同
    Nixie_num[1]=value/100%10;
    Nixie_num[2]=value/10%10;
    Nixie_num[3]=value/1%10;
}
elseif(Wei_shu==2)//两位数据,前两个数码管熄灭,后两个显示数据
{
    Nixie_num[0]=10;
    Nixie_num[1]=10;
    Nixie_num[2]=value/10%10;
    Nixie_num[3]=value/1%10;
}
else if(Wei_shu==1)//一位数据,前三个数码管熄灭,最后一个显示数据
{
    Nixie_num[0]=10;
    Nixie_num[1]=10;
    Nixie_num[2]=10;
    Nixie_num[3]=value/1%10;
}

这种方法当然可行,但是太麻烦了(反正我刚接触单片机编程时,遇到这个问题就是这样想的,也不知道和大家想到一样不一样)。现在在反过来看问题,我们完全可以边判断数据的位数,边显示数据。如果value/1000>0,说明这个数据是一个四位(或者以上)数据,则该位显示Value/1000%10(千位),否则熄灭,其他数码管同理,个位的数码管如果也都没有数据的话,则直接显示0即可,不然整个数据位就全部熄灭了。这里用到了三目运算符,是编程的基础,就不过多介绍了

Nixie_num[4]=value/1000>0 ? value/1000%10:20;
Nixie_num[5]=value/100>0 ? value/100%10:20;
Nixie_num[6]=value/10>0 ? value/10%10:20;
//Nixie_num[7]=value/1>0 ? value/1%10:0;//数据连一位数都没有,则显示0而非全部熄灭
Nixie_num[7]=value/1%10;//数据连一位数都没有,则显示0而非全部熄灭

至此,我们就实现了“数据不足四位,高位熄灭”的功能,对应题目的话,大概在这些地方提到过

2.关于显示小数位的处理

之前我们显示数码管,都是通过断码表Seg_Table来完成Nixie_num数组内的数据到数码管显示的数据之间的映射的。比如基本的Nixie_num[0]=0就代表第0位显示0(如果不修改Seg_Table数组内的值的话)。我们也知道,0和0.的段码绝对是不一样的,虽然只相差一点,我们不妨把Seg_Table数组内,0到9为段码对应数组0到9,10到19为段码对应0.到9.(注意有小数点欧)。具体0.到9.的段码如何计算,这里就不在介绍了,完善后的Seg_Table为

code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
//0.  1.  2.    3.   4.     5.        6.    7.  8.   9.
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,

}

这样,我们写Nixie_num[0]=0,表示数码管第0位显示0,Nixie_num[0]=0+10就表示数码管第0位显示0.了。这也算一个小窍门吧,以后真的考这个赚钱时,肯定还是优先考虑把函数封装好。

需要显示小数点的地方题目上还是比较多的,比如

3.关于“校准值”的正负数据的处理

没错,这个正负数据仅针对题目要求显示的校准值

这个校准值不但要显示正负,还要完成第二章第一节提到的高位为0时熄灭的功能,正数的处理跟前边提到的一样,这里主要介绍负数的处理。第二章第一节实现了在判断数据长度的同时显示数据,而这里还需要根据数据的长度,判断正负号显示的位置,我是没更好的办法了,只能使用第二章第一节提到的那个“笨方法”了。不过还好,因为校准值取值是-90到90,每次增减也是5,所以校准值只有可能是两位数或者一位数,判断起来也好判断

if(remote_jiaozhun>=0)//正
{
    Nixie_num[5]=20;//熄灭
    Nixie_num[6]=jiaozhun/10>0 ? jiaozhun/10%10:20;
    Nixie_num[7]=jiaozhun/1>0 ? jiaozhun/1%10:0;
}
else//负
{
    if(jiaozhun/10>0)
    {
        Nixie_num[5]=21;//显示-
        Nixie_num[6]=jiaozhun/10%10;//距离参数
        Nixie_num[7]=jiaozhun/1%10;
    }
    else
    {
        Nixie_num[5]=20;
        Nixie_num[6]=21;//显示-
        Nixie_num[7]=jiaozhun/1%10;//距离参数
    }
}

三、温度传感器小数部分的处理

之前咱们写的温度传感器读取是这样的


unsigned int read_18b20()
{
    
    unsigned int T=0;//定义温度
    unsigned char low=0;//用于接受温度的低八位
    unsigned char high=0;//用于接受温度的高八位
    
    init_ds18b20();//初始化DS18B20
    Write_DS18B20(0xCC);//跳过ROM检测
    Write_DS18B20(0x44);//发送开始温度转换的命令
    Delay_OneWire(200);//温度转化需要时间,这里直接延时一下。。注意应避免连续读取DS18B20
    
    
    init_ds18b20();//重新初始化DS18B20
    Write_DS18B20(0xCC);//跳过ROM检查
    Write_DS18B20(0xBE);//发送读取温度数据的指令
    
    low=Read_DS18B20();//接收低八位
    high=Read_DS18B20();//接收高八位
    
    T=high;
    T&=0x0F;//第八位的高四位置0,也就是不考虑符号位
    T<<=8;
    T|=low;
    T>>=4;//舍去低八位的低四位,也就是不考虑小数位
    
    return T;

}

这里是实打实的直接舍去了符号位和小数位,因为符号位和小数位一般用不上,但是偏偏在国赛出现了温度传感器需要读取到小数点后一位,其实也简单。

我们知道从温度传感器读取到的温度数据是16位的温度数据,其中高八位的高四位是符号位,低八位的低四位是小数位,我们之前都是只取中间八位,也就是高八位的低四位和低八位的高四位,也就是只有温度的整数部分,现在我们只需要加上小数部分即可。

但是直接加小数的话,温度值可就得变成float型的数据了,这显然不是我们想要看到的,我们不妨把温度数据扩大十倍,也就是整数部分*10加小数部分,这样我们就还可以使用unsigned int来记录温度数据了。修改之后的代码

unsigned int read_temp(void)
{
    unsigned int temp=0;
    unsigned char low=0;
    unsigned char high=0;
    unsigned char xiaoshu=0;
    
    init_ds18b20();
    Write_DS18B20(0xCC);
    Write_DS18B20(0x44);
    Delay_OneWire(200);
    
    init_ds18b20();
    Write_DS18B20(0xCC);
    Write_DS18B20(0xBE);
    low=Read_DS18B20();
    high=Read_DS18B20();
    
    temp=high;
    temp&=0x0F;
    temp<<=8;
    temp|=low;
    temp>>=4;
    
    /*获取小数部分*/
    xiaoshu=low;
    xiaoshu&=0x0F;
    
    temp=temp*10+xiaoshu;//温度扩大了十倍,把小数点后一位也加上了
    return temp;
}

四、两个按键长按2s功能的实现

在第十四届省赛已经实现了按键的长按,这里就不再赘述,我们这里要解决的是这个:

也就是同时按下两个按键,并长按两秒。

其实,用理性的角度来解释的话,是不存在“同时按下两个按键”的过程的,只可能是“按下一个按键后,按下第二个按键”,因此,我们只需要在按下S8或S9时,判断S9或S8是否被按下,两种情况分别对应按下S8后按下S9和按下S9后按下S8,当检查到两个按键都被按下之后,我们再开始数数,把它当按下一个按键的长按处理。

需要注意的,应避免按下一个S8之后按下S9,此时松开S8,保持S9按下,这种情况不能算作S8和S9同时按下。我们的短按都是在松开按键之后才生效的,而题目要求按下S8和S9达到2s就触发复位,也就是说不需要再松开S9或S9(好事,不然又得多一堆判断了),因此,如果S8和S9瞎按的话,就比如这一段话最开始提到的情况,那确实会出现一些不太好的情况,这涉及到底层逻辑的问题,而且题目也没要求,所以就暂时不管了。

至于按下按键2s,我们还是使用定时器数数,定义一个标志位is_2s_changan,如果is_2s_changan为0时,2s后会被置为1,通过判断将is_2s_changan置0到松开按键之前is_2s_changan是否被置为1,就可以判断是否长按够2s了。

对于两个按键的处理类似,这里只介绍其中一个:

if(P32==0)

{
    Delay5ms();
    while(P32==0)//按下s9
    {
        run();
        /*以下为同时长按s8和s9*/
        is_2s_changan=0;//在按下s9,但没按下s8之前,已经将2s数数置为0,确保按下s8时is_2s_changan=0;
        while(P33==0)//按下s8(此时处于同时按下S9和S8的状态太)
        {
            run();
            if(is_2s_changan==1)//如果这个状态持续了2s,一直等到is_2s_changan=1了,说明长按了2s
            {
                restart=1;//启动重置功能(见下方的if(restart==1))
                break;//并跳出等待(题目的意思貌似是,按够2s直接重置,不管松没松按键,所以要break。
                //如果要等松开按键才重置的话,可以把这个和下边那个break及其if判断注释掉
            }
            if(!(P32==0))
                break;
        }
        if(restart==1)
            break;
    }
    Delay5ms();
    key_value=9;
}

五、LED灯功能的实现

之前写的代码都是LED_ON(X),通过一个宏函数,快速点亮一个LED灯,但是现在,至少对于这个国赛题是一点也不行了,我们只能单独写。要说也简单,之前的宏函数都是根据传入的参数x来改变Led_Num的值,进而改变Led灯的状态,如下(代码有点长,其实是一行,可能显示不下就被换行了):

#define LED_ON(x)            Led_Num&=~(0x01<<x);P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;

现在,只穿一个参数点亮一个LED灯已经不能实现题目要求了:

我们干脆直接手动修改Led_Num的值,然后给P0赋值,最后开关一次锁存器。

1.LED灯显示距离功能的实现

LED指示灯的第一个功能,就是在测距界面(mod==10)下,显示距离值,这里我们加一个数数,定时器数100ms,每100ms处理一次LED灯(因为超声波更新的也不会那么快,而且后边也有100ms闪烁的功能),切记不要一直重复地给P0赋值,开关锁存器,LED灯容易误闪烁。

下面是代码演示

if(mod==10&&is_100ms==1)//距离显示界面下,led灯显示距离(注意取反)
{
    is_100ms=0;//这个是100ms是额外的处理,减慢led处理的速度,
    Led_Num=~remote;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
}

2.其他LED灯功能

其他LED灯功能就中规中矩了,都是之前提到了,不过既然这里已经舍弃使用LED_ON(x)的宏函数了,就干脆都直接修改Led_Num的值,来控制LED灯

if(mod==10&&is_100ms==1)//距离显示界面下,led灯显示距离(注意取反)
{
    is_100ms=0;//这个是100ms是额外的处理,减慢led处理的速度,
    Led_Num=~remote;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
}
else if(mod==20||mod==21)//在参数界面下,L8点亮,同时其它灯熄灭
{
    if(Led_Num!=~(0x80))//如果在参数界面下,未处在“L8点亮,同时其它灯熄灭”的状态,则使“L8点亮,同时其它灯熄灭”
    {
        Led_Num=~0x80;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
    }
}
else if((mod==30||mod==31||mod==32)&&is_100ms==1)//在工厂模式下L1以100ms闪烁
{
    is_100ms=0;//is_100ms为0时,100ms后会被置为1,每次进入这个else if,则翻转一次L1
    if(Led_Num==~(0x01))//如果点亮了,则熄灭
    {
        Led_Num=~0x00;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
    }
    else if(Led_Num!=~(0x01))//如果熄灭了,则点亮
    {
        Led_Num=~0x01;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
    }
}

六、代码实现

先说一句,过年回家的着急,万用表没带,关于DAC输出的我都没办法测试,包括前几篇提到的,不过应该也没什么大问题。本次写的代码中涉及到有许多简单重复的if判断,写的时候都改成三目运算符了。

main.c

#include <stc15.h>
#include <intrins.h>
#include "onewire.h"
#include "iic.h"code unsigned char Seg_Table[] =
{
0xc0, //0
0xf9, //1
0xa4, //2
0xb0, //3
0x99, //4
0x92, //5
0x82, //6
0xf8, //7
0x80, //8
0x90, //9
//0.  1.  2.    3.   4. 	5.		6.	7.  8.   9.
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,
0xFF,//熄灭
0xBF,//- 21
0x8C,//P 22
0x8E,//F 23
};
unsigned char Led_Num=0xFF;/*在这次国赛题目中,关于LED的宏用着不太方便*/
#define LED_ON(x)			Led_Num&=~(0x01<<x);P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF(x)		Led_Num|=0x01<<x;		P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;
#define LED_OFF_ALL()	Led_Num=0xFF;				P0=0xFF;P2|=0x80;P2&=0x9F;P2&=0x1F;#define NIXIE_CHECK()	P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON()		P2|=0xE0;P2&=0xFF;P2&=0x1F;unsigned char ULN=0x00;
#define RELAY_ON()	ULN|=0x10;		P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
#define RELAY_OFF()	ULN&=0xEF;		P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;#define BUZZER_ON()	ULN|=0x40;		P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
#define BUZZER_OFF()	ULN&=0xBF;		P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;sbit TX=P1^0;//定义超声波的TX
sbit RX=P1^1;//定义超声波的RXvoid Timer0_Init(void);		//1毫秒@12.000MHz
void Timer1_Init(void);		//@12.000MHz
void Delay100ms(void);	//@12.000MHz
void get_key(void);//按键读取与处理
void read_ul(void);//读取超声波测距(其他子函数均在read_ul前定义了,就不在这里声明了)
void show_menu(void);//显示菜单
void run(void);//主运行函数
void led_run(void);//led运行函数
void relay_run(void);//继电器运行函数unsigned char location=0;//当前扫描到的数码管的位置,中间变量
unsigned char Nixie_num[]={20,20,20,20,20,20,20,20};//数码管待显示的数据
unsigned char key_value=0;//读取到的键值,中间变量
unsigned int temp=0;//温度,这里为了方便处理温度的小数部分,已经将温度扩大了10倍
unsigned int remote=0;//距离
unsigned char mod=10;//菜单模式,取值10,20,21,30,31,32,分分别对应三个菜单的各个子菜单
unsigned int speed=340;//超声波传播速度
unsigned char remote_canshu=40;//距离参数
unsigned char wendu_canshu=30;//温度参数
signed int remote_jiaozhun=0;//距离校准(有符号)
unsigned char dac_xiaxian=10;//为便于除了。扩大了10倍,取值1到20,对应0.1v到20v
unsigned char jilu_remote=0;//记录距离(用于DAC输出)bit is_read_ul=1;//读取超声波的标志位
bit remote_danwei=0;//0:cm,1:0
bit is_jilu=0;//是否处在记录距离的标志位(就是6s那个状态)
bit is_2s_changan=0;//记录是否完成长按2s按键的标志位
bit restart=0;//重置标志位
bit is_100ms=0;
void main()
{BUZZER_OFF();//关闭蜂鸣器RELAY_OFF();//关闭继电器read_temp();//初始化温度传感器Timer0_Init();//定时器0初始化Timer1_Init();//定时器1初始化(超声波)EA=1;Delay100ms();//如果想上电第一次就能读取到正确的温度,可以多加六七个Delay100ms()//mod=0;while(1){get_key();run();//Delay100ms();}
}
void run(void)
{temp=read_temp();//读取温度led_run();//控制led灯relay_run();//控制继电器if(is_read_ul==1)//每500ms读取一次超声波(见定时器0){read_ul();is_read_ul=0;}if(remote_danwei!=0&&mod!=10)//确保每一次从其它菜单进入菜单10时,距离单位都是10:cm,也即remote_danwei=0remote_danwei=0;show_menu();
}unsigned int count_500ms=0;
unsigned int count_6s=0;
unsigned int count_2s=0;
unsigned int count_100ms=0;
void Timer0_Isr(void) interrupt 1
{P0=0x01<<location;NIXIE_CHECK();P0=Seg_Table[Nixie_num[location]];NIXIE_ON();if(++location>=8)location=0;//is_read_ul为0时,500ms后被置为1,用于每500ms读取一次超声波if(is_read_ul==0){if(++count_500ms>500){is_read_ul=1;count_500ms=0;}}//is_jilu为1时,6s后被置为0(用于s8的第6个功能)if(is_jilu==1){if(++count_6s==6000){is_jilu=0;count_6s=0;}}		//is_2s_changan为0时,2s后被置为1,根据将is_2s_changan置为0之后//检查is_2s_changan是否被置为1可以检测,从将is_2s_changan置为0到现在//是否过了2s(用于检查长按2s)if(is_2s_changan==0){if(++count_2s>2000){is_2s_changan=1;count_2s=0;}}if(is_100ms==0)//100ms,用于led闪烁{if(++count_100ms>100){count_100ms=0;is_100ms=1;}}
}void Timer0_Init(void)		//1毫秒@12.000MHz
{AUXR |= 0x80;			//定时器时钟1T模式TMOD &= 0xF0;			//设置定时器模式TL0 = 0x20;				//设置定时初始值TH0 = 0xD1;				//设置定时初始值TF0 = 0;				//清除TF0标志TR0 = 1;				//定时器0开始计时ET0 = 1;				//使能定时器0中断
}void Timer1_Init(void)		//@12.000MHz
{AUXR |= 0x40;			//定时器时钟1T模式TMOD &= 0x0F;			//设置定时器模式TL1 = 0x00;				//设置定时初始值TH1 = 0x00;				//设置定时初始值TF1 = 0;				//清除TF1标志//TR1 = 1;				//定时器1开始计时//ET1 = 1;				//使能定时器1中断
}void Delay100ms(void)	//@12.000MHz
{unsigned char data i, j, k;_nop_();_nop_();i = 5;j = 144;k = 71;do{do{while (--k);} while (--j);} while (--i);
}void Delay5ms(void)	//@12.000MHz
{unsigned char data i, j;i = 59;j = 90;do{while (--j);} while (--i);
}
void get_key()
{unsigned char key_P3=P3;unsigned char key_P4=P4;float V=0;//中间变量,记录需要输出的电压值//当处在6s距离记录的状态下if(is_jilu==1)//6秒的距离记录具有最高的优先级,在记录过程中,所有按键功能失效{jilu_remote=remote;//实时记录距离信息//restart=0;key_value=0;return;//直接返回,不再往下进行}P44=0;if(P32==0){Delay5ms();while(P32==0){run();}Delay5ms();key_value=5;}else if(P33==0){Delay5ms();while(P33==0){run();}Delay5ms();key_value=4;}P42=0;if(P32==0){Delay5ms();while(P32==0)//按下s9{run();/*以下为同时长按s8和s9*/is_2s_changan=0;//在按下s9,但没按下s8之前,已经将2s数数置为0,确保按下s8时is_2s_changan=0;while(P33==0)//按下s8(此时处于同时按下S9和S8的状态太){run();if(is_2s_changan==1)//如果这个状态持续了2s,一直等到is_2s_changan=1了,说明长按了2s{restart=1;//启动重置功能(见下方的if(restart==1))break;//并跳出等待(题目的意思貌似是,按够2s直接重置,不管松没松按键,所以要break。//如果要等松开按键才重置的话,可以把这个和下边那个break及其if判断注释掉}if(!(P32==0))break;}if(restart==1)break;}Delay5ms();key_value=9;}else if(P33==0){Delay5ms();while(P33==0){run();/*以下为同时长按s8和s9*//*同上*/is_2s_changan=0;while(P32==0){run();if(is_2s_changan==1){restart=1;break;}if(!(P33==0))break;}if(restart==1)break;}Delay5ms();key_value=8;}//重置数据if(restart==1){restart=0;mod=10;//重置菜单remote_canshu=40;//重置距离参数wendu_canshu=30;//重置温度参数remote_jiaozhun=0;//重置距离校准speed=340;//重置速度dac_xiaxian=10;//重置DAC下限remote_danwei=0;//重置距离单位为:cmkey_value=0;//key_value=0,使得刚才长按2s的效果不会被当成短按处理}//s4模式切换if(key_value==4){if(mod==10)//在测距界面{mod=20;//跳转到参数界面}else if(mod==20||mod==21)//在参数界面{mod=30;//跳转到工厂界面}else if(mod==30||mod==31||mod==32)//在工厂界面{mod=10;//跳转到测距界面}}//s5在同一个界面下不同子菜单之间跳转else if(key_value==5){if(mod==10)//在测距界面,调整距离单位remote_danwei=~remote_danwei;else if(mod==20)//在参数界面的距离参数界面,跳转到温度参数界面,下类似mod=21;else if(mod==21)mod=20;else if(mod==30)mod=31;else if(mod==31)mod=32;else if(mod==32)mod=30;}//s8加else if(key_value==8){if(mod==20)remote_canshu=remote_canshu<90?remote_canshu+10:90;//距离参数+10=(取值范围10到90)else if(mod==21)wendu_canshu=wendu_canshu<80?wendu_canshu+1:80;//温度参数+10(0到80)else if(mod==30)	remote_jiaozhun=remote_jiaozhun<90?remote_jiaozhun+5:90;//距离校准+5(取值-90到90)else if(mod==31)speed=speed<9990?speed+10:9990;//超声波速度+10(取值10到9990)else if(mod==32)dac_xiaxian=dac_xiaxian<200?dac_xiaxian+1:20;//DAC下限加0.1(取值0.1到2v)//注意前边已经提到为便于处理DAC扩大了10倍,,所以这里加的是1else if(mod==10)is_jilu=1;//在测距模式下,按下s8触发一次6s的记录}//s9减,与上边的加类似,三目运算符的作用均为控制取值范围else if(key_value==9){if(mod==20)remote_canshu=remote_canshu>10?remote_canshu-10:10;else if(mod==21)wendu_canshu=wendu_canshu>0?wendu_canshu-1:0;else if(mod==30)	remote_jiaozhun=remote_jiaozhun>-90?remote_jiaozhun-5:-90;else if(mod==31)speed=speed>10?speed-10:10;else if(mod==32)dac_xiaxian=dac_xiaxian>10?dac_xiaxian-1:10;else if(mod==20&&!(jilu_remote==0)){V=(5-dac_xiaxian)/80*jilu_remote+dac_xiaxian-10*(5-dac_xiaxian)/80;//计算需要输出的电压V=V>dac_xiaxian?V:dac_xiaxian;//输出电压限幅V=V<5?V:5;wirte_pcf((unsigned char)(V/5*255));}}}void Delay14us(void)	//@12.000MHz
{unsigned char data i;_nop_();_nop_();i = 45;while (--i);
}
void send_wave(void)
{unsigned char i=0;for(i=0;i<8;i++)//8个40kHz的超声波{TX=1;Delay14us();TX=0;Delay14us();}
}
void read_ul(void)
{unsigned int ul_time;//记录超声波来回的时间(注意没有单位),中间变量send_wave();//发送超声波TR1=1;//开始计时while((RX==1)&&(TF1==0));//等待接受返回的数据TR1=0;//接收到返回的数据,停止计时if(TF1==1)//如果是因为定时器溢出,说明没检测到底有效数据{ul_time=0;TF1=0;}else//检查到有效数据{/*读取超声波来回的时间*/ul_time=TH1;ul_time<<=8;ul_time|=TL1;}/*距离=时间*速度/2=定时器时间/单片机主频*速度(m/s)*100/2+距离校准 单位:cm*/remote=ul_time*0.00000452115*speed+remote_jiaozhun>0?ul_time*0.0000041667*speed+remote_jiaozhun:0;ul_time=0;TH1=0;TL1=0;	
}
void show_menu(void)
{unsigned char jiaozhun=0;if(mod==10)//测距界面{Nixie_num[0]=temp/100>0 ? temp/100%10:20;//显示温度十位或者熄灭(十位为0)Nixie_num[1]=temp/10>0 ? temp/10%10+10:20+10;//显示个位加小数点,十位各位为0时显示0.Nixie_num[2]=temp/1%10;Nixie_num[3]=21;if(remote_danwei==0)//显示单位为cm时,直接显示。最高位之前的各位熄灭{Nixie_num[4]=remote/1000>0 ? remote/1000%10:20;Nixie_num[5]=remote/100>0 ? remote/100%10:20;Nixie_num[6]=remote/10>0 ? remote/10%10:20;//Nixie_num[7]=remote/1>0 ? remote/1%10:0;//数据连一位数都没有,则显示0而非全部熄灭Nixie_num[7]=remote/1%10;//数据连一位数都没有,则显示0而非全部熄灭}else if(remote_danwei==1)//如果单位是m{Nixie_num[4]=remote/1000>0 ? remote/1000%10:20;Nixie_num[5]=remote/100>0 ? remote/100%10+10:10;//精确到小数点后两位,只需要在倒数第二位前加个小数点即可Nixie_num[6]=remote/10>0 ? remote/10%10:0;Nixie_num[7]=remote/1>0 ? remote/1%10:0;}}else if(mod==20)//参数界面1距离参数{Nixie_num[0]=22;//PNixie_num[1]=1;Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=20;Nixie_num[5]=20;Nixie_num[6]=remote_canshu/10>0 ? remote_canshu/10%10:0;//显示距离参数Nixie_num[7]=remote_canshu/1>0 ? remote_canshu/1%10:0;}else if(mod==21)//参数界面2温度参数{Nixie_num[0]=22;//PNixie_num[1]=2;Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=20;Nixie_num[5]=20;Nixie_num[6]=wendu_canshu/10>0 ? wendu_canshu/10%10:0;//显示温度参数Nixie_num[7]=wendu_canshu/1>0 ? wendu_canshu/1%10:0;}else if(mod==30)//工厂1校准值{Nixie_num[0]=23;//FNixie_num[1]=1;Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=20;//正负号转化,remote_jiaozhun是实际值,带正负。jiaozhun是中间变量,不带符号,值与remote_jiaozhun的绝对值相同//用abs()也行jiaozhun=remote_jiaozhun>0?remote_jiaozhun:-remote_jiaozhun;//PS,距离参数取值10到90,故数码管5熄灭(正数)或显示-(负数)后两位显示数据if(remote_jiaozhun>=0)//正{Nixie_num[5]=20;//熄灭Nixie_num[6]=jiaozhun/10>0 ? jiaozhun/10%10:20;Nixie_num[7]=jiaozhun/1>0 ? jiaozhun/1%10:0;}else//负{if(jiaozhun/10>0){Nixie_num[5]=21;//显示-Nixie_num[6]=jiaozhun/10%10;//距离参数Nixie_num[7]=jiaozhun/1%10;}else{Nixie_num[5]=20;Nixie_num[6]=21;//显示-Nixie_num[7]=jiaozhun/1%10;//距离参数}}}else if(mod==31)//工厂2速度{Nixie_num[0]=23;//FNixie_num[1]=2;Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=speed/1000>0 ? speed/1000%10:20;//显示速度Nixie_num[5]=speed/100>0 ? speed/100%10:20;Nixie_num[6]=speed/10>0 ? speed/10%10:20;Nixie_num[7]=speed/1>0 ? speed/1%10:20;}else if(mod==32)//工厂3DAC输出下限{Nixie_num[0]=23;//FNixie_num[1]=3;Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=20;Nixie_num[5]=20;Nixie_num[6]=dac_xiaxian/10%10+10;//DAC下限Nixie_num[7]=dac_xiaxian/1%10;}
}
void led_run(void)
{if(mod==10&&is_100ms==1)//距离显示界面下,led灯显示距离(注意取反){is_100ms=0;//这个是100ms是额外的处理,减慢led处理的速度,Led_Num=~remote;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;}else if(mod==20||mod==21)//在参数界面下,L8点亮,同时其它灯熄灭{if(Led_Num!=~(0x80))//如果在参数界面下,未处在“L8点亮,同时其它灯熄灭”的状态,则使“L8点亮,同时其它灯熄灭”{Led_Num=~0x80;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;}}else if((mod==30||mod==31||mod==32)&&is_100ms==1)//在工厂模式下L1以100ms闪烁{is_100ms=0;//is_100ms为0时,100ms后会被置为1,每次进入这个else if,则翻转一次L1if(Led_Num==~(0x01))//如果点亮了,则熄灭{Led_Num=~0x00;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;}else if(Led_Num!=~(0x01))//如果熄灭了,则点亮{Led_Num=~0x01;P0=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;}}
}bit relay_is_on=0;//继电器状态,1打开,0关闭
void relay_run()
{//如果距离参数-5<当前距离<距离参数+5,并且当前温度小于温度参数(前两个条件都成立的话,就需要打开继电器)//并且继电器没有打开,则打开继电器if(remote_canshu-5<=remote&&remote<=remote_canshu+5&&temp/10<=wendu_canshu&&relay_is_on==0){RELAY_ON();relay_is_on=1;}//如果不满足上述前两个条件(此时需要关闭继电器)//并且继电器没有关闭,则关闭继电器else if(!(remote_canshu-5<=remote&&remote<=remote_canshu+5&&temp/10<=wendu_canshu)&&relay_is_on==1){RELAY_OFF();relay_is_on=0;}
}

onewire.c

/*	# 	单总线代码片段说明1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <stc15.h>
#include <intrins.h>
#include "onewire.h"
sbit DQ=P1^4;
//
void Delay_OneWire(unsigned int t)  
{unsigned char i;while(t--){for(i=0;i<12;i++);}
}//
void Write_DS18B20(unsigned char dat)
{unsigned char i;for(i=0;i<8;i++){DQ = 0;DQ = dat&0x01;Delay_OneWire(5);DQ = 1;dat >>= 1;}Delay_OneWire(5);
}//
unsigned char Read_DS18B20(void)
{unsigned char i;unsigned char dat;for(i=0;i<8;i++){DQ = 0;dat >>= 1;DQ = 1;if(DQ){dat |= 0x80;}	    Delay_OneWire(5);}return dat;
}//
bit init_ds18b20(void)
{bit initflag = 0;DQ = 1;Delay_OneWire(12);DQ = 0;Delay_OneWire(80);DQ = 1;Delay_OneWire(10); initflag = DQ;     Delay_OneWire(5);return initflag;
}
unsigned int read_temp(void)
{unsigned int temp=0;unsigned char low=0;unsigned char high=0;unsigned char xiaoshu=0;init_ds18b20();Write_DS18B20(0xCC);Write_DS18B20(0x44);Delay_OneWire(200);init_ds18b20();Write_DS18B20(0xCC);Write_DS18B20(0xBE);low=Read_DS18B20();high=Read_DS18B20();temp=high;temp&=0x0F;temp<<=8;temp|=low;temp>>=4;/*获取小数部分*/xiaoshu=low;xiaoshu&=0x0F;temp=temp*10+xiaoshu;//温度扩大了十倍,把小数点后一位也加上了return temp;
}

onewire.h

#ifndef _ONEWIRE_H_
#define _ONEWIRE_H_unsigned int read_temp(void);#endif

iic.c

/*	#   I2C代码片段说明1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求,进行代码调试和修改。
*/#define DELAY_TIME	5
#include <stc15.h>
#include <intrins.h>
#include "iic.h"
sbit sda=P2^1;
sbit scl=P2^0;
//
static void I2C_Delay(unsigned char n)
{do{_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();_nop_();		}while(n--);      	
}//
void I2CStart(void)
{sda = 1;scl = 1;I2C_Delay(DELAY_TIME);sda = 0;I2C_Delay(DELAY_TIME);scl = 0;    
}//
void I2CStop(void)
{sda = 0;scl = 1;I2C_Delay(DELAY_TIME);sda = 1;I2C_Delay(DELAY_TIME);
}//
void I2CSendByte(unsigned char byt)
{unsigned char i;for(i=0; i<8; i++){scl = 0;I2C_Delay(DELAY_TIME);if(byt & 0x80){sda = 1;}else{sda = 0;}I2C_Delay(DELAY_TIME);scl = 1;byt <<= 1;I2C_Delay(DELAY_TIME);}scl = 0;  
}//
unsigned char I2CReceiveByte(void)
{unsigned char da;unsigned char i;for(i=0;i<8;i++){   scl = 1;I2C_Delay(DELAY_TIME);da <<= 1;if(sda) da |= 0x01;scl = 0;I2C_Delay(DELAY_TIME);}return da;    
}//
unsigned char I2CWaitAck(void)
{unsigned char ackbit;scl = 1;I2C_Delay(DELAY_TIME);ackbit = sda; scl = 0;I2C_Delay(DELAY_TIME);return ackbit;
}//
void I2CSendAck(unsigned char ackbit)
{scl = 0;sda = ackbit; I2C_Delay(DELAY_TIME);scl = 1;I2C_Delay(DELAY_TIME);scl = 0; sda = 1;I2C_Delay(DELAY_TIME);
}void wirte_pcf(unsigned char dat)
{I2CStart();I2CSendByte(0x90);I2CWaitAck();I2CSendByte(0x40);I2CWaitAck();I2CSendByte(dat);I2CWaitAck();I2CStop();
}

iic.h

#ifndef _IIC_H_
#define _IIC_H_void wirte_pcf(unsigned char dat);#endif

临近比赛了,这里也提前祝参加比赛的同学能够拿个好奖,也对得起这么久的努力了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/706712.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Nginx反向代理ip透传与负载均衡

前言 上篇介绍了nginx服务器单向代理和动静分离等相关内容&#xff0c;可参考Nginx重写功能和反向代理-CSDN博客&#xff0c;这里就ip透传和负载均衡对nginx反向代理做进一步了解。 目录 一、客户端ip透传 1. 概述 2. 一级代理 2.1 图示 2.2 操作过程 3. 二级代理 3.…

使用ffmpeg压缩视频

一、到ffmpeg官网下载文件包&#xff1a; Download FFmpeg 下载后找到 bin 下的3个exe文件&#xff0c;复制到自己本机的某个目录下, 如&#xff1a; 二、使用命令行压缩&#xff1a; ffmpeg -i input.mp4 -c:v libx265 -crf 28 -y output.mp4 这条命令使用 FFmpeg 工具对输…

机器学习——线性代数中矩阵和向量的基本介绍

矩阵和向量的基本概念 矩阵的基本概念&#xff08;这里不多说&#xff0c;应该都知道&#xff09; 而向量就是一个特殊的矩阵&#xff0c;即向量只有一列&#xff0c;是个n*1的矩阵 注&#xff1a;一般矩阵用大写字母表示&#xff0c;向量用小写字母表示 矩阵的加减运算 两个…

前端架构: 脚手架命令行交互核心实现之inquirer和readline的应用教程

命令行交互核心实现 核心目标&#xff1a;实现命令行行交互&#xff0c;如List命令行的交互呢比命令行的渲难度要更大&#xff0c;因为它涉及的技术点会会更多它涉及以下技术点 键盘输入的一个监听 (这里通过 readline来实现)计算命令行窗口的尺寸清屏光标的移动输出流的静默 …

关于年化收益率的思考

近期&#xff0c;对于投资的年化收益率有一些思考&#xff0c;想着将这些思考整理一下&#xff0c;顺便也就记录在这里。 1. 计算方式 年化收益率常见的计算有三种&#xff1a;算数平均&#xff0c;几何平均&#xff0c;IRR。 1.1 算术平均 算数平均用于度量产品的回报率&a…

MetaGPT 1 安装与配置踩坑实录

安装 与 配置直接参考这里就行&#xff1a;Hugging Muti Agent&#xff08;二月学习&#xff09; - 飞书云文档 (feishu.cn) 这里按照教程安装的是metagpt 0.6.6 &#xff0c;经过跟0.7.0对比&#xff0c;个人认为0.7对其他llm接入可能更好&#xff0c;文档也更清晰。 0.6.6的…

uniapp android 原生插件开发-测试流程

前言 最近公司要求研究一下 uniapp 的 android 原生插件的开发&#xff0c;为以后的工作做准备。这篇文章记录一下自己的学习过程&#xff0c;也帮助一下有同样需求的同学们 : ) 一、下载安装Hbuilder X , Android studio&#xff08;相关的安装配置过程网上有很多&#xff0c;…

安全运营中心(SOC)综合指南

什么是安全运营中心&#xff08;SOC&#xff09; 安全运营中心&#xff0c;也称为信息安全运营中心 &#xff08;ISOC&#xff09;&#xff0c;是结构良好的网络安全战略的核心。安全运营中心是一个集中式枢纽&#xff0c;无论是在组织内部还是外包&#xff0c;都致力于对整个…

云计算时代的运维: 职业发展方向与岗位选择

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua&#xff0c;在这里我会分享我的知识和经验。&#x…

Nginx网络服务五-----rewrite和反向代理

1.rewrite 1.1rewrite指令 通过正则表达式的匹配来改变URI&#xff0c;可以同时存在一个或多个指令&#xff0c;按照顺序依次对URI进行匹配&#xff0c;rewrite主要是针对用户请求的URL或者是URI做具体处理 官方文档&#xff1a; https://nginx.org/en/docs/http/ngx_http_r…

自动驾驶消息传输机制-LCM

需要用到LCM消息通讯&#xff0c;遂研究下。 这里写目录标题 1 LCM简介2. LCM源码分析3 LCM C教程与实例3.1 安装配置及介绍3.2 创建类型定义3.3 初始化LCM3.4 发布publish一个消息3.5 订阅和接收一个消息3.6 LCM进程间通讯3.7 注意事项&#xff1f;3.7.1 当数据结构定义的是数…

代码随想录算法训练营29期|day64 任务以及具体安排

第十章 单调栈part03 有了之前单调栈的铺垫&#xff0c;这道题目就不难了。 84.柱状图中最大的矩形class Solution {int largestRectangleArea(int[] heights) {Stack<Integer> st new Stack<Integer>();// 数组扩容&#xff0c;在头和尾各加入一个元素int [] ne…

斯元Z-ONE-China Cybersecurity Tech Landscape·中国网络安全全景图-百度网盘下载

面向全球&#xff0c;斯元Z-ONE正式发布首版「China Cybersecurity Tech Landscape中国网络安全全景图」。 为了提升海外市场对中国网络安全行业的全局认识&#xff0c;方便国际客户及合作伙伴了解中国网络安全科技的赛道分布和国内外厂商对标&#xff0c;助力中国网安厂商出海…

uni-app之android原生插件开发

官网 uni小程序SDK 一 插件简介 1.1 当HBuilderX中提供的能力无法满足App功能需求&#xff0c;需要通过使用Andorid/iOS原生开发实现时&#xff0c;可使用App离线SDK开发原生插件来扩展原生能力。 1.2 插件类型有两种&#xff0c;Module模式和Component模式 Module模式&…

【架构笔记1】剃刀思维-如无必要,勿增实体

欢迎来到文思源想的架构空间&#xff0c;前段时间博主做了一个工作经历复盘&#xff0c;10年开发路&#xff0c;走了不少弯路&#xff0c;也算积累了不少软件开发、架构设计的经验和心得&#xff0c;确实有必要好好盘一盘&#xff0c;作为个人的总结&#xff0c;同时也留给有缘…

Flink SQL 中的流式概念:状态算子

博主历时三年精心创作的《大数据平台架构与原型实现&#xff1a;数据中台建设实战》一书现已由知名IT图书品牌电子工业出版社博文视点出版发行&#xff0c;点击《重磅推荐&#xff1a;建大数据平台太难了&#xff01;给我发个工程原型吧&#xff01;》了解图书详情&#xff0c;…

10W 音频功率放大电路芯片TDA2003,可用于汽车收音机及收录机中作音频功率放大器,内部具有短路保护和过热保护等功能

TDA2003 用于汽车收音机及收录机中作音频功率放大器。 采用 TO220B5 封装形式。 主要特点&#xff1a; ⚫ 内部具有短路保护和过热保护。内部具有地线开路、电源极性接 反和负载泄放电压反冲等保护电路。 ⚫ 输出电流大。 ⚫ 负载电阻可低至 1.6 。 …

LeetCode 刷题 [C++] 第141题.环形链表

题目描述 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置&a…

STC-ISP原厂代码研究之 V3.7d汇编版本

最近在研究STC的ISP程序,用来做一个上位机烧录软件,逆向了上位机软件,有些地方始终没看明白,因此尝试读取它的ISP代码,但是没有读取成功。应该是目前的芯片架构已经将引导代码放入在了单独的存储块中,而这存储块有硬件级的使能线,在面包板社区-宏晶STC单片机的ISP的BIN文…

matlab绘制雷达图和二维FFT变换图

1、内容简介 略 49-可以交流、咨询、答疑 matlab绘制雷达图和二维FFT变换图 NMO组及NORMAL组 RNFL层、GCL层、IPL层、GCC层、ORL层做雷达图&#xff08;共10张&#xff09; 2、内容说明 略 NMO组及NORMAL组 RNFL层、GCL层、IPL层、GCC层、ORL层请分别做雷达图&#xff08…