jianhong_wu 发表于 2014-3-30 09:14

woairgzn 发表于 2014-3-28 22:07 static/image/common/back.gif
我就喜欢这样的实干家,现实生活也没有那么多理论,也不需要精确的理论分析,知道大概,多实践把问题解决了 ...

我很赞成你的观点。谢谢你的关注。

544539018 发表于 2014-3-30 14:26

高手啊

lf19880813 发表于 2014-3-30 14:43

真的非常感谢鸿哥的无私分享!让我完善程序的框架,我会一直关注鸿哥!希望自己能力也不断上升,传递鸿哥的正能量!

十月风城 发表于 2014-3-30 18:01

jianhong_wu 发表于 2014-3-30 09:11 static/image/common/back.gif
我觉得你误会了我想表达的意思。我想很多人不会这样解答我第一节的内容。
(1)汇编语言就没有指针的概念 ...

以下纯属个人理解:
         1、 C语言说白了,是对输入数据的处理与加工,并给出输出。在进行数据处理过程中,会有:输入的数据,处理的数据和输出的数据。当数据数量比较少的时候,我们还可以给他们一一起个名字,但是当需要处理更多的数据时怎么办,这时就需要指针了。现在产品越来越复杂,团队合作开发一个产品是一个趋势。当队友需要帮助时,你只能因为不懂指针,爱莫能助啊!换句话说,现在单片机厂商为了提高竞争力,会自动产生底层驱动,来减少软件的开发时程,他们的代码里面也会有指针。所以说不要让指针成为自己的短板。
         2、我现在是做通讯类的产品,也用的是单片机,当接收和发送数据时,对时间都有很高的要求;还有一个硬线信号的输入,需要防反跳的一个时间;单片机上电,多长时间开始工作,需要根据电源部分什么时候电源稳定(电容的充放电)来决定时间,等等;那么这是功能对时间的要求。另一方面,有的客户很关心产品的反应时间,那就需要工程师根据软件的运行,来对反应时间做一个评估。那这都是前期设计需要产出的东西。
大白话:
         说实话,我也没有多少工作经验,只是发泄一下:老板,你要求真。。严格。不多说了,都是痛!

cjseng 发表于 2014-3-30 21:24

jianhong_wu 发表于 2014-3-30 09:11 static/image/common/back.gif
我觉得你误会了我想表达的意思。我想很多人不会这样解答我第一节的内容。
(1)汇编语言就没有指针的概念 ...

1.51的汇编里就有指针的概念,不过它不叫指针,叫做间接寻址,用@来表示,高128字节的RAM必须用间接寻址来访问,直接访问就成了访问SFR;还有外部RAM也是用指针来访问的。
2.时间精度重要么?大多数运用确实对时间精度没要求,但是必须要有一个概念:资源有限!既然开了定时中断,就必须尽量保证定时时间的准确性,因为这决定了程序运行的“节奏”。“节奏”没必要太快,就是定时时间不要过短,以免经常进入中断,导致其它中断(如串口中断不能及时响应,会丢失数据)得不到正确的执行;定时时间也不能太长,以免产生按键扫描不灵敏、数码管显示有闪烁等等情况出现。总之,定时器周期需要根据系统的响应周期、程序的运行时间来确定,而不是随便设个数字就搞定一切的。
所以,我认为,应该经常性地测试每一个功能模块的运行时间、统计各模块一个循环需要的最长时间,看这个最长的时间是否满足系统的实时性要求,如果能满足,就以这个时间为基准,确定定时器的定时周期,当然要留有一定余量,用来响应其它中断。
至于定时时间的精确计算,可以用公式计算,也可以用软件仿真,直接在Keil里仿真调试一下就可以确定;或者在某个引脚输出一个反转的电平信号,用示波器观察,也可以用万用表量这个引脚的平均电平,从而测算出CPU的使用率,尽可能地不要让CPU使用率太高。
总之,一定要记住:资源有限!

liuhaihai00 发表于 2014-3-30 21:52

这些程序似曾相识

jianhong_wu 发表于 2014-3-31 09:46

cjseng 发表于 2014-3-30 21:24 static/image/common/back.gif
1.51的汇编里就有指针的概念,不过它不叫指针,叫做间接寻址,用@来表示,高128字节的RAM必须用间接寻址 ...

你的观点我很认同。

善解人意 发表于 2014-3-31 09:54

好东西,楼主辛苦了

soho789 发表于 2014-3-31 17:21

最近一直在学习 寄存器,发现寄存器太多了,根本就记不住啊,然后还有使能,开启中断,各种移位,然后头就大了,
看了楼主说不用记这些,又给我增加了很多信心,但是还是担心以后会不会要走弯路, 你说的这些 while,for 我都知道,难道我一直停留在这个阶段 ? 怎么才能更深入一步?

叶丷 发表于 2014-3-31 17:55

要是做单片机销售,你会怎么去做好呢,以前我做PCB行业的。

jianhong_wu 发表于 2014-3-31 18:35

soho789 发表于 2014-3-31 17:21 static/image/common/back.gif
最近一直在学习 寄存器,发现寄存器太多了,根本就记不住啊,然后还有使能,开启中断,各种移位,然后头就 ...

      会了开桑塔纳,就等于会了开奥迪,宝马等所有小汽车。你如果问我怎么样才能进一步提高驾驶技术,那就是在不同的路况,在不同的气候下多开车。
      会了51单片机的编程框架和套路,就等于你会了PIC,STM32等所有裸机跑的单片机。你如果问我怎么样才能更深一步,那就是多做一些项目。
    所以,你最缺的是做项目经验。做项目你会遇到各种问题,你把这些问题克服解决了,你就自然进一步了。

jianhong_wu 发表于 2014-3-31 18:51

叶丷 发表于 2014-3-31 17:55 static/image/common/back.gif
要是做单片机销售,你会怎么去做好呢,以前我做PCB行业的。

我有一个做单片机销售很厉害的朋友,他是某著名芯片公司深圳地区的负责人,他跟我分享的单片机销售经验是:
(1)在你打算改行做销售时,首先你要敢于清零自己,从头再来。他当时是写程序代码的,工资有一万多,做了三年后,改行跑业务,工资才1500元。现在已经很厉害了。
(2)想做好芯片的销售,首先要尽可能选择加入一家有实力的芯片公司。
(3)选客户时,要找到各行业前三名的公司,只有这种公司出货量才会大。
(4)选客户时,要找到负责人。
(5)选择销售,就是选择一种生活方式,要有泡的心理,不能急,要能喝酒。在优哉游哉的生活中,慢慢积累客户,慢慢跟同行销售交朋友,交换客户信息,形成庞大的人脉网络。那时候想找某某公司的负责人,只需要让同行的朋友打个招呼就轻而易举联系上,到了那个层次和阶段,哪里还要陌生拜访,哪里还要电话营销。

JessLi 发表于 2014-3-31 19:55

专业的**,支持!!!

叶丷 发表于 2014-4-1 12:03

jianhong_wu 发表于 2014-3-31 18:51 static/image/common/back.gif
我有一个做单片机销售很厉害的朋友,他是某著名芯片公司深圳地区的负责人,他跟我分享的单片机销售经验是 ...

嗯是的不知道我否成功谢谢你的意见

jianhong_wu 发表于 2014-4-3 01:19

第三十七节:数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

开场白:
    我在第24节中讲过按键控制跑马灯的方向,速度和运行状态的项目程序,只可惜那个程序不能直观地显示运行中的三种状态,这节我决定在24节的基础上,增加一个数码管显示作为类似汽车仪表盘的界面,实时显示跑马灯的方向,速度,和运行状态。
这一节要教会大家一个知识点:继续加深理解运动,按键与数码管三者之间的关联程序框架。

具体内容,请看源代码讲解。

(1)硬件平台:
基于朱兆祺51单片机学习板。用S1键作为控制跑马灯的方向按键,S5键作为控制跑马灯方向的加速度按键,S9键作为控制跑马灯方向的减速度按键,S13键作为控制跑马灯方向的启动或者暂停按键。记得把输出线P0.4一直输出低电平,模拟独立按键的触发地GND。

(2)实现功能:
跑马灯运行:第1个至第8个LED灯一直不亮。在第9个至第16个LED灯,依次逐个亮灯并且每次只能亮一个灯。每按一次独立按键S13键,原来运行的跑马灯会暂停,原来暂停的跑马灯会运行。用S1来改变方向。用S5和S9来改变速度,每按一次按键的递增或者递减以10为单位。
数码管显示:本程序只有1个窗口,这个窗口分成3个局部显示。8,7,6位数码管显示运行状态,启动时显示“on”,停止时显示“oFF”。5位数码管显示数码管方向,正向显示“n”,反向显示“U”。4,3,2,1位数码管显示速度。数值越大速度越慢,最慢的速度是550,最快的速度是50。

(3)源代码讲解如下:


总结陈词:
    前面花了大量的章节在讲数码管显示,按键,运动的关联程序框架,从下一节开始,我将会用八节内容来讲我常用的串口程序框架,内容非常精彩和震撼,思路非常简单而又实用。欲知详情,请听下回分解-----判断数据尾来接收一串数据的串口通用程序框架。

(未完待续,下节更精彩,不要走开哦)

tabu 发表于 2014-4-4 14:47

马克一下,顶

jianhong_wu 发表于 2014-4-5 11:03

本帖最后由 jianhong_wu 于 2014-4-6 22:22 编辑

第三十八节:判断数据尾来接收一串数据的串口通用程序框架。
开场白:    在实际项目中,串口通讯不可能一次通讯只发送或接收一个字节,大部分的项目都是一次发送或者接受一串的数据。我们还要在这一串数据里解析数据协议,提取有用的数据。这一节要教会大家三个知识点:第一个:如何识别一串数据已经发送接收完毕。第二个:如何在已经接收到的一串数据中解析数据尾协议并且提取有效数据。第三个:接收一串数据的通用程序框架涉及到main循环里的串口服务程序,定时器的计时程序,串口接收中断程序的密切配合。大家要理解它们三者之间是如何关联起来的。
具体内容,请看源代码讲解。

(1)硬件平台:基于朱兆祺51单片机学习板。

(2)实现功能:
波特率是:9600 。
通讯协议:XX YYEB 00 55
          其中后三位 EB 00 55就是我所说的数据尾,它的有效数据XX YY在数据尾的前面。
      任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB00 55 ,并且此关键字前面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据尾和有效数据都是正确的。
(3)源代码讲解如下:
#include "REG52.H"


#define const_voice_short40   //蜂鸣器短叫的持续时间
#define const_rc_size10//接收串口中断数据的缓冲区数组大小

#define const_receive_time5//如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完,这个时间根据实际情况来调整大小

void initial_myself(void);   
void initial_peripheral(void);
void delay_long(unsigned int uiDelaylong);



void T0_time(void);//定时中断函数
void usart_receive(void); //串口接收中断函数
void usart_service(void);//串口服务程序,在main函数里

sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

unsigned intuiSendCnt=0;   //用来识别串口是否接收完一串数据的计时器
unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
unsigned intuiRcregTotal=0;//代表当前缓冲区已经接收了多少个数据
unsigned char ucRcregBuf; //接收串口中断数据的缓冲区数组
unsigned intuiRcMoveIndex=0;//用来解析数据协议的中间变量


unsigned intuiVoiceCnt=0;//蜂鸣器鸣叫的持续时间计数器



void main()
{
   initial_myself();
   delay_long(100);   
   initial_peripheral();
   while(1)
   {
       usart_service();//串口服务程序
   }

}


void usart_service(void)//串口服务程序,在main函数里
{

      
/* 注释一:
* 识别一串数据是否已经全部接收完了的原理:
* 在规定的时间里,如果没有接收到任何一个字节数据,那么就认为一串数据被接收完了,然后就进入数据协议
* 解析和处理的阶段。这个功能的实现要配合定时中断,串口中断的程序一起阅读,要理解他们之间的关系。
*/
   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

            ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据

                  //下面的代码进入数据协议解析和数据处理的阶段

                  uiRcMoveIndex=uiRcregTotal; //由于是判断数据尾,所以下标移动变量从数组的最尾端开始向0移动
            while(uiRcMoveIndex>=5)   //如果处理的数据量大于等于5(2个有效数据,3个数据头)说明还没有把缓冲区的数据处理完
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据尾eb 00 55的判断
               {
                              if(ucRcregBuf==0x01&&ucRcregBuf==0x02)//有效数据01 02的判断
                                  {
                                    uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据尾和有效数据都接收正确
                                  }
                  break;   //退出循环
               }
               uiRcMoveIndex--; //因为是判断数据尾,下标向着0的方向移动
         }
                                       
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}


void T0_time(void) interrupt 1    //定时中断
{
TF0=0;//清除中断标志
TR0=0; //关中断


if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
{
          uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
      ucSendLock=1;   //开自锁标志
}

if(uiVoiceCnt!=0)
{
   uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
   beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。

}
else
{
   ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
   beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
}


TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;
TR0=1;//开中断
}


void usart_receive(void) interrupt 4               //串口接收数据中断      
{      

   if(RI==1)
   {
      RI = 0;

            ++uiRcregTotal;
      if(uiRcregTotal>const_rc_size)//超过缓冲区
      {
         uiRcregTotal=const_rc_size;
      }
      ucRcregBuf=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
      uiSendCnt=0;//及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
   
   }
   else//我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
   {
      TI = 0;
   }
                                                         
}                              


void delay_long(unsigned int uiDelayLong)
{
   unsigned int i;
   unsigned int j;
   for(i=0;i<uiDelayLong;i++)
   {
      for(j=0;j<500;j++)//内嵌循环的空指令数量
          {
             ; //一个分号相当于执行一条空语句
          }
   }
}


void initial_myself(void)//第一区 初始化单片机
{

beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

//配置定时器
TMOD=0x01;//设置定时器0为工作方式1
TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
TL0=0x0b;


//配置串口
SCON=0x50;
TMOD=0X21;
TH1=TL1=-(11059200L/12/32/9600);//这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
TR1=1;

}

void initial_peripheral(void) //第二区 初始化外围
{

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

}
总结陈词:
   这一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这样的程序该怎么写?欲知详情,请听下回分解-----判断数据头来接收一串数据的串口通用程序框架。

(未完待续,下节更精彩,不要走开哦)

mwxpk 发表于 2014-4-5 15:29

哄不会的啊,全是没用教程

-单片机爱好者- 发表于 2014-4-5 16:48

jianhong_wu 发表于 2014-3-5 21:53 static/image/common/back.gif
第一节:吴坚鸿谈初学单片机的误区。

(1)很难记住繁杂的寄存器?寄存器不用死记硬背,鸿哥我行走江湖多 ...

很给力

cjseng 发表于 2014-4-5 21:53

鸿哥,你知道你单片机不能做精确定时的原因了吗?
你每次用到定时中断,都是先把定时器停掉,然后执行一端程序后,再重装初值,再启动定时器,这里边中间执行的那段程序会因为各种条件,导致每次执行的时间不一样,最终导致你的定时器每次中断的周期不一样,所以不能精确定时。

所以,你可以这样用,但是不要把错误的方法传授给别人。
单片机是可以精确定时的!
页: 1 2 3 4 5 6 7 8 [9] 10 11 12 13 14 15 16 17 18
查看完整版本: 从业将近十年!手把手教你单片机程序框架(连载)