14

帖子

0

TA的资源

一粒金砂(初级)

101
 
强贴,必须要顶
此帖出自51单片机论坛
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

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

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

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

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

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

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


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

  4. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  5. #define const_voice_long   200  //蜂鸣器长叫的持续时间

  6. void initial_myself();   
  7. void initial_peripheral();
  8. void delay_long(unsigned int uiDelaylong);
  9. void led_flicker();
  10. void alarm_run();   
  11. void T0_time();  //定时中断函数

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

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

  15. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  16. unsigned char ucLock=0;     //互斥量,俗称原子锁
  17. void main()
  18.   {
  19.    initial_myself();  
  20.    delay_long(100);   
  21.    initial_peripheral();
  22.    while(1)  
  23.    {
  24.       alarm_run();   //报警器定时报警
  25.    }

  26. }


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

  33. void alarm_run() //报警器的应用程序
  34. {
  35.   
  36.   switch(ucAlarmStep)
  37.   {
  38.      case 0:

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

  70.               ucAlarmStep=0; //返回到上一个步骤
  71.            }
  72.            break;
  73.   }

  74. }

  75. void T0_time() interrupt 1
  76. {
  77.   TF0=0;  //清除中断标志
  78.   TR0=0; //关中断
  79.   
  80.   if(ucLock==0) //互斥量判断
  81.   {
  82.      if(uiTimeAlarmCnt<0xffff)  //设定这个条件,防止uiTimeAlarmCnt超范围。
  83.      {
  84.          uiTimeAlarmCnt++;  //报警的时间计数器,累加定时中断的次数,
  85.      }

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

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


  101. void delay_long(unsigned int uiDelayLong)
  102. {
  103.    unsigned int i;
  104.    unsigned int j;
  105.    for(i=0;i<uiDelayLong;i++)
  106.    {
  107.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  108.           {
  109.              ; //一个分号相当于执行一条空语句
  110.           }
  111.    }
  112. }


  113. void initial_myself()  //第一区 初始化单片机
  114. {
  115.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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


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

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

  125. }
复制代码

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

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

24

帖子

1

TA的资源

一粒金砂(中级)

103
 
多谢前辈的指点!
此帖出自51单片机论坛
 
 
 

回复

24

帖子

1

TA的资源

一粒金砂(中级)

104
 
鸿哥什么时候能把后面的也写上去?好期待!!
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

105
 
第六十一节:组合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)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间


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

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

  8. void initial_myself(void);   
  9. void initial_peripheral(void);
  10. void delay_long(unsigned int uiDelaylong);
  11. void delay_short(unsigned int uiDelayShort);


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


  15. void eusart_send(unsigned char ucSendData);

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


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

  23. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  24. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  25. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  26. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  27. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量

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

  35. void main()
  36.   {
  37.    initial_myself();  
  38.    delay_long(100);   
  39.    initial_peripheral();
  40.    while(1)  
  41.    {
  42.        usart_service();  //串口服务程序
  43.    }

  44. }

  45. void number_to_BCD4(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit4)//把数值转换成组合BCD码
  46. {
  47.    unsigned long ulNumberTemp=0;
  48.    unsigned char ucTemp=0;
  49.    ulNumberTemp=p_ucNumber[3];  //把4个字节的数值合并成一个long类型数据
  50.    ulNumberTemp=ulNumberTemp<<8;
  51.    ulNumberTemp=ulNumberTemp+p_ucNumber[2];
  52.    ulNumberTemp=ulNumberTemp<<8;
  53.    ulNumberTemp=ulNumberTemp+p_ucNumber[1];
  54.    ulNumberTemp=ulNumberTemp<<8;
  55.    ulNumberTemp=ulNumberTemp+p_ucNumber[0];


  56.    p_ucBCD_bit4[3]=ulNumberTemp%100000000/10000000;
  57.    p_ucBCD_bit4[3]=p_ucBCD_bit4[3]<<4; //前半4位存第8位组合BCD码
  58.    ucTemp=ulNumberTemp%10000000/1000000;
  59.    p_ucBCD_bit4[3]=p_ucBCD_bit4[3]+ucTemp; //后半4位存第7位组合BCD码

  60.    p_ucBCD_bit4[2]=ulNumberTemp%1000000/100000;
  61.    p_ucBCD_bit4[2]=p_ucBCD_bit4[2]<<4; //前半4位存第6位组合BCD码
  62.    ucTemp=ulNumberTemp%100000/10000;
  63.    p_ucBCD_bit4[2]=p_ucBCD_bit4[2]+ucTemp;//后半4位存第5位组合BCD码

  64.    p_ucBCD_bit4[1]=ulNumberTemp%10000/1000;
  65.    p_ucBCD_bit4[1]=p_ucBCD_bit4[1]<<4; //前半4位存第4位组合BCD码
  66.    ucTemp=ulNumberTemp%1000/100;
  67.    p_ucBCD_bit4[1]=p_ucBCD_bit4[1]+ucTemp;//后半4位存第3位组合BCD码

  68.    p_ucBCD_bit4[0]=ulNumberTemp%100/10;
  69.    p_ucBCD_bit4[0]=p_ucBCD_bit4[0]<<4; //前半4位存第2位组合BCD码
  70.    ucTemp=ulNumberTemp%10;
  71.    p_ucBCD_bit4[0]=p_ucBCD_bit4[0]+ucTemp;//后半4位存第1位组合BCD码

  72. }


  73. void number_to_BCD8(const unsigned char *p_ucNumber,unsigned char *p_ucBCD_bit8)//把数值转换成非组合BCD码
  74. {
  75.    unsigned long ulNumberTemp=0;
  76.    ulNumberTemp=p_ucNumber[3];  //把4个字节的数值合并成一个long类型数据
  77.    ulNumberTemp=ulNumberTemp<<8;
  78.    ulNumberTemp=ulNumberTemp+p_ucNumber[2];
  79.    ulNumberTemp=ulNumberTemp<<8;
  80.    ulNumberTemp=ulNumberTemp+p_ucNumber[1];
  81.    ulNumberTemp=ulNumberTemp<<8;
  82.    ulNumberTemp=ulNumberTemp+p_ucNumber[0];

  83.    p_ucBCD_bit8[7]=ulNumberTemp%100000000/10000000;//一个字节8位存储第8位非组合BCD码
  84.    p_ucBCD_bit8[6]=ulNumberTemp%10000000/1000000;//一个字节8位存储第7位非组合BCD码
  85.    p_ucBCD_bit8[5]=ulNumberTemp%1000000/100000;//一个字节8位存储第6位非组合BCD码
  86.    p_ucBCD_bit8[4]=ulNumberTemp%100000/10000;//一个字节8位存储第5位非组合BCD码
  87.    p_ucBCD_bit8[3]=ulNumberTemp%10000/1000;//一个字节8位存储第4位非组合BCD码
  88.    p_ucBCD_bit8[2]=ulNumberTemp%1000/100;//一个字节8位存储第3位非组合BCD码
  89.    p_ucBCD_bit8[1]=ulNumberTemp%100/10;//一个字节8位存储第2位非组合BCD码
  90.    p_ucBCD_bit8[0]=ulNumberTemp%10;//一个字节8位存储第1位非组合BCD码

  91. }


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

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

  97.    ulTmep=0;
  98.    ulTmep=p_ucBCD_bit4[3];
  99.    ulTmep=ulTmep>>4;  //把组合BCD码第8位分解出来
  100.    ulTmep=ulTmep*10000000;
  101.    ulSum=ulSum+ulTmep; //累加各位数值

  102.    ulTmep=0;
  103.    ulTmep=p_ucBCD_bit4[3];
  104.    ulTmep=ulTmep&0x0000000f;  //把组合BCD码第7位分解出来
  105.    ulTmep=ulTmep*1000000;
  106.    ulSum=ulSum+ulTmep; //累加各位数值

  107.    ulTmep=0;
  108.    ulTmep=p_ucBCD_bit4[2];
  109.    ulTmep=ulTmep>>4;  //把组合BCD码第6位分解出来
  110.    ulTmep=ulTmep*100000;
  111.    ulSum=ulSum+ulTmep; //累加各位数值

  112.    ulTmep=0;
  113.    ulTmep=p_ucBCD_bit4[2];
  114.    ulTmep=ulTmep&0x0000000f;  //把组合BCD码第5位分解出来
  115.    ulTmep=ulTmep*10000;
  116.    ulSum=ulSum+ulTmep; //累加各位数值

  117.    ulTmep=0;
  118.    ulTmep=p_ucBCD_bit4[1];
  119.    ulTmep=ulTmep>>4;  //把组合BCD码第4位分解出来
  120.    ulTmep=ulTmep*1000;
  121.    ulSum=ulSum+ulTmep; //累加各位数值

  122.    ulTmep=0;
  123.    ulTmep=p_ucBCD_bit4[1];
  124.    ulTmep=ulTmep&0x0000000f;  //把组合BCD码第3位分解出来
  125.    ulTmep=ulTmep*100;
  126.    ulSum=ulSum+ulTmep; //累加各位数值

  127.    ulTmep=0;
  128.    ulTmep=p_ucBCD_bit4[0];
  129.    ulTmep=ulTmep>>4;  //把组合BCD码第2位分解出来
  130.    ulTmep=ulTmep*10;
  131.    ulSum=ulSum+ulTmep; //累加各位数值

  132.    ulTmep=0;
  133.    ulTmep=p_ucBCD_bit4[0];
  134.    ulTmep=ulTmep&0x0000000f;  //把组合BCD码第1位分解出来
  135.    ulTmep=ulTmep*1;
  136.    ulSum=ulSum+ulTmep; //累加各位数值

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

  138.    p_ucNumber[3]=ulSum>>24;  //把long类型数据分解成4个字节
  139.    p_ucNumber[2]=ulSum>>16;
  140.    p_ucNumber[1]=ulSum>>8;
  141.    p_ucNumber[0]=ulSum;
  142. }



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

  146.    ucTmep=p_ucBCD_bit4[3];
  147.    p_ucBCD_bit8[7]=ucTmep>>4;    //把组合BCD码第8位分解出来
  148.    p_ucBCD_bit8[6]=ucTmep&0x0f;  //把组合BCD码第7位分解出来

  149.    ucTmep=p_ucBCD_bit4[2];
  150.    p_ucBCD_bit8[5]=ucTmep>>4;    //把组合BCD码第6位分解出来
  151.    p_ucBCD_bit8[4]=ucTmep&0x0f;  //把组合BCD码第5位分解出来

  152.    ucTmep=p_ucBCD_bit4[1];
  153.    p_ucBCD_bit8[3]=ucTmep>>4;    //把组合BCD码第4位分解出来
  154.    p_ucBCD_bit8[2]=ucTmep&0x0f;  //把组合BCD码第3位分解出来

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

  158. }



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

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

  164.    ulTmep=0;
  165.    ulTmep=p_ucBCD_bit8[7];
  166.    ulTmep=ulTmep*10000000;
  167.    ulSum=ulSum+ulTmep; //累加各位数值

  168.    ulTmep=0;
  169.    ulTmep=p_ucBCD_bit8[6];
  170.    ulTmep=ulTmep*1000000;
  171.    ulSum=ulSum+ulTmep; //累加各位数值

  172.    ulTmep=0;
  173.    ulTmep=p_ucBCD_bit8[5];
  174.    ulTmep=ulTmep*100000;
  175.    ulSum=ulSum+ulTmep; //累加各位数值

  176.    ulTmep=0;
  177.    ulTmep=p_ucBCD_bit8[4];
  178.    ulTmep=ulTmep*10000;
  179.    ulSum=ulSum+ulTmep; //累加各位数值

  180.    ulTmep=0;
  181.    ulTmep=p_ucBCD_bit8[3];
  182.    ulTmep=ulTmep*1000;
  183.    ulSum=ulSum+ulTmep; //累加各位数值

  184.    ulTmep=0;
  185.    ulTmep=p_ucBCD_bit8[2];
  186.    ulTmep=ulTmep*100;
  187.    ulSum=ulSum+ulTmep; //累加各位数值

  188.    ulTmep=0;
  189.    ulTmep=p_ucBCD_bit8[1];
  190.    ulTmep=ulTmep*10;
  191.    ulSum=ulSum+ulTmep; //累加各位数值

  192.    ulTmep=0;
  193.    ulTmep=p_ucBCD_bit8[0];
  194.    ulTmep=ulTmep*1;
  195.    ulSum=ulSum+ulTmep; //累加各位数值

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

  197.    p_ucNumber[3]=ulSum>>24;  //把long类型数据分解成4个字节
  198.    p_ucNumber[2]=ulSum>>16;
  199.    p_ucNumber[1]=ulSum>>8;
  200.    p_ucNumber[0]=ulSum;
  201. }



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

  205.    ucTmep=p_ucBCD_bit8[7];    //把非组合BCD码第8位分解出来
  206.    p_ucBCD_bit4[3]=ucTmep<<4;
  207.    p_ucBCD_bit4[3]=p_ucBCD_bit4[3]+p_ucBCD_bit8[6];    //把非组合BCD码第7位分解出来

  208.    ucTmep=p_ucBCD_bit8[5];    //把非组合BCD码第6位分解出来
  209.    p_ucBCD_bit4[2]=ucTmep<<4;
  210.    p_ucBCD_bit4[2]=p_ucBCD_bit4[2]+p_ucBCD_bit8[4];    //把非组合BCD码第5位分解出来

  211.    ucTmep=p_ucBCD_bit8[3];    //把非组合BCD码第4位分解出来
  212.    p_ucBCD_bit4[1]=ucTmep<<4;
  213.    p_ucBCD_bit4[1]=p_ucBCD_bit4[1]+p_ucBCD_bit8[2];    //把非组合BCD码第3位分解出来

  214.    ucTmep=p_ucBCD_bit8[1];    //把非组合BCD码第2位分解出来
  215.    p_ucBCD_bit4[0]=ucTmep<<4;
  216.    p_ucBCD_bit4[0]=p_ucBCD_bit4[0]+p_ucBCD_bit8[0];    //把非组合BCD码第1位分解出来
  217.   
  218. }

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

  221.      unsigned char i=0;   

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

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

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

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

  227.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  228.             {
  229.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  230.                {
  231.                     switch(ucRcregBuf[uiRcMoveIndex+3])  //根据命令类型来进行不同的处理
  232.                                         {
  233.                                            case 1:  //接收到的是数值,需要转成组合BCD码和非组合BCD码
  234.                             for(i=0;i<4;i++)
  235.                             {
  236.                                 ucBufferNumber[3-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的数据,注意,高位在数组下标大的方向
  237.                             }
  238.                             number_to_BCD4(ucBufferNumber,ucBufferBCB_bit4);//把数值转换成组合BCD码
  239.                             number_to_BCD8(ucBufferNumber,ucBufferBCB_bit8);//把数值转换成非组合BCD码
  240.                             for(i=0;i<4;i++)
  241.                             {
  242.                                eusart_send(ucBufferBCB_bit4[3-i]);  ////把组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
  243.                             }
  244.                             eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
  245.                             eusart_send(0xee);
  246.                             eusart_send(0xee);
  247.                             for(i=0;i<8;i++)
  248.                             {
  249.                                eusart_send(ucBufferBCB_bit8[7-i]);  ////把非组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
  250.                             }

  251.                                                 break;
  252.                                            case 2:  //接收到的是组合BCD码,需要转成数值和非组合BCD码
  253.                             for(i=0;i<4;i++)
  254.                             {
  255.                                 ucBufferBCB_bit4[3-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的组合BCD码,注意,高位在数组下标大的方向
  256.                             }
  257.                             BCD4_to_number(ucBufferBCB_bit4,ucBufferNumber); //组合BCD码转成数值
  258.                             BCD4_to_BCD8(ucBufferBCB_bit4,ucBufferBCB_bit8); //组合BCD码转成非组合BCD码
  259.                             for(i=0;i<4;i++)
  260.                             {
  261.                                eusart_send(ucBufferNumber[3-i]);  ////把数值返回给上位机观察,注意,高位在数组下标大的方向
  262.                             }
  263.                             eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为分割线
  264.                             eusart_send(0xee);
  265.                             eusart_send(0xee);
  266.                             for(i=0;i<8;i++)
  267.                             {
  268.                                eusart_send(ucBufferBCB_bit8[7-i]);  ////把非组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
  269.                             }

  270.                                                 break;
  271.                                            case 3:  //接收到的是非组合BCD码,需要转成数值和组合BCD码
  272.                             for(i=0;i<8;i++)
  273.                             {
  274.                                 ucBufferBCB_bit8[7-i]=ucRcregBuf[uiRcMoveIndex+4+i]; //从串口接收到的非组合BCD码,注意,高位在数组下标大的方向
  275.                             }

  276.                             BCD8_to_number(ucBufferBCB_bit8,ucBufferNumber); //非组合BCD码转成数值
  277.                             BCD8_to_BCD4(ucBufferBCB_bit8,ucBufferBCB_bit4); //非组合BCD码转成组合BCD码
  278.                             for(i=0;i<4;i++)
  279.                             {
  280.                                eusart_send(ucBufferNumber[3-i]);  ////把数值返回给上位机观察
  281.                             }
  282.                             eusart_send(0xee);  //为了方便上位机观察,多发送3个字节ee ee ee作为分割线,注意,高位在数组下标大的方向
  283.                             eusart_send(0xee);
  284.                             eusart_send(0xee);
  285.                             for(i=0;i<4;i++)
  286.                             {
  287.                                eusart_send(ucBufferBCB_bit4[3-i]);  ////把组合BCD码返回给上位机观察,注意,高位在数组下标大的方向
  288.                             }

  289.                                                 break;
  290.                                         }

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

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

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

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

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

  309. }



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


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



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


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

  325.    if(RI==1)  
  326.    {
  327.         RI = 0;

  328.             ++uiRcregTotal;
  329.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  330.         {
  331.            uiRcregTotal=const_rc_size;
  332.         }
  333.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  334.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  335.    
  336.    }
  337.    else  //发送中断,及时把发送中断标志位清零
  338.    {
  339.         TI = 0;
  340.    }
  341.                                                          
  342. }                                


  343. void delay_long(unsigned int uiDelayLong)
  344. {
  345.    unsigned int i;
  346.    unsigned int j;
  347.    for(i=0;i<uiDelayLong;i++)
  348.    {
  349.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  350.           {
  351.              ; //一个分号相当于执行一条空语句
  352.           }
  353.    }
  354. }

  355. void delay_short(unsigned int uiDelayShort)
  356. {
  357.    unsigned int i;  
  358.    for(i=0;i<uiDelayShort;i++)
  359.    {
  360.      ;   //一个分号相当于执行一条空语句
  361.    }
  362. }


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

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

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


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

  375. }

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

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

  382. }
复制代码

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

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

105

帖子

1

TA的资源

一粒金砂(中级)

106
 
刚看了几节,对我有了一些启发,我对51也算了解了,也做过一些51的设计,但是一些编程思想还是不怎么懂,还有就是缺乏真正的开发经验,楼主不愧是有开发经验的啊,很多东西都说到点上了。
此帖出自51单片机论坛
 
 
 

回复

105

帖子

1

TA的资源

一粒金砂(中级)

107
 
我想说,鸿哥你的这些经验对于刚接触不久单片机的人来说,非常有难度啊,虽然是实际工程中应用的,但是对于初学者来说不太适合。
此帖出自51单片机论坛
 
 
 

回复

9

帖子

0

TA的资源

一粒金砂(初级)

108
 
顶一个
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

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

开场白:
直接用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 0a  YY YY  0d 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 99  0d 0a 56 0d 0a
单片机返回:EE EE EE  表示出错了

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


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

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

  11. #define const_rc_size  30  //接收串口中断数据的缓冲区数组大小

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

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

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

  18. void initial_myself(void);   
  19. void initial_peripheral(void);
  20. void delay_long(unsigned int uiDelaylong);
  21. void delay_short(unsigned int uiDelayShort);


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


  25. void eusart_send(unsigned char ucSendData);

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

  28. void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
  29. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
  30. uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData);

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

  32. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  33. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  34. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  35. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  36. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


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

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

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


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

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

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

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

  50. void main()
  51.   {
  52.    initial_myself();  
  53.    delay_long(100);   
  54.    initial_peripheral();
  55.    while(1)  
  56.    {
  57.        usart_service();  //串口服务程序
  58.    }

  59. }

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

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


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

  77.    for(i=0;i<ucBCD4_cnt;i++)
  78.    {
  79.       ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
  80.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;   
  81.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;  
  82.    }

  83. }


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

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

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

  103.    for(i=0;i<ucBCD4_cnt;i++)
  104.    {
  105.       ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2];    //把非组合BCD码第8位分解出来
  106.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
  107.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2];    //把非组合BCD码第7位分解出来
  108.    }
  109.   
  110. }

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

  116. void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
  117. {
  118.   uchar i;

  119.   for(i=0;i<ucARRAY_MAX;i++)
  120.   {
  121.      destData[i]=0;
  122.   }

  123. }


  124. /* 注释五:
  125. *函数介绍:获取数组的有效长度
  126. *输入参数:*destData--被获取的数组。
  127. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  128. *返回值  :返回数组的有效长度。比如58786这个数据的有效长度是5
  129. *电子开发者作者:吴坚鸿
  130. */
  131. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
  132. {
  133.   uchar i;
  134.   uchar DataLength=ucARRAY_MAX;
  135.   for(i=0;i<ucARRAY_MAX;i++)
  136.   {
  137.       if(0!=destData[ucARRAY_MAX-1-i])
  138.           {
  139.              break;
  140.           }
  141.           else
  142.           {
  143.              DataLength--;
  144.           }

  145.   }

  146.   return DataLength;

  147. }



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

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

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

  169. if(destCnt>=sourceCnt)  //找出两个运算数据中最大的有效位数
  170. {
  171.    maxCnt=destCnt;
  172. }
  173. else
  174. {
  175.    maxCnt=sourceCnt;
  176. }

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

  183. resultData[i]=carryData;

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

  186.   ClearAllData(BCD8_MAX,resultData);

  187.   addResult=10;  //报错
  188. }


  189. return addResult;
  190. }




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

  193.      unsigned char i=0;   
  194.      unsigned char k=0;   
  195.          unsigned char ucGetDataStep=0;

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

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

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

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

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

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

  226.                              }
  227.                                                          else
  228.                                                          {
  229.                                                                 i++;
  230.                                                                 ucDataBCD4_cnt_1++;  //统计第1个有效数据的长度
  231.                              }
  232.                                                                                                                          
  233.                                              }
  234.                                                  else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是加数
  235.                                                  {
  236.                                                           if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  237.                                                          {
  238.                                 for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
  239.                                                                 {
  240.                                                                    ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  241.                                                                 }
  242.                                                                                                                                                        
  243.                                 break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

  244.                              }
  245.                                                          else
  246.                                                          {
  247.                                                                 i++;
  248.                                                                 ucDataBCD4_cnt_2++;  //统计第2个有效数据的长度
  249.                              }
  250.                          }
  251.                     }


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


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

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

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

  276.            ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
  277.                    ucRcregBuf[1]=0;
  278.                    ucRcregBuf[2]=0;         
  279.                   
  280.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  281.   
  282.      }
  283.                         
  284. }

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

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

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

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

  293. }



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


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



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


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

  309.    if(RI==1)  
  310.    {
  311.         RI = 0;

  312.             ++uiRcregTotal;
  313.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  314.         {
  315.            uiRcregTotal=const_rc_size;
  316.         }
  317.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  318.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  319.    
  320.    }
  321.    else  //发送中断,及时把发送中断标志位清零
  322.    {
  323.         TI = 0;
  324.    }
  325.                                                          
  326. }                                


  327. void delay_long(unsigned int uiDelayLong)
  328. {
  329.    unsigned int i;
  330.    unsigned int j;
  331.    for(i=0;i<uiDelayLong;i++)
  332.    {
  333.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  334.           {
  335.              ; //一个分号相当于执行一条空语句
  336.           }
  337.    }
  338. }

  339. void delay_short(unsigned int uiDelayShort)
  340. {
  341.    unsigned int i;  
  342.    for(i=0;i<uiDelayShort;i++)
  343.    {
  344.      ;   //一个分号相当于执行一条空语句
  345.    }
  346. }


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

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

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


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

  359. }

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

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

  366. }
复制代码

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

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复

19

帖子

0

TA的资源

一粒金砂(中级)

110
 
此帖出自51单片机论坛
 
 
 

回复

26

帖子

2

TA的资源

一粒金砂(中级)

111
 
楼主继续 强烈支持
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

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

开场白:
直接用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 0a  YY YY  0d 0a指令,其中EB 00 55是数据头,XX 是被减数,可以是1个字节,也可以是2个字节。YY是减数,可以是1个字节,也可以是2个字节。0d 0a是固定的结束标志。
例如:
(a)8259 – 5267 = 2992
上位机发送数据:eb 00 55 82 59 0d 0a  52 67 0d 0a
单片机返回:29 92

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

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


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

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

  11. #define const_rc_size  30  //接收串口中断数据的缓冲区数组大小

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

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

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

  18. void initial_myself(void);   
  19. void initial_peripheral(void);
  20. void delay_long(unsigned int uiDelaylong);
  21. void delay_short(unsigned int uiDelayShort);


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


  25. void eusart_send(unsigned char ucSendData);

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

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

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

  33. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  34. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  35. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  36. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  37. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


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

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

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


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

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

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

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

  51. void main()
  52.   {
  53.    initial_myself();  
  54.    delay_long(100);   
  55.    initial_peripheral();
  56.    while(1)  
  57.    {
  58.        usart_service();  //串口服务程序
  59.    }

  60. }

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

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


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

  78.    for(i=0;i<ucBCD4_cnt;i++)
  79.    {
  80.       ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
  81.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;   
  82.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;  
  83.    }

  84. }


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

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

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

  104.    for(i=0;i<ucBCD4_cnt;i++)
  105.    {
  106.       ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2];    //把非组合BCD码第8位分解出来
  107.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
  108.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2];    //把非组合BCD码第7位分解出来
  109.    }
  110.   
  111. }

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

  117. void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
  118. {
  119.   uchar i;

  120.   for(i=0;i<ucARRAY_MAX;i++)
  121.   {
  122.      destData[i]=0;
  123.   }

  124. }


  125. /* 注释五:
  126. *函数介绍:获取数组的有效长度
  127. *输入参数:*destData--被获取的数组。
  128. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  129. *返回值  :返回数组的有效长度。比如58786这个数据的有效长度是5
  130. *电子开发者作者:吴坚鸿
  131. */
  132. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
  133. {
  134.   uchar i;
  135.   uchar DataLength=ucARRAY_MAX;
  136.   for(i=0;i<ucARRAY_MAX;i++)
  137.   {
  138.       if(0!=destData[ucARRAY_MAX-1-i])
  139.           {
  140.              break;
  141.           }
  142.           else
  143.           {
  144.              DataLength--;
  145.           }

  146.   }

  147.   return DataLength;

  148. }



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

  162. destCnt=GetDataLength(destData,BCD8_MAX);
  163. sourceCnt=GetDataLength(sourceData,BCD8_MAX);

  164. if(destCnt>sourceCnt)  //大于
  165. {
  166.   cmpResult=11;
  167. }
  168. else if(destCnt<sourceCnt) //小于
  169. {
  170.   cmpResult=9;
  171. }
  172. else if((destCnt==0)&&(sourceCnt==0))  //如果都是等于0则等于
  173. {
  174.   cmpResult=10;
  175. }
  176. else  //否则就要继续判断
  177. {
  178.   for(i=0;i<destCnt;i++)
  179.   {
  180.      if(destData[destCnt-1-i]>sourceData[destCnt-1-i])   //从最高位开始判断,如果最高位大于则大于
  181.          {
  182.            cmpResult=11;
  183.            break;
  184.          }
  185.      else if(destData[destCnt-1-i]<sourceData[destCnt-1-i])  //从最高位开始判断,如果最高位小于则小于
  186.          {
  187.            cmpResult=9;
  188.            break;
  189.          }

  190.      //否则继续判断下一位
  191.   }
  192. }


  193. return cmpResult;
  194. }


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

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

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

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

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


  220. for(i=0;i<maxCnt;i++)
  221. {

  222.    resultTemp=sourceData[i]+carryData; //按位相加
  223.    if(resultTemp>destData[i])
  224.    {
  225.       resultData[i]=destData[i]+10-sourceData[i]-carryData;    //借位
  226.           carryData=1;
  227.    }
  228.    else
  229.    {
  230.       resultData[i]=destData[i]-sourceData[i]-carryData;    //不用借位
  231.           carryData=0;
  232.    }

  233. }


  234. return subResult;
  235. }



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

  238.      unsigned char i=0;   
  239.      unsigned char k=0;   
  240.          unsigned char ucGetDataStep=0;

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

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

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

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

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

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

  270.                            }
  271.                            else
  272.                            {
  273.                                 i++;
  274.                                 ucDataBCD4_cnt_1++;  //统计第1个有效数据的长度
  275.                            }
  276.                                                                                                                         
  277.                        }
  278.                        else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是加数
  279.                        {
  280.                            if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  281.                            {
  282.                                 for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
  283.                                 {
  284.                                     ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  285.                                 }
  286.                                                                                                                                                         
  287.                                 break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

  288.                             }
  289.                             else
  290.                             {
  291.                                 i++;
  292.                                 ucDataBCD4_cnt_2++;  //统计第2个有效数据的长度
  293.                             }
  294.                        }
  295.                     }


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


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

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

  320.            ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
  321.            ucRcregBuf[1]=0;
  322.            ucRcregBuf[2]=0;         
  323.                   
  324.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  325.   
  326.      }
  327.                         
  328. }

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

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

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

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

  337. }



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


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



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


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

  353.    if(RI==1)  
  354.    {
  355.         RI = 0;

  356.             ++uiRcregTotal;
  357.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  358.         {
  359.            uiRcregTotal=const_rc_size;
  360.         }
  361.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  362.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  363.    
  364.    }
  365.    else  //发送中断,及时把发送中断标志位清零
  366.    {
  367.         TI = 0;
  368.    }
  369.                                                          
  370. }                                


  371. void delay_long(unsigned int uiDelayLong)
  372. {
  373.    unsigned int i;
  374.    unsigned int j;
  375.    for(i=0;i<uiDelayLong;i++)
  376.    {
  377.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  378.           {
  379.              ; //一个分号相当于执行一条空语句
  380.           }
  381.    }
  382. }

  383. void delay_short(unsigned int uiDelayShort)
  384. {
  385.    unsigned int i;  
  386.    for(i=0;i<uiDelayShort;i++)
  387.    {
  388.      ;   //一个分号相当于执行一条空语句
  389.    }
  390. }


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

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

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


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

  403. }

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

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

  410. }
复制代码

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

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

此帖出自51单片机论坛
 
 
 

回复

1

帖子

0

TA的资源

一粒金砂(初级)

113
 
顶楼主 你这真是做好事啊
此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

114
 
第六十四节:大数据的乘法运算。

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

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

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

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被乘数和乘数,单片机把组合BCD码的运算结果返回到上位机。被乘数与乘数的最大范围都是从0到99,如果运算的乘积超过允许保存的最大位数范围则返回EE EE EE报错。
往单片机发送的数据格式:EB 00 55 XX  0d  0a  YY  0d  0a指令,其中EB 00 55是数据头,XX 是被乘数,是1个字节的组合BCD码。YY是乘数,可以是1个字节的组合BCD码。0d 0a是固定的结束标志。
例如:
(a)83 x 98 = 8134
上位机发送数据:eb 00 55 83 0d 0a 98 0d 0a
单片机返回:81 34

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


  2. /* 注释一:
  3. * 本系统中的乘法运算,规定两个乘数的最大范围是0至99.
  4. * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
  5. * 不能超过256个,如果超过了这个极限,编译器就会报错。由于51单片机RAM资源有限,
  6. * 因此规定乘数的最大范围不能超过99,如果这个算法移植到stm32或者PIC等RAM比较大
  7. * 的单片机上,那么就可以把这个运算位数设置得更加大一点。调整下面 BCD4_MAX的大小,
  8. * 可以调整运算的数据范围。
  9. */

  10. #define  BCD4_MAX     3  //为了让乘法的结果不超过范围,因此把组合BCD码最大字节数从上一节的2改成3,一个字节包含2位,因此可以保存6位有效数
  11. #define  BCD8_MAX    (BCD4_MAX*2)  //本系统中,规定的非组合BCD码能保存的最大字节数,一个字节包含1位,因此能保存6位有效运算数

  12. #define const_rc_size  30  //接收串口中断数据的缓冲区数组大小

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

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

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

  19. void initial_myself(void);   
  20. void initial_peripheral(void);
  21. void delay_long(unsigned int uiDelaylong);
  22. void delay_short(unsigned int uiDelayShort);


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


  26. void eusart_send(unsigned char ucSendData);

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

  29. void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
  30. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
  31. uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData);  //两个数相加
  32. void EnlargeData(uchar *destData,uchar enlarge_cnt); //数组向大索引值移位,移一位相当于放大10倍
  33. uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData); //两个数相乘

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

  35. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  36. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  37. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  38. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  39. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


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

  42. unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式  这里是指乘数
  43. unsigned char ucDataBCD4_cnt_2=0;  //接收到的第2个数组合BCD码数组的有效数据长度

  44. unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式  这里是指积
  45. unsigned char ucDataBCD4_cnt_3=0;  //接收到的第3个数组合BCD码数组的有效数据长度


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

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

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

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

  53. void main()
  54.   {
  55.    initial_myself();  
  56.    delay_long(100);   
  57.    initial_peripheral();
  58.    while(1)  
  59.    {
  60.        usart_service();  //串口服务程序
  61.    }

  62. }

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

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


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

  80.    for(i=0;i<ucBCD4_cnt;i++)
  81.    {
  82.       ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
  83.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;   
  84.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;  
  85.    }

  86. }


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

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

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

  106.    for(i=0;i<ucBCD4_cnt;i++)
  107.    {
  108.       ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2];    //把非组合BCD码第8位分解出来
  109.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
  110.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2];    //把非组合BCD码第7位分解出来
  111.    }
  112.   
  113. }

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

  119. void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
  120. {
  121.   uchar i;

  122.   for(i=0;i<ucARRAY_MAX;i++)
  123.   {
  124.      destData[i]=0;
  125.   }

  126. }


  127. /* 注释五:
  128. *函数介绍:获取数组的有效长度
  129. *输入参数:*destData--被获取的数组。
  130. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  131. *返回值  :返回数组的有效长度。比如58786这个数据的有效长度是5
  132. *电子开发者作者:吴坚鸿
  133. */
  134. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
  135. {
  136.   uchar i;
  137.   uchar DataLength=ucARRAY_MAX;
  138.   for(i=0;i<ucARRAY_MAX;i++)
  139.   {
  140.       if(0!=destData[ucARRAY_MAX-1-i])
  141.           {
  142.              break;
  143.           }
  144.           else
  145.           {
  146.              DataLength--;
  147.           }

  148.   }

  149.   return DataLength;

  150. }



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

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

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

  172. if(destCnt>=sourceCnt)  //找出两个运算数据中最大的有效位数
  173. {
  174.    maxCnt=destCnt;
  175. }
  176. else
  177. {
  178.    maxCnt=sourceCnt;
  179. }

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

  186. resultData[i]=carryData;

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

  189.   ClearAllData(BCD8_MAX,resultData);

  190.   addResult=10;  //报错
  191. }


  192. return addResult;
  193. }



  194. /* 注释七:
  195. *函数介绍:数组向大索引值移位,移一位相当于放大10倍
  196. *输入参数:*destData--被移位的数组。
  197. *输入参数:enlarge_cnt--被移位的个数。
  198. */
  199. void EnlargeData(uchar *destData,uchar enlarge_cnt)
  200. {
  201.   uchar i;

  202.   if(enlarge_cnt!=0)
  203.   {
  204.     for(i=0;i<(BCD8_MAX-enlarge_cnt);i++)
  205.         {
  206.        destData[BCD8_MAX-1-i]=destData[BCD8_MAX-1-enlarge_cnt-i];
  207.         }

  208.     for(i=0;i<enlarge_cnt;i++) //最低位被移空的补上0
  209.         {
  210.       destData[i]=0;
  211.         }
  212.   }

  213. }




  214. /* 注释八:
  215. *函数介绍:两个数相乘
  216. *输入参数:
  217. *(1)*destData--被乘数的数组。
  218. *(2)*sourceData--乘数的数组。
  219. *(3)*resultData--积的数组。
  220. *返回值  :10代表计算结果超出范围出错,11代表正常。
  221. */
  222. uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData)
  223. {
  224. uchar multResult=11; //开始默认正常
  225. uchar destCnt=0;
  226. uchar sourceCnt=0;
  227. uchar i;
  228. uchar j;
  229. uchar carryData=0;  //进位

  230. uchar resultTemp=0; //存放临时运算结果的中间变量

  231. uchar nc_add_result;  //接收相加的运算是否超出范围,这里不用判断,因为不会溢出

  232. uchar multArrayTemp[BCD8_MAX]; //存放临时运算结果的数组中间变量



  233. destCnt=GetDataLength(destData,BCD8_MAX);      //获取被乘数的长度
  234. sourceCnt=GetDataLength(sourceData,BCD8_MAX);   //获取乘数的长度

  235. ClearAllData(BCD8_MAX,resultData);   //清零存储的结果
  236. if((0==destCnt)||(0==sourceCnt)) //被乘数或者乘数为0,则结果为0
  237. {
  238.    return multResult;
  239. }


  240. if((destCnt+sourceCnt+2)>BCD8_MAX)
  241. {
  242.    multResult=10; //运算结果有可能超范围报错
  243.    return multResult;
  244. }

  245. for(i=0;i<sourceCnt;i++)  //乘数
  246. {
  247.    carryData=0; //清零进位
  248.    ClearAllData(BCD8_MAX,multArrayTemp); //清零一位乘数相乘的结果中间变量数组
  249.    for(j=0;j<destCnt;j++) //被乘数
  250.    {
  251.       resultTemp=destData[j]*sourceData[i]+carryData;  //乘数的一位依次与被乘数各位相乘,并且加进位
  252.       multArrayTemp[j]=resultTemp%10;  //存储一位乘数相乘的结果
  253.           carryData=resultTemp/10; //保存进位
  254.    }
  255.    multArrayTemp[j]=carryData; //存储最后的进位
  256.    EnlargeData(multArrayTemp,i); //移位。移一次相当于放大10倍。
  257.    nc_add_result=AddData(resultData,multArrayTemp,resultData); //把一位乘数相乘的结果存储进总结果

  258. }


  259. return multResult;
  260. }



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

  263.      unsigned char i=0;   
  264.      unsigned char k=0;   
  265.          unsigned char ucGetDataStep=0;

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

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

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

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

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

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

  295.                            }
  296.                            else
  297.                            {
  298.                                 i++;
  299.                                 ucDataBCD4_cnt_1++;  //统计第1个有效数据的长度
  300.                            }
  301.                                                                                                                         
  302.                        }
  303.                        else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是乘数
  304.                        {
  305.                            if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  306.                            {
  307.                                 for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
  308.                                 {
  309.                                     ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  310.                                 }
  311.                                                                                                                                                         
  312.                                 break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

  313.                             }
  314.                             else
  315.                             {
  316.                                 i++;
  317.                                 ucDataBCD4_cnt_2++;  //统计第2个有效数据的长度
  318.                             }
  319.                        }
  320.                     }


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


  324.                     ClearAllData(BCD8_MAX,ucDataBCD8_3);  //清零第3个参与运算的数据,用来接收运行的结果
  325.                     ucResultFlag=MultData(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相乘运算,结果放在ucDataBCD8_3数组里
  326.                     if(ucResultFlag==11) //表示运算结果没有超范围
  327.                     {
  328.                        ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX);  //获取运算结果的有效字节数
  329.                        if(ucDataBCD8_cnt_3==0) //如果1个有效位数都没有,表示数组所有的数据都是0,这个时候的有效位数应该人为的默认是1,表示一个0
  330.                                            {
  331.                                                ucDataBCD8_cnt_3=1;
  332.                                            }

  333.                        BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
  334.                        for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
  335.                        {
  336.                           eusart_send(ucDataBCD4_3[ucDataBCD4_cnt_3-1-k]); //往上位机发送一个字节的函数
  337.                        }
  338.                     }
  339.                     else //运算结果超范围,返回EE EE EE
  340.                     {
  341.                        eusart_send(0xee); //往上位机发送一个字节的函数
  342.                        eusart_send(0xee); //往上位机发送一个字节的函数
  343.                        eusart_send(0xee); //往上位机发送一个字节的函数
  344.                     }

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

  349.            ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
  350.            ucRcregBuf[1]=0;
  351.            ucRcregBuf[2]=0;         
  352.                   
  353.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  354.   
  355.      }
  356.                         
  357. }

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

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

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

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

  366. }



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


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



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


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

  382.    if(RI==1)  
  383.    {
  384.         RI = 0;

  385.             ++uiRcregTotal;
  386.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  387.         {
  388.            uiRcregTotal=const_rc_size;
  389.         }
  390.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  391.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  392.    
  393.    }
  394.    else  //发送中断,及时把发送中断标志位清零
  395.    {
  396.         TI = 0;
  397.    }
  398.                                                          
  399. }                                


  400. void delay_long(unsigned int uiDelayLong)
  401. {
  402.    unsigned int i;
  403.    unsigned int j;
  404.    for(i=0;i<uiDelayLong;i++)
  405.    {
  406.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  407.           {
  408.              ; //一个分号相当于执行一条空语句
  409.           }
  410.    }
  411. }

  412. void delay_short(unsigned int uiDelayShort)
  413. {
  414.    unsigned int i;  
  415.    for(i=0;i<uiDelayShort;i++)
  416.    {
  417.      ;   //一个分号相当于执行一条空语句
  418.    }
  419. }


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

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

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


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

  432. }

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

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

  439. }
复制代码

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

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

此帖出自51单片机论坛
 
 
 

回复

5

帖子

0

TA的资源

一粒金砂(初级)

115
 
受教了。谢谢你的分享心得!
此帖出自51单片机论坛
 
 
 

回复

552

帖子

3

TA的资源

纯净的硅(初级)

116
 
标记一下
此帖出自51单片机论坛
 
 
 

回复

128

帖子

0

TA的资源

一粒金砂(中级)

117
 
拿下,虽然很久不动这东西了
此帖出自51单片机论坛
 
个人签名where there is wade,there is a way...
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

118
 
第六十五节:大数据的除法运算。

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

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

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

(2)实现功能:
波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机发送组合BCD码的被除数和除数,单片机把组合BCD码的运算结果返回到上位机。被除数与除数的最大范围都是从0到9999,如果运算的商超过允许保存的最大位数范围或者除数为0,则返回EE EE EE报错。
往单片机发送的数据格式:EB 00 55 XX XX 0d  0a  YY YY 0d  0a指令,其中EB 00 55是数据头,XX XX是被除数,是1到2个字节的组合BCD码。YY YY是除数,是1到2个字节的组合BCD码。0d 0a是固定的结束标志。
例如:
(a)9816 ÷ 8= 1227
上位机发送数据:eb 00 55 98 16 0d 0a 08 0d 0a
单片机返回:12 27
(b)9816 ÷ 0= 出错了,除数不能为0。
上位机发送数据:eb 00 55 98 16 0d 0a 00 0d 0a
单片机返回:EE EE EE   

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


  2. /* 注释一:
  3. * 本系统中的除法运算,规定被除数和除数的最大范围是0至9999.
  4. * 由于STC89C52单片机的RAM只有256个,也就是说系统的变量数最大
  5. * 不能超过256个,如果超过了这个极限,编译器就会报错。由于51单片机RAM资源有限,
  6. * 因此规定除数的最大范围不能超过9999,如果这个算法移植到stm32或者PIC等RAM比较大
  7. * 的单片机上,那么就可以把这个运算位数设置得更加大一点。调整下面 BCD4_MAX的大小,
  8. * 可以调整运算的数据范围。
  9. */

  10. #define  BCD4_MAX     3  //调整BCD4_MAX的大小,可以调整运算的数据范围。
  11. #define  BCD8_MAX    (BCD4_MAX*2)  //本系统中,规定的非组合BCD码能保存的最大字节数,一个字节包含1位有效运算数

  12. #define const_rc_size  30  //接收串口中断数据的缓冲区数组大小

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

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

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

  19. void initial_myself(void);   
  20. void initial_peripheral(void);
  21. void delay_long(unsigned int uiDelaylong);
  22. void delay_short(unsigned int uiDelayShort);


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


  26. void eusart_send(unsigned char ucSendData);

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

  29. void ClearAllData(uchar ucARRAY_MAX,uchar *destData);
  30. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX);
  31. uchar AddData(const uchar *destData,const uchar *sourceData,uchar *resultData);  //两个数相加
  32. uchar CmpData(const uchar *destData,const uchar *sourceData); //比较两个数的大小
  33. uchar SubData(const uchar *destData,const uchar *sourceData,uchar *resultData);//两个数相减
  34. void EnlargeData(uchar *destData,uchar enlarge_cnt); //数组向大索引值移位,移一位相当于放大10倍
  35. uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData); //两个数相乘

  36. uchar DivLessTenData(const uchar *destData,const uchar *sourceData,uchar *resultData,uchar *remData);//局部两个数相除,商不超过10。当商为0时,余数等于被除数
  37. uchar Div(const uchar *destData,const uchar *sourceData,uchar *resultData);//两个数相除

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

  39. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  40. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  41. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  42. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  43. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量


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

  46. unsigned char ucDataBCD4_2[BCD4_MAX]; //接收到的第2个数组合BCD码数组形式  这里是指乘数
  47. unsigned char ucDataBCD4_cnt_2=0;  //接收到的第2个数组合BCD码数组的有效数据长度

  48. unsigned char ucDataBCD4_3[BCD4_MAX]; //接收到的第3个数组合BCD码数组形式  这里是指积
  49. unsigned char ucDataBCD4_cnt_3=0;  //接收到的第3个数组合BCD码数组的有效数据长度


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

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

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

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

  57. void main()
  58.   {
  59.    initial_myself();  
  60.    delay_long(100);   
  61.    initial_peripheral();
  62.    while(1)  
  63.    {
  64.        usart_service();  //串口服务程序
  65.    }

  66. }

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

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


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

  84.    for(i=0;i<ucBCD4_cnt;i++)
  85.    {
  86.       ucTmep=p_ucBCD_bit4[ucBCD4_cnt-1-i];
  87.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-1]=ucTmep>>4;   
  88.       p_ucBCD_bit8[ucBCD4_cnt*2-i*2-2]=ucTmep&0x0f;  
  89.    }

  90. }


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

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

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

  110.    for(i=0;i<ucBCD4_cnt;i++)
  111.    {
  112.       ucTmep=p_ucBCD_bit8[ucBCD4_cnt*2-1-i*2];    //把非组合BCD码第8位分解出来
  113.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=ucTmep<<4;
  114.       p_ucBCD_bit4[ucBCD4_cnt-1-i]=p_ucBCD_bit4[ucBCD4_cnt-1-i]+p_ucBCD_bit8[ucBCD4_cnt*2-2-i*2];    //把非组合BCD码第7位分解出来
  115.    }
  116.   
  117. }

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

  123. void ClearAllData(uchar ucARRAY_MAX,uchar *destData)
  124. {
  125.   uchar i;

  126.   for(i=0;i<ucARRAY_MAX;i++)
  127.   {
  128.      destData[i]=0;
  129.   }

  130. }


  131. /* 注释五:
  132. *函数介绍:获取数组的有效长度
  133. *输入参数:*destData--被获取的数组。
  134. *输入参数:ucARRAY_MAX代表数组定义的最大长度
  135. *返回值  :返回数组的有效长度。比如58786这个数据的有效长度是5
  136. *电子开发者作者:吴坚鸿
  137. */
  138. uchar GetDataLength(const uchar *destData,uchar ucARRAY_MAX)
  139. {
  140.   uchar i;
  141.   uchar DataLength=ucARRAY_MAX;
  142.   for(i=0;i<ucARRAY_MAX;i++)
  143.   {
  144.       if(0!=destData[ucARRAY_MAX-1-i])
  145.           {
  146.              break;
  147.           }
  148.           else
  149.           {
  150.              DataLength--;
  151.           }

  152.   }

  153.   return DataLength;

  154. }


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

  168. destCnt=GetDataLength(destData,BCD8_MAX);
  169. sourceCnt=GetDataLength(sourceData,BCD8_MAX);

  170. if(destCnt>sourceCnt)  //大于
  171. {
  172.   cmpResult=11;
  173. }
  174. else if(destCnt<sourceCnt) //小于
  175. {
  176.   cmpResult=9;
  177. }
  178. else if((destCnt==0)&&(sourceCnt==0))  //如果都是等于0则等于
  179. {
  180.   cmpResult=10;
  181. }
  182. else  //否则就要继续判断
  183. {
  184.   for(i=0;i<destCnt;i++)
  185.   {
  186.      if(destData[destCnt-1-i]>sourceData[destCnt-1-i])   //从最高位开始判断,如果最高位大于则大于
  187.          {
  188.            cmpResult=11;
  189.            break;
  190.          }
  191.      else if(destData[destCnt-1-i]<sourceData[destCnt-1-i])  //从最高位开始判断,如果最高位小于则小于
  192.          {
  193.            cmpResult=9;
  194.            break;
  195.          }

  196.      //否则继续判断下一位
  197.   }
  198. }


  199. return cmpResult;
  200. }


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

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

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

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

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


  226. for(i=0;i<maxCnt;i++)
  227. {

  228.    resultTemp=sourceData[i]+carryData; //按位相加
  229.    if(resultTemp>destData[i])
  230.    {
  231.       resultData[i]=destData[i]+10-sourceData[i]-carryData;    //借位
  232.           carryData=1;
  233.    }
  234.    else
  235.    {
  236.       resultData[i]=destData[i]-sourceData[i]-carryData;    //不用借位
  237.           carryData=0;
  238.    }

  239. }


  240. return subResult;
  241. }


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

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

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

  263. if(destCnt>=sourceCnt)  //找出两个运算数据中最大的有效位数
  264. {
  265.    maxCnt=destCnt;
  266. }
  267. else
  268. {
  269.    maxCnt=sourceCnt;
  270. }

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

  277. resultData[i]=carryData;

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

  280.   ClearAllData(BCD8_MAX,resultData);

  281.   addResult=10;  //报错
  282. }


  283. return addResult;
  284. }



  285. /* 注释九:
  286. *函数介绍:数组向大索引值移位,移一位相当于放大10倍
  287. *输入参数:*destData--被移位的数组。
  288. *输入参数:enlarge_cnt--被移位的个数。
  289. */
  290. void EnlargeData(uchar *destData,uchar enlarge_cnt)
  291. {
  292.   uchar i;

  293.   if(enlarge_cnt!=0)
  294.   {
  295.     for(i=0;i<(BCD8_MAX-enlarge_cnt);i++)
  296.         {
  297.        destData[BCD8_MAX-1-i]=destData[BCD8_MAX-1-enlarge_cnt-i];
  298.         }

  299.     for(i=0;i<enlarge_cnt;i++) //最低位被移空的补上0
  300.         {
  301.       destData[i]=0;
  302.         }
  303.   }

  304. }




  305. /* 注释十:
  306. *函数介绍:两个数相乘
  307. *输入参数:
  308. *(1)*destData--被乘数的数组。
  309. *(2)*sourceData--乘数的数组。
  310. *(3)*resultData--积的数组。
  311. *返回值  :10代表计算结果超出范围出错,11代表正常。
  312. */
  313. uchar MultData(const uchar *destData,const uchar *sourceData,uchar *resultData)
  314. {
  315. uchar multResult=11; //开始默认正常
  316. uchar destCnt=0;
  317. uchar sourceCnt=0;
  318. uchar i;
  319. uchar j;
  320. uchar carryData=0;  //进位

  321. uchar resultTemp=0; //存放临时运算结果的中间变量

  322. uchar nc_add_result;  //接收相加的运算是否超出范围,这里不用判断,因为不会溢出

  323. uchar multArrayTemp[BCD8_MAX]; //存放临时运算结果的数组中间变量



  324. destCnt=GetDataLength(destData,BCD8_MAX);      //获取被乘数的长度
  325. sourceCnt=GetDataLength(sourceData,BCD8_MAX);   //获取乘数的长度

  326. ClearAllData(BCD8_MAX,resultData);   //清零存储的结果
  327. if((0==destCnt)||(0==sourceCnt)) //被乘数或者乘数为0,则结果为0
  328. {
  329.    return multResult;
  330. }


  331. if((destCnt+sourceCnt+2)>BCD8_MAX)
  332. {
  333.    multResult=10; //运算结果有可能超范围报错
  334.    return multResult;
  335. }

  336. for(i=0;i<sourceCnt;i++)  //乘数
  337. {
  338.    carryData=0; //清零进位
  339.    ClearAllData(BCD8_MAX,multArrayTemp); //清零一位乘数相乘的结果中间变量数组
  340.    for(j=0;j<destCnt;j++) //被乘数
  341.    {
  342.       resultTemp=destData[j]*sourceData[i]+carryData;  //乘数的一位依次与被乘数各位相乘,并且加进位
  343.       multArrayTemp[j]=resultTemp%10;  //存储一位乘数相乘的结果
  344.           carryData=resultTemp/10; //保存进位
  345.    }
  346.    multArrayTemp[j]=carryData; //存储最后的进位
  347.    EnlargeData(multArrayTemp,i); //移位。移一次相当于放大10倍。
  348.    nc_add_result=AddData(resultData,multArrayTemp,resultData); //把一位乘数相乘的结果存储进总结果

  349. }


  350. return multResult;
  351. }



  352. /* 注释十一:
  353. *函数介绍:局部两个数相除,商不超过10。当商为0时,余数是被除数
  354. *原理精髓:根据手工除法的原理,我们都是从高位开始借位相除,此时是局部相除,因此商都不超过10,剩下的余数继续借位
  355. *依次除下去。这个程序的除法原理是挨个猜值,反正商是从0,1,2.。。。9这10个数中的其中一个,为了快速找到我们想要的那个商,我是
  356. *利用中间法则进行寻找,先猜是5,然后判断一下是大了还是小了,如果是大了,就猜是3,如果小了就猜是7,最后肯定会找到商。
  357. *输入参数:
  358. *(1)*destData--被除数的数组。
  359. *(2)*sourceData--除数的数组。
  360. *(3)*resultData--商的数,不是数组,传址进去,是0,1,2到9中的某个数
  361. *(4)*remData--余数的数组。
  362. *返回值  :10代表计算结果超出范围出错,11代表正常。
  363. */
  364. uchar DivLessTenData(const uchar *destData,const uchar *sourceData,uchar *resultData,uchar *remData)
  365. {
  366. uchar DivLessTenResult=11; //开始默认正常
  367. uchar destCnt=0;
  368. uchar sourceCnt=0;
  369. uchar i;



  370. uchar resultRunStep=5;  
  371. uchar cmpError=10;
  372. uchar DivLessTenArrayTemp[BCD8_MAX]; //存放临时运算结果的数组中间变量
  373. uchar DivLessTenArrayResult[BCD8_MAX]; //存放临时运算结果的数组中间变量的结果
  374. uchar DivLessTenArrayBackup[BCD8_MAX]; //存放临时运算结果的数组中间变量的备份
  375. uchar while_flag=0;  //结束猜算的中间变量
  376. uchar multError=11;
  377. uchar subError=11;

  378. destCnt=GetDataLength(destData,BCD8_MAX);     //获取被除数的数据有效长度
  379. sourceCnt=GetDataLength(sourceData,BCD8_MAX); //获取除数的数据有效长度

  380. cmpError=CmpData(destData,sourceData); //比较被除数和除数的大小

  381. ClearAllData(BCD8_MAX,remData); //清空余数,余数为0

  382. if(cmpError==9) //被除数比除数小
  383. {
  384.    *resultData=0;   //商肯定为0
  385.    for(i=0;i<destCnt;i++)
  386.    {
  387.      remData[i]=destData[i]; //余数等于被除数
  388.    }
  389.    return DivLessTenResult;
  390. }
  391. else if(cmpError==10) //被除数与除数相等
  392. {

  393.    *resultData=1; //商等于1余数为0
  394.    return DivLessTenResult;
  395. }
  396. else  //开始猜值
  397. {
  398.    resultRunStep=5;  //先猜是5  ,从这里开始直接看以下 case 5 的详细讲解,其他case原理相同
  399.    while_flag=0;
  400.    while(1)
  401.    {
  402.       switch(resultRunStep)
  403.           {

  404.                  case 1:
  405.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp);
  406.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult);
  407.              DivLessTenArrayTemp[0]=resultRunStep;
  408.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);

  409.              subError=SubData(destData,DivLessTenArrayResult,remData);//求余数
  410.              *resultData=1; //商等于1
  411.                      while_flag=1; //退出循环
  412.                          break;
  413.                  case 2:
  414.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp);
  415.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult);
  416.              DivLessTenArrayTemp[0]=resultRunStep;
  417.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);
  418.              cmpError=CmpData(DivLessTenArrayResult,destData);
  419.                          if(cmpError==10) //等于
  420.                          {
  421.                  *resultData=2; //商等于2余数为0
  422.                                  while_flag=1; //退出循环
  423.                          }
  424.                          else if(cmpError==11) //大于
  425.                          {
  426.                  resultRunStep=1;
  427.                          }
  428.                          else             //小于
  429.                          {
  430.                  subError=SubData(destData,DivLessTenArrayResult,remData);//求余数
  431.                  *resultData=2; //商等于2
  432.                                  while_flag=1; //退出循环
  433.                          }




  434.                          break;
  435.                  case 3:
  436.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp);
  437.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult);
  438.              DivLessTenArrayTemp[0]=resultRunStep;
  439.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);
  440.              cmpError=CmpData(DivLessTenArrayResult,destData);
  441.                          if(cmpError==10) //等于
  442.                          {
  443.                  *resultData=3; //商等于3余数为0
  444.                                  while_flag=1; //退出循环
  445.                          }
  446.                          else if(cmpError==11) //大于
  447.                          {
  448.                 resultRunStep=2;
  449.                          }
  450.                          else             //小于
  451.                          {
  452.                 resultRunStep=4;

  453.                             ClearAllData(BCD8_MAX,DivLessTenArrayBackup);
  454.                             for(i=0;i<BCD8_MAX;i++) //备份
  455.                                 {
  456.                               DivLessTenArrayBackup[i]=DivLessTenArrayResult[i];
  457.                                 }
  458.                          }



  459.                          break;
  460.                  case 4:
  461.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp);
  462.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult);
  463.              DivLessTenArrayTemp[0]=resultRunStep;
  464.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);
  465.              cmpError=CmpData(DivLessTenArrayResult,destData);
  466.                          if(cmpError==10) //等于
  467.                          {
  468.                  *resultData=4; //商等于4余数为0
  469.                                  while_flag=1; //退出循环
  470.                          }
  471.                          else if(cmpError==11) //大于
  472.                          {
  473.                  subError=SubData(destData,DivLessTenArrayBackup,remData);//求余数
  474.                  *resultData=3; //商等于3
  475.                                  while_flag=1; //退出循环
  476.                          }
  477.                          else             //小于
  478.                          {
  479.                  subError=SubData(destData,DivLessTenArrayResult,remData);//求余数
  480.                  *resultData=4; //商等于4
  481.                                  while_flag=1; //退出循环
  482.                          }



  483.                          break;
  484.                  case 5:    //重点讲解一下case 5,其它case 原理相同,不多讲
  485.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp); //清空运算中需要用到的中间数组变量
  486.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult); //清空运算中需要用到的中间数组变量
  487.              DivLessTenArrayTemp[0]=resultRunStep;  //把猜的变量形式的商传递给数组形式的变量
  488.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);  //猜的商跟除数像乘,看看结果跟被除数谁大。
  489.              cmpError=CmpData(DivLessTenArrayResult,destData); //猜的商跟除数像乘,看看结果跟被除数谁大。
  490.                          if(cmpError==10) //等于 恭喜猜中是5
  491.                          {
  492.                  *resultData=5; //商等于5余数为0
  493.                                  while_flag=1; //退出循环
  494.                          }
  495.                          else if(cmpError==11) //大于   猜不中,大了,就继续往小的猜,看看有没有可能是3
  496.                          {
  497.                 resultRunStep=3;
  498.                          }
  499.                          else             //小于        猜不中,小了,就继续往大的猜,看看有没有可能是7
  500.                          {
  501.                 resultRunStep=7;

  502.                             ClearAllData(BCD8_MAX,DivLessTenArrayBackup);
  503.                             for(i=0;i<BCD8_MAX;i++) //备份
  504.                                 {
  505.                                DivLessTenArrayBackup[i]=DivLessTenArrayResult[i];
  506.                                 }
  507.                          }


  508.                          break;
  509.                  case 6:
  510.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp);
  511.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult);
  512.              DivLessTenArrayTemp[0]=resultRunStep;
  513.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);
  514.              cmpError=CmpData(DivLessTenArrayResult,destData);
  515.                          if(cmpError==10) //等于
  516.                          {
  517.                  *resultData=6; //商等于6余数为0
  518.                                  while_flag=1; //退出循环
  519.                          }
  520.                          else if(cmpError==11) //大于
  521.                          {
  522.                  subError=SubData(destData,DivLessTenArrayBackup,remData);//求余数
  523.                  *resultData=5; //商等于5
  524.                                  while_flag=1; //退出循环
  525.                          }
  526.                          else             //小于
  527.                          {
  528.                  subError=SubData(destData,DivLessTenArrayResult,remData);//求余数
  529.                  *resultData=6; //商等于6
  530.                                  while_flag=1; //退出循环
  531.                          }


  532.                          break;
  533.                  case 7:
  534.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp);
  535.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult);
  536.              DivLessTenArrayTemp[0]=resultRunStep;
  537.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);
  538.              cmpError=CmpData(DivLessTenArrayResult,destData);
  539.                          if(cmpError==10) //等于
  540.                          {
  541.                  *resultData=7; //商等于7余数为0
  542.                                  while_flag=1; //退出循环
  543.                          }
  544.                          else if(cmpError==11) //大于
  545.                          {
  546.                 resultRunStep=6;
  547.                          }
  548.                          else             //小于
  549.                          {
  550.                 resultRunStep=8;

  551.                               ClearAllData(BCD8_MAX,DivLessTenArrayBackup);
  552.                             for(i=0;i<BCD8_MAX;i++) //备份
  553.                                 {
  554.                                DivLessTenArrayBackup[i]=DivLessTenArrayResult[i];
  555.                                 }
  556.                          }



  557.                          break;
  558.                  case 8:
  559.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp);
  560.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult);
  561.              DivLessTenArrayTemp[0]=resultRunStep;
  562.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);
  563.              cmpError=CmpData(DivLessTenArrayResult,destData);
  564.                          if(cmpError==10) //等于
  565.                          {
  566.                  *resultData=8; //商等于8余数为0
  567.                                  while_flag=1; //退出循环
  568.                          }
  569.                          else if(cmpError==11) //大于
  570.                          {
  571.                  subError=SubData(destData,DivLessTenArrayBackup,remData);//求余数
  572.                  *resultData=7; //商等于7
  573.                                  while_flag=1; //退出循环
  574.                          }
  575.                          else             //小于
  576.                          {
  577.                 resultRunStep=9;

  578.                               ClearAllData(BCD8_MAX,DivLessTenArrayBackup);
  579.                             for(i=0;i<BCD8_MAX;i++) //备份
  580.                                 {
  581.                                DivLessTenArrayBackup[i]=DivLessTenArrayResult[i];
  582.                                 }
  583.                          }

  584.                          break;
  585.                  case 9:
  586.                          ClearAllData(BCD8_MAX,DivLessTenArrayTemp);
  587.                          ClearAllData(BCD8_MAX,DivLessTenArrayResult);
  588.              DivLessTenArrayTemp[0]=resultRunStep;
  589.                          multError=MultData(sourceData,DivLessTenArrayTemp,DivLessTenArrayResult);
  590.              cmpError=CmpData(DivLessTenArrayResult,destData);
  591.                          if(cmpError==10) //等于
  592.                          {
  593.                  *resultData=9; //商等于9余数为0
  594.                                  while_flag=1; //退出循环
  595.                          }
  596.                          else if(cmpError==11) //大于
  597.                          {
  598.                  subError=SubData(destData,DivLessTenArrayBackup,remData);//求余数
  599.                  *resultData=8; //商等于8
  600.                                  while_flag=1; //退出循环
  601.                          }
  602.                          else             //小于
  603.                          {
  604.                  subError=SubData(destData,DivLessTenArrayResult,remData);//求余数
  605.                  *resultData=9; //商等于9
  606.                                  while_flag=1; //退出循环
  607.                          }
  608.                          break;

  609.           }
  610.    
  611.       if(while_flag==1)  //猜中了就退出循环
  612.           {
  613.              break;
  614.           }
  615.    }

  616. }

  617. return DivLessTenResult;
  618. }



  619. /* 注释十二:
  620. *函数介绍:两个数相除
  621. *输入参数:
  622. *(1)*destData--被除数的数组。
  623. *(2)*sourceData--除数的数组。
  624. *(3)*resultData--商的数组
  625. *返回值  :10代表计算结果超出范围出错,11代表正常。
  626. */
  627. uchar Div(const uchar *destData,const uchar *sourceData,uchar *resultData)
  628. {
  629. uchar DivResult=11; //开始默认正常
  630. uchar destCnt=0;
  631. uchar sourceCnt=0;
  632. uchar i;
  633. uchar j;
  634. uchar resultTemp=0; //存放临时运算结果的中间变量
  635. uchar DivArrayTemp[BCD8_MAX]; //存放临时运算结果的数组中间变量
  636. uchar DivArrayResult[BCD8_MAX]; //存放临时运算结果的数组中间变量的结果
  637. uchar divError=11;



  638. destCnt=GetDataLength(destData,BCD8_MAX);   //获取被除数的数据有效长度
  639. sourceCnt=GetDataLength(sourceData,BCD8_MAX);   //获取除数的数据有效长度


  640. ClearAllData(BCD8_MAX,resultData);   //把结果清零

  641. if(sourceCnt==0)  //除数为0,报错
  642. {
  643.    DivResult=10; //报错
  644. }
  645. else
  646. {
  647.    ClearAllData(BCD8_MAX,DivArrayTemp); //清零局部被除数的数组
  648.    for(i=0;i<destCnt;i++)
  649.    {
  650.       DivArrayTemp[0]=destData[destCnt-1-i];  //从被除数的高位开始借位,放到局部被除数的个位
  651.       divError=DivLessTenData(DivArrayTemp,sourceData,&resultTemp,DivArrayResult);  //局部相除,求商resultTemp,此商resultTemp不超过10
  652.           if(divError==10)  //报错
  653.           {
  654.                 DivResult=10;
  655.             break;
  656.           }
  657.           else
  658.           {
  659.         resultData[destCnt-1-i]=resultTemp;  //保存商

  660.             for(j=0;j<(destCnt-1);j++)  //把余数移一次位,相当于放大十倍,重新放进DivArrayTemp数组对应的位
  661.                 {
  662.               DivArrayTemp[j+1]=DivArrayResult[j];
  663.                 }
  664.           }

  665.    }

  666. }



  667. return DivResult;
  668. }



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

  671.      unsigned char i=0;   
  672.      unsigned char k=0;   
  673.          unsigned char ucGetDataStep=0;

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

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

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

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

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

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

  703.                            }
  704.                            else
  705.                            {
  706.                                 i++;
  707.                                 ucDataBCD4_cnt_1++;  //统计第1个有效数据的长度
  708.                            }
  709.                                                                                                                         
  710.                        }
  711.                        else if(ucGetDataStep==1) //步骤1,相当于我平时用的case 1,获取第2个参与运行的数,在这里是除数
  712.                        {
  713.                            if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a) //结束标志
  714.                            {
  715.                                 for(k=0;k<ucDataBCD4_cnt_2;k++) //提取第2个参与运算的数组数据
  716.                                 {
  717.                                     ucDataBCD4_2[k]=ucRcregBuf[uiRcMoveIndex+3+i-1-k]; //注意,接收到的数组数据与实际存储的数组数据的下标方向是相反的
  718.                                 }
  719.                                                                                                                                                         
  720.                                 break; //截取数据完成。直接跳出截取数据的while(i<(BCD8_MAX+4))循环

  721.                             }
  722.                             else
  723.                             {
  724.                                 i++;
  725.                                 ucDataBCD4_cnt_2++;  //统计第2个有效数据的长度
  726.                             }
  727.                        }
  728.                     }


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


  732.                     ClearAllData(BCD8_MAX,ucDataBCD8_3);  //清零第3个参与运算的数据,用来接收运行的结果
  733.                     ucResultFlag=Div(ucDataBCD8_1,ucDataBCD8_2,ucDataBCD8_3); //相除运算,结果放在ucDataBCD8_3数组里
  734.                     if(ucResultFlag==11) //表示运算结果没有超范围
  735.                     {
  736.                        ucDataBCD8_cnt_3=GetDataLength(ucDataBCD8_3,BCD8_MAX);  //获取运算结果的有效字节数
  737.                        if(ucDataBCD8_cnt_3==0) //如果1个有效位数都没有,表示数组所有的数据都是0,这个时候的有效位数应该人为的默认是1位数据,表示一个0
  738.                        {
  739.                           ucDataBCD8_cnt_3=1;
  740.                        }

  741.                        BCD8_to_BCD4(ucDataBCD8_3,ucDataBCD8_cnt_3,ucDataBCD4_3,&ucDataBCD4_cnt_3); //把非组合BCD码转成组合BCD码。注意,&ucDataBCD4_cnt_3带地址符号&
  742.                        for(k=0;k<ucDataBCD4_cnt_3;k++) //返回运算结果到上位机上观察。看到的是组合BCD码形式。返回的时候注意数组下标的顺序要反过来发送,先发高位的下标数组
  743.                        {
  744.                           eusart_send(ucDataBCD4_3[ucDataBCD4_cnt_3-1-k]); //往上位机发送一个字节的函数
  745.                        }
  746.                     }
  747.                     else //运算结果超范围,返回EE EE EE
  748.                     {
  749.                        eusart_send(0xee); //往上位机发送一个字节的函数
  750.                        eusart_send(0xee); //往上位机发送一个字节的函数
  751.                        eusart_send(0xee); //往上位机发送一个字节的函数
  752.                     }

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

  757.            ucRcregBuf[0]=0; //把数据头清零,方便下次接收判断新数据
  758.            ucRcregBuf[1]=0;
  759.            ucRcregBuf[2]=0;         
  760.                   
  761.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  762.   
  763.      }
  764.                         
  765. }

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

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

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

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

  774. }



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


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



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


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

  790.    if(RI==1)  
  791.    {
  792.         RI = 0;

  793.             ++uiRcregTotal;
  794.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  795.         {
  796.            uiRcregTotal=const_rc_size;
  797.         }
  798.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  799.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  800.    
  801.    }
  802.    else  //发送中断,及时把发送中断标志位清零
  803.    {
  804.         TI = 0;
  805.    }
  806.                                                          
  807. }                                


  808. void delay_long(unsigned int uiDelayLong)
  809. {
  810.    unsigned int i;
  811.    unsigned int j;
  812.    for(i=0;i<uiDelayLong;i++)
  813.    {
  814.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  815.           {
  816.              ; //一个分号相当于执行一条空语句
  817.           }
  818.    }
  819. }

  820. void delay_short(unsigned int uiDelayShort)
  821. {
  822.    unsigned int i;  
  823.    for(i=0;i<uiDelayShort;i++)
  824.    {
  825.      ;   //一个分号相当于执行一条空语句
  826.    }
  827. }


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

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

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


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

  840. }

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

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

  847. }
复制代码

总结陈词:
前面四个章节讲完了四则运算的大数据算法,下一节讲单片机的外部中断功能。外部中断是单片机非常重要的内部资源,应用很广,它是单片机的高速开关感应器输入接口,它可以检测脉冲输入,可以接收红外遥控器的输入信号,可以检测高速运转的车轮或者电机圆周运动的反馈信号,可以检测输液器里瞬间即逝的水滴信号,可以接收模拟串口的数据信息,等等。单片机外部中断的有什么特点?欲知详情,请听下回分解----单片机外部中断的基础。

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

此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

119
 
第六十六节:单片机外部中断的基础。

开场白:
外部中断是单片机非常重要的内部资源,应用很广,它是单片机的高速开关感应器输入接口,它可以检测脉冲输入,可以接收红外遥控器的输入信号,可以检测高速运转的车轮或者电机圆周运动的反馈信号,可以检测输液器里瞬间即逝的水滴信号,可以接收模拟串口的数据信息,等等。
    这一节要教大家两个知识点:
   第一个:外部中断的初始化代码和中断函数的基本程序模板。
   第二个:当系统存在两种中断以上时,如何设置外部中断0为最高优先级,实现中断嵌套功能。

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

(1)硬件平台:
    基于朱兆祺51单片机学习板。用S1按键作为模拟外部中断0的下降沿脉冲输入。原来S1按键是直接连接到P0^0口的,因此必须通过跳线把P0^0口连接到单片机外部中断0专用IO口P3^2上,只需把P0^0和P3^2的两根黄颜色跳冒去掉,通过一根线把P0^0和P3^2相互连接起来即可。这时每按下一次S1按键,就会给P3^2口产生一个下降沿的脉冲,然后程序会自动跳到中断函数中执行一次。

(2)实现功能:
    用数码管低4位显示记录当前的下降沿脉冲数。用S1按键经过跳线后模拟外部中断0的下降沿输入,每按一次数码管就会显示往上累加的脉冲数。由于按键按下去的时候有抖动,也就按一次可能产生几个脉冲,所以按一次往往看到数据一次加了三四个,这种实验现象都是正常的。

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

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_key_time1  20    //按键去抖动延时的时间

  4. void initial_myself();   
  5. void initial_peripheral();
  6. void delay_short(unsigned int uiDelayShort);
  7. void delay_long(unsigned int uiDelaylong);
  8. //驱动数码管的74HC595
  9. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  10. void display_drive(); //显示数码管字模的驱动函数
  11. void display_service(); //显示的窗口菜单服务程序
  12. //驱动LED的74HC595
  13. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  14. void T0_time();  //定时中断函数
  15. void INT0_int();//外部0中断函数



  16. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  17. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口


  18. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  19. sbit dig_hc595_st_dr=P2^1;  
  20. sbit dig_hc595_ds_dr=P2^2;  


  21. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  22. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  23. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  24. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  25. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  26. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  27. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  28. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  29. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  30. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  31. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  32. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  33. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  34. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  35. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  36. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  37. unsigned char ucDigShowTemp=0; //临时中间变量
  38. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  39. unsigned char ucWd1Update=1; //窗口1更新显示标志

  40. unsigned char ucWd=1;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。本程序只有一个显示窗口
  41. unsigned int  uiPluseCnt=0;  //本程序中累加中断脉冲数的变量

  42. unsigned char ucTemp1=0;  //中间过渡变量
  43. unsigned char ucTemp2=0;  //中间过渡变量
  44. unsigned char ucTemp3=0;  //中间过渡变量
  45. unsigned char ucTemp4=0;  //中间过渡变量

  46. //根据原理图得出的共阴数码管字模表
  47. code unsigned char dig_table[]=
  48. {
  49. 0x3f,  //0       序号0
  50. 0x06,  //1       序号1
  51. 0x5b,  //2       序号2
  52. 0x4f,  //3       序号3
  53. 0x66,  //4       序号4
  54. 0x6d,  //5       序号5
  55. 0x7d,  //6       序号6
  56. 0x07,  //7       序号7
  57. 0x7f,  //8       序号8
  58. 0x6f,  //9       序号9
  59. 0x00,  //无      序号10
  60. 0x40,  //-       序号11
  61. 0x73,  //P       序号12
  62. };
  63. void main()
  64.   {
  65.    initial_myself();  
  66.    delay_long(100);   
  67.    initial_peripheral();
  68.    while(1)  
  69.    {
  70.        display_service(); //显示的窗口菜单服务程序
  71.    }
  72. }


  73. void display_service() //显示的窗口菜单服务程序
  74. {

  75.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  76.    {
  77.        case 1:   //显示第一个窗口的数据  本系统中只有一个显示窗口
  78.             if(ucWd1Update==1)  //窗口1要全部更新显示
  79.             {
  80.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描
  81.                ucDigShow8=10;  //第8位数码管显示无
  82.                ucDigShow7=10;  //第7位数码管显示无
  83.                ucDigShow6=10;  //第6位数码管显示无
  84.                ucDigShow5=10;  //第5位数码管显示无

  85.               //先分解数据
  86.                ucTemp4=uiPluseCnt/1000;     
  87.                ucTemp3=uiPluseCnt%1000/100;
  88.                ucTemp2=uiPluseCnt%100/10;
  89.                ucTemp1=uiPluseCnt%10;
  90.   
  91.              //再过渡需要显示的数据到缓冲变量里,让过渡的时间越短越好

  92.               //以下增加的if判断就是略作修改,把整个4位数据中高位为0的去掉不显示。
  93.                if(uiPluseCnt<1000)   
  94.                {
  95.                    ucDigShow4=10;  //如果小于1000,千位显示无
  96.                }
  97.                else
  98.                {
  99.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  100.                }
  101.                if(uiPluseCnt<100)
  102.                {
  103.                   ucDigShow3=10;  //如果小于100,百位显示无
  104.                }
  105.                else
  106.                {
  107.                   ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  108.                }
  109.                if(uiPluseCnt<10)
  110.                {
  111.                   ucDigShow2=10;  //如果小于10,十位显示无
  112.                }
  113.                else
  114.                {
  115.                   ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  116.                }
  117.                ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  118.             }
  119.             break;
  120.    
  121.     }
  122.    

  123. }


  124. void display_drive()  
  125. {
  126.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  127.    switch(ucDisplayDriveStep)
  128.    {
  129.       case 1:  //显示第1位
  130.            ucDigShowTemp=dig_table[ucDigShow1];
  131.                    if(ucDigDot1==1)
  132.                    {
  133.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  134.                    }
  135.            dig_hc595_drive(ucDigShowTemp,0xfe);
  136.                break;
  137.       case 2:  //显示第2位
  138.            ucDigShowTemp=dig_table[ucDigShow2];
  139.                    if(ucDigDot2==1)
  140.                    {
  141.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  142.                    }
  143.            dig_hc595_drive(ucDigShowTemp,0xfd);
  144.                break;
  145.       case 3:  //显示第3位
  146.            ucDigShowTemp=dig_table[ucDigShow3];
  147.                    if(ucDigDot3==1)
  148.                    {
  149.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  150.                    }
  151.            dig_hc595_drive(ucDigShowTemp,0xfb);
  152.                break;
  153.       case 4:  //显示第4位
  154.            ucDigShowTemp=dig_table[ucDigShow4];
  155.                    if(ucDigDot4==1)
  156.                    {
  157.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  158.                    }
  159.            dig_hc595_drive(ucDigShowTemp,0xf7);
  160.                break;
  161.       case 5:  //显示第5位
  162.            ucDigShowTemp=dig_table[ucDigShow5];
  163.                    if(ucDigDot5==1)
  164.                    {
  165.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  166.                    }
  167.            dig_hc595_drive(ucDigShowTemp,0xef);
  168.                break;
  169.       case 6:  //显示第6位
  170.            ucDigShowTemp=dig_table[ucDigShow6];
  171.                    if(ucDigDot6==1)
  172.                    {
  173.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  174.                    }
  175.            dig_hc595_drive(ucDigShowTemp,0xdf);
  176.                break;
  177.       case 7:  //显示第7位
  178.            ucDigShowTemp=dig_table[ucDigShow7];
  179.                    if(ucDigDot7==1)
  180.                    {
  181.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  182.            }
  183.            dig_hc595_drive(ucDigShowTemp,0xbf);
  184.                break;
  185.       case 8:  //显示第8位
  186.            ucDigShowTemp=dig_table[ucDigShow8];
  187.                    if(ucDigDot8==1)
  188.                    {
  189.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  190.                    }
  191.            dig_hc595_drive(ucDigShowTemp,0x7f);
  192.                break;
  193.    }
  194.    ucDisplayDriveStep++;
  195.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  196.    {
  197.      ucDisplayDriveStep=1;
  198.    }

  199. }

  200. //数码管的74HC595驱动函数
  201. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  202. {
  203.    unsigned char i;
  204.    unsigned char ucTempData;
  205.    dig_hc595_sh_dr=0;
  206.    dig_hc595_st_dr=0;
  207.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  208.    for(i=0;i<8;i++)
  209.    {
  210.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  211.          else dig_hc595_ds_dr=0;
  212.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  213.          delay_short(1);
  214.          dig_hc595_sh_dr=1;
  215.          delay_short(1);
  216.          ucTempData=ucTempData<<1;
  217.    }
  218.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  219.    for(i=0;i<8;i++)
  220.    {
  221.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  222.          else dig_hc595_ds_dr=0;
  223.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  224.          delay_short(1);
  225.          dig_hc595_sh_dr=1;
  226.          delay_short(1);
  227.          ucTempData=ucTempData<<1;
  228.    }
  229.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  230.    delay_short(1);
  231.    dig_hc595_st_dr=1;
  232.    delay_short(1);
  233.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  234.    dig_hc595_st_dr=0;
  235.    dig_hc595_ds_dr=0;
  236. }

  237. void T0_time() interrupt 1   //定时器中断函数
  238. {
  239.   TF0=0;  //清除中断标志
  240.   TR0=0; //关中断

  241.   display_drive();  //数码管字模的驱动函数

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


  246. /* 注释一:
  247. * 用朱兆祺51学习板中的S1按键作为模拟外部中断0的下降沿脉冲输入。
  248. * 原来S1按键是直接连接到P0^0口的,因此必须通过跳线把P0^0口连接到
  249. * 单片机外部中断0专用IO口P3^2上,只需把P0^0和P3^2的两个黄颜色跳冒去掉,通过一根
  250. * 线把P0^0和P3^2相互连接起来即可。这时每按下一次S1按键,就会给P3^2口
  251. * 产生一个下降沿的脉冲,然后程序会自动跳到以下中断函数中执行一次。
  252. * 由于按键按下去的时候有抖动,也就按一次可能产生几个脉冲,所以按一次往往看到数据一次加了三四个,
  253. * 这种实验现象都是正常的。
  254. */

  255. void  INT0_int(void) interrupt 0  //INT0外部中断函数
  256. {
  257.    EX0=0;   //禁止外部0中断 这个只是我个人的编程习惯,也可以不关闭

  258.    uiPluseCnt++;  //累计外部中断下降沿的脉冲数
  259.    ucWd1Update=1;  //窗口1更新显示

  260.    EX0=1;  //打开外部0中断
  261. }

  262. void delay_short(unsigned int uiDelayShort)
  263. {
  264.    unsigned int i;  
  265.    for(i=0;i<uiDelayShort;i++)
  266.    {
  267.      ;   //一个分号相当于执行一条空语句
  268.    }
  269. }

  270. void delay_long(unsigned int uiDelayLong)
  271. {
  272.    unsigned int i;
  273.    unsigned int j;
  274.    for(i=0;i<uiDelayLong;i++)
  275.    {
  276.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  277.           {
  278.              ; //一个分号相当于执行一条空语句
  279.           }
  280.    }
  281. }

  282. void initial_myself()  //初始化单片机
  283. {
  284. /* 注释二:
  285. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  286. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  287. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。S1经过跳线后
  288. * 连接到单片机的外部中断专用接口P3^2上,用来模拟外部下降沿脉冲输入。
  289. */
  290.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  291.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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

  296. void initial_peripheral() //初始化外围
  297. {

  298.    ucDigDot8=0;   //小数点全部不显示
  299.    ucDigDot7=0;  
  300.    ucDigDot6=0;
  301.    ucDigDot5=0;  
  302.    ucDigDot4=0;
  303.    ucDigDot3=0;  
  304.    ucDigDot2=0;
  305.    ucDigDot1=0;

  306.    EX0=1; //允许外部中断0
  307.    IT0=1;  //下降沿触发外部中断0   如果是0代表低电平中断

  308. /* 注释三:
  309. * 注意,由于本系统中用了2个中断,一个是定时中断,一个是外部中断,
  310. * 因此必须设置IP寄存器,让外部中断0为最高优先级,让外部中断0可以打断
  311. * 定时中断。
  312. */

  313.    IP=0x01; //设置外部中断0为最高优先级,可以打断低优先级中断服务。实现中断嵌套功能

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


  317. }
复制代码

总结陈词:
    这节讲了外部中断的基本程序模板,下一节我会讲一个外部中断的实际应用项目例子。欲知详情,请听下回分解----利用外部中断实现模拟串口数据的收发。

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

此帖出自51单片机论坛
 
 
 

回复

98

帖子

0

TA的资源

一粒金砂(高级)

120
 
第六十七节:利用外部中断实现模拟串口数据的收发。

开场白:
     鸿哥曾经亲自用外部中断做过红外遥控器的数据接收,步进电机圆周运动的光电反馈信号检测,输液器里瞬间即逝的水滴信号,以及本节的模拟串口数据的接收,其实这些项目的原理都大同小异,会一样即可触类旁通其它的。
    这一节要教大家四个知识点:
   第一个:如何利用外部中断实现模拟串口数据的收发。
   第二个:在退出外部中断函数时,必须通过软件把外部中断标志位IE0清零,否则在接收到的数据包最后面会多收到一个无效的字节0xFF。
   第三个:实际做项目的时候,尽量利用单片机内部自带的集成串口,不到万不得已尽量不要用自制的模拟串口,如果非要用本节讲的模拟串口,那么一次接收的数据包不要太长,尽可能越短越好,因为自己做的模拟串口在稳定性上肯定比不上单片机自带的串口。这种模拟串口在批量生产时容易因为晶振的误差,以及外界各地温度的温差而影响产品的一致性,是有隐患的。
第四个:用模拟串口时,尽量不要选用动态数码管的显示方案,因为单片机在收发串口数据时,只能专心干一件事,此时不能中途被动态数码管扫描程序占用。而动态数码管得不到均匀扫描,就会产生略微闪烁的现象瑕疵。

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

(1)硬件平台:
    基于朱兆祺51单片机学习板。当把程序下载到单片机之后,要做以下跳线处理:
   单片机原来的P3.1引脚是TI串口输出引脚,P3.0是RI串口输入引脚,分别把P3.1和P3.0的黄颜色跳冒去掉,同时也把外部中断0的引脚P3.2和一根IO口P1.0引脚的换颜色跳冒去掉,把P3.2跳冒的右针连接到P3.0跳冒的左针,作为模拟串口的接收数据线。把P1.0跳冒的右针连接到P3.1跳冒的左针,作为模拟串口的发送数据线。

(2)实现功能:
    波特率是:9600 。
通过电脑串口调试助手模拟上位机,往单片机任意发送一串不超过10个的数据包,单片机如实地返回接收到的整包数据给上位机。

例如:
(a)上位机发送数据:01 02 03 04 05 06 07 08 09 0A
单片机返回:    01 02 03 04 05 06 07 08 09 0A

(b)上位机发送数据: 05 07 EE A8 F9
单片机返回:     05 07 EE A8 F9


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

  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  3. #define const_rc_size  20  //接收串口中断数据的缓冲区数组大小

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


  5. /* 注释一:
  6. * 以下时序脉冲延时参数我是在keil uVision2 平台下,Memory Model在small模式,Code Rom Size在Large模式下编译的,
  7. * 如果在不同keil版本,不同的模式下,编译出来的程序有可能此参数会不一样。
  8. * 以下的时序脉冲延时参数是需要一步一步慢慢调的。我一开始的时候先编写一个简单的发送数据测试程序,
  9. * 先确调试出合适的发送时序延时数据。然后再编写串口接收数据的程序,从而调试出接收时序的延时参数。
  10. * 比如:我第一步发送数据的测试程序是这样的:
  11. void main()
  12.   {
  13.    initial_myself();  
  14.    delay_long(100);   
  15.    initial_peripheral();
  16.    while(1)  
  17.    {
  18.   //      usart_service();  //串口服务程序
  19.        eusart_send(0x08);    //测试程序,让它不断发送数据给上位机观察,确保发送延时时序的参数准确性
  20.            delay_long(300);

  21.        eusart_send(0xE5);    //测试程序,让它不断发送数据给上位机观察,确保发送延时时序的参数准确性
  22.            delay_long(300);
  23.    }

  24. }
  25. */


  26. #define const_t_1  10  //发送时序延时1  第一步先调出此数据
  27. #define const_t_2  9  //发送时序延时2   第一步先调出此数据


  28. #define const_r_1  7  //接收时序延时1   第二步再调出此数据
  29. #define const_r_2  9 //接收时序延时2  第二步再调出此数据

  30. void initial_myself(void);   
  31. void initial_peripheral(void);
  32. void delay_long(unsigned int uiDelaylong);
  33. void delay_short(unsigned int uiDelayShort);
  34. void delay_minimum(unsigned char ucDelayMinimum);  //细分度最小的延时,用char类型一个字节

  35. void T0_time(void);  //定时中断函数
  36. void INT0_int(void);  //外部0中断函数,在本系统中是模拟串口的接收中断函数。
  37. void usart_service(void);  //串口服务程序,在main函数里


  38. void eusart_send(unsigned char ucSendData);
  39. unsigned char read_eusart_byte();//从串口读一个字节


  40. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  41. sbit ti_dr=P1^0;  //模拟串口发送数据的IO口
  42. sbit ri_sr=P3^2;  //模拟串口接收数据的IO口 也是外部中断0的复用IO口
  43. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  44. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  45. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  46. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组

  47. unsigned char ucTest=0;

  48. void main()
  49.   {
  50.    initial_myself();  
  51.    delay_long(100);   
  52.    initial_peripheral();
  53.    while(1)  
  54.    {
  55.         usart_service();  //串口服务程序

  56.    }

  57. }



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

  60.      unsigned char i=0;   

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

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

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


  65.                         for(i=0;i<uiRcregTotal;i++)  //返回全部接收到的数据包
  66.                         {
  67.                            eusart_send(ucRcregBuf[i]);
  68.                         }
  69.          
  70.                                          
  71.             uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  72.   
  73.      }
  74.                         
  75. }


  76. //往串口发送一个字节
  77. void eusart_send(unsigned char ucSendData) //往上位机发送一个字节的函数
  78. {
  79.      unsigned char i=8;
  80.      EA=0;     //关总中断
  81.      ti_dr=0; //发送启始位
  82.          delay_minimum(const_t_1); //发送时序延时1   delay_minimum是本程序细分度最小的延时
  83.      while(i--)
  84.      {
  85.          ti_dr=ucSendData&0x01;      //先传低位
  86.              delay_minimum(const_t_2); //发送时序延时2   delay_minimum是本程序细分度最小的延时
  87.          ucSendData=ucSendData>>1;
  88.      }

  89.      ti_dr=1;  //发送结束位
  90.      delay_short(400);  //每个字节之间的延时,这里非常关键,也是最容易出错的地方。延时的大小请根据实际项目来调整
  91.      EA=1;     //开总中断
  92. }


  93. //从串口读取一个字节
  94. unsigned char read_eusart_byte()
  95. {
  96.     unsigned char  ucReadData=0;
  97.     unsigned char  i=8;


  98.          delay_minimum(const_r_1);  //接收时序延时1 。作用是等过起始位  delay_minimum是本程序细分度最小的延时
  99.      while(i--)
  100.      {
  101.          ucReadData >>=1;
  102.          if(ri_sr==1)
  103.                  {  
  104.                      ucReadData|=0x80;      //先收低位
  105.                  }

  106.          if(ri_sr==0) //此处空指令,是为了让驱动时序的时间保持一致性
  107.                  {  
  108.                      ;
  109.                  }

  110.              delay_minimum(const_r_2);    //接收时序延时2    delay_minimum是本程序细分度最小的延时   
  111.      }

  112.      return ucReadData;
  113. }




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


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



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


  127.                              
  128. void  INT0_int(void) interrupt 0  //INT0外部中断函数
  129. {
  130.    EX0=0;   //禁止外部0中断 这个只是我个人的编程习惯,也可以不关闭

  131.    ++uiRcregTotal;
  132.    if(uiRcregTotal>const_rc_size)  //超过缓冲区
  133.    {
  134.            uiRcregTotal=const_rc_size;
  135.    }
  136.    ucRcregBuf[uiRcregTotal-1]=read_eusart_byte();   //将串口接收到的数据缓存到接收缓冲区里
  137.    uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。

  138. /* 注释二:
  139. * 注意,此处必须把IE0中断标志清零,否则在接收到的数据包最后面会多收到一个无效的字节0xFF。
  140. */

  141.    IE0=0;  //外部中断0标志位清零,必须的!

  142.    EX0=1;  //打开外部0中断
  143. }


  144. void delay_long(unsigned int uiDelayLong)
  145. {
  146.    unsigned int i;
  147.    unsigned int j;
  148.    for(i=0;i<uiDelayLong;i++)
  149.    {
  150.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  151.           {
  152.              ; //一个分号相当于执行一条空语句
  153.           }
  154.    }
  155. }

  156. void delay_short(unsigned int uiDelayShort)
  157. {
  158.    unsigned int i;  
  159.    for(i=0;i<uiDelayShort;i++)
  160.    {
  161.      ;   //一个分号相当于执行一条空语句
  162.    }
  163. }


  164. /* 注释三:
  165. * 由于IO口模拟的串口时序要求很高,所以用的延时函数尽可能细分度越高越好,以下用一个字节的延时计时器
  166. */
  167. void delay_minimum(unsigned char ucDelayMinimum)  //细分度最小的延时,用char类型一个字节
  168. {
  169.    unsigned char i;  
  170.    for(i=0;i<ucDelayMinimum;i++)
  171.    {
  172.      ;   //一个分号相当于执行一条空语句
  173.    }

  174. }

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

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

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


  182. }

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

  185.    EX0=1; //允许外部中断0
  186.    IT0=1;  //下降沿触发外部中断0   如果是0代表低电平中断
  187.    IP=0x01; //设置外部中断0为最高优先级,可以打断低优先级中断服务。实现中断嵌套功能

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

  191. }
复制代码

总结陈词:
这节讲完了外部中断的应用例子,下一节我会开始讲单片机C语言的多文件编程技巧。很多人也把多文件编程称作模块化编程,其实我觉得叫多文件编程会更加符合实际一些。多文件编程有两个最大的好处,一个是给我们的程序增加了目录,方便我们查找。另外一个好处是方便移植别人已经做好的功能程序模块,利用这个特点,特别适合团队一起做大型项目。很多初学者刚开始学多文件编程时,会经常遇到重复定义等问题,想知道怎么解决这些问题吗?欲知详情,请听下回分解----单片机C语言的多文件编程技巧。

(未完待续,下节更精彩,不要走开哦)
此帖出自51单片机论坛
 
 
 

回复
您需要登录后才可以回帖 登录 | 注册

随便看看
查找数据手册?

EEWorld Datasheet 技术支持

相关文章 更多>>
关闭
站长推荐上一条 1/8 下一条

 
EEWorld订阅号

 
EEWorld服务号

 
汽车开发圈

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 国产芯 安防电子 汽车电子 手机便携 工业控制 家用电子 医疗电子 测试测量 网络通信 物联网

北京市海淀区中关村大街18号B座15层1530室 电话:(010)82350740 邮编:100190

电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2025 EEWORLD.com.cn, Inc. All rights reserved
快速回复 返回顶部 返回列表