第二节和第三节介绍了如何使用C语言编程单片机,制作 LED 闪烁小灯,以及 LED “呼吸灯”。上一节介绍了利用 DHT11 温湿度传感器测量室内温度和湿度的方法,本节将把它们结合,制作一些更加“有意思”的小玩意儿。
温湿度传感器的很多实际应用的场景都是检测一个“阈值”——例如,如果环境温度在 40 摄氏度以下则一切正常,一旦环境温度超过 40 摄氏度,就会有报警信息,通常以“声光”的形式(铃声、警报灯等)给出。
参照实际应用,我们可以使用C语言编程单片机,用缓和的 LED “呼吸灯”表示正常温湿度情况。而一旦出现温度超过阈值的情况,则可以用急促的 LED 闪烁小灯表示。
先来看看上一节获取室内温湿度的单片机 C语言程序代码:
void main()
{
char dat[5] = {0};
int cks;
init_uart(9600);
set_timer0(10); // 10us
prints("init...\r\n");
delay_10us(50000);delay_10us(50000);
delay_10us(50000);delay_10us(50000);
prints("program start...\r\n");
while(1){
dht11_start();
dht11_read_once(dat);
cks = 0;
cks += dat[0]; cks += dat[1];
cks += dat[2]; cks += dat[3];
if((char)cks == dat[4]){
prints("RH: ");printn(dat[0]);prints(".");printn(dat[1]);prints(" ");
prints("TM: ");printn(dat[2]);prints(".");printn(dat[3]);prints("\r\n");
}else{
prints("capture failed\r\n");
}
delay_10us(50000);delay_10us(50000);
}
}
乍一看,要实现上面的设计好像很简单,只需要判断一下温度值,再决定使用哪一套 LED 小灯的控制程序(“呼吸灯”程序,或者“闪烁”程序)就可以了。
但是 DHT11 传感器需要的 delay_10us(50000); 语句会破坏 LED 小灯的控制程序,因为在这 500ms 期间,单片机处于等待状态,无法控制 LED 小灯闪烁或者“呼吸”。那么,没有办法解决这个问题吗?也不是,请看 LED 小灯“闪烁”的核心C语言程序:
while(1){
P20 = 0;
delay(10);
P20 = 1;
delay(10);
}
以及 LED 的“呼吸灯”的核心C语言程序:
void twinkle_once(unsigned char darkTime)
{
P20 = 0;
delay(100-darkTime);
P20 = 1;
delay(darkTime);
}
仔细思考一下,应该能明白无论是 LED “闪烁”程序,还是“呼吸”程序,其实都依赖“延时”。那思路就有了,直接使用 LED 小灯的控制程序代替 delay_10us(50000); 不就可以了吗?的确如此,请继续往下看。
先在上一节 C语言代码的基础上,实现更大尺度的 5ms 延时函数:
void delay_5ms(unsigned int n)
{
while(n--)
delay_10us(5);
}
使用 delay_5m() 函数替换之前定义的“不精确”延时函数 delay() 函数,这样我们就能知道较为精确的 LED 小灯的控制程序执行时间,也就能更加方便的替换 delay_10us(50000); 语句。
现在写出“呼吸”1 秒的 LED “呼吸灯”C语言程序就不难了,请看:
void led_breath_1s()
{
static int cnt = 0;
static char dark_time = 0, dir = 1 ;
while(1){
twinkle_once(dark_time);
if( 0==((cnt++)%4) ){
if(dir)
dark_time += 1;
else
dark_time -= 1;
if(dark_time >= 100)
dir = 0;
if(dark_time <= 60)
dir = 1;
}
if(cnt > 200){
cnt = 0;
break;
}
}
}
twinkle_once() 执行一次需要 5ms 的时间,cnt 计数到 200 次就恰好是 1 秒,另外,if( 0==((cnt++)%4) ) 语句仍然能够控制 LED 小灯的“呼吸”频率。其他部分的代码与之前介绍的就没什么不同了。
类似的,闪烁 1 秒的 LED “闪烁”C语言程序可以如下实现:
void led_twinkle_1s()
{
char cnt = 5;
while(cnt--){
P21 = 1;
delay_10us(10000);
P21 = 0;
delay_10us(10000);
}
}
闪烁一次需要 200 ms 的时间,那么闪烁 5 次就是 1秒了。现在使用 led_breath_1s() 和 led_twinkle_1s() 函数替换delay_10us(50000); 语句就可以了。请看:
void main()
{
char dat[5] = {0};
int cks;
init_uart(9600);
set_timer0(10); // 10us
prints("init...\r\n");
// delay_10us(50000);delay_10us(50000);
// delay_10us(50000);delay_10us(50000);
led_breath_1s();
led_breath_1s();
prints("program start...\r\n");
while(1){
dht11_start();
dht11_read_once(dat);
cks = 0;
cks += dat[0]; cks += dat[1];
cks += dat[2]; cks += dat[3];
if((char)cks == dat[4]){
prints("RH: ");printn(dat[0]);prints(".");printn(dat[1]);prints(" ");
prints("TM: ");printn(dat[2]);prints(".");printn(dat[3]);prints("\r\n");
}else{
prints("capture failed\r\n");
}
if(dat[2] > 15)
led_twinkle_1s();
else
led_breath_1s();
//delay_10us(50000);delay_10us(50000);
}
}
初始化时,使用了 led_breath_1s(); 代替了 DHT11 传感器需要的延时,每次获取温湿度值的时候,则判断了温度值,如果温度大于 15 摄氏度,使用LED的“闪烁”程序代替延时 1秒,否则使用 LED 的“呼吸灯”程序代替。
现在编译C语言程序,烧写到单片机,可以得到如下结果:
一开始,DHT11 探测到的温度较低,所以LED指示灯是“呼吸”状态的,然后使用手捏住 DHT11,温度升高超过“阈值”后,LED 小灯就变成“闪烁”状态了。
还有问题吗?
上面的控制程序还不是很完美,相信细心的朋友应该看出来了,LED小灯处于“呼吸”状态时,总是会有一次闪烁。这是因为我们采集 DHT11 传回来的温湿度信息时,也有几十毫秒的延时,在这段时间内,单片机是无法控制 LED 小灯的,所以小灯会一直处于最近一次的亮暗状态,查看 led_twinkle_1s() 函数的C语言代码就知道,LED 小灯最后的状态是亮的状态,这就解释了我们遇到的问题。
这其实也是单片机编程的一个特点,若单片机内部没有运行操作系统,则编写并行的程序几乎是不可能的,所以串行的阻塞延时应该尽量避免,否则就会出现类似上面的问题。那么,上面这种问题不借助于操作系统也能解决吗?当然可以,借助于单片机的中断系统就很好解决,限于篇幅,以后再说了。