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

前言

一、决赛题目

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

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

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

相关文章

【QT】pro文件里添加又删除LIBS不影响运行的原因

我发现个问题啊&#xff0c;如果运行项目&#xff0c;发现报错&#xff0c;缺少某dll&#xff0c;接着你在pro文件里加上win32:LIBS -lOpengl32&#xff08;举个例子&#xff09;&#xff0c;接着可以运行了&#xff0c;接着把这行删掉&#xff0c;再运行&#xff0c;仍然可以…

瑞_23种设计模式_访问者模式

文章目录 1 访问者模式&#xff08;Visitor Pattern&#xff09;1.1 介绍1.2 概述1.3 访问者模式的结构1.4 访问者模式的优缺点1.5 访问者模式的使用场景 2 案例一2.1 需求2.2 代码实现 3 案例二3.1 需求3.2 代码实现 4 拓展——双分派4.1 分派4.2 动态分派&#xff08;多态&am…

安卓刷机fastboot分段传输

win10 fastboot 无法识别&#xff0c;驱动下载地址GitHub - xushuan/google_latest_usb_driver_windows 把inf文件更新到设备管理器驱动更新即可 问题 archive does not contain super_empty.img Sending vbmeta_a (4 KB) OKAY [ 0.117s] Writing …

antd+Vue 3实现table行内upload文件图片上传【超详细图解】

目录 一、背景 二、效果图 三、代码 一、背景 一名被组长逼着干前端的苦逼后端&#xff0c;在一个晴天霹雳的日子&#xff0c;被要求前端订单产品实现上传产品图片并立刻回显图片。 二、效果图 三、代码 <template><a-table :dataSource"dataSource" :c…

使用单点登录(SSO)如何提高安全性和用户体验

什么是单点登录&#xff08;SSO&#xff09; 对于所有大量采用云应用程序的组织来说&#xff0c;有效的身份管理是一个巨大的挑战&#xff0c;如果每个 SaaS 应用程序的用户身份都是独立管理的&#xff0c;则用户必须记住多个密码&#xff0c;技术支持技术人员在混合环境中管理…

官网下载IDE插件并导入IDE

官网下载IDEA插件并导入IDEA 1. 下载插件2. 导入插件 1. 下载插件 地址&#xff1a;https://plugins.jetbrains.com/plugin/21068-codearts-snap/versions 说明&#xff1a;本次演示以IDEA软件为例 操作&#xff1a; 等待下载完成 2. 导入插件 点击File->setting->Pl…

Linux下使用C语言实现高并发服务器

高并发服务器 这一个课程的笔记 相关文章 协议 Socket编程 高并发服务器实现 线程池 使用多进程并发服务器时要考虑以下几点&#xff1a; 父进程最大文件描述个数(父进程中需要close关闭accept返回的新文件描述符)系统内创建进程个数(与内存大小相关)进程创建过多是否降低整体…

docker从入门到熟悉

一、什么是docker&#xff1f; Docker是一个用于开发&#xff0c;交付和运行应用程序的开放平台。Docker使您能够将应用程序与基础架构分开&#xff0c;从而可以快速交付软件。借助Docker&#xff0c;您可以以与管理应用程序相同的方式来管理基础架构。通过利用Docker的快速交付…

Java——数组练习

目录 一.数组转字符串 二.数组拷贝 三.求数组中元素的平均值 四.查找数组中指定元素(顺序查找) 五.查找数组中指定元素(二分查找) 六.数组排序(冒泡排序) 七.数组逆序 一.数组转字符串 代码示例&#xff1a; import java.util.Arrays int[] arr {1,2,3,4,5,6}; String…

政安晨:【深度学习神经网络基础】(三)—— 激活函数

目录 线性激活函数 阶跃激活函数 S型激活函数 双曲正切激活函数 修正线性单元 Softmax激活函数 偏置扮演什么角色&#xff1f; 政安晨的个人主页&#xff1a;政安晨 欢迎 &#x1f44d;点赞✍评论⭐收藏 收录专栏: 政安晨的机器学习笔记 希望政安晨的博客能够对您有所裨…

超越ChatGPT,国内快速访问的强大 AI 工具 Claude

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

GeoServer:忘记密码重置

操作步骤 1. 找到data_dir/security/usergroup/default目录下的users.xml文件&#xff0c; 2.修改password为plain:geoserver&#xff0c; 这里无论原来的密码是什么&#xff0c;改为plain:geoserver之后&#xff0c;就可以通过admin&#xff1a;geoserver默认账户密码登录了。…

[计算机效率] 鼠标手势工具:WGestures(解放键盘的超级效率工具)

3.22 鼠标手势工具&#xff1a;WGestures 通过设置各种鼠标手势和操作进行绑定。当用户通过鼠标绘制出特定的鼠标手势后就会触发已经设置好的操作。有点像浏览器中的鼠标手势&#xff0c;通过鼠标手势操纵浏览器做一些特定的动作。这是一款强大的鼠标手势工具&#xff0c;可以…

2024年能源环境、材料科学与人工智能国际会议(ICEEMSAI2024)

2024年能源环境、材料科学与人工智能国际会议(ICEEMSAI2024) 会议简介 2024国际能源环境、材料科学和人工智能大会&#xff08;ICEEMSAI 2024&#xff09;主要围绕能源环境、物质科学和人工智慧等研究领域&#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…

最近一些前端面试问题整理

最近一些前端面试问题整理 4月8号1. TS 中的 类型别名 和接口的区别是什么&#xff1f;2. 什么是深拷贝和浅拷贝&#xff1f;深浅拷贝的方法有哪些&#xff1f;浅拷贝&#xff08;Shallow Copy&#xff09;深拷贝&#xff08;Deep Copy&#xff09;区别总结 3. 使用 JSON.strin…

Python中Python-docx 包的run介绍

先对run做一个简单地介绍。每个paragraph对象都包含一个run对象的列表。举例&#xff1a; 这是一个简短的段落。 from docx import Document doc Document("1.docx") #上面这段话保存在1.docx中 print("这一段的run个数是&#xff1a;",len(doc.paragr…

GitHub repository - Code - Issues - Pull Requests - Wiki

GitHub repository - Code - Issues - Pull Requests - Wiki 1. Code2. Issues3. Pull Requests4. WikiReferences 1. Code 显示该仓库中的文件列表。仓库名下方是该仓库的简单说明和 URL. 2. Issues 用于 BUG 报告、功能添加、方向性讨论等&#xff0c;将这些以 Issue 形式进…

ESXI 中安装 虚拟机 麒麟v10 操作系统

浏览器访问登录ESXI 上传镜像文件 创建新虚拟机 选择虚拟机名称和操作系统 选择存储 配置虚拟机 配置虚拟机 cpu 内存 硬盘 并选择虚拟机驱动 配置完成后&#xff0c;点击下一步&#xff0c;并点击完成。 开机 完成后选择该虚拟机&#xff0c;并打开电源 等待出现以下界面…

[Linux_IMX6ULL驱动开发]-驱动的分层及实现

目录 驱动分层的思路 驱动分层的实现 上层驱动的实现 次设备号的使用 上层驱动代码 底层驱动的实现 底层驱动c文件的实现 底层驱动头文件实现 应用层文件的实现 驱动分层的思路 在上一篇文章中&#xff0c;博主实现了通过寄存器控制引脚&#xff0c;以此来达到控制LE…