第三届初赛赛题除了基础的独立按键、数码管显示、继电器和蜂鸣器的控制、LED灯的控制以外,难度增加的部分体现在AD转换部分。需要涉及到I2C总线驱动程序的调用。
本解析不代表标准答案或官方答案,仅做分享。若有不足或是更好的写法,望在评论区进行指正!
基础的功能:
1、独立按键
2、数码管显示
可以独立于赛题,提前写好,适用于各类赛题,仅做修改即可。
代码(板子):
#include
#include
//#include
#define uchar unsigned char
uchar keyPressFlag[4] = {0, 0, 0, 0};
uchar keyPress[4] = {0, 0, 0, 0};
uchar code shapeOfNum[11] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xbf};
uchar numGrp[8] = {11, 11, 11, 11, 11, 11, 11, 11};
void Delay5ms(){
unsigned char i, j;
i = 54;
j = 199;
do
{
while (--j);
} while (--i);
}
void Delay1ms(){
unsigned char i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
void keyScan(){
uchar i;
for(i = 0; i < 4; i ++) keyPress[i] = 0;
if((P3 & 0x0f) != 0x0f){
Delay5ms();
if((P3 & 0x0f) != 0x0f){
switch(P3 & 0x0f){
case 0x0e:keyPressFlag[0] = 1;break;
case 0x0d:keyPressFlag[1] = 1;break;
case 0x0b:keyPressFlag[2] = 1;break;
case 0x07:keyPressFlag[3] = 1;break;
}
}
}
for(i = 0; i < 4; i ++){
if(keyPressFlag[i] != 0){
if((P3 & 0x0f) == 0x0f){
keyPress[i] = 1;
keyPressFlag[i] = 0;
}
}
}
}
void showNum(){
uchar i;
for(i = 0; i < 8; i ++){
if(numGrp[i] != 11){
P2 = (6 << 5);P0 = (0x01 << i);
P2 = (7 << 5);P0 = shapeOfNum[numGrp[i]];
Delay1ms();
}
}
P2 = (6 << 5);P0 = 0x00;
}
void main(){
P2 = 0xa0;P0 = 0x00;
P2 = 0x80;P0 = 0xff;
while(1){
}
}
这里的模板代码有几个固定地方需要注意:
1、按键开了两个数组,keyPressFlag
数组来判断是否按下,但只有按键松开的时候才会在keyPress
数组中置1,是一个上升沿开关(按键按下的时候为0)。这样设计的原因在于,如果使用单数组,很容易造成——一旦按住按键不放手,程序就会进入按键函数无法跳出,影响其他程序同步运行。当数码管没有用定时器中断写的时候,也会收到按键的影响。所以这里用两个数组来进行检测,可以达到较好的效果。(某年初赛赛题恰好要求的是按键松开的时候达到某效果)
2、数码管没有用定时器来写。当然,用定时器中断来写数码管对于数字显示而言,效果会更好。但是定时器的强制中断也会给单片机的一些其他外设带来一些影响。尤其是题目需要用到定时器的时候,同时启用多个定时器可能会带来一些问题。所以这里模板采用的是传统的函数调用来写数码管显示。
3、通常赛题需要用到-
符号,也就是数码管通常需要显示0 1 2 3 4 5 6 7 8 9 -
,因此,模板里面保存的shapeOfNum[11]
通常保存11个数,即11个字符。
1、我们发现该题需要调用I2C通信。而在I2C通信期间,会调用P2端口。也就是说,当我们使能一些芯片的时候,需要改变P2.5-P2.7的值,如果此时直接采用
P2 = 0xXX;
这样的赋值语句,会对I2C产生干扰,也就是说我们只能改变P2端口后三位,而不能影响前面的数值。所以我们需要对模板进行修改。
void openP2(uchar num){
P2 &= 0x1f;
P2 |= (num << 5);
}
编辑这样一个函数
该函数不同于普通的P2赋值语句的区别在于,它不会影响前P2前五位的值。
比如我们要控制LED灯的时候,参照CT107D单片机原理图可以发现——
LE口对应的是Y4C,也就是要控制LED灯时,openP2
函数的输入应为4
,即openP2(4);
需要将showNum
函数进行修改:
void showNum(){
uchar i;
for(i = 0; i < 8; i ++){
if(numGrp[i] != 11){
openP2(6);P0 = (0x01 << i);
openP2(7);P0 = shapeOfNum[numGrp[i]];
Delay1ms();
}
}
openP2(6);P0 = 0x00;
}
官方为我们提供了iic的一个c文件一个h头文件。需要在项目中导入iic.c,同时声明iic.h。
同时要注意,由于官方提供的参考驱动代码是基于52单片机的,我们需要在.c文件中将所有的延时函数扩展为原来的12倍,才能成功调用驱动。
官方提供的AT24C02芯片说明书中描述了如何用IIC读取。(此题没有涉及到E2PROM的写入,所以用读就可以了)。结合该图对函数进行设计:
uchar IICRead(uchar add){
uchar dat;
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_Stop();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
dat = IIC_RecByte();
IIC_WaitAck();
IIC_Stop();
return dat;
}
这里A/D转换的 写、读硬件地址为0x90/0x91
。
而word address可以由CT107D单片机原理图上找到,AIN1对应的是光敏电阻的A/D转换,所以此处函数输入应为0x01
。即add=0x01;
在系统设计的时候,尽量避免对P2的重复操作。
比如第一次循环的时候LED1为亮,第二次循环的时候LED1仍然为亮。此时我们应该尽量避免对LED1的重复操作。换言之,就是我们的代码仅仅在某个状态发生变化的时候才进行操作。
以光敏电阻部分为例。
当电压>1.25V时灯熄灭,否则灯亮
light = IICRead(0x01);
if(light > (255 / 4)){
if(L1 == 1){
openP2(4);P0 = 0xff;
L1 = 0;
}
}
else{
if(L1 == 0){
openP2(4);P0 = 0xfe;
L1 = 1;
}
}
而总系统部分,其实就是两个模式之间的切换
1、出水显示模式
2、计价显示模式
按下s7时,实质上就是从模式2变成模式1,而按下s6时,实质上就是从模式1变成模式2。(初始化的时候其实是计价为0元的模式2)
换言之,在每次循环的时候,仅当模式发生了变换的时候,才会进行一部分操作。
if(mod == 0){
if(preMod == 1){
openP2(5);P0 = 0x10;openP2(0);
water = 0;count = 0;
Timer0Init();
ET0 = 1;
preMod = 0;
}
numGrp[4] = water / 1000;
numGrp[5] = (water / 100) % 10;
numGrp[6] = (water / 10) % 10;
numGrp[7] = water % 10;
if((keyPress[1] == 1) || (water == 9999)){
mod = 1;
}
}
else if(mod == 1){
if(preMod == 0){
openP2(5);P0 = 0x00;openP2(0);
ET0 = 0;
money = water / 2;
preMod = 1;
}
numGrp[4] = money / 1000;
numGrp[5] = (money / 100) % 10;
numGrp[6] = (money / 10) % 10;
numGrp[7] = money % 10;
if(keyPress[0] == 1){
mod = 0;
}
}
#include
#include
#include
//#include
#define uchar unsigned char
uchar keyPressFlag[4] = {0, 0, 0, 0};
uchar keyPress[4] = {0, 0, 0, 0};
uchar code shapeOfNum[11] = {0xc0, 0xf9, 0xa4, 0xb0, 0x99, 0x92, 0x82, 0xf8, 0x80, 0x90, 0xbf};
uchar numGrp[8] = {11, 0, 5, 0, 0, 0, 0, 0};
uchar count = 0;
int water = 0;
void Delay5ms(){
unsigned char i, j;
i = 54;
j = 199;
do
{
while (--j);
} while (--i);
}
void Delay1ms(){
unsigned char i, j;
_nop_();
_nop_();
_nop_();
i = 11;
j = 190;
do
{
while (--j);
} while (--i);
}
void openP2(uchar num){
P2 &= 0x1f;
P2 |= (num << 5);
}
void keyScan(){
uchar i;
for(i = 0; i < 4; i ++) keyPress[i] = 0;
if((P3 & 0x0f) != 0x0f){
Delay5ms();
if((P3 & 0x0f) != 0x0f){
switch(P3 & 0x0f){
case 0x0e:keyPressFlag[0] = 1;break;
case 0x0d:keyPressFlag[1] = 1;break;
case 0x0b:keyPressFlag[2] = 1;break;
case 0x07:keyPressFlag[3] = 1;break;
}
}
}
for(i = 0; i < 4; i ++){
if(keyPressFlag[i] != 0){
if((P3 & 0x0f) == 0x0f){
keyPress[i] = 1;
keyPressFlag[i] = 0;
}
}
}
}
void showNum(){
uchar i;
for(i = 0; i < 8; i ++){
if(numGrp[i] != 11){
if((i == 1) || (i == 5)){
openP2(6);P0 = (0x01 << i);
openP2(7);P0 = shapeOfNum[numGrp[i]] - 0x80;
Delay1ms();
}
else{
openP2(6);P0 = (0x01 << i);
openP2(7);P0 = shapeOfNum[numGrp[i]];
Delay1ms();
}
}
}
openP2(6);P0 = 0x00;
}
uchar IICRead(uchar add){
uchar dat;
IIC_Start();
IIC_SendByte(0x90);
IIC_WaitAck();
IIC_SendByte(add);
IIC_WaitAck();
IIC_Stop();
IIC_Start();
IIC_SendByte(0x91);
IIC_WaitAck();
dat = IIC_RecByte();
IIC_WaitAck();
IIC_Stop();
return dat;
}
void Timer0Init(void){
AUXR |= 0x80; //定时器时钟1T模式
TMOD &= 0xF0; //设置定时器模式
TL0 = 0xCD; //设置定时初值
TH0 = 0xD4; //设置定时初值
TF0 = 0; //清除TF0标志
TR0 = 1; //定时器0开始计时
}
void timer0() interrupt 1{
//100 ms 进1位
if(count == 99){
count = 0;
water ++;
}
else count ++;
}
void main(){
uchar light, mod = 1, preMod = 1, L1 = 0;
int money = 0;
P2 = 0xa0;P0 = 0x00;
P2 = 0x80;P0 = 0xff;
EA = 1;ET0 = 0;
while(1){
light = IICRead(0x01);
if(light > (255 / 4)){
if(L1 == 1){
openP2(4);P0 = 0xff;
L1 = 0;
}
}
else{
if(L1 == 0){
openP2(4);P0 = 0xfe;
L1 = 1;
}
}
keyScan();
if(mod == 0){
if(preMod == 1){
openP2(5);P0 = 0x10;openP2(0);
water = 0;count = 0;
Timer0Init();
ET0 = 1;
preMod = 0;
}
numGrp[4] = water / 1000;
numGrp[5] = (water / 100) % 10;
numGrp[6] = (water / 10) % 10;
numGrp[7] = water % 10;
if((keyPress[1] == 1) || (water == 9999)){
mod = 1;
}
}
else if(mod == 1){
if(preMod == 0){
openP2(5);P0 = 0x00;openP2(0);
ET0 = 0;
money = water / 2;
preMod = 1;
}
numGrp[4] = money / 1000;
numGrp[5] = (money / 100) % 10;
numGrp[6] = (money / 10) % 10;
numGrp[7] = money % 10;
if(keyPress[0] == 1){
mod = 0;
}
}
showNum();
}
}