蓝桥杯单片机竞赛主观题总结(全)(2.5W字)

news2024/11/19 15:32:52

前言

对于蓝桥杯的单片机竞赛,主观题很重要,占了百分之70-80的大头,因此主观题做得怎么样决定了比赛是否能拿奖,而客观题的正确率很大程度上决定了你能否获得省一,从而进入决赛。因此我在这里分享一期关于主观题中各个模块的编写规范和注意事项。

本人参加了2022年的第十四届蓝桥杯大赛,并有幸获得了电子类单片机个人赛的省二。同时还手把手教出了一个省二,辅导出了一个国二。很遗憾自己没有进入决赛,刚出成绩的时候,打抱不平,觉得很不甘心。但后来想了想还是自己的个人问题,虽然主观题做的很好,但客观题错了六个。虽然都说客观题,三分靠实力,七分靠运气,但我大二上学期数电模电学的确实不咋滴,这也导致客观题我一遇到数电模电就头大,而恰巧这次蓝桥杯省赛数电模电题占了一大半。

所以我想强调的是学习嵌入式,除了学好应用层的知识,会写代码,我们还要学会数电模电硬件层电路相关知识,这样今后才能成为一名合格的嵌入式攻城狮。我在最近几个月学习STM32的时候,同时也对数电模电进行了相关温故,这使我对理解相关总线协议的工作原理和各种外设的工作方式有了很大帮助。

蓝桥杯全模块代码+部分国赛省赛程序下载
https://download.csdn.net/download/qq_25218501/87965874

正文

译码器、锁存器电路结构

image-20221014221627611

因为WR接GND,所以当Y5为低电平,从而使Y5C为高电平(接通锁存器),从而可以使用P0口作为I/O输出,当P0^6输出低电平,从而使Q7为0,关闭蜂鸣器

三种关闭蜂鸣器写法:

//关闭蜂鸣器习惯写法
sbit buzzer=P0^6;//蜂鸣器
P2=0xa0;;buzzer=0;P2=0x00;
//IO控制
#include"stc15.h"
sbit buzzer=P0^6;//蜂鸣器

void main()
{
    P2=0xa0;;buzzer=0;P2=0x00;//关闭蜂鸣器
    while(1)
    {	
        //...
    }
}


[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rsNYYIR4-1688019066384)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221014224602369.png)]

MM控制方法,此时WR由P3^6自动控制,因为P2和P0作为地址总线,所以P2接收高8位,P0接收低8位

使用继电器

image-20221014230820018

首先接通Y5C锁存器,然后使RELAY为低电平,从而接通继电器,最后再关闭锁存器

#include"stc15.h"
sbit buzzer=P0^6;//蜂鸣器
sbit relay=P0^4;
void main()
{
    P2=0xa0;buzzer=0;relay=0;P2=0x00;//关闭蜂鸣器
    while(1)
    {	
        //...
    }
}

LED灯

image-20221014235048956

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2Kw9FlYc-1688019066385)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221014235026780.png)]

独立按键、矩阵键盘——三行代码

独立按键

image-20221016194352551

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aBZfEGdI-1688019066385)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221016195006255.png)]

矩阵键盘

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1scsJu38-1688019066385)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221016195200210.png)]

image-20221016195320755

image-20221016195425966

注意点

image-20221016195627427

补充

关于三行代码的理解

  • trg:触发一次,表示按键被按下,是个瞬时值

  • cont:表示按键状态,如果按下(未弹起),为对应键值

长按过程trg和cont的变化

  • 假设按下的按键键值为key

  • 刚按下按键时,trg对应的值为key,但key只可读一次,之后都为0。

  • cont在按键未弹起时值始终为key。

如何实现短按

​ 若不涉及长按,直接进行trg值的判断即可(或使用switch case语句批量实现不同键值的操作),但如果
涉及长按,该方法仍可以实现短按功能,但不可与长按独立开

如何实现长按

​ 显然,可以将trg看作按键按下的标志,由于长按是通过时间T(从按下开始)判断的,则可通过trg触发定
时器工作,产生计数值可与T进行比较,当计数值>T时,此时可停止定时器中计数值的累加,同时抛出
标志位进行规定的长按操作。

如何实现长按与短按的独立

​ 若实现短按使用trg进行判别,不论长按的标志位是否可执行,使用trg进行判别及操作必然会先于长按
执行(且必然执行)。为解决冲突,此时可以在定时器计数值累加且计数值<T时进行判别,在判别时轮询
cont的状态,若按键弹开(cont不为key或者cont为0),此时触发短按功能,停止定时器中计数值的累
加,同时抛出标志位进行规定的短按操作。若在定时器计数值累加且计数值<T时按键始终未弹开,此时
会由计数值累加==T,即达到长按的状态,停止定时器中计数值的累加,同时抛出标志位进行规定的长
按操作。由于长短按键的判别都是通过定时器设定计数值与T进行比较确定的,当停止定时器中计数值的
累加可保证长按与短按功能的独立。

数码管显示

共阳极、共阴极

关于共阳极和共阴极的问题,总会有人来问我,怎么看共阴极和共阴极。

答:共阴极、共阳极,顾名思义,公共端口连接电源,就是共阳极;公共端口连接GND,就是共阴极。共阳极就是高电平有效,共阴极为低电平有效。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vu8ZsRjD-1688019066387)(https://note.youdao.com/yws/res/5/WEBRESOURCE8b494f5ae25b5817505d9db94c08a105)]

image-20221016201753594

image-20221016202813336

数码管消影

  • 位选不选择任何数码管(消除影子)->再段选->再位选——(实测最有效)

  • 先段选不选中数码管的任何段->再位选->再段选——(可能仍会有)

  • 写法:

    P2=0xc0;P0=0x00;P2=0x00;//位选

    P2=0xe0;P0=0xXX;P2=0x00;//段选

    P2=0xc0;P0=0xXX;P2=0x00;//位选

定时器中断

学会使用STC软件来配置

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zlpmnuKQ-1688019066388)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20230629134219302.png)]

注意模式一定要选择16位自动重载(实际比赛中也只用它),时钟选择1T。

补充:IAP15不像89C51,89C51只有定时器工作方式2可以设置自动重新装载并且只有8个位;在IAP15中,每个定时器都可以设置为重新装载,并且还是16位的,大大提高了单片机的工作效率,也节省了程序编写。

定时器0:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9ba86y3o-1688019066393)(D:/typora-user-images/image-20230629134931080.png)]

void Timer0Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0x20;		//设置定时初值
	TH0 = 0xD1;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
    
    /*中断开启要自己添加*/
    ET0 = 1; //开启定时器0中断
    EA = 1;//开启总中断
}

定时器1:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dCbxh4eG-1688019066393)(D:/typora-user-images/image-20230629134940825.png)]

void Timer1Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x40;		//定时器时钟1T模式
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0x20;		//设置定时初值
	TH1 = 0xD1;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
    
    /*中断开启要自己添加*/
    ET1 = 1; //开启定时器1中断
    EA = 1;//开启总中断
}

定时器2:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MRFIGxJO-1688019066394)(D:/typora-user-images/image-20230629134917674.png)]

void Timer2Init(void)		//1毫秒@12.000MHz
{
	AUXR |= 0x04;		//定时器时钟1T模式
	T2L = 0x20;		//设置定时初值
	T2H = 0xD1;		//设置定时初值
	AUXR |= 0x10;		//定时器2开始计时
    
    /*中断开启要自己添加*/
    IE2 |= 0x04; //开启定时器2中断
    EA = 1;//开启总中断
}

具体使用可如图位置参考例程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZBRsqQgX-1688019066394)(D:/typora-user-images/image-20221016212159619.png)]

单片机中断程序中,例如T0,如果不对TH0和TL0赋值,那么定时器的初值就为0,这种情况常用于计数模式,同样适用于某些计时如超声波

NE555频率测量

注意P34跳冒接法。

#include"stc15.h"

typedef unsigned int u16;
typedef unsigned char u8;

u8 code t_display[]={0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F};
u8 code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,0x00};      //位码	
u8 buff[8]={0};

u8 Trg,Cont,key_flag,mod;
long signal;
u16 time;

void TimerInit(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x40;		//定时器时钟1T模式
	TMOD &= 0x0F;		//设置定时器模式
	TMOD |= 0x15;		//设置定时器模式
	TL1 = 0xCD;		//设置定时初值
	TH1 = 0xD4;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	
	TH0=0;
	TL0=0;
	ET1=1;
	EA=1;
	TR1=1;
}

void init()
{
	P2=0xa0;P0=0xaf;
	P2=0x80;P0=0xff;
}

void Readkey()
{
	u8 temp,ReadDate;
	P3=0xf0;
	P42=1;P44=1;
	P36=P42;
	P37=P44;
	temp=P3;
	P3=0x0f;
	P42=0;
	P44=0;
	ReadDate=0xff^(P3|temp);
	Trg=ReadDate&(ReadDate^Cont);
	Cont=ReadDate;
}

void number_display(u16 num,u8 mod)
{
	buff[0]=~0x40;
	buff[1]=~t_display[mod];
	buff[2]=~0x40;
	buff[3]=~t_display[num/10000];
	buff[4]=~t_display[num/1000%10];
	buff[5]=~t_display[num/100%10];
	buff[6]=~t_display[num/10%10];
	buff[7]=~t_display[num%10];
}

void main()
{
	u8 flag=0;
	init();
	TimerInit();
	mod=1;
	while(1)
	{
		if(key_flag)
		{
			key_flag=0;
			Readkey();
			if(Trg==0x88)
			{
				if(mod==1)
					mod=2;
				else if(mod==2)
					mod=1;
			}
			if(Trg==0x84)
			{
				flag=1;
				TR0=1;
			}
		}
		if(flag==1)
		time=1000000/signal;
	}
}

void time1() interrupt 3
{
	static u16 count=0,i=0;
	TL1 = 0xCD;
	TH1 = 0xD4;
	if(mod==1)
	{
		number_display(signal,1);
	}else{
		number_display(time,2);
	}
	if(++count%2==0)
	{
		P2=0xc0;P0=0x00;
		P2=0xe0;P0=buff[i];
		P2=0xc0;P0=T_COM[i++];
		if(i==8)
			i=0;
	}
	if(count%10==0)
		key_flag=1;
	if(count==1000)
	{
		signal=(TH0<<8)+TL0;//注意算数优先级
		count=0;
		TH0=0;
		TL0=0;
	}
}

单总线协议介绍-DS18B20

image-20221028222738346

DS18B20使用单总线,顾名思义就是使用P14一个I/O口来控制输入和输出

DS18B20初始化

DS18B20初始化(为了检测单片机上是否存在DS18B20)(前半段为单片机向DS18B20发送的信号,后半段为DS18B20向单片机发送的信号):

!image-20221028224007091

//初始化18B20
bit init_ds18b20(void)
{
    bit initflag=0;
    DQ=1;		//P14
    delay(12);	//先延时一段时间
    DQ=0;		//拉低总线
    delay(80);	//延时大约480-960us
    DQ=1;		//拉高总线
    delay(10);	//延时大约15-60us
    initflag=DQ;//如果initflag等于1则初始化失败
    delay(5);	//延时大约60-240us
    
    return initflag;
}

单片机向DS18B20写数据

  • 操作步骤
    • 1、拉低总线(在15us内)
    • 2、给总线赋值(0或1),最好在刚拉低完总线就立刻给总线赋值
    • 3、延时共60us
    • 4、如果写2位数据,写入的间隙要拉高总线大约1us
  • 重点
    • 1、15us之内应该将所需写的数据送到总线上
    • 2、18B20,15us-60us内对总线进行采样
    • 3、若低电平,写入的位是0;若高电,平写入的位是1
    • 4、先写的是低位数据

//单片机向18B20发数据函数
void wr_ds18b20(unsigned char byt)
{
    unsigned char i;
    for(i=0;i<8;i++)
    {
        DQ=0;
        DQ=byt&0x01;
        delay(5);//延时约60us
        DQ=1;
        byt>>=1;
    }
    delay(5);
}

单片机读取DS18B20的数据

image-20221028225651606

  • 操作步骤

    • 1、拉低总线1us
    • 2、拉高总线
    • 3、延时大于5us小于15us
    • 4、读取总线的电平(高:1 低: 2)
    • 5、在延时60us内完成一个指令
  • 注意

    • 1、15us内是读时隙
    • 2、读两位数据间要把总线拉高1us(与写一样)
    • 3、先读低位
//单片机读取数据函数
unsigned char rd_ds18b20(void)
{
    unsigned char i;
    unsigned char byt;
    
    for(i=0;i<8;i++)
    {
        DQ=0;
        byt>>=1;	//单片机每执行一个指令约为1us
        DQ=1;
        if(DQ)
        {
            byt|=0x80;
        }
        delay(5);//延时约10us
    }
    return byt;
}

读取温度

image-2022121923200760223

//读取温度函数
float rd_temperature(void)
{
    unsigned int temp;
    float temperature;
    unsigned char low,high;
    
    init_ds18b20();
    wr_ds18b20(0xcc);
    wr_ds18b20(0x44);//启动温度转换
    Delay_OneWire(200);
    
    init_ds18b20();
    wr_ds18b20(0xcc);
    wr_ds18b20(0xbe);//读取寄存器
    
    low=rd_ds18b20();//低字节
    high=rd_ds18b20();//高字节
    /*精度为0.0625摄氏度*/
    temp=high;
    temp<<=8;
    temp|=low;
    temperature=temp*0.0625;
    return temperature;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-sNhv4Elh-1688019066395)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221029183557082.png)]

红色圈处不能返回,如果返回,数码管就会在实际温度和10000(乘10000在主函数内)之间来回闪烁切换,

正确写法

image-20221029183754096

代码需要如下修改:

延时函数增加t要先乘10

然后主程序如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-usmPdRRT-1688019066396)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221219230459780.png)]

IIC协议

AD/DA电路

image-20221105182334846

  • 采用PCF8591芯片
    • 为8位AD/DA
    • 有四路AD采样口:AIN0——AIN3,和一路模拟输出口:AOUT
  • 最左边采样电路通过光敏电阻和普通电阻产生的分压给采样口进行采样
  • 旁边的通过电位器(滑动变阻器)的分压来给AD进行采样
  • 用到的单片机通讯口为P20和P20,只用到了单片机的两个I/O
  • A0、A1、A2用定义IIC的通讯地址,因为单片机总线上只接了一个PCF8591,所以直接接地,让地址为000

EEPROM电路

image-20221105182636869

  • 用到的单片机通讯口同样为为P20和P20,挂载到一个IIC的I/O口上
  • A0、A1、A2用定义IIC的通讯地址,直接接地

IIC协议介绍

总线的启动和停止

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HfEsioNx-1688019066397)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221105183107262.png)]

  • SCL为串行时钟线,SDA为串行数据线

  • 启动:SCL为高电平时,SDA由高电平向低电平变化

  • 停止:SCL为高电平时,SDA由低电平向高电平变化

//总线启动函数
void IIC_Start(void)
{
	SCL=1;
    SDA=1;
    somenop; //>4.7us
    SDA=0;
    somenop; //>4.7us
    SDL=0;
}

//总线停止条件
void IIC_Stop(void)
{
    SDA=0;
    SCL=1;
    somenop; //>4.7us
    SDA=1;
}

等待应答

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dkpCtZl6-1688019066397)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221105213430428.png)]

  • 应答信号
    • 在SCL为高电平时,接受设备将SDA拉为低电平表示传输正确,产生应答
    • 在SCL为高电平时,延时一段时间,SDA仍为高电平,则表示传输失败,产生非应答
bit IIC_WaitAck(void)
{
    SDA=1;
    somenop;
    SCL=1;
    somenop;
    if(SDA)
    {
        SCL=0;
        IIC_Stop();
        return 0;
    }
    else 
    {
        SCL=0;
        return 1;
    }
}

数据发送

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hQLHrZOm-1688019066397)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221105215030925.png)]

  • 先发送高位在发送地位

数据接收

image-20221105215227375

  • 先接收高位再接受地位

PCF8591编程

PCF8591写时序

image-20221105215807813

  • 因为A0-A2接地,所以都为0。
  • PCF8591前四位固定为1001,低地址的前三位为芯片地址,最后一位为读写命令。
    • 0x91为读
    • 0x90为写
void write_adc(unsigned char add)
{
    IIC_Start();	//启动IIC协议
    IIC_SendByte(0x90);	//通过IIC协议发送PCF8591地址和读写命令
    IIC_WaitAck();	//单片机等待PCF8951应答
    IIC_SendByte(add);	//通过IIC发送ADC采样通道控制字,AIN0—AIN3,例如选择通道0,则add=0x00
    IIC_WaitAck();	//等待应答
    IIC_Stop();	//停止总线
}

PCF8591读时序

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fNrKzK6c-1688019066398)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221105221429261.png)]

  • 前半部分先发送写命令给芯片,告诉芯片要读哪个通道
  • 然后后半部分发送读命令给芯片,从芯片中读数据
unsigned char read_adc(unsigned char add)
{
    unsigned char temp;
    IIC_Start();
    IIC_SendByte(0x90);
    IIC_WaitAck();
    IIC_SendByte(add);
    IIC_WaitAck();
    
    IIC_Start();
    IIC_SendByte(0x91);
    IIC_WaitAck();
    temp=IIC_Recbyte();
    IIC_WaitAck();//告诉单片机是否读取成功
    IIC_Stop();
    return temp;
}

1、读取RB2或光敏电压(需记忆)

unsigned char AD_Get(unsigned char add)
{
	unsigned char temp;
	IIC_Start();
	IIC_SendByte(0x90);
	IIC_WaitAck();
	IIC_SendByte(add);
	IIC_WaitAck();
	IIC_Start();
	IIC_SendByte(0x91);
	IIC_WaitAck();
	temp=IIC_RecByte();
	IIC_Stop();
	return temp;
}

单独读取,光敏为0x01,RB2位0x03。

2、DA电压输出

dat取值0—255,对应0—5V。

分辨率=模拟量/2^位数 数字量=分辨率*最大电压量

0x40:DAC输出模式;

电压输出有时会干扰AD读取,其中一种解决方法是在读取之前关中断,然后开中断。

void DAC_out(unsigned char dat)
{
    IIC_Start();
    IIC_SendByte(0x90);
    IIC_WaitAck();
    IIC_SendByte(0x40);
    IIC_WaitAck();
    IIC_SendByte(dat);
    IIC_WaitAck();
    IIC_Stop();
}

3、关于连续读取

如果单独读取,add=1时为光敏,add=3时RB2;若两者都要读,则要交换add的值或者对同一个值读取两次以确保数据正确。

方法1:将读取光敏的地址改为0x03,读取RB2的地址改为0x01。

方法2:地址保持不变,连续读取两次。代码如下:

AD_Get(0x01);
luminance = AD_Get(0x01); //读取光敏
AD_Get(0x01);
humidity = AD_Get(0x01) / 255.0 * 99; //读取RB2

4、电压超出阈值事件检测问题

上电瞬间读出的RB2电压值会有异常,可以多次读取RB2电压值或者在判断时多次验证以确保读取值为稳定的电压。

AT24C02编程

AT24C02写时序

image-20221105222828216

void write_24c02(unsigned char add,unsigned char data1)
{
    IIC_Start();
    IIC_SendByte(0xa0);
    IIC_WaitAck();
    IIC_SendByte(add);	//AT245C02为EEPROM芯片,存储单元地址为0x00-0xFF
    IIC_WaitAck();
    IIC_SendByte(data1);	//往单元格地址里面写数据
    IIC_WaitAck();
    IIC_Stop();
}

AT24C02读时序

image-20221105223900244

  • 前半部分先发送写命令给芯片,告诉芯片要读哪个单元地址内容
  • 然后后半部分发送读命令给芯片,从芯片中读数据
unsigned char read_24c02(unsigned char add)
{
    unsigned char temp;
    IIC_Start();
    IIC_SendByte(0xa0);
    IIC_WaitAck();
    IIC_SendByte(add);
    IIC_WaitAck();
    
    IIC_Start();
    IIC_SendByte(0xa1);
    IIC_WaitAck();
    temp=IIC_SendByte();
    IIC_WaitAck();
    IIC_Stop();
    return temp;
}

定时器对时序的影响

image-20221105230602844

定时器中断有的时候会打断读写数据的时序,所以再读写时序前要关闭中断,读写完再打开。关闭EA即可。方法同样适用于DS18B20实验

超声波模块原理及编程

超声波电路

image-20221108201546769

左边为超声波发射电路,右边为超声波接受电路。

image-20221108201800035

1-3 2-4短接为超声波

5-3 4-6短接为红外发生器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QcsrVVn1-1688019066399)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221108202124583.png)]


超声波发送模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RS46xgBP-1688019066400)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221108210040240.png)]

  • 如果上面一路接受到的是正向方波信号,那么下面一路接收到的就是反向方波信号。其中c7、c8电容起耦合作用。两个反向器并联可以提高超声波发射强度。

  • N_A1接单片机的一个I/O口

超声波发送模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5y1dq0zd-1688019066400)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221108210027328.png)]

  • 放大、限幅、带通滤波、峰值检波、整形和比较等功能通过内部的各种电容和电阻来实现
  • 比较完后,超声波通过N_B1口向单片机发送一个低电平请求中断

超声波测距编程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7rc30yLL-1688019066400)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221108212137716.png)]

左边为超声波测距程序,右边为超声波发送函数

  • 之所以超声波要发送8次脉冲而不是一次,是因为多发几次可以提高超声波被接受到的成功率。TX为P10,当TX=1发送高电平,TX=0,发送低电平
  • somenop为五个_ nop() _,#define somenop _nop_();_nop_();_nop_();_nop_();_nop_();
  • TR1是打开定时器,当发送完脉冲后打开开启计时。当RX==0即P11为0,表明接收成功,如果TF1为0,表明时间未超时
  • distance的单位为cm/s,因为一发一收,所以时间要除以2
  • 这里P0并没用到P2的锁存器,因为只是单纯的使用P0的I/O口,并没使用单片机模块,如蜂鸣器、LED灯等
sbit TX=P10;
sbit RX=P11;
unsigned int distance,time;
#define somenop _nop_();_nop_();_nop_();_nop_();_nop_();
void send_wave(void)
{
    unsigned char i;
    for(i=0;i<8;i++)
    {
        TX=1;
        somenop();somenop();somenop();somenop();somenop();
        TX=0;
        somenop();somenop();somenop();somenop();somenop();
    }
}

void main()
{
    while(1)
    {
        send_wave();
        TR1=0;
        while(RX==1&&TF0==0);
        TR1=1;
        if(TF0==1)
        {
            TF0=0;
            distance=999;
        }else{
            time=TH0;
            time<<=8;
            time+=TL0;
            distance=(unsigned int)(time*0.017);
        }
        TH0=0;
        TL0=0;
    }
}


最好将超声波每隔多长时间发送一次,比如200us,这样可以降低外界别的超声波信号干扰。

SPI协议介绍-DS1302编程

实时时钟电路

image-20221119154535779

  • 第八管脚接外接电池,当开发板断电后还能继续走时
  • P17/SCK为驱动ds1302芯片的I/O
  • P23为进行通信的I/O
  • P13为复位I/O

SPI协议介绍

写数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iJ5tZdv6-1688019066401)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221119154859697.png)]

  • CE为复位I/O,高电平使能有效
  • SCK_SET表示SCLK为高电平,SCK_CLR表示SCLK为低电平

读数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3oHrIIf7-1688019066401)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221119155645547.png)]

  • SDA_R为I/O口管脚

DS1302编程

读数据

image-20221119160002632

写数据

image-20221119160108869

时间编程(需要自己写):

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Vdl7PUQS-1688019066402)(https://gitee.com/MyStarOrbit/cloudimages/raw/master/https://gitee.com/MyStarOrbit/cloudimages/image-20221119160936942.png)]

  • 左边为设置时间函数
  • 右边为读取时间,直接全部在主函数中调用
  • 注意要先设置时间,再读取时间

简单的说,十进制数12的BCD码就是0x12,34的BCD就是0x34,56的BCD就是0x56,随便哪个十进制数的BCD码其实就是把这个十进制数的每一位按位填到十六进制位里,所为高半字节低半字节有毛的区分,BCD就是按单个十六进制位只表示0-9的方式来表达一个按十六进制位看起来和所要表示的十进制数一致的一种编码形式。低半+0x06,高半+0x60,就是这么单纯。

void getTime()
{
	unsigned char bcd;
	bcd = Read_Ds1302_Byte(0x81);//读取秒值
	miao = bcd/16*10 + bcd%16;
	bcd = Read_Ds1302_Byte(0x83);//读取分值
	fen = bcd/16*10 + bcd%16;
	bcd = Read_Ds1302_Byte(0x85);//读取小时值
	shi = bcd/16*10 + bcd%16;
}
void buff_set()
{
	buff[0]=~t_display[shi/10];
	buff[1]=~t_display[shi%10];
	buff[2]=~0x40;
	buff[3]=~t_display[fen/10];
	buff[4]=~t_display[fen%10];
	buff[5]=~0x40;
	buff[6]=~t_display[miao/10];
	buff[7]=~t_display[miao%10];
}

void set_time(u8 shi,u8 fen,u8 miao)
{
	Write_Ds1302_Byte(0x8e,0);
	Write_Ds1302_Byte(0x80,miao/10*16+miao%10);
	Write_Ds1302_Byte(0x82,fen/10*16+fen%10);
	Write_Ds1302_Byte(0x84,shi/10*16+shi%10);
	Write_Ds1302_Byte(0x8e,0x80);
}

串口通信

注意键盘扫描和串口通信最好别一起用,非要一起用则再键盘扫描中必须避免使用P30和P31,因为在串口通信中P30为RXD,P31为TXD

#include "stc15.h"
#include "onewire.h"
#include <stdio.h>
typedef unsigned int u16;
typedef unsigned char u8;

bit busy,temp_flag;
u8 RI_cnt;
u8 RI_buffer[10];
u8 recv;
float temp;
u8 str[30]={0};
void Timer1Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x40;		//定时器时钟1T模式
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0xCD;		//设置定时初值
	TH1 = 0xD4;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
	ET1=1;
}

void UartInit(void)		//2400bps@11.0592MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x01;		//串口1选择定时器2为波特率发生器
	AUXR |= 0x04;		//定时器2时钟为Fosc,即1T
	T2L = 0x80;		//设定定时初值
	T2H = 0xFB;		//设定定时初值
	AUXR |= 0x10;		//启动定时器2
	EA=1;
	ES=1;
}

void init()
{
	P2=0xa0;P0=0x00;
	P2=0x80;P0=0xff;
}

void PrintString(u8 *str)
{
	for(;*str!='\0';*(str++))
	{
		SBUF=*str;
		busy=1;
		while(busy);
	}
}
void main()
{
	init();
	UartInit();
	PrintString("hello");
	while(rd_temperature()==85);
	Timer1Init();
	while(1)
	{
		temp=rd_temperature();
		sprintf(str,"%s%6.3f%c%c","temperature:",temp,'\r','\n');
		if(temp_flag)
		{
			temp_flag=0;
			PrintString(str);
		}
	}
}

void time1() interrupt 3
{
	static u16 count;
	if(++count==500)
	{
		temp_flag=1;
		count=0;
	}
	if(recv!=0){	
	P2=0x80;P0=~(0x01<<(recv-48-1));
	}
else 
	{
		P2=0x80;P0=0xff;
	}
}
void Uart2() interrupt 4 using 1
{
	if(RI)
	{
		RI=0;
		recv=SBUF;
	}
	if(TI)
	{
		TI=0;
		busy=0;
	}
}

iic——24C02补充

1)EEPROM 一个单元只能存储8位数据,注意溢出。
//存一个大于255的数
uint num=23333,read_num;
write_24C02(1,num/256);
Delay10ms();//如果出错就加延时
write_24C02(2,num%256);
//读取
read_num=read_24C02(1)*256;
Delay10ms();//如果出错就加延时
read_num+=read_24C02(2)
2)读写EEPROM(需要记忆)
void write_24C02(unsigned char add,unsigned char dat)
{
	IIC_Start();
	IIC_SendByte(0xa0);
	IIC_WaitAck();
	IIC_SendByte(add);
	IIC_WaitAck();
	IIC_SendByte(dat);
	IIC_WaitAck();
	IIC_Stop();
}

unsigned char read_24C02(unsigned char add)
{
	unsigned char temp;
	IIC_Start();
	IIC_SendByte(0xa0);
	IIC_WaitAck();
	IIC_SendByte(add);
	IIC_WaitAck();
	
	IIC_Start();
	IIC_SendByte(0xa1);
	IIC_WaitAck();
	temp=IIC_RecByte();
	IIC_Stop();
	return temp;
}
3)数据读取设置

因为IAP15的速度远快于外设,读取EEPROM的数据过快会产生错误。所以尽量不要在while(1)里持续循环读取存储器的内容,可以设置一个 read_flag 用于标记是否可读取外设,大约每100ms读取一次,关键代码如下:

void time1() interrupt 3 //1ms
{
    static int read_count;
    ...
    if(++read_count==100)
    {
        read_count=0;
        read_flag=1; //每100ms将read_flag置1
	}
	...
}

void main()
{
    ...
    while(1)
    {
        if(read_flag)//读EEPROM的存储值
        {
            read_flag=0;
            read_24C02(*,*;
        }
    }
    ...
}
4)数据存储设置

原理同上,写EEPROM存储器时,需要增加间隔时间,连续存储EEPROM时,应在数据存储函数后插入Delay10ms(),防止下一次的操作打断上一次的操作,产生未知错误。

如果仍然无法正确存储,则需要在调用读写数据函数之前要关中断即:EA=0; 读写完数据之后再开中断EA=1。

5)连续读取数据的代码实现方法
//第一种写法,优先选择。
for(i=0;i<6;i++)//读地址1-6,的数据存放在数组mm_temp_init数组中,记录上次断电时的密码。
{
	mm_temp_init[i]=read_24c02(i+1);
	Delay10ms();
}
//第一种写法失效时,第二种
for(i=0;i<6;i++)
{
    EA=0;
	mm_temp_init[i]=read_24c02(i+1);
    EA=1Delay10ms();
}
//第二种失效,就第三种,这种就是把延时放在关中断里,关中断的时间更长一些
//可能会影响数码管显示,但这是初始化工作,放在while(1)的前面,只会执行一次。
for(i=0;i<6;i++)
{
    EA=0;
	mm_temp_init[i]=read_24c02(i+1);
	Delay10ms();
    EA=1}
6)连续存储数据的代码实现方法

放在key_function()里,当有确定功能的按键按下时会触发功能,连续写一组数。

在连续采集的时候,会要储存最近几组采集数据。但采集有时间间隔,不算连续写。一般写操作不会出问题。

//同读操作的代码一样,优先第一种,只需要把读24c02的语句改成写操作的
//这里是示范第二种写法,把m[]数组中的数,写到24c02的一段连续空间内
for(k=0;k<6;k++)
{
	EA=0;
	write_24c02(k,mm[k]);
	EA=1;Delay20ms();
}

如果存取仍有问题,可以试试将数据间隔存储,再读取。

//存储
for(k=0;k<6;k++)
{
	EA=0;
	write_24c02(2k,mm[k]);
	EA=1;Delay20ms();
}
//读取
for(k=0;k<6;k++)
{
	EA=0;
	read=read_24c02(2k);
	EA=1;Delay20ms();
}

长按

#include <stc15.h>
typedef unsigned int u16;
typedef unsigned char u8;

u8 code t_display[]={                       //标准字库
//   0    1    2    3    4    5    6    7    8    9    A    B    C    D    E    F
    0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71,
//black  -     H    J    K    L    N    o   P    U     t    G    Q    r   M    y
    0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e,
    0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46};    //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1

u8 code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};      //位码

bit key_flag,s4_press,s4_long_press;
u8 Cont,Trg;
u16 num;
u8 table[8];
void buff_set()
{
	table[0]=16;
	table[1]=16;
	table[2]=16;
	table[3]=16;
	table[4]=16;
	table[5]=num>=100?num/100%10:16;
	table[6]=num>=10?num/10%10:16;
	table[7]=num%10;
}
void Timer0Init(void)		//1毫秒@11.0592MHz
{
	AUXR |= 0x80;		//定时器时钟1T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xCD;		//设置定时初值
	TH0 = 0xD4;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 1;		//定时器0开始计时
	ET0=1;
	EA=1;
}

void init()
{
	P2=0x80;P0=0xff;
	P2=0xa0;P0=0x00;
}

void Readkey()
{
	u8 ReadData,key_press;
	P3=0xf0;
	P42=1;
	P44=1;
	P36=P42;
	P37=P44;
	key_press=P3;
	P3=0x0f;
	P42=0;
	P44=0;
	ReadData=0xff^(P3|key_press);
	Trg=ReadData&(ReadData^Cont);
	Cont=ReadData;
}

void main()
{
	init();
	Timer0Init();
	while(1)
	{
		buff_set();
		if(key_flag)
		{
			key_flag=0;
			Readkey();
			if(Trg==0x88)
			{
				s4_press=1;;
				if(++num>100)
					num=0;
			}
			if(Cont==0)
			{
				s4_press=0;;
			}
		}
	}
}

void time0() interrupt 1
{
	static u16 i,smg_count,key_count,s4_count,long_press_count;
	if(++key_count==100)
	{
		key_flag=1;
		key_count=0;
	}
	if(++smg_count%2==0)
	{
		smg_count=0;
		P2=0xc0;P0=0x00;
		P2=0xe0;P0=~t_display[table[i]];
		P2=0xc0;P0=T_COM[i];
		if(++i==8)
		i=0;
	}
	if(s4_press)
	{
		if(s4_count<1000)s4_count++;
		if(s4_count==1000)
		{
			s4_long_press=1;
		}
		if(s4_long_press)
		{
			long_press_count++;
			if(long_press_count==100)
			{
				long_press_count=0;
				if(++num>100)
				num=0;
			}
		}
	}
	else
	{
		s4_long_press=0;
		s4_count=0;
	}
}

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

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

相关文章

95道MongoDB面试题

1、mongodb是什么&#xff1f; MongoDB 是由 C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。 再高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。 MongoDB 旨在给 WEB 应用提供可扩展的高性能数据存储解决方案。 MongoDB 将数…

前端技术栈 - ES6 - Promise -模块化编程

文章目录 &#x1f55b;ES6⚡let变量⚡const常量⚡数组解构⚡对象解构⚡模板字符串⚡对象声明简写⚡对象方法简写⚡对象运算符扩展⚡箭头函数&#x1f98a;传统函数>箭头函数&#x1f98a;箭头函数对象解构 ⚡作业布置 &#x1f550;Promise⚡需求分析⚡传统ajax回调嵌套⚡p…

GO、KEGG(批量分组)分析及可视化

这篇帖子其实是更新、补充、解决一下问题的。我们号写过很多GO、KEGG富集分析的可视化&#xff0c;数不胜数&#xff0c;可以在公众号检索“富集”了解更多。我们演示的时候都是直接提供了富集的结果文件&#xff0c;一般演示为了图方便&#xff0c;也是利用在线工具cytoscape做…

网络工程师的副业,能有多野?

大家好&#xff0c;我是许公子。 在网工这行做了这么久&#xff0c;经常会有同事或者朋友来问我&#xff0c;有没有什么搞副业的路子。 别说&#xff0c;选择还真挺多。 前两天的文章里&#xff0c;这位朋友就秀了一波&#xff0c;这工作速度、便捷程度、收款金额&#xff0c…

Java并发编程-线程基础

哈喽,大家好,这篇文章主要讲解Java并发编程,线程基础的知识点,讲解知识点的同时也会穿插面试题的讲解. 主要讲解以下内容 进程,线程基础常见的一些区别比较如何使用和查看线程理解线程如何运行以及线程上下文切换的知识线程方法线程状态 希望能给大家带来帮助.加油~~~ 目录 进…

自学黑客(网络安全),一般人我劝你还是算了吧(自学网络安全学习路线--第十九章 口令破解与防御技术下)【建议收藏】

文章目录 一、自学网络安全学习的误区和陷阱二、学习网络安全的一些前期准备三、自学网络安全学习路线一、口令攻击的综合应用1、Windows NT, 2000口令攻击2、Windows XP, 2003口令攻击3、Unix系统口令攻击4、远程口令攻击 二、口令攻击的防御1、口令攻击防御概述2、保持口令的…

RS485转Profinet通讯

RS485转Profinet通讯 概述系统组成流量积算仪网关 软件总结 概述 一个支持RS485的流量积算仪的数据要被Profinet的PLC读取。制作一个网关&#xff0c;实现RS485到Profinet的转换。 系统组成 流量积算仪 支持RS485通讯&#xff0c;通讯协议是modbus RTU。采用功能码3可以读取…

非GUI模式下如何传参

非GUI模式下如何传参 Jmeter 有两种模式&#xff0c;GUI模式和非GUI模式&#xff0c;通常使用GUI模式编辑脚本&#xff0c;使用非GUI模式运行压测&#xff0c;官网上有强调&#xff0c;尽量使用非GUI模式&#xff0c;因为GUI模式下&#xff0c;jmeter UI组件本身在压测过程中会…

论文浅尝 | SimKGC:基于预训练语言模型的简单对比知识图谱补全

笔记整理&#xff1a;李雅新&#xff0c;天津大学硕士&#xff0c;研究方向为知识图谱补全 链接&#xff1a;https://dl.acm.org/doi/10.1145/3539597.3570483 动机 知识图谱补全 (KGC) 旨在对已知事实进行推理并推断缺失的链接。基于文本的方法从自然语言描述中学习实体表示&a…

Spring Boot 中的 @EnableConfigurationProperties 注解

Spring Boot 中的 EnableConfigurationProperties 注解 在 Spring Boot 中&#xff0c;EnableConfigurationProperties 注解是一个非常有用的注解&#xff0c;它可以用于启用对特定配置类的支持。在本文中&#xff0c;我们将深入探讨 EnableConfigurationProperties 注解&…

ST CubeMX 实现6对PWM同步输出/互补输出/三相PWM输出

频率为1KHz的6对PWM波形 原理:定时器1为主模式,定时器8为从模式,TIM1的定时器使能操作作为触发输出[TRGO]触发TIM8并使能TIM8的计数器,同时输出两路频率、占空比以及脉冲数量(小于256个,高级定时器重复计数功能为8位)可调PWM波形。 Tim1 参数配置 Tim8参数配置 未同步输出…

CC2530 GPIO口输出配置说明

第一章 原理图分析 CC2530核心板上带有两颗晶振:第一颗频率为32MHZ,第二颗频率为32.768KHZ CC250正常运行的时候,需要一个高频的时钟信号和一个低频的时钟信号。 高频时钟信号,主要供给CPU,保证程序的运行。 低频时钟信号,主要供给看门狗、睡眠定时器等片上外设。 按…

【强化学习】常用算法之一 “TRPO”

作者主页&#xff1a;爱笑的男孩。的博客_CSDN博客-深度学习,活动,python领域博主爱笑的男孩。擅长深度学习,活动,python,等方面的知识,爱笑的男孩。关注算法,python,计算机视觉,图像处理,深度学习,pytorch,神经网络,opencv领域.https://blog.csdn.net/Code_and516?typeblog个…

StorageGRID——开放式的 S3 对象存储,可大规模管理非结构化数据

StorageGRID——开放式的 S3 对象存储&#xff0c;可大规模管理您的非结构化数据 专为混合多云体验打造的对象存储 StorageGRID 通过简化的平台为对象数据提供更强大的数据管理智能。由于 StorageGRID 利用 S3&#xff0c;因此可以轻松地连接混合云工作流&#xff0c;提供流畅…

C++ - 20230628

一. 思维导图 二. 练习 1) 总结类和结构体的区别 本身的访问级别不同struct是值类型&#xff0c;class是引用类型struct在栈&#xff0c;适合处理小型数据。class在堆区&#xff0c;适合处理大型逻辑和数据。 2) 定义一个矩形类&#xff08;Rectangle&#xff09;&#xff…

基于Java+SpringBoot+vue的高校学生党员发展管理系统设计与实现

博主介绍&#xff1a;✌擅长Java、微信小程序、Python、Android等&#xff0c;专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;&#x1f3fb; 不然下次找不到哟 Java项目精品实战案…

我是怎么把win11一步一步变成Mac的

目录 【三指拖动】 【空格预览】 【切换Ctrl和Alt】 【使用Linux命令】 【其它】 之前很长一段时间在MacBook上面开发习惯了&#xff0c;然后因为一些原因现在换到了windows上面&#xff0c;不管是使用上还是系统上都很不习惯&#xff0c;因此做了一些改造&#xff0c;…

LTD232次升级 | 社区新增PC版首页 • 名片新增卡片样式、可展示传真 • 导航数据可官微中心管理 • 个人中心可定制

1、社区支持PC版首页 2、名片小程序新增一种全局卡片样式、支持显示传真 3、官微中心新增导航管理 4、手机版商城个人中心支持版块配置 5、新增一组新闻轮播模块 01 用户社区应用 1) 新增PC版社区首页 在本次升级中&#xff0c;我们为用户社区应用新增了PC版的首页。 开…

【探索 Kubernetes|作业管理篇 系列 15】DaemonSet 的”过人之处“

前言 大家好&#xff0c;我是秋意零。 在上一篇中&#xff0c;我们讲解了 StatefulSet 的存储状态&#xff1b;我们发现&#xff0c;它的存储状态&#xff0c;就是利用了 PV 与 PVC 的设计。StatefulSet 自动为我们创建 PVC 并且以 <pvc-name>-<pod-name>-<编…

selenium模拟!看这篇就够了

介绍 Selenium是一个用于自动化Web浏览器测试的开源工具&#xff0c;它支持多种Web浏览器&#xff08;如Google Chrome、Firefox、Safari等&#xff09;和操作系统&#xff08;如Windows、Mac和Linux&#xff09;。Selenium可以模拟用户在Web浏览器中的行为&#xff0c;例如点…