工作也已经三个多月了,谈点自己个人的观点吧,欢迎大家讨论!如有误人子弟的地方,欢迎指正!我平时不太善于写文字,所以文笔不好的话勿见怪,想加我好友聊天的话,我的QQ610967340!好了,废话不多说了,开始讲正题吧!
一、关于单片机分时
在大学,我们学习单片机程序,都是只有涉及到定时或需要精确计时的时候我们才会使用定时器,而其他情况下我们一般不用!例如我们学习流水灯,学习按键的时候都没有用定时器。按键所需要的消抖时间也是通过一个死循环(通过for语句或while语句实现的软件延时程序)来实现。其实CPU是很宝贵的,它在不断的执行我们所需要的功能,如果让它在那里等待,执行死循环,是在浪费它的资源。因为它执行死循环的时候,是不能干其他的事的,只有当死循环退出的时候,才能执行其他的功能。所以,我们应该让CPU分时的执行我们的功能,也就是说,当哪个功能发生的时候,我们才去执行它,否则不执行。其实操作系统的分时,也是利用定时器产生一个时间,然后所有的计时都是通过那个时间来实现。而定时器定时是在中断里实现的。所以,它不会浪费资源,只有当定时时间到了,才去执行我们需要的功能,定时时间没有到,就不执行。
下面我就讲讲如何用单片机的定时器来实现一个简易的分时机制,从而使各个功能函数有条不紊的执行。方法如下:
首先在一个C文件里定义如下变量
bit fTimer0_10ms = 0;//10ms计时到标志位
bit fTimer0_100ms= 0;//100ms计时到标志位
bit fTimer0_500ms = 0;//500ms计时到标志位
bit fTimer0_1min = 0;//1min计时到标志位
uchar dTimer0_100ms = 0;//100ms计时变量
uchar dTimer0_500ms = 0;//500ms计时变量
uchar dTimer0_1min = 0;//1min计时变量
/**********************************************************************
***************程序主循环**********************************************
**********************************************************************/
void main(void)
{
MPort_Init();//IO端口初始化函数
MTimer_Init();//定时器初始化函数
/************************************************************************************
****
************************************************************************************/
while(1){
if(fTimer0_10ms){
//--------------------------------------10ms程序区
fTimer0_10ms = 0;
//下面可以放一些优先级比较高的函数,例如按键扫描函数,按键功能函数等
MKey_Scan();//按键扫描函数,所有用到的按键都在这里扫描出来
MKey1_Function();//下面是每个按键的功能函数,这里为了方面说明,用按键1表示,下面可以写按键2......
}
if(fTimer0_100ms){
//--------------------------------------100ms程序区
fTimer0_100ms = 0;
//这里可以放一些需要100ms执行一次的函数,或者是声明一个全局变量,然后让它自加,从而可以放入一些需要1s执行一次的函数,例如i++;if(i == 10){//放入需要执行的函数},这样就可以实现一个功能,这个功能是1s执行一次的,没有到1秒是不会执行该功能的。通过这样的方法,我们可以实现任何我们想要的功能,只要我们设置一个时间变量,让它自加,然后放入10ms,100ms,500ms,1min四个中的一个,就可以了。再举个例子,比如我们要实现一个10min钟自动关机的功能MPower_OFF(),我们可以定义一个变量j,让它自加,if(j++ == 10){MPower_OFF();},我们把if(j++ == 10){MPower_OFF();}放到1min计时到标志下就可以了。不知道讲明白没有,其他的也一样。通过这种方法后,我们可以实现一个简易分时,从而不需要用死循环,包括按键扫描也不需要死循环,从而不浪费CPU资源。这种框架下的按键扫描程序在第二点会讲到。
}
if(fTimer0_500ms){
//--------------------------------------500ms程序区
fTimer0_500ms = 0;
MStandby_Off();
MPowerLow();
}
if(fTimer0_1min){
//--------------------------------------1min程序区
fTimer0_1min = 0;
}
}
}
void timer() interrupt 1
{
TH0=(65536-10000)/256;
TL0=(65536-10000)%256;
fTimer0_10ms = 1;
dTimer0_100ms++;
if(dTimer0_100ms == 10){
dTimer0_100ms = 0;
fTimer0_100ms = 1;
dTimer0_500ms++;
if(dTimer0_500ms == 5){
dTimer0_500ms = 0;
fTimer0_500ms = 1;
dTimer0_1min++;
if(dTimer0_1min == 120){
dTimer0_1min = 0;
fTimer0_1min = 1;
}
}
}
}
这里我只是在讲一个框架,所以大家不用去深究具体函数的功能,我也没有把具体函数的代码贴出来。这就是我要讲的单片机简单分时,有了这四个分时标志的话,需要延时的地方我们就不用去
delay了,比如按键扫描,写好了直接放到10ms程序区就可以了,而不用去delay,一到10ms产生一次中断,扫描一次按键,而不用像delay一样在那里死等,浪费CPU。
还有比如我们有一个一个小时才发生的任务,我们不可能去delay,这是我们可以声明一个变量,然后让他自加到60,如果到了60,再执行需要的任务,然后放到1min程序区就可以了。
至于其他的任务,也是那样用,比较紧急的任务,放10ms程序区,其他的根据任务根据自己的时间变量放到相应的程序区。不知道讲的明不明白,不明白的欢迎提问。
二、按键
在大学里面学习按键扫描,买的开发板,不用说,学习按键几乎都是一个模式。if判断IO端口电平,再delay。熟不知,用delay是非常浪费单片机CPU的资源的,因为delay是死循环,单片机一直在那里死等,不能干别的事情!所以我觉得这种方法不是一种好的按键扫描方法。下面就讲一下自己在工作中用的一种方法吧!
#define cKey_Connect_NUM 0x0f
#define Power_FUN_NUM 0x01//开机键
#define AddSub_FUN_NUM 0x02//加挡键
#define Touch_FUN_NUM 0x04//触摸键
#define DcOk_FUN_NUM 0x08//充电完成检测口
uchar dKey_Filter = 0;//滤波
uchar dKey_NUM = 0;//键值传递
uchar dKey_Process = 0;//按键处理标志
uchar dKey_TimerH = 0;
uchar dKey_TimerL = 0;
uchar dKey_L_flag = 0;//按键长按标志
/*********************************************************************************
** 1.函数名称: MKey_Scan(void).
** 功能描述: 按键扫描函数.
** 输入参数: None.
** 输出参数: None.
** 返回参数: None.
**********************************************************************************/
void MKey_Scan(void)
{
uchar gLocal_L;
if(FP11 == 1) gLocal_L |= 0X01;//开机没按下
else gLocal_L &= 0xFE;开机按下
if(FP20 == 1) gLocal_L |= 0X02;//加挡
else gLocal_L &= 0xFD;
if(FP24==0) gLocal_L |= 0X04;//触摸
else gLocal_L &= 0xFB;
if(FP50==1) gLocal_L |= 0X08;//充电完成检测口
else gLocal_L &= 0xF7;
if((gLocal_L&cKey_Connect_NUM) != cKey_Connect_NUM)
{ //有键按下
++dKey_Filter;
if(dKey_Filter > 3)
{ //滤波
dKey_Filter = 0;
dKey_NUM = gLocal_L&cKey_Connect_NUM;
gLocal_L = dKey_NUM ^ dKey_Process;
dKey_Process &= gLocal_L;
}
}
else
{
dKey_Process = 0;
dKey_Filter = 0;
dKey_NUM = cKey_Connect_NUM;
dKey_TimerH = 0;
dKey_TimerL = 0;
}
}
/*********************************************************************************
** 4.函数名称: MKeyAddSub_Function(void)
** 功能描述: 红光LED和马达档位切换功能函数.
** 输入参数: None.
** 输出参数: None.
** 返回参数: None.
**********************************************************************************/
void MKeyAddSub_Function(void){
if((dKey_NUM &AddSub_FUN_NUM) == 0)
{
if((dKey_Process &AddSub_FUN_NUM) == 0)
{
dKey_Process |= AddSub_FUN_NUM;
//这个函数中,前面语句都是确认该按键已经按下,执行到这里说明按下了,一下语句就是
//该函数要处理一些什么东西了,每个按键都是这种模式,具体里面代码大家不用去深究,只要知道这个框架,以后可以根据自己需要在里面添加代码
if(fPowerOn_flag) //开机状态下
{
dSYS_PowerOFF_Tmr = 0; //清定时1分钟关机变量
if(dKeyPowerOnCount == 1)
{
if(++dRedLed_Gear > 3) dRedLed_Gear = 0;
AT24CXX_WriteOneByte(0,dRedLed_Gear);
if(fTouchKey_flag == 1){
if(fExitDCPower_flag == 0)MPwm1_Out(pwmtable[dRedLed_Gear]);
else MPwm1_Out(TAB_EXPower_Led[dRedLed_Gear]);
}
MRed_Speed(dRedLed_Gear+1);
}
if(dKeyPowerOnCount == 2)
{
if(++dMotor_Gear > 3) dMotor_Gear = 0;
AT24CXX_WriteOneByte(1,dMotor_Gear);
MMotor_Speed(dMotor_Gear+1);
}
}
}
}
}
/*********************************************************************************
** 5.函数名称: MTouch_Function(void)
** 功能描述: 触摸按键处理函数.
** 输入参数: None.
** 输出参数: None.
** 返回参数: None.
**********************************************************************************/
void MTouch_Function(void)
{
if((dKey_NUM &Touch_FUN_NUM) == 0)//按键发生处理部分
{
if((dKey_Process &Touch_FUN_NUM) == 0)
{
dKey_Process |= Touch_FUN_NUM;
if(fPowerOn_flag){
fTouchKey_flag = 1;
dSYS_PowerOFF_Tmr = 0; //清定时1分钟关机变量
 _Standby = 0;
 _BackLightLed = 0; //关背光
FTC1ENB = 1;
FPWM1OUT = 1;
if(dKeyPowerOnCount == 1){
if(fExitDCPower_flag == 0)MPwm1_Out(pwmtable[dRedLed_Gear]);
else MPwm1_Out(TAB_EXPower_Led[dRedLed_Gear]);
}
else if(dKeyPowerOnCount == 2){ //固定红光为0挡 250mA
if(fExitDCPower_flag == 0)MPwm1_Out(pwmtable[0]);
else MPwm1_Out(TAB_EXPower_Led[0]);
}
dMotor_PWM_TMR = 0; //马达停止
}
}
}
else//按键没有发生处理部分
{
if(fTouchKey_flag == 1){
fTouchKey_flag = 0;
FPWM1OUT = 0;
FTC1ENB = 0;
 _BackLightLed = 1; //开背光
dMotor_PWM_TMR = 0; //马达停止
 _Standby = 1;
}
}
}
然后在第一部分讲的分时里面,把这三个函数放到10ms程序区,这样就可以中断扫描外部按键的按下与否了!而不用delay了。
这里变量gLocal_L是用来记录哪个按键按下的变量。这种按键扫描的方法还有个好处就是,你按下一个按键的同时,不会妨碍另外的按键正常工作,不像你delay的,你一直按着
一个按键不松开,再去按其他的按键,没有用!讲的比较乱,不知道大家懂了多少,不懂的可以提问!
还在这里提点,大家养成好的习惯,形成自己的代码风格。我写的也不咋滴,但是我在努力改进!例如:一般一个函数不要超过一屏,一个C文件配合一个.H文件等,这个自己根据个人喜好!
好了,今天就讲到这里吧,讲的比较乱,希望大家不要嫌弃!从现在开始,抛弃delay吧!只有像I2C那种很少的微秒级延时才去用delay,其他情况尽量不用!
|