当前位置: 首页 > news >正文

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

前言

一、决赛题目

1.比赛题目

2.题目解读

二、功能实现

1.关于定时器资源

1)超声波和NE555需要的定时器资源

2)定时器2

2.单位切换

3.数据长度不足时,高位熄灭

4.AD/DA多通道的处理

5.PWM输出

6.长按功能的实现

三、完整代码演示

main.c

iic.c

iic.h


前言

之前一直吐槽第十四届省赛已经赶上国赛水平了,现在我感觉我错了,还是国赛更难一些。但是也不排除,今年省赛会出前几年国赛考过的知识点,这里还是多给大家分享一些。重复分享某一个知识点的话,确实有些繁琐,所以这里分享十三届决赛代码的同时,更多的还是分享一些前边没有提到的知识点,给出我的处理方法供大家参考,以应对赛场上的各种突发情况。

另外今年的赛点的资料好像已经发了,回头我会在比对一下跟去年的有没有区别,考虑是近两年底层驱动才不给.h文件的,后续我也会分享一下.h文件应该怎么写,底层的.c文件应该怎么修改(前边每一篇文章底层的.c 都是我自己修改过后的,.h文件都是我自己写的)

一、决赛题目

1.比赛题目

2.题目解读

首先,可能会很让人头大的一个点就是,这里不仅仅需要读取超声波,还要读取NE555,这两个就要各占一个定时器资源,以我们之前写的代码来看,定时器资源是不够的。

其次,这里用到的AD和DA,而AD和DA都是通过PCF8591来控制的,之前从来没有遇到过同时控制两路PCF8591的情况。

再次这个题目还要求输出PWM,这个对有单片机基础的人来说不是什么难事,但是要是你根本不知道PWM是什么,那省赛的时候,你绝对难受的要死。

当然,省赛的话绝对不会这么复杂,但也不排除考国赛的某个知识点的可能。下边都会一一介绍

二、功能实现

1.关于定时器资源

1)超声波和NE555需要的定时器资源

之前我们已经介绍过如何读取超声波和NE555了。之前写的代码都是定时器0来完成扫描数码管等其他主要的工作,定时器1用来为超声波计数,或者使用定时器1的外部中断来读取NE555.你如果要问用定时器1同时来为超声波计数和读取NE555可以不可以,那当然不可以,一个需要定时器工作在计时模式,一个需要定时器工作在计数模式,这样使用肯定冲突。

所以他俩每个都需要独占一个定时器资源,我们需要把定时器0分给超声波,用来给超声波计时,定时器1的外部中断模式用于读取NE555.

那数码管还有其他计时工作怎么办呢?其实STC15F2系列单片机还有一个定时器2可以使用

2)定时器2

跟常规的定时器一样,我们可以直接在STC-ISP生成定时器2的初始化函数。

如果你赛点的stc不是新版的,没有使能定时器中断的选项,那你可得提前记一下了,定时器0使能中断使用的是ET0=1;定时器1是ET1 =1;定时器2可不是ET2 =1;而是IE2 |= 0x04;  定时器2的中断号是12。完整的代码如下

void Timer2_Isr(void) interrupt 12
{
}

void Timer2_Init(void)        //1毫秒@12.000MHz
{
    AUXR |= 0x04;            //定时器时钟1T模式
    T2L = 0x20;                //设置定时初始值
    T2H = 0xD1;                //设置定时初始值
    AUXR |= 0x10;            //定时器2开始计时
    IE2 |= 0x04;            //使能定时器2中断
}
 

定时器2的初始化函数跟我们常用的定时器0和1也不太一样,我们也不方便使用定时器2来给超声波计时,所以我一般选择定时器0给超声波计时,定时器2扫描数码管了。

2.单位切换

对于这次的单位切换,我选择采取简单粗暴的方式——把显示不同单位的菜单直接定义成不同的菜单,比如频率界面可以按按键切换单位,我直接定义两个菜单显示不同单位的频率,这样切换单位就变成了切换菜单,简单粗暴。不过这样的话,那就得写7个菜单了,不过还好菜单里面需要显示的东西并不复杂。

3.数据长度不足时,高位熄灭

前几篇文章应该也提到过,这里在简单介绍一下。

定义一个数据为dat,如果它的长度小于3位,也就是dat/1000为0时,那么倒数第三位数码管就熄灭,如果他的长度大于2位,则正常显示dat/100%10;我们可以使用三木运算符来实现,这里以显示菜单1的频率为例:

if(mod==0)//显示频率
{
    Nixie_num[0]=21,//F
    Nixie_num[1]=20,
    Nixie_num[2]=fre/100000>0 ? fre/100000%10 : 20;
    Nixie_num[3]=fre/10000>0 ? fre/10000%10 : 20;
    Nixie_num[4]=fre/1000>0 ? fre/1000%10 : 20;
    Nixie_num[5]=fre/100>0 ? fre/100%10 : 20;
    Nixie_num[6]=fre/10>0 ? fre/10%10 : 20;
    Nixie_num[7]=fre/1>0 ? fre/1%10 : 20;
}
 

当然前提是你得知道什么是三目运算符。

4.AD/DA多通道的处理

首先:为什么要处理呢?

答案:如果你不处理的话,如果要读取两个通道的AD值,那可能读取第二个通道的AD值时读取到的还是第一个通道。

当然上边只是对于多通道处理的一种情况哈,总之就是第二个通道可能出现意想不到的结果。老师咋讲的现在我已经忘完了,都具体读和写的哪些情况组合会出现问题,我也记不清了,但是我咱们可以使用一种一劳永逸的办法,那就是连续读取两次,比如我需要读取通道0和3那我就这样写:


unsigned char AD0;

unsigned char AD1;

AD0=read_pcf(0);AD0=read_pcf(0);

AD1=read_pcf(1);AD1=read_pcf(1);

如果它还读取不到期望的值的话,那我们就在中间加几个Delay。

5.PWM输出

提到PWM,就得顺带着占空比一块提一下,占空比就是上文提到的duty。

PWM是一种常用的信号控制和转换技术。简单来说,PWM通过调节脉冲信号的宽度来模拟一个连续变化的信号,通常用于控制直流(DC)电动机、LED亮度调节、声音的调节以及其他需要精确控制输入信号的应用。

比如高电平电压为5V,低电平电压为0V,我现在定义PWM为1KHz也就是说一个周期(一个高电平+一个低电平)时1ms,如果定义精度为10%,也就是十分之一个周期。如果一个周期的前50%是高电平,后50%是低电平,这样就记为的50%占空比。刚才又说,高电平为5V,低电平为0V,而一个周期有一半时间是高电平,那么此时输出的电压应该是高电平的一半,也就是5V。对于其他占空比同理。

因此,我们可以通过测量电压,来测量占空比是多少。

题目要求输出1KHz的两种占空比的PWM信号,分别为80%占空比和20%占空比的。

我们当然可以使用一个0.1ms的定时器,定时器计数,前八次输出高电平,后两次输出低电平,这样就可以实现80%占空比输出了,20%也同理。

但是,现在我们的定时器资源急缺,唯一一个用来计时的定时器也设置的1ms,而我又不想修改怎么办呢,我们直接延时其实也可以。输出高电平,延时800us,输出低电平,延时200us,同样也输出了80%占空比,1Khz的PWM信号,然后我们再在main的while循环运行这串输出代码即可,当然,main函数里不可以有延时。

至于如何输出高电平,如何输出低电平,其实就跟控制继电器和蜂鸣器一样,毕竟都是ULN芯片控制的,前边也介绍过ULN芯片了,可以看这篇文章中关于继电器开启与关闭的部分

蓝桥杯第十三届电子类单片机组程序设计-CSDN博客

只是开关继电器我们控制的事N RELAY引脚的高低电平,现在我们需要控制N MOTOR引脚的高低电平而已。

输出80%和20%占空比,1KHz信号的代码如下:

#define MOTOR_ON()        ULN|=0x20;    P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
#define MOTOR_OFF()        ULN&=0xDF;    P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;

void Delay800us(void)    //@12.000MHz
{
    unsigned char data i, j;

    i = 10;
    j = 83;
    do
    {
        while (--j);
    } while (--i);
}
void Delay200us(void)    //@12.000MHz
{
    unsigned char data i, j;

    i = 3;
    j = 82;
    do
    {
        while (--j);
    } while (--i);
}


void PWM_out_80(void)//输出duty为80的PWM
{
    MOTOR_ON()
    Delay800us();
    MOTOR_OFF();
    Delay200us();
}
void PWM_out_20(void)//输出duty为20的PWM
{
    MOTOR_ON()
    Delay200us();
    MOTOR_OFF();
    Delay800us();
}

6.长按功能的实现

虽然之前提到过,这里还是介绍一下我个人的思路吧。

题目上要求的是长按1s,那我也以长按1s为例介绍,如何判断长按1s吧。

首先定义一个标志位is_1s,在定时器里检查is_1s,如果is_1s=0,则开始数数(定时器每1s进一次),数够1000了,就让is_1s置为1,如果检测到is_1s已经是1了,则清零数数。注意初始状态下is_1s应为1.

bit is_1s=1;

unsigned int count_1000ms=0;//为长按按键数数

void Timer2_Isr(void) interrupt 12
{
    if(is_1s==0)//1s数数,主要服务于长按S7 1秒
    {
        if(++count_1000ms==1000)
        {
            is_1s=1;
            count_1000ms=0;
        }
    }
    else
    {
        count_1000ms=0;
    }
}

现在就需要我们利用这个is_1s来判断长按是否达到1s了。其实对于长按1s我一直有两种理解,最开始的时候我理解的是长按达到1s后松开,则触发效果,不过现在感觉这样理解的不对,应该是长按1S之后,就算没松开按键,也应该产生长按1s的现象了。我们在按下按键后第一个Delay5ms()消抖之后加上is_1s=0来开始计时,在while(P30==0)里判断is_1s的值,如果is_1s为1了,也就是说从上一次清零(即按下按键之后)到现在,已经过来1s,此时就判定为长按1s了,处理长按1s并跳出while循环。具体代码如下:

P3=0xFF;
if(P30==0)
{
    Delay5ms();
    is_1s=0;//is_1s置为0的1s之后会被定时器置为1,通过检查is_1s就可以判断是否长按了1S
    while(P30==0)
    {

        if(is_1s==1&&mod==2)//如果检测到长按1s了,并且此时处在湿度界面
        {
            write_at(0,0);//则重置计数
            break;
        }
    }
    is_1s=1;
    Delay5ms();
    key_value=7;
}
 

三、完整代码演示

首先,我要说明,因为PWM输出需要持续修改ULN的值,可能会导致数据窜位,进而导致蜂鸣器和继电器条一下。之前在LED处理时就提到过,应该避免重复开关控制某个外设的锁存器,LED也进行了相关处理,但是这个PWM没办法,不得不重复开关它的锁存器。这是代码中没有解决的问题(之一)。

另外,由于LED灯需要100ms闪烁,刚才提到的PWM输出可以放在main的while循环里,但是循环里不能有延时,如果while循环不加延时,全用定时器数数的话,真的太麻烦了。所以我就把while里面的延时留下,不过延时的同时输出PWM了,具体可以看代码。

代码还有很多不足的地方,不过内容基本实现了。

后续还是老老实实做省赛的题目吧......

main.c

#include <stc15.h>
#include <intrins.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
0x40,0x79,0x24,0x30,0x19,0x12,0x02,0x78,0x00,0x10,
0xFF,//熄灭 20
0x8E, //F 21
0x89,//H 22
0x88, //A 23
0x8C,	//P 24
};volatile unsigned char Led_Num=0xFF;
volatile unsigned char ULN=0x00;
#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=Led_Num;P2|=0x80;P2&=0x9F;P2&=0x1F;#define NIXIE_CHECK()	P2|=0xC0;P2&=0xDF;P2&=0x1F;
#define NIXIE_ON()		P2|=0xE0;P2&=0xFF;P2&=0x1F;#define MOTOR_ON()		ULN|=0x20;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;
#define MOTOR_OFF()		ULN&=0xDF;	P0=ULN;P2|=0xA0;P2&=0xBF;P2&=0x1F;#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;void get_key(void);
void Delay100ms(void);	//@12.000MHz
void Delay5ms(void);	//@12.000MHz
void Timer0_Init(void);
void Timer1_Init(void);		//1毫秒@12.000MHz
void Timer2_Init(void);		//1毫秒@12.000MHz
void send_wave(void);
void read_remote(void);
void read_ne555(void);
void show_menu(void);
void run(void);
void PWM_out_80(void);
void PWM_out_20(void);
void Led_run(void);
void relay_run(void);unsigned char location;
unsigned char key_value=0;
unsigned char Nixie_num[]={20,20,20,20,20,20,20,20};
unsigned char AD=0;
sbit TX=P1^0;
sbit RX=P1^1;
bit is_read_remote=0;
bit is_read_555=0;
unsigned int remote=0;
unsigned int fre=0;
unsigned char mod=0;
unsigned char fre_canshu=90;//频率参数的单位是0.1KHz
unsigned char shidu_canshu=40;
unsigned char remote_canshu=6;
unsigned char shidu=0;
unsigned char count_relay=0;
bit relay_is_on=0;
void main()
{unsigned char count_100=0;//中间变量,记录100个1ms循环LED_OFF_ALL();//关闭LED灯RELAY_OFF();//关闭继电器count_relay=read_at(0);//读取继电器闭合次数Delay100ms();Timer0_Init();Timer1_Init();Timer2_Init();EA=1;while(1){get_key();//读取按键run();while(1)//一个100ms的延时,延时的同时,输出PWM驱动电机{//迫不得已的做法,主要LED灯闪烁哪里太需要这个100ms的延时了if(++count_100==100)//100ms后跳出while(1)循环,下边的PWM输出一次刚好1ms{count_100=0;break;}//在此延时100ms的循环内,输出PWM,1KHz的PWM周期刚好是1ms//注意频率参数的单位是0.1KHzif(fre/100>fre_canshu)//如果频率大于频率参数的话PWM_out_80();//就输出duty为80的PWMelse if(fre/100<=fre_canshu)//如果频率小于频率参数的话PWM_out_20();//就输出duty为20的PWM}}
}
void run()
{unsigned char DA=0;//定义一个中间变量,用于记录待输出的DA值read_remote();//超声波测距read_ne555();//读取NE555show_menu();//显示菜单Led_run();//控制LED灯运行relay_run();//控制继电器AD=read_pcf(3);AD=read_pcf(3);//读取电位计,因为下边还要DA输出,这里重复读取两次是为了防止读取不出来数据shidu=AD*0.3921;//湿度值=AD/255*100;DA=(shidu-shidu_canshu)*4/(80-shidu_canshu)+1;//根据曲线拟合出的函数DA=DA>5 ? 5 : DA;//限制输出幅值DA=DA<1 ? 1 :DA;write_pcf(DA*51);write_pcf(DA*51);//同上连续输出两次。输出=待输出电压/5*255;
}
bit is_1s=1;
unsigned int count_500ms;
unsigned int count_1s=0;
unsigned int count_1000ms=0;//为长按按键数数
void Timer2_Isr(void) interrupt 12
{P0=0x01<<location;NIXIE_CHECK();//数码管扫描P0=Seg_Table[Nixie_num[location]];NIXIE_ON();if(++location==8)location=0;if(is_read_remote==0)//每500ms读取一次超声波{if(++count_500ms==500){is_read_remote=1;count_500ms=0;}}if(is_read_555==0)//每过1s读取一次Ne555{if(++count_1s==1000){is_read_555=1;count_1s=0;}}if(is_1s==0)//1s数数,主要服务于长按S7 1秒{if(++count_1000ms==1000){is_1s=1;count_1000ms=0;}}else{count_1000ms=0;}
}
void Timer0_Init(void)
{AUXR = 0x80;                    //定时器0为1T模式TMOD = 0x04;                    //设置定时器0为16位自动重装载外部记数模式TH0 = TL0 = 0x00;               //设置定时器0初始值TR0 = 1;                        //定时器0开始工作//ET0 = 1;                        //开定时器0中断
}
void Timer1_Init(void)		//1毫秒@12.000MHz
{AUXR |= 0x40;			//定时器时钟1T模式TMOD &= 0x0F;			//设置定时器模式TL1 = 0x20;				//设置定时初始值TH1 = 0xD1;				//设置定时初始值TF1 = 0;				//清除TF1标志//TR1 = 1;				//定时器1开始计时
}
void Timer2_Init(void)		//1毫秒@12.000MHz
{AUXR |= 0x04;			//定时器时钟1T模式T2L = 0x20;				//设置定时初始值T2H = 0xD1;				//设置定时初始值AUXR |= 0x10;			//定时器2开始计时IE2 |= 0x04;			//使能定时器2中断
}
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(void)
{unsigned char key_P3=P3;P3=0xFF;if(P30==0){Delay5ms();is_1s=0;//is_1s置为0的1s之后会被定时器置为1,通过检查is_1s就可以判断是否长按了1Swhile(P30==0){run();if(is_1s==1&&mod==2)//如果检测到长按1s了,并且此时处在湿度界面{write_at(0,0);//则重置计数break;}}is_1s=1;Delay5ms();key_value=7;}else if(P31==0){Delay5ms();while(P31==0){run();}Delay5ms();key_value=6;}else 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;}//S4菜单切换if(key_value==4){if(mod==0||mod==1)mod=2;else if(mod==2)mod=3;else if(mod==3||mod==4)mod=5;else if(mod==5||mod==6||mod==7)mod=0;}//S5在三个参数界面之间切换else if(key_value==5){if(mod==5)mod=6;else if(mod==6)mod=7;else if(mod==7)mod=5;}//S6 在参数界面:加 在距离界面,切换距离单位else if(key_value==6){if(mod==5)fre_canshu=fre_canshu<120 ? fre_canshu+5 : 10;//限幅,下同else if(mod==6)shidu_canshu=shidu_canshu<60 ? shidu_canshu+10 : 10;else if(mod==7)remote_canshu=remote_canshu<12 ? remote_canshu+1 : 1;if(mod==3)mod=4;else if(mod==4)mod=3;}//S7 在参数界面:减 在频率界面,切换频率单位 长按功能在上边读取按键那里else if(key_value==7){if(mod==5)fre_canshu=fre_canshu>10 ? fre_canshu-5 : 120;//限幅else if(mod==6)shidu_canshu=shidu_canshu>10 ? shidu_canshu-10 : 60;else if(mod==7)remote_canshu=remote_canshu>1 ? remote_canshu-1 : 12;if(mod==0)mod=1;else if(mod==1)mod=0;}key_value=0;P3=key_P3;
}void Delay14us(void)	//@12.000MHz
{unsigned char data i;_nop_();_nop_();i = 47;while (--i);
}void send_wave(void)
{unsigned char i=0;for(;i<8;i++){TX=0;Delay14us();TX=1;Delay14us();}
}
void read_remote(void)
{unsigned int url_t=0;//记录超声波来回的时间,注意没有单位if(is_read_remote==1)//没过一段时间读取一次超声波,避免连续发送读取时相互干扰{is_read_remote=0;send_wave();//发送超声波TR1=1;//开始计时while(RX==1&&TF1==0);//如果检测到返回的超声波或者定时器超时TR1=0;//停止计时if(RX==0)//如果检测到了返回的超声波{//则记录来回的时间url_t=TH1;url_t<<=8;url_t|=TL1;}else//如果超声波超时{ url_t=0;}//实际的时间=url_t/12000000秒//实际的距离=(url_t/12000000)*340*100/2 厘米remote=(unsigned int)(url_t*0.001417);url_t=0;//为下次读取超声波,清零所有数据。下同TL1=0;TH1=0;TF1=0;}
}
void read_ne555(void)
{if(is_read_555==1)//每隔1s读取一次NE555,读出来的数据就刚好是频率{is_read_555=0;TR0=0;fre=TH0;//读取频率fre<<=8;fre|=TL0;TH0=0;//清零相关数据TL0=0;TF0=0;TR0=1;}
}
void show_menu(void)
{if(mod==0)//显示频率{Nixie_num[0]=21,//FNixie_num[1]=20,Nixie_num[2]=fre/100000>0 ? fre/100000%10 : 20;Nixie_num[3]=fre/10000>0 ? fre/10000%10 : 20;Nixie_num[4]=fre/1000>0 ? fre/1000%10 : 20;Nixie_num[5]=fre/100>0 ? fre/100%10 : 20;Nixie_num[6]=fre/10>0 ? fre/10%10 : 20;Nixie_num[7]=fre/1>0 ? fre/1%10 : 20;}else if(mod==1)//显示频率,单位KHz{Nixie_num[0]=21,Nixie_num[1]=20,Nixie_num[2]=fre/10000000>0 ? fre/10000000%10 : 20;Nixie_num[3]=fre/1000000>0 ? fre/1000000%10 : 20;Nixie_num[4]=fre/100000>0 ? fre/100000%10 : 20;Nixie_num[5]=fre/10000>0 ? fre/10000%10 : 20;Nixie_num[6]=fre/1000>0 ? fre/1000%10+10 : 10;//显示带小数点的数字Nixie_num[7]=fre/100>0 ? fre/100%10 : 0;}else if(mod==2)//显示湿度{Nixie_num[0]=22,Nixie_num[1]=20,Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=20;Nixie_num[5]=20;Nixie_num[6]=shidu/10>0 ? shidu/10%10 : 20;Nixie_num[7]=shidu/1>0 ? shidu/1%10 : 0;;		}else if(mod==3)//显示距离{Nixie_num[0]=23,Nixie_num[1]=20,Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=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;	}else if(mod==4)//显示距离,单位m{Nixie_num[0]=23,Nixie_num[1]=20,Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=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==5)//显示频率参数{Nixie_num[0]=24,Nixie_num[1]=1,Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=20;Nixie_num[5]=fre_canshu/100>0 ? fre_canshu/100%10 : 20;Nixie_num[6]=fre_canshu/10>0 ? fre_canshu/10%10+10 : 10;Nixie_num[7]=fre_canshu/1>0 ? fre_canshu/1%10 : 0;}else if(mod==6)//显示湿度参数{Nixie_num[0]=24,Nixie_num[1]=2,Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=20;Nixie_num[5]=20;Nixie_num[6]=shidu_canshu/10%10;Nixie_num[7]=shidu_canshu/1%10;}else if(mod==7)//显示距离参数{Nixie_num[0]=24,Nixie_num[1]=3,Nixie_num[2]=20;Nixie_num[3]=20;Nixie_num[4]=20;Nixie_num[5]=20;Nixie_num[6]=remote_canshu/10%10+10;Nixie_num[7]=remote_canshu/1%10;}
}
void Delay800us(void)	//@12.000MHz
{unsigned char data i, j;i = 10;j = 83;do{while (--j);} while (--i);
}
void Delay200us(void)	//@12.000MHz
{unsigned char data i, j;i = 3;j = 82;do{while (--j);} while (--i);
}void PWM_out_80(void)//输出duty为80的PWM
{MOTOR_ON()Delay800us();MOTOR_OFF();Delay200us();
}
void PWM_out_20(void)//输出duty为20的PWM
{MOTOR_ON()Delay200us();MOTOR_OFF();Delay800us();
}
void Led_run(void)
{static bit L1_is_on=0;static bit L2_is_on=0;static bit L3_is_on=0;static bit L4_is_on=0;static bit L5_is_on=0;static bit L6_is_on=0;//配合主函数里的100ms延时,即可达到每次运行Led_run切换一次灯的状态,完成闪烁if(mod==0||mod==1)//在频率界面,L1闪烁{if(L1_is_on==0)//如果L1没有点亮,则点亮{LED_ON(0);L1_is_on=1;}else if(L1_is_on==1)//否则熄灭。{LED_OFF(0);L1_is_on=0;}}else if(mod==2)//在湿度界面,L2闪烁{if(L2_is_on==0){LED_ON(1);L2_is_on=1;}else if(L2_is_on==1){LED_OFF(1);L2_is_on=0;}}else if(mod==3||mod==4)//在距离界面,L3闪烁{if(L3_is_on==0){LED_ON(2);L3_is_on=1;}else if(L3_is_on==1){LED_OFF(2);L3_is_on=0;}}//下面为退出某个模式,但是刚好闪烁到LED点亮的状态,则关闭不该点亮的LEDif(!(mod==0||mod==1)&&L1_is_on==1)//如果不在频率界面,并且L1点亮了{//则熄灭。。下边都一样LED_OFF(0);L1_is_on=0;}else if(!(mod==2)&&L2_is_on==1){LED_OFF(1);L2_is_on=0;}else if(!(mod==3||mod==4)&&L3_is_on==1){LED_OFF(2);L3_is_on=0;}//如果频率大于频率参数,并且L4没有点亮,if(fre/100>fre_canshu&&L4_is_on==0)//注意频率参数的单位是0.1KHz{//则点亮LED_ON(3);L4_is_on=1;}else if((!(fre/100>fre_canshu))&&L4_is_on==1)//如果频率小于频率参数,并且L4还没西梅{//则熄灭。。下同LED_OFF(3);L4_is_on=0;}if(shidu>shidu_canshu&&L5_is_on==0)//{LED_ON(4);L5_is_on=1;}else if(!(shidu>shidu_canshu)&&L5_is_on==1){LED_OFF(4);L5_is_on=0;}if(remote/10>remote_canshu&&L6_is_on==0){LED_ON(5);L6_is_on=1;}else if(!(remote/10>remote_canshu)&&L6_is_on==1){LED_OFF(5);L6_is_on=0;}
}
void relay_run(void)
{if((remote/10>=remote_canshu)&&(relay_is_on==0))//如果距离大于距离参数,并且继电器没有打开{//则打开继电器relay_is_on=1;RELAY_ON();write_at(0,++count_relay);}else if((!(remote/10>remote_canshu))&&(relay_is_on==1))//如果距离小于距离参数,并且继电器打开了{//则熄灭relay_is_on=0;RELAY_OFF();}
}

iic.c

/*	#   I2C代码片段说明1. 	本文件夹中提供的驱动代码供参赛选手完成程序设计参考。2. 	参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题中对单片机时钟频率的要求,进行代码调试和修改。
*/
#include <stc15.h>
#include <intrins.h>
#include "iic.h"
sbit sda=P2^1;
sbit scl=P2^0;
#define DELAY_TIME	5//
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 write_pcf(unsigned char add)
{I2CStart();I2CSendByte(0x90);I2CWaitAck();I2CSendByte(0x40);I2CWaitAck();I2CSendByte(add);I2CWaitAck();I2CStop();
}
unsigned char read_pcf(unsigned char add)
{unsigned char ad=0;I2CStart();I2CSendByte(0x90);I2CWaitAck();I2CSendByte(add);I2CWaitAck();I2CStop();I2CStart();I2CSendByte(0x91);I2CWaitAck();ad=I2CReceiveByte();I2CSendAck(1);I2CStop();return ad;
}
void write_at(unsigned char add,dat)
{I2CStart();I2CSendByte(0xA0);I2CWaitAck();I2CSendByte(add);I2CWaitAck();I2CSendByte(dat);I2CWaitAck();I2CStop();
}
unsigned char read_at(unsigned char add)
{unsigned char at=0;I2CStart();I2CSendByte(0xA0);I2CWaitAck();I2CSendByte(add);I2CWaitAck();I2CStop();I2CStart();I2CSendByte(0xA1);I2CWaitAck();at=I2CReceiveByte();I2CSendAck(1);I2CStop();return at;
}

iic.h

#ifndef _IIC_H_
#define _IIC_H_void write_pcf(unsigned char add);
unsigned char read_pcf(unsigned char add);
void write_at(unsigned char add,dat);
unsigned char read_at(unsigned char add);#endif

相关文章:

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

前言 一、决赛题目 1.比赛题目 2.题目解读 二、功能实现 1.关于定时器资源 1&#xff09;超声波和NE555需要的定时器资源 2&#xff09;定时器2 2.单位切换 3.数据长度不足时&#xff0c;高位熄灭 4.AD/DA多通道的处理 5.PWM输出 6.长按功能的实现 三、完整代码演…...

【Entity Framework】如何使用EF中的生成值

【Entity Framework】如何使用EF中的生成值 文章目录 【Entity Framework】如何使用EF中的生成值一、概述二、默认值三、计算列四、设置主键五、显示配置值生成六、设置日期/时间值生成6.1 创建时间戳6.2 更新时间戳 七、替代值生成八、无值生成九、总结 一、概述 数据库列的值…...

【MATLAB源码-第185期】基于matlab的16QAM系统相位偏移估计EOS算法仿真,对比补偿前后的星座图误码率。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 1. 引言 M-QAM调制技术的重要性 现代通信系统追求的是更高的数据传输速率和更有效的频谱利用率。M-QAM调制技术&#xff0c;作为一种高效的调制方案&#xff0c;能够通过在相同的带宽条件下传输更多的数据位来满足这一需求…...

C++入门语法(命名空间缺省函数函数重载引用内联函数nullptr)

目录 前言 1. 什么是C 2. C关键字 3. 命名空间 3.1 命名空间的定义 3.2 命名空间的使用 4. C输入和输出 5. 缺省函数 5.1 概念 5.2 缺省参数分类 6. 函数重载 6.1 概念 6.2 为何C支持函数重载 7. 引用 7.1 概念 7.2 特性 7.3 常引用 7.4 引用与指针的区别 7…...

9.vector的使用介绍和模拟实现

1.vector的介绍及使用 1.1 vector的介绍 vector的文档介绍 vector是表示可变大小数组的序列容器。 就像数组一样&#xff0c;vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问&#xff0c;和数组一样高效。但是又不像数组&#xff0c…...

探索设计模式的魅力:MVVM模式在AI大模型领域的创新应用-打破传统,迎接智能未来

​&#x1f308; 个人主页&#xff1a;danci_ &#x1f525; 系列专栏&#xff1a;《设计模式》 &#x1f4aa;&#x1f3fb; 制定明确可量化的目标&#xff0c;坚持默默的做事。 MVVM模式在AI大模型领域的创新应用-打破传统迎接智能未来 &#x1f680; “在人工智能的领域里&a…...

Docker使用— Docker部署安装Nginx

Nginx简介 Nginx 是一款高性能的 web 服务器、反向代理服务器以及电子邮件&#xff08;IMAP/POP3/SMTP&#xff09;代理服务器&#xff0c;由俄罗斯开发者伊戈尔塞索耶夫&#xff08;Igor Sysoev&#xff09;编写&#xff0c;并在2004年10月4日发布了首个公开版本0.1.0。Nginx…...

C/C++基础----运算符

算数运算符 运算符 描述 例子 两个数字相加 两个变量a b得到两个变量之和 - 两个数字相减 - * 两个数字相乘 - / 两个数字相除 - % 两个数字相除后取余数 8 % 3 2 -- 一个数字递减 变量a&#xff1a;a-- 、--a 一个数字递增 变量a: a 、 a 其中递…...

YOLOv9:下一代目标检测的革新

目标检测作为计算机视觉领域的一个重要分支&#xff0c;一直是研究的热点。YOLO系列作为目标检测算法的佼佼者&#xff0c;自YOLO1发布以来&#xff0c;就在速度和精度上取得了很好的平衡&#xff0c;深受业界和学术界的喜爱。 YOLOv9作为该系列的最新版本&#xff0c;不仅在性…...

Leetcode算法训练日记 | day20

一、合并二叉树 1.题目 Leetcode&#xff1a;第 617 题 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两棵树上的一些节点将会重叠&#xff08;而另一些不会&#xff09;。你需要将这两棵树合并成一棵新…...

conda创建虚拟环境太慢,Collecting package metadata (current_repodata.json): failed

(省流版&#xff1a;只看加粗红色&#xff0c;末尾也有哦) 平时不怎么用conda&#xff0c;在前公司用服务器的时候用的是公司的conda源&#xff0c;在自己电脑上直接用python创建虚拟环境完事儿&#xff0c;所以对conda的配置并不熟悉~~【狗头】。但是python虚拟环境的最大缺点…...

Tensorflow(GPU版本配置)一步到位!!!

Tensorflow&#xff08;GPU版本配置&#xff09;一步到位&#xff01;&#xff01;&#xff01; CUDA安装CUDA配置Tensorflow配置常见的包 CUDA安装 配置了N次的Tensorflow–Gpu版本&#xff0c;完成了踩坑&#xff0c;这里以配置Tensorflow_gpu 2.6.0为例子进行安装 以下为ten…...

STL之map

CSTL之map 1.介绍 map是映射的意思&#xff0c;即每个x对应一个y&#xff0c;我们这里说成key和value 举例子说明&#xff1a;运动->篮球 &#xff08;运动是key,篮球是value&#xff09;用电脑->写代码 &#xff08;用电脑是key&#xff0c;写代码是value&#xff09;…...

闲谈2024(一)

时光飞逝&#xff0c;一转眼24年的第一个季度已经过去了&#xff0c;回望这3个多月&#xff0c;感触颇多。首先&#xff0c;24年从一个一心只读圣贤书&#xff0c;全身心投入在技术上的研发工程师&#xff0c;转变为一个团队的小leader。从我个人对自己的定位来说&#xff0c;我…...

SQL注入利用 学习- 布尔盲注

布尔盲注适用场景&#xff1a; 1、WAF或者过滤函数完全过滤掉union关键字 2、页面中不再回显具体数据&#xff0c;但是在SQL语句执行成功或失败返回不同的内容 代码分析&#xff1a;过滤关键字 union if(preg_match(/union/i, $id)) { echo "fail"; exit; } 代码…...

前端项目部署教程——有域名有证书

一、拉取nginx镜像 docker pull nginx //先拉取nginx镜像二、打包前端项目 1、将Vue打包项目传输到/usr/local/vue/下blog和admin文件夹下 重点: 每一个子域名都要申请证书&#xff0c;在阿里云每年可以免费申请20个证书&#xff0c; 免费证书申请教程在 免费证书申请教程 …...

《看漫画学C++》第12章 可大可小的“容器”——向量

在C编程的世界里&#xff0c;数组是一种基础且广泛使用的数据结构。然而&#xff0c;传统的静态数组在大小固定、管理不便等方面的局限性&#xff0c;常常让开发者感到束手束脚。幸运的是&#xff0c;C标准库中的vector类为我们提供了一种更加灵活、高效的动态数组解决方案。 …...

OpenAI推出GPTBot网络爬虫:提升AI模型同时引发道德法律争议

文章目录 一、GPTBot 简介二、功能特点三、技术细节3.1、用户代理标识3.2、数据采集规则3.3、数据使用目的3.4、网站屏蔽方法3.5、数据过滤 四、GPTBot 的道德和法律问题五、GPTBot 的使用方法和限制六、总结 一、GPTBot 简介 OpenAI 推出的网络爬虫GPTBot旨在通过从互联网上收…...

Claude使用教程

claude 3 opus面世后&#xff0c;网上盛传吊打了GPT-4。网上这几天也已经有了许多应用&#xff0c;但竟然还有很多小伙伴不知道国内怎么用gpt&#xff0c;也不知道怎么去用这个据说已经吊打了gpt-4的claude3。 今天我们想要进行的一项尝试就是—— 用claude3和gpt4&#xff0c…...

【经典算法】LeetCode25:K 个一组翻转链表(Java/C/Python3,Hard)

#算法 目录 题目描述思路及实现方式一&#xff1a;递归思路代码实现Java 版本C 语言版本Python3 版本 复杂度分析 方式二&#xff1a;迭代和原地反转思路代码实现Java 版本C 语言版本Python3 版本 复杂度分析 总结相似题目 标签&#xff1a;链表、递归 题目描述 给你链表的头…...

6.11物联网RK3399项目开发实录-驱动开发之定时器的使用(wulianjishu666)

嵌入式实战开发例程【珍贵收藏&#xff0c;开发必备】&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1tkDBNH9R3iAaHOG1Zj9q1Q?pwdt41u 定时器使用 前言 RK3399有 12 个 Timers (timer0-timer11)&#xff0c;有 12 个 Secure Timers(stimer0~stimer11) 和 2 个 …...

算法训练营第二十三天(二叉树完结)

算法训练营第二十三天&#xff08;二叉树完结&#xff09; 669. 修剪二叉搜索树 力扣题目链接(opens new window) 题目 给定一个二叉搜索树&#xff0c;同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[L, R]中 (R>L) 。你可能需要改…...

mysql主从复制Slave_SQL_Running: No

1、SHOW SLAVE STATUS \G; Slave_SQL_Running: No 解决方案&#xff1a; 重新同步主库和从库的数据 1、从库先停掉slave stop slave; 2、在主库查看此时的日志文件和位置 show master status; 3、在从库中执行 change master to master_host192.168.2.177,master_userslave…...

【SpringBoot】SpringBoot项目快速搭建

本文将介绍Springboot项目的快速搭建 快速创建SpringBoot项目 打开IDEA在File->New->Project中新建项目 点击左侧的Spring Initializr 输入以下信息&#xff1a; Name 项目名称Group 根据公司域名来&#xff0c;或者默认com.example【倒序域名】Package Name 包名&am…...

Terraform 状态不同步处理

背景 在使用 Terraform 创建 TencentCloud TKE 的时候&#xff0c;手贱把 node pool 删掉了。导致执行 destroy, plan 都会报错。 │ Error: [TencentCloudSDKError] CodeInternalError.UnexpectedInternal, Messagerelated node pool query err(get node pool failed: [E501…...

4.2.k8s的pod-标签管理、镜像拉取策略、容器重启策略、资源限制、优雅终止

一、标签管理 1.标签在k8s中极其重要&#xff0c;大多数资源的相互关联就需要使用标签&#xff1b;也就是说&#xff0c;资源的相互关联大多数时候&#xff0c;是使用标签进行关联的&#xff1b; 2.其他作用&#xff0c;在k8s集群中&#xff0c;node节点的一些操作比如污点及污…...

能源党建后台项目总结

1.引入 本次框架是Ruoyi-plusvue2element组合。 2.样式 由于是后台项目&#xff0c;样式要求统一&#xff0c;不可以有的输入框长有的短。着重几点&#xff1a; 1.关于form表单应该如何水平布局 在element中&#xff0c;form有个属性叫&#xff1a;:inline"true"…...

股票高胜率的交易法则是什么?

股票交易中的高胜率交易法则并非一成不变&#xff0c;而是根据市场状况、个人投资风格和经验等多种因素综合而定的。以下是一些有助于提升交易胜率的法则和策略&#xff1a; 1.趋势跟踪法则&#xff1a;在股票交易中&#xff0c;趋势跟踪是一种有效的策略。通过观察大盘和个股…...

C语言 | sizeof与strlen的区别(附笔试题)

目录&#xff1a; 1. sizeof和strlen的对比 2. 数组和指针 笔试题解析 3. 指针运算 笔试题解析 内容多多&#xff0c;需耐心看完&#xff0c;加油&#xff01;&#xff01;&#xff01; 一.sizeof和strlen的对比 1.1 sizeof 在学习操作符的时候&#xff0c;我们学习了 s…...

AI自动绘画器介绍和应用场景

AI自动绘画器是一种利用人工智能技术来生成绘画作品的工具。以下是一些常见的AI自动绘画器&#xff1a; DeepDream&#xff1a; 风格&#xff1a;可以生成三种风格的图片&#xff0c;包括深度梦幻风格、深度风格和浅层风格。应用场景&#xff1a;起初设计用于帮助研究人员理解…...