搜索
bottom↓
楼主: 吴坚鸿

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

  [复制链接]

出0入0汤圆

发表于 2014-5-24 23:36:54 | 显示全部楼层
学习~~~~~~~~~~~~

出0入0汤圆

 楼主| 发表于 2014-5-25 01:05:07 | 显示全部楼层
xxzzhy 发表于 2014-5-24 00:11
学习中。楼主,你能说说无刷电机的控制与驱动吗?

我以后会讲到步进电机的控制与驱动,会涉及到单片机中的变频技术。但是短期内还没那么快,因为前面还有很多内容要讲。

出0入0汤圆

发表于 2014-5-25 10:40:51 | 显示全部楼层
收获多多,谢谢鸿哥。

出0入0汤圆

发表于 2014-5-25 16:12:55 | 显示全部楼层
吴坚鸿 发表于 2014-5-25 01:05
我以后会讲到步进电机的控制与驱动,会涉及到单片机中的变频技术。但是短期内还没那么快,因为前面还有很 ...

鸿哥,无刷电机和步进电机控制不一样吧。

出0入0汤圆

发表于 2014-5-25 19:58:57 | 显示全部楼层
mark, 谢谢LZ

出0入0汤圆

 楼主| 发表于 2014-5-25 23:34:47 | 显示全部楼层
xxzzhy 发表于 2014-5-25 16:12
鸿哥,无刷电机和步进电机控制不一样吧。

经过你的提醒,好像是不一样。

出0入0汤圆

发表于 2014-5-26 08:39:39 | 显示全部楼层
这个好 ,我喜欢~

出0入0汤圆

发表于 2014-5-26 20:20:06 | 显示全部楼层
顶起来一个,值得借鉴。经验都是积累错误原因的精华

出0入0汤圆

发表于 2014-5-27 09:01:20 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 18:54
第三十节:数码管通过闪烁来设置数据。

开场白:

鸿哥,你的循序渐进的程序令我学习更容易了!!感谢!!

出0入0汤圆

发表于 2014-5-27 11:00:02 | 显示全部楼层
吴坚鸿 发表于 2014-5-3 08:04
第四十四节:从机的串口收发综合程序框架

开场白:

谢了!!顶鸿哥!!

出0入0汤圆

发表于 2014-5-27 11:05:21 | 显示全部楼层
吴坚鸿 发表于 2014-5-5 11:40
第四十五节:主机的串口收发综合程序框架

开场白:

支持鸿哥,是牛人就拿出真东西出来亮亮!!!

出0入0汤圆

 楼主| 发表于 2014-5-28 11:52:13 | 显示全部楼层
第四十九节:利用DS18B20做一个温控器  。

开场白:
      DS18B20是一款常用的温度传感器芯片,它只占用单片机一根IO口,使用起来也特别方便。需要特别注意的是,正因为它只用一根IO口跟单片机通讯,因此读取一次温度值的通讯时间比较长,而且时序要求严格,在通讯期间不允许被单片机其它的中断干扰,因此在实际项目中,系统一旦选用了这款传感器芯片,就千万不要选用动态扫描数码管的显示方式。否则在关闭中断读取温度的时候,数码管的显示会有略微的“闪烁”现象。
      DS18B20的测温范围是-55度至125度。在-10度至85度的温度范围内误差是+-0.5度,能满足大部分常用的测温要求。
这一节要教会大家三个知识点:
第一个:大概了解一下DS18B20的驱动程序。
第二个:做温控设备的时候,为了避免继电器在临界温度附近频繁跳动切换,应该设置一个缓冲温差。本程序的缓冲温差是2度。
第三个:继续加深了解按键,显示,传感器它们三者是如何紧密关联起来的程序框架。

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

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

(2)实现功能:
     本程序只有1个窗口。这个窗口有2个局部显示。
第1个局部是第7,6,5位数码管,显示设定的温度。
第2个局部是第4,3,2,1位数码管,显示实际环境温度。其中第4位数码管显示正负符号位。
S1按键是加键,S5按键是减键。通过它们可以直接设置“设定温度”。
一个LED灯用来模拟工控的继电器。
当实际温度低于或者等于设定温度2度以下时,模拟继电器的LED灯亮。
当实际温度等于或者大于设定温度时,模拟继电器的LED灯灭。
当实际温度处于设定温度和设定温度减去2度的范围内,模拟继电器的LED维持现状,这个2度范围用来做缓冲温差,避免继电器在临界温度附近频繁跳动切换。

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


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

  5. #define const_ds18b20_sampling_time    180   //累计主循环次数的时间,每次刷新采样时钟芯片的时间


  6. void initial_myself(void);   
  7. void initial_peripheral(void);
  8. void delay_short(unsigned int uiDelayShort);
  9. void delay_long(unsigned int uiDelaylong);


  10. //驱动数码管的74HC595
  11. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  12. void display_drive(void); //显示数码管字模的驱动函数
  13. void display_service(void); //显示的窗口菜单服务程序
  14. //驱动LED的74HC595
  15. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

  17. void key_service(void); //按键服务的应用程序
  18. void key_scan(void);//按键扫描函数 放在定时中断里

  19. void temper_control_service(void); //温控程序
  20. void ds18b20_sampling(void); //ds18b20采样程序

  21. void ds18b20_reset(); //复位ds18b20的时序
  22. unsigned char ds_read_byte(void ); //读一字节
  23. void ds_write_byte(unsigned char dat); //写一个字节
  24. unsigned int get_temper();  //读取一次没有经过换算的温度数值

  25. sbit dq_dr_sr=P2^6; //ds18b20的数据驱动线

  26. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  27. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键

  28. sbit led_dr=P3^5;  //LED灯,模拟工控中的继电器

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

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



  31. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  32. sbit dig_hc595_st_dr=P2^1;  
  33. sbit dig_hc595_ds_dr=P2^2;  
  34. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  35. sbit hc595_st_dr=P2^4;  
  36. sbit hc595_ds_dr=P2^5;  


  37. unsigned int uiSampingCnt=0;   //采集Ds1302的计时器,每秒钟更新采集一次
  38. unsigned char ucSignFlag=0; //正负符号。0代表正数,1代表负数,表示零下多少度。
  39. unsigned long ulCurrentTemper=33; //实际温度
  40. unsigned long ulSetTemper=26; //设定温度

  41. unsigned int uiTemperTemp=0; //中间变量

  42. unsigned char ucKeySec=0;   //被触发的按键编号

  43. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  44. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  45. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  46. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  47. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器
  48. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  49. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  50. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  51. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  52. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  53. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  54. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  55. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  56. unsigned char ucDigShow1;  //第1位数码管要显示的内容

  57. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  58. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  59. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  60. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  61. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  62. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  63. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  64. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  65. unsigned char ucDigShowTemp=0; //临时中间变量
  66. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  67. unsigned char ucWd=1;  //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要

  68. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  69. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  70. unsigned char ucTemp1=0;  //中间过渡变量
  71. unsigned char ucTemp2=0;  //中间过渡变量
  72. unsigned char ucTemp3=0;  //中间过渡变量
  73. unsigned char ucTemp4=0;  //中间过渡变量
  74. unsigned char ucTemp5=0;  //中间过渡变量
  75. unsigned char ucTemp6=0;  //中间过渡变量
  76. unsigned char ucTemp7=0;  //中间过渡变量
  77. unsigned char ucTemp8=0;  //中间过渡变量


  78. //根据原理图得出的共阴数码管字模表
  79. code unsigned char dig_table[]=
  80. {
  81. 0x3f,  //0       序号0
  82. 0x06,  //1       序号1
  83. 0x5b,  //2       序号2
  84. 0x4f,  //3       序号3
  85. 0x66,  //4       序号4
  86. 0x6d,  //5       序号5
  87. 0x7d,  //6       序号6
  88. 0x07,  //7       序号7
  89. 0x7f,  //8       序号8
  90. 0x6f,  //9       序号9
  91. 0x00,  //无      序号10
  92. 0x40,  //-       序号11
  93. 0x73,  //P       序号12
  94. };
  95. void main()
  96.   {
  97.    initial_myself();  
  98.    delay_long(100);   
  99.    initial_peripheral();
  100.    while(1)  
  101.    {
  102.       key_service(); //按键服务的应用程序
  103.       ds18b20_sampling(); //ds18b20采样程序
  104.       temper_control_service(); //温控程序
  105.       display_service(); //显示的窗口菜单服务程序
  106.    }
  107. }

  108. /* 注释一:
  109.   * 做温控设备的时候,为了避免继电器在临界温度附近频繁跳动切换,应该设置一个
  110.   * 缓冲温差。本程序的缓冲温差是2度。
  111.   */
  112. void temper_control_service(void) //温控程序
  113. {
  114.    if(ucSignFlag==0) //是正数的前提下
  115.    {
  116.       if(ulCurrentTemper>=ulSetTemper)  //当实际温度大于等于设定温度时
  117.       {
  118.         led_dr=0; //模拟继电器的LED灯熄灭
  119.       }
  120.       else if(ulCurrentTemper<=(ulSetTemper-2))  //当实际温度小于等于设定温度2读以下时,这里的2是缓冲温差2度
  121.       {
  122.         led_dr=1; //模拟继电器的LED灯点亮
  123.       }
  124.    }
  125.    else  //是负数,说明是零下多少度的情况下
  126.    {
  127.       led_dr=1; //模拟继电器的LED灯点亮
  128.    }

  129. }


  130. void ds18b20_sampling(void) //ds18b20采样程序
  131. {

  132.       ++uiSampingCnt;  //累计主循环次数的时间
  133.       if(uiSampingCnt>const_ds18b20_sampling_time)  //每隔一段时间就更新采集一次Ds18b20数据
  134.           {
  135.           uiSampingCnt=0;

  136.           ET0=0;  //禁止定时中断
  137.           uiTemperTemp=get_temper();  //读取一次没有经过换算的温度数值
  138.           ET0=1; //开启定时中断

  139.           if((uiTemperTemp&0xf800)==0xf800) //是负号
  140.           {
  141.                          ucSignFlag=1;

  142.              uiTemperTemp=~uiTemperTemp;  //求补码
  143.              uiTemperTemp=uiTemperTemp+1;

  144.           }
  145.           else //是正号
  146.           {
  147.                          ucSignFlag=0;

  148.           }



  149.           ulCurrentTemper=0; //把int数据类型赋给long类型之前要先清零
  150.           ulCurrentTemper=uiTemperTemp;

  151.           ulCurrentTemper=ulCurrentTemper*10; //为了先保留一位小数点,所以放大10倍,
  152.           ulCurrentTemper=ulCurrentTemper>>4;  //往右边移动4位,相当于乘以0.0625. 此时保留了1位小数点,

  153.           ulCurrentTemper=ulCurrentTemper+5;  //四舍五入
  154.           ulCurrentTemper=ulCurrentTemper/10; //四舍五入后,去掉小数点

  155.           ucWd1Part2Update=1; //局部2更新显示实时温度
  156.           }
  157. }


  158. //ds18b20驱动程序
  159. unsigned int get_temper()  //读取一次没有经过换算的温度数值
  160. {
  161. unsigned char temper_H;
  162. unsigned char temper_L;
  163. unsigned int ds18b20_data=0;

  164. ds18b20_reset(); //复位ds18b20的时序
  165. ds_write_byte(0xCC);
  166. ds_write_byte(0x44);

  167. ds18b20_reset(); //复位ds18b20的时序
  168. ds_write_byte(0xCC);
  169. ds_write_byte(0xBE);
  170. temper_L=ds_read_byte();
  171. temper_H=ds_read_byte();

  172. ds18b20_data=temper_H;     //把两个字节合并成一个int数据类型
  173. ds18b20_data=ds18b20_data<<8;
  174. ds18b20_data=ds18b20_data|temper_L;
  175. return ds18b20_data;
  176. }



  177. void ds18b20_reset() //复位ds18b20的时序
  178. {
  179.   unsigned char x;
  180.   dq_dr_sr=1;
  181.   delay_short(8);
  182.   dq_dr_sr=0;
  183.   delay_short(80);
  184.   dq_dr_sr=1;
  185.   delay_short(14);
  186.   x=dq_dr_sr;
  187.   delay_short(20);

  188. }

  189. void ds_write_byte(unsigned char date) //写一个字节
  190. {
  191. unsigned char  i;

  192. for(i=0;i<8;i++)
  193. {
  194.   dq_dr_sr=0;
  195.   dq_dr_sr=date&0x01;
  196.   delay_short(5);
  197.   dq_dr_sr=1;
  198.   date=date>>1;
  199. }
  200. }

  201. unsigned char ds_read_byte(void ) //读一字节
  202. {
  203. unsigned char i;
  204. unsigned char date=0;
  205. for(i=0;i<8;i++)
  206. {
  207.   dq_dr_sr=0;
  208.   date=date>>1;
  209.   dq_dr_sr=1;
  210.   if(dq_dr_sr)
  211.   {
  212.      date=date|0x80;
  213.   }
  214.   delay_short(5);
  215. }
  216. return (date);
  217. }



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

  220.    switch(ucWd)  //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  221.    {
  222.        case 1:  

  223.                         if(ucWd1Part1Update==1)//局部设定温度更新显示
  224.                         {
  225.                            ucWd1Part1Update=0;

  226.                ucTemp8=10; //显示空

  227.                            if(ulSetTemper>=100)
  228.                            {
  229.                   ucTemp7=ulSetTemper%1000/100; //显示设定温度的百位
  230.                            }
  231.                            else
  232.                            {
  233.                               ucTemp7=10; //显示空
  234.                            }

  235.                            if(ulSetTemper>=10)
  236.                            {
  237.                   ucTemp6=ulSetTemper%100/10; //显示设定温度的十位
  238.                            }
  239.                            else
  240.                            {
  241.                               ucTemp6=10; //显示空
  242.                            }

  243.                ucTemp5=ulSetTemper%10; //显示设定温度的个位


  244.                ucDigShow8=ucTemp8; //数码管显示实际内容
  245.                ucDigShow7=ucTemp7;
  246.                ucDigShow6=ucTemp6;
  247.                ucDigShow5=ucTemp5;
  248.                         }


  249.                         if(ucWd1Part2Update==1)//局部实际温度更新显示
  250.                         {
  251.                            if(ucSignFlag==0)  //正数
  252.                            {
  253.                   ucTemp4=10; //显示空
  254.                            }
  255.                            else  //负数,说明是零下多少度的情况下
  256.                            {
  257.                   ucTemp4=11; //显示负号-
  258.                            }

  259.                            if(ulCurrentTemper>=100)
  260.                            {
  261.                   ucTemp3=ulCurrentTemper%100/100; //显示实际温度的百位
  262.                            }
  263.                            else
  264.                            {
  265.                           ucTemp3=10; //显示空
  266.                            }


  267.                            if(ulCurrentTemper>=10)
  268.                            {
  269.                   ucTemp2=ulCurrentTemper%100/10;  //显示实际温度的十位
  270.                            }
  271.                            else
  272.                            {
  273.                   ucTemp2=10;  //显示空
  274.                            }

  275.                ucTemp1=ulCurrentTemper%10; //显示实际温度的个数位

  276.                ucDigShow4=ucTemp4; //数码管显示实际内容
  277.                ucDigShow3=ucTemp3;
  278.                ucDigShow2=ucTemp2;
  279.                ucDigShow1=ucTemp1;
  280.                         }

  281.             break;

  282.       }
  283.    

  284. }

  285. void key_scan(void)//按键扫描函数 放在定时中断里
  286. {  
  287.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  288.   {
  289.      ucKeyLock1=0; //按键自锁标志清零
  290.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  291.   }
  292.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  293.   {
  294.      uiKeyTimeCnt1++; //累加定时中断次数
  295.      if(uiKeyTimeCnt1>const_key_time1)
  296.      {
  297.         uiKeyTimeCnt1=0;
  298.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  299.         ucKeySec=1;    //触发1号键
  300.      }
  301.   }

  302.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  303.   {
  304.      ucKeyLock2=0; //按键自锁标志清零
  305.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  306.   }
  307.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  308.   {
  309.      uiKeyTimeCnt2++; //累加定时中断次数
  310.      if(uiKeyTimeCnt2>const_key_time2)
  311.      {
  312.         uiKeyTimeCnt2=0;
  313.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  314.         ucKeySec=2;    //触发2号键
  315.      }
  316.   }





  317. }

  318. void key_service(void) //按键服务的应用程序
  319. {

  320.   switch(ucKeySec) //按键服务状态切换
  321.   {
  322.     case 1:// 加按键 对应朱兆祺学习板的S1键
  323.           switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  324.           {
  325.               case 1: //在窗口1下设置设定温度
  326.                    ulSetTemper++;
  327.                                    if(ulSetTemper>125)
  328.                                    {
  329.                                      ulSetTemper=125;
  330.                                    }

  331.                                ucWd1Part1Update=1; //更新显示设定温度
  332.                    break;
  333.           }

  334.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  335.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  336.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  337.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  338.           break;   
  339.    
  340.     case 2:// 减按键 对应朱兆祺学习板的S5键
  341.           switch(ucWd) //因为本程序只有1个窗口,在实际项目中,此处的ucWd也可以省略不要
  342.           {
  343.                case 1: //在窗口1下设置设定温度
  344.                     if(ulSetTemper>2)  //由于缓冲温差是2度,所以我人为规定最小允许设定的温度不能低于2度
  345.                                         {
  346.                                            ulSetTemper--;
  347.                                         }

  348.                           ucWd1Part1Update=1; //更新显示设定温度
  349.                     break;
  350.          
  351.           }

  352.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  353.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  354.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  355.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  356.           break;  


  357.          
  358.   }         
  359.   

  360. }

  361. void display_drive(void)  
  362. {
  363.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  364.    switch(ucDisplayDriveStep)
  365.    {
  366.       case 1:  //显示第1位
  367.            ucDigShowTemp=dig_table[ucDigShow1];
  368.                    if(ucDigDot1==1)
  369.                    {
  370.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  371.                    }
  372.            dig_hc595_drive(ucDigShowTemp,0xfe);
  373.                break;
  374.       case 2:  //显示第2位
  375.            ucDigShowTemp=dig_table[ucDigShow2];
  376.                    if(ucDigDot2==1)
  377.                    {
  378.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  379.                    }
  380.            dig_hc595_drive(ucDigShowTemp,0xfd);
  381.                break;
  382.       case 3:  //显示第3位
  383.            ucDigShowTemp=dig_table[ucDigShow3];
  384.                    if(ucDigDot3==1)
  385.                    {
  386.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  387.                    }
  388.            dig_hc595_drive(ucDigShowTemp,0xfb);
  389.                break;
  390.       case 4:  //显示第4位
  391.            ucDigShowTemp=dig_table[ucDigShow4];
  392.                    if(ucDigDot4==1)
  393.                    {
  394.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  395.                    }
  396.            dig_hc595_drive(ucDigShowTemp,0xf7);
  397.                break;
  398.       case 5:  //显示第5位
  399.            ucDigShowTemp=dig_table[ucDigShow5];
  400.                    if(ucDigDot5==1)
  401.                    {
  402.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  403.                    }
  404.            dig_hc595_drive(ucDigShowTemp,0xef);
  405.                break;
  406.       case 6:  //显示第6位
  407.            ucDigShowTemp=dig_table[ucDigShow6];
  408.                    if(ucDigDot6==1)
  409.                    {
  410.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  411.                    }
  412.            dig_hc595_drive(ucDigShowTemp,0xdf);
  413.                break;
  414.       case 7:  //显示第7位
  415.            ucDigShowTemp=dig_table[ucDigShow7];
  416.                    if(ucDigDot7==1)
  417.                    {
  418.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  419.            }
  420.            dig_hc595_drive(ucDigShowTemp,0xbf);
  421.                break;
  422.       case 8:  //显示第8位
  423.            ucDigShowTemp=dig_table[ucDigShow8];
  424.                    if(ucDigDot8==1)
  425.                    {
  426.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  427.                    }
  428.            dig_hc595_drive(ucDigShowTemp,0x7f);
  429.                break;
  430.    }
  431.    ucDisplayDriveStep++;
  432.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  433.    {
  434.      ucDisplayDriveStep=1;
  435.    }

  436. }

  437. //数码管的74HC595驱动函数
  438. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  439. {
  440.    unsigned char i;
  441.    unsigned char ucTempData;
  442.    dig_hc595_sh_dr=0;
  443.    dig_hc595_st_dr=0;
  444.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  445.    for(i=0;i<8;i++)
  446.    {
  447.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  448.          else dig_hc595_ds_dr=0;
  449.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  450.          delay_short(1);
  451.          dig_hc595_sh_dr=1;
  452.          delay_short(1);
  453.          ucTempData=ucTempData<<1;
  454.    }
  455.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  456.    for(i=0;i<8;i++)
  457.    {
  458.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  459.          else dig_hc595_ds_dr=0;
  460.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  461.          delay_short(1);
  462.          dig_hc595_sh_dr=1;
  463.          delay_short(1);
  464.          ucTempData=ucTempData<<1;
  465.    }
  466.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  467.    delay_short(1);
  468.    dig_hc595_st_dr=1;
  469.    delay_short(1);
  470.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  471.    dig_hc595_st_dr=0;
  472.    dig_hc595_ds_dr=0;
  473. }

  474. //LED灯的74HC595驱动函数
  475. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  476. {
  477.    unsigned char i;
  478.    unsigned char ucTempData;
  479.    hc595_sh_dr=0;
  480.    hc595_st_dr=0;
  481.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  482.    for(i=0;i<8;i++)
  483.    {
  484.          if(ucTempData>=0x80)hc595_ds_dr=1;
  485.          else hc595_ds_dr=0;
  486.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  487.          delay_short(1);
  488.          hc595_sh_dr=1;
  489.          delay_short(1);
  490.          ucTempData=ucTempData<<1;
  491.    }
  492.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  493.    for(i=0;i<8;i++)
  494.    {
  495.          if(ucTempData>=0x80)hc595_ds_dr=1;
  496.          else hc595_ds_dr=0;
  497.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  498.          delay_short(1);
  499.          hc595_sh_dr=1;
  500.          delay_short(1);
  501.          ucTempData=ucTempData<<1;
  502.    }
  503.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  504.    delay_short(1);
  505.    hc595_st_dr=1;
  506.    delay_short(1);
  507.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  508.    hc595_st_dr=0;
  509.    hc595_ds_dr=0;
  510. }


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


  515.   if(ucVoiceLock==0) //原子锁判断
  516.   {
  517.      if(uiVoiceCnt!=0)
  518.      {

  519.         uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  520.         beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  521.      
  522.      }
  523.      else
  524.      {

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


  530.   key_scan(); //按键扫描函数
  531.   display_drive();  //数码管字模的驱动函数

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

  536. void delay_short(unsigned int uiDelayShort)
  537. {
  538.    unsigned int i;  
  539.    for(i=0;i<uiDelayShort;i++)
  540.    {
  541.      ;   //一个分号相当于执行一条空语句
  542.    }
  543. }

  544. void delay_long(unsigned int uiDelayLong)
  545. {
  546.    unsigned int i;
  547.    unsigned int j;
  548.    for(i=0;i<uiDelayLong;i++)
  549.    {
  550.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  551.           {
  552.              ; //一个分号相当于执行一条空语句
  553.           }
  554.    }
  555. }


  556. void initial_myself(void)  //第一区 初始化单片机
  557. {
  558.   led_dr=0;//此处的LED灯模拟工控中的继电器
  559.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  560.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  561.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  562.   TMOD=0x01;  //设置定时器0为工作方式1
  563.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  564.   TL0=0x0b;

  565. }
  566. void initial_peripheral(void) //第二区 初始化外围
  567. {

  568.    ucDigDot8=0;   //小数点全部不显示
  569.    ucDigDot7=0;  
  570.    ucDigDot6=0;
  571.    ucDigDot5=0;  
  572.    ucDigDot4=0;
  573.    ucDigDot3=0;  
  574.    ucDigDot2=0;
  575.    ucDigDot1=0;

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

  579. }
复制代码

总结陈词:
下一节开始讲单片机采集模拟信号的内容,欲知详情,请听下回分解-----利用ADC0832采集电压的模拟信号。

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

出0入4汤圆

发表于 2014-5-28 18:56:43 | 显示全部楼层
顶一下楼主,向楼主学习。

出0入0汤圆

发表于 2014-5-28 20:18:43 | 显示全部楼层
来听鸿哥讲课,感谢鸿哥!

出0入0汤圆

发表于 2014-5-29 07:31:31 | 显示全部楼层
本帖最后由 牛东 于 2014-5-29 07:32 编辑
吴坚鸿 发表于 2014-5-3 08:04
第四十四节:从机的串口收发综合程序框架

开场白:


鸿哥,这段程序中最后面那个break是退出      if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55){}      这个循环吗?   作用是什么呢?   好像不用break也会退出把!!    有点糊涂!!


                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
                        {
                                ucRcType=ucRcregBuf[uiRcMoveIndex+3];   //数据类型  一个字节
                                uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //有效数据长度高位  
                                uiRcSize=uiRcSize<<8;  //移至ui数据的高8位,ui型数据为16位
                                uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];        //有效数据长度=高低位相加(把两个字节合并成一个int类型的数据)
                                ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];  //校验和
                                ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;//清零最后一个字节的累加和变量
                                /**** 注释四:
                                计算校验累加和的方法:除了最后一个字节,其它前面所有的字节累加起来,溢出的不用我们管,
                                C语言编译器会按照固定的规则自动处理。以下for循环里的(3+1+2+uiRcSize),其中3代表3个字节数据头,1代表1个字节数据类型
                                2代表2个字节的数据长度变量,uiRcSize代表实际上一串数据中的有效数据个数。
                                ******/
                                for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
                                {
                                        ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf;
                                }        
                                if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])  //如果校验正确,则进入以下数据处理
                                {                                                  
                                        switch(ucRcType)   //根据不同的数据类型来做不同的数据处理
                                        {
                                                case 0x01:   //驱动蜂鸣器发出声音,并且可以控制蜂鸣器持续发出声音的时间长度
                                                        uiRcVoiceTime=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
                                                        uiRcVoiceTime=uiRcVoiceTime<<8;  
                                                        uiRcVoiceTime=uiRcVoiceTime+ucRcregBuf[uiRcMoveIndex+7];
                                                        ucVoiceLock=1;  //共享数据的原子锁加锁
                                                        uiVoiceCnt=uiRcVoiceTime; //蜂鸣器发出声音
                                                        ucVoiceLock=0;  //共享数据的原子锁解锁
                                                break;        
                                                case 0x02:   //点亮一个LED灯,并且可以控制LED灯持续亮的时间长度
                                                        uiRcLedTime=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
                                                        uiRcLedTime=uiRcLedTime<<8;  
                                                        uiRcLedTime=uiRcLedTime+ucRcregBuf[uiRcMoveIndex+7];
                                                        ucLedLock=1;  //共享数据的原子锁加锁
                                                        uiLedTime=uiRcLedTime; //更改点亮Led灯的时间长度
                                                        uiLedCnt=0;   //在本程序中,清零计数器就等于自动点亮Led灯
                                                        ucLedLock=0;  //共享数据的原子锁解锁
                                                break;
                                        }
                                }        
                                break;  //退出循环

出0入0汤圆

发表于 2014-5-29 09:31:04 | 显示全部楼层
留脚印!

出0入0汤圆

 楼主| 发表于 2014-5-29 10:02:50 | 显示全部楼层
牛东 发表于 2014-5-29 07:31
鸿哥,这段程序中最后面那个break是退出      if(ucRcregBuf==0xeb&&ucRcregBuf==0x00&&ucRcregBuf==0x55 ...

           
           while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))   //是直接退出这行的while循环
            {
               if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断

出0入0汤圆

发表于 2014-5-30 00:21:18 | 显示全部楼层
来晚了,听前辈讲课

出0入0汤圆

发表于 2014-5-30 08:53:11 | 显示全部楼层
先顶再看。。

出0入0汤圆

发表于 2014-5-30 09:23:45 | 显示全部楼层
果断顶你了

出0入0汤圆

发表于 2014-5-30 11:05:19 | 显示全部楼层
mark      

出0入0汤圆

发表于 2014-5-30 14:53:04 | 显示全部楼层
  话不多说,要是早两年遇见这样的好文章,我就不用学两年程序了。  我就把这个介绍给我那些刚毕业的朋友

出0入0汤圆

 楼主| 发表于 2014-5-30 21:25:52 | 显示全部楼层
slzm40 发表于 2014-5-30 14:53
话不多说,要是早两年遇见这样的好文章,我就不用学两年程序了。  我就把这个介绍给我那些刚毕业 ...

好眼光,好眼力。

出0入0汤圆

发表于 2014-5-30 23:34:25 | 显示全部楼层
顶鸿哥,原来看过你的博客···

出0入0汤圆

发表于 2014-5-31 22:51:23 | 显示全部楼层
顶顶更健康

出0入0汤圆

发表于 2014-6-1 08:16:25 来自手机 | 显示全部楼层
鸿哥,看了你的18b20并没有改善动态扫描效果的方法,而且按键的实时性也变差。毕竟这是高速与低速的配合,就算采用静态扫描有时候还是要关中断,长时间关中断实时性也会变差。不知道鸿哥有什么好的办法解决。真心谢谢。

出0入0汤圆

 楼主| 发表于 2014-6-1 14:15:57 | 显示全部楼层
xrydt 发表于 2014-6-1 08:16
鸿哥,看了你的18b20并没有改善动态扫描效果的方法,而且按键的实时性也变差。毕竟这是高速与低速的配合, ...

在实际的项目中,如果采用静态显示方案,你不用担心影响按键的实时性,根据客户的使用习惯,我可以多加一个标志变量,用来识别客户正在设置参数和设置完毕参数。当发现客户正在设置参数的时候,我们干脆停止采集温度数据,当客户设置完数据,我们才开始采集温度数据。我测试过现在我发表的程序,我觉得当前按键实时性还是可以接受的。

出0入0汤圆

发表于 2014-6-1 14:54:54 来自手机 | 显示全部楼层
感谢洪哥第一时间回复。

出0入0汤圆

发表于 2014-6-1 21:03:35 | 显示全部楼层
Well done.

出0入0汤圆

发表于 2014-6-1 22:23:06 | 显示全部楼层
感谢楼主的分享

出0入0汤圆

发表于 2014-6-5 11:43:26 | 显示全部楼层
支持楼主 会继续关注下去

出0入0汤圆

发表于 2014-6-5 12:45:23 来自手机 | 显示全部楼层
打印网页,认真学习了半个月了,收获多多,鸿哥加油!

出0入0汤圆

 楼主| 发表于 2014-6-5 15:37:31 | 显示全部楼层
第五十节:利用ADC0832采集电压信号,用平均法和区间法进行软件滤波处理。

开场白:
ADC0832是一款常用的8位AD采样芯片,通过它可以把外部的模拟电压信号转换成数字信号,然后给单片机进行换算,显示等处理。
这一节要教会大家五个知识点:
第一个:分辨率的算法。有些书上说8位AD最高分辩可达到256级(0xff+1),当输入电压是0---5V时,电压精度为19.53mV(5000mV除以256),我认为这种说法是错误的。8位AD的最高分辨率应该是255级(0xff),当输入电压是0---5V时,电压精度为19.61mV(5000mV除以255)。
第二个:用求平均值的滤波法,可以使AD采样的数据更加圆滑,去除小毛刺。
第三个:用区间滤波法,在一些干扰很大的场合,可以避免末尾小数点的数据频繁跳动。
第四个:如何使系统可以采集到更高的电压。由于ADC0832直接采集的电压最大不能超过5V,如果要采集的最大电压是25V该怎么办?我们只要在外部多增加1个10K的电阻和1个40K的电阻组成分压电路,把25V分压成5V,然后再让ADC0832采样,这时采样到的数据只要乘以5的系数,就可以得到超过5V的实际电压。选择分压电阻时,阻值尽量不要太小,一般要10K级别以上,阻值大一点,对被采样的系统干扰影响就越小。
第五个:如何有效保护AD通道口。我在一些电压不稳定的工控场合,一般是在AD通道口对负极反接一个瞬变二极管SA5.0A。当电压超过5V时,瞬变二极管会导通吸收掉多余的能量,把电压降下来,避免AD通道口烧坏。

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

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

(2)实现功能:
     本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过平均法,区间法滤波的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象

系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。

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

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

  3. void initial_myself(void);   
  4. void initial_peripheral(void);
  5. void delay_short(unsigned int uiDelayShort);
  6. void delay_long(unsigned int uiDelaylong);


  7. //驱动数码管的74HC595
  8. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  9. void display_drive(void); //显示数码管字模的驱动函数
  10. void display_service(void); //显示的窗口菜单服务程序
  11. //驱动LED的74HC595
  12. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

  14. void ad_sampling_service(void); //AD采样与处理的服务程序


  15. sbit led_dr=P3^5;  //LED灯
  16. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口



  17. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  18. sbit dig_hc595_st_dr=P2^1;  
  19. sbit dig_hc595_ds_dr=P2^2;  
  20. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  21. sbit hc595_st_dr=P2^4;  
  22. sbit hc595_ds_dr=P2^5;  


  23. sbit adc0832_clk_dr     = P1^2;  // 定义adc0832的引脚
  24. sbit adc0832_cs_dr      = P1^0;
  25. sbit adc0832_data_sr_dr = P1^1;


  26. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  27. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  28. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  29. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  30. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  31. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  32. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  33. unsigned char ucDigShow1;  //第1位数码管要显示的内容

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


  44. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  45. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  46. unsigned char ucTemp1=0;  //中间过渡变量
  47. unsigned char ucTemp2=0;  //中间过渡变量
  48. unsigned char ucTemp3=0;  //中间过渡变量
  49. unsigned char ucTemp4=0;  //中间过渡变量
  50. unsigned char ucTemp5=0;  //中间过渡变量
  51. unsigned char ucTemp6=0;  //中间过渡变量
  52. unsigned char ucTemp7=0;  //中间过渡变量
  53. unsigned char ucTemp8=0;  //中间过渡变量

  54. unsigned char ucAD=0;   //AD值
  55. unsigned char ucCheckAD=0; //用来做校验对比的AD值


  56. unsigned long ulTemp=0;  //参与换算的中间变量
  57. unsigned long ulTempFilterV=0; //参与换算的中间变量
  58. unsigned long ulBackupFilterV=5000;  //备份最新采样数据的中间变量
  59. unsigned char ucSamplingCnt=0; //统计采样的次数  本程序采样8次后求平均值

  60. unsigned long ulV=0; //未经滤波处理的实时电压值
  61. unsigned long ulFilterV=0; //经过滤波后的实时电压值


  62. //根据原理图得出的共阴数码管字模表
  63. code unsigned char dig_table[]=
  64. {
  65. 0x3f,  //0       序号0
  66. 0x06,  //1       序号1
  67. 0x5b,  //2       序号2
  68. 0x4f,  //3       序号3
  69. 0x66,  //4       序号4
  70. 0x6d,  //5       序号5
  71. 0x7d,  //6       序号6
  72. 0x07,  //7       序号7
  73. 0x7f,  //8       序号8
  74. 0x6f,  //9       序号9
  75. 0x00,  //无      序号10
  76. 0x40,  //-       序号11
  77. 0x73,  //P       序号12
  78. };
  79. void main()
  80.   {
  81.    initial_myself();  
  82.    delay_long(100);   
  83.    initial_peripheral();
  84.    while(1)  
  85.    {
  86.       ad_sampling_service(); //AD采样与处理的服务程序
  87.       display_service(); //显示的窗口菜单服务程序
  88.    }
  89. }

  90. void ad_sampling_service(void) //AD采样与处理的服务程序
  91. {
  92.     unsigned char i;

  93.     ucAD=0;   //AD值
  94.     ucCheckAD=0; //用来做校验对比的AD值


  95.     /* 片选信号置为低电平 */
  96.     adc0832_cs_dr = 0;

  97.         /* 第一个脉冲,开始位 */
  98.         adc0832_data_sr_dr = 1;
  99.         adc0832_clk_dr  = 0;
  100.     delay_short(1);
  101.         adc0832_clk_dr  = 1;

  102.         /* 第二个脉冲,选择通道 */
  103.         adc0832_data_sr_dr = 1;
  104.         adc0832_clk_dr  = 0;
  105.         adc0832_clk_dr  = 1;

  106.         /* 第三个脉冲,选择通道 */
  107.         adc0832_data_sr_dr = 0;
  108.         adc0832_clk_dr  = 0;
  109.         adc0832_clk_dr  = 1;

  110.     /* 数据线输出高电平 */
  111.         adc0832_data_sr_dr = 1;
  112.     delay_short(2);

  113.         /* 第一个下降沿 */
  114.         adc0832_clk_dr  = 1;
  115.         adc0832_clk_dr  = 0;
  116.     delay_short(1);


  117.         /* AD值开始送出 */
  118.         for (i = 0; i < 8; i++)
  119.         {
  120.         ucAD <<= 1;
  121.                 adc0832_clk_dr = 1;
  122.                 adc0832_clk_dr = 0;
  123.                 if (adc0832_data_sr_dr==1)
  124.                 {
  125.             ucAD |= 0x01;
  126.                 }
  127.         }

  128.         /* 用于校验的AD值开始送出 */
  129.         for (i = 0; i < 8; i++)
  130.         {
  131.         ucCheckAD >>= 1;
  132.                 if (adc0832_data_sr_dr==1)
  133.                 {
  134.            ucCheckAD |= 0x80;
  135.                 }
  136.                 adc0832_clk_dr = 1;
  137.                 adc0832_clk_dr = 0;
  138.         }
  139.        
  140.         /* 片选信号置为高电平 */
  141.         adc0832_cs_dr = 1;


  142.         if(ucCheckAD==ucAD)  //检验相等
  143.         {
  144.        
  145.             ulTemp=0;  //把char类型数据赋值给long类型数据之前,必须先清零
  146.         ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型

  147. /* 注释一:
  148. * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
  149. * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
  150. * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
  151. */
  152.         ulTemp=5000*ulTemp/255;  //进行电压换算

  153.         ulV=ulTemp; //得到未经滤波处理的实时电压值
  154.         ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压


  155.                 ulTempFilterV=ulTempFilterV+ulTemp;  //累加8次后求平均值
  156.         ucSamplingCnt++;  //统计已经采样累计的次数
  157.                 if(ucSamplingCnt>=8)
  158.                 {

  159. /* 注释二:
  160. * 求平均值滤波法,为了得到的数据更加圆滑,去除小毛刺。
  161. * 向右边移动3位相当于除以8。
  162. */

  163.                      ulTempFilterV=ulTempFilterV>>3; //求平均值滤波法


  164. /* 注释三:
  165. * 以下区间滤波法,为了避免末尾小数点的数据频繁跳动。
  166. * 这里的20用于区间滤波法的正负偏差,这里的20代表0.020V。
  167. * 意思是只要最近采集到的数据在正负0.020V偏差范围内,就不更新。
  168. */
  169.                     if(ulBackupFilterV>=20)  //最近备份的上一次数据大于等于0.02V的情况下
  170.                     {
  171.                        if(ulTempFilterV<(ulBackupFilterV-20)||ulTempFilterV>(ulBackupFilterV+20)) //在正负0.020V偏差范围外,更新
  172.                        {
  173.                            ulBackupFilterV=ulTempFilterV;  //备份最新采样的数据,方便下一次对比判断

  174.                    ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  175.                    ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
  176.                               }
  177.                     }
  178.                     else   //最近备份的上一次数据小于0.02V的情况下
  179.                     {
  180.                        if(ulTempFilterV>(ulBackupFilterV+20))  //在正0.020V偏差范围外,更新
  181.                        {
  182.                            ulBackupFilterV=ulTempFilterV;  //备份最新采样的数据,方便下一次对比判断

  183.                    ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  184.                    ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压
  185.                            }
  186.                   
  187.                     }


  188.                     ucSamplingCnt=0;  //清零,为下一轮采样滤波作准备。
  189.                     ulTempFilterV=0;
  190.                 }
  191.        
  192.         }

  193. }

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

  196.                         if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
  197.                         {
  198.                            ucWd1Part1Update=0;

  199.                ucTemp8=ulV%10000/1000;  //显示电压值个位
  200.                ucTemp7=ulV%1000/100;    //显示电压值小数点后第1位
  201.                ucTemp6=ulV%100/10;      //显示电压值小数点后第2位
  202.                ucTemp5=ulV%10;          //显示电压值小数点后第3位


  203.                ucDigShow8=ucTemp8; //数码管显示实际内容
  204.                ucDigShow7=ucTemp7;
  205.                ucDigShow6=ucTemp6;
  206.                ucDigShow5=ucTemp5;
  207.                         }


  208.                         if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
  209.                         {
  210.                              ucWd1Part2Update=0;

  211.                ucTemp4=ulFilterV%10000/1000;  //显示电压值个位
  212.                ucTemp3=ulFilterV%1000/100;    //显示电压值小数点后第1位
  213.                ucTemp2=ulFilterV%100/10;      //显示电压值小数点后第2位
  214.                ucTemp1=ulFilterV%10;          //显示电压值小数点后第3位


  215.                ucDigShow4=ucTemp4; //数码管显示实际内容
  216.                ucDigShow3=ucTemp3;
  217.                ucDigShow2=ucTemp2;
  218.                ucDigShow1=ucTemp1;
  219.                         }


  220. }



  221. void display_drive(void)  
  222. {
  223.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  224.    switch(ucDisplayDriveStep)
  225.    {
  226.       case 1:  //显示第1位
  227.            ucDigShowTemp=dig_table[ucDigShow1];
  228.                    if(ucDigDot1==1)
  229.                    {
  230.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  231.                    }
  232.            dig_hc595_drive(ucDigShowTemp,0xfe);
  233.                break;
  234.       case 2:  //显示第2位
  235.            ucDigShowTemp=dig_table[ucDigShow2];
  236.                    if(ucDigDot2==1)
  237.                    {
  238.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  239.                    }
  240.            dig_hc595_drive(ucDigShowTemp,0xfd);
  241.                break;
  242.       case 3:  //显示第3位
  243.            ucDigShowTemp=dig_table[ucDigShow3];
  244.                    if(ucDigDot3==1)
  245.                    {
  246.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  247.                    }
  248.            dig_hc595_drive(ucDigShowTemp,0xfb);
  249.                break;
  250.       case 4:  //显示第4位
  251.            ucDigShowTemp=dig_table[ucDigShow4];
  252.                    if(ucDigDot4==1)
  253.                    {
  254.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  255.                    }
  256.            dig_hc595_drive(ucDigShowTemp,0xf7);
  257.                break;
  258.       case 5:  //显示第5位
  259.            ucDigShowTemp=dig_table[ucDigShow5];
  260.                    if(ucDigDot5==1)
  261.                    {
  262.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  263.                    }
  264.            dig_hc595_drive(ucDigShowTemp,0xef);
  265.                break;
  266.       case 6:  //显示第6位
  267.            ucDigShowTemp=dig_table[ucDigShow6];
  268.                    if(ucDigDot6==1)
  269.                    {
  270.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  271.                    }
  272.            dig_hc595_drive(ucDigShowTemp,0xdf);
  273.                break;
  274.       case 7:  //显示第7位
  275.            ucDigShowTemp=dig_table[ucDigShow7];
  276.                    if(ucDigDot7==1)
  277.                    {
  278.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  279.            }
  280.            dig_hc595_drive(ucDigShowTemp,0xbf);
  281.                break;
  282.       case 8:  //显示第8位
  283.            ucDigShowTemp=dig_table[ucDigShow8];
  284.                    if(ucDigDot8==1)
  285.                    {
  286.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  287.                    }
  288.            dig_hc595_drive(ucDigShowTemp,0x7f);
  289.                break;
  290.    }
  291.    ucDisplayDriveStep++;
  292.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  293.    {
  294.      ucDisplayDriveStep=1;
  295.    }

  296. }

  297. //数码管的74HC595驱动函数
  298. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  299. {
  300.    unsigned char i;
  301.    unsigned char ucTempData;
  302.    dig_hc595_sh_dr=0;
  303.    dig_hc595_st_dr=0;
  304.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  305.    for(i=0;i<8;i++)
  306.    {
  307.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  308.          else dig_hc595_ds_dr=0;
  309.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  310.          delay_short(1);
  311.          dig_hc595_sh_dr=1;
  312.          delay_short(1);
  313.          ucTempData=ucTempData<<1;
  314.    }
  315.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  316.    for(i=0;i<8;i++)
  317.    {
  318.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  319.          else dig_hc595_ds_dr=0;
  320.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  321.          delay_short(1);
  322.          dig_hc595_sh_dr=1;
  323.          delay_short(1);
  324.          ucTempData=ucTempData<<1;
  325.    }
  326.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  327.    delay_short(1);
  328.    dig_hc595_st_dr=1;
  329.    delay_short(1);
  330.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  331.    dig_hc595_st_dr=0;
  332.    dig_hc595_ds_dr=0;
  333. }

  334. //LED灯的74HC595驱动函数
  335. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  336. {
  337.    unsigned char i;
  338.    unsigned char ucTempData;
  339.    hc595_sh_dr=0;
  340.    hc595_st_dr=0;
  341.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  342.    for(i=0;i<8;i++)
  343.    {
  344.          if(ucTempData>=0x80)hc595_ds_dr=1;
  345.          else hc595_ds_dr=0;
  346.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  347.          delay_short(1);
  348.          hc595_sh_dr=1;
  349.          delay_short(1);
  350.          ucTempData=ucTempData<<1;
  351.    }
  352.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  353.    for(i=0;i<8;i++)
  354.    {
  355.          if(ucTempData>=0x80)hc595_ds_dr=1;
  356.          else hc595_ds_dr=0;
  357.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  358.          delay_short(1);
  359.          hc595_sh_dr=1;
  360.          delay_short(1);
  361.          ucTempData=ucTempData<<1;
  362.    }
  363.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  364.    delay_short(1);
  365.    hc595_st_dr=1;
  366.    delay_short(1);
  367.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  368.    hc595_st_dr=0;
  369.    hc595_ds_dr=0;
  370. }


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


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

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

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

  388. void delay_long(unsigned int uiDelayLong)
  389. {
  390.    unsigned int i;
  391.    unsigned int j;
  392.    for(i=0;i<uiDelayLong;i++)
  393.    {
  394.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  395.           {
  396.              ; //一个分号相当于执行一条空语句
  397.           }
  398.    }
  399. }


  400. void initial_myself(void)  //第一区 初始化单片机
  401. {
  402.   led_dr=0;//LED灯默认关闭
  403.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  404.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  405.   TMOD=0x01;  //设置定时器0为工作方式1
  406.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  407.   TL0=0x0b;

  408. }
  409. void initial_peripheral(void) //第二区 初始化外围
  410. {

  411.    ucDigDot8=1;   //显示未经过滤波电压的小数点
  412.    ucDigDot7=0;  
  413.    ucDigDot6=0;
  414.    ucDigDot5=0;  
  415.    ucDigDot4=1;  //显示经过滤波后电压的小数点
  416.    ucDigDot3=0;  
  417.    ucDigDot2=0;
  418.    ucDigDot1=0;

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

  422. }
复制代码

总结陈词:
这节用区间滤波法虽然可以解决小数点后面的数据出现频繁跳动的现象,但是也存在一个小问题,就是精度受到了影响,比如我们设置的正负偏差是0.02V,那就意味着系统存在0.02V的误差。有没有更好的办法解决这个问题?如果系统的末尾数据一直不断处于频繁跳动中,那么只能牺牲一点精度,我认为用区间法已经是最好的解决办法了,但是经过本次实验,我观察到未经过滤波处理的数据只是偶尔跳动,并非频繁跳动,所以下一节我会给大家介绍一种不用牺牲精度,又可以很好滤波的方法。欲知详情,请听下回分解-----利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。

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

出0入0汤圆

发表于 2014-6-6 22:57:58 | 显示全部楼层
正要用到AD转换,楼主加油呀。快点更新吧。看一下你所说的合不合我用。一直都有关注楼主的更新。楼主是好人。

出0入0汤圆

发表于 2014-6-6 23:02:29 | 显示全部楼层
xinwu 发表于 2014-5-20 21:30
前段时间打印的还没看完,况且鸿哥最近才更新了两节,过段时间在这整理一下。 ...

兄弟该整理了。群主发了这么多了。

出0入0汤圆

发表于 2014-6-9 23:57:01 | 显示全部楼层
楼主请问有试过AD过采样提高AD精确度吗,现在用的AD精确度太低了。

出0入0汤圆

 楼主| 发表于 2014-6-10 12:02:26 | 显示全部楼层
jeoo8888 发表于 2014-6-9 23:57
楼主请问有试过AD过采样提高AD精确度吗,现在用的AD精确度太低了。

选位数更高的AD芯片不就行了吗?

出0入0汤圆

发表于 2014-6-10 12:21:38 | 显示全部楼层
收藏了~~谢谢鸿哥~

出0入0汤圆

发表于 2014-6-11 00:02:25 | 显示全部楼层
吴坚鸿 发表于 2014-6-10 12:02
选位数更高的AD芯片不就行了吗?

如在硬件不用改动,AD提高一到两位这样就不用增加成本了呀。
      在网上看到有人说过采样可以增加AD位数,但是看了不是很明白。

出0入0汤圆

发表于 2014-6-11 00:26:21 | 显示全部楼层
好贴收藏,谢楼主

出0入0汤圆

发表于 2014-6-12 08:58:53 | 显示全部楼层
楼主,可否将您实验开发板的原理图分享一下?谢谢

出0入0汤圆

 楼主| 发表于 2014-6-12 11:33:27 | 显示全部楼层
jeoo8888 发表于 2014-6-11 00:02
如在硬件不用改动,AD提高一到两位这样就不用增加成本了呀。
      在网上看到有人说过采样可以增加AD位 ...

世上有这么厉害的秘籍?我也是第一次听说。

出0入0汤圆

 楼主| 发表于 2014-6-12 11:34:20 | 显示全部楼层
mqlltxdl 发表于 2014-6-12 08:58
楼主,可否将您实验开发板的原理图分享一下?谢谢

这个就是你所需要的原理图。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?注册

x

出0入0汤圆

发表于 2014-6-12 13:42:18 | 显示全部楼层
吴坚鸿 发表于 2014-6-12 11:34
这个就是你所需要的原理图。

谢谢,楼主。您的QQ是多少的?以后想要多多向您学习,请教。

出0入0汤圆

 楼主| 发表于 2014-6-12 15:07:54 | 显示全部楼层
mqlltxdl 发表于 2014-6-12 13:42
谢谢,楼主。您的QQ是多少的?以后想要多多向您学习,请教。

有什么问题可以在这个帖子给我留言,我平时很少上QQ的。

出0入0汤圆

发表于 2014-6-12 15:14:08 | 显示全部楼层

出0入0汤圆

发表于 2014-6-14 00:36:23 | 显示全部楼层
鸿哥你好,我想问下,我听前辈们说while(1)里面是不能用delay()这种死循环的,鸿哥的24c02 IIC程序中用了delay()没事的吗?

出0入0汤圆

发表于 2014-6-15 11:17:30 | 显示全部楼层
循环延时用得少,一般都用中断与计数器吧

出0入0汤圆

 楼主| 发表于 2014-6-15 11:35:43 | 显示全部楼层
llqy 发表于 2014-6-14 00:36
鸿哥你好,我想问下,我听前辈们说while(1)里面是不能用delay()这种死循环的,鸿哥的24c02 IIC程序中用了 ...

没事的。理由有两个:
(1)在24c02 IIC程序中,用的delay()时间非常短,是用来模拟IIC通讯的,这种驱动程序必须用delay(),用其他任何延时方式都不好使。
(2)虽然24c02 IIC程序中包含了delay(),但是系统不是时时刻刻在执行24c02 IIC程序,只是有新数据更新的时候才会偶尔执行一次24c02 IIC程序。

出0入0汤圆

 楼主| 发表于 2014-6-15 11:39:00 | 显示全部楼层
第五十一节:利用ADC0832采集电压信号,用连续N次一致性的方法进行滤波处理。

开场白:
连续判断N次一致性的滤波法,是为了避免末尾小数点的数据偶尔跳动。这种滤波方法的原理跟我在按键扫描中去抖动的原理是一模一样的,被我频繁地应用在大量的工控项目中。
这一节要教会大家一个知识点:连续判断N次一致性的滤波法。
具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。

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

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

(2)实现功能:
     本程序有2个局部显示。
第1个局部是第8,7,6,5位数码管,显示没有经过滤波处理的实际电压值。此时能观察到未经滤波的数据不太稳定,末尾小数点数据会有跳动的现象
第2个局部是第4,3,2,1位数码管,显示经过特定算法滤波后的实际电压值。此时能观察到经过滤波后的数据很稳定,没有跳动的现象。而且显示的电压值跟未经过滤波的电压值几乎是完全一致,不会出现上一节用区间滤波法所留下的0.02V误差问题。

系统保留3位小数点。手动调节可调电阻时,可以看到显示的数据在变化。

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

  2. #define const_N   8  //连续判断N次一致性滤波方法中,N的取值
  3. #define const_voice_short  40   //蜂鸣器短叫的持续时间

  4. void initial_myself(void);   
  5. void initial_peripheral(void);
  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(void); //显示数码管字模的驱动函数
  11. void display_service(void); //显示的窗口菜单服务程序
  12. //驱动LED的74HC595
  13. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

  15. void ad_sampling_service(void); //AD采样与处理的服务程序


  16. sbit led_dr=P3^5;  //LED灯
  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. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  22. sbit hc595_st_dr=P2^4;  
  23. sbit hc595_ds_dr=P2^5;  


  24. sbit adc0832_clk_dr     = P1^2;  // 定义adc0832的引脚
  25. sbit adc0832_cs_dr      = P1^0;
  26. sbit adc0832_data_sr_dr = P1^1;


  27. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  28. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  29. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  30. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  31. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  32. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  33. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  34. unsigned char ucDigShow1;  //第1位数码管要显示的内容

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


  45. unsigned char ucWd1Part1Update=1;  //在窗口1中,局部1的更新显示标志
  46. unsigned char ucWd1Part2Update=1; //在窗口1中,局部2的更新显示标志


  47. unsigned char ucTemp1=0;  //中间过渡变量
  48. unsigned char ucTemp2=0;  //中间过渡变量
  49. unsigned char ucTemp3=0;  //中间过渡变量
  50. unsigned char ucTemp4=0;  //中间过渡变量
  51. unsigned char ucTemp5=0;  //中间过渡变量
  52. unsigned char ucTemp6=0;  //中间过渡变量
  53. unsigned char ucTemp7=0;  //中间过渡变量
  54. unsigned char ucTemp8=0;  //中间过渡变量

  55. unsigned char ucAD=0;   //AD值
  56. unsigned char ucCheckAD=0; //用来做校验对比的AD值


  57. unsigned long ulTemp=0;  //参与换算的中间变量
  58. unsigned long ulTempFilterV=0; //参与换算的中间变量
  59. unsigned long ulBackupFilterV=5000;  //备份最新采样数据的中间变量
  60. unsigned char ucSamplingCnt=0; //记录连续N次采样的计数器

  61. unsigned long ulV=0; //未经滤波处理的实时电压值
  62. unsigned long ulFilterV=0; //经过滤波后的实时电压值


  63. //根据原理图得出的共阴数码管字模表
  64. code unsigned char dig_table[]=
  65. {
  66. 0x3f,  //0       序号0
  67. 0x06,  //1       序号1
  68. 0x5b,  //2       序号2
  69. 0x4f,  //3       序号3
  70. 0x66,  //4       序号4
  71. 0x6d,  //5       序号5
  72. 0x7d,  //6       序号6
  73. 0x07,  //7       序号7
  74. 0x7f,  //8       序号8
  75. 0x6f,  //9       序号9
  76. 0x00,  //无      序号10
  77. 0x40,  //-       序号11
  78. 0x73,  //P       序号12
  79. };
  80. void main()
  81.   {
  82.    initial_myself();  
  83.    delay_long(100);   
  84.    initial_peripheral();
  85.    while(1)  
  86.    {
  87.       ad_sampling_service(); //AD采样与处理的服务程序
  88.       display_service(); //显示的窗口菜单服务程序
  89.    }
  90. }

  91. void ad_sampling_service(void) //AD采样与处理的服务程序
  92. {
  93.     unsigned char i;

  94.     ucAD=0;   //AD值
  95.     ucCheckAD=0; //用来做校验对比的AD值


  96.     /* 片选信号置为低电平 */
  97.     adc0832_cs_dr = 0;

  98.         /* 第一个脉冲,开始位 */
  99.         adc0832_data_sr_dr = 1;
  100.         adc0832_clk_dr  = 0;
  101.     delay_short(1);
  102.         adc0832_clk_dr  = 1;

  103.         /* 第二个脉冲,选择通道 */
  104.         adc0832_data_sr_dr = 1;
  105.         adc0832_clk_dr  = 0;
  106.         adc0832_clk_dr  = 1;

  107.         /* 第三个脉冲,选择通道 */
  108.         adc0832_data_sr_dr = 0;
  109.         adc0832_clk_dr  = 0;
  110.         adc0832_clk_dr  = 1;

  111.     /* 数据线输出高电平 */
  112.         adc0832_data_sr_dr = 1;
  113.     delay_short(2);

  114.         /* 第一个下降沿 */
  115.         adc0832_clk_dr  = 1;
  116.         adc0832_clk_dr  = 0;
  117.     delay_short(1);


  118.         /* AD值开始送出 */
  119.         for (i = 0; i < 8; i++)
  120.         {
  121.         ucAD <<= 1;
  122.                 adc0832_clk_dr = 1;
  123.                 adc0832_clk_dr = 0;
  124.                 if (adc0832_data_sr_dr==1)
  125.                 {
  126.             ucAD |= 0x01;
  127.                 }
  128.         }

  129.         /* 用于校验的AD值开始送出 */
  130.         for (i = 0; i < 8; i++)
  131.         {
  132.         ucCheckAD >>= 1;
  133.                 if (adc0832_data_sr_dr==1)
  134.                 {
  135.            ucCheckAD |= 0x80;
  136.                 }
  137.                 adc0832_clk_dr = 1;
  138.                 adc0832_clk_dr = 0;
  139.         }
  140.         
  141.         /* 片选信号置为高电平 */
  142.         adc0832_cs_dr = 1;


  143.         if(ucCheckAD==ucAD)  //检验相等
  144.         {
  145.         
  146.             ulTemp=0;  //把char类型数据赋值给long类型数据之前,必须先清零
  147.             ulTemp=ucAD; //把char类型数据赋值给long类型数据,参与乘除法运算的数据,为了避免运算结果溢出,我都用long类型

  148. /* 注释一:
  149. * 因为保留3为小数点,这里的5000代表5.000V。ulTemp/255代表分辨率.
  150. * 有些书上说8位AD最高分辩可达到256级(0xff+1),我认为这种说法是错误的。
  151. * 8位AD最高分辩应该是255级(0xff),所以这里除以255,而不是256.
  152. */
  153.             ulTemp=5000*ulTemp/255;  //进行电压换算
  154.             ulV=ulTemp; //得到未经滤波处理的实时电压值
  155.             ucWd1Part1Update=1; //局部更新显示未经滤波处理的电压


  156. /* 注释二:
  157. * 以下连续判断N次一致性的滤波法,为了避免末尾小数点的数据偶尔跳动。
  158. * 这种滤波方法的原理跟我在按键扫描中的去抖动原理是一模一样的,被我频繁
  159. * 地应用在大量的工控项目中。
  160. * 具体原理:当某个采样变量发生变化时,有两种可能,一种可能是外界的一个瞬间干扰。
  161. * 另一种可能是变量确实发生变化。为了有效去除干扰,当发现变量有变化时,
  162. * 我会连续采集N次,如果连续N次都是一致的结果,我才认为不是干扰。如果中间
  163. * 只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
  164. *
  165. */
  166.                       if(ulTempFilterV!=ulTemp) //发现变量有变化
  167.                      {
  168.                         ucSamplingCnt++;    //计数器累加
  169.                               if(ucSamplingCnt>const_N)  //如果连续N次都是一致的,则认为不是干扰。确实有数据需要更新显示。这里的const_N取值是8
  170.                             {
  171.                                 ucSamplingCnt=0;

  172.                                 ulTempFilterV=ulTemp;   //及时保存更新了的数据,方便下一次有新数据对比做准备

  173.                     ulFilterV=ulTempFilterV; //得到经过滤波处理的实时电压值
  174.                     ucWd1Part2Update=1; //局部更新显示经过滤波处理的电压                         
  175.                             }
  176.                        }
  177.                     else
  178.                     {
  179.                          ucSamplingCnt=0;  //只要出现一次不一致,我会马上把计数器清零,这一步是精华,很关键。
  180.                     }



  181.         
  182.         }

  183. }

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

  186.                         if(ucWd1Part1Update==1)//未经滤波处理的实时电压更新显示
  187.                         {
  188.                            ucWd1Part1Update=0;

  189.                ucTemp8=ulV%10000/1000;  //显示电压值个位
  190.                ucTemp7=ulV%1000/100;    //显示电压值小数点后第1位
  191.                ucTemp6=ulV%100/10;      //显示电压值小数点后第2位
  192.                ucTemp5=ulV%10;          //显示电压值小数点后第3位


  193.                ucDigShow8=ucTemp8; //数码管显示实际内容
  194.                ucDigShow7=ucTemp7;
  195.                ucDigShow6=ucTemp6;
  196.                ucDigShow5=ucTemp5;
  197.                         }


  198.                         if(ucWd1Part2Update==1)//经过滤波处理后的实时电压更新显示
  199.                         {
  200.                              ucWd1Part2Update=0;

  201.                ucTemp4=ulFilterV%10000/1000;  //显示电压值个位
  202.                ucTemp3=ulFilterV%1000/100;    //显示电压值小数点后第1位
  203.                ucTemp2=ulFilterV%100/10;      //显示电压值小数点后第2位
  204.                ucTemp1=ulFilterV%10;          //显示电压值小数点后第3位


  205.                ucDigShow4=ucTemp4; //数码管显示实际内容
  206.                ucDigShow3=ucTemp3;
  207.                ucDigShow2=ucTemp2;
  208.                ucDigShow1=ucTemp1;
  209.                         }


  210. }



  211. void display_drive(void)  
  212. {
  213.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  214.    switch(ucDisplayDriveStep)
  215.    {
  216.       case 1:  //显示第1位
  217.            ucDigShowTemp=dig_table[ucDigShow1];
  218.                    if(ucDigDot1==1)
  219.                    {
  220.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  221.                    }
  222.            dig_hc595_drive(ucDigShowTemp,0xfe);
  223.                break;
  224.       case 2:  //显示第2位
  225.            ucDigShowTemp=dig_table[ucDigShow2];
  226.                    if(ucDigDot2==1)
  227.                    {
  228.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  229.                    }
  230.            dig_hc595_drive(ucDigShowTemp,0xfd);
  231.                break;
  232.       case 3:  //显示第3位
  233.            ucDigShowTemp=dig_table[ucDigShow3];
  234.                    if(ucDigDot3==1)
  235.                    {
  236.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  237.                    }
  238.            dig_hc595_drive(ucDigShowTemp,0xfb);
  239.                break;
  240.       case 4:  //显示第4位
  241.            ucDigShowTemp=dig_table[ucDigShow4];
  242.                    if(ucDigDot4==1)
  243.                    {
  244.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  245.                    }
  246.            dig_hc595_drive(ucDigShowTemp,0xf7);
  247.                break;
  248.       case 5:  //显示第5位
  249.            ucDigShowTemp=dig_table[ucDigShow5];
  250.                    if(ucDigDot5==1)
  251.                    {
  252.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  253.                    }
  254.            dig_hc595_drive(ucDigShowTemp,0xef);
  255.                break;
  256.       case 6:  //显示第6位
  257.            ucDigShowTemp=dig_table[ucDigShow6];
  258.                    if(ucDigDot6==1)
  259.                    {
  260.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  261.                    }
  262.            dig_hc595_drive(ucDigShowTemp,0xdf);
  263.                break;
  264.       case 7:  //显示第7位
  265.            ucDigShowTemp=dig_table[ucDigShow7];
  266.                    if(ucDigDot7==1)
  267.                    {
  268.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  269.            }
  270.            dig_hc595_drive(ucDigShowTemp,0xbf);
  271.                break;
  272.       case 8:  //显示第8位
  273.            ucDigShowTemp=dig_table[ucDigShow8];
  274.                    if(ucDigDot8==1)
  275.                    {
  276.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  277.                    }
  278.            dig_hc595_drive(ucDigShowTemp,0x7f);
  279.                break;
  280.    }
  281.    ucDisplayDriveStep++;
  282.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  283.    {
  284.      ucDisplayDriveStep=1;
  285.    }

  286. }

  287. //数码管的74HC595驱动函数
  288. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  289. {
  290.    unsigned char i;
  291.    unsigned char ucTempData;
  292.    dig_hc595_sh_dr=0;
  293.    dig_hc595_st_dr=0;
  294.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  295.    for(i=0;i<8;i++)
  296.    {
  297.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  298.          else dig_hc595_ds_dr=0;
  299.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  300.          delay_short(1);
  301.          dig_hc595_sh_dr=1;
  302.          delay_short(1);
  303.          ucTempData=ucTempData<<1;
  304.    }
  305.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  306.    for(i=0;i<8;i++)
  307.    {
  308.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  309.          else dig_hc595_ds_dr=0;
  310.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  311.          delay_short(1);
  312.          dig_hc595_sh_dr=1;
  313.          delay_short(1);
  314.          ucTempData=ucTempData<<1;
  315.    }
  316.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  317.    delay_short(1);
  318.    dig_hc595_st_dr=1;
  319.    delay_short(1);
  320.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  321.    dig_hc595_st_dr=0;
  322.    dig_hc595_ds_dr=0;
  323. }

  324. //LED灯的74HC595驱动函数
  325. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  326. {
  327.    unsigned char i;
  328.    unsigned char ucTempData;
  329.    hc595_sh_dr=0;
  330.    hc595_st_dr=0;
  331.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  332.    for(i=0;i<8;i++)
  333.    {
  334.          if(ucTempData>=0x80)hc595_ds_dr=1;
  335.          else hc595_ds_dr=0;
  336.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  337.          delay_short(1);
  338.          hc595_sh_dr=1;
  339.          delay_short(1);
  340.          ucTempData=ucTempData<<1;
  341.    }
  342.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  343.    for(i=0;i<8;i++)
  344.    {
  345.          if(ucTempData>=0x80)hc595_ds_dr=1;
  346.          else hc595_ds_dr=0;
  347.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  348.          delay_short(1);
  349.          hc595_sh_dr=1;
  350.          delay_short(1);
  351.          ucTempData=ucTempData<<1;
  352.    }
  353.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  354.    delay_short(1);
  355.    hc595_st_dr=1;
  356.    delay_short(1);
  357.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  358.    hc595_st_dr=0;
  359.    hc595_ds_dr=0;
  360. }


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


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

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

  370. void delay_short(unsigned int uiDelayShort)
  371. {
  372.    unsigned int i;  
  373.    for(i=0;i<uiDelayShort;i++)
  374.    {
  375.      ;   //一个分号相当于执行一条空语句
  376.    }
  377. }

  378. void delay_long(unsigned int uiDelayLong)
  379. {
  380.    unsigned int i;
  381.    unsigned int j;
  382.    for(i=0;i<uiDelayLong;i++)
  383.    {
  384.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  385.           {
  386.              ; //一个分号相当于执行一条空语句
  387.           }
  388.    }
  389. }


  390. void initial_myself(void)  //第一区 初始化单片机
  391. {
  392.   led_dr=0;//LED灯默认关闭
  393.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  394.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  395.   TMOD=0x01;  //设置定时器0为工作方式1
  396.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  397.   TL0=0x0b;

  398. }
  399. void initial_peripheral(void) //第二区 初始化外围
  400. {

  401.    ucDigDot8=1;   //显示未经过滤波电压的小数点
  402.    ucDigDot7=0;  
  403.    ucDigDot6=0;
  404.    ucDigDot5=0;  
  405.    ucDigDot4=1;  //显示经过滤波后电压的小数点
  406.    ucDigDot3=0;  
  407.    ucDigDot2=0;
  408.    ucDigDot1=0;

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

  412. }
复制代码

总结陈词:
在单片机AD采样的系统中,我常用的滤波方法有求平均值法,区间法,连续判断N次一致性这三种方法。读者可以根据不同的系统特点选择对应的滤波方法,有一些要求高的系统还可以把三种滤波方法混合在一起用。关于AD采样的知识到本节已经讲完,下一节会讲什么新内容呢?欲知详情,请听下回分解-----return语句鲜为人知的用法。

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

出0入0汤圆

发表于 2014-6-15 13:36:40 | 显示全部楼层
做个记号,文章有点长还没看完,楼主写得深入浅出,文章大爱啊

出0入0汤圆

发表于 2014-6-15 16:51:59 | 显示全部楼层
坐等鸿哥更新

出0入0汤圆

发表于 2014-6-15 19:16:09 | 显示全部楼层
吴坚鸿 发表于 2014-6-15 11:35
没事的。理由有两个:
(1)在24c02 IIC程序中,用的delay()时间非常短,是用来模拟IIC通讯的,这种驱动 ...

嗯,明白了。多谢鸿哥的回答!

出0入0汤圆

发表于 2014-6-15 22:18:27 | 显示全部楼层
刚好用的着,谢谢.

出0入0汤圆

发表于 2014-6-16 23:34:27 | 显示全部楼层
楼主,请教一个问题,单片机的GPIO口控制继电器闭合或者断开。之后如何判断继电器的状态呢?比如P1.0口输出高电平,这时候继电器闭合。如果这时候我想检测继电器是闭合的还是断开的,是要在后面加电路得到反馈信号给单片机检测吗?

出0入0汤圆

 楼主| 发表于 2014-6-17 00:06:04 | 显示全部楼层
mqlltxdl 发表于 2014-6-16 23:34
楼主,请教一个问题,单片机的GPIO口控制继电器闭合或者断开。之后如何判断继电器的状态呢?比如P1.0口输出 ...

是的,必须加电路才能检测到继电器的实际工作状态。我没做过这方面的电路,具体的电路要靠你自己根据系统的特点来设计。

出0入0汤圆

发表于 2014-6-17 09:06:49 | 显示全部楼层
感谢楼主热心贡献!有了你这个,那些只知道纸上谈兵的大学叫兽们该下岗了。

出0入0汤圆

发表于 2014-6-17 09:41:59 | 显示全部楼层
mark,搬个凳子慢慢看。
谢谢分享~

出0入0汤圆

发表于 2014-6-17 10:24:42 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 20:38
根据客户的需要吧。基本的按键程序框架就是这样写的。

楼主您好,长按和短按组合的时候,长按时会多触发一次短按,会不会造成影响?

出0入0汤圆

发表于 2014-6-17 10:30:20 | 显示全部楼层
不错,真的是好帖子,对初学者以及单片机嵌入式开发很受用啊

出0入0汤圆

 楼主| 发表于 2014-6-17 10:39:50 | 显示全部楼层
lyricpoem0726 发表于 2014-6-17 10:24
楼主您好,长按和短按组合的时候,长按时会多触发一次短按,会不会造成影响? ...

这个要看客户需求,有的项目有影响,有的没有影响。比如我最近正在做的一个项目就没有影响。

出0入0汤圆

发表于 2014-6-17 11:39:14 | 显示全部楼层
apolloalfred 发表于 2014-4-1 13:05
多谢分享,有学到东西,辛苦辛苦!

多谢你整理的资料,阿莫论坛雷锋多!

出0入0汤圆

发表于 2014-6-17 11:53:48 | 显示全部楼层
吴坚鸿 发表于 2014-6-17 10:39
这个要看客户需求,有的项目有影响,有的没有影响。比如我最近正在做的一个项目就没有影响。 ...

那有没有好的方法去除这种因为长按而多余产生的短按?

出0入0汤圆

 楼主| 发表于 2014-6-17 12:01:23 | 显示全部楼层
lyricpoem0726 发表于 2014-6-17 11:53
那有没有好的方法去除这种因为长按而多余产生的短按?


这个编程太简单了。你自己想想办法。我不多讲了。

出0入0汤圆

发表于 2014-6-17 22:10:05 | 显示全部楼层
还挺不错的.再次顶起来!

出60入0汤圆

发表于 2014-6-18 08:59:36 来自手机 | 显示全部楼层
佩服楼主,感谢分享

出0入0汤圆

发表于 2014-6-18 09:08:46 | 显示全部楼层
传道授业解惑也

出0入0汤圆

发表于 2014-6-18 15:04:10 | 显示全部楼层
现在才看到这个帖子,支持楼主这种分享精神!

出0入0汤圆

发表于 2014-6-19 18:02:18 | 显示全部楼层
mark,有空看看

出0入0汤圆

发表于 2014-6-19 18:41:04 | 显示全部楼层
哇,牛叉,关注!加油,步进电机这块很感兴趣,期待!

出0入0汤圆

发表于 2014-6-20 15:35:15 | 显示全部楼层
好贴,留着,慢慢学习

出0入0汤圆

发表于 2014-6-20 17:01:50 | 显示全部楼层
MARK  学习一下

出0入0汤圆

发表于 2014-6-20 22:01:40 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 12:40
我现在已经从打游击转为正规军了,在某科技园开了一家科技公司,接一些老外的开发和生产的单。
其实单干 ...

请问怎么接老外的开发和生产的单?

出0入0汤圆

 楼主| 发表于 2014-6-20 22:31:15 | 显示全部楼层
henai521 发表于 2014-6-20 22:01
请问怎么接老外的开发和生产的单?

我们有个股东一直以来都是做外贸的,所以我们接老外的单就自然而然了。目前还有几个外贸员是跟我们合作的,研发工程费直接给我们,后续批量生产的利润大家一起分成的合作方式。老外一般是找我们做PCBA的比较多,但是也有一些需要开发和生产一起做的。我看我们这边的外贸员主要是靠网络跟老外取得联系,有进一步意向的,老外会直接过来考察。

出0入0汤圆

发表于 2014-6-21 10:24:45 | 显示全部楼层
请问一下楼主给老外开发与生产,只要都是用到那些CPU多呀。不会都是51吧。

出0入0汤圆

发表于 2014-6-21 11:01:18 | 显示全部楼层
lyricpoem0726 发表于 2014-6-17 10:24
楼主您好,长按和短按组合的时候,长按时会多触发一次短按,会不会造成影响? ...

程序写好了是不会有这个现象的。

出0入0汤圆

 楼主| 发表于 2014-6-21 12:30:38 | 显示全部楼层
jeoo8888 发表于 2014-6-21 10:24
请问一下楼主给老外开发与生产,只要都是用到那些CPU多呀。不会都是51吧。 ...

我做项目的时候,用PIC单片机最多,也有用到STM32的。

出0入0汤圆

发表于 2014-6-21 12:51:08 | 显示全部楼层
顶起!!!!!

出0入0汤圆

发表于 2014-6-21 17:15:04 | 显示全部楼层
用了点时间看了一下代码,我感觉你在写代码中有一个特定是值得我学习了,对于数据的数量中每一个过程都有一个独立的控制参数来决定函数的运行状态。当你需要修改函数的运行状态时只需要修改该控制参数就可以完成对整个函数的输出结果。比如你在做跑马灯时就使用16独立的参数来修改LED的状态值,应使用一个独立的函数不断的去对这16值赋值到595中来完成对LED灯的状态控制。而当你要做LED的运动方向的使用了一个函数定时的修改上述的16值的状态进而形成了跑马灯的效果。而当你说使用按键来控制这个写LED的状态时,在按键处理函数中去修改数据处理函数的刷新速率来控制LED灯的状态变化。如果我没猜错的话,单片机的构建应该是在自己写的每个目的的模块上留出一个数据接口,当修改这个数据接口时就可以实现对其控制的对象进行修改,而不必重新构造一个新的模块来实现一个单一功能 。

出0入0汤圆

 楼主| 发表于 2014-6-21 18:06:38 | 显示全部楼层
陈正杰 发表于 2014-6-21 17:15
用了点时间看了一下代码,我感觉你在写代码中有一个特定是值得我学习了,对于数据的数量中每一个过程都有一 ...

感谢你的回复。我目前还没抽象到你所说的理论程度----“单片机的构建应该是在自己写的每个目的的模块上留出一个数据接口”。其实我真的没有考虑到这么抽象,我只是感觉我现在写的东西很有规律,可以复用。

出0入0汤圆

发表于 2014-6-21 19:37:05 | 显示全部楼层
吴坚鸿 发表于 2014-6-20 22:31
我们有个股东一直以来都是做外贸的,所以我们接老外的单就自然而然了。目前还有几个外贸员是跟我们合作的 ...

原来这样,厉害

出0入0汤圆

 楼主| 发表于 2014-6-22 01:32:27 | 显示全部楼层
第五十二节:程序后续升级修改的利器,return语句鲜为人知的用法。

开场白:
return语句经常用在带参数返回的函数中,字面上理解就是返回的意思,因此很多单片机初学者很容易忽略了return语句还有中断强行退出的功能。利用这个强行退出的功能,在项目后续程序的升级修改上很方便,还可以有效减少if语句的嵌套层数,使程序阅读起来很简洁。这一节要教大家return语句三个鲜为人知的用法:
第一个鲜为人知的用法:在空函数里,可以插入很多个return语句,不仅仅是一个。
第二个鲜为人知的用法:return语句可以有效较少程序里条件判断语句的嵌套层数。
第三个鲜为人知的用法:return语句本身已经包含了类似break语句的功能,不管当前处于几层的内部循环嵌套,只要遇到return语句都可以强行退出全部循环,并且直接退出当前子程序,不执行当前子程序后面的任何语句,这个功能实在是太强大,太铁腕了。

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

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

(2)实现功能:
本程序实现的功能跟第三十九节是一摸一样的,唯一的差别就是在第三十九节的基础上,插入了几个return语句,用新的return语句替代原来的条件和循环判断语句。

波特率是:9600 。
通讯协议:EB 00 55  XX YY  
加无效填充字节后,上位机实际上应该发送:00  EB 00 55  XX YY
其中第1位00是无效填充字节,防止由于硬件原因丢失第一个字节。
其中第2,3,4位EB 00 55就是数据头
           后2位XX YY就是有效数据
任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字后面两个字节的数据XX YY 分别为01 02,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。

也就是说,当在 串口助手往单片机发送十六进制数据串:  eb 00 55 01 02  时,会听到蜂鸣器”滴”的一声。

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


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

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

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);



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

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

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


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



  18. void main()
  19.   {
  20.    initial_myself();  
  21.    delay_long(100);   
  22.    initial_peripheral();
  23.    while(1)  
  24.    {
  25.        usart_service();  //串口服务程序
  26.    }

  27. }

  28. /* 注释一:
  29. * 以下函数说明了,在空函数里,可以插入很多个return语句。
  30. * 用return语句非常便于后续程序的升级修改。
  31. */
  32. void usart_service(void)  //串口服务程序,在main函数里
  33. {

  34.         

  35. //     if(uiSendCnt>=const_receive_time&&ucSendLock==1) //原来的语句,现在被两个return语句替代了
  36. //     {

  37.        if(uiSendCnt<const_receive_time)  //延时还没超过规定时间,直接退出本程序,不执行return后的任何语句。
  38.            {
  39.               return;  //强行退出本子程序,不执行以下任何语句
  40.            }

  41.            if(ucSendLock==0)  //不是最新一次接收到串口数据,直接退出本程序,不执行return后的任何语句。
  42.            {
  43.               return;  //强行退出本子程序,不执行以下任何语句
  44.            }
  45. /* 注释二:
  46. * 以上两条return语句就相当于原来的一条if(uiSendCnt>=const_receive_time&&ucSendLock==1)语句。
  47. * 用了return语句后,就明显减少了一个if嵌套。
  48. */


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

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

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


  52. //           while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5)) //原来的语句,现在被两个return语句替代了
  53.             while(1) //死循环可以被以下return或者break语句中断,return本身已经包含了break语句功能。
  54.             {
  55.                if(uiRcregTotal<5)  //串口接收到的数据太少
  56.                            {
  57.                               uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  58.                                   return;  //强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句
  59.                            }

  60.                            if(uiRcMoveIndex>(uiRcregTotal-5)) //数组缓冲区的数据已经处理完
  61.                            {
  62.                               uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  63.                                   return;  //强行退出while(1)循环嵌套,直接退出本程序,不执行以下任何语句
  64.                            }
  65. /* 注释三:
  66. * 以上两条return语句就相当于原来的一条while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))语句。
  67. * 以上两个return语句的用法,同时说明了return本身已经包含了break语句功能,不管当前处于几层的内部循环嵌套,
  68. * 都可以强行退出循环,并且直接退出本程序。
  69. */


  70.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  71.                {
  72.                   if(ucRcregBuf[uiRcMoveIndex+3]==0x01&&ucRcregBuf[uiRcMoveIndex+4]==0x02)  //有效数据01 02的判断
  73.                   {
  74.                        uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据头和有效数据都接收正确
  75.                   }
  76.                   break;   //退出while(1)循环
  77.                }
  78.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  79.            }
  80.                                          
  81.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  82.   
  83. //     }
  84.                         
  85. }


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


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

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

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


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


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

  111.    if(RI==1)  
  112.    {
  113.         RI = 0;

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


  129. void delay_long(unsigned int uiDelayLong)
  130. {
  131.    unsigned int i;
  132.    unsigned int j;
  133.    for(i=0;i<uiDelayLong;i++)
  134.    {
  135.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  136.           {
  137.              ; //一个分号相当于执行一条空语句
  138.           }
  139.    }
  140. }


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

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

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


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

  153. }

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

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

  160. }
复制代码

总结陈词:
我在第一节就告诉读者了,搞单片机开发如果不会C语言的指针也没关系,不会影响做项目。我本人平时做项目时,也很少用指针,只有在三种场合下我才会用指针,因为在这三种场合下,用了指针感觉程序阅读起来更加清爽了。所以,指针还是有它独到的好处,有哪三种好处?欲知详情,请听下回分解-----指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

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

出0入4汤圆

发表于 2014-6-22 06:02:38 | 显示全部楼层
必须顶,感谢LZ的辛勤付出

出0入0汤圆

发表于 2014-6-22 17:56:12 | 显示全部楼层
不错感谢楼主的...........

出0入0汤圆

发表于 2014-6-22 17:57:48 | 显示全部楼层
很好           果断收藏学习

出0入0汤圆

发表于 2014-6-23 09:23:25 | 显示全部楼层
rom 发表于 2014-6-21 11:01
程序写好了是不会有这个现象的。

怎么样才算写好呢  长按必定会触发一次短按,怎么处理?

出0入0汤圆

发表于 2014-6-23 09:28:42 | 显示全部楼层
lyricpoem0726 发表于 2014-6-23 09:23
怎么样才算写好呢  长按必定会触发一次短按,怎么处理?

你明显判断短按在按下的时候,短按应该是在弹起的时候判断,这样就不会触发了

出0入0汤圆

发表于 2014-6-24 22:13:21 | 显示全部楼层
辛苦吴总了 帖子顶起来

出0入0汤圆

发表于 2014-6-27 20:45:47 | 显示全部楼层
吴坚鸿 发表于 2014-6-22 01:32
第五十二节:程序后续升级修改的利器,return语句鲜为人知的用法。

开场白:

楼主的办法怎么总是这么给力呀,为什么我想不出来 ,经常看楼主的发贴,会有很多收获。楼主你是发样的。

出0入0汤圆

 楼主| 发表于 2014-6-27 23:58:22 | 显示全部楼层
jeoo8888 发表于 2014-6-27 20:45
楼主的办法怎么总是这么给力呀,为什么我想不出来 ,经常看楼主的发贴,会有很多收获。楼主你是 ...

因为我当年就是靠自己一步一步摸索出来的,一将功成万骨枯,我写的东西是经过大量项目的磨练和总结的,几乎全部都是我个人的原创,因此我非常理解初学者缺什么。我后面还有很多精彩内容,敬请关注。

出0入0汤圆

发表于 2014-6-28 01:31:54 | 显示全部楼层
现在我天天都有关注你的贴,从中收获不少。

出0入0汤圆

发表于 2014-6-28 22:00:54 | 显示全部楼层
学习中,等待更新

出0入0汤圆

发表于 2014-6-28 23:20:44 | 显示全部楼层
鸿哥,一切尽在不言中
希望鸿哥能出一本书,如果每一讲都能附上一张图我想会更好的,希望鸿哥在能整理不来一本书,不管电子版的还是纸质的我都不会错过

出0入0汤圆

 楼主| 发表于 2014-6-29 06:47:48 | 显示全部楼层
第五十三节:指针的第一大好处,让一个函数可以封装多个相当于return语句返回的参数。

开场白:
当我们想把某种算法通过一个函数来实现的时候,如果不会指针,那么只有两种方法。
第1种:用不带参数返回的空函数。这是最原始的做法,也是我当年刚毕业就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。这种方法的缺点是阅读不直观,封装性不强,没有面对用户的输入输出接口。
第2种:用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,如果要用在返回多个输出结果的函数中,就无能为力了,这时候该怎么办?就必须用指针了,也就是我下面讲到的第3种方法。
这一节要教大家一个知识点:通过指针,让函数可以返回多个变量。

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

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

(2)实现功能:
通过电脑串口调试助手,往单片机发送EB 00 55 XX YY  指令,其中EB 00 55是数据头, XX是被除数,YY是除数。单片机收到指令后就会返回6个数据,最前面两个数据是第1种运算方式的商和余数,中间两个数据是第2种运算方式的商和余数,最后两个数据是第3种运算方式的商和余数。
比如电脑发送:EB 00 55 08 02
单片机就返回:04 00 04 00 04 00  (04是商,00是余数)

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

波特率是:9600 。

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


  2. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  3. #define const_rc_size  10  //接收串口中断数据的缓冲区数组大小

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

  5. void initial_myself(void);   
  6. void initial_peripheral(void);
  7. void delay_long(unsigned int uiDelaylong);
  8. void delay_short(unsigned int uiDelayShort);


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


  12. void eusart_send(unsigned char ucSendData);
  13. void chu_fa_yun_suan_1(void);//第1种方法 求商和余数
  14. unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求商
  15. unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp); //第2种方法 求余数
  16. void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp);//第3种方法 求商和余数

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

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


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


  24. unsigned char ucBeiChuShu_1=0;  //第1种方法中的被除数
  25. unsigned char ucChuShu_1=1;     //第1种方法中的除数
  26. unsigned char ucShang_1=0;      //第1种方法中的商
  27. unsigned char ucYu_1=0;         //第1种方法中的余数

  28. unsigned char ucBeiChuShu_2=0;  //第2种方法中的被除数
  29. unsigned char ucChuShu_2=1;     //第2种方法中的除数
  30. unsigned char ucShang_2=0;      //第2种方法中的商
  31. unsigned char ucYu_2=0;         //第2种方法中的余数

  32. unsigned char ucBeiChuShu_3=0;  //第3种方法中的被除数
  33. unsigned char ucChuShu_3=1;     //第3种方法中的除数
  34. unsigned char ucShang_3=0;      //第3种方法中的商
  35. unsigned char ucYu_3=0;         //第3种方法中的余数

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

  45. }


  46. /* 注释一:
  47. * 第1种方法,用不带参数返回的空函数,这是最原始的做法,也是我当年刚毕业
  48. * 就开始做项目的时候经常用的方法。它完全依靠全局变量作为函数的输入和输出口。
  49. * 我们要用到这个函数,就要把参与运算的变量直接赋给对应的输入全局变量,
  50. * 调用一次函数之后,再找到对应的输出变量,这些输出变量就是我们要的结果。
  51. * 在本函数中,被除数ucBeiChuShu_1和除数ucChuShu_1就是输入全局变量,
  52. * 商ucShang_1和余数ucYu_1就是输出全局变量。这种方法的缺点是阅读不直观,
  53. * 封装性不强,没有面对用户的输入输出接口,
  54. */
  55. void chu_fa_yun_suan_1(void)//第1种方法 求商和余数
  56. {
  57.    if(ucChuShu_1==0) //如果除数为0,则商和余数都为0
  58.    {
  59.       ucShang_1=0;
  60.           ucYu_1=0;
  61.    }
  62.    else
  63.    {
  64.       ucShang_1=ucBeiChuShu_1/ucChuShu_1;  //求商
  65.       ucYu_1=ucBeiChuShu_1%ucChuShu_1;  //求余数
  66.    }

  67. }


  68. /* 注释二:
  69. * 第2种方法,用return返回参数和带输入形参的函数,这种方法已经具备了完整的输入和输出性能,
  70. * 比第1种方法直观多了。但是这种方法有它的局限性,因为return只能返回一个变量,
  71. * 如果要用在返回多个输出结果的函数中,就无能为力了。比如本程序,就不能同时输出
  72. * 商和余数,只能分两个函数来做。如果要在一个函数中同时输出商和余数,该怎么办?
  73. * 这个时候就必须用指针了,也就是我下面讲到的第3种方法。
  74. */
  75. unsigned char get_shang_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求商
  76. {
  77.    unsigned char ucShangTemp;
  78.    if(ucChuShuTemp==0) //如果除数为0,则商为0
  79.    {
  80.       ucShangTemp=0;
  81.    }
  82.    else
  83.    {
  84.       ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp;  //求商
  85.    }

  86.    return ucShangTemp; //返回运算后的结果 商
  87. }

  88. unsigned char get_yu_2(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp) //第2种方法 求余数
  89. {
  90.    unsigned char ucYuTemp;
  91.    if(ucChuShuTemp==0) //如果除数为0,则余数为0
  92.    {
  93.       ucYuTemp=0;
  94.    }
  95.    else
  96.    {
  97.       ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp;   //求余数
  98.    }

  99.    return ucYuTemp; //返回运算后的结果 余数
  100. }

  101. /* 注释三:
  102. * 第3种方法,用带指针的函数,就可以顺心所欲,不受return的局限,想输出多少个
  103. * 运算结果都可以,赞一个!在本函数中,ucBeiChuShuTemp和ucChuShuTemp是输入变量,
  104. * 它们不是指针,所以不具备输出接口属性。*p_ucShangTemp和*p_ucYuTemp是输出变量,
  105. * 因为它们是指针,所以具备输出接口属性。
  106. */
  107. void chu_fa_yun_suan_3(unsigned char ucBeiChuShuTemp,unsigned char ucChuShuTemp,unsigned char *p_ucShangTemp,unsigned char *p_ucYuTemp)//第3种方法 求商和余数
  108. {
  109.    if(ucChuShuTemp==0) //如果除数为0,则商和余数都为0
  110.    {
  111.       *p_ucShangTemp=0;
  112.           *p_ucYuTemp=0;
  113.    }
  114.    else
  115.    {
  116.       *p_ucShangTemp=ucBeiChuShuTemp/ucChuShuTemp;  //求商
  117.       *p_ucYuTemp=ucBeiChuShuTemp%ucChuShuTemp;  //求余数
  118.    }

  119. }

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

  122.         

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

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

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

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

  128.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  129.             {
  130.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  131.                {

  132.                   //第1种运算方法,依靠全局变量
  133.                   ucBeiChuShu_1=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  134.                   ucChuShu_1=ucRcregBuf[uiRcMoveIndex+4];  //除数
  135.                                   chu_fa_yun_suan_1(); //调用一次空函数就出结果了,结果保存在ucShang_1和ucYu_1全局变量中
  136.                                   eusart_send(ucShang_1); //把运算结果返回给上位机观察
  137.                                   eusart_send(ucYu_1);//把运算结果返回给上位机观察

  138.                   //第2种运算方法,依靠两个带return语句的返回函数
  139.                   ucBeiChuShu_2=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  140.                   ucChuShu_2=ucRcregBuf[uiRcMoveIndex+4];  //除数
  141.                   ucShang_2=get_shang_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求商
  142.                   ucYu_2=get_yu_2(ucBeiChuShu_2,ucChuShu_2); //第2种方法 求余数
  143.                                   eusart_send(ucShang_2); //把运算结果返回给上位机观察
  144.                                   eusart_send(ucYu_2);//把运算结果返回给上位机观察

  145.                   //第3种运算方法,依靠指针
  146.                   ucBeiChuShu_3=ucRcregBuf[uiRcMoveIndex+3]; //被除数
  147.                   ucChuShu_3=ucRcregBuf[uiRcMoveIndex+4];  //除数
  148. /* 注释四:
  149. * 注意,由于商和余数是指针形参,我们代入的变量必须带地址符号& 。比如&ucShang_3和&ucYu_3。
  150. * 因为我们是把变量的地址传递进去的。
  151. */
  152.                                   chu_fa_yun_suan_3(ucBeiChuShu_3,ucChuShu_3,&ucShang_3,&ucYu_3);//第3种方法 求商和余数
  153.                                   eusart_send(ucShang_3); //把运算结果返回给上位机观察
  154.                                   eusart_send(ucYu_3);//把运算结果返回给上位机观察


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

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

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

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

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

  173. }



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


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

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

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


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


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

  199.    if(RI==1)  
  200.    {
  201.         RI = 0;

  202.             ++uiRcregTotal;
  203.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  204.         {
  205.            uiRcregTotal=const_rc_size;
  206.         }
  207.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  208.         uiSendCnt=0;  //及时喂狗,虽然main函数那边不断在累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个中断都被清零。
  209.    
  210.    }
  211.    else  //发送中断,及时把发送中断标志位清零
  212.    {
  213.         TI = 0;
  214.    }
  215.                                                          
  216. }                                


  217. void delay_long(unsigned int uiDelayLong)
  218. {
  219.    unsigned int i;
  220.    unsigned int j;
  221.    for(i=0;i<uiDelayLong;i++)
  222.    {
  223.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  224.           {
  225.              ; //一个分号相当于执行一条空语句
  226.           }
  227.    }
  228. }

  229. void delay_short(unsigned int uiDelayShort)
  230. {
  231.    unsigned int i;  
  232.    for(i=0;i<uiDelayShort;i++)
  233.    {
  234.      ;   //一个分号相当于执行一条空语句
  235.    }
  236. }


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

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

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


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

  249. }

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

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

  256. }
复制代码

总结陈词:
这节讲了指针的第一大好处,它的第二大好处是什么?欲知详情,请听下回分解-----指针的第二大好处,指针作为数组在函数内部的化身。

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

出0入0汤圆

 楼主| 发表于 2014-6-29 06:54:33 | 显示全部楼层
晨星 发表于 2014-6-28 23:20
鸿哥,一切尽在不言中
希望鸿哥能出一本书,如果每一讲都能附上一张图我想会更好的,希望鸿哥在能整理不来 ...

我以后一定会出书的,但是短期内不会那么快整理,至少一年以后。因为感觉要分享的东西太多了。
晨星,我记住你的名字了,因为前晚十点钟左右,我刚好看到你在另外一个论坛大面积转载我的文章,
我非常支持你的做法,同时也感谢你的努力和付出,让更多的单片机初学者获益。

出0入0汤圆

发表于 2014-6-29 08:05:17 | 显示全部楼层
建议楼主赶紧出书,这样论坛看太吃力了

出0入0汤圆

发表于 2014-6-29 12:24:48 | 显示全部楼层
楼主您好,我是单片机菜鸟,按照您能的教程学习到了“第25节”,现有如下疑问能否帮忙指导,不甚感激
1.硬件变更如下:
a.用2线制磁性开关(1、2、3)代替您提到的按键(S5、S9、S13)
b.学习板采用光耦检测输入、光耦隔离输出控制LED小灯(A、B、C),
2.试验效果
a.正常情况可以完成一次完整动作,但是只要人为干扰任意磁性开关,A、B小灯即会同时点亮(重新上电依然如此),除非手动触发3#磁性开关使其完成整个动作流程

出0入0汤圆

发表于 2014-6-29 19:08:59 | 显示全部楼层
最近怎么没有人整理鸿哥的资料.哈哈

出0入0汤圆

 楼主| 发表于 2014-6-29 20:09:31 | 显示全部楼层
无敌笨笨熊 发表于 2014-6-29 12:24
楼主您好,我是单片机菜鸟,按照您能的教程学习到了“第25节”,现有如下疑问能否帮忙指导,不甚感激
1.硬 ...

这个我也不知道是什么原因。

出0入0汤圆

发表于 2014-6-29 20:41:13 | 显示全部楼层
感谢楼主孜孜不倦。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-7-23 08:16

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表