2018-10-06 14:22:51 m0_37697335 阅读数 606
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3401 人正在学习 去看看 朱有鹏

我理解的所谓数据驱动程序的方法,简单而言:将数据与程序分析,将代码逻辑的组织转换成数字规律的统计。即将数据作为一个要处理的脚本(当做数据库),然后程序作为解释器,将脚本的内容用代码逻辑解释出来,实现编程功能。

这就要求我们将代码逻辑分解出其内在数据关系,这样我们就可以写出与数据无关的函数,这样的函数通用性很大,利于复用,耦合性也比较低,利于修改,且修改后不易出错。

这样的方法也有坏处,就是可读性变差,解决的办法就是多写点关于逻辑的注释。

下面举个例子:(来自书籍--单片机编程魔法师之高级裸编程思想,挺好的一本书,可以看看,可以去下载下载链接处,待有缘人! )

(1)要实现如下波形:

(2)我们 一般的实现方法:将上面分成16段(依据是波形存在不一样的时候作为分界点),每段单独分析。代码如下(使用51单片机实现)。

#include <reg51.h>

sbit P10=P1^0;
sbit P11=P1^1;
sbit P12=P1^2;
sbit P13=P1^3;

void delay(unsigned char d, unsigned int t)
{
	unsigned int i;

	P1 = P1 & 0xF0 | d;
	for(i=0; i<t; i++);
}

main(void)
{
	while(1)
	{
		delay(0x00, 749);		// 阶段1
		delay(0x01, 251);		// 阶段2

		delay(0x00, 749);		// 阶段3
		delay(0x03, 251);		// 阶段4

		delay(0x00, 749);		// 阶段5
		delay(0x01, 251);		// 阶段6

		delay(0x00, 749);		// 阶段7
		delay(0x07, 251);		// 阶段8

		delay(0x00, 749);		// 阶段9
		delay(0x01, 251);		// 阶段10

		delay(0x00, 749);		// 論僇11
		delay(0x03, 251);		// 論僇12

		delay(0x00, 749);		// 阶段13
		delay(0x01, 251);		// 阶段14

		delay(0x00, 749);		// 阶段15
		delay(0x0F, 251);		// 阶段16
	}
}

(3)而采用数据驱动程序的方法:将每个阶段分离出数据,然后用一个函数统一处理。本例中使用数组来存储代表各个阶段的逻辑的数据,接着用一段代码就可解决,来代码如下:

#include <reg51.h>

sbit P10=P1^0;
sbit P11=P1^1;
sbit P12=P1^2;
sbit P13=P1^3;

#define STAGES		16
unsigned char code P1_1234[] = {0, 1, 0, 3, 0, 1, 0, 7, 0, 1, 0, 3, 0, 1, 0, 15};
unsigned int code Dts[] = {749, 251, 749, 251, 749, 251, 749, 251, 749, 251, 749, 251, 749, 251, 749, 251};

void delay(unsigned char d, unsigned int t)
{
	unsigned int i;

	P1 = P1 & 0xF0 | d;
	for(i=0; i<t; i++);
}

main(void)
{
	int i;

	while(1)
	{
		for(i=0; i<STAGES; i++)
			delay(P1_1234[i], Dts[i]);
	}
}

(4)好处:似乎没有看出了,牺牲了易读性,有得必有失,我们会的得到什么?

再举几个例子:实现以下波形。对于这个波形,如果我们不采用数据驱动程序的方法,那么相当于重写(重新分阶段,不难但是繁琐),而使用这种高阶的方法后,解释器代码一个字母都不用改变。

数据驱动程序的代码如下:

#include <reg51.h>

sbit P10=P1^0;
sbit P11=P1^1;
sbit P12=P1^2;
sbit P13=P1^3;

#define STAGES		8
unsigned char code P1_1234[] = {0, 1, 3, 7, 15, 7, 3, 1};

void delay(unsigned char d, unsigned int t)
{
	unsigned int i;

	P1 = P1 & 0xF0 | d;
	for(i=0; i<t; i++);
}

main(void)
{
	int i;

	while(1)
	{
		for(i=0; i<STAGES; i++)
			delay(P1_1234[i], 500);
	}
}

(5)总结:

虽然数据驱动代码这种高阶的方法不是万能,但是对于相似的编程要求,则给我们提供了一种可复用的方法,避免一些简单却繁琐的工作任务。

还有, 用这种方法记得写注释,不然不仅别人看不懂, 就连几天后的你再次回顾时,都不知道是怎么写出来的。

2014-05-21 13:04:57 xiezhihua120 阅读数 701
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3401 人正在学习 去看看 朱有鹏

实践

1.嵌入式软件开发

2.嵌入式驱动开发

3.Linux内核移植

4.Linux驱动调试

5.Linux内核开发


2020-01-13 10:33:30 BaoTTing 阅读数 27
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3401 人正在学习 去看看 朱有鹏

51或者STM32开发中,我们在很多时候都会使用到数码管,数码管的刷新会占用很大的时间资源,少数的数码管不影响系统的运行,但如果数码管过多,刷新这一工作势必会影响系统的运行,下面来介绍一种芯片方案,单片机只需向芯片写入要显示的数字即可,不需要CPU自己来做动态刷新的工作。

原理图

#include <reg52.h>
#include <intrins.h>
#define u8 unsigned char
#define uint unsigned int
sbit DIN = P2^0;
sbit LOAD = P2^1;
sbit CLK = P2^2;
u8 Disp_Buffer[8]={2,0,0,9,10,8,10,9};
//软件延时函数,延时 xms
void DelayMS(uint x)
{
	u8 t;
	while(x--)
	{
		for(t=120;t>0;t--);
	}
} 
/向第 Num_Addr 片 7219 的 Addr 寄存器写入一个 Dat 数据
//Num_Addr:7219 地址(0~255)
//Addr:7219 数显寄存器地址(1~8)
//Dat:要显示的数据(0~15)(含字母)
void Write(u8 Num_Addr,u8 Addr,u8 Dat)
{
	u8 i,j;
	LOAD = 0;
	for(i=0;i<8;i++)
	{
		CLK = 0;
		if(Addr&0x80)
		DIN=1;
		else
		DIN=0;
		Addr <<= 1;
		CLK = 1;
		_nop_();
		_nop_();
		CLK = 0;
	} 
	for(i=0;i<8;i++)
	{
		CLK = 0;
		if(Dat&0x80)
		DIN=1;
		else
		DIN=0;
		Dat <<= 1;
		CLK = 1;
		_nop_();
		_nop_();
		CLK = 0;
	} 
	for(i=0;i<Num_Addr;i++)
	{
		for(j=0;j<16;j++)
		{
			CLK = 0;
			DIN = 0;
			CLK = 1;
			_nop_();
			_nop_();
			CLK = 0;
		}
	}
	 LOAD = 1;
} 
void Initialise7219()//初始化
{
	//第 0 个 7219 初始化
	Write(0x00,0x09,0xff); //编码模式
	Write(0x00,0x0b,0x07); //全部扫描
	Write(0x00,0x0c,0x01); //正常模式:1,掉电模式:0
	Write(0x00,0x0a,0x07); //消隐时间设为 T/2
	//第 1 个 7219 初始化
	Write(0x01,0x09,0xff); //编码模式
	Write(0x01,0x0b,0x07); //全部扫描
	Write(0x01,0x0c,0x01); //正常模式
	Write(0x01,0x0a,0x07); //消隐时间设为 T/2
}
void main()
{
	u8 i;
	Initialise7219();
	DelayMS(1);
	for(i=0;i<8;i++)
	{
		Write(0x00,i+1,Disp_Buffer[i]);
	} 
	Write(0x01,1,Disp_Buffer[0]);
	Write(0x01,2,11);
	Write(0x01,3,12);
	Write(0x01,4,13);
	Write(0x01,5,14);
	while(1);
} 
/*大学生单片机交流 QQ 群>>677033632*/
2018-10-04 22:16:47 m0_37697335 阅读数 431
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3401 人正在学习 去看看 朱有鹏

《单片机编程魔法师之高级裸编程思想》以单片机裸环境为基础,为编程者定义了一个微操作系统(MOS)的编程环境,并面向应用中不断提高的需求对编程策略进行了深度剖析与研究,从而分离出数据驱动、并行多任务、面向对象等重要编程思想。这些思想既可独立运用,又可有机结合成一个体系,是我们实践中解决问题的致胜法宝。《单片机编程魔法师之高级裸编程思想》以实例为基础,分6章对这一思想体系进行了阐述。阐述通常以提出问题开始,然后针对解决问题的现状,从心理学的角度对问题展开讨论,力求将容易遇见的问题一网打尽。《单片机编程魔法师之高级裸编程思想》通过一些列的优化过程对思想要点进行完整描述,然后通过软件仿真手段给读者一个清晰的认识,并在最后进行归纳总结。

《单片机编程魔法师之高级裸编程思想》既介绍了思想,又介绍了使用思想的方法。无论您是单片机自动化领域的初行者,还是资深的单片机自动化领域的工程师,《单片机编程魔法师之高级裸编程思想》都将成为您的得力帮手。希望这种既有理论又有方法论的阐述方式能帮助读者在事业上更上一层楼。

该书PDF格式的链接:链接:https://pan.baidu.com/s/1WIyZvfq__jCOaZU_B4Mz1w   提取码:mldv

下面是我的一些体会和总结:

(一)数据驱动程序思想描述文章链接:数据驱动程序

 

 

2019-03-05 00:31:36 hugetree_bibi 阅读数 47
  • 51单片机综合小项目-第2季第4部分

    本课程是《朱有鹏老师单片机完全学习系列课程》第2季第4个课程,也是51单片机学完之后的一个综合小项目,该项目运用了开发板上大多数外设设备,并将之结合起来实现了一个时间、温度显示以及报警功能、时间调整功能等单片机控制常见的功能,有一定代码量,需要一定调试技巧和编程能力来完成,对大家是个很好的总结和锻炼,并且能拓展项目经验。

    3401 人正在学习 去看看 朱有鹏

       很多产品都会用到io按键这一成本很低的,技术也很容易实现的产品。如果是刚入行的单片机工程师来说,写一个完整的具有实用性和稳定性和扩展性的按键驱动还是比较困难的,这篇博客主要是结合自己的实际经验之谈,解析一下普通的Io按键驱动如何写和实现,如果有大神看出有错误的东西,请指教。此篇作为学习和交流之用。谢谢大家。

       首先普通的io 口按键来说的话,主要分为两种情况,一种是低推按键,另一种是高推按键。低推按键指的就是没有按下去的时候是高电平,按下去之后会检测到低电平,io口一般配置为内部上拉输入的模式。高推按键的话就是指平常为低电平,按下去之后检测的io口应该为高电平,io设置为下拉输入的情况。图1-0为高推按键,图1-2为低推按键。

图1-2
图1-1

 

     这种轻触按键按下去松手都会有一个抖动的过程,所以大家在看很多关于按键驱动这类文章的话很多都会有一个防抖的程序,这是能更稳定的检测。实际工作中,一般会根据单片机芯片的特性及资源来写这类驱动的代码。最简单的应该需要一个按下去的检测以及松手的检测,然后加入短按,长按,连续按,双击等比较全面的操作。可能还需要检测同时按这些功能。所以就需要一个比较完整的驱动代码来实现这一类的问题。其实这种驱动写好了,以后对于类似的像矩阵按键,遥控按键还有触摸按键等都会有一个很好的参考作用。废话不说了,下面就开始进行干货的派发。

      首先,配置好io脚之后,第一步就是检测什么时候按下去,这个检测得出键值一般放在主循环函数里面,对于8位单片机系统来说应该是最快的。

//例如,一个低推按键的io口为PB1口,检测放在主循环里面

#define KEY_NO  0xff
#define KEY_1   0x01

#define KEY_PORT PB1
 
//取得键值的函数
u8 Get_key_value(void)
{
     u8 key_value;
    if(!KEY_PORT)//KEY_PORT代表PB1,按下去的时候为低电平
     {
         key_value=KEY_1;      
     }
     else
     {

         key_value=KEY_NO;
     }  

     return key_value;

}


void main(void)
{
       Sys_init();
        while(1)
        {
            asm(clrwdt);//清除看门狗
            gu8_key_value=Get_key_value();//放的位置为主循环,一般在us级别内就能扫一次
            if(main_loop)//1ms进来一次
            {
               main_loop=0;
      

            }
    


        }
         




}

       上面这段小代码就是取得键值的方法,上面说过,按键一般都会有抖动的存在,所以需要清除抖动的影响,抖动按照我的理解以及工作的经验来看,应该是发生在按下去的瞬间,会有一段小波动,以及松手的时候也会有小波动,按着不放的情况下应该有几率存在一定的毛刺。所以一个保险的按键驱动这些方面都要考虑清楚。为了不阻塞单片机工作,我们可以将去抖程序利用单片机的定时器计时来解决。上面的程序主程序的主循环里面就已经默认规划好了1ms进入一次中断,防止按键抖动以及判断长短按这些都需要放在这里面。     

//加入一段防抖函数,放在1ms中断里面


void key_value_filter(u8 key_value)
{

        static u16 key_press_cnt;//按下去的检测
        static u8  key_release_cnt;//松手的检测 
        static u8 back_key;//存储按下去的键值   

          if(key_value!=KEY_NO)
        {
    
             key_press_cnt++;
             key_release_cnt=0;//松手计数清零
             if(key_press_cnt==15)//有些按键的作用只有短按,实际工作中具体分析
              {


              }
              else if(key_press_cnt==1000)//到达长按的阈值,1s
              {


              }
              else if(key_press_cnt>=1300)//到达了长按不放的阈值,1.3s
              {

                     key_press_cnt=1000;//防止超过参数的最大值,同时防止长按继续触发

              }
             


        }
       else
        {
               key_release_cnt++;//检测到松手电平了,不要马上判断说是松手了
               if(key_release_cnt>=30)//约30ms稳定的松手电平之后就判断为松手,
                {
                     key_release_cnt=0;
                     if(key_press_cnt>=15 && key_press_cnt<1000)//大于15ms小于1s判断为短按
                        {
                               //待会继续补充 

                        }   
                       key_press_cnt=0;//只有真正松手了才会清掉这个计数             
                }
               
              

        }
      



}

      上面这段去抖函数只是一个框架,这里面还是有很多需要优化的地方,突出的思想就是检测到一个按键真正按下至少需要15ms的稳定电平,同样松手也要检测几十毫秒才能真正判断是松手。中间按下去之后计时,这个时间就可以用来判断短按,长按和连续按着不放了。好了,主要思想大家应该懂了,为什么说C语音移植性和可修改能力强呢,因为其中会运用到大量的宏和简便易懂的参数名。现在就对上面这一小段代码进行优化,看完后大家就会懂了。

//加入一段防抖函数,放在1ms中断里面


//宏定义
//具体需要的时间以后直接改变宏的参数就ok了!
#define KEY_SHORT_CNT  15  //短按标准
#define KEY_LONG_CNT      1000
#define KEY_HOLD_CNT      1300
#define KEY_RELEASE_CNT    30  //松手


//定义按键按下去的状态
#define KEY_SHORT_UP  0x01//短按松手放开
#define KEY_SHORT     0x05 //执行一次短按之后松开手之后不需要呼应
#define KEY_LONG_UP   0x02//长按松手后执行消息
#define KEY_HOLD      0x03//长按不放,需要持续性消息
#define KEY_LONG      0x04 //长按


//原来的防抖函数升级成防抖加上执行动作的按键主函数

void key_value_filter(u8 key_value)
{

        static u16 key_press_cnt;//按下去的检测
        static u8  key_release_cnt;//松手的检测 
        static u8  back_key;//存储按下去的键值  
        u8 key_state=KEY_NO;//初始化键值的状态 

          if(key_value!=KEY_NO)
        {
          
            if( back_key!=key_value)//快速变换按键的情况下,以最新的按键为主
              {
                   back_key=key_value;
                   key_press_cnt=0;

              }
             key_press_cnt++;
             key_release_cnt=0;//松手计数清零
             if(key_press_cnt==KEY_SHORT_CNT )//有些按键的作用只有短按,实际工作中具体分析
              {
                   key_state=KEY_SHORT;

              }
              else if(key_press_cnt==KEY_LONG_CNT)//到达长按的阈值,1s
              {

                 key_state=KEY_LONG;
              }
              else if(key_press_cnt>= KEY_HOLD_CNT)//到达了长按不放的阈值,1.3s
              {

                     key_press_cnt=KEY_LOMG_CNT;//防止超过参数的最大值,同时防止长按继续触发
                      key_state=KEY_HOLD;


              }
             


        }
       else
        {
               key_release_cnt++;//检测到松手电平了,不要马上判断说是松手了
               if(key_release_cnt>=KEY_RELEASE_CNT)//约30ms稳定的松手电平之后就判断为松手,
                {
                     key_release_cnt=0;
                     if(key_press_cnt>=KEY_SHORT_CNT && key_press_cnt<KEY_LONG_CNT)//大于15ms小于1s判断为短按
                        {
                               //待会继续补充 
                                 key_state=KEY_SHORT_UP;

                        }   
                      else if(key_press_cnt>=KEY_LONG_CNT)
                        {
                                  key_state=KEY_LONG_UP;

                        }
                       key_press_cnt=0;//只有真正松手了才会清掉这个计数             
                }
               
              

        }
      



}

   上面通过一个static u16 key_press_cnt以及定时器中断就能判断出短按,长按,长按不放等的key的状态,这时候基本就是一个成型的按键驱动了,在工程应用中,只要通过不同的键值及状态去执行不同的功能,就能完成简单以及比较复杂点点的工程了。这篇博文就结束了,下面继续下一个博文会对按键深一步解析和增加内容

 

 

 

从单片机到嵌入式

阅读数 244

MCU驱动74hc595

阅读数 358

没有更多推荐了,返回首页