打印
[51单片机]

从业将近十年!手把手教你单片机程序框架(连载)

[复制链接]
楼主: jianhong_wu
手机看帖
扫描二维码
随时随地手机跟帖
741
徐文涛| | 2015-8-28 22:32 | 只看该作者 回帖奖励 |倒序浏览
鸿哥,每个.C的函数都得声明吗,如果某个函数其他所有文件都不用,是不是可以不声明

使用特权

评论回复
742
光光ing| | 2015-8-30 08:12 | 只看该作者
学习,谢谢前辈

使用特权

评论回复
743
月影风| | 2015-9-26 10:48 | 只看该作者
来支持下。

使用特权

评论回复
744
whirt_noob| | 2015-10-9 23:18 | 只看该作者
jianhong_wu 发表于 2014-3-5 22:06
第九节:独立按键的双击按键触发。

开场白:

else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数

这儿为什么是ucKeyLock1==0而不是Key_sr1==0啦?看了半天没想明白,希望楼主指教一下!:L

使用特权

评论回复
745
yr_xie| | 2015-10-14 10:53 | 只看该作者
吴工:
    有遥控的器的案例不

使用特权

评论回复
746
whirt_noob| | 2015-10-19 22:41 | 只看该作者
jianhong_wu 发表于 2014-3-9 20:03
第三十三节:能设置速度档位的数码管倒计时程序。

开场白:

定义太多的全局变量会占用太多的内存,这样在大一点的项目中会不会有影响?如何减少全局变量?

使用特权

评论回复
747
jianhong_wu|  楼主 | 2015-11-28 01:39 | 只看该作者
本帖最后由 jianhong_wu 于 2015-11-28 14:34 编辑

第九十节:针对行程开关感应器,分享一种既能及时响应,又能抗干扰处理的识别思路。
         我今天在做数控三轴运动卡的项目时,正在考虑ARM单片机和FPGA如何检测步进电机回原点的问题,灵感一来,想到了一种我很满意的行程开关识别思路。为了说明此思路的优越性,我会先列出前面两种常用的思路作对比。并且举一个最简单的工控项目来说明。
      此工控项目要求:上电后,一个普通电机控制一个滑块从左边往右边推,最右边有一个行程开关,滑块碰到行程开关后,电机停止,运动结束。
       转化成单片机编程思路 :用1个IO输出高电平时电机运动,输出低电平时电机停止。另外再用1个IO口作输入,检测行程开关的电平状态,如果发现是高电平说明电机还没碰上行程开关感应器,如果发现是低电平就说明碰上了感应器,此时就可以发出停止电机的命令。
        就是这么简单的过程,其实在识别开关感应器时暗藏玄机,现在一一解剖分析给大家:
        第一种思路:直接判断行程感应器的电平状态,一旦发现低电平,就认为电机已经碰到了行程开关,马上停止电机。
        优点:响应及时。
        缺点:太灵敏了,以至于抗干扰能力非常差,在工控环境里,当电机正在行进的过程中,如果受到电源的波动或者外来的毛刺信号干扰,行程开关的输入信号可能会读取到瞬间的低电平,导致单片机误判断,提前把电机停止了,电机还没碰到行程开关就草率停机。
        源码如下:
#include "REG52.H"


#define CONT_TIME_20MS   10   //大概10次中断相当于20毫秒

void motor_run();      //电机往右边移动
void motor_stop();     //电机停止
void motor_pause();    //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样

void T0_time();  //定时中断函数


sbit motor_dr=P3^5; //控制电机的启动和停止的输出  对应朱兆祺学习板的独立LED灯
sbit right_sr=P0^2; //右边的行程开关检测输入  对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平

unsigned char u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
unsigned char u8MotorFlagBackup=0;  //用来记录上一次的电机状态,用于判断电机的状态是否发生了改变。

unsigned char u8TimeFlag=0; //计时器的开关
unsigned int  u16TimeCnt=0; //计时器的计时变量

unsigned char u8RunStep;  //运动控制的步骤变量

void main()
{
   key_gnd_dr=0; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平
   TMOD=0x01;  //设置定时器0为工作方式1

   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f   大概每2ms产生一次中断,我没有详细计算。
   TL0=0x2f;

   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

   u8RunStep=1;  //一上电,电机就开始从第1步开始启动
   while(1)   
   {
       switch(u8RunStep)
       {
          case 1:   
               motor_run();     //电机往右边移动
               u8RunStep=2;  //切换到下一个步骤
               break;

           case 2:    //等待滑块移动到最右边,直到触发了最右边的行程开关感应器。
               if(0==right_sr)  //右边的行程感应器被触发
               {
                                motor_stop();     //电机停止
                    u8RunStep=3;       //马上切换到电机停止的步骤
               }
               break;

           case 3:    //电机处于停止的步骤,除非重新断电重启,否则就一直处于此步骤状态
                break;
       }
   }

}



void T0_time() interrupt 1   //定时中断,大概每2ms就中断一次
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f   大概每2ms产生一次中断,我没有详细计算。
  TL0=0x2f;
  TR0=1;  //开中断
}



void motor_run()      //电机往右边移动
{
    motor_dr=1;
    u8MotorFlag=1;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_stop()     //电机停止
{
    motor_dr=0;
    u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_pause()    //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
{
    motor_dr=0;
    u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}





       第二种思路:在判断行程感应器的电平状态时,加入了软件的抗干扰处理,一旦发现低电平,一个计时器开始计时,在计时的期间,如果发现出现高电平就马上把计时器清零,如果一直是低电平,并且期间没有出现高电平,就认为是稳定的低电平,此时判定是碰到了行程开关。
        优点:增加了抗干扰处理,几乎能百分百保证电机碰到了行程开关才停机,几乎不会误判了。
        缺点:在软件抗干扰环节增加了一小段延时,而这一小段的延时,会导致电机碰到行程开关后没有马上停止,滑块继续往右边运动了一段时间才停止,时间长了容易把右边的限位机械结构压坏挤坏,因为有应力存在。
        源码如下:
#include "REG52.H"


#define CONT_TIME_20MS   10   //大概10次中断相当于20毫秒

void motor_run();      //电机往右边移动
void motor_stop();     //电机停止
void motor_pause();    //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样

void T0_time();  //定时中断函数


sbit motor_dr=P3^5; //控制电机的启动和停止的输出  对应朱兆祺学习板的单个LED灯
sbit right_sr=P0^2; //右边的行程开关检测输入   对应朱兆祺学习板的S9键
sbit key_gnd_dr=P0^4; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平

unsigned char u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
unsigned char u8MotorFlagBackup=0;  //用来记录上一次的电机状态,用于判断电机的状态是否发生了改变。

unsigned char u8TimeFlag=0; //计时器的开关
unsigned int  u16TimeCnt=0; //计时器的计时变量

unsigned char u8RunStep;  //运动控制的步骤变量

void main()
{
   key_gnd_dr=0; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平

   TMOD=0x01;  //设置定时器0为工作方式1

   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f   大概每2ms产生一次中断,我没有详细计算。
   TL0=0x2f;

   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

   u8RunStep=1;  //一上电,电机就开始从第1步开始启动
   while(1)   
   {
       switch(u8RunStep)
       {
          case 1:   
               motor_run();     //电机往右边移动

               u8TimeFlag=0; //计时器关
               u16TimeCnt=0; //计时器的计时变量清零

               u8RunStep=2;  //切换到下一个步骤
               break;

           case 2:    //等待滑块移动到最右边,直到触发了最右边的行程开关感应器。
               if(0==right_sr)  //右边的行程感应器被触发
               {
                    u8TimeFlag=1; //计时器开
                    if(u16TimeCnt>=CONT_TIME_20MS)  //连续20ms内都是低电平,则认为是低电平
                    {
                       u8TimeFlag=0; //计时器关
                       u16TimeCnt=0; //计时器的计时变量及时清零

                                   motor_stop();     //电机停止
                       u8RunStep=3;       //马上切换到电机停止的步骤
                    }
               }
               else  //如果是高电平
               {
                    u8TimeFlag=0; //计时器关
                    u16TimeCnt=0; //计时器的计时变量及时清零
               }
               break;

           case 3:    //电机处于停止的步骤,除非重新断电重启,否则就一直处于此步骤状态
                break;
       }
   }

}



void T0_time() interrupt 1   //定时中断,大概每2ms就中断一次
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  if(1==u8TimeFlag)
  {
     u16TimeCnt++;
  }


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f   大概每2ms产生一次中断,我没有详细计算。
  TL0=0x2f;
  TR0=1;  //开中断
}



void motor_run()      //电机往右边移动
{
    motor_dr=1;
    u8MotorFlag=1;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_stop()     //电机停止
{
    motor_dr=0;
    u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_pause()    //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
{
    motor_dr=0;
    u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}




       第三种思路:本思路是结合了前面两种的优点,在判断行程感应器的电平状态时,当发现是低电平时(哪怕是干扰时出现的瞬间低电平),电机马上暂停(暂停和停止的概念不一样,虽然电机都是没有转),当发现是高电平时,电机继续运行,什么时候才认为碰到行程开关?当低电平像第二种思路那样连续持续低电平的时间超过某个值时,才认为碰到了行程开关。巧妙的是,在此判断低电平的小延时期间,电机是处于暂停的状态(没有转),所以不会过冲挤压右边的行程限位机构。
        优点:既能及时响应,又增加了行程开关检测的抗干扰处理,又不会让电机过冲挤压右边的行程开关。
        缺点:暂时还没想出来。
        源码如下:
#include "REG52.H"


#define CONT_TIME_20MS   10   //大概10次中断相当于20毫秒

void motor_run();      //电机往右边移动
void motor_stop();     //电机停止
void motor_pause();    //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样

void T0_time();  //定时中断函数


sbit motor_dr=P3^5; //控制电机的启动和停止的输出,对应朱兆祺学习板的单个LED灯
sbit right_sr=P0^2; //右边的行程开关检测输入,对应朱兆祺学习板的S9键

sbit key_gnd_dr=P0^4; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平

unsigned char u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动

unsigned char u8RightSrFlagBackup=0;  //用来记录上一次的行程开关电平状态,用来判断行程开关是否发生过电平变化

unsigned char u8TimeFlag=0; //计时器的开关
unsigned int  u16TimeCnt=0; //计时器的计时变量

unsigned char u8RunStep;  //运动控制的步骤变量

void main()
{
   key_gnd_dr=0; //矩阵键盘模拟独立按键的地GND,因此必须一直输出低电平

   TMOD=0x01;  //设置定时器0为工作方式1

   TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f   大概每2ms产生一次中断,我没有详细计算。
   TL0=0x2f;

   EA=1;     //开总中断
   ET0=1;    //允许定时中断
   TR0=1;    //启动定时中断

   u8RunStep=1;  //一上电,电机就开始从第1步开始启动
   while(1)   
   {
       switch(u8RunStep)
       {
          case 1:   
               motor_run();     //电机往右边移动
               u8RightSrFlagBackup=1;  //用来判断行程开关是否发生过电平变化

               u8TimeFlag=0; //计时器关
               u16TimeCnt=0; //计时器的计时变量清零

               u8RunStep=2;  //切换到下一个步骤
               break;

           case 2:    //等待滑块移动到最右边,直到触发了最右边的行程开关感应器。
               if(0==right_sr)  //右边的行程感应器被触发
               {
                                if(u8RightSrFlagBackup!=0) //行程开关的电平发生过变化
                                        {
                                           u8RightSrFlagBackup=0; //及时更新,避免一直触发电机的命令操作
                                           motor_pause();    //电机及时暂停
                                        }

                    u8TimeFlag=1; //计时器开
                    if(u16TimeCnt>=CONT_TIME_20MS)  //连续20ms内都是低电平,则认为是低电平
                    {
                       u8TimeFlag=0; //计时器关
                       u16TimeCnt=0; //计时器的计时变量及时清零

                                   motor_stop();     //电机停止
                       u8RunStep=3;       //马上切换到电机停止的步骤
                    }
               }
               else  //如果是高电平
               {

                                if(u8RightSrFlagBackup!=1) //行程开关的电平发生过变化
                                        {
                                           u8RightSrFlagBackup=1; //及时更新,避免一直触发电机的命令操作
                       motor_run();      //电机继续往右边移动
                                        }
                    u8TimeFlag=0; //计时器关
                    u16TimeCnt=0; //计时器的计时变量及时清零
               }
               break;

           case 3:    //电机处于停止的步骤,除非重新断电重启,否则就一直处于此步骤状态
                break;
       }
   }

}



void T0_time() interrupt 1   //定时中断,大概每2ms就中断一次
{
  TF0=0;  //清除中断标志
  TR0=0; //关中断

  if(1==u8TimeFlag)
  {
     u16TimeCnt++;
  }


  TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f   大概每2ms产生一次中断,我没有详细计算。
  TL0=0x2f;
  TR0=1;  //开中断
}



void motor_run()      //电机往右边移动
{
    motor_dr=1;
    u8MotorFlag=1;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_stop()     //电机停止
{
    motor_dr=0;
    u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}
void motor_pause()    //电机暂停,虽然暂停里面的代码跟停止的代码一致,但是概念不一样
{
    motor_dr=0;
    u8MotorFlag=0;  //电机的实时运动状态映射在此变量里,此变量的0和1代表了电机的停止和运动
}





         最后再提醒一下:凡是有行程限位开关的地方,速度都不宜太快,要控制好速度,因为高速会由于比较大的惯性产生碰击现象。如果需要高速的场合,最好能在到达终点前多增加一个感应器,在到达终点前做一些减速操作。如果是步进电机,也可以利用实时坐标信息来识别快到行程开关终点时做一些减速处理。
       (本连载仅为初稿,我计划在2017年从头开始重写一次此连载,融入我最新的技术理解,在原来的基础上进一步提炼和补充)


使用特权

评论回复
748
战斗机007| | 2015-12-2 17:44 | 只看该作者
鸿哥很久没更新了。最近忙啥,分享分享。

使用特权

评论回复
749
hanfei08131106| | 2015-12-3 17:27 | 只看该作者
继续学习中,不错的帖子。鸿哥继续加油啊!

使用特权

评论回复
750
高频脉冲| | 2015-12-12 00:17 | 只看该作者
不错,值得学习。

使用特权

评论回复
751
adofe| | 2015-12-12 11:24 | 只看该作者

果然是好东东,收藏。

使用特权

评论回复
752
叶与秋风舞2| | 2015-12-14 17:48 | 只看该作者
受教了!!!!!!!1

使用特权

评论回复
753
李良123| | 2015-12-16 17:30 | 只看该作者
最近怎么不更新了???

使用特权

评论回复
754
TMTZcml| | 2015-12-23 09:19 | 只看该作者
楼主辛苦了

使用特权

评论回复
755
yuanquan12345| | 2015-12-24 14:12 | 只看该作者
吴老师你好,第40节《常用的自定义串口通讯协议》,将程序下载到单片机后,用串口调试助手,怎么实现不了,控制功能?

使用特权

评论回复
756
j308374705| | 2015-12-24 17:06 | 只看该作者
收藏!

使用特权

评论回复
757
kfhzy| | 2016-1-10 22:17 | 只看该作者
本帖最后由 kfhzy 于 2016-1-10 22:19 编辑
jianhong_wu 发表于 2014-3-5 23:06
真的不好意思。我毕业后就从来没有用汇编写过程序了。因为我遇到的项目都是可以直接用C语言实现的,如果 ...

楼主辛苦,只是楼主这句话不敢苟同,学汇编怎么了!学汇编,退 可以开发高效率的单片机或逻辑控制器,进 还可以做一个高水平的黑客,掌控任何操作系统如探囊取物

使用特权

评论回复
758
kfhzy| | 2016-1-11 00:06 | 只看该作者
cjseng 发表于 2014-4-6 00:47
你这样做对系统的影响就是定时周期不确定。你的周期=定时器周期+中断程序运行时间,而中断程序运行时间是 ...

我觉得吧,你说的是对的,他程序确实有毛病,但是他是拿他自己的代码说问题,这给新手很多鼓励,如果你能共享出来你的方案,既能实现他所说的功能,又能实现你所要求的精确定时该多好

使用特权

评论回复
759
李不走寻常路| | 2016-1-11 16:18 | 只看该作者
是的  这个的软件清除,要不然的话就会一直进串口中断

使用特权

评论回复
760
李不走寻常路| | 2016-1-11 16:21 | 只看该作者
我老师  麻烦问你一下  你在第四十节提到的原子锁, 我有点不懂 ,在哪里能找到相关的资料啥的 ,谢谢你。

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则