gprs_new 发表于 2014-6-29 20:45:21

谢谢分享。{:handshake:}

黑夜之狼 发表于 2014-6-29 22:48:58

好资料!!!

tt98 发表于 2014-6-29 22:53:02

鸿哥给力啊!继续听讲!{:titter:}

晨星 发表于 2014-6-29 23:08:54

吴坚鸿 发表于 2014-6-29 06:54
我以后一定会出书的,但是短期内不会那么快整理,至少一年以后。因为感觉要分享的东西太多了。
晨星,我 ...

鸿哥好眼力,名字一样就知道是一个人,也确实是我转载的,我真实名字就叫晨星{:lol:}

rockyyangyang 发表于 2014-6-29 23:26:54

是从电子发烧友论坛转来啊啊            

吴坚鸿 发表于 2014-6-29 23:37:16

本帖最后由 吴坚鸿 于 2014-6-29 23:38 编辑

rockyyangyang 发表于 2014-6-29 23:26
是从电子发烧友论坛转来啊啊

不是转来的,是同时发布,每周同步更新一节,目前已经更新到53节了,初步估计不会低于100节。

吴坚鸿 发表于 2014-6-29 23:40:19

晨星 发表于 2014-6-29 23:08
鸿哥好眼力,名字一样就知道是一个人,也确实是我转载的,我真实名字就叫晨星...

晨星,你现在还在读书?还是出来工作了?在哪个城市?

晨星 发表于 2014-6-30 08:44:11

吴坚鸿 发表于 2014-6-29 23:40
晨星,你现在还在读书?还是出来工作了?在哪个城市?

现在在深圳工作,不知道鸿哥是否有提点之意啊{:lol:}

cxhy 发表于 2014-6-30 09:18:52

马克一下

503126063 发表于 2014-6-30 09:32:31

MARK{:victory:}{:victory:}{:victory:}{:victory:}

吴坚鸿 发表于 2014-6-30 12:17:39

晨星 发表于 2014-6-30 08:44
现在在深圳工作,不知道鸿哥是否有提点之意啊

要靠你自己,我帮不上什么忙。但是可以交个朋友,有空出来见一下面聊聊。我的联系方式可以在百度上找到。

ZYBing 发表于 2014-6-30 13:34:03

mark........

hkjabcd 发表于 2014-7-1 21:58:38

鸿哥!!能否来点步进电机驱动程序?脉冲+方向的就行,最近被这个搞得头晕了~

吴坚鸿 发表于 2014-7-2 00:12:17

hkjabcd 发表于 2014-7-1 21:58
鸿哥!!能否来点步进电机驱动程序?脉冲+方向的就行,最近被这个搞得头晕了~ ...

我去年就做过步进电机的项目。涉及单片机的变频技术,任意变换频率,而且要求脉冲要均匀对称,否则步进电机工作不规则不圆滑,最后还要修正精度。光是研究算法我就专门花了一个多星期,修正精度又花了一个多星期。其实这个过程都是我自己去想办法,自己去解决的,我几乎遇到问题都是自己解决的,所以我现在写的东西很有原创性。兄弟,不是我不想帮你,这个东西不是一言两语能讲清楚,要花大量时间,不亚于让我重新开发一个新项目的工作量,我最近也一直非常忙,抽不出时间,帮不了你。我这个连载技术贴近期的规划还不会那么快讲到步进电机的变频驱动技术,至少还要两三个月以后才会讲到这方面的内容。我给你的建议是,遇到问题不要急着求外援,不要害怕困难,自己静下心来多想多思考,这样才最锻炼人。

1501697860 发表于 2014-7-2 08:24:47

热烈欢迎,多谢了

XIUQIN 发表于 2014-7-2 10:58:23

吴坚鸿 发表于 2014-7-2 00:12
我去年就做过步进电机的项目。涉及单片机的变频技术,任意变换频率,而且要求脉冲要均匀对称,否则步进电 ...

说的好!

吴坚鸿 发表于 2014-7-6 11:04:09

第五十四节:指针的第二大好处,指针作为数组在函数中的输入接口。

开场白:
如果不会指针,当我们想把一个数组的数据传递进某个函数内部的时候,只能通过全局变量的方式,这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入接口。
针对以上问题,这一节要教大家一个知识点:通过指针,为函数增加一个数组输入接口。

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

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

(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第1种方法的排序结果,中间3个数据EE EE EE是第1种和第2种的分割线,为了方便观察,没实际意义。最后5个数据是第2种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

(3)源代码讲解如下:
#include "REG52.H"


#define const_array_size5//参与排序的数组大小

#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 delay_short(unsigned int uiDelayShort);


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


void eusart_send(unsigned char ucSendData);

void big_to_small_sort_1(void);//第1种方法 把一个数组从大小小排序
void big_to_small_sort_2(unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大小小排序

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

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

unsigned char ucUsartBuffer;//从串口接收到的需要排序的原始数据
unsigned char ucGlobalBuffer_1; //第1种方法,参与具体排序算法的全局变量数组
unsigned char ucGlobalBuffer_2; //第2种方法,参与具体排序算法的全局变量数组
void main()
{
   initial_myself();
   delay_long(100);   
   initial_peripheral();
   while(1)
   {
       usart_service();//串口服务程序
   }

}


/* 注释一:
* 第1种方法,用不带输入输出接口的空函数,这是最原始的做法,它完全依靠
* 全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算
* 的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的
* 输出全局变量,这些输出全局变量就是我们要的结果。
* 在本函数中,ucGlobalBuffer_1既是输入全局变量,也是输出全局变量,
* 这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口,
*/
void big_to_small_sort_1(void)//第1种方法 把一个数组从大小小排序
{
   unsigned char i;
   unsigned char k;
   unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量

/* 注释二:
* 以下就是著名的 冒泡法排序。这个方法几乎所有的C语言大学教材都讲过了。大家在百度上可以直接
* 搜索到它的工作原理和详细的讲解步骤,我就不再详细讲解了。
*/
   for(i=0;i<(const_array_size-1);i++)//冒泡的次数是(const_array_size-1)次
   {
      for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
          {
             if(ucGlobalBuffer_1>ucGlobalBuffer_1)//后一个与前一个数据两两比较
               {
                     ucTemp=ucGlobalBuffer_1;   //通过一个中间变量实现两个数据交换
             ucGlobalBuffer_1=ucGlobalBuffer_1;
             ucGlobalBuffer_1=ucTemp;
               }
          
          }
   }

}

/* 注释三:
* 第2种方法,为了改进第1种方法的用户体验,用指针为函数增加一个输入接口。
* 为什么要用指针?因为C语言的函数中,数组不能直接用来做函数的形参,只能用指针作为数组的形参。
* 比如,你不能这样写一个函数void big_to_small_sort_2(unsigned char a),否则编译就会出错不通过。
* 在本函数中,*p_ucInputBuffer指针就是输入接口,而输出接口仍然是全局变量数组ucGlobalBuffer_2。
* 这种方法由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了。
*/
void big_to_small_sort_2(unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大小小排序
{
   unsigned char i;
   unsigned char k;
   unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


   for(i=0;i<const_array_size;i++)
   {
      ucGlobalBuffer_2=p_ucInputBuffer;//参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
   }


   //以下就是著名的 冒泡法排序。详细讲解请找百度。
   for(i=0;i<(const_array_size-1);i++)//冒泡的次数是(const_array_size-1)次
   {
      for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
          {
             if(ucGlobalBuffer_2>ucGlobalBuffer_2)//后一个与前一个数据两两比较
               {
                     ucTemp=ucGlobalBuffer_2;   //通过一个中间变量实现两个数据交换
             ucGlobalBuffer_2=ucGlobalBuffer_2;
             ucGlobalBuffer_2=ucTemp;
               }
          
          }
   }

}



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

   unsigned char i=0;   

   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

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

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

            uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

            while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
               {


                                  for(i=0;i<const_array_size;i++)
                                  {
                     ucUsartBuffer=ucRcregBuf; //从串口接收到的需要被排序的原始数据
                                  }


                  //第1种运算方法,依靠全局变量
                                  for(i=0;i<const_array_size;i++)
                                  {
                                     ucGlobalBuffer_1=ucUsartBuffer;//把需要被排列的数据放进输入全局变量数组
                                  }
                  big_to_small_sort_1(); //调用一次空函数就出结果了,结果还是保存在ucGlobalBuffer_1全局变量数组中
                  for(i=0;i<const_array_size;i++)
                                  {
                                  eusart_send(ucGlobalBuffer_1);////把用第1种方法排序后的结果返回给上位机观察
                                  }


                                  eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为第1种方法与第2种方法的分割线
                                  eusart_send(0xee);
                                  eusart_send(0xee);

                  //第2种运算方法,依靠指针为函数增加一个数组的输入接口
                                  //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
                  big_to_small_sort_2(ucUsartBuffer);
                  for(i=0;i<const_array_size;i++)
                                  {
                                  eusart_send(ucGlobalBuffer_2);//把用第2种方法排序后的结果返回给上位机观察
                                  }





                  break;   //退出循环
               }
               uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
         }
                                       
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}

void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
{

ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节

delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断

}



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


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



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//发送中断,及时把发送中断标志位清零
   {
      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 delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}


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;    //启动定时中断

}
总结陈词:
第2种方法通过指针,为函数增加了一个数组输入接口,已经比第1种纯粹用全局变量的方法直观多了,但是还有一个小小的遗憾,因为它的输出排序结果仍然要靠全局变量。为了让函数更加完美,我们能不能为函数再增加一个输出接口?当然可以。欲知详情,请听下回分解-----指针的第三大好处,指针作为数组在函数中的输出接口。

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

lsszk 发表于 2014-7-6 17:39:01

看看LZ更新的,加油!顶一下

tt98 发表于 2014-7-6 19:53:57

继续听讲!{:titter:}

xxzzhy 发表于 2014-7-6 22:44:56

很喜欢。加油!

萧山|残月 发表于 2014-7-7 21:05:38

非常适合初学者

miyadai 发表于 2014-7-8 13:10:35

精彩,太精彩了。牛人~~{:victory:}

鱼尾之恋 发表于 2014-7-8 13:23:34

不错,工作8年了,自己也没有这样整理过。

晨星 发表于 2014-7-9 21:03:41

来给鸿哥继续捧场,鸿哥应该另外开一个帖子了

吴坚鸿 发表于 2014-7-9 21:37:06

晨星 发表于 2014-7-9 21:03
来给鸿哥继续捧场,鸿哥应该另外开一个帖子了

我在四五个月之后,一定会开一个新的连载技术贴,具体内容暂时保密。

lyricpoem0726 发表于 2014-7-10 12:01:32

liuzp001 发表于 2014-5-12 10:08
看我刚才的代码,已实现!

按键为什么不做成模块,而是在每个子程序里都有按键操作 ?

吴坚鸿 发表于 2014-7-10 14:54:37

第五十五节:指针的第三大好处,指针作为数组在函数中的输出接口。

开场白:
上一节介绍的第2种方法,由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了,但是由于只有输入接口,没有输出接口,输出接口仍然要靠全局变量数组,所以还是有一个小小的遗憾,这节介绍的第3种方法就是为了改变这个遗憾,为数组在函数中多增加一个输出接口,这样,函数既有输入接口,又有输出接口,这样的函数才算完美直观。这一节要教大家一个知识点:通过指针,为函数增加一个数组输出接口。

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

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

(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第2种方法的排序结果,中间3个数据EE EE EE是第2种和第3种的分割线,为了方便观察,没实际意义。最后5个数据是第3种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

(3)源代码讲解如下:
#include "REG52.H"


#define const_array_size5//参与排序的数组大小

#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 delay_short(unsigned int uiDelayShort);


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


void eusart_send(unsigned char ucSendData);


void big_to_small_sort_2(unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大到小排序
void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer);//第3种方法 把一个数组从大到小排序
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

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

unsigned char ucUsartBuffer;//从串口接收到的需要排序的原始数据

unsigned char ucGlobalBuffer_2; //第2种方法,参与具体排序算法的全局变量数组
unsigned char ucGlobalBuffer_3; //第3种方法,用来接收输出接口数据的全局变量数组

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

}



/* 注释一:
* 第2种方法,为了改进第1种方法的用户体验,用指针为函数增加一个输入接口。
* 为什么要用指针?因为C语言的函数中,数组不能直接用来做函数的形参,只能用指针作为数组的形参。
* 比如,你不能这样写一个函数void big_to_small_sort_2(unsigned char a),否则编译就会出错不通过。
* 在本函数中,*p_ucInputBuffer指针就是输入接口,而输出接口仍然是全局变量数组ucGlobalBuffer_2。
* 这种方法由于为函数多增加了一个数组输入接口,已经比第1种方法更加直观了,但是由于只有输入接口,
* 没有输出接口,输出接口仍然要靠全局变量,所以还是有点小遗憾,以下第3种方法就是为了改变这个遗憾。
*/
void big_to_small_sort_2(unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大到小排序
{
   unsigned char i;
   unsigned char k;
   unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


   for(i=0;i<const_array_size;i++)
   {
      ucGlobalBuffer_2=p_ucInputBuffer;//参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
   }


   //以下就是著名的 冒泡法排序。详细讲解请找百度。
   for(i=0;i<(const_array_size-1);i++)//冒泡的次数是(const_array_size-1)次
   {
      for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
          {
             if(ucGlobalBuffer_2>ucGlobalBuffer_2)//后一个与前一个数据两两比较
               {
                     ucTemp=ucGlobalBuffer_2;   //通过一个中间变量实现两个数据交换
             ucGlobalBuffer_2=ucGlobalBuffer_2;
             ucGlobalBuffer_2=ucTemp;
               }
          
          }
   }

}



/* 注释二:
* 第3种方法,为了改进第2种方法的用户体验,用指针为函数多增加一个数组输出接口。
* 这样,函数的数组既有输入接口,又有输出接口,已经堪称完美了。
* 本程序中*p_ucInputBuffer输入接口,*p_ucOutputBuffer是输出接口。
*/
void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer)//第3种方法 把一个数组从大到小排序
{
   unsigned char i;
   unsigned char k;
   unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量
   unsigned char ucBuffer_3; //第3种方法,参与具体排序算法的局部变量数组

   for(i=0;i<const_array_size;i++)
   {
      ucBuffer_3=p_ucInputBuffer;//参与排序算法之前,先把输入接口的数据全部搬移到局部变量数组中。
   }


   //以下就是著名的 冒泡法排序。详细讲解请找百度。
   for(i=0;i<(const_array_size-1);i++)//冒泡的次数是(const_array_size-1)次
   {
      for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
          {
             if(ucBuffer_3>ucBuffer_3)//后一个与前一个数据两两比较
               {
                     ucTemp=ucBuffer_3;   //通过一个中间变量实现两个数据交换
             ucBuffer_3=ucBuffer_3;
             ucBuffer_3=ucTemp;
               }
          
          }
   }


   for(i=0;i<const_array_size;i++)
   {
      p_ucOutputBuffer=ucBuffer_3;//参与排序算法之后,把运算结果的数据全部搬移到输出接口中,方便外面程序调用
   }
}




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

   unsigned char i=0;   

   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

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

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

            uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

            while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
               {


                                  for(i=0;i<const_array_size;i++)
                                  {
                     ucUsartBuffer=ucRcregBuf; //从串口接收到的需要被排序的原始数据
                                  }


                  //第2种运算方法,依靠指针为函数增加一个数组的输入接口
                                  //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
                  big_to_small_sort_2(ucUsartBuffer);
                  for(i=0;i<const_array_size;i++)
                                  {
                                  eusart_send(ucGlobalBuffer_2);//把用第2种方法排序后的结果返回给上位机观察
                                  }


                                  eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为第2种方法与第3种方法的分割线
                                  eusart_send(0xee);
                                  eusart_send(0xee);

                  //第3种运算方法,依靠指针为函数增加一个数组的输出接口
                                  //通过指针输出接口,排序运算后的结果直接从这个输出口中导出到ucGlobalBuffer_3数组中
                  big_to_small_sort_3(ucUsartBuffer,ucGlobalBuffer_3);   //ucUsartBuffer是输入的数组,ucGlobalBuffer_3是接收排序结果的数组
                  for(i=0;i<const_array_size;i++)
                                  {
                                  eusart_send(ucGlobalBuffer_3);//把用第3种方法排序后的结果返回给上位机观察
                                  }



                  break;   //退出循环
               }
               uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
         }
                                       
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}

void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
{

ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节

delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断

}



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


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



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//发送中断,及时把发送中断标志位清零
   {
      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 delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}


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;    //启动定时中断

}
总结陈词:
通过本节程序的讲解,一部分细心的读者可能会发现一个规律,其实所谓指针作为数组在函数中的输入接口和输出接口,输入接口的指针跟输出接口的指针在语法上没有任何区别,我没有用到C语言中专门的关键词去限定某个指针是输入,某个指针是输出,因此,这个告诉我们什么道理?指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,不像普通的函数变量形参只能做输入。发现了这个秘密,我们可不可以把本节程序中的输入接口和输出接口合并成一个输入输出接口?当然可以。欲知详情,请听下回分解-----指针的第四大好处,指针作为数组在函数中的输入输出接口。

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

吴坚鸿 发表于 2014-7-10 17:28:43

第五十六节:指针的第四大好处,指针作为数组在函数中的输入输出接口。

开场白:
通过前面几个章节的学习,我们知道指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,类似全局变量的特点。我们根据实际项目的情况,在必要的时候可以直接把输入接口和输出接口合并在一起,这种方法的缺点是没有把输入和输出分开,没有那么直观。但是优点也是很明显的,就是比较省程序ROM容量和数据RAM容量,而且运行效率也比较快。这一节要教大家一个知识点:指针作为数组在函数中输入输出接口的特点。

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

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

(2)实现功能:
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第3种方法的排序结果,中间3个数据EE EE EE是第3种和第4种的分割线,为了方便观察,没实际意义。最后5个数据是第4种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

串口程序的接收部分请参考第39节。串口程序的发送部分请参考第42节。

波特率是:9600 。

(3)源代码讲解如下:
#include "REG52.H"


#define const_array_size5//参与排序的数组大小

#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 delay_short(unsigned int uiDelayShort);


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


void eusart_send(unsigned char ucSendData);

void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer);//第3种方法 把一个数组从大到小排序
void big_to_small_sort_4(unsigned char *p_ucInputAndOutputBuffer);//第4种方法 把一个数组从大到小排序
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

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

unsigned char ucUsartBuffer;//从串口接收到的需要排序的原始数据

unsigned char ucGlobalBuffer_3; //第3种方法,用来接收输出接口数据的全局变量数组
unsigned char ucGlobalBuffer_4; //第4种方法,用来输入和输出接口数据的全局变量数组
void main()
{
   initial_myself();
   delay_long(100);   
   initial_peripheral();
   while(1)
   {
       usart_service();//串口服务程序
   }

}




/* 注释一:
* 第3种方法,为了改进第2种方法的用户体验,用指针为函数多增加一个数组输出接口。
* 这样,函数的数组既有输入接口,又有输出接口,已经堪称完美了。
* 本程序中*p_ucInputBuffer输入接口,*p_ucOutputBuffer是输出接口。
*/
void big_to_small_sort_3(unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer)//第3种方法 把一个数组从大到小排序
{
   unsigned char i;
   unsigned char k;
   unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量
   unsigned char ucBuffer_3; //第3种方法,参与具体排序算法的局部变量数组

   for(i=0;i<const_array_size;i++)
   {
      ucBuffer_3=p_ucInputBuffer;//参与排序算法之前,先把输入接口的数据全部搬移到局部变量数组中。
   }


   //以下就是著名的 冒泡法排序。详细讲解请找百度。
   for(i=0;i<(const_array_size-1);i++)//冒泡的次数是(const_array_size-1)次
   {
      for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
          {
             if(ucBuffer_3>ucBuffer_3)//后一个与前一个数据两两比较
               {
                     ucTemp=ucBuffer_3;   //通过一个中间变量实现两个数据交换
             ucBuffer_3=ucBuffer_3;
             ucBuffer_3=ucTemp;
               }
          
          }
   }


   for(i=0;i<const_array_size;i++)
   {
      p_ucOutputBuffer=ucBuffer_3;//参与排序算法之后,把运算结果的数据全部搬移到输出接口中,方便外面程序调用
   }
}


/* 注释二:
* 第4种方法.指针在函数的接口中,天生就是既可以做输入,也可以是做输出,它是双向性的,类似全局变量的特点。
* 我们可以根据实际项目的情况,在必要的时候可以直接把输入接口和输出接口合并在一起,
* 这种方法的缺点是没有把输入和输出分开,没有那么直观。但是优点也是很明显的,就是比较
* 省程序ROM容量和数据RAM容量,而且运行效率也比较快。现在介绍给大家。
* 本程序的*p_ucInputAndOutputBuffer是输入输出接口。
*/
void big_to_small_sort_4(unsigned char *p_ucInputAndOutputBuffer)//第4种方法 把一个数组从大到小排序
{
   unsigned char i;
   unsigned char k;
   unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量

   //以下就是著名的 冒泡法排序。详细讲解请找百度。
   for(i=0;i<(const_array_size-1);i++)//冒泡的次数是(const_array_size-1)次
   {
      for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
          {
             if(p_ucInputAndOutputBuffer>p_ucInputAndOutputBuffer)//后一个与前一个数据两两比较
               {
                     ucTemp=p_ucInputAndOutputBuffer;   //通过一个中间变量实现两个数据交换
             p_ucInputAndOutputBuffer=p_ucInputAndOutputBuffer;
             p_ucInputAndOutputBuffer=ucTemp;
               }
          
          }
   }


}


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

   unsigned char i=0;   

   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

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

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

            uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

            while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
               {


                                  for(i=0;i<const_array_size;i++)
                                  {
                     ucUsartBuffer=ucRcregBuf; //从串口接收到的需要被排序的原始数据
                                  }


                  //第3种运算方法,依靠指针为函数增加一个数组的输出接口
                                  //通过指针输出接口,排序运算后的结果直接从这个输出口中导出到ucGlobalBuffer_3数组中
                  big_to_small_sort_3(ucUsartBuffer,ucGlobalBuffer_3);   //ucUsartBuffer是输入的数组,ucGlobalBuffer_3是接收排序结果的数组
                  for(i=0;i<const_array_size;i++)
                                  {
                                  eusart_send(ucGlobalBuffer_3);//把用第3种方法排序后的结果返回给上位机观察
                                  }

                                  eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为第2种方法与第3种方法的分割线
                                  eusart_send(0xee);
                                  eusart_send(0xee);

                  //第4种运算方法,依靠一个指针作为函数的输入输出接口。
                                  //通过这个指针输入输出接口,ucGlobalBuffer_4数组既是输入数组,也是输出数组,排序运算后的结果直接存放在它本身,类似于全局变量的特点。
                                  for(i=0;i<const_array_size;i++)
                                  {
                     ucGlobalBuffer_4=ucUsartBuffer; //把需要被排序的原始数据传递给接收输入输出数组ucGlobalBuffer_4,
                                  }
                  big_to_small_sort_4(ucGlobalBuffer_4);
                  for(i=0;i<const_array_size;i++)
                                  {
                                  eusart_send(ucGlobalBuffer_4);//把用第4种方法排序后的结果返回给上位机观察
                                  }


                  break;   //退出循环
               }
               uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
         }
                                       
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}

void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
{

ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节

delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断

}



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


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



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//发送中断,及时把发送中断标志位清零
   {
      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 delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}


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;    //启动定时中断

}
总结陈词:
通过本章的学习,我们知道指针在函数接口中的双向性,这个双向性是一把双刃剑,既给我们带来便捷,也给我们在以下两个场合中带来隐患。
第一个场合:当需要把输入接口和输出接口分开时,我们希望输入接口的参数不要被意外改变,改变的仅仅只能是输出接口的数据。但是指针的双向性,就有可能导致我们在写函数内部代码的时候一不小心改变而没有发觉。
第二个场合:如果是一个现成封装好的函数直接给我们调用,当我们发现是指针作为接口的时候,我们就不敢确定这个接口是输入接口,还是输出接口,或者是输入输出接口,我们传递进去的参数可能会更改,除非用之前进行数据备份,否则是没有安全感可言的。
有没有办法巧妙的解决以上两个问题?当然有。欲知详情,请听下回分解-----为指针加上紧箍咒const,避免意外修改了只做输入接口的数据。

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

zjsdlt2013 发表于 2014-7-10 18:55:04

不顶不行啊,太给力了!!!

tt98 发表于 2014-7-11 20:42:36

继续听讲!{:titter:}

xxzzhy 发表于 2014-7-11 22:03:23

很喜欢鸿哥的阐述方式。

吴坚鸿 发表于 2014-7-13 08:02:11

第五十七节:为指针加上紧箍咒const,避免意外修改了只做输入接口的数据。

开场白:
通过上一节的学习,我们知道指针在函数接口中具有双向性,这个双向性是一把双刃剑,既给我们带来便捷,也给我们带来隐患。这一节要教大家以下知识点:
凡是做输入接口的指针,都应该加上const标签来标识,它可以让原来双向性的接口变成了单向性接口,它有两个好处:
第一个:如果你是用别人已经封装好的函数,你发现接口指针带了const标签,就足以说明这个指针只能做输入接口,你用了它,不用担心输入数据被修改。
第二个:如果是你自己写的函数,你在输入接口处的指针加了const标签,它可以预防你在写函数内部代码时不小心修改了输入接口的数据。比如,你试着在函数内部更改带const标签的输入接口数据,当你点击编译时,会编译不过,出现错误提示:error C183: unmodifiable lvalue。这就是一道防火墙啊!

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

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

(2)实现功能:
我只是把第55节中凡是输入接口数据的指针都加了const关键字标签,其它代码内容没变。
把5个随机数据按从大到小排序,用冒泡法来排序。
通过电脑串口调试助手,往单片机发送EB 00 55 08 06 09 05 07指令,其中EB 00 55是数据头,08 06 09 05 07 是参与排序的5个随机原始数据。单片机收到指令后就会返回13个数据,最前面5个数据是第3种方法的排序结果,中间3个数据EE EE EE是第3种和第4种的分割线,为了方便观察,没实际意义。最后5个数据是第4种方法的排序结果.

比如电脑发送:EB 00 55 08 06 09 05 07
单片机就返回:09 08 07 06 05 EE EE EE 09 08 07 06 05

波特率是:9600 。

(3)源代码讲解如下:
#include "REG52.H"


#define const_array_size5//参与排序的数组大小

#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 delay_short(unsigned int uiDelayShort);


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


void eusart_send(unsigned char ucSendData);


void big_to_small_sort_2(const unsigned char *p_ucInputBuffer);//第2种方法 把一个数组从大到小排序
void big_to_small_sort_3(const unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer);//第3种方法 把一个数组从大到小排序
sbit beep_dr=P2^7; //蜂鸣器的驱动IO口

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

unsigned char ucUsartBuffer;//从串口接收到的需要排序的原始数据

unsigned char ucGlobalBuffer_2; //第2种方法,参与具体排序算法的全局变量数组
unsigned char ucGlobalBuffer_3; //第3种方法,用来接收输出接口数据的全局变量数组

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

}


void big_to_small_sort_2(const unsigned char *p_ucInputBuffer)//第2种方法 把一个数组从大到小排序
{
   unsigned char i;
   unsigned char k;
   unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量


   for(i=0;i<const_array_size;i++)
   {
      ucGlobalBuffer_2=p_ucInputBuffer;//参与排序算法之前,先把输入接口的数据全部搬移到全局变量数组中。
   }


   //以下就是著名的 冒泡法排序。详细讲解请找百度。
   for(i=0;i<(const_array_size-1);i++)//冒泡的次数是(const_array_size-1)次
   {
      for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
          {
             if(ucGlobalBuffer_2>ucGlobalBuffer_2)//后一个与前一个数据两两比较
               {
                     ucTemp=ucGlobalBuffer_2;   //通过一个中间变量实现两个数据交换
             ucGlobalBuffer_2=ucGlobalBuffer_2;
             ucGlobalBuffer_2=ucTemp;
               }
         
          }
   }


}



/* 注释一:
* 凡是做输入接口的指针,都应该加上const标签来标识,它可以让原来双向性的接口变成了单向性接口,它有两个好处:
* 第一个:如果你是用别人已经封装好的函数,你发现接口指针带了const标签,就足以说明
* 这个指针只能做输入接口,你用了它,不用担心输入数据被修改。
* 第二个:如果是你自己写的函数,你在输入接口处的指针加了const标签,它可以预防你在写函数内部代码时
* 不小心修改了输入接口的数据。比如,你试着在以下函数最后的地方加一条更改输入接口数据的指令,
* 当你点击编译时,会编译不过,出现错误提示:error C183: unmodifiable lvalue。
*/
void big_to_small_sort_3(const unsigned char *p_ucInputBuffer,unsigned char *p_ucOutputBuffer)//第3种方法 把一个数组从大到小排序
{
   unsigned char i;
   unsigned char k;
   unsigned char ucTemp; //在两两交换数据的过程中,用于临时存放交换的某个变量
   unsigned char ucBuffer_3; //第3种方法,参与具体排序算法的局部变量数组

   for(i=0;i<const_array_size;i++)
   {
      ucBuffer_3=p_ucInputBuffer;//参与排序算法之前,先把输入接口的数据全部搬移到局部变量数组中。
   }


   //以下就是著名的 冒泡法排序。详细讲解请找百度。
   for(i=0;i<(const_array_size-1);i++)//冒泡的次数是(const_array_size-1)次
   {
      for(k=0;k<(const_array_size-1-i);k++) //每次冒泡的过程中,需要两两比较的次数是(const_array_size-1-i)
          {
             if(ucBuffer_3>ucBuffer_3)//后一个与前一个数据两两比较
               {
                     ucTemp=ucBuffer_3;   //通过一个中间变量实现两个数据交换
             ucBuffer_3=ucBuffer_3;
             ucBuffer_3=ucTemp;
               }
         
          }
   }


   for(i=0;i<const_array_size;i++)
   {
      p_ucOutputBuffer=ucBuffer_3;//参与排序算法之后,把运算结果的数据全部搬移到输出接口中,方便外面程序调用
   }


/* 注释二:
* 以下这条是企图修改输入接口数据的指令,如果不屏蔽,编译的时候就会出错提醒:error C183: unmodifiable lvalue?
*/

   //p_ucInputBuffer=0;//修改输入接口数据的指令
}




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

   unsigned char i=0;   

   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

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

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

            uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

            while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
               {


                                  for(i=0;i<const_array_size;i++)
                                  {
                     ucUsartBuffer=ucRcregBuf; //从串口接收到的需要被排序的原始数据
                                  }


                  //第2种运算方法,依靠指针为函数增加一个数组的输入接口
                                  //通过指针输入接口,直接把ucUsartBuffer数组的首地址传址进去,排序后输出的结果还是保存在ucGlobalBuffer_2全局变量数组中
                  big_to_small_sort_2(ucUsartBuffer);
                  for(i=0;i<const_array_size;i++)
                                  {
                                    eusart_send(ucGlobalBuffer_2);//把用第2种方法排序后的结果返回给上位机观察
                                  }


                                  eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为第2种方法与第3种方法的分割线
                                  eusart_send(0xee);
                                  eusart_send(0xee);

                  //第3种运算方法,依靠指针为函数增加一个数组的输出接口
                                  //通过指针输出接口,排序运算后的结果直接从这个输出口中导出到ucGlobalBuffer_3数组中
                  big_to_small_sort_3(ucUsartBuffer,ucGlobalBuffer_3);   //ucUsartBuffer是输入的数组,ucGlobalBuffer_3是接收排序结果的数组
                  for(i=0;i<const_array_size;i++)
                                  {
                                    eusart_send(ucGlobalBuffer_3);//把用第3种方法排序后的结果返回给上位机观察
                                  }



                  break;   //退出循环
               }
               uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
         }
                                       
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}

void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
{

ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节

delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断

}



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


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



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//发送中断,及时把发送中断标志位清零
   {
      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 delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}


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;    //启动定时中断

}
总结陈词:
    通过前面几节的学习,我们知道了指针在函数接口中的输入输出用途,以及const关键字的作用。下一节将要讲指针的第五大好处。欲知详情,请听下回分解-----指针的第五大好处,指针在众多数组中的中转站作用。

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

njhying 发表于 2014-7-13 09:21:54

谢谢楼主学习 中

xxzzhy 发表于 2014-7-13 11:11:33

看了段时间,感觉还不错!

siko 发表于 2014-7-13 18:28:01

非常感谢

mavericklx 发表于 2014-7-13 22:22:16

佩服楼主~~

tt98 发表于 2014-7-14 21:59:09

顶下鸿哥!鸿哥加油!{:handshake:}

hkjabcd 发表于 2014-7-15 22:33:34

吴坚鸿 发表于 2014-7-2 00:12
我去年就做过步进电机的项目。涉及单片机的变频技术,任意变换频率,而且要求脉冲要均匀对称,否则步进电 ...

多谢鸿哥的回复和建议!您的文章一直在追!
步进电机程序现在采用查表法,用梯形曲线好了些;
只是对于限位传感器和原点传感器的检测,鸿哥有什么建议?
用定时中断还是小延时处理?控制电机较多,小延时消抖实时性不好,响应慢~!

吴坚鸿 发表于 2014-7-15 23:10:27

本帖最后由 吴坚鸿 于 2014-7-15 23:13 编辑

hkjabcd 发表于 2014-7-15 22:33
多谢鸿哥的回复和建议!您的文章一直在追!
步进电机程序现在采用查表法,用梯形曲线好了些;
只是对于限 ...

(1)限位传感器和原点传感器我的项目中没有用到,所以不能给你什么建议。
(2)至于延时,我是用了两个单片机,一个单片机做按键和显示,另外一个单片机专门做步进电机的驱动,它们之间通过串口通讯。做步进电机我是用小延时的,不是用定时中断。因为小延时的细分度高。

吴坚鸿 发表于 2014-7-20 07:13:48

本帖最后由 吴坚鸿 于 2014-7-20 12:59 编辑

第五十八节:指针的第五大好处,指针在众多数组中的中转站作用。

开场白:
    单个变量数据之间可以通过一条指令任意自由赋值转移,但是数组之间不能通过一条指令直接赋值转移,必须用for等循环指令挨个把数组的数据一个一个来赋值转移,如果一个 函数中,有很多数组需要赋值转移,那就非常麻烦了,要用很多for语句,耗时。还好C语言里有个指针,它可以非常高效地来切换我们所需要的数组,起到很好的中转站作用。这一节要教大家一个知识点:指针在众多数组中的中转站作用。

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

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

(2)实现功能:
在第57节的串口收发程序基础上修改。在串口接收函数中,以下代码有略微修改:
while(uiRcregTotal>=4&&uiRcMoveIndex<=(uiRcregTotal-4)) //注意,这里是4,不是上一节的5,因为只有eb 00 55 xx这4个数据

通过上位机来调用下位机对应的数组数据。
通过电脑串口调试助手,往单片机发送EB 00 55 XX 指令,其中EB 00 55是数据头,XX的取值范围是0x01 至 0x05,每个不同的值代表调用下位机不同的数组数据。0x01调用第1组数据,0x02调用第2组数据,0x05调用第5组数据。
第1组:11 12 13 14 15
第2组:21 22 23 24 25
第3组:31 32 33 34 35
第4组:41 42 43 44 45
第5组:51 52 53 54 55

下位机返回21个数据,前面5个是第1种不带指针函数返回的数据。中间5个是第2种不带指针函数返回的数据。最后5个是第3种带指针函数返回的数据。期间2组EE EE EE是各函数返回的数据分割线,为了方便观察,没实际意义。
比如电脑发送:EB 00 55 02
单片机就返回:21 22 23 24 25 EE EE EE 21 22 23 24 25 EE EE EE 21 22 23 24 25

波特率是:9600 。

(3)源代码讲解如下:
#include "REG52.H"


#define const_array_size5//参与排序的数组大小

#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 delay_short(unsigned int uiDelayShort);


void T0_time(void);//定时中断函数
void usart_receive(void); //串口接收中断函数
void usart_service(void);//串口服务程序,在main函数里
void send_array_1(unsigned char ucArraySec);//第1种函数,不带指针
void send_array_2(unsigned char ucArraySec);//第2种函数,不带指针
void send_array_3(unsigned char ucArraySec);//第3种函数,带指针
void eusart_send(unsigned char ucSendData);


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

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

const unsigned char array_0x01[]={0x11,0x12,0x13,0x14,0x15}; //第1个常量数组
const unsigned char array_0x02[]={0x21,0x22,0x23,0x24,0x25}; //第2个常量数组
const unsigned char array_0x03[]={0x31,0x32,0x33,0x34,0x35}; //第3个常量数组
const unsigned char array_0x04[]={0x41,0x42,0x43,0x44,0x45}; //第4个常量数组
const unsigned char array_0x05[]={0x51,0x52,0x53,0x54,0x55}; //第5个常量数组

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

}

/* 注释一:
* 第1种函数,内部不带指针,根据上位机相关的指令,
* 直接返回对应的数组。由于不带指针,因此多用了5个for循环来搬运数组。
* 比较耗程序ROM容量,也不够简洁清晰。
*/
void send_array_1(unsigned char ucArraySec)
{
   unsigned int i;
   switch(ucArraySec)
   {
      case 1://直接返回第1个常量数组
               for(i=0;i<5;i++)
                   {
                      eusart_send(array_0x01);
                   }
               break;
      case 2://直接返回第2个常量数组
               for(i=0;i<5;i++)
                   {
                      eusart_send(array_0x02);
                   }
               break;
      case 3://直接返回第3个常量数组
               for(i=0;i<5;i++)
                   {
                      eusart_send(array_0x03);
                   }
               break;
      case 4://直接返回第4个常量数组
               for(i=0;i<5;i++)
                   {
                      eusart_send(array_0x04);
                   }
               break;   
      case 5://直接返回第5个常量数组
               for(i=0;i<5;i++)
                   {
                      eusart_send(array_0x05);
                   }
               break;

   }

}


/* 注释二:
* 第2种函数,内部不带指针,根据上位机相关的指令,
* 先转移对应的数组放到一个中间变量数组,然后发送数组。
* 由于不带指针,因此多用了6个for循环来搬运数组。
* 跟第1种函数一样,比较耗程序ROM容量,也不够简洁清晰。
*/
void send_array_2(unsigned char ucArraySec)//第2种函数,不带指针
{
   unsigned int i;
   unsigned char array_temp; //临时中间数组
   switch(ucArraySec)
   {
      case 1://直接返回第1个常量数组
               for(i=0;i<5;i++)
                   {
                        array_temp=array_0x01; //先挨个把对应的数组数据转移到中间数组里
                   }
               break;
      case 2://直接返回第2个常量数组
               for(i=0;i<5;i++)
                   {
                        array_temp=array_0x02; //先挨个把对应的数组数据转移到中间数组里
                   }
               break;
      case 3://直接返回第3个常量数组
               for(i=0;i<5;i++)
                   {
                        array_temp=array_0x03; //先挨个把对应的数组数据转移到中间数组里
                   }
               break;
      case 4://直接返回第4个常量数组
               for(i=0;i<5;i++)
                   {
                        array_temp=array_0x04; //先挨个把对应的数组数据转移到中间数组里
                   }
               break;   
      case 5://直接返回第5个常量数组
               for(i=0;i<5;i++)
                   {
                        array_temp=array_0x05; //先挨个把对应的数组数据转移到中间数组里
                   }
               break;

   }

   for(i=0;i<5;i++)
   {
         eusart_send(array_temp);//把临时存放在中间数组的数据全部发送出去
   }

}

/* 注释三:
* 第3种函数,内部带指针,根据上位机相关的指令,
* 先把对应的数组首地址传递给一个中间指针,然后再通过
* 指针把整个数组的数据发送出去,由于带指针,切换转移数组的数据非常快,
* 只需传递一下首地址给指针就可以,非常高效,整个函数只用了1个for循环。
* 跟前面第1,2种函数相比,更加节省程序容量,处理速度更加快,更加简洁。
*/
void send_array_3(unsigned char ucArraySec)//第3种函数,带指针
{
   unsigned int i;
   unsigned char *p_array; //临时中间指针,作为数组的中转站,非常高效
   switch(ucArraySec)
   {
      case 1://直接返回第1个常量数组
                   p_array=array_0x01;//把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
               break;
      case 2://直接返回第2个常量数组
                   p_array=array_0x02;//把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
               break;
      case 3://直接返回第3个常量数组
                   p_array=array_0x03;//把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
               break;
      case 4://直接返回第4个常量数组
                   p_array=array_0x04;//把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
               break;   
      case 5://直接返回第5个常量数组
                   p_array=array_0x05;//把数组的首地址传递给指针,一个指令就可以,不用for来挨个搬移数据,高效!
               break;

   }

   for(i=0;i<5;i++)
   {
         eusart_send(p_array);//通过指针把数组的数据全部发送出去
   }

}

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

   unsigned char i=0;   
   unsigned char ucWhichArray;
   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

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

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

            uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

            while(uiRcregTotal>=4&&uiRcMoveIndex<=(uiRcregTotal-4)) //注意,这里是4,不是上一节的5,因为只有eb 00 55 xx这4个数据
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
               {

                   ucWhichArray=ucRcregBuf; //上位机需要返回的某个数组

                   send_array_1(ucWhichArray); //第1种函数返回数组的5个数据,不带指针

                   eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为分割线
                   eusart_send(0xee);
                   eusart_send(0xee);

                   send_array_2(ucWhichArray); //第2种函数返回数组的5个数据,不带指针

                   eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为分割线
                   eusart_send(0xee);
                   eusart_send(0xee);

                   send_array_3(ucWhichArray); //第3种函数返回数组的5个数据,带指针


                   break;   //退出循环
               }
               uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
         }
                                       
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}

void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
{

ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节

delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断

}



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


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



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//发送中断,及时把发送中断标志位清零
   {
      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 delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}


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;    //启动定时中断

}
总结陈词:
通过前面几节的学习,基本上讲完了我平时用指针的所有心得体会。
下一节开始讲新内容。在前面一些章节中,我提到为了防止中断函数把某些共享数据破坏,在主函数中更改某个数据变量时,应该先关闭中断,修改完后再打开中断;我也提到了网友“红金龙吸味”关于原子锁的建议。经过这段时间的思考和总结,我发现不管是关中断开中断,还是原子锁,其实本质上都是程序在多进程中临界点的数据处理,原子锁在程序员中有个专用名词叫互斥量,而我引以为豪的状态机程序框架,主函数的switch语句,外加一个定时中断,本质上就是2个独立进程在不断切换并行运行。我觉得这个临界点处理的知识很重要,也很容易忽略,所以我决定专门用两节内容来讲讲这方面的知识应用。欲知详情,请听下回分解-----关中断和开中断在多进程临界点的应用。

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

tt98 发表于 2014-7-20 16:33:37

继续跟进!{:lol:}

lsszk 发表于 2014-7-20 17:20:45

继续顶贴!

qinchl 发表于 2014-7-20 23:10:50

这样通俗易懂的教程真的不错!

guxinghan 发表于 2014-7-21 00:30:42

吴坚鸿 发表于 2014-3-12 23:07
我一路走来,都是靠自己不断积累不断摸索的,积累到一定程度才形成现在的理论。我懂初学者缺什么,我知道 ...

真心谢谢大大,小子在此受益了!

吴坚鸿 发表于 2014-7-21 00:38:13

第五十九节:串口程序第40,44,45节中存在一个bug,特此紧急公告。
                                 
   经过网友“intech2008”的提醒,在我之前发表的第40,44,45节串口接收程序中,在计算检验和的地方,存在一个不容易发觉的bug。
   原来的是:

for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
{
      ucRcregBuf=ucRcregBuf+ucRcregBuf;
}   

应该改成:
for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
{
      ucRcregBuf=ucRcregBuf+ucRcregBuf;
}   

由于本连载技术文章在各大论坛发布和被转载,我没法做到处处提醒,不得不专门用一节内容来告知各位读者。

下节预告-----关中断和开中断在多进程临界点的应用。

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

song1km 发表于 2014-7-22 11:08:10

举手之劳

song1km 发表于 2014-7-22 11:08:50

力所能及

chenfuen 发表于 2014-7-22 11:34:56

记号一下,慢慢看

吴坚鸿 发表于 2014-7-22 12:28:32

本帖最后由 吴坚鸿 于 2014-7-22 12:29 编辑

song1km 发表于 2014-7-22 11:08
力所能及

感谢你们的整理,分享,转载和传播。另外需要声明一下,第一节后面插入的《从单片机初学者迈向单片机工程师第二版第一章 究竟该如何学习》这段章节的内容不是我本人的作品。

tt98 发表于 2014-7-25 21:19:46

力顶鸿哥!{:victory:}{:victory:}

吴坚鸿 发表于 2014-7-26 10:43:22

第六十节:用关中断和互斥量来保护多线程共享的全局变量。

开场白:
在前面一些章节中,我提到为了防止中断函数把某些共享数据破坏,在主函数中更改某个数据变量时,应该先关闭中断,修改完后再打开中断;我也提到了网友“红金龙吸味”关于原子锁的建议。经过这段时间的思考和总结,我发现不管是关中断开中断,还是原子锁,其实本质上都是程序在多进程中临界点的数据处理,原子锁有个专用名词叫互斥量,而我引以为豪的状态机程序框架,主函数的switch语句,外加一个定时中断,本质上就是2个独立进程在不断切换并行运行。
为什么要保护多线程共享的全局变量?因为,多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
这一节要教大家一个知识点:如何用关中断和互斥量来保护多线程共享的全局变量。

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

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

(2)实现功能:
在第5节的基础上略作修改,让蜂鸣器在前面3秒发生一次短叫报警,在后面6秒发生一次长叫报警,如此反复循环。

(3)源代码讲解如下:
#include "REG52.H"


#define const_time_3s 1332   //3秒钟的时间需要的定时中断次数
#define const_time_6s 2664   //6秒钟的时间需要的定时中断次数

#define const_voice_short40   //蜂鸣器短叫的持续时间
#define const_voice_long   200//蜂鸣器长叫的持续时间

void initial_myself();   
void initial_peripheral();
void delay_long(unsigned int uiDelaylong);
void led_flicker();
void alarm_run();   
void T0_time();//定时中断函数

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

unsigned char ucAlarmStep=0; //报警的步骤变量
unsigned intuiTimeAlarmCnt=0; //报警统计定时中断次数的延时计数器

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

unsigned char ucLock=0;   //互斥量,俗称原子锁
void main()
{
   initial_myself();
   delay_long(100);   
   initial_peripheral();
   while(1)
   {
      alarm_run();   //报警器定时报警
   }

}


/* 注释一:
* 保护多线程共享全局变量的原理:
* 多个线程同时访问同一个全局变量,如果都是读取操作,则不会出现问题。如果一个线程负责改变此变量的值,
* 而其他线程负责同时读取变量内容,则不能保证读取到的数据是经过写线程修改后的。
* 鸿哥的基本程序框架都是两线程为主,一个是main函数线程,一个是定时函数线程。
*/

void alarm_run() //报警器的应用程序
{

switch(ucAlarmStep)
{
   case 0:

         if(uiTimeAlarmCnt>=const_time_3s) //时间到
         {
/* 注释二:
* 用关中断来保护多线程共享的全局变量:
* 因为uiTimeAlarmCnt和uiVoiceCnt都是unsigned int类型,本质上是由两个字节组成。
* 在C语言中uiTimeAlarmCnt=0和uiVoiceCnt=const_voice_short看似一条指令,
* 实际上经过编译之后它不只一条汇编指令。由于另外一个定时中断线程里也会对这个变量
* 进行判断和操作,如果不禁止定时中断或者采取其它措施,定时函数往往会在主函数还没有
* 结束操作共享变量前就去访问或处理这个共享变量,这就会引起冲突,导致系统运行异常。
*/
            ET0=0;//禁止定时中断
            uiTimeAlarmCnt=0; //时间计数器清零
            uiVoiceCnt=const_voice_short;//蜂鸣器短叫
                          ET0=1; //开启允许定时中断
            ucAlarmStep=1; //切换到下一个步骤
         }
         break;
   case 1:
         if(uiTimeAlarmCnt>=const_time_6s) //时间到
         {
/* 注释三:
* 用互斥量来保护多线程共享的全局变量:
* 我觉得,在这种场合,用互斥量比前面用关中断的方法更加好。
* 因为一旦关闭了定时中断,整个中断函数就会在那一刻停止运行了,
* 而加一个互斥量,既能保护全局变量,又能让定时中断函数正常运行,
* 真是一举两得。
*/
                      ucLock=1;//互斥量加锁。 俗称原子锁
            uiTimeAlarmCnt=0; //时间计数器清零
            uiVoiceCnt=const_voice_long;//蜂鸣器长叫
                      ucLock=0; //互斥量解锁。俗称原子锁

            ucAlarmStep=0; //返回到上一个步骤
         }
         break;
}

}

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

if(ucLock==0) //互斥量判断
{
   if(uiTimeAlarmCnt<0xffff)//设定这个条件,防止uiTimeAlarmCnt超范围。
   {
         uiTimeAlarmCnt++;//报警的时间计数器,累加定时中断的次数,
   }

   if(uiVoiceCnt!=0)
   {
         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
         beep_dr=0;//蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
   }
   else
   {
         ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
         beep_dr=1;//蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
   }
}

TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1;//开中断
}


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()//第一区 初始化单片机
{
beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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


TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;

}
void initial_peripheral() //第二区 初始化外围
{
EA=1;   //开总中断
ET0=1;    //允许定时中断
TR0=1;    //启动定时中断

}
总结陈词:
从下一节开始我准备用几章节的内容来讲常用的数学算法程序。这些程序经常要用在计算器,工控,以及高精度的仪器仪表等领域。C语言的语法中不是已经提供了+,-,*,/这些运算符号吗?为什么还要专门写算法程序?因为那些运算符只能进行简单的运算,一旦数据超过了unsigned long(4个字节)的范围就会出错。而解决这种问题的大数据算法程序是什么样的?欲知详情,请听下回分解----大数据的加法运算。

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

strongking 发表于 2014-7-26 11:23:43

不错先打个记号,.

sandman 发表于 2014-7-26 11:29:51

mark,程序架构

yanpenghao 发表于 2014-7-26 11:37:33

一直支持的,谢谢楼主

rom 发表于 2014-7-26 11:39:58

{:lol:}坚持这么多了,支持一下。

xxzzhy 发表于 2014-7-26 15:05:32

不错不错。挺喜欢的。

Arthur244 发表于 2014-7-26 15:29:43

感觉还不错。。从学51到现在玩32已经一年多了。。感觉编程习惯一般般。。学习一下。。话说卤煮有整理好的文档供下载么。。

吴坚鸿 发表于 2014-7-27 00:49:04

Arthur244 发表于 2014-7-26 15:29
感觉还不错。。从学51到现在玩32已经一年多了。。感觉编程习惯一般般。。学习一下。。话说卤煮有整理好的文 ...

我本人没有整理过,但是有很多网友整理过,你自己在网上查找一下。

YZDREAM8 发表于 2014-7-27 22:24:51

互斥量的处理似乎有缺陷,在LOCK的状态下发生中断后会丢失一次修改变量,很多时候这样处理会有严重隐患的。

tt98 发表于 2014-7-27 22:38:33

继续顶!!

吴坚鸿 发表于 2014-7-28 17:42:35

YZDREAM8 发表于 2014-7-27 22:24
互斥量的处理似乎有缺陷,在LOCK的状态下发生中断后会丢失一次修改变量,很多时候这样处理会有严重隐患的。 ...

要看互斥量用在什么场合,像我这种用来计时的丢失一次中断计数是没有影响的。而且单片机其实没办法做到方方面面都兼顾,往往是顾此失彼,想得到什么就要失去另外一些性能,我们只能根据系统的要求来选择取舍。

FULINGDA 发表于 2014-7-31 15:21:00

楼主辛苦!

Myheartisbroken 发表于 2014-7-31 16:53:48

学习一下!谢谢

wpami 发表于 2014-7-31 22:07:51

看着挺累(楼太高了),想问声:出的有书吗,想买本仔细学习、收藏。

Achilics 发表于 2014-8-1 00:51:10

支持一下

zhoust 发表于 2014-8-1 13:50:24

欢迎支持,做个记号!

吴坚鸿 发表于 2014-8-1 22:38:37

wpami 发表于 2014-7-31 22:07
看着挺累(楼太高了),想问声:出的有书吗,想买本仔细学习、收藏。

还没有出书。估计至少要一年后才会整理出书。

坚持学习 发表于 2014-8-2 12:02:27

吴坚鸿 发表于 2014-3-10 15:07
第十二节:按住一个独立按键不松手的连续步进触发。

开场白:


这个程序好像不能实现每0.25S加一次吧,因为只是在不断的循环键值是否为1,当第一次长按1S,键值就为1了,到按住0.25S键值为1,再到0.25S键值还是1,0.25到0.25中间这一段时间,键值一直为1,变量一直自加,不会有0.25S自加一次的现象吧????请教大家{:handshake:}

吴坚鸿 发表于 2014-8-2 12:20:47

坚持学习 发表于 2014-8-2 12:02
这个程序好像不能实现每0.25S加一次吧,因为只是在不断的循环键值是否为1,当第一次长按1S,键值就为1了 ...

会自加的,你说的那个1只是标志位,自加的内容在按键服务程序里
void key_service() //第三区 按键服务的应用程序
{
switch(ucKeySec) //按键服务状态切换
{
    case 1:// 1号键 连续加键对应朱兆祺学习板的S1键
            uiSetNumber++; //被设置的参数连续往上加
                        if(uiSetNumber>20) //最大是20
                        {
                            uiSetNumber=20;
                        }
            uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
            ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;      
    case 2:// 2号键 连续减键对应朱兆祺学习板的S5键
/* 注释二:
* 在单片机的C语言编译器中,当无符号数据0减去1时,就会溢出,变成这个类型数据的最大值。
* 比如是unsigned int的0减去1就等于65535(0xffff),unsigned char的0减去1就等于255(0xff)
*/
            uiSetNumber--; //被设置的参数连续往下减
                        if(uiSetNumber>20) //最小是0.为什么这里用20?因为0减去1就是溢出变成了65535(0xffff)
                        {
                            uiSetNumber=0;
                        }
            uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
            ucKeySec=0;//响应按键服务处理程序后,按键编号清零,避免一致触发
          break;                  
}               
}

坚持学习 发表于 2014-8-2 12:36:48

吴坚鸿 发表于 2014-8-2 12:20
会自加的,你说的那个1只是标志位,自加的内容在按键服务程序里

我的意思是他会不停的加不是 每0.25S加一次

maweidong18596 发表于 2014-8-2 15:21:12

高手就是高手,看蒙了!

吴坚鸿 发表于 2014-8-4 13:32:03

第六十一节:组合BCD码,非组合BCD码,以及数值三者之间的相互转换和关系。
开场白:
本来这一节打算讲大数据的加法运算的,但是考虑大数据运算的基础是非组合BCD码,所以多增加一节讲BCD码的内容。
计算机中的BCD码,经常使用的有两种格式,即组合BCD码,非组合BCD码。
组合BCD码,是将两位十进制数,存放在一个字节中,例如:十进制数51的存放格式是0101 0001。
非组合BCD码,是将一个字节的低四位编码表示十进制数的一位,而高4位都为0。例如:十进制数51的占用了两个字节的空间,存放格式为:00000101 00000001。
    这一节要教大家两个知识点:
第一个:如何编写组合BCD码,非组合BCD码,以及数值三者之间的相互转换函数。
第二个:通过转换函数的编写,重温前面几节所讲到的指针用法。

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

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

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送EB 00 55 XX YY YY … YY YY指令,其中EB 00 55是数据头,XX 是指令类型。YY是具体的数据。
指令类型01代表发送的是数值,需要转成组合BCD码和非组合BCD码,并且返回上位机显示。
指令类型02代表发送的是组合BCD码,需要转成数值和非组合BCD码,并且返回上位机显示。
指令类型03代表发送的是非组合BCD码,需要转成数值和组合BCD码,并且返回上位机显示。

返回上位机的数据中,中间3个数据EE EE EE是分割线,为了方便观察,没实际意义。

例如:十进制的数据52013140,它的十六进制数据是03 19 A8 54。
(a)上位机发送数据:eb 00 55 01 03 19 a8 54
单片机返回:52 01 31 40 EE EE EE 05 02 00 01 03 01 04 00
(b)上位机发送组合BCD码:eb 00 55 02 52 01 31 40
单片机返回:03 19 A8 54 EE EE EE 05 02 00 01 03 01 04 00
(c)发送非组合BCD码:eb 00 55 03 05 02 00 01 03 01 04 00
单片机返回:03 19 A8 54 EE EE EE 52 01 31 40

(3)源代码讲解如下:
#include "REG52.H"

#define const_voice_short40   //蜂鸣器短叫的持续时间


/* 注释一:
* 注意,此处的const_rc_size是20,比之前章节的缓冲区稍微改大了一点。
*/
#define const_rc_size20//接收串口中断数据的缓冲区数组大小

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

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


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


void eusart_send(unsigned char ucSendData);

void number_to_BCD4(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit4);//把数值转换成组合BCD码
void number_to_BCD8(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit8);//把数值转换成非组合BCD码
void BCD4_to_number(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucNumber); //组合BCD码转成数值
void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD_bit8); //组合BCD码转成非组合BCD码
void BCD8_to_number(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucNumber); //非组合BCD码转成数值
void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD_bit4); //非组合BCD码转成组合BCD码


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

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

/* 注释二:
* 注意,本程序规定数值的最大范围是0至99999999
* 数组中的数据。高位在数组下标大的方向,低位在数组下标小的方向。
*/
unsigned char ucBufferNumber; //数值,用4个字节表示long类型的数值
unsigned char ucBufferBCB_bit4; //组合BCD码
unsigned char ucBufferBCB_bit8; //非组合BCD码

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

}

void number_to_BCD4(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit4)//把数值转换成组合BCD码
{
   unsigned long ulNumberTemp=0;
   unsigned char ucTemp=0;
   ulNumberTemp=p_ucNumber;//把4个字节的数值合并成一个long类型数据
   ulNumberTemp=ulNumberTemp<<8;
   ulNumberTemp=ulNumberTemp+p_ucNumber;
   ulNumberTemp=ulNumberTemp<<8;
   ulNumberTemp=ulNumberTemp+p_ucNumber;
   ulNumberTemp=ulNumberTemp<<8;
   ulNumberTemp=ulNumberTemp+p_ucNumber;


   p_ucBCD_bit4=ulNumberTemp%100000000/10000000;
   p_ucBCD_bit4=p_ucBCD_bit4<<4; //前半4位存第8位组合BCD码
   ucTemp=ulNumberTemp%10000000/1000000;
   p_ucBCD_bit4=p_ucBCD_bit4+ucTemp; //后半4位存第7位组合BCD码

   p_ucBCD_bit4=ulNumberTemp%1000000/100000;
   p_ucBCD_bit4=p_ucBCD_bit4<<4; //前半4位存第6位组合BCD码
   ucTemp=ulNumberTemp%100000/10000;
   p_ucBCD_bit4=p_ucBCD_bit4+ucTemp;//后半4位存第5位组合BCD码

   p_ucBCD_bit4=ulNumberTemp%10000/1000;
   p_ucBCD_bit4=p_ucBCD_bit4<<4; //前半4位存第4位组合BCD码
   ucTemp=ulNumberTemp%1000/100;
   p_ucBCD_bit4=p_ucBCD_bit4+ucTemp;//后半4位存第3位组合BCD码

   p_ucBCD_bit4=ulNumberTemp%100/10;
   p_ucBCD_bit4=p_ucBCD_bit4<<4; //前半4位存第2位组合BCD码
   ucTemp=ulNumberTemp%10;
   p_ucBCD_bit4=p_ucBCD_bit4+ucTemp;//后半4位存第1位组合BCD码

}


void number_to_BCD8(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit8)//把数值转换成非组合BCD码
{
   unsigned long ulNumberTemp=0;
   ulNumberTemp=p_ucNumber;//把4个字节的数值合并成一个long类型数据
   ulNumberTemp=ulNumberTemp<<8;
   ulNumberTemp=ulNumberTemp+p_ucNumber;
   ulNumberTemp=ulNumberTemp<<8;
   ulNumberTemp=ulNumberTemp+p_ucNumber;
   ulNumberTemp=ulNumberTemp<<8;
   ulNumberTemp=ulNumberTemp+p_ucNumber;

   p_ucBCD_bit8=ulNumberTemp%100000000/10000000;//一个字节8位存储第8位非组合BCD码
   p_ucBCD_bit8=ulNumberTemp%10000000/1000000;//一个字节8位存储第7位非组合BCD码
   p_ucBCD_bit8=ulNumberTemp%1000000/100000;//一个字节8位存储第6位非组合BCD码
   p_ucBCD_bit8=ulNumberTemp%100000/10000;//一个字节8位存储第5位非组合BCD码
   p_ucBCD_bit8=ulNumberTemp%10000/1000;//一个字节8位存储第4位非组合BCD码
   p_ucBCD_bit8=ulNumberTemp%1000/100;//一个字节8位存储第3位非组合BCD码
   p_ucBCD_bit8=ulNumberTemp%100/10;//一个字节8位存储第2位非组合BCD码
   p_ucBCD_bit8=ulNumberTemp%10;//一个字节8位存储第1位非组合BCD码

}


void BCD4_to_number(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucNumber) //组合BCD码转成数值
{
   unsigned long ulTmep;
   unsigned long ulSum;

   ulSum=0;//累加和数值清零

   ulTmep=0;
   ulTmep=p_ucBCD_bit4;
   ulTmep=ulTmep>>4;//把组合BCD码第8位分解出来
   ulTmep=ulTmep*10000000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit4;
   ulTmep=ulTmep&0x0000000f;//把组合BCD码第7位分解出来
   ulTmep=ulTmep*1000000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit4;
   ulTmep=ulTmep>>4;//把组合BCD码第6位分解出来
   ulTmep=ulTmep*100000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit4;
   ulTmep=ulTmep&0x0000000f;//把组合BCD码第5位分解出来
   ulTmep=ulTmep*10000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit4;
   ulTmep=ulTmep>>4;//把组合BCD码第4位分解出来
   ulTmep=ulTmep*1000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit4;
   ulTmep=ulTmep&0x0000000f;//把组合BCD码第3位分解出来
   ulTmep=ulTmep*100;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit4;
   ulTmep=ulTmep>>4;//把组合BCD码第2位分解出来
   ulTmep=ulTmep*10;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit4;
   ulTmep=ulTmep&0x0000000f;//把组合BCD码第1位分解出来
   ulTmep=ulTmep*1;
   ulSum=ulSum+ulTmep; //累加各位数值

   //以上代码非常有规律,有兴趣的读者也可以自己想办法把它压缩成一个for循环的函数,可以极大节省容量。

   p_ucNumber=ulSum>>24;//把long类型数据分解成4个字节
   p_ucNumber=ulSum>>16;
   p_ucNumber=ulSum>>8;
   p_ucNumber=ulSum;
}



void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD_bit8) //组合BCD码转成非组合BCD码
{
   unsigned char ucTmep;

   ucTmep=p_ucBCD_bit4;
   p_ucBCD_bit8=ucTmep>>4;    //把组合BCD码第8位分解出来
   p_ucBCD_bit8=ucTmep&0x0f;//把组合BCD码第7位分解出来

   ucTmep=p_ucBCD_bit4;
   p_ucBCD_bit8=ucTmep>>4;    //把组合BCD码第6位分解出来
   p_ucBCD_bit8=ucTmep&0x0f;//把组合BCD码第5位分解出来

   ucTmep=p_ucBCD_bit4;
   p_ucBCD_bit8=ucTmep>>4;    //把组合BCD码第4位分解出来
   p_ucBCD_bit8=ucTmep&0x0f;//把组合BCD码第3位分解出来

   ucTmep=p_ucBCD_bit4;
   p_ucBCD_bit8=ucTmep>>4;    //把组合BCD码第2位分解出来
   p_ucBCD_bit8=ucTmep&0x0f;//把组合BCD码第1位分解出来

}



void BCD8_to_number(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucNumber) //非组合BCD码转成数值
{
   unsigned long ulTmep;
   unsigned long ulSum;

   ulSum=0;//累加和数值清零

   ulTmep=0;
   ulTmep=p_ucBCD_bit8;
   ulTmep=ulTmep*10000000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit8;
   ulTmep=ulTmep*1000000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit8;
   ulTmep=ulTmep*100000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit8;
   ulTmep=ulTmep*10000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit8;
   ulTmep=ulTmep*1000;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit8;
   ulTmep=ulTmep*100;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit8;
   ulTmep=ulTmep*10;
   ulSum=ulSum+ulTmep; //累加各位数值

   ulTmep=0;
   ulTmep=p_ucBCD_bit8;
   ulTmep=ulTmep*1;
   ulSum=ulSum+ulTmep; //累加各位数值

   //以上代码非常有规律,有兴趣的读者也可以自己想办法把它压缩成一个for循环的函数,可以极大节省容量。

   p_ucNumber=ulSum>>24;//把long类型数据分解成4个字节
   p_ucNumber=ulSum>>16;
   p_ucNumber=ulSum>>8;
   p_ucNumber=ulSum;
}



void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD_bit4) //非组合BCD码转成组合BCD码
{
   unsigned char ucTmep;

   ucTmep=p_ucBCD_bit8;    //把非组合BCD码第8位分解出来
   p_ucBCD_bit4=ucTmep<<4;
   p_ucBCD_bit4=p_ucBCD_bit4+p_ucBCD_bit8;    //把非组合BCD码第7位分解出来

   ucTmep=p_ucBCD_bit8;    //把非组合BCD码第6位分解出来
   p_ucBCD_bit4=ucTmep<<4;
   p_ucBCD_bit4=p_ucBCD_bit4+p_ucBCD_bit8;    //把非组合BCD码第5位分解出来

   ucTmep=p_ucBCD_bit8;    //把非组合BCD码第4位分解出来
   p_ucBCD_bit4=ucTmep<<4;
   p_ucBCD_bit4=p_ucBCD_bit4+p_ucBCD_bit8;    //把非组合BCD码第3位分解出来

   ucTmep=p_ucBCD_bit8;    //把非组合BCD码第2位分解出来
   p_ucBCD_bit4=ucTmep<<4;
   p_ucBCD_bit4=p_ucBCD_bit4+p_ucBCD_bit8;    //把非组合BCD码第1位分解出来

}

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

   unsigned char i=0;   

   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

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

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

            uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动

            while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
               {
                  switch(ucRcregBuf)//根据命令类型来进行不同的处理
                                        {
                                           case 1://接收到的是数值,需要转成组合BCD码和非组合BCD码
                            for(i=0;i<4;i++)
                            {
                              ucBufferNumber=ucRcregBuf; //从串口接收到的数据,注意,高位在数组下标大的方向
                            }
                            number_to_BCD4(ucBufferNumber,ucBufferBCB_bit4);//把数值转换成组合BCD码
                            number_to_BCD8(ucBufferNumber,ucBufferBCB_bit8);//把数值转换成非组合BCD码
                            for(i=0;i<4;i++)
                            {
                               eusart_send(ucBufferBCB_bit4);////把组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
                            }
                            eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为分割线
                            eusart_send(0xee);
                            eusart_send(0xee);
                            for(i=0;i<8;i++)
                            {
                               eusart_send(ucBufferBCB_bit8);////把非组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
                            }

                                                break;
                                           case 2://接收到的是组合BCD码,需要转成数值和非组合BCD码
                            for(i=0;i<4;i++)
                            {
                              ucBufferBCB_bit4=ucRcregBuf; //从串口接收到的组合BCD码,注意,高位在数组下标大的方向
                            }
                            BCD4_to_number(ucBufferBCB_bit4,ucBufferNumber); //组合BCD码转成数值
                            BCD4_to_BCD8(ucBufferBCB_bit4,ucBufferBCB_bit8); //组合BCD码转成非组合BCD码
                            for(i=0;i<4;i++)
                            {
                               eusart_send(ucBufferNumber);////把数值返回给上位机观察,注意,高位在数组下标大的方向
                            }
                            eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为分割线
                            eusart_send(0xee);
                            eusart_send(0xee);
                            for(i=0;i<8;i++)
                            {
                               eusart_send(ucBufferBCB_bit8);////把非组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
                            }

                                                break;
                                           case 3://接收到的是非组合BCD码,需要转成数值和组合BCD码
                            for(i=0;i<8;i++)
                            {
                              ucBufferBCB_bit8=ucRcregBuf; //从串口接收到的非组合BCD码,注意,高位在数组下标大的方向
                            }

                            BCD8_to_number(ucBufferBCB_bit8,ucBufferNumber); //非组合BCD码转成数值
                            BCD8_to_BCD4(ucBufferBCB_bit8,ucBufferBCB_bit4); //非组合BCD码转成组合BCD码
                            for(i=0;i<4;i++)
                            {
                               eusart_send(ucBufferNumber);////把数值返回给上位机观察
                            }
                            eusart_send(0xee);//为了方便上位机观察,多发送3个字节ee ee ee作为分割线,注意,高位在数组下标大的方向
                            eusart_send(0xee);
                            eusart_send(0xee);
                            for(i=0;i<4;i++)
                            {
                               eusart_send(ucBufferBCB_bit4);////把组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
                            }

                                                break;
                                        }

                  break;   //退出循环
               }
               uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
         }
                                       
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}

void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
{

ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节

delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断

}



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


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



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//发送中断,及时把发送中断标志位清零
   {
      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 delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}


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;    //启动定时中断

}
总结陈词:
有了这一节非组合BCD的基础知识,下一节就开始讲大数据的算法程序。这些算法程序经常要用在计算器,工控,以及高精度的仪器仪表等领域。C语言的语法中不是已经提供了+,-,*,/这些运算符号吗?为什么还要专门写算法程序?因为那些运算符只能进行简单的运算,一旦数据超过了unsigned long(4个字节)的范围就会出错。而这种大数据算法的程序是什么样的?欲知详情,请听下回分解----大数据的加法运算。

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

vctor、南 发表于 2014-8-4 13:49:19

{:hug:}{:hug:}大牛!

xxzzhy 发表于 2014-8-6 20:48:24

有段时间没有来了。又更新了几节。鸿哥加油。

Solar_Gao 发表于 2014-8-6 20:51:44

标记,慢慢读

小车 发表于 2014-8-7 09:18:12

努力学习!!!楼主大赞

lymahao 发表于 2014-8-8 22:11:52

l继续学习,楼主加油。

gz_dailin 发表于 2014-8-10 16:44:48

mark一下,学习

吴坚鸿 发表于 2014-8-13 01:19:58

第六十二节:大数据的加法运算。

开场白:
直接用C语言的“+”运算符进行加法运算时,“被加数”,“加数”,“和”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据加法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。
      我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是上一节讲到的BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。
    这一节要教大家两个知识点:
第一个:如何通过用for循环语句改写上一节的组合BCD码跟非组合BCD码的转换函数。
第二个:如何编写涉及到大数据加法运算的算法程序函数,同时也复习了指针的用途。
第三个:如何在串口程序中通过关键字来截取所需要的数据。

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

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

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被加数和加数。单片机把组合BCD码的运算结果返回到上位机。最大范围4位,从0到9999,如果超范围则返回EE EE EE报错。往单片机发送的数据格式:EB 00 55 XX XX 0d 0aYY YY0d 0a指令,其中EB 00 55是数据头,XX 是被加数,可以是1个字节,也可以是2个字节。YY是加数,可以是1个字节,也可以是2个字节。0d 0a是固定的结束标志。
例如:
(a)1234+5678=6912
上位机发送数据:eb 00 55 12 34 0d 0a 56 78 0d 0a
单片机返回:69 12

(b)9999+56=10055超过4位的9999,所以报错
上位机发送数据:eb 00 55 99 990d 0a 56 0d 0a
单片机返回:EE EE EE表示出错了

(3)源代码讲解如下:
#include "REG52.H"


/* 注释一:
* 本系统中,规定最大运算位数是4位。
* 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
* 不能超过256个,如果超过了这个极限,编译器就会报错。如果这个算法
* 移植到stm32或者PIC等RAM比较大的单片机上,那么就可以把这个运算位数
* 设置得更加大一点。
*/

#defineBCD4_MAX   2//本系统中,规定的组合BCD码最大字节数,一个字节包含2位,因此4位有效运算数
#defineBCD8_MAX    (BCD4_MAX*2)//本系统中,规定的非组合BCD码最大字节数,一个字节包含1位,因此4位有效运算数

#define const_rc_size30//接收串口中断数据的缓冲区数组大小

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

#define uchar unsigned char    //方便移植平台
#define ulong unsigned long   //方便移植平台

//如果在VC的平台模拟此算法,则都定义成int类型,如下:
//#define uchar int
//#define ulong int

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


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


void eusart_send(unsigned char ucSendData);

void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt);
void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt);

void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData);

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

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


unsigned char ucDataBCD4_1; //接收到的第1个数组合BCD码数组形式这里是指被加数
unsigned char ucDataBCD4_cnt_1=0;//接收到的第1个数组合BCD码数组的有效数据长度

unsigned char ucDataBCD4_2; //接收到的第2个数组合BCD码数组形式这里是指加数
unsigned char ucDataBCD4_cnt_2=0;//接收到的第2个数组合BCD码数组的有效数据长度

unsigned char ucDataBCD4_3; //接收到的第3个数组合BCD码数组形式这里是指和
unsigned char ucDataBCD4_cnt_3=0;//接收到的第3个数组合BCD码数组的有效数据长度


unsigned char ucDataBCD8_1; //接收到的第1个数非组合BCD码数组形式   这里是指被加数
unsigned char ucDataBCD8_cnt_1=0;//接收到的第1个数非组合BCD码数组的有效数据长度

unsigned char ucDataBCD8_2; //接收到的第2个数非组合BCD码数组形式   这里是指加数
unsigned char ucDataBCD8_cnt_2=0;//接收到的第2个数非组合BCD码数组的有效数据长度

unsigned char ucDataBCD8_3; //接收到的第3个数非组合BCD码数组形式   这里是指和
unsigned char ucDataBCD8_cnt_3=0;//接收到的第3个数非组合BCD码数组的有效数据长度

unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。

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

}

/* 注释二:
* 组合BCD码转成非组合BCD码。
* 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数.
* 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去
* 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
* 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度,
* 可以让我们根据数据的实际大小灵活运用。
*/
void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt)
{
   unsigned char ucTmep;
   unsigned char i;

   for(i=0;i<BCD8_MAX;i++)   //先把即将保存转换结果的缓冲区清零
   {
      p_ucBCD_bit8=0;
   }


   *p_ucBCD8_cnt=ucBCD4_cnt*2; //转换成非组合BCD码后的有效数据长度

   for(i=0;i<ucBCD4_cnt;i++)
   {
      ucTmep=p_ucBCD_bit4;
      p_ucBCD_bit8=ucTmep>>4;   
      p_ucBCD_bit8=ucTmep&0x0f;
   }

}


/* 注释三:
* 非组合BCD码转成组合BCD码。
* 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数.
* 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去
* 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
* 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度,
* 可以让我们根据数据的实际大小灵活运用。
*/
void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt)
{
   unsigned char ucTmep;
   unsigned char i;
   unsigned char ucBCD4_cnt;

   for(i=0;i<BCD4_MAX;i++)   //先把即将保存转换结果的缓冲区清零
   {
      p_ucBCD_bit4=0;
   }

   ucBCD4_cnt=(ucBCD8_cnt+1)/2; //非组合BCD码转化成组合BCD码的有效数,这里+1避免非组合数据长度是奇数位
   *p_ucBCD4_cnt=ucBCD4_cnt; //把转换后的结果付给接口指针的数据,可以对外输出结果

   for(i=0;i<ucBCD4_cnt;i++)
   {
      ucTmep=p_ucBCD_bit8;    //把非组合BCD码第8位分解出来
      p_ucBCD_bit4=ucTmep<<4;
      p_ucBCD_bit4=p_ucBCD_bit4+p_ucBCD_bit8;    //把非组合BCD码第7位分解出来
   }

}

/* 注释四:
*函数介绍:清零数组的全部数组数据
*输入参数:ucARRAY_MAX代表数组定义的最大长度
*输入输出参数:*destData--被清零的数组。
*/

void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
{
uchar i;

for(i=0;i<ucARRAY_MAX;i++)
{
   destData=0;
}

}


/* 注释五:
*函数介绍:获取数组的有效长度
*输入参数:*destData--被获取的数组。
*输入参数:ucARRAY_MAX代表数组定义的最大长度
*返回值:返回数组的有效长度。比如58786这个数据的有效长度是5
*电子开发者作者:吴坚鸿
*/
uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
{
uchar i;
uchar DataLength=ucARRAY_MAX;
for(i=0;i<ucARRAY_MAX;i++)
{
      if(0!=destData)
          {
             break;
          }
          else
          {
             DataLength--;
          }

}

return DataLength;

}



/* 注释六:
*函数介绍:两个数相加
*输入参数:
*(1)*destData--被加数的数组。
*(2)*sourceData--加数的数组。
*(3)*resultData--和的数组。注意,调用本函数前,必须先把这个数组清零
*返回值:10代表计算结果超出范围出错,11代表正常。
*/
uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData)
{
uchar addResult=11; //开始默认返回的运算结果是正常
uchar destCnt=0;
uchar sourceCnt=0;
uchar i;
uchar carryData=0;//进位
uchar maxCnt=0; //最大位数
uchar resultTemp=0; //存放临时运算结果的中间变量

//为什么不在本函数内先把resultData数组清零?因为后面章节中的乘法运算中要用到此函数实现连加功能。
//因此如果纯粹实现加法运算时,在调用本函数之前,必须先在外面把和的数组清零,否则会计算出错。

destCnt=GetDataLength(destData,BCD8_MAX);   //获取被加数的有效位数
sourceCnt=GetDataLength(sourceData,BCD8_MAX);//获取加数的有效位数

if(destCnt>=sourceCnt)//找出两个运算数据中最大的有效位数
{
   maxCnt=destCnt;
}
else
{
   maxCnt=sourceCnt;
}

for(i=0;i<maxCnt;i++)
{
   resultTemp=destData+sourceData+carryData; //按位相加
   resultData=resultTemp%10;   //截取最低位存放进保存结果的数组
   carryData=resultTemp/10;    //存放进位
}

resultData=carryData;

if((maxCnt==BCD8_MAX)&&(carryData==1))//如果数组的有效位是最大值并且最后的进位是1,则计算溢出报错
{

ClearAllData(BCD8_MAX,resultData);

addResult=10;//报错
}


return addResult;
}




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

   unsigned char i=0;   
   unsigned char k=0;   
       unsigned char ucGetDataStep=0;

   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

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

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

            uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
            while(uiRcMoveIndex<uiRcregTotal)//说明还没有把缓冲区的数据读取完
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
               {
                  
                                   i=0;
                                   ucGetDataStep=0;
                   ucDataBCD4_cnt_1=0;//第1个数组合BCD码数组的有效数据长度
                   ucDataBCD4_cnt_2=0;//第2个数组合BCD码数组的有效数据长度

                                   ClearAllData(BCD4_MAX,ucDataBCD4_1);//清零第1个参与运算的数据
                                   ClearAllData(BCD4_MAX,ucDataBCD4_2);//清零第2个参与运算的数据

                   //以下while循环是通过关键字0x0d 0x0a来截取第1个和第2个参与运算的数据。
                                   while(i<(BCD8_MAX+4))//这里+4是因为有2对0x0d 0x0a结尾特殊符号,一个共4个字节
                                   {
                                           if(ucGetDataStep==0)//步骤0,相当于我平时用的case 0,获取第1个数,在这里是指被加数
                                           {
                                                       if(ucRcregBuf==0x0d&&ucRcregBuf==0x0a) //结束标志
                                                       {
                              for(k=0;k<ucDataBCD4_cnt_1;k++) //提取第1个参与运算的数组数据
                                                                {
                                                                   ucDataBCD4_1=ucRcregBuf; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
                                                                }
                                                                                                                                                       
                                                                i=i+2; //跳过 0x0d 0x0a 这两个字节,进行下一轮的关键字提取
                                                          ucGetDataStep=1;//切换到下一个关键字提取的步骤

                           }
                                                       else
                                                       {
                                                                i++;
                                                                ucDataBCD4_cnt_1++;//统计第1个有效数据的长度
                           }
                                                                                                                       
                                             }
                                               else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是加数
                                               {
                                                      if(ucRcregBuf==0x0d&&ucRcregBuf==0x0a) //结束标志
                                                       {
                              for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
                                                                {
                                                                   ucDataBCD4_2=ucRcregBuf; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
                                                                }
                                                                                                                                                       
                              break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

                           }
                                                       else
                                                       {
                                                                i++;
                                                                ucDataBCD4_cnt_2++;//统计第2个有效数据的长度
                           }
                         }
                  }


                  //注意ucDataBCD8_cnt_1和ucDataBCD8_cnt_2要带地址符号&传址进去
                  BCD4_to_BCD8(ucDataBCD4_1,ucDataBCD4_cnt_1,ucDataBCD8_1,&ucDataBCD8_cnt_1); //把接收到的组合BCD码转换成非组合BCD码第1个数
                  BCD4_to_BCD8(ucDataBCD4_2,ucDataBCD4_cnt_2,ucDataBCD8_2,&ucDataBCD8_cnt_2); //把接收到的组合BCD码转换成非组合BCD码第2个数


                                  ClearAllData(BCD8_MAX,ucDataBCD8_3);//清零第3个参与运算的数据,用来接收运行的结果
                                        ucResultFlag=AddData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相加运算,结果放在ucDataBCD8_3数组里

                                        if(ucResultFlag==11) //表示运算结果没有超范围
                                        {
                     ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX);//获取和的有效字节数
                                           BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
                     for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
                                          {
                                                eusart_send(ucDataBCD4_3); //往上位机发送一个字节的函数
                                           }
                                        }
                                        else //运算结果超范围,返回EE EE EE
                                        {
                                             eusart_send(0xee); //往上位机发送一个字节的函数
                                             eusart_send(0xee); //往上位机发送一个字节的函数
                                             eusart_send(0xee); //往上位机发送一个字节的函数
                                        }

                  break;   //退出循环
               }
               uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
         }

         ucRcregBuf=0; //把数据头清零,方便下次接收判断新数据
                   ucRcregBuf=0;
                   ucRcregBuf=0;         
                  
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}

void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
{

ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节

delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断

}



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


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



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//发送中断,及时把发送中断标志位清零
   {
      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 delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}


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;    //启动定时中断

}
总结陈词:
既然这节讲了加法程序,那么下一节接着讲常用的减法程序,这种大数据的减法程序是什么样的?欲知详情,请听下回分解----大数据的减法运算。

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

lsszk 发表于 2014-8-13 06:15:19

谢谢LZ,坚持就是胜利

mavericklx 发表于 2014-8-13 13:41:41

吴坚鸿 发表于 2014-3-14 13:09
其实以前我年轻的时候也不愿意分享技术的,我想以后我的技术传男不传女,世世代代传下去。
后来我想开了 ...

楼主胸怀博大,怎么看待自己的知识,值得所有的小伙伴们学习。

bingshuihuo888 发表于 2014-8-13 15:33:50

热烈欢迎!

吴坚鸿 发表于 2014-8-13 22:34:35

lsszk 发表于 2014-8-13 06:15
谢谢LZ,坚持就是胜利

我至少每个星期更新一篇。因为我把这个看得很重,每写一篇前,我都要仔细进行规划,并且等我某天状态最好的时候才开始动笔写。我宁愿进度慢一点,也要把质量内容写好。因为分享的东西,只要写了一次,以后就可以批量传播和复制给千千万万的初学者,所以我有这个责任心好好写,写出我最真实的水平。

leolink 发表于 2014-8-13 22:54:35

感谢楼主,先收藏再慢慢看

li460135301 发表于 2014-8-14 07:58:53

吴老师好,很高兴您能和我们分享您对单片机的总结。
看了您写的第一节,表达下我个人的看法
1、一般只是说单片机的寄存器不用刻意去记忆,但是有必要记住。
2、汇编语言有C语言不可替代的地方,也有其优良的地方,学习汇编有其重要意义,不应该定义为浪费时间。应为汇编是直接操作底层工作寄存器,如果学会了用汇编去编程序,换用C语言就完全不是问题了。反过来却不然。
3、说C语言牛或许也就牛在指针这里,指针是直接对硬件物理地址写数据,应该说是最贴近底层的,不了解指针或许对单片机的了解或许就如冰山的一角。
4、或许给你时间你也很难写出可以用于商业用途的操作系统,就目前来看,在用的商用操作系统也不多,也都是团队开发的,并非个人开发(当然了Linux是也是在Unix基础上改的,也不是完整开发的。)
5、而对于用单片机计时,没有大家都认可的理论说目前的集成电路能做到最精确的计时操作,只能说误差小,说精确都做不到。就算是国家的计时中心也是要隔年进行时间调整的。
6、而对于通信协议,只能说目前在物理上只有这两种通信方式,但是未来的世界不知道。
7、对于写短小精悍的程序并非一朝一夕,即使是你已经很熟悉了,如何做到程序小、高效且稳定,这依然是个难题。
我个人总结是:学习单片机不要在一个问题上止步不前,程序先写起来,指令从目前需要用的指令用起来,寄存器不要刻意的去记忆(有必要记住但不是必须的),走出了第一步才能走第二步,对与任何一个知识的学习,你对他的熟悉程度决定能你能走多远,你对该理论的掌握程度决定着你能趴多高。一步一个脚印。任重道远。
(吴老师,以上是本人的一点小小的心得。请指教)

allanwang1101 发表于 2014-8-14 11:43:40

标记一下,慢慢学习!

吴坚鸿 发表于 2014-8-15 00:14:11

li460135301 发表于 2014-8-14 07:58
吴老师好,很高兴您能和我们分享您对单片机的总结。
看了您写的第一节,表达下我个人的看法
1、一般只是说 ...

你的提的观点和建议我都愿意照单全收,你讲的很有道理。可能是我在某些观点上表达得有点偏激了,是我的错。

atolei 发表于 2014-8-15 04:23:23

{:titter:}{:titter:}{:titter:}{:titter:}{:handshake:}{:handshake:}{:handshake:}好教程

ghhuang 发表于 2014-8-15 09:18:37

楼主出书吧,我先预定了{:lol:}

u123321 发表于 2014-8-15 17:44:07

再感谢lz,和749楼的整理

xxzzhy 发表于 2014-8-15 21:02:01

上面的兄弟说得有道理,是非总有两面的。还想对你说,你不做律师可惜了!

夏日么么茶 发表于 2014-8-16 00:03:52

不错不错。

li460135301 发表于 2014-8-16 08:11:40

吴坚鸿 发表于 2014-8-15 00:14
你的提的观点和建议我都愿意照单全收,你讲的很有道理。可能是我在某些观点上表达得有点偏激了,是我的错 ...

这些只是我个人的一个小小的看法,交流下,我会好好学习您的分享。

lsszk 发表于 2014-8-16 17:52:17


我至少每个星期更新一篇。因为我把这个看得很重,每写一篇前,我都要仔细进行规划,并且等我某天状态最好的时候才开始动笔写。我宁愿进度慢一点,也要把质量内容写好。因为分享的东西,只要写了一次,以后就可以批量传播和复制给千千万万的初学者,所以我有这个责任心好好写,写出我最真实的水平。


LZ 好心态,做事严谨,佩服!初学者的福气,继续!

liuguo0530 发表于 2014-8-18 16:03:45

认真的人最可爱~~楼主赞一个!

zjsdlt2013 发表于 2014-8-18 20:04:55

太丰厚了,一时半会儿看不完,楼主有没有打好包的文件啊?

吴坚鸿 发表于 2014-8-19 16:33:36

第六十三节:大数据的减法运算。

开场白:
直接用C语言的“-”运算符进行加法运算时,“被减数”,“ 减数”,“差”,这三个数据的最大范围是unsigned long 类型,也就是数据最大范围是4个字节,十进制的范围是0至4294967295。一旦超过了这个范围,则运算会出错。因此,当进行大数据减法运算时,我们要额外编程序,实现大数据的算法。其实这种算法并不难,就是我们在小学里学的四则运算算法。
      我们先要弄清楚一个新的概念。不考虑小数点的情况下,数据有两种表现形式。一种是常用的变量形式,另外一种是BCD码数组形式。变量的最大范围有限,而BCD码数组的形式是无限的,正因为这个特点,所以我们可以进行大数据运算。
    这一节要教大家两个知识点:
第一个:如何编写比较两个非组合BCD码数据的大小。
第二个:如何编写涉及到大数据减法运算的算法程序函数,同时也复习了指针的用途。

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

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

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被减数和减数。单片机把组合BCD码的运算结果返回到上位机。最大范围4位,从0到9999,如果被减数小于减数则返回EE EE EE报错。往单片机发送的数据格式:EB 00 55 XX XX 0d 0aYY YY0d 0a指令,其中EB 00 55是数据头,XX 是被减数,可以是1个字节,也可以是2个字节。YY是减数,可以是1个字节,也可以是2个字节。0d 0a是固定的结束标志。
例如:
(a)8259 – 5267 = 2992
上位机发送数据:eb 00 55 82 59 0d 0a52 67 0d 0a
单片机返回:29 92

(b)5267 - 8259=小于0所以报错
上位机发送数据:eb 00 5552 67 0d 0a82 59 0d 0a
单片机返回:EE EE EE表示出错了

(3)源代码讲解如下:
#include "REG52.H"


/* 注释一:
* 本系统中,规定最大运算位数是4位。
* 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
* 不能超过256个,如果超过了这个极限,编译器就会报错。如果这个算法
* 移植到stm32或者PIC等RAM比较大的单片机上,那么就可以把这个运算位数
* 设置得更加大一点。
*/

#defineBCD4_MAX   2//本系统中,规定的组合BCD码最大字节数,一个字节包含2位,因此4位有效运算数
#defineBCD8_MAX    (BCD4_MAX*2)//本系统中,规定的非组合BCD码最大字节数,一个字节包含1位,因此4位有效运算数

#define const_rc_size30//接收串口中断数据的缓冲区数组大小

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

#define uchar unsigned char    //方便移植平台
#define ulong unsigned long   //方便移植平台

//如果在VC的平台模拟此算法,则都定义成int类型,如下:
//#define uchar int
//#define ulong int

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


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


void eusart_send(unsigned char ucSendData);

void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt);
void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt);

void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
uchar CmpData(const uchar *destData,const uchar *sourceData); //比较两个数的大小
uchar SubData(const uchar *destData,const uchar *sourceData,uchar *resultData);//两个数相减

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

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


unsigned char ucDataBCD4_1; //接收到的第1个数组合BCD码数组形式这里是指被减数
unsigned char ucDataBCD4_cnt_1=0;//接收到的第1个数组合BCD码数组的有效数据长度

unsigned char ucDataBCD4_2; //接收到的第2个数组合BCD码数组形式这里是指减数
unsigned char ucDataBCD4_cnt_2=0;//接收到的第2个数组合BCD码数组的有效数据长度

unsigned char ucDataBCD4_3; //接收到的第3个数组合BCD码数组形式这里是指差
unsigned char ucDataBCD4_cnt_3=0;//接收到的第3个数组合BCD码数组的有效数据长度


unsigned char ucDataBCD8_1; //接收到的第1个数非组合BCD码数组形式   这里是指被减数
unsigned char ucDataBCD8_cnt_1=0;//接收到的第1个数非组合BCD码数组的有效数据长度

unsigned char ucDataBCD8_2; //接收到的第2个数非组合BCD码数组形式   这里是指减数
unsigned char ucDataBCD8_cnt_2=0;//接收到的第2个数非组合BCD码数组的有效数据长度

unsigned char ucDataBCD8_3; //接收到的第3个数非组合BCD码数组形式   这里是指差
unsigned char ucDataBCD8_cnt_3=0;//接收到的第3个数非组合BCD码数组的有效数据长度

unsigned char ucResultFlag=11; //运算结果标志,10代表计算结果超出范围出错,11代表正常。

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

}

/* 注释二:
* 组合BCD码转成非组合BCD码。
* 这里的变量ucBCD4_cnt代表组合BCD码的有效字节数.
* 这里的变量*p_ucBCD8_cnt代表经过转换后,非组合BCD码的有效字节数,记得加地址符号&传址进去
* 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
* 同时引进了组合BCD码的有效字节数变量。这样就不限定了数据的长度,
* 可以让我们根据数据的实际大小灵活运用。
*/
void BCD4_to_BCD8(const unsigned char *p_ucBCD_bit4,unsigned char ucBCD4_cnt,unsigned char *p_ucBCD_bit8,unsigned char *p_ucBCD8_cnt)
{
   unsigned char ucTmep;
   unsigned char i;

   for(i=0;i<BCD8_MAX;i++)   //先把即将保存转换结果的缓冲区清零
   {
      p_ucBCD_bit8=0;
   }


   *p_ucBCD8_cnt=ucBCD4_cnt*2; //转换成非组合BCD码后的有效数据长度

   for(i=0;i<ucBCD4_cnt;i++)
   {
      ucTmep=p_ucBCD_bit4;
      p_ucBCD_bit8=ucTmep>>4;   
      p_ucBCD_bit8=ucTmep&0x0f;
   }

}


/* 注释三:
* 非组合BCD码转成组合BCD码。
* 这里的变量ucBCD8_cnt代表非组合BCD码的有效字节数.
* 这里的变量*p_ucBCD4_cnt代表经过转换后,组合BCD码的有效字节数,记得加地址符号&传址进去
* 本程序在上一节的基础上,略作修改,用循环for语句压缩了代码,
* 同时引进了非组合BCD码的有效字节数变量。这样就不限定了数据的长度,
* 可以让我们根据数据的实际大小灵活运用。
*/
void BCD8_to_BCD4(const unsigned char *p_ucBCD_bit8,unsigned char ucBCD8_cnt,unsigned char *p_ucBCD_bit4,unsigned char *p_ucBCD4_cnt)
{
   unsigned char ucTmep;
   unsigned char i;
   unsigned char ucBCD4_cnt;

   for(i=0;i<BCD4_MAX;i++)   //先把即将保存转换结果的缓冲区清零
   {
      p_ucBCD_bit4=0;
   }

   ucBCD4_cnt=(ucBCD8_cnt+1)/2; //非组合BCD码转化成组合BCD码的有效数,这里+1避免非组合数据长度是奇数位
   *p_ucBCD4_cnt=ucBCD4_cnt; //把转换后的结果付给接口指针的数据,可以对外输出结果

   for(i=0;i<ucBCD4_cnt;i++)
   {
      ucTmep=p_ucBCD_bit8;    //把非组合BCD码第8位分解出来
      p_ucBCD_bit4=ucTmep<<4;
      p_ucBCD_bit4=p_ucBCD_bit4+p_ucBCD_bit8;    //把非组合BCD码第7位分解出来
   }

}

/* 注释四:
*函数介绍:清零数组的全部数组数据
*输入参数:ucARRAY_MAX代表数组定义的最大长度
*输入输出参数:*destData--被清零的数组。
*/

void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
{
uchar i;

for(i=0;i<ucARRAY_MAX;i++)
{
   destData=0;
}

}


/* 注释五:
*函数介绍:获取数组的有效长度
*输入参数:*destData--被获取的数组。
*输入参数:ucARRAY_MAX代表数组定义的最大长度
*返回值:返回数组的有效长度。比如58786这个数据的有效长度是5
*电子开发者作者:吴坚鸿
*/
uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
{
uchar i;
uchar DataLength=ucARRAY_MAX;
for(i=0;i<ucARRAY_MAX;i++)
{
      if(0!=destData)
          {
             break;
          }
          else
          {
             DataLength--;
          }

}

return DataLength;

}



/* 注释六:
*函数介绍:比较两个数的大小
*输入参数:
*(1)*destData--被比较数的数组。
*(2)*sourceData--比较数的数组。
*返回值:9代表小于,10代表相等,11代表大于。
*/
uchar CmpData(const uchar *destData,const uchar *sourceData)
{
uchar cmpResult=10; //开始默认相等
uchar destCnt=0;
uchar sourceCnt=0;
uchar i;

destCnt=GetDataLength(destData,BCD8_MAX);
sourceCnt=GetDataLength(sourceData,BCD8_MAX);

if(destCnt>sourceCnt)//大于
{
cmpResult=11;
}
else if(destCnt<sourceCnt) //小于
{
cmpResult=9;
}
else if((destCnt==0)&&(sourceCnt==0))//如果都是等于0则等于
{
cmpResult=10;
}
else//否则就要继续判断
{
for(i=0;i<destCnt;i++)
{
   if(destData>sourceData)   //从最高位开始判断,如果最高位大于则大于
       {
           cmpResult=11;
           break;
       }
   else if(destData<sourceData)//从最高位开始判断,如果最高位小于则小于
       {
           cmpResult=9;
           break;
       }

   //否则继续判断下一位
}
}


return cmpResult;
}


/* 注释七:
*函数介绍:两个数相减
*输入参数:
*(1)*destData--被减数的数组。
*(2)*sourceData--减数的数组。
*(3)*resultData--差的数组。注意,调用本函数前,必须先把这个数组清零
*返回值:10代表计算结果是负数或者超出范围出错,11代表正常。
*/
uchar SubData(const uchar *destData,const uchar *sourceData,uchar *resultData)
{
uchar subResult=11; //开始默认正常
uchar destCnt=0;

uchar i;
uchar carryData=0;//进位
uchar maxCnt=0; //最大位数
uchar resultTemp=0; //存放临时运算结果的中间变量

//为什么不在本函数内先把resultData数组清零?因为后面章节中的除法运算中要用到此函数实现连减功能。
//因此如果纯粹实现减法运算时,在调用本函数之前,必须先在外面把差的数组清零,否则会计算出错。

if(CmpData(destData,sourceData)==9)//被减数小于减数,报错
{
   subResult=10;
   return subResult;//返回判断结果,并且退出本程序,不往下执行本程序余下代码
}

destCnt=GetDataLength(destData,BCD8_MAX);//获取被减数的有效数据长度
maxCnt=destCnt;


for(i=0;i<maxCnt;i++)
{

   resultTemp=sourceData+carryData; //按位相加
   if(resultTemp>destData)
   {
      resultData=destData+10-sourceData-carryData;    //借位
          carryData=1;
   }
   else
   {
      resultData=destData-sourceData-carryData;    //不用借位
          carryData=0;
   }

}


return subResult;
}



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

   unsigned char i=0;   
   unsigned char k=0;   
         unsigned char ucGetDataStep=0;

   if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
   {

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

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

            uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动
            while(uiRcMoveIndex<uiRcregTotal)//说明还没有把缓冲区的数据读取完
            {
               if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55)//数据头eb 00 55的判断
               {
                  
                   i=0;
                   ucGetDataStep=0;
                   ucDataBCD4_cnt_1=0;//第1个数组合BCD码数组的有效数据长度
                   ucDataBCD4_cnt_2=0;//第2个数组合BCD码数组的有效数据长度

                   ClearAllData(BCD4_MAX,ucDataBCD4_1);//清零第1个参与运算的数据
                   ClearAllData(BCD4_MAX,ucDataBCD4_2);//清零第2个参与运算的数据

                   //以下while循环是通过关键字0x0d 0x0a来截取第1个和第2个参与运算的数据。
                   while(i<(BCD8_MAX+4))//这里+4是因为有2对0x0d 0x0a结尾特殊符号,一个共4个字节
                   {
                      if(ucGetDataStep==0)//步骤0,相当于我平时用的case 0,获取第1个数,在这里是指被加数
                      {
                           if(ucRcregBuf==0x0d&&ucRcregBuf==0x0a) //结束标志
                           {
                              for(k=0;k<ucDataBCD4_cnt_1;k++) //提取第1个参与运算的数组数据
                              {
                                    ucDataBCD4_1=ucRcregBuf; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
                              }                                                                                                               
                              i=i+2; //跳过 0x0d 0x0a 这两个字节,进行下一轮的关键字提取
                              ucGetDataStep=1;//切换到下一个关键字提取的步骤

                           }
                           else
                           {
                              i++;
                              ucDataBCD4_cnt_1++;//统计第1个有效数据的长度
                           }
                                                                                                                        
                     }
                     else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是加数
                     {
                           if(ucRcregBuf==0x0d&&ucRcregBuf==0x0a) //结束标志
                           {
                              for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
                              {
                                    ucDataBCD4_2=ucRcregBuf; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
                              }
                                                                                                                                                      
                              break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

                            }
                            else
                            {
                              i++;
                              ucDataBCD4_cnt_2++;//统计第2个有效数据的长度
                            }
                     }
                  }


                  //注意ucDataBCD8_cnt_1和ucDataBCD8_cnt_2要带地址符号&传址进去
                  BCD4_to_BCD8(ucDataBCD4_1,ucDataBCD4_cnt_1,ucDataBCD8_1,&ucDataBCD8_cnt_1); //把接收到的组合BCD码转换成非组合BCD码第1个数
                  BCD4_to_BCD8(ucDataBCD4_2,ucDataBCD4_cnt_2,ucDataBCD8_2,&ucDataBCD8_cnt_2); //把接收到的组合BCD码转换成非组合BCD码第2个数


                  ClearAllData(BCD8_MAX,ucDataBCD8_3);//清零第3个参与运算的数据,用来接收运行的结果
                  ucResultFlag=SubData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相减运算,结果放在ucDataBCD8_3数组里
                  if(ucResultFlag==11) //表示运算结果没有超范围
                  {
                     ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX);//获取运算结果的有效字节数
                     BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
                     for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
                     {
                        eusart_send(ucDataBCD4_3); //往上位机发送一个字节的函数
                     }
                  }
                  else //运算结果超范围,返回EE EE EE
                  {
                     eusart_send(0xee); //往上位机发送一个字节的函数
                     eusart_send(0xee); //往上位机发送一个字节的函数
                     eusart_send(0xee); //往上位机发送一个字节的函数
                  }

                  break;   //退出循环
               }
               uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
         }

         ucRcregBuf=0; //把数据头清零,方便下次接收判断新数据
         ucRcregBuf=0;
         ucRcregBuf=0;         
                  
         uiRcregTotal=0;//清空缓冲的下标,方便下次重新从0下标开始接受新数据

   }
                        
}

void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
{

ES = 0; //关串口中断
TI = 0; //清零串口发送完成中断请求标志
SBUF =ucSendData; //发送一个字节

delay_short(400);//每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整

TI = 0; //清零串口发送完成中断请求标志
ES = 1; //允许串口中断

}



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


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



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//发送中断,及时把发送中断标志位清零
   {
      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 delay_short(unsigned int uiDelayShort)
{
   unsigned int i;
   for(i=0;i<uiDelayShort;i++)
   {
   ;   //一个分号相当于执行一条空语句
   }
}


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;    //启动定时中断

}
总结陈词:
既然这节讲了减法程序,那么下一节接着讲常用的乘法程序,这种大数据的乘法程序是什么样的?欲知详情,请听下回分解----大数据的乘法运算。

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

tt98 发表于 2014-8-20 20:51:06

追着更新的步伐{:titter:}

papa0305 发表于 2014-8-20 21:01:13

占座学习   

fly7817 发表于 2014-8-20 22:08:16

mark, 这个要学习一下
页: 1 2 3 4 5 6 7 [8] 9 10 11 12 13 14 15
查看完整版本: 从业将近十年!手把手教你单片机程序框架(连载)