比赛最主要的实现功能,需要关注各个部分之间的逻辑!
赛前的一点总结
LED
整个工程中LED和数码管,按键部分其实是最主要的部分,往年省赛涉及部分也是相对较多的。
- 点亮L1
sbit L1 = P0^0;
void led_1()
{
P2 = (P2 & 0x1f) | 0x80;
L1 = 0;
}
- L1隔0.1s闪烁
#define uchar unsigned char
sbit L1 = P0^0
bit flag_L1;
void Timer0Init(void); //50毫秒@12.000MHz
void led_1()
{
if (flag_L1)
{
P2 = (P2 & 0x1f) | 0x80;
L1 = 0;
}
else
{
P2 = (P2 & 0x1f) | 0x80;
L1 = 1;
}
}
void timer0() interrupt 1
{
static uchar i1;
if (++i1 == 2)
{
i1 = 0;
flag_L1 = !flag_L1;
}
}
void Timer0Init(void) //50毫秒@12.000MHz
{
AUXR &= 0x7F; //定时器时钟12T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xB0; //设置定时初值
TH0 = 0x3C; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
ET0 = 1;
EA = 1;
}
数码管
- 注意消隐和小数点处理
#define uchar unsigned char
uchar tab[] = {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90,0xff }
//0 ~ 9 数码管熄灭
void Delay1ms(); //1ms@12.000MHz,延时1ms用于给足数码管足够显示时间
void dsp_smg_bit1(uchar pos, val, sta)
{
P2 = (P2 & 0x1f) | 0x80;
P0 = 1 << (pos - 1);
P2 = (P2 & 0x1f) | 0x80;
if (sta)//小数点
P0 = tab[val] & 0x7f;
else
P0 = tab[val];
Delay1ms();//给足显示时间
P0 = 0xff;//消隐
P2 &= 0x1f;
}
独立按键
- 先调到BTN模式
正常处理按键
sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;
void key_handle()
{
if (!S7)
{
delay_k(15);//灵敏度更高就消抖时间短一些
if (!S7)
{
whlie(!S7)
display();
ps:这部分处理按键
}
}
//跟S7实现一样
if (!S6)
....
if (!S5)
....
if (!S4)
....
}
长按键功能–规定秒数
- S7长按时间超过1s判断为长按键,否则为短按
sbit S7 = P3^0;
sbit S6 = P3^1;
sbit S5 = P3^2;
sbit S4 = P3^3;
#define uint unsigned int
bit pre_flag;//长按键标志,0为判断时间结束,1为判断开始
uint pre_cnt;//计算按下去多久
//这里利用定时器 定时1ms进入中断(ps:当然你也可以定时其他的时间)
void Timer0Init(void) //1毫秒@12.000MHz
{
...
}
//中断服务函数
void timer0() interrupt 1
{
if (pre_flag)//开始判断
{
pre_cnt++;
}
}
void key_handle()
{
if (!S7)
{
delay_k(15);//灵敏度更高就消抖时间短一些
if (!S7)
{
pre_flag = 1;
whlie(!S7)
display();
pre_flag = 0;
if (pre_cnt > 1000)//大于1s
...长按键操作
else
...短按键操作
//还有一点重要的是,清零!
pre_cnt = 0;
}
}
//跟S7实现一样
if (!S6)
....
if (!S5)
....
if (!S4)
....
}
长按键功能–不规定秒数
if (!S7)
{
delay_k(15);//灵敏度更高就消抖时间短一些
if (!S7)
{
if (1 == jm)
{
...界面1的操作
}
if (2 == jm)//界面2操作长按为显示其他内容,短按或者按完不按处于其他界面
{
while(!S7)//长按为显示其他内容
dsp_other();
}
whlie(!S7)//短按或者按完不按处于其他界面
display();
}
}
矩阵按键
- 模式选择KBD
4 * 4 矩阵按键
#define uchar unsigned char
void key_scanf()
{
uchar i;
P44 = P42 = P35 = P34 = 1;//初始化列线拉高
for (i = 0; i < 4; i++)
{
P3 |= 0x0f;//行初始化拉高,列线不变
P3 &= ~(1 << i);//从上往下依次拉低行线
if (!P44)//该行第一个键
{
delay_k(20);
if (!P44)
{
while(!P44)
{
display();
}
}
key_val = 4 * i + 1;
break;
}
剩下三个键类似
}
}
void key_handle()
{
key_scanf();
通过key_val做出按键处理
}
2 * 2 矩阵按键
- 与4 * 4类似,对应初始化某两条列线先拉高。
- 需要for循环就控制i的初始值,不需要就除了需要判断的那一行拉低,另一行拉高,判断拉低的行的两个按键,对应哪一列为低
四个按键对应于左下角四个键s5 s4 s9 s8
void key_handle()
{
P32 = P44 = P42 = 1;
P33 = 0;
if (!P44)//s4
{
delay_k(20);
if (!P44)
{
while(!P44)
display();
对应处理按键操作
}
}
斜按键操作
P34 = 1;
P33 = 0, P30 = 1, P31 = 1, P32 = 1;//其他行默认低电平,所以我们将其拉高
if(!P34)//s16
{
delay_k(10);
if(!P34)
{
if (2 == jm)
{
if (--para_temp < 10)//参数操作 + 参数检查
para_temp = 10;
}
if (1 == jm)
{
while(!P34)//s17的长按键操作
{
rd_time();
dsp_min_sec();
}
}
while(!P34)
{
display();
}
}
}
NE555频率测量
①NE555频率测量,要把跳线帽接在P34和SIGNAL上,使用完之后记得把跳线帽摘下来,不然就会影响矩阵按键,因为矩阵按键第4列也接在P34上
②改变Rb3可以改变输出的频率值
uint cnt_freq, curr_freq;
void timer0() interrupt 1
{
cnt_freq++;
}
void timer1() interrupt 3
{
static uchar i1 = 0;
if (++i1 == 20)//1s
{
i1 = 0;
curr_freq = cnt_freq;
cnt_freq = 0;
}
}
void sys_init()
{
TMOD = 0x04; //设置定时器模式,定时器0--计数器模式,定时器1--定时器模式
TL0 = 0xff;
TH0 = 0xff;//来一个脉冲就溢出计数一次
TR0 = 1;
//50ms
TL1 = 0xB0; //设置定时初值
TH1 = 0x3C; //设置定时初值
TF1 = 0; //清除TF1标志
TR1 = 1; //定时器1开始计时
ET0 = 1;
ET1 = 1;
EA = 1;
}
ADC
通过pcf8591读数据,省赛中一般不会太搞人心态的,记住下方模板即可。
- 从pcf8591读数据
#define uchar unsigned char
uchar rd_pcf8591(uchar addr)
{
uchar da;
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
da = IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
return da;
}
需要注意的是,由于pcf8591读取电压读取的为上一次的值,当我们连续从某多个通道读取数据时,每次调用时我们需要读取两次,这样才能保证数据的正确性。
#define uchar unsigned char
float v_val;
uchar rd_pcf8591(uchar addr)
{
uchar da;
uchar i;
for (i = 0; i < 2; i++)
{
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
da = IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
}
return da;
}
//255.0是小数,整个表达式被强制类型转换为浮点数,最后赋值给浮点数v_val
v_val = rd_pcf8591(0x03) * 5 / 255.0;
DAC
切换成为DAC输出模式,允许 DAC, 使用ADC 通道 0,最后注意需要延时5ms
void dac_pcf8591(uchar da)
{
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(0x40); //DAC输出模式,允许 DAC, ADC 通道 0
IIC_WaitAck();
IIC_SendByte(da);
IIC_WaitAck();
IIC_Stop();
Delay5ms();
}
eeprom
省赛中一般就是对eeprom进行读写,也都是固定的代码模块,需要留意的是,为了保证一个完整的写入周期,我们需要延时5ms
- 写eeprom
#define uchar unsigned char
#define uint unsigned int
void write_eeprom(uchar addr, da)
{
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_SendByte(da);
IIC_WaitAck();
IIC_Stop();
Delay5ms();
}
- 读eeprom
#define uchar unsigned char
#define uint unsigned int
uchar rd_eeprom(uchar addr)
{
uchar da;
IIC_Start();
IIC_SendByte(0xa0);
IIC_WaitAck();
IIC_SendByte(addr);
IIC_WaitAck();
IIC_Start();
IIC_SendByte(0xa1);
IIC_WaitAck();
da = IIC_RecByte();
IIC_SendAck(1);
IIC_Stop();
return da;
}
DS18B20
省赛中一般对温度进行保留小数或者读取整数的操作,也是几乎固定的模板
- 读取整数
#define uchar unsigned char
uchar rd_temp()
{
uchar l, h, t;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
l = Read_DS18B20();
h = Read_DS18B20();
t = (h << 4);
t |= (l >> 4);
return t;
}
- 读取带小数的温度
#define uchar unsigned char
#define uint unsigned int
float rd_temp()
{
uchar l, h;
uint t;
float temp;
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0x44);
while(!DQ);
init_ds18b20();
Write_DS18B20(0xcc);
Write_DS18B20(0xbe);
l = Read_DS18B20();
h = Read_DS18B20();
t = (h & 0x0f);
t <<= 8;
t |= l;
temp = t * 0.0625;
return temp;
}
DS1302
省赛中一般仅仅使用秒、分、时,使用过程中主要就是需要注意,由于ds1302芯片存储的数据是BCD码,所以我们要进行BCD码与十进制之间的转换,例如0x16 的BCD码为16,十进制为1 * 16 + 6 = 32。
我个人是将这两者之间的转换放到官方给的底层代码中完成的,这样做的好处就是,我们在main.c中,只需要使用十进制,表示时间即可。
- 读时间
unsigned char Read_Ds1302_Byte ( unsigned char address )
{
unsigned char i,temp=0x00;
RST=0; _nop_();
SCK=0; _nop_();
RST=1; _nop_();
Write_Ds1302(address);
for (i=0;i<8;i++)
{
SCK=0;
temp>>=1;
if(SDA)
temp|=0x80;
SCK=1;
}
RST=0; _nop_();
SCK=0; _nop_();
SCK=1; _nop_();
SDA=0; _nop_();
SDA=1; _nop_();
temp = temp / 16 * 10 + temp % 16;
return (temp);
}
- 写时间
void Write_Ds1302_Byte( unsigned char address,unsigned char dat )
{
RST=0; _nop_();
SCK=0; _nop_();
RST=1; _nop_();
Write_Ds1302(address);
dat = dat / 10 * 16 + dat % 10;
Write_Ds1302(dat);
RST=0;
}
- main.c中初始化与读取时间
uchar init_time[] = {45, 59, 23};
uchar time[3] = { 0};
void time_init()
{
Write_Ds1302_Byte(0x8e, 0);
Write_Ds1302_Byte(0x80, init_time[0]);
Write_Ds1302_Byte(0x82, init_time[1]);
Write_Ds1302_Byte(0x84, init_time[2]);
Write_Ds1302_Byte(0x8e, 80);//注意这个地方写80,而不是0x80,因为我们在底层代码中完成十进制转BCD码操作
}
void rd_time()
{
time[0] = Read_Ds1302_Byte(0x81);
time[1] = Read_Ds1302_Byte(0x83);
time[2] = Read_Ds1302_Byte(0x85);
}
数据处理
在C语言中,浮点数 可以直接跟uint,uchar直接比较,在C语言内部会将这些类型都给强制类型转换为 同一类型。
float curr_temp = 25.6;
uchar temp_para = 25;
if (curr_temp > temp_para )
{
对应处理操作
}
else
{
对应处理操作
}
头文件修改
官方给iic.h、onewire.h、ds1302.h中使用的时C51的头件"reg52.h",我们需修改为对应的15系列头文件"STC15F2K60S2.h",这样才可以使用一些特殊位寄存器。
底层代码修改
需修改驱动代码onewire.c中单总线延时函数,官方给我们是STC89C52RC系列,15系列的单片机速度比51快8~12倍,需要修改对应的延时函数。修改如下:
void Delay_OneWire(unsigned int t)
{
char i;
while(t--)
for (i = 0; i < 12; i++);
}
最后祝大家都能取得理想的成绩!!!