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

您所在的位置:网站首页 蓝桥杯onewire 蓝桥杯单片机总结(2022年国一)

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

2024-01-21 12:35| 来源: 网络整理| 查看: 265

文章目录 前言一、my代码模块1. main.c2. initial.c3. onewire.c4. ds1302.c5. iic.c 二、注意点1. sprintf1.1 函数:sprintf1.2 printf输出控制符1.3 printf的格式控制的完整格式1.4 seg_buf[10] 2. ds18b202.1 避免初始85℃2.1.1 避免方法1:延时2.1.2 避免方法2:多次读取2.1.3 避免方法3:效率读取 2.2 驱动延时修改2.3 防止中断打断时序 3. pcf85913.1 注意操作命令3.2 adc避免初始128(2.51V) 4. eeprom5. 数据超过ram容量5.1 解决方法1:更改全部变量存储区域5.2 解决方法2:更改超出部分变量存储区域 6. 优化代码 总结

前言

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

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

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

一、my代码模块

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

1. main.c #include "initial.h" #include "onewire.h" #include "ds1302.h" #include "iic.h" #include //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 // 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 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 // 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 // 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 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 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 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 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 头文件:函数原型: int sprintf(char *str, char *farmat [,argument,…]);功 能: 格式化输出到字符串中参数: char *str 要输出的字符串     char *farmat [,argument,…] 要输入的格式返回值: 返回字符串的字节数

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

#include #include 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.32或09.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 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(); }

此为页写代码,num1,则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/

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



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3