five如我又花了两天才写出来这玩意儿,中途还发现了自己以前好多不规范的地方(悲),于是修修补补算是勉强写出来了。。。太拉了太拉了。
因为以前很多的问题都刚改过,所以过几天我打算再写一个基于金沙滩教程的蓝桥杯单片机固定模板,也就是按键扫描和数码管扫描都放在定时器中断中的蓝桥杯固定模板。(开新坑)
老样子,我们先看赛题,提取出关键内容。
先看它给出的功能图片,需要用到的有:
输出模块:数码管,LED(PWM显示)
输入模块:按键、DS18B20(温度传感器获取数据)
让后是输出模块的基本显示格式。(比如数码管,数码管,还是数码管)
1、工作模式的输出设备明显是PWM控制的LED灯,PWM频率1kHZ,也就是1s/1000,大概是每100微秒进入一次中断。
2、数码管显示格式(图片很清晰了,不用多说明了)
让后是输入设备的功能,大概就是按键按下对应输出设备输出什么功能,温度传感器获取的数据什么时候输出,
让后可以提取出
S4:循环切换PWM模式
睡眠风PWM20 SMG1 LED1,
自然风PWM30 SMG2 LED2,
常风 PWM70 SMG3 LED3,
循环
S5:每次按下,定时时间加一分钟,剩余时间重置为当前
0分钟->1分钟->2分钟 循环
S6:清空时间,停止PWM输出
S7:温度显示(数码管显示内容改变)(不影响LED的PWM输出和定时器)
这样的内容,简捷方便,便于敲代码。
审题完成,开始进入代码部分!
首先设计主函数流程
void main()
{
Close_Others(); //关闭其它设备
Init_T0(); //初始化定时器0中断
Init_PWM(); //初始化定时器1中断作为PWM
while(1)
{
if( (State>>4)==0x0f && Timer>0 ) //判断是否开启PWM,详解如下
{
ET1 = 1; //进入打开PWM
}
else
{
ET1 = 0; //未进入关闭PWM
LED(OFF); //关闭LED
}
if(f200ms) //每隔200ms读取一次温度数据
{
f200ms = 0; //200ms标志位置0
Temp = rd_temperature(); //读取温度数据
SMG_CAL(); //计算数码管显示
}
Key_Act(); //检测按键
}
}
因为有PWM灯开启的和关闭的条件有俩,一个是按键6按下停止输出,所以我们设置了两个条件判断PWM灯的开启,一个是灯光状态数,另一个就是时间。
模块化编程时c语言重要的思想,所以我们就以一个一个功能函数的形式逐步实现。关闭其他设备,初始化定时器0,1中断这个直接上代码
void Sel_HC138(int8 n) //选择三八译码器
{
switch(n)
{
case 4:P2 = (P2&0x1f)|0x80;break;//LED
case 5:P2 = (P2&0x1f)|0xa0;break;//BUZZ and RELAY
case 6:P2 = (P2&0x1f)|0xc0;break;//数码管正极
case 7:P2 = (P2&0x1f)|0xe0;break;//数码管阴极
case 0:P2 = (P2&0x1f);break;
default:break;
}
}
void Close_Others() //关闭其他设备
{
Sel_HC138(5);
P0 = 0x00;
Sel_HC138(0);
}
void Init_T0()
{
EA = 1;
TMOD |= 0x01;
TH0 = (65536 - 1000)/256; //高八位
TL0 = (65535 - 1000)%256; //第八位
TR0 = 1; //定时器0开始计时
ET0 = 1; //定时器0中断
}
void Init_PWM() //每隔10ms进入一次中断,频率为100
{
EA = 1;
TMOD |= 0x10;
TH1 = (65536 - 100)/256; //高八位
TL1 = (65535 - 100)%256; //第八位
TR1 = 1; //定时器1开始计时
}
然后根据我们所认出的数码管标准格式显示数码管内容
如图数码管显示有两种模式,一个是时间显示模式,一个是温度显示模式。
时间显示模式有三个状态,温度显示又不能干涉PWM的显示和程序时间的正常流动,所以我们自己可以设置两个状态数State和Timer,一个负责时间的三种模式切换和LED显示,另一个负责温度状态的开关。
void SMG_CAL()
{
SMG_SHOW[7] = 0xbf;
SMG_SHOW[5] = 0xbf;
SMG_SHOW[4] = 0xff;
if(Temp_On == 0) //当状态数为显示时间时
{
SMG_SHOW[6] = SMG_NUM[State&0x0f];//状态位数为LED选择数
SMG_SHOW[3] = SMG_NUM[0];
SMG_SHOW[2] = SMG_NUM[Timer/100];
SMG_SHOW[1] = SMG_NUM[(Timer/10)%10];
SMG_SHOW[0] = SMG_NUM[Timer%10];
}
else //当状态数为显示温度时
{
SMG_SHOW[6] = SMG_NUM[4];
SMG_SHOW[3] = 0xff;
SMG_SHOW[2] = SMG_NUM[(Temp/10)%10];//Temp为DS18B20得来的温度数据
SMG_SHOW[1] = SMG_NUM[Temp%10];
SMG_SHOW[0] = 0xc6;
}
}
void SMG_Scan()//此函数放入定时器0中断这种进行数码管扫描
{
static int8 index = 0;
Sel_HC138(7); //消隐,必须用段选部分消除
P0 = 0xff;
Sel_HC138(0);
Sel_HC138(6);
P0 = (0x80>>index);
Sel_HC138(0);
Sel_HC138(7);
P0 = SMG_SHOW[index];
Sel_HC138(0);
index++;
index = (index&0x07);
}
不太理解消隐的可以看我上一篇文章,对CT107D板子的消隐有一定介绍。
点我转去看消隐
设置完状态数,我们就可以根据State状态数来设置PWM控制的LED灯了
由于题目要求1kHZ,儿ISP15的晶振是12MHz的,即定时器计数一次为1微秒,由于T0定时器需要刷新数码管,扫秒按键,计算时间灯操作,我们将PWM输出这个重担交给定时器1的十六定时器模式。
在设置一个全局变量PWM_NUM,作为占空比。
void PWM_CON() interrupt 3
{
static int8 n = 0;
//进入中断时间为100微秒
TH1 = (65536 - 100)/256; //高八位
TL1 = (65536 - 100)%256; //第八位
if(n < PWM_NUM) //尚未达到占空比,LED灯打开
LED(ON);
else
LED(OFF); //达到占空比,LED灯关闭
n++;
if(n>=100) //最大值不能超过100,占空比最大值不能比100大
{
n = 0;
}
}
void LED(int8 Sta) //Sta可取ON和OFF(前面宏定义ON为1,OFF为0)
{
int8 LED_Con;
switch(State) //根据状态选择不同的LED灯的闪亮
{
case 0xf1:LED_Con = ~0x01;break;
case 0xf2:LED_Con = ~0x02;break;
case 0xf3:LED_Con = ~0x04;break;
default:break;
}
ET0 = 0; //关闭定时器0中断,防止数码管刷新影响LED
P0 = 0xff; //P0口提前归零,防止P0残留数据影响LED
if(Sta)
{
Sel_HC138(4);
P0 = 0xff;
P0 = LED_Con; //点亮对应LED灯光
Sel_HC138(0);
}
else //如果传入数值为OFF,关闭LED
{
Sel_HC138(4);
P0 = 0xff;
Sel_HC138(0);
}
ET0 = 1; //打开数码管刷新中断
}
主函数中有了灯打开的条件,中断中有了PWM的输出,PWM的输出就齐全了,接下来只需要使输入设备按键改变Timer时间值,PWM_NUM占空比,State状态值来改变LED和数码管的输出即可。
按键扫描是基于金沙滩的教程所写的中断扫描,此处可以自行查询,不做介绍。
所以我们直接说按键要实现的动作
根据我们所提取的要点,实现按键的功能。
S4:通过按键改变PWM_NUM和State实现S4的功能
S5:改变Timer值实现时间的改变
S6:清空Timer值,将State值变为0x00关闭PWM输出
S7:打开Temp_On,打开温度显示
void Action(int8 KeyCode)
{
switch(KeyCode)
{
//按键4切换PWM模式
case 4:
if(State == 0xf3) //当为0xf3按下时,自动变成0xf1的睡眠风模式
State = 0xf0;
State = (State|0xf0)+1; //每次进入,state变为0xfn
switch(State)
{
case 0xf1:
PWM_NUM = 20; //PWM设置为20%
break;
case 0xf2:
PWM_NUM = 30; //PWM设置为30%
break;
case 0xf3:
PWM_NUM = 70; //PWM设置为30%
break;
default:break;
}
break;
//按键5改变时间
case 5:
if(Timer<120) //时间小于120s时加六十秒
Timer += 60;
if(Timer>120) //时间大与120s时,时间归零
Timer = 0;
break;
case 6:
Timer = 0; //关闭时间和State,直接清零
State = 0x00;
break;
case 7:
if(Temp_On==0)
Temp_On = 1; //当不显示温度时,按下显示温度
else if(Temp_On)
Temp_On = 0; //当显示温度时,按下关闭温度
break;
default:break;
}
}
最后,温度数值Temp和时间数Timer还没获取,Temp的数值可以通过DS18B20获得,Timer则可以在定时器0中获取。
先是温度
DS18B20驱动流程:
1、复位DS18B20。
2、向DS18B20中写入0xCC指令,跳过ROM操作。
3、向DS18B20中写入0x44指令,开始温度转换。
4、延时700ms左右,等待温度转换成功。
5、再次复位。
6、再次向DS18B20中写入0xCC指令,跳过ROM操作。
7、向写入0xBE指令,读取高速暂存器。
8、读取温度第八位。
9、读取温度高八位。
10、按要求处理温度数据。
int16 rd_temperature()
{
int8 LSB,MSB;
int16 temp;
init_ds18b20(); //初始化ds18b20
Write_DS18B20(0xCC); //写入0xCC跳过ROM操作指令
Write_DS18B20(0x44); //启动温度转换
Delay_OneWire(100); //延迟700ms左右,等待温度转换
init_ds18b20(); //初始化ds18b20
Write_DS18B20(0xCC); //写入0xCC跳过ROM操作指令
Write_DS18B20(0xBE); //启动温度转换
LSB = Read_DS18B20(); //读取第八位
MSB = Read_DS18B20(); //读取高八位
temp = MSB;
temp = (temp<<8)|LSB;
temp >>= 4; //只获取整数
return temp;
}
DS18B20初始化详情见
DS18B20初始化部分(看目录中DS18B20初始化部分)
Timer的流动
void it0() interrupt 1
{
static int8 i = 0;
TH0 = (65536 - 1000)/256; //高八位
TL0 = (65536 - 1000)%256; //第八位
i++;
if(i==200)//两百毫秒进入一次
{
f200ms = 1;
NUM_200ms++;
if(NUM_200ms == 5)//5个两百毫秒为1s,时间减一秒
{
if(Timer>0)
Timer--;
NUM_200ms = 0;
}
i = 0;
}
SMG_Scan();//数码管刷新
Key_Scan();//按键刷新
}
最后,整理完成之后,一定寻找问题修bug!bug太多写了好久属实痛苦
第七届赛题总体来说不算难,主要的难点和第八届赛题比较像,比如利用状态数和按键切换数码管的显示和模式,以及一些位运算。
链接:https://pan.baidu.com/s/1GmKYQEs1jYePIYGVJ4p8Zg
提取码:62nd