前言
之前参加了两个设计,一个是工程训练赛,一个是声控小车的设计,而这两道题目的核心都用到了那个核心关键字static!!!
回顾Static关键字
一、static全局变量
该变量只能在定义的那个文本中使用,不能被另外一个文本直接使用。(可以用函数进行调用)
二、static局部变量
1、位置:静态局部变量被编译器放在全局存储区,所以虽然它是局部的,但是在整个程序的生命周期中存在;
2、访问权限:静态局部变量只能被其作用域内的变量或函数访问;
3、值:静态局部变量如果没有初始化,则会被编译器自动赋0,以后每次调用静态局部变量的都用上一次调用后的值;
其实这个关键字就在按键中!
我们先来回顾一下单片机教程中的按键实验。在按键实验中我们知道,我们按下按键的瞬间单片机采集到的引脚电平信号是抖动的,所以需要进行消抖处理。按键的消抖,可用硬件或软件两种方法,在实际的设计中,为了节省硬件成本,大多数情况会选择使用软件消抖。
一、延时消抖
软件消抖,即检测出按键闭合后执行一个延时程序,5ms~10ms 的延时,让前沿抖动消失后再一次检测键的状 态,如果仍保持闭合状态电平,则确认为真正有键按下。当检测到按键释放后,也要给 5ms~10ms 的延时,待后沿抖动消失后才能转入该键的处理程序。
以前常用的消抖算法往往就是**:判断按下->延时->再次判断是否按下->是,执行/否,退出。**
最简单的消抖原理,就是当检测到按键状态变化后,先等待一个 10ms 左右的延时时间,让抖动消失后再进行一次按键状态检测,如果与刚才检测到的状态相同,就可以确认按键已经稳定的动作了。
//按键处理函数
void ButtonScan(void)
{
static int ButtonFlag = 0;//按键标志,1代表松开,0代表按下
if(Button == 0)//第一次判断按键按下
{
delay_ms(10);//延时10ms,去抖动
if(Button == 0)//第二次判断按键按下
{
ButtonFlag = 1; //确认按键为按下,按键标志置1
}
}
}
二、利用定时器消抖
利用定时器消抖算法:通过中断每1ms对按键进行扫描,当检测到连续的稳定无抖动电平信号(长度可设置)之后,才进行相应的逻辑操作。
我们启用一个定时中断,每 1ms 进一次中断,扫描一次按键状态并且存储起来,连续扫描 25 次后,看看这连续25 次的按键状态是否是一致的。25 次按键的时间是 25ms,这 25ms 内如果按键状态一直保持一致,那就可以确定现在按键处于稳定的阶段,而非处于抖动的阶段。
工程训练赛项目
工程训练赛的题目是这样:车载机械臂从充电点出发,到中转置物台取相应的货物,并把货物放到相应的货架上面去,如从置物台1上取到绿色货物,小车需要把它放到货架1上去;取到红色货物,放到货架2上去;取到蓝色货物,放到3上去。
该题没有具体给代码,只是强调以下Static的重要性!
本文讲的只是车载机械臂的小车部分的一个核心点没有涉及到机械臂部分;那我们小车的核心设计部分就是如何让小车知道它要到哪里去?也就是小车从充电点出发以后,要怎么走才能到达货物台,因为地图是黑线相互交叉的,不可能只依靠循迹就能到达。所以我们的设计方案是这样,让小车每走过一条黑线就记一次数,这样我们观察一下中转置物台1的位置可以发现,只要小车走了3次黑线,就是到达目的地了,所以我们只需要定义一个count变量,用来计数小车走过的距离即可解决。
我们思考完后,兴致冲冲地干了起来,本以为应该就是这逻辑没错了,但是却遇到了big bug!(哈哈哈),就是小车并不按你设置的count=3而停下来,而是有了随机性!我靠,当初心里就想,不会搞这个还要用概率论吧!
老师看我们一直在那里调试不成功,就过来问了一下我们问题,听完,他说,你们想啊,你的红外传感器的采集速度是非常快的,小车过了一次黑线可能采集了好几次,count当然加了很多次啊!经过老师一点评,我们瞬间明白了,是啊,我们要让count加一,应该是小车的红外传感器从亮到灭之后才加一的!!!(碰到黑线,传感器灯会亮,没有碰到黑线灯会灭,所以经过由亮到灭就说明走过了一条黑线)。
而代码的思路也就变得清晰了,就是和按键实验样,在一个函数里面定义一个静态变量,进行第一个if语句,如果传感器识别到黑线后,delay一小段时间,在进行另一个if语句判断,如果传感器没有识别到黑线了,count才加1,否则不加1.
通过这么一改,小车果然乖乖听话了,当然概率论也不用上场了,哈哈哈!
声控小车
电子设计的题目是这样,要求设计一辆声控小车,使不同的声音控制小车的不同行进状态,实验时以LED灯的状态代表小车的状态。
最后设计后是:使用声音传感器(咪头),来进行声音指令的采集。用msp430来处理声音指令的信号,以点亮LED1和LED2来代替小车的行动。
LED1 亮 LED2 暗 ——开始状态
LED1 暗 LED2 暗 ——第一种状态(前进)
LED1 暗 LED2 亮 ——第二种状态(左转)
LED1 亮 LED2 亮 ——第三种状态(右转)
这道题目是这么分析的,题目要求我们用不同的声音来控制小车不同状态,那声音可以有很多种物理量来进行表示,最后选择了用声音的长短来表示(声音短表示前进、中表示左转、长表示右转),同时选择了咪头作为传感器来对声音进行采集。
但是咪头存在一个问题,就是它只能给单片机高电平和低电平,如果有声音就给高,无声音就给低。所以我们就得要设置一个变量count,然后不断对引脚进行检测,如果检测到高电平(有声音)count就加1,然后设置一个变量a=6,b=8,如果count小于6表示为短音,小车前进;如果在6到8表示为中音,小车左转;如果count大于8表示为长音,小车右转。
那么问题来了,当初我的count设置为全局变量,那就意味着,代码中主函数每走一遍count就会被清除,所以count永远也不会大于6。
(思考了很久,想到上次那个训练赛才发现我又忘了static了!!!)如果count定义为静态变量,那么它的值都是上一次调用完之后的值,所以它不会被覆盖掉,也就是主函数不管怎么执行,count也不会在受它影响了!后来改成static之后就解决问题了!
(也可以用定时器进行扫描,当初对单片机不是很理解!)
代码如下:
/*
* Key_Long_Short
* 硬件描述:Launchpad G2553开发板上P1.3接了一个按键,P1.0和P1.6各接了1个LED(用跳线帽连接)
* 功能描述:使用状态机判别长短按键,短按键切换LED1状态,长按键切换LED2状态
* Created on: 2013-4-8
* Author: Administrator
*/
#include "MSP430G2553.h"
//-----对状态进行宏定义-----
#define IDLE 0
#define SHORT 1
#define LONG 2
#define COUNTER_THRESHOLD 30 /*长键判别门限*/
//-----全局变量-----
unsigned char WDT_Counter=0; /*用于对按键按下时间进行计数*/
//-----在main函数前提前申明函数-----
void GPIO_init();
void WDT_init();
void Key_SM();
unsigned char LongClick_Dect();
void P13_OnShortRelease();
void P13_OnLongClick();
/******为符合阅读习惯,将main函数放最前面,但其他函数就必须提前声明***/
void main(void) {
WDTCTL = WDTPW + WDTHOLD; //关狗
GPIO_init();
WDT_init();
_enable_interrupts(); //开总中断
_bis_SR_register(LPM3_bits); //LPM3休眠
}
/******************************************************************************************************
* 名 称:GPIO_Init()
* 功 能:设定按键和LED控制IO的方向,启用按键IO的上拉电阻
* 入口参数:无
* 出口参数:无
* 说 明:无
* 范 例:无
******************************************************************************************************/
void GPIO_init()
{
//-----设定P1.0和P1.6的输出初始值-----
P1DIR |= BIT0+BIT6; //设定P1.0和P1.6为输出
P1OUT |= BIT0; //设定P1.0初值
P1OUT &= ~BIT6; //设定P1.6初值
//-----配合机械按键,启用内部上拉电阻-----
P1REN |= BIT3; //启用P1.3内部上下拉电阻
P1OUT |= BIT3; //将电阻设置为上拉
}
/******************************************************************************************************
* 名 称:WDT_init()
* 功 能:设定WDT定时中断为16ms,开启WDT定时中断使能
* 入口参数:无
* 出口参数:无
* 说 明:WDT定时中断的时钟源选择ACLK,可以用LPM3休眠。
* 范 例:无
******************************************************************************************************/
void WDT_init()
{
//-----设定WDT为-----
WDTCTL=WDT_ADLY_16;
//-----WDT中断使能-----
IE1|=WDTIE;
}
/******************************************************************************************************
* 名 称:WDT_ISR()
* 功 能:响应WDT定时中断服务
* 入口参数:无
* 出口参数:无
* 说 明:不能直接判断事件,需启用状态机
* 范 例:无
******************************************************************************************************/
#pragma vector=WDT_VECTOR
__interrupt void WDT_ISR(void)
{
//-----启用按键状态机-----
Key_SM();
}
/******************************************************************************************************
* 名 称:Key_SM()
* 功 能:判断出长短键
* 入口参数:无
* 出口参数:无
* 说 明:本状态机为Mealy型状态机,在Switch(State)中需要判断事件
* 范 例:无
******************************************************************************************************/
void Key_SM()
{
static unsigned char State; //状态机的状态变量
static unsigned char Key_Now; //记录按键的当前电平
unsigned char Key_Past=0; //记录按键的前一次电平
unsigned char Key_Dect=0; //按键状态值
Key_Past=Key_Now;
//-----查询IO的输入寄存器-----
if(P1IN&BIT3) Key_Now=1;
else Key_Now=0;
//-----电平前高后低,表明按下-----
if((Key_Past==1)&&(Key_Now==0))
Key_Dect=1;
//-----电平前低后高,表明按下-----
if((Key_Past==0)&&(Key_Now==1))
Key_Dect=2 ;
switch(State) //该状态机靠扫描的按键值Key_Dect跳转状态
{
case IDLE: WDT_Counter=0; //空闲状态对计数清零
if(Key_Dect==1) State=SHORT; break; //路径1
case SHORT: if(Key_Dect==2) //路径2
{
State=IDLE;
P13_OnShortRelease(); //短按事件处理函数
}
if(LongClick_Dect()) //路径3
{
State=LONG;
P13_OnLongClick(); //长按事件处理函数
}
break;
case LONG: WDT_Counter=0; //长按状态对计数清零
if(Key_Dect==2) State=IDLE; break; //路径4
default: State=IDLE; break;
}
}
/******************************************************************************************************
* 名 称:LongClick_Dect()
* 功 能:对WDT中断计时,计满清零并返回”长键“信息
* 入口参数:无
* 出口参数:无
* 说 明:
* 范 例:无
******************************************************************************************************/
unsigned char LongClick_Dect()
{
WDT_Counter++;
if (WDT_Counter==COUNTER_THRESHOLD)
{
WDT_Counter=0;
return(1);
}
else return(0);
}
/******************************************************************************************************
* 名 称:P13_OnShortRelease()
* 功 能:P1.3的短按事件处理函数,即当P1.3键被”短按“后,下一步干什么
* 入口参数:无
* 出口参数:无
* 说 明:使用事件处理函数的形式,可以增强代码的移植性和可读性
* 范 例:无
******************************************************************************************************/
void P13_OnShortRelease() //P1.3的事件处理函数
{
//----翻转P1.3IO电平-----
P1OUT ^= BIT0;
}
/******************************************************************************************************
* 名 称:P13_OnLongClick()
* 功 能:P1.3的长按事件处理函数,即当P1.3键被”长按“后,下一步干什么
* 入口参数:无
* 出口参数:无
* 说 明:使用事件处理函数的形式,可以增强代码的移植性和可读性
* 范 例:无
******************************************************************************************************/
void P13_OnLongClick() //P1.3的事件处理函数
{
//----翻转P1.6IO电平-----
P1OUT ^= BIT6;
}
总结
1、其实单片机的那些例程虽然跟项目比起来比较简单也比较有局限性,但是但我们之后遇到项目实战时仔细思考会发现,很多知识点是更这些例程息息相关的!
2、看完文章,记得把static记住哦!
彩蛋
记得收藏、点赞,博主祈求一点鼓励!!!