蓝桥杯单片机总结(2022年国一)


前言

提示:代码全部参考官方指导书,在其基础上修改优化
指导书:
在这里插入图片描述

电子版指导书链接:
版本较老,建议购买最新版本(淘宝:国信长天科技)


提示:以下是本篇文章正文内容,下面案例可供参考

一、my代码模块

新建工程,文件夹,导入驱动文件,写自己的c.h文件
在这里插入图片描述
在这里插入图片描述

1. main.c

#include "initial.h"
#include "onewire.h"
#include "ds1302.h"
#include "iic.h"
#include <stdio.h>//sprintf函数

void key_proc();
void seg_proc();
void led_proc();
void uart_proc();

u16 wendu_val;
u8 time[3]={23,59,50};
u8 adc_val;
u8 write_buf[3]={0,0,0};
u8 read_buf[3]={0,0,0};
u8 dist_val;
u8 RX_buf[12],RX_num,pdata TX_buf[12];//超出data区(110),用pdata(xdata前一小段)

u8 key_dly,seg_dly;
unsigned long ms,key_time;
u8 led=0xff;
u8 seg_pos,seg_buf[10],seg_code[8]={0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff};
				//一个小数点,结束符
u8 key_old;
u8 state;
bit key_long;//长按标志位

u8 key_cnt;
u8 key_down_old;

void main()
{
	initial_sys();
	
	do
		wendu_val=rd_temperature()>>4;
	while(wendu_val==85);//读掉初始的85°
	set_rtc(time);
	do
		adc_val=pcf8591_adc();
	while(adc_val==128);//读掉初始的2.51V(128)
	eeprom_read(read_buf,0x00,3);//掉电读取
	
	Timer1Init();//前面初始化完再显示正确数值
	Timer0Init();
	UartInit();
	ET1=1;
	ES=1;
	EA=1;
	
	while(1)
	{
		key_proc();
		seg_proc();
		led_proc();
		uart_proc();		
	}
}

void Timer1(void) interrupt 3//主中断
{
	ms++;
	if(++key_dly==10)
		key_dly=0;
	if(++seg_dly==200)
		seg_dly=0;
	
	led_disp(led);
	seg_disp(seg_pos,seg_code);
	if(++seg_pos==8)
		seg_pos=0;
}
void Uart0(void) interrupt 4//串口接收用中断,发送用查询
{
	if(RI)
	{
		RX_buf[RX_num++]=SBUF;
		RI=0;
	}
}

void key_proc()
{
	u8 key_val,key_down,key_up;
	
	if(key_dly)
		return;
	key_dly=1;
	
	key_val=key_scan();//三段式按键扫描
	key_down=key_val&(key_val^key_old);//^异或
	key_up=~key_val&(key_val^key_old);
	key_old=key_val;
	
//	if(key_down)//普通单击短按和长按
//	{
//		key_time=ms;
//		key_long=0;
//	}
//	if(ms-key_time<=1000)//短按
//	{
//		switch(key_up)
//		{
//			case 0:break;
//			case 4:
//				if(++state==5)
//					state=0;
//				break;
//			case 5:
//				break;
//			case 8:
//				break;
//			case 9:
//				break;
//		}
//	}
//	else//长按1s以上
//	{
//		if(!key_long)
//		{
//			key_long=1;//此if内容只执行一次
//			switch(key_old)
//			{
//				case 8:
//					break;
//			}
//		}
//	}
	
	if(key_down)//单击短按,双击或多击短按(以前没考过)和长按
	{
		key_cnt++;
		if(key_cnt==1)
		{
			key_time=ms;
			key_long=0;
		}
		key_down_old=key_down;
	}
	if(ms-key_time>300)//超过300ms判断单击还是双击
	{
		if(key_cnt==1)//单击。按键只按下一次
		{
			switch(key_down_old)
			{
				case 4:
					if(state==0)
						state=4;
					else
						state--;
				  break;
			}
		}
		else if(key_cnt>1)//双击以上
		{
			switch(key_down_old)
			{
				case 4:
					if(++state==5)
						state=0;
				  break;
			}
		}
		key_cnt=0;
	}
	if(ms-key_time>1000)//另外按键长按1s以上
	{
		if(!key_long)
		{
			key_long=1;
			switch(key_old)
			{
				case 5:
					state+=2;
					if(state>4)
						state=0;
				  break;
			}
		}
	}
	
}
void seg_proc()
{
	if(seg_dly)
		return;
	seg_dly=1;
	
	wendu_val=rd_temperature();
	dist_val=wave();	
	
	switch(state)
	{
		case 0:
			sprintf(seg_buf,"1   %05.2f",(float)wendu_val/16.0);
			break;
		case 1:
			read_rtc(time);
			sprintf(seg_buf,"2 %02u%02u%02u",(u16)time[0],(u16)time[1],(u16)time[2]);
			break;
		case 2:
			adc_val=pcf8591_adc();
			pcf8591_dac(adc_val);
			sprintf(seg_buf,"3    %4.2f",(float)adc_val/51.0);
			break;
		case 3:
			sprintf(seg_buf,"4 %2u%2u%2u",(u16)read_buf[0],(u16)read_buf[1],(u16)read_buf[2]);
			break;
		case 4:
			sprintf(seg_buf,"5    %03u",(u16)dist_val);
			break;	
	}
	seg_tran(seg_buf,seg_code);
}
void led_proc()
{
	
}
void uart_proc()//10届国赛
{
	if(RX_num)//触发完RI中断
	{
		if(RX_buf[RX_num-1]=='\n')//正常接收
		{
			if(RX_buf[0]=='S' && RX_buf[1]=='T' && RX_buf[2]=='\r')//接收到ST/r/n
				sprintf(TX_buf,"$%02u,%05.2f\r\n",(u16)dist_val,(float)wendu_val/16.0);
			else if(RX_buf[0]=='P' && RX_buf[1]=='A'  && RX_buf[2]=='R' && RX_buf[3]=='A' && RX_buf[4]=='\r')
				sprintf(TX_buf,"#%02u,%2u\r\n",(u16)dist_val,(u16)wendu_val>>4);
			else
				sprintf(TX_buf,"ERROR1\r\n");
			
			uart_send(TX_buf);
			RX_num=0;
		}
		else//接收到错误指令
		{
			if(RX_num == 6)//收到六个字符,第六个字符不为\n
			{
				sprintf(TX_buf,"ERROR2\r\n");
				uart_send(TX_buf);
				RX_num = 0;
			}
		}
	}
}

2. initial.c

包括点灯,系统初始化,数码管,按键(矩阵4x4,2x2,独立),超声波,串口

#include "initial.h"

sbit TX=P1^0;
sbit RX=P1^1;

void led_disp(u8 led)
{
	P0=led;
	P2=P2&0x1f|0x80;
	P2&=0x1f;
}
void initial_sys()
{
	P0=0xff;
	P2=P2&0x1f|0x80;
	P2&=0x1f;
	
	P0=0x00;
	P2=P2&0x1f|0xa0;
	P2&=0x1f;
}
void seg_tran(u8* seg_buf,u8* seg_code)
{
	u8 i,j=0,temp;
	for(i=0;i<8;i++,j++)
	{
		switch(seg_buf[j])
		{
			case '0':temp=0xC0;break;
			case '1':temp=0xF9;break;
			case '2':temp=0xA4;break;
			case '3':temp=0xB0;break;
			case '4':temp=0x99;break;
			case '5':temp=0x92;break;
			case '6':temp=0x82;break;
			case '7':temp=0xF8;break;
			case '8':temp=0x80;break;
			case '9':temp=0x90;break;
			case ' ':temp=0xff;break;
			default:temp=0xff;
		}
		if(seg_buf[j+1]=='.')
		{
			temp&=0x7f;
			j++;
		}
		seg_code[i]=temp;
	}
}
void seg_disp(u8 seg_pos,u8* seg_code)
{
	P0=0xff;//消隐
	P2=P2&0x1f|0xe0;
	P2&=0x1f;
	
	P0=1<<seg_pos;
	P2=P2&0x1f|0xc0;
	P2&=0x1f;
	
	P0=seg_code[seg_pos];
	P2=P2&0x1f|0xe0;
	P2&=0x1f;	
}
//u8 key_scan()//4*4
//{
//	u16 k;u8 key_val;
//	P44=0;P42=1;P35=1;P34=1;
//	k=P3;
//	P44=1;P42=0;
//	k=(k<<4)|(P3&0x0f);
//	P42=1;P35=0;
//	k=(k<<4)|(P3&0x0f);
//	P35=1;P34=0;
//	k=(k<<4)|(P3&0x0f);
//	switch(~k)
//	{
//		case 0x8000:key_val=4;break;
//		case 0x4000:key_val=5;break;
//		case 0x2000:key_val=6;break;
//		case 0x1000:key_val=7;break;
//		case 0x0800:key_val=8;break;
//		case 0x0400:key_val=9;break;
//		case 0x0200:key_val=10;break;
//		case 0x0100:key_val=11;break;
//		case 0x0080:key_val=12;break;
//		case 0x0040:key_val=13;break;
//		case 0x0020:key_val=14;break;
//		case 0x0010:key_val=15;break;
//		case 0x0008:key_val=16;break;
//		case 0x0004:key_val=17;break;
//		case 0x0002:key_val=18;break;
//		case 0x0001:key_val=19;break;
//		default:key_val=0;
//	}
//	return key_val;
//}
u8 key_scan()//2*2
{
	u8 k;u8 key_val;
	P44=0;P42=1;
	k=P3;
	P44=1;P42=0;
	k=(k<<4)|(P3&0x0f);
	switch(~k)
	{
		case 0x80:key_val=4;break;
		case 0x40:key_val=5;break;
		case 0x08:key_val=8;break;
		case 0x04:key_val=9;break;
		default:key_val=0;
	}
	return key_val;
}
//u8 key_scan()//独立按键
//{
//	if(P33==0)
//		return(4);
//	else if(P32==0)
//		return(5);
//	else if(P31==0)
//		return(6);
//	else if(P30==0)
//		return(7);
//	else
//		return(0);
//}

void Timer1Init(void)		//1毫秒@12.000MHz
{
	AUXR &= 0xBF;		//定时器时钟12T模式
	TMOD &= 0x0F;		//设置定时器模式
	TL1 = 0x18;		//设置定时初值
	TH1 = 0xFC;		//设置定时初值
	TF1 = 0;		//清除TF1标志
	TR1 = 1;		//定时器1开始计时
}

void Timer0Init(void)		//12微秒@12.000MHz
{
	AUXR &= 0x7F;		//定时器时钟12T模式
	TMOD &= 0xF0;		//设置定时器模式
	TL0 = 0xF4;		//设置定时初值
	TH0 = 0xFF;		//设置定时初值
	TF0 = 0;		//清除TF0标志
	TR0 = 0;		//定时器0停止计时
}
u8 wave()//超声波测距
{
	u8 dist,num=10;//1个方波测不了,2个方波只能110cm,3个可以180cm...
			//发的越少,越远越难接收到,5个方波基本能测满255cm,实际测量也最准
	TX=0;
	
	TL0 = 0xF4;		//设置定时初值
	TH0 = 0xFF;		
	TR0 = 1;		//定时器0开始计时,发送40khz方波
	while(num--)
	{
		while(!TF0);//等待溢出标志置1,中断方式能硬件清零,查询需手动清零
		TX^=1;
		TF0=0;
	}
	TR0 = 0;//定时器0停止,发送完毕,等待接收
	
	TL0 = 0x00;	
	TH0 = 0x00;	
	TR0 = 1;//定时器0开始计时
	while(RX && !TF0);//RX=0收到脉冲,或定时器溢出,超过65535us还没接收到
	TR0=0;
	if(TF0)//若溢出
	{
		TF0=0;
		dist=255;
	}
	else
		dist=((TH0<<8)+TL0)*0.017;//时间*速度(来回)
	
	return dist;
} 

void UartInit(void)		//4800bps@12.000MHz
{
	SCON = 0x50;		//8位数据,可变波特率
	AUXR |= 0x01;		//串口1选择定时器2为波特率发生器
	AUXR |= 0x04;		//定时器2时钟为Fosc,即1T
	T2L = 0x8F;		//设定定时初值
	T2H = 0xFD;		//设定定时初值
	AUXR |= 0x10;		//启动定时器2
}
void uart_send(u8* str)//串口发送用查询,接收用中断
{
	while(*str != '\0')
	{
		SBUF=*str;
		while(TI==0);
		TI=0;
		str++;
	}
}

3. onewire.c

包括温度传感器ds18b20

#include "onewire.h"
 
//
void Delay_OneWire(unsigned int t)  
{
	u8 i;
	while(t--)
	{
		for(i=0;i<12;i++);//8~12倍皆可
	}
}

//
void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
//	EA=0;//如果ds18b20数据显示发生跳变等情况,极有可能是通信过程被打断了,通信过程中可提前关闭中断
	for(i=0;i<8;i++)
	{
		DQ = 0;
		DQ = dat&0x01;
		Delay_OneWire(5);
		DQ = 1;
		dat >>= 1;
	}
//	EA=1;//通信完毕再开启中断。(非必要情况不写,会影响中断)
	Delay_OneWire(5);
}

//
unsigned char Read_DS18B20(void)
{
	unsigned char i;
	unsigned char dat;
//	EA=0;  
	for(i=0;i<8;i++)
	{
		DQ = 0;
		dat >>= 1;
		DQ = 1;
		if(DQ)
		{
			dat |= 0x80;
		}	    
		Delay_OneWire(5);
	}
//	EA=1;
	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 rd_temperature(void)
{
	u8 low,high;
	
	init_ds18b20();
	Write_DS18B20(0xCC);
	Write_DS18B20(0x44);
	
	init_ds18b20();
	Write_DS18B20(0xCC);
	Write_DS18B20(0xBE);
	
	low=Read_DS18B20();
	high=Read_DS18B20();
	
	return (high<<8)+low;
		
}

4. ds1302.c

包括时钟模块ds18b20
部分代码:(必要在通信过程前关中断,通信完再开中断,同上)

void set_rtc(u8* time)
{
	u8 temp;
	
	Write_Ds1302_Byte(0x8E,0x00);
	
	temp=((time[0]/10)<<4)+time[0]%10;//时
	Write_Ds1302_Byte(0x84,temp);
	temp=((time[1]/10)<<4)+time[1]%10;//分
	Write_Ds1302_Byte(0x82,temp);	
	temp=((time[2]/10)<<4)+time[2]%10;//秒
	Write_Ds1302_Byte(0x80,temp);	

	Write_Ds1302_Byte(0x8E,0x80);	
	
}
void read_rtc(u8* time)
{
	u8 temp;
	
	temp=Read_Ds1302_Byte(0x85);
	time[0]=(temp>>4)*10+(temp&0x0f);//时
	temp=Read_Ds1302_Byte(0x83);
	time[1]=(temp>>4)*10+(temp&0x0f);//分
	temp=Read_Ds1302_Byte(0x81);
	time[2]=(temp>>4)*10+(temp&0x0f);//秒
	
}

5. iic.c

包括AD模块pcf8591,eeprom模块AT24C02
部分代码:

u8 pcf8591_adc()
{
	u8 temp;
	
	IIC_Start();
	IIC_SendByte(0x90);//主机向从机发送写指令
	IIC_WaitAck();//slave设备正常接收数据后,自动将SDA置0
	
	IIC_SendByte(0x43);//dac开启时43,dac未开启03。允许DAC,ADC通道3
	IIC_WaitAck();
	
	IIC_Start();
	IIC_SendByte(0x91);//主机向从机发送读指令
	IIC_WaitAck();
	
	temp=IIC_RecByte();
	IIC_SendAck(1);//结束通信,不想应答(从设备),(不想接受从设备发来的信息)
	IIC_Stop();
	
	return temp;
}
void pcf8591_dac(u8 dat)
{
	IIC_Start();
	IIC_SendByte(0x90);
	IIC_WaitAck();
	
	IIC_SendByte(0x43);//允许DAC,ADC通道3
	IIC_WaitAck();
	
	IIC_SendByte(dat);
	IIC_WaitAck();
	IIC_Stop();
}
void eeprom_write(u8* write_buf,u8 addr,u8 num)
{
	IIC_Start();
	IIC_SendByte(0xa0);
	IIC_WaitAck();
	
	IIC_SendByte(addr);
	IIC_WaitAck();
	
	while(num--)
	{
		IIC_SendByte(*write_buf++);
		IIC_WaitAck();
		IIC_Delay(200);
	}
	IIC_Stop();
}
void eeprom_read(u8* read_buf,u8 addr,u8 num)
{
	IIC_Start();
	IIC_SendByte(0xa0);
	IIC_WaitAck();
	
	IIC_SendByte(addr);
	IIC_WaitAck();
	
	IIC_Start();
	IIC_SendByte(0xa1);
	IIC_WaitAck();
	
	while(num--)
	{
		*read_buf++=IIC_RecByte();
		if(num)
			IIC_SendAck(0);
		else
			IIC_SendAck(1);
	}
	IIC_Stop();
}

二、注意点

1. sprintf

1.1 函数:sprintf

  • 头文件:<stdio.h>
  • 函数原型: int sprintf(char *str, char *farmat [,argument,…]);
  • 功 能: 格式化输出到字符串中
  • 参数: char *str 要输出的字符串
        char *farmat [,argument,…] 要输入的格式
  • 返回值: 返回字符串的字节数

程序例: 格式化输出到字符串中,并输出字符串

#include<stdio.h>
#include<math.h>
int main(void){
   char buffer[80];
   sprintf(buffer, "An approximation of Pi is %f", M_PI);
   puts(buffer);
   return 0;
}

运行结果:
An approximation of Pi is 3.141593

1.2 printf输出控制符

  • %hd用来输出 short int 类型,hd 是 short decimal 的简写;
  • %d用来输出 int 类型,d 是 decimal 的简写;
  • %ld用来输出 long int 类型,ld 是 long decimal 的简写。
  • %c:输出一个字符。c 是 character 的简写。
  • %s:输出一个字符串。s 是 string 的简写。
  • %f:输出一个小数。f 是 float 的简写。

在输出整数方面, 格式控制符和整数的符号是紧密相关的,具体就是:

  • %d 以十进制形式输出有符号数;
  • %u 以十进制形式输出无符号数;
  • %o 以八进制形式输出无符号数;
  • %x 以十六进制形式输出无符号数。
  • printf函数并不支持“输出负的八进制或者十六进制数”。

在这里插入图片描述
小数的输出格式:

  • %f 以十进制形式输出float类型;
  • %lf 以十进制形式输出double类型;
  • %e 以指数形式输出float类型,输出结果中的 e 小写;
  • %E 以指数形式输出float类型,输出结果中的 E 大写;
  • %le 以指数形式输出double类型,输出结果中的 e 小写;
  • %lE 以指数形式输出double类型,输出结果中的 E 大写。

1.3 printf的格式控制的完整格式

% - 0 m.n l或h 格式字符

  • %:表示格式说明的起始符号,不可缺少。
  • -:有-表示左对齐输出,如省略表示右对齐输出。
  • 0:有0表示指定空位填0,如省略表示指定空位不填。
  • m.n:m指域宽,即对应的输出项在输出设备上所占的字符数(小数点也算一个字符数。若实际数值比m大,按实际数值显示,比如28.4567,%3.1,实际显示的是28.4)。n指精度,用于说明输出的实型数的小数位数。为指定n时,隐含的精度为n=6位。
  • l或h:l对整型指long型,对实型指double型。h用于将整型的格式字符修正为short型。
  • 格式字符为上述d,u,f等

1.4 seg_buf[10]

8位数码管,seg_buf至少为seg_buf[9],因为字符串要以’\0’ 作为结束符。若有一个小数点,应为seg_buf[10],依次类推。
C系统在用字符数组存储字符串常量时会自动加一个’\0’作为结束符。例如“C program”共有9个字符。字符串是存放在一维数组中的,在数组中他占10个字节,最后一个字节‘\0’是系统自动加上的。

char c[ ] = {"I am happy"}; = char c[ ] = " I am happy"; = 	char c[ ] = {'I',' ','a','m',' ','h','a','p','p','y','\0'};

char c[10] = {"China"};
数组的前5个元素为:'C','h','i','n','a',6个元素为'\0',后4个元素也设定为空字符‘\0'。

sprintf(seg_buf,"1   %05.2f",(float)wendu_val/16.0);
结果:seg_buf为字符串,第一个字符为1,第234字符为空格,第56789字符为25.3209.45(低于10℃,小数点一位,小数点后两位,第一位填0),第10个字符为'\0'

2. ds18b20

2.1 避免初始85℃

datasheet:
在这里插入图片描述
在这里插入图片描述
器件原因,上电初值即为85℃,一次温度转换时间最长为750ms。上电初始不能让数码管显示85℃,应避免。

2.1.1 避免方法1:延时

初始化时,读取一次温度数据后延时750ms,之后再开定时器显示数码管
在这里插入图片描述
延时函数可直接在软件STC-ISP中生成

2.1.2 避免方法2:多次读取

开定时器前连续读取温度100次(经验次数100~200,可自行尝试)
在这里插入图片描述

2.1.3 避免方法3:效率读取

开定时器前连续读取温度,直至不为85℃
在这里插入图片描述

2.2 驱动延时修改

示例程序为51单片机代码,51单片机的机械周期=12 * 时钟周期,读指令比较慢。而15单片机的机械周期=时钟周期,速度比传统51单片机快12倍(时钟周期相同,均选择外部12M晶振)。故修改:

//单总线内部延时函数
void Delay_OneWire(unsigned int t)  
{
	u8 i;
	while(t--)
	{
		for(i=0;i<12;i++);//8~12倍皆可
	}
}

简单点修改:第一行加上t=t*12;即可,第二行为原来的while(t–);

2.3 防止中断打断时序

有时出现数据显示发生跳变等情况,不是板子有问题,代码有问题,中断处代码过多导致中断占用时间过多,执行主函数时,有些通信过程又被很快来到的中断打断了。整个代码一直在执行中断的内容,主函数内容很少被执行到。
方法1:优化代码。
方法2:通信过程提前关闭中断。(会影响中断)

void Write_DS18B20(unsigned char dat)
{
	unsigned char i;
 	EA=0;//如果ds18b20数据显示发生跳变等情况,极有可能是通信过程被打断了,通信过程中可提前关闭中断
	for(i=0;i<8;i++)
	{
		DQ = 0;
		DQ = dat&0x01;
		Delay_OneWire(5);
		DQ = 1;
		dat >>= 1;
	}
	EA=1;//通信完毕再开启中断。(非必要情况不写,会影响中断)
	Delay_OneWire(5);
}

其余ds1302,adc等也可采取同样操作。

3. pcf8591

3.1 注意操作命令

假设adc开启通道3(滑动变阻器),dac未开启。则adc指令为:

IIC_SendByte(0x03);

假设adc开启通道3,dac也开启,即两者均开启,两者指令应一致:

ADC的:IIC_SendByte(0x43);//dac开启时43,dac未开启03。允许DAC,ADC通道3
DAC的:IIC_SendByte(0x43);//允许DAC,ADC通道3

3.2 adc避免初始128(2.51V)

方法同上ds18b20,但adc读第二次就能把正确数据读到。
方法1:定时器开启前加上一行:

adc_val=pcf8591_adc();//读掉初始的2.51V(128)

方法2:do…while结构

do
	adc_val=pcf8591_adc();
while(adc_val==128);//读掉初始的2.51V(128)

4. eeprom

AT24C02是一个2K Bit的串行EEPROM存储器(掉电不丢失),内部含有256个字节。在24C02里面有一个8字节的页写缓冲器。

256*8
存储量只有256个字节,地址为0x00~0xff
AT24C02的存储容量为2K bit,内容分成32页,每页8Byte,共256Byte(一个字节8个Bit,总共2K Bit)
8指的是8字节的页写缓冲器,一次最大可以写入8个字节,存到8个地址中,存储量只有256个字节

void eeprom_write(u8* write_buf,u8 addr,u8 num)
{
	IIC_Start();
	IIC_SendByte(0xa0);
	IIC_WaitAck();
	
	IIC_SendByte(addr);
	IIC_WaitAck();
	
	while(num--)
	{
		IIC_SendByte(*write_buf++);
		IIC_WaitAck();
		IIC_Delay(200);
	}
	IIC_Stop();
}

此为页写代码,num<=8
addr为页写存储的第一个地址,
若num=1,则只把buf[0]存入addr中,若num>1,则buf[0]存入addr中,buf[1]存入addr+1中,依次顺延
addr为0x00,addr+1就为0x01
*buf++相当于下标++,buf[0],buf[1]……
一个数据一个地址(一字节,8bit),下一个数据的地址自动变为下一位地址

存:按输入的地址往后依次存。
读:按输入的地址往后依次读。
若write_buf[3]={0,1,2},addr=0x00,num=3
运行eeprom_write后,write_buf[0]=0存入0x00,write_buf[1]=1存入0x01,write_buf[2]=2存入0x02。
若read_buf[3]={0,0,0},addr=0x00,num=3
运行eeprom_read后,read_buf[0]=0x00地址的内容0,read_buf[1]=0x01地址的内容1,read_buf[2]=0x02地址的内容2。

5. 数据超过ram容量

由于定义的变量太多(变量存在ram,程序存在rom),占用的内部ram已经超出(data慢110左右),发送报错
在这里插入图片描述

5.1 解决方法1:更改全部变量存储区域

点击keil中的魔法棒,调出Options for Target界面,在Memory Model中将默认的DATA区(内部RAM)改为XDATA区(外部拓展RAM),或者PDATA区(XDATA的前一小段)。
在这里插入图片描述

5.2 解决方法2:更改超出部分变量存储区域

data区的变量访问速度是最快的,xdata区访问速度比data慢很多,pdata为xdata中访问速度较快的前一小段区域。

u8 read_buf[3]={0,0,0};
u8 dist_val;
u8 RX_buf[12],RX_num,pdata TX_buf[12];//超出data区(110),用xdata或者pdata(xdata前一小段)

运行结果:
在这里插入图片描述

6. 优化代码

优化代码,代码执行效率高,存储量小,这个得靠平时积累。

  • 官方参考代码规范便于学习的同时缺点也很明显,由于用了sprintf结构,占用的code非常大。

  • 最简单的一个优化例子:>> 能代替 / 的情况下尽量代替,>>作为移位运算符,运算效率远高于 / 。乘除法的本质也就是移位加 / 减。

  • 降低code最简单的就是少调用函数,能直接表明意思就直接表明,没必要再进行封装(封装为了易读和改写往往以牺牲效率为代价,好比与高级语言和汇编,汇编的效率是最最高的,很多时候关键的代码都得靠汇编编写)。

  • 中断中执行的程序,能简洁尽量简洁,不然主程序无法执行,一直在执行中断中的程序。(可以自己计算,例如12M晶振,15单片机机械周期等于时钟周期,也就是1/12M s,一条指令需要多少机械周期,调用函数又需要多少机械周期,加在一起即为中断执行时间,而中断多长时间一次我们也知道)

  • 能减少变量的使用尽量减少,能共用变量的尽量共用(buf缓冲数组不只是可以用作数码管,别的也可以调用)

  • 遇到复杂的控制时,精准定时led做出相应变化,多用标志位(定义为bit型,unsigned char浪费空间)理清思路,最后再优化代码。

最后,理解好各个模块的工作原理非常重要,不要只会记住模板,得理解其含义,每个芯片的datasheet仔细看完,理解。
今年(2022)国赛题:

  • 定时器0必须用作ne555频率测量,定时器1用作主定时1ms,超声波平时用的都是定时器0,这次只有定时器2能用,定时器2和0、1有个不一样的地方,没有溢出标志位,这样很多人甚至连超声波都没做出来,其实可以通过判断T2H和T2L是否同时达到某个数值即可,初值和达到的数值之差即可得出定时时间差。12us那边直接软件延时都能实现。
  • 难点主要在于pwm波,定时器已用完,主定时器用更短的100us,200us?还是软件延时?都试过了,效果都不好,各种影响别的模块。最后居然发现板子上的uln2003芯片电机引脚根本没反应,不知道芯片原因还是什么。。。

总结

b站小蜜蜂老师:https://space.bilibili.com/397050828
小蜜蜂老师资源链接:https://www.xmf393.com/2019/06/11/lqbmcu/

如有错误,敬请指正。内容之后会陆续进行补充

  • 24
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

聂某人

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值