蓝桥杯单片机总结(2022年国一) |
您所在的位置:网站首页 › 蓝桥杯onewire › 蓝桥杯单片机总结(2022年国一) |
文章目录
前言一、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文件 包括点灯,系统初始化,数码管,按键(矩阵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函数并不支持“输出负的八进制或者十六进制数”。
% - 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: 初始化时,读取一次温度数据后延时750ms,之后再开定时器显示数码管 开定时器前连续读取温度100次(经验次数100~200,可自行尝试) 开定时器前连续读取温度,直至不为85℃ 示例程序为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. eepromAT24C02是一个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左右),发送报错 点击keil中的魔法棒,调出Options for Target界面,在Memory Model中将默认的DATA区(内部RAM)改为XDATA区(外部拓展RAM),或者PDATA区(XDATA的前一小段)。 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前一小段)运行结果: 优化代码,代码执行效率高,存储量小,这个得靠平时积累。 官方参考代码规范便于学习的同时缺点也很明显,由于用了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 |