前言
对于蓝桥杯的单片机竞赛,主观题很重要,占了百分之70-80的大头,因此主观题做得怎么样决定了比赛是否能拿奖,而客观题的正确率很大程度上决定了你能否获得省一,从而进入决赛。因此我在这里分享一期关于主观题中各个模块的编写规范和注意事项。
本人参加了2022年的第十四届蓝桥杯大赛,并有幸获得了电子类单片机个人赛的省二。同时还手把手教出了一个省二,辅导出了一个国二。很遗憾自己没有进入决赛,刚出成绩的时候,打抱不平,觉得很不甘心。但后来想了想还是自己的个人问题,虽然主观题做的很好,但客观题错了六个。虽然都说客观题,三分靠实力,七分靠运气,但我大二上学期数电模电学的确实不咋滴,这也导致客观题我一遇到数电模电就头大,而恰巧这次蓝桥杯省赛数电模电题占了一大半。
所以我想强调的是学习嵌入式,除了学好应用层的知识,会写代码,我们还要学会数电模电硬件层电路相关知识,这样今后才能成为一名合格的嵌入式攻城狮。我在最近几个月学习STM32的时候,同时也对数电模电进行了相关温故,这使我对理解相关总线协议的工作原理和各种外设的工作方式有了很大帮助。
蓝桥杯全模块代码+部分国赛省赛程序下载
https://download.csdn.net/download/qq_25218501/87965874
正文
译码器、锁存器电路结构
因为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)
{
//...
}
}
MM控制方法,此时WR由P3^6自动控制,因为P2和P0作为地址总线,所以P2接收高8位,P0接收低8位
使用继电器
首先接通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灯
独立按键、矩阵键盘——三行代码
独立按键
矩阵键盘
注意点
补充
关于三行代码的理解
-
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,就是共阴极。共阳极就是高电平有效,共阴极为低电平有效。
数码管消影
-
位选不选择任何数码管(消除影子)->再段选->再位选——(实测最有效)
-
先段选不选中数码管的任何段->再位选->再段选——(可能仍会有)
-
写法:
P2=0xc0;P0=0x00;P2=0x00;//位选
P2=0xe0;P0=0xXX;P2=0x00;//段选
P2=0xc0;P0=0xXX;P2=0x00;//位选
定时器中断
学会使用STC软件来配置
注意模式一定要选择16位自动重载(实际比赛中也只用它),时钟选择1T。
补充:IAP15不像89C51,89C51只有定时器工作方式2可以设置自动重新装载并且只有8个位;在IAP15中,每个定时器都可以设置为重新装载,并且还是16位的,大大提高了单片机的工作效率,也节省了程序编写。
定时器0:
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:
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:
void Timer2Init(void) //1毫秒@12.000MHz
{
AUXR |= 0x04; //定时器时钟1T模式
T2L = 0x20; //设置定时初值
T2H = 0xD1; //设置定时初值
AUXR |= 0x10; //定时器2开始计时
/*中断开启要自己添加*/
IE2 |= 0x04; //开启定时器2中断
EA = 1;//开启总中断
}
具体使用可如图位置参考例程
单片机中断程序中,例如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
DS18B20使用单总线,顾名思义就是使用P14一个I/O口来控制输入和输出
DS18B20初始化
DS18B20初始化(为了检测单片机上是否存在DS18B20)(前半段为单片机向DS18B20发送的信号,后半段为DS18B20向单片机发送的信号):
!
//初始化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的数据
-
操作步骤
- 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;
}
读取温度
//读取温度函数
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;
}
红色圈处不能返回,如果返回,数码管就会在实际温度和10000(乘10000在主函数内)之间来回闪烁切换,
正确写法
代码需要如下修改:
延时函数增加t要先乘10
然后主程序如下
IIC协议
AD/DA电路
- 采用PCF8591芯片
- 为8位AD/DA
- 有四路AD采样口:AIN0——AIN3,和一路模拟输出口:AOUT
- 最左边采样电路通过光敏电阻和普通电阻产生的分压给采样口进行采样
- 旁边的通过电位器(滑动变阻器)的分压来给AD进行采样
- 用到的单片机通讯口为P20和P20,只用到了单片机的两个I/O
- A0、A1、A2用定义IIC的通讯地址,因为单片机总线上只接了一个PCF8591,所以直接接地,让地址为000
EEPROM电路
- 用到的单片机通讯口同样为为P20和P20,挂载到一个IIC的I/O口上
- A0、A1、A2用定义IIC的通讯地址,直接接地
IIC协议介绍
总线的启动和停止
-
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;
}
等待应答
- 应答信号
- 在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;
}
}
数据发送
- 先发送高位在发送地位
数据接收
- 先接收高位再接受地位
PCF8591编程
PCF8591写时序
- 因为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读时序
- 前半部分先发送写命令给芯片,告诉芯片要读哪个通道
- 然后后半部分发送读命令给芯片,从芯片中读数据
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写时序
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读时序
- 前半部分先发送写命令给芯片,告诉芯片要读哪个单元地址内容
- 然后后半部分发送读命令给芯片,从芯片中读数据
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;
}
定时器对时序的影响
定时器中断有的时候会打断读写数据的时序,所以再读写时序前要关闭中断,读写完再打开。关闭EA即可。方法同样适用于DS18B20实验
超声波模块原理及编程
超声波电路
左边为超声波发射电路,右边为超声波接受电路。
1-3 2-4短接为超声波
5-3 4-6短接为红外发生器
超声波发送模块
-
如果上面一路接受到的是正向方波信号,那么下面一路接收到的就是反向方波信号。其中c7、c8电容起耦合作用。两个反向器并联可以提高超声波发射强度。
-
N_A1接单片机的一个I/O口
超声波发送模块
- 放大、限幅、带通滤波、峰值检波、整形和比较等功能通过内部的各种电容和电阻来实现
- 比较完后,超声波通过N_B1口向单片机发送一个低电平请求中断
超声波测距编程
左边为超声波测距程序,右边为超声波发送函数
- 之所以超声波要发送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编程
实时时钟电路
- 第八管脚接外接电池,当开发板断电后还能继续走时
- P17/SCK为驱动ds1302芯片的I/O
- P23为进行通信的I/O
- P13为复位I/O
SPI协议介绍
写数据
- CE为复位I/O,高电平使能有效
- SCK_SET表示SCLK为高电平,SCK_CLR表示SCLK为低电平
读数据
- SDA_R为I/O口管脚
DS1302编程
读数据
写数据
时间编程(需要自己写):
- 左边为设置时间函数
- 右边为读取时间,直接全部在主函数中调用
- 注意要先设置时间,再读取时间
简单的说,十进制数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=1;
Delay10ms();
}
//第二种失效,就第三种,这种就是把延时放在关中断里,关中断的时间更长一些
//可能会影响数码管显示,但这是初始化工作,放在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;
}
}