搜索
bottom↓
楼主: 吴坚鸿

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

  [复制链接]

出0入0汤圆

发表于 2014-4-1 18:54:01 | 显示全部楼层
挺好的,适合初学者们

出0入0汤圆

发表于 2014-4-2 09:54:40 | 显示全部楼层
不错。。。支持下!

出0入0汤圆

发表于 2014-4-2 10:01:04 | 显示全部楼层
支持楼主,无私分享!欢迎

出0入0汤圆

发表于 2014-4-2 11:12:57 | 显示全部楼层
标记,后续留看

出0入0汤圆

发表于 2014-4-2 11:28:14 | 显示全部楼层
写的不错

出0入0汤圆

发表于 2014-4-2 14:08:42 | 显示全部楼层
先mark,单片机还是挺有意思的,

出0入0汤圆

发表于 2014-4-2 16:10:50 | 显示全部楼层
我把程序全部复制下来了,谢谢分享,要是我在大学那会刚学习单片机的时候,有这个资料就好了,

出0入0汤圆

发表于 2014-4-2 20:18:03 | 显示全部楼层
学习 了                  

出0入0汤圆

发表于 2014-4-2 23:09:06 | 显示全部楼层
写得很好,大力支持,正在学习中,本来在论坛里看到个,新型单片机操作系统TreeOS 1.0 ,有打算学一下TreeOS 1.0 但是现在看了吴坚鸿前辈写得这么好
就先学这个.

出0入0汤圆

发表于 2014-4-2 23:54:24 | 显示全部楼层
非常的好啊

出0入0汤圆

 楼主| 发表于 2014-4-3 01:29:54 | 显示全部楼层
第三十七节:数码管作为仪表盘显示跑马灯的方向,速度和运行状态。

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

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

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

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

(3)源代码讲解如下:
  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_key_time3  20    //按键去抖动延时的时间
  6. #define const_key_time4  20    //按键去抖动延时的时间


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

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

  15. //驱动LED的74HC595
  16. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  17. void led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  18. void led_update();  //LED更新函数

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


  22. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  23. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  24. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  25. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  26. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

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

  28. sbit led_dr=P3^5;  


  29. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  30. sbit dig_hc595_st_dr=P2^1;  
  31. sbit dig_hc595_ds_dr=P2^2;  

  32. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  33. sbit hc595_st_dr=P2^4;  
  34. sbit hc595_ds_dr=P2^5;  


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

  36. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  37. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志

  38. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  39. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志

  40. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  41. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志


  42. unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  43. unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

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

  45. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  46. unsigned char ucLed_dr2=0;
  47. unsigned char ucLed_dr3=0;
  48. unsigned char ucLed_dr4=0;
  49. unsigned char ucLed_dr5=0;
  50. unsigned char ucLed_dr6=0;
  51. unsigned char ucLed_dr7=0;
  52. unsigned char ucLed_dr8=0;
  53. unsigned char ucLed_dr9=0;
  54. unsigned char ucLed_dr10=0;
  55. unsigned char ucLed_dr11=0;
  56. unsigned char ucLed_dr12=0;
  57. unsigned char ucLed_dr13=0;
  58. unsigned char ucLed_dr14=0;
  59. unsigned char ucLed_dr15=0;
  60. unsigned char ucLed_dr16=0;

  61. unsigned char ucLed_update=0;  //刷新变量。每次更改LED灯的状态都要更新一次。


  62. unsigned char ucLedStep_09_16=0; //第9个至第16个LED跑马灯的步骤变量
  63. unsigned int  uiTimeCnt_09_16=0; //第9个至第16个LED跑马灯的统计定时中断次数的延时计数器

  64. unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  65. unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

  66. unsigned char ucLedDirFlag=0;   //方向变量,把按键与跑马灯关联起来的核心变量,0代表正方向,1代表反方向
  67. unsigned int  uiSetTimeLevel_09_16=300;  //速度变量,此数值越大速度越慢,此数值越小速度越快。
  68. unsigned char ucLedStartFlag=1;   //启动和暂停的变量,0代表暂停,1代表启动



  69. unsigned char ucDigShow8;  //第8位数码管要显示的内容
  70. unsigned char ucDigShow7;  //第7位数码管要显示的内容
  71. unsigned char ucDigShow6;  //第6位数码管要显示的内容
  72. unsigned char ucDigShow5;  //第5位数码管要显示的内容
  73. unsigned char ucDigShow4;  //第4位数码管要显示的内容
  74. unsigned char ucDigShow3;  //第3位数码管要显示的内容
  75. unsigned char ucDigShow2;  //第2位数码管要显示的内容
  76. unsigned char ucDigShow1;  //第1位数码管要显示的内容


  77. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  78. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  79. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  80. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  81. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  82. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  83. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  84. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志

  85. unsigned char ucDigShowTemp=0; //临时中间变量
  86. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  87. unsigned char ucWd1Part1Update=1;  //窗口1的局部1更新显示变量
  88. unsigned char ucWd1Part2Update=1;  //窗口1的局部2更新显示变量
  89. unsigned char ucWd1Part3Update=1;  //窗口1的局部3更新显示变量


  90. //根据原理图得出的共阴数码管字模表
  91. code unsigned char dig_table[]=
  92. {
  93. 0x3f,  //0       序号0
  94. 0x06,  //1       序号1
  95. 0x5b,  //2       序号2
  96. 0x4f,  //3       序号3
  97. 0x66,  //4       序号4
  98. 0x6d,  //5       序号5
  99. 0x7d,  //6       序号6
  100. 0x07,  //7       序号7
  101. 0x7f,  //8       序号8
  102. 0x6f,  //9       序号9
  103. 0x00,  //无      序号10
  104. 0x40,  //-       序号11
  105. 0x73,  //P       序号12
  106. 0x5c,  //o       序号13
  107. 0x71,  //F       序号14
  108. 0x3e,  //U       序号15
  109. 0x37,  //n       序号16
  110. };

  111. void main()
  112.   {
  113.    initial_myself();  
  114.    delay_long(100);   
  115.    initial_peripheral();
  116.    while(1)  
  117.    {
  118.       key_service(); //按键服务的应用程序
  119.       display_service(); //显示的窗口菜单服务程序

  120.       led_flicker_09_16(); //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  121.           led_update();  //LED更新函数
  122.    }

  123. }



  124. /* 注释一:
  125. * 由于本程序只有1个窗口,而这个窗口又分成3个局部,因此可以省略去窗口变量uWd,
  126. * 只用三个局部变量ucWdxPartyUpdate就可以了。
  127. */

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


  130.     if(ucWd1Part1Update==1) //更新显示当前系统是处于运行还是暂停的状态
  131.         {
  132.        ucWd1Part1Update=0; //及时把更新变量清零,防止一直进来更新
  133.            if(ucLedStartFlag==1)  //启动,显示on
  134.            {
  135.                ucDigShow8=13;  //显示o
  136.            ucDigShow7=16;  //显示n
  137.            ucDigShow6=10;  //显示空
  138.            }
  139.            else  //暂停,显示oFF
  140.            {
  141.                       ucDigShow8=13;  //显示o
  142.            ucDigShow7=14;  //显示F
  143.            ucDigShow6=14;  //显示F
  144.            }
  145.         }

  146.     if(ucWd1Part2Update==1) //更新显示当前系统是处于正方向还是反方向
  147.         {
  148.        ucWd1Part2Update=0; //及时把更新变量清零,防止一直进来更新
  149.            if(ucLedDirFlag==0)  //正方向,向上,显示n
  150.            {
  151.                ucDigShow5=16;  //显示n
  152.            }
  153.            else  //反方向,向下,显示U
  154.            {
  155.                ucDigShow5=15;  //显示U
  156.            }
  157.         }

  158.     if(ucWd1Part3Update==1) //更新显示当前系统的速度,此数值越大速度越慢,此数值越小速度越快。
  159.         {
  160.        ucWd1Part3Update=0; //及时把更新变量清零,防止一直进来更新

  161.            ucDigShow4=10;  //显示空  这一位不用,作为空格

  162.            if(uiSetTimeLevel_09_16>=100)
  163.            {
  164.           ucDigShow3=uiSetTimeLevel_09_16/100;     //显示速度的百位
  165.            }
  166.            else
  167.            {
  168.           ucDigShow3=10;     //显示空
  169.            }

  170.            if(uiSetTimeLevel_09_16>=10)
  171.            {
  172.           ucDigShow2=uiSetTimeLevel_09_16%100/10;  //显示速度的十位
  173.            }
  174.            else
  175.            {
  176.           ucDigShow2=10;     //显示空
  177.            }

  178.        ucDigShow1=uiSetTimeLevel_09_16%10;      //显示速度的个位
  179.         }


  180.    


  181. }


  182. void key_scan()//按键扫描函数 放在定时中断里
  183. {  

  184.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  185.   {
  186.      ucKeyLock1=0; //按键自锁标志清零
  187.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  188.   }
  189.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  190.   {
  191.      uiKeyTimeCnt1++; //累加定时中断次数
  192.      if(uiKeyTimeCnt1>const_key_time1)
  193.      {
  194.         uiKeyTimeCnt1=0;
  195.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  196.         ucKeySec=1;    //触发1号键
  197.      }
  198.   }

  199.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  200.   {
  201.      ucKeyLock2=0; //按键自锁标志清零
  202.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  203.   }
  204.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  205.   {
  206.      uiKeyTimeCnt2++; //累加定时中断次数
  207.      if(uiKeyTimeCnt2>const_key_time2)
  208.      {
  209.         uiKeyTimeCnt2=0;
  210.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  211.         ucKeySec=2;    //触发2号键
  212.      }
  213.   }

  214.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  215.   {
  216.      ucKeyLock3=0; //按键自锁标志清零
  217.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  218.   }
  219.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  220.   {
  221.      uiKeyTimeCnt3++; //累加定时中断次数
  222.      if(uiKeyTimeCnt3>const_key_time3)
  223.      {
  224.         uiKeyTimeCnt3=0;
  225.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  226.         ucKeySec=3;    //触发3号键
  227.      }
  228.   }

  229.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  230.   {
  231.      ucKeyLock4=0; //按键自锁标志清零
  232.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  233.   }
  234.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  235.   {
  236.      uiKeyTimeCnt4++; //累加定时中断次数
  237.      if(uiKeyTimeCnt4>const_key_time4)
  238.      {
  239.         uiKeyTimeCnt4=0;
  240.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  241.         ucKeySec=4;    //触发4号键
  242.      }
  243.   }

  244. }


  245. void key_service() //按键服务的应用程序
  246. {
  247.   switch(ucKeySec) //按键服务状态切换
  248.   {
  249.     case 1:// 改变跑马灯方向的按键 对应朱兆祺学习板的S1键

  250.           if(ucLedDirFlag==0) //通过中间变量改变跑马灯的方向
  251.                   {
  252.                      ucLedDirFlag=1;
  253.                   }
  254.                   else
  255.                   {
  256.                            ucLedDirFlag=0;
  257.                   }

  258.           ucWd1Part2Update=1; //及时更新显示方向

  259.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  260.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  261.           break;   
  262.    
  263.     case 2:// 加速按键 对应朱兆祺学习板的S5键 uiSetTimeLevel_09_16越小速度越快
  264.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16-10;
  265.                   if(uiSetTimeLevel_09_16<50)  //最快限定在50
  266.                   {
  267.                       uiSetTimeLevel_09_16=50;
  268.                   }

  269.           ucWd1Part3Update=1; //及时更新显示速度

  270.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  271.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  272.           break;  

  273.     case 3:// 减速按键 对应朱兆祺学习板的S9键  uiSetTimeLevel_09_16越大速度越慢
  274.           uiSetTimeLevel_09_16=uiSetTimeLevel_09_16+10;
  275.                   if(uiSetTimeLevel_09_16>550)  //最慢限定在550
  276.                   {
  277.                       uiSetTimeLevel_09_16=550;
  278.                   }
  279.           ucWd1Part3Update=1; //及时更新显示速度
  280.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  281.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  282.           break;         
  283.          
  284.     case 4:// 启动和暂停按键 对应朱兆祺学习板的S13键  ucLedStartFlag为0时代表暂停,为1时代表启动

  285.               if(ucLedStartFlag==1)  //启动和暂停两种状态循环切换
  286.                   {
  287.                      ucLedStartFlag=0;
  288.                   }
  289.                   else                   //启动和暂停两种状态循环切换
  290.                   {
  291.                            ucLedStartFlag=1;
  292.                   }
  293.           ucWd1Part1Update=1; //及时更新显示系统的运行状态,是运行还是暂停.
  294.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  295.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  296.           break;   
  297.   }               
  298. }




  299. void led_update()  //LED更新函数
  300. {

  301.    if(ucLed_update==1)
  302.    {
  303.        ucLed_update=0;   //及时清零,让它产生只更新一次的效果,避免一直更新。

  304.        if(ucLed_dr1==1)
  305.            {
  306.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  307.            }
  308.            else
  309.            {
  310.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  311.            }

  312.        if(ucLed_dr2==1)
  313.            {
  314.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  315.            }
  316.            else
  317.            {
  318.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  319.            }

  320.        if(ucLed_dr3==1)
  321.            {
  322.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  323.            }
  324.            else
  325.            {
  326.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  327.            }

  328.        if(ucLed_dr4==1)
  329.            {
  330.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  331.            }
  332.            else
  333.            {
  334.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  335.            }


  336.        if(ucLed_dr5==1)
  337.            {
  338.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  339.            }
  340.            else
  341.            {
  342.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  343.            }


  344.        if(ucLed_dr6==1)
  345.            {
  346.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  347.            }
  348.            else
  349.            {
  350.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  351.            }


  352.        if(ucLed_dr7==1)
  353.            {
  354.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  355.            }
  356.            else
  357.            {
  358.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  359.            }


  360.        if(ucLed_dr8==1)
  361.            {
  362.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  363.            }
  364.            else
  365.            {
  366.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  367.            }

  368.        if(ucLed_dr9==1)
  369.            {
  370.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  371.            }
  372.            else
  373.            {
  374.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  375.            }

  376.        if(ucLed_dr10==1)
  377.            {
  378.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  379.            }
  380.            else
  381.            {
  382.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  383.            }

  384.        if(ucLed_dr11==1)
  385.            {
  386.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  387.            }
  388.            else
  389.            {
  390.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  391.            }

  392.        if(ucLed_dr12==1)
  393.            {
  394.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  395.            }
  396.            else
  397.            {
  398.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  399.            }


  400.        if(ucLed_dr13==1)
  401.            {
  402.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  403.            }
  404.            else
  405.            {
  406.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  407.            }


  408.        if(ucLed_dr14==1)
  409.            {
  410.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  411.            }
  412.            else
  413.            {
  414.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  415.            }


  416.        if(ucLed_dr15==1)
  417.            {
  418.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  419.            }
  420.            else
  421.            {
  422.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  423.            }


  424.        if(ucLed_dr16==1)
  425.            {
  426.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  427.            }
  428.            else
  429.            {
  430.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  431.            }

  432.        hc595_drive(ucLedStatus16_09,ucLedStatus08_01);  //74HC595底层驱动函数

  433.    }
  434. }


  435. void display_drive()  
  436. {
  437.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  438.    switch(ucDisplayDriveStep)
  439.    {
  440.       case 1:  //显示第1位
  441.            ucDigShowTemp=dig_table[ucDigShow1];
  442.                    if(ucDigDot1==1)
  443.                    {
  444.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  445.                    }
  446.            dig_hc595_drive(ucDigShowTemp,0xfe);
  447.                break;
  448.       case 2:  //显示第2位
  449.            ucDigShowTemp=dig_table[ucDigShow2];
  450.                    if(ucDigDot2==1)
  451.                    {
  452.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  453.                    }
  454.            dig_hc595_drive(ucDigShowTemp,0xfd);
  455.                break;
  456.       case 3:  //显示第3位
  457.            ucDigShowTemp=dig_table[ucDigShow3];
  458.                    if(ucDigDot3==1)
  459.                    {
  460.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  461.                    }
  462.            dig_hc595_drive(ucDigShowTemp,0xfb);
  463.                break;
  464.       case 4:  //显示第4位
  465.            ucDigShowTemp=dig_table[ucDigShow4];
  466.                    if(ucDigDot4==1)
  467.                    {
  468.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  469.                    }
  470.            dig_hc595_drive(ucDigShowTemp,0xf7);
  471.                break;
  472.       case 5:  //显示第5位
  473.            ucDigShowTemp=dig_table[ucDigShow5];
  474.                    if(ucDigDot5==1)
  475.                    {
  476.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  477.                    }
  478.            dig_hc595_drive(ucDigShowTemp,0xef);
  479.                break;
  480.       case 6:  //显示第6位
  481.            ucDigShowTemp=dig_table[ucDigShow6];
  482.                    if(ucDigDot6==1)
  483.                    {
  484.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  485.                    }
  486.            dig_hc595_drive(ucDigShowTemp,0xdf);
  487.                break;
  488.       case 7:  //显示第7位
  489.            ucDigShowTemp=dig_table[ucDigShow7];
  490.                    if(ucDigDot7==1)
  491.                    {
  492.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  493.            }
  494.            dig_hc595_drive(ucDigShowTemp,0xbf);
  495.                break;
  496.       case 8:  //显示第8位
  497.            ucDigShowTemp=dig_table[ucDigShow8];
  498.                    if(ucDigDot8==1)
  499.                    {
  500.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  501.                    }
  502.            dig_hc595_drive(ucDigShowTemp,0x7f);
  503.                break;
  504.    }

  505.    ucDisplayDriveStep++;
  506.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  507.    {
  508.      ucDisplayDriveStep=1;
  509.    }



  510. }


  511. //数码管的74HC595驱动函数
  512. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  513. {
  514.    unsigned char i;
  515.    unsigned char ucTempData;
  516.    dig_hc595_sh_dr=0;
  517.    dig_hc595_st_dr=0;

  518.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  519.    for(i=0;i<8;i++)
  520.    {
  521.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  522.          else dig_hc595_ds_dr=0;

  523.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  524.          delay_short(1);
  525.          dig_hc595_sh_dr=1;
  526.          delay_short(1);

  527.          ucTempData=ucTempData<<1;
  528.    }

  529.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  530.    for(i=0;i<8;i++)
  531.    {
  532.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  533.          else dig_hc595_ds_dr=0;

  534.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  535.          delay_short(1);
  536.          dig_hc595_sh_dr=1;
  537.          delay_short(1);

  538.          ucTempData=ucTempData<<1;
  539.    }

  540.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  541.    delay_short(1);
  542.    dig_hc595_st_dr=1;
  543.    delay_short(1);

  544.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  545.    dig_hc595_st_dr=0;
  546.    dig_hc595_ds_dr=0;

  547. }


  548. //LED灯的74HC595驱动函数
  549. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  550. {
  551.    unsigned char i;
  552.    unsigned char ucTempData;
  553.    hc595_sh_dr=0;
  554.    hc595_st_dr=0;

  555.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  556.    for(i=0;i<8;i++)
  557.    {
  558.          if(ucTempData>=0x80)hc595_ds_dr=1;
  559.          else hc595_ds_dr=0;

  560.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  561.          delay_short(1);
  562.          hc595_sh_dr=1;
  563.          delay_short(1);

  564.          ucTempData=ucTempData<<1;
  565.    }

  566.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  567.    for(i=0;i<8;i++)
  568.    {
  569.          if(ucTempData>=0x80)hc595_ds_dr=1;
  570.          else hc595_ds_dr=0;

  571.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  572.          delay_short(1);
  573.          hc595_sh_dr=1;
  574.          delay_short(1);

  575.          ucTempData=ucTempData<<1;
  576.    }

  577.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  578.    delay_short(1);
  579.    hc595_st_dr=1;
  580.    delay_short(1);

  581.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  582.    hc595_st_dr=0;
  583.    hc595_ds_dr=0;

  584. }


  585. void led_flicker_09_16() //第9个至第16个LED的跑马灯程序,逐个亮并且每次只能亮一个.
  586. {
  587.   if(ucLedStartFlag==1)  //此变量为1时代表启动
  588.   {
  589.      switch(ucLedStep_09_16)
  590.      {
  591.      case 0:
  592.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  593.            {
  594.                uiTimeCnt_09_16=0; //时间计数器清零

  595.                            if(ucLedDirFlag==0)  //正方向
  596.                            {
  597.                   ucLed_dr16=0;  //第16个灭
  598.                   ucLed_dr9=1;  //第9个亮

  599.                   ucLed_update=1;  //更新显示
  600.                   ucLedStep_09_16=1; //切换到下一个步骤
  601.                            }
  602.                            else  //反方向
  603.                            {
  604.                   ucLed_dr15=1;  //第15个亮
  605.                   ucLed_dr16=0;  //第16个灭

  606.                   ucLed_update=1;  //更新显示
  607.                   ucLedStep_09_16=7; //返回上一个步骤
  608.                            }
  609.            }
  610.            break;
  611.      case 1:
  612.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  613.            {
  614.                uiTimeCnt_09_16=0; //时间计数器清零

  615.                            if(ucLedDirFlag==0)  //正方向
  616.                            {
  617.                   ucLed_dr9=0;  //第9个灭
  618.                   ucLed_dr10=1;  //第10个亮

  619.                   ucLed_update=1;  //更新显示
  620.                   ucLedStep_09_16=2; //切换到下一个步骤
  621.                            }
  622.                            else  //反方向
  623.                            {
  624.                   ucLed_dr16=1;  //第16个亮
  625.                   ucLed_dr9=0;  //第9个灭

  626.                   ucLed_update=1;  //更新显示
  627.                   ucLedStep_09_16=0; //返回上一个步骤
  628.                            }
  629.            }
  630.            break;
  631.      case 2:
  632.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  633.            {
  634.                uiTimeCnt_09_16=0; //时间计数器清零

  635.                            if(ucLedDirFlag==0)  //正方向
  636.                            {
  637.                   ucLed_dr10=0;  //第10个灭
  638.                   ucLed_dr11=1;  //第11个亮

  639.                   ucLed_update=1;  //更新显示
  640.                   ucLedStep_09_16=3; //切换到下一个步骤
  641.                            }
  642.                            else  //反方向
  643.                            {
  644.                   ucLed_dr9=1;  //第9个亮
  645.                   ucLed_dr10=0;  //第10个灭

  646.                   ucLed_update=1;  //更新显示
  647.                   ucLedStep_09_16=1; //返回上一个步骤
  648.                            }
  649.            }
  650.            break;
  651.      case 3:
  652.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  653.            {
  654.                uiTimeCnt_09_16=0; //时间计数器清零

  655.                            if(ucLedDirFlag==0)  //正方向
  656.                            {
  657.                   ucLed_dr11=0;  //第11个灭
  658.                   ucLed_dr12=1;  //第12个亮

  659.                   ucLed_update=1;  //更新显示
  660.                   ucLedStep_09_16=4; //切换到下一个步骤
  661.                            }
  662.                            else  //反方向
  663.                            {
  664.                   ucLed_dr10=1;  //第10个亮
  665.                   ucLed_dr11=0;  //第11个灭

  666.                   ucLed_update=1;  //更新显示
  667.                   ucLedStep_09_16=2; //返回上一个步骤
  668.                            }
  669.            }
  670.            break;
  671.      case 4:
  672.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  673.            {
  674.                uiTimeCnt_09_16=0; //时间计数器清零

  675.                            if(ucLedDirFlag==0)  //正方向
  676.                            {
  677.                   ucLed_dr12=0;  //第12个灭
  678.                   ucLed_dr13=1;  //第13个亮

  679.                   ucLed_update=1;  //更新显示
  680.                   ucLedStep_09_16=5; //切换到下一个步骤
  681.                            }
  682.                            else  //反方向
  683.                            {
  684.                   ucLed_dr11=1;  //第11个亮
  685.                   ucLed_dr12=0;  //第12个灭

  686.                   ucLed_update=1;  //更新显示
  687.                   ucLedStep_09_16=3; //返回上一个步骤
  688.                            }
  689.            }
  690.            break;
  691.      case 5:
  692.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  693.            {
  694.                uiTimeCnt_09_16=0; //时间计数器清零

  695.                            if(ucLedDirFlag==0)  //正方向
  696.                            {
  697.                   ucLed_dr13=0;  //第13个灭
  698.                   ucLed_dr14=1;  //第14个亮

  699.                   ucLed_update=1;  //更新显示
  700.                   ucLedStep_09_16=6; //切换到下一个步骤
  701.                            }
  702.                            else  //反方向
  703.                            {
  704.                   ucLed_dr12=1;  //第12个亮
  705.                   ucLed_dr13=0;  //第13个灭

  706.                   ucLed_update=1;  //更新显示
  707.                   ucLedStep_09_16=4; //返回上一个步骤
  708.                            }
  709.            }
  710.            break;
  711.      case 6:
  712.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  713.            {
  714.                uiTimeCnt_09_16=0; //时间计数器清零

  715.                            if(ucLedDirFlag==0)  //正方向
  716.                            {
  717.                   ucLed_dr14=0;  //第14个灭
  718.                   ucLed_dr15=1;  //第15个亮

  719.                   ucLed_update=1;  //更新显示
  720.                   ucLedStep_09_16=7; //切换到下一个步骤
  721.                            }
  722.                            else  //反方向
  723.                            {
  724.                   ucLed_dr13=1;  //第13个亮
  725.                   ucLed_dr14=0;  //第14个灭

  726.                   ucLed_update=1;  //更新显示
  727.                   ucLedStep_09_16=5; //返回上一个步骤
  728.                            }
  729.            }
  730.            break;
  731.      case 7:
  732.            if(uiTimeCnt_09_16>=uiSetTimeLevel_09_16) //时间到
  733.            {
  734.                uiTimeCnt_09_16=0; //时间计数器清零

  735.                            if(ucLedDirFlag==0)  //正方向
  736.                            {
  737.                   ucLed_dr15=0;  //第15个灭
  738.                   ucLed_dr16=1;  //第16个亮

  739.                   ucLed_update=1;  //更新显示
  740.                   ucLedStep_09_16=0; //返回到开始处,重新开始新的一次循环
  741.                            }
  742.                            else  //反方向
  743.                            {
  744.                   ucLed_dr14=1;  //第14个亮
  745.                   ucLed_dr15=0;  //第15个灭

  746.                   ucLed_update=1;  //更新显示
  747.                   ucLedStep_09_16=6; //返回上一个步骤
  748.                            }
  749.            }
  750.            break;
  751.    
  752.       }
  753.    }

  754. }


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


  759.   if(uiTimeCnt_09_16<0xffff)  //设定这个条件,防止uiTimeCnt超范围。
  760.   {
  761.       if(ucLedStartFlag==1)  //此变量为1时代表启动
  762.           {
  763.          uiTimeCnt_09_16++;  //累加定时中断的次数,
  764.           }
  765.   }

  766.   key_scan(); //按键扫描函数






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

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


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


  784. void delay_short(unsigned int uiDelayShort)
  785. {
  786.    unsigned int i;  
  787.    for(i=0;i<uiDelayShort;i++)
  788.    {
  789.      ;   //一个分号相当于执行一条空语句
  790.    }
  791. }


  792. void delay_long(unsigned int uiDelayLong)
  793. {
  794.    unsigned int i;
  795.    unsigned int j;
  796.    for(i=0;i<uiDelayLong;i++)
  797.    {
  798.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  799.           {
  800.              ; //一个分号相当于执行一条空语句
  801.           }
  802.    }
  803. }


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

  806. /* 注释二:
  807. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  808. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  809. * 朱兆祺51学习板的S1就是本程序中用到的一个独立按键。
  810. */
  811.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平

  812.   led_dr=0;  //关闭独立LED灯
  813.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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

  815.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  816.   TL0=0x0b;

  817. }

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


  820.    ucDigDot8=0;   //小数点全部不显示
  821.    ucDigDot7=0;  
  822.    ucDigDot6=0;
  823.    ucDigDot5=0;  
  824.    ucDigDot4=0;
  825.    ucDigDot3=0;  
  826.    ucDigDot2=0;
  827.    ucDigDot1=0;

  828.    EA=1;     //开总中断
  829.    ET0=1;    //允许定时中断
  830.    TR0=1;    //启动定时中断

  831. }
复制代码


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

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

出0入0汤圆

发表于 2014-4-3 09:53:31 来自手机 | 显示全部楼层
标记一下,有空看看。

出50入0汤圆

发表于 2014-4-3 13:07:42 | 显示全部楼层
花了一上午的时间终于把20节读完了,下到手机上没网也能读,顺便把文字复制下来了。方便其它的读者。

本帖子中包含更多资源

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

x

出0入0汤圆

发表于 2014-4-3 13:18:32 | 显示全部楼层
欢迎欢迎。。

出0入0汤圆

发表于 2014-4-3 13:19:04 | 显示全部楼层
欢迎欢迎!!

出0入0汤圆

发表于 2014-4-3 16:21:54 | 显示全部楼层
好强大,牛人啊!学习!

出0入0汤圆

发表于 2014-4-3 23:49:46 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 15:06
第十一节:同一个按键短按与长按的区别触发。

开场白:

我是个新手,刚来学avr,看了楼主以下代码4个小时了,硬是没有理解怎么没有:key_sr1==0 ?   这句话:else if(ucKeyLock1==0)//有按键按下,且是第一次被按下     *****怎么就能说明按下按键了呢,也就是key_sr1=0?请楼主能否指导一下,谢谢!
if(key_sr1==1)//IO是高电平,说明两个按键没有全部被按下,这时要及时清零一些标志位
  {
      ucKeyLock1=0; //按键自锁标志清零
      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。   
            if(ucShortTouchFlag1==1)  //短按触发标志
          {
             ucShortTouchFlag1=0;
                 ucKeySec=1;    //触发一号键的短按
          }
  }
  else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  {
     uiKeyTimeCnt1++; //累加定时中断次数
     if(uiKeyTimeCnt1>const_key_time_short1)
     {
            ucShortTouchFlag1=1;   //激活按键短按的有效标志  
     }

     if(uiKeyTimeCnt1>const_key_time_long1)
     {
            ucShortTouchFlag1=0;  //清除按键短按的有效标志

        uiKeyTimeCnt1=0;
        ucKeyLock1=1;  //自锁按键置位,避免一直触发

        ucKeySec=2;    //触发1号键的长按
              
     }

  }

出0入0汤圆

发表于 2014-4-4 09:43:03 | 显示全部楼层
睡了一觉,我似乎想明白了,如果key_sr1=0,也就是按下后,就不会执行f(key_sr1==1)后面这句了。转因为ucKeyLock1==0之前被设置,而执行else if(ucKeyLock1==0)后面的,也就等同检测到key_sr1=0。我搜索了一下if语句解释,原来if(key_sr1==1)这句执行以后就不会再执行 else if(ucKeyLock1==0)这句了,而是执行下一句:if(key_sr2==1)。

出0入0汤圆

 楼主| 发表于 2014-4-4 11:15:45 | 显示全部楼层
biying 发表于 2014-4-4 09:43
睡了一觉,我似乎想明白了,如果key_sr1=0,也就是按下后,就不会执行f(key_sr1==1)后面这句了。转因为ucKe ...

没错。就是这样理解的。

出0入0汤圆

发表于 2014-4-4 11:27:34 | 显示全部楼层
好资料啊!学习了!谢谢楼主啊!

出0入0汤圆

发表于 2014-4-4 12:05:43 | 显示全部楼层
留名收下

出0入0汤圆

发表于 2014-4-4 12:22:00 | 显示全部楼层
吴坚鸿 发表于 2014-4-4 11:15
没错。就是这样理解的。

谢谢指导!吃透你这节,正好为我的单键密码锁程序做好按键准备

出0入0汤圆

 楼主| 发表于 2014-4-4 13:03:23 | 显示全部楼层
biying 发表于 2014-4-4 12:22
谢谢指导!吃透你这节,正好为我的单键密码锁程序做好按键准备

想做密码锁程序,你还可以顺便参考一下我的第三十四节:在数码管中实现iphone4S开机密码锁的程序。

出5入8汤圆

发表于 2014-4-4 13:43:08 | 显示全部楼层
谢谢LZ,等着你些串口的相关程序

出0入0汤圆

发表于 2014-4-4 13:53:28 | 显示全部楼层
感谢楼主无私奉献

出0入0汤圆

发表于 2014-4-5 10:17:27 | 显示全部楼层
谢谢楼主分享谢谢楼主

出0入0汤圆

发表于 2014-4-5 10:42:59 | 显示全部楼层
谢谢楼主分享

出0入0汤圆

 楼主| 发表于 2014-4-5 11:10:41 | 显示全部楼层
第三十八节:判断数据尾来接收一串数据的串口通用程序框架。

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

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

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

(2)实现功能:
通讯协议:XX YY  EB 00 55
           其中后三位 EB 00 55就是我所说的数据尾,它的有效数据XX YY在数据尾的前面。
        任意时刻,单片机从电脑“串口调试助手”上位机收到的一串数据中,只要此数据中包含关键字EB 00 55 ,并且此关键字前面两个字节的数据XX YY 分别为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. void usart_service(void)  //串口服务程序,在main函数里
  29. {

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

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

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

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


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


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

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

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


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


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

  84.    if(RI==1)  
  85.    {
  86.         RI = 0;

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


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


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

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

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


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

  126. }

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

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

  133. }
复制代码

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

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

出0入0汤圆

 楼主| 发表于 2014-4-5 11:13:57 | 显示全部楼层
xssr123 发表于 2014-4-4 13:43
谢谢LZ,等着你些串口的相关程序

你所期待的串口程序今天已经开讲了。

出0入0汤圆

发表于 2014-4-5 11:23:29 | 显示全部楼层
必须得顶一下

出0入264汤圆

发表于 2014-4-5 11:41:00 | 显示全部楼层
吴坚鸿 发表于 2014-4-5 11:10
第三十八节:判断数据尾来接收一串数据的串口通用程序框架。

开场白:

注意变量的保护,这里有隐患。
中断和主程序同时访问了共享变量,需要做好保护。

出0入0汤圆

发表于 2014-4-5 12:15:03 | 显示全部楼层
mark一下

出0入0汤圆

发表于 2014-4-5 13:05:27 | 显示全部楼层
哈哈,谢谢鸿哥

出0入0汤圆

 楼主| 发表于 2014-4-5 14:56:04 | 显示全部楼层
mcu_lover 发表于 2014-4-5 11:41
注意变量的保护,这里有隐患。
中断和主程序同时访问了共享变量,需要做好保护。 ...

感谢提醒。我以这样的程序框架做过很多项目了,感觉非常稳定,目前尚未发现隐患,请问,你平时是怎样有效保护共享变量的,是在临界处关闭相关中断还是用中间变量替代判断的方式,请跟我们分享一下,让我也学习学习。谢谢。

出0入264汤圆

发表于 2014-4-5 16:14:59 | 显示全部楼层
吴坚鸿 发表于 2014-4-5 14:56
感谢提醒。我以这样的程序框架做过很多项目了,感觉非常稳定,目前尚未发现隐患,请问,你平时是怎样有效 ...

兄弟,写程序可不能只凭感觉稳定。首先原理上要分析的清楚了,再去实践,这与做的项目多少没有关系。
共享变量不保护出问题的几率小,但是并不代表没有问题,串口丢几个字节,或者蜂鸣器少叫一会儿没有关系,如果是生命攸关的设备呢?
共享变量存在读写不一致的情况,在8位机里尤其严重。当增减变量值会导致高8位数据产生变化时候,这个时候会有一定几率出现读取的值与实际的不一致。
解决办法可以使用临界区保护,如关中断。或者加原子锁保护。

EnterCriticalSection();

//DoSomething();

ExitCriticalSection();


if(!g_Lock)
{
     g_Lock = 1;

     //DoSomething();

     g_Lock = 0;
}

出0入0汤圆

 楼主| 发表于 2014-4-5 17:39:51 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-5 18:32 编辑
mcu_lover 发表于 2014-4-5 16:14
兄弟,写程序可不能只凭感觉稳定。首先原理上要分析的清楚了,再去实践,这与做的项目多少没有关系。
共 ...


作为一个从业将近十年的单片机工程师,我写程序在一些核心关键的问题上当然不是靠感觉,比如你提到的共享变量保护的问题,其实我在一开始的“第四节:累计定时中断次数使LED灯闪烁。”里面就明确的告诉初学者了,对于要求非常高的项目,一定要在临界点保护数据,而且还附有示范程序代码,不信你可以看看我第四节的内容。但是大部分的项目,其实没必要非要那么做,不然就会把程序写的很繁琐,而且一旦在程序里大量应用这种共享数据保护方法,就会严重影响程序的运行效率,会让程序寸步难行。就像你所说,蜂鸣器的声音就没必要那么严格。其实做一个项目不要指望单片机能方方面面都能照顾到,因为单片机的资源有限,往往顾此失彼,只有把好钢用在刀刃上,去关注这个项目最重要最致命的地方,其它的尽可能越简单越好,我平时就是这样建议我的客户的。就像一台电脑,当你任务运行越多的时候,它的速度和效率必然会下降。

出0入0汤圆

发表于 2014-4-5 18:19:46 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 13:57
第七节:在主函数中利用累计定时中断的次数来实现独立按键的检测。

开场白:

我处理按键的原理和你相同,这种方法确实抗干扰能力非常强的

出0入0汤圆

 楼主| 发表于 2014-4-5 18:24:32 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-5 18:37 编辑
xuehu5808 发表于 2014-4-5 18:19
我处理按键的原理和你相同,这种方法确实抗干扰能力非常强的


是的。这种按键的处理方法,让我在大量的工控项目上应用得游刃有余。

出100入101汤圆

发表于 2014-4-5 18:37:41 | 显示全部楼层
本帖最后由 fengyunyu 于 2014-4-5 18:58 编辑
mcu_lover 发表于 2014-4-5 16:14
兄弟,写程序可不能只凭感觉稳定。首先原理上要分析的清楚了,再去实践,这与做的项目多少没有关系。
共 ...


if(!g_Lock)
{
     g_Lock = 1;

     //DoSomething();

     g_Lock = 0;
}

这种加锁机制,g_lock会不会被中断、高优先级的任务等干扰了?

出0入0汤圆

发表于 2014-4-5 18:41:30 | 显示全部楼层
吴坚鸿 发表于 2014-4-5 18:24
是的。这种按键的处理方法,让我在大量的工控项目上应用得游刃有余。

不错不错,我以前一直采用延时抗干扰和小抖动,但是机器偶尔会出现误动作,后来才想了这个办法。

出0入0汤圆

 楼主| 发表于 2014-4-5 18:45:32 | 显示全部楼层
xuehu5808 发表于 2014-4-5 18:41
不错不错,我以前一直采用延时抗干扰和小抖动,但是机器偶尔会出现误动作,后来才想了这个办法。{:handsh ...

英雄所见略同,我们都是在实战中遇到过同样类似的问题,然后自己动脑筋想办法解决,所以我们很有共鸣。

出0入0汤圆

发表于 2014-4-5 19:15:22 | 显示全部楼层
mark 一下

出0入264汤圆

发表于 2014-4-5 20:09:57 | 显示全部楼层
吴坚鸿 发表于 2014-4-5 17:39
作为一个从业将近十年的单片机工程师,我写程序在一些核心关键的问题上当然不是靠感觉,比如你提到的共享 ...

这种观点我认为是不对的,如果一个程序明知道有问题,还自己安慰自己说,没事,顶多就是蜂鸣器叫的时间时长时短,或者时不时的串口数据解析不对。我想也没有用户能够接受。
不会因为说你做了数据的保护,程序就会变得繁琐,更不会出现你说的影响执行效率。
如果效率有问题,一定是程序结构设计的有问题。

出0入264汤圆

发表于 2014-4-5 20:10:56 | 显示全部楼层
fengyunyu 发表于 2014-4-5 18:37
if(!g_Lock)
{
     g_Lock = 1;

没有关系,g_Lock本身就是原子访问的

出0入0汤圆

发表于 2014-4-5 20:12:07 | 显示全部楼层
这得支持

出0入0汤圆

发表于 2014-4-5 20:12:35 | 显示全部楼层
这得真心支持

出0入0汤圆

发表于 2014-4-5 22:03:01 | 显示全部楼层
进来膜拜学习
先收藏了

出0入0汤圆

 楼主| 发表于 2014-4-5 23:26:05 | 显示全部楼层
mcu_lover 发表于 2014-4-5 20:09
这种观点我认为是不对的,如果一个程序明知道有问题,还自己安慰自己说,没事,顶多就是蜂鸣器叫的时间时 ...

比如我现在这种蜂鸣器程序的写法,估计运行一年也不会发现一次蜂鸣器时长时短的问题,客户是肯定可以接受的。我平时的做法是,一般不加所谓的共享数据保护,除非是非常核心和致命的变量我才加。最后,要感谢你的热心建议,让我知道了那种临界保护数据方法的名称叫“原子锁”,感觉这个名字取得很好听,很好记。

出100入101汤圆

发表于 2014-4-6 08:45:15 | 显示全部楼层
mcu_lover 发表于 2014-4-5 20:10
没有关系,g_Lock本身就是原子访问的

应该还是有些问题。if (!g_lock)到g_lock=1可能被中断?

出0入264汤圆

发表于 2014-4-6 09:00:42 | 显示全部楼层
本帖最后由 mcu_lover 于 2014-4-6 09:02 编辑
fengyunyu 发表于 2014-4-6 08:45
应该还是有些问题。if (!g_lock)到g_lock=1可能被中断?


被中断了没有关系啊,中断优先级本来就高于主循环。

ISR里面:
if(!g_Lock)
{
    g_Lock = 1;
    //修改变量
    g_Lock = 0;
}

主程序里:
if(!g_Lock)
{
    g_Lock = 1;
    //修改变量
    g_Lock = 0;
}

关键点在于g_Lock 的访问是原子的,主程序不管在什么地方被中断,都能保证在修改变量的时候,中断是无法修改的。

出50入0汤圆

发表于 2014-4-6 09:50:45 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 13:56
第六节:在主函数中利用累计主循环次数来实现独立按键的检测。

开场白:

请教一下子:为什么这里的else if(ucKeyLock1==0)//有按键按下,且是第一次被按下  ???
为何不是else if (key_sr1 == 0)  ??

本帖子中包含更多资源

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

x

出0入0汤圆

 楼主| 发表于 2014-4-6 12:12:35 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-7 10:22 编辑
mcu_lover 发表于 2014-4-6 09:00
被中断了没有关系啊,中断优先级本来就高于主循环。

ISR里面:


你介绍的原子锁方法还是值得借鉴的。但是如果在定时中断里,就不应该更改g_Lock,否则就会跟主程

序相互干扰。我把你的原子锁修改成以下方案,并且应用在以后的项目上,感谢你的分享。
ISR里面:
if(!g_Lock)
{
    //g_Lock = 1;  注意:不要这句,否则会跟主循环相互干扰
    //修改变量
    //g_Lock = 0;  注意:也不要这句,否则会跟主循环相互干扰
}

主程序里:
if(!g_Lock)
{
    g_Lock = 1;
    //修改变量
    g_Lock = 0;
}

出0入10汤圆

发表于 2014-4-6 12:12:58 | 显示全部楼层
好人。。。。。。。。。。。。。。。。。

出0入0汤圆

 楼主| 发表于 2014-4-6 12:18:47 | 显示全部楼层
261854681 发表于 2014-4-6 09:50
请教一下子:为什么这里的else if(ucKeyLock1==0)//有按键按下,且是第一次被按下  ???
为何不是else if ( ...

你要把else if(ucKeyLock1==0)前面的语句if(key_sr==1)连起来看就会明白。首先你必须理解C语言中
if(条件)
{   }
else if(条件)
{   }
的具体语法,它们是一对的,不能拆开。
如果if(key_sr==1)这个条件满足,就说明没有按键被按下,就不会再执行后面else if的语句。
如果if(key_sr==1)这个条件不满足,就说明有按键被按下,就必然会执行后面else if的语句。

出0入0汤圆

 楼主| 发表于 2014-4-6 12:19:38 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-7 08:46 编辑

第三十九节:判断数据头来接收一串数据的串口通用程序框架。

开场白:
上一节讲了判断数据尾的程序框架,但是在大部分的项目中,都是通过判断数据头来接收数据的,这一节要教会大家两个知识点:
第一个:如何在已经接收到的一串数据中解析数据头协议并且提取有效数据。
第二个:无论是判断数据头还是判断数据尾,无论是单片机还是上位机,最好在固定协议前多发送一个填充的无效字节0x00,因为硬件原因,第一个字节往往容易丢失。

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

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

(2)实现功能:

波特率是: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,那么蜂鸣器鸣叫一声表示接收的数据头和有效数据都是正确的。

(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. void usart_service(void)  //串口服务程序,在main函数里
  29. {

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

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



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

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

  41. /* 注释二:
  42. * 判断数据头,进入循环解析数据协议必须满足两个条件:
  43. * 第一:最大接收缓冲数据必须大于一串数据的长度(这里是5。包括2个有效数据,3个数据头)
  44. * 第二:游标uiRcMoveIndex必须小于等于最大接收缓冲数据减去一串数据的长度(这里是5。包括2个有效数据,3个数据头)
  45. */
  46.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  47.             {
  48.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  49.                {
  50.                               if(ucRcregBuf[uiRcMoveIndex+3]==0x01&&ucRcregBuf[uiRcMoveIndex+4]==0x02)  //有效数据01 02的判断
  51.                                   {
  52.                                       uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,说明数据头和有效数据都接收正确
  53.                                   }
  54.                   break;   //退出循环
  55.                }
  56.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  57.            }
  58.                                          
  59.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  60.   
  61.      }
  62.                         
  63. }


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


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

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

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


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


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

  89.    if(RI==1)  
  90.    {
  91.         RI = 0;

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


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


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

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

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


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

  131. }

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

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

  138. }
复制代码

总结陈词:
     这一节讲了常用的判断数据头来接收一串数据的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,有效数据长度,有效数据,数据校验的通讯协议。这样的程序该怎么写?欲知详情,请听下回分解-----常用的自定义串口通讯协议。

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

出0入0汤圆

发表于 2014-4-7 06:28:11 | 显示全部楼层
谢谢分享

出0入0汤圆

发表于 2014-4-7 07:26:39 | 显示全部楼层
谢谢分享,楼主辛苦了!

出0入0汤圆

发表于 2014-4-7 10:13:17 | 显示全部楼层
热烈欢迎

出0入0汤圆

 楼主| 发表于 2014-4-7 10:23:53 | 显示全部楼层
mcu_lover 发表于 2014-4-6 09:00
被中断了没有关系啊,中断优先级本来就高于主循环。

ISR里面:

你介绍的原子锁方法还是值得借鉴的。但是如果在定时中断里,就不应该更改g_Lock,否则就会跟主程

序相互干扰。我把你的原子锁修改成以下方案,并且应用在以后的项目上,感谢你的分享。
ISR里面:
if(!g_Lock)
{
    //g_Lock = 1;  注意:不要这句,否则会跟主循环相互干扰
    //修改变量
    //g_Lock = 0;  注意:也不要这句,否则会跟主循环相互干扰
}

主程序里:
if(!g_Lock)
{
    g_Lock = 1;
    //修改变量
    g_Lock = 0;
}

出0入264汤圆

发表于 2014-4-7 10:54:02 | 显示全部楼层
吴坚鸿 发表于 2014-4-7 10:23
你介绍的原子锁方法还是值得借鉴的。但是如果在定时中断里,就不应该更改g_Lock,否则就会跟主程

序相互 ...

有什么干扰呢?
如果有更高优先级中断打断当前中断呢,同时这个高优先级中断也需要修改这个变量呢?考虑过了吗?

出0入0汤圆

 楼主| 发表于 2014-4-7 12:59:07 | 显示全部楼层
mcu_lover 发表于 2014-4-7 10:54
有什么干扰呢?
如果有更高优先级中断打断当前中断呢,同时这个高优先级中断也需要修改这个变量呢?考虑 ...

经过你的提醒,我仔细思考了一下,我觉得你说的很有道理。你的那种写法确实考虑更全面。
我一般不会同时在多个中断里修改变量,为了书写方便,因此我把你的原子锁简化成以下方案,并且应用在以后的项目上,感谢你的分享。
ISR里面:
if(!g_Lock)
{
    //g_Lock = 1;  针对我个人用在单个定时中断修改变量的项目中,我省去这句
    //直接在这里修改变量
    //g_Lock = 0;  针对我个人用在单个定时中断修改变量的项目中,我省去这句
}

主程序里:
//if(!g_Lock)   针对我个人用在单个定时中断修改变量的项目中,我省去这句判断
//{
    g_Lock = 1;
    //直接在这里修改变量
    g_Lock = 0;
//}

出0入0汤圆

 楼主| 发表于 2014-4-7 13:01:16 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-7 15:31 编辑

第四十节:常用的自定义串口通讯协议。

开场白:
上一节讲了判断数据头的程序框架,但是在很多项目中,仅仅靠判断数据头还是不够的,必须要有更加详细的通讯协议,比如可以包含数据类型,数据地址,有效数据长度,有效数据,数据校验的通讯协议。这一节要教会大家三个知识点:
第一个:常用自定义串口通讯协议的程序框架。
第二个:累加校验和的校验方法。累加和的意思是前面所有字节的数据相加,超过一个字节的溢出部分会按照固定的规则自动丢弃,不用我们管。比如以下数据:
      eb 00 55 01 00 02 00 28 6b  
      其中eb 00 55为数据头,01为数据类型,00 02为有效数据长度,00 28 分别为具体的有效数据,6b为前面所有字节的累加和。累加和可以用电脑系统自带的计算器来验证。打开电脑上的计算器,点击“查看”下拉的菜单,选“科学型”,然后选左边的“十六进制”,最后选右边的“字节”,然后把前面所有的字节相加,它们的和就是6b,没错吧。
第三个:原子锁的使用方法,实际上是借鉴了"红金龙吸味"关于原子锁的建议,专门用来保护中断与主函数的共享数据。

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

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

(2)实现功能:
  波特率是:9600.
通讯协议:EB 00 55  GG HH HH XX XX …YY YY CY
其中第1,2,3位EB 00 55就是数据头
其中第4位GG就是数据类型。01代表驱动奉命,02代表驱动Led灯。
其中第5,6位HH就是有效数据长度。高位在左,低位在右。
其中第5,6位HH就是有效数据长度。高位在左,低位在右。
其中从第7位开始,到最后一个字节Cy之前,XX..YY都是具体的有效数据。
在本程序中,当数据类型是01时,有效数据代表蜂鸣器鸣叫的时间长度。当数据类型是02时,有效数据代表Led灯点亮的时间长度。
最后一个字节CY是累加和,前面所有字节的累加。
发送以下测试数据,将会分别控制蜂鸣器和Led灯的驱动时间长度。
蜂鸣器短叫发送:eb 00 55 01 00 02 00 28 6b  
蜂鸣器长叫发送:eb 00 55 01 00 02 00 fa 3d  
Led灯短亮发送:eb 00 55 02 00 02 00 28 6c
Led灯长亮发送:eb 00 55 02 00 02 00 fa 3e  

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

  2. /* 注释一:
  3. * 请评估实际项目中一串数据的最大长度是多少,并且留点余量,然后调整const_rc_size的大小。
  4. * 本节程序把上一节的缓冲区数组大小10改成了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 T0_time(void);  //定时中断函数
  12. void usart_receive(void); //串口接收中断函数
  13. void usart_service(void);  //串口服务程序,在main函数里
  14. void led_service(void);  //Led灯的服务程序。

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

  17. unsigned int  uiSendCnt=0;     //用来识别串口是否接收完一串数据的计时器
  18. unsigned char ucSendLock=1;    //串口服务程序的自锁变量,每次接收完一串数据只处理一次
  19. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  20. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  21. unsigned int  uiRcMoveIndex=0;  //用来解析数据协议的中间变量
  22. /* 注释二:
  23. * 为串口计时器多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  24. */
  25. unsigned char  ucSendCntLock=0; //串口计时器的原子锁

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

  27. unsigned char  ucVoiceLock=0;  //蜂鸣器鸣叫的原子锁

  28. unsigned char ucRcType=0;  //数据类型
  29. unsigned int  uiRcSize=0;  //数据长度
  30. unsigned char ucRcCy=0;  //校验累加和

  31. unsigned int  uiRcVoiceTime=0;  //蜂鸣器发出声音的持续时间

  32. unsigned int  uiRcLedTime=0; //在串口服务程序中,Led灯点亮时间长度的中间变量
  33. unsigned int  uiLedTime=0;  //Led灯点亮时间的长度
  34. unsigned int  uiLedCnt=0;   //Led灯点亮的计时器
  35. unsigned char ucLedLock=0;  //Led灯点亮时间的原子锁

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

  46. }

  47. void led_service(void)
  48. {
  49.    if(uiLedCnt<uiLedTime)
  50.    {
  51.       led_dr=1; //开Led灯
  52.    }
  53.    else
  54.    {
  55.       led_dr=0; //关Led灯
  56.    }
  57. }

  58. void usart_service(void)  //串口服务程序,在main函数里
  59. {
  60. /* 注释三:
  61. * 我借鉴了朱兆祺的变量命名习惯,单个字母的变量比如i,j,k,h,这些变量只用作局部变量,直接在函数内部定义。
  62. */
  63.      unsigned int i;  
  64.         
  65.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  66.      {

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



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

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


  70.             while(uiRcregTotal>=5&&uiRcMoveIndex<=(uiRcregTotal-5))
  71.             {
  72.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  73.                {
  74.                          ucRcType=ucRcregBuf[uiRcMoveIndex+3];   //数据类型  一个字节

  75.                                          uiRcSize=ucRcregBuf[uiRcMoveIndex+4];   //数据长度  两个字节
  76.                                          uiRcSize=uiRcSize<<8;
  77.                                          uiRcSize=uiRcSize+ucRcregBuf[uiRcMoveIndex+5];
  78.                                                                  
  79.                                          ucRcCy=ucRcregBuf[uiRcMoveIndex+6+uiRcSize];   //记录最后一个字节的校验
  80.                      ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=0;  //清零最后一个字节的累加和变量
  81. /* 注释四:
  82. * 计算校验累加和的方法:除了最后一个字节,其它前面所有的字节累加起来,
  83. * 溢出的不用我们管,C语言编译器会按照固定的规则自动处理。
  84. * 以下for循环里的(3+1+2+uiRcSize),其中3代表3个字节数据头,1代表1个字节数据类型,
  85. * 2代表2个字节的数据长度变量,uiRcSize代表实际上一串数据中的有效数据个数。
  86. */
  87.                                           for(i=0;i<(3+1+2+uiRcSize);i++) //计算校验累加和
  88.                                          {
  89.                                                  ucRcregBuf[uiRcMoveIndex+6+uiRcSize]=ucRcregBuf[uiRcMoveIndex+6+uiRcSize]+ucRcregBuf[i];
  90.                      }       


  91.                                          if(ucRcCy==ucRcregBuf[uiRcMoveIndex+6+uiRcSize])  //如果校验正确,则进入以下数据处理
  92.                                          {                                                  
  93.                          switch(ucRcType)   //根据不同的数据类型来做不同的数据处理
  94.                                              {

  95.                              case 0x01:   //驱动蜂鸣器发出声音,并且可以控制蜂鸣器持续发出声音的时间长度
  96.        
  97.                                   uiRcVoiceTime=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  98.                                   uiRcVoiceTime=uiRcVoiceTime<<8;  
  99.                                   uiRcVoiceTime=uiRcVoiceTime+ucRcregBuf[uiRcMoveIndex+7];

  100.                                   ucVoiceLock=1;  //共享数据的原子锁加锁
  101.                                   uiVoiceCnt=uiRcVoiceTime; //蜂鸣器发出声音
  102.                                   ucVoiceLock=0;  //共享数据的原子锁解锁

  103.                                                               break;       
  104.                                                                        
  105.                              case 0x02:   //点亮一个LED灯,并且可以控制LED灯持续亮的时间长度
  106.                                   uiRcLedTime=ucRcregBuf[uiRcMoveIndex+6];  //把两个字节合并成一个int类型的数据
  107.                                   uiRcLedTime=uiRcLedTime<<8;  
  108.                                   uiRcLedTime=uiRcLedTime+ucRcregBuf[uiRcMoveIndex+7];

  109.                                   ucLedLock=1;  //共享数据的原子锁加锁
  110.                                   uiLedTime=uiRcLedTime; //更改点亮Led灯的时间长度
  111.                                                                   uiLedCnt=0;  //在本程序中,清零计数器就等于自动点亮Led灯
  112.                                   ucLedLock=0;  //共享数据的原子锁解锁
  113.                                                               break;
  114.                                                                          
  115.                          }

  116.                                          }       

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


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

  131. /* 注释五:
  132.   * 此处多增加一个原子锁,作为中断与主函数共享数据的保护,实际上是借鉴了"红金龙吸味"关于原子锁的建议.
  133.   */  
  134.   if(ucSendCntLock==0)  //原子锁判断
  135.   {
  136.      ucSendCntLock=1; //加锁
  137.      if(uiSendCnt<const_receive_time)   //如果超过这个时间没有串口数据过来,就认为一串数据已经全部接收完
  138.      {
  139.         uiSendCnt++;    //表面上这个数据不断累加,但是在串口中断里,每接收一个字节它都会被清零,除非这个中间没有串口数据过来
  140.         ucSendLock=1;     //开自锁标志
  141.      }
  142.      ucSendCntLock=0; //解锁
  143.   }

  144.   if(ucVoiceLock==0) //原子锁判断
  145.   {
  146.      if(uiVoiceCnt!=0)
  147.      {

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

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

  159.   if(ucLedLock==0)  //原子锁判断
  160.   {
  161.      if(uiLedCnt<uiLedTime)
  162.          {
  163.             uiLedCnt++;  //Led灯点亮的时间计时器
  164.          }
  165.   }

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


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

  172.    if(RI==1)  
  173.    {
  174.         RI = 0;

  175.             ++uiRcregTotal;
  176.         if(uiRcregTotal>const_rc_size)  //超过缓冲区
  177.         {
  178.            uiRcregTotal=const_rc_size;
  179.         }
  180.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里

  181.         if(ucSendCntLock==0)  //原子锁判断
  182.         {
  183.             ucSendCntLock=1; //加锁
  184.             uiSendCnt=0;  //及时喂狗,虽然在定时中断那边此变量会不断累加,但是只要串口的数据还没发送完毕,那么它永远也长不大,因为每个串口接收中断它都被清零。
  185.                     ucSendCntLock=0; //解锁
  186.                 }
  187.    
  188.    }
  189.    else  //我在其它单片机上都不用else这段代码的,可能在51单片机上多增加" TI = 0;"稳定性会更好吧。
  190.    {
  191.         TI = 0;
  192.    }
  193.                                                          
  194. }                                


  195. void delay_long(unsigned int uiDelayLong)
  196. {
  197.    unsigned int i;
  198.    unsigned int j;
  199.    for(i=0;i<uiDelayLong;i++)
  200.    {
  201.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  202.           {
  203.              ; //一个分号相当于执行一条空语句
  204.           }
  205.    }
  206. }


  207. void initial_myself(void)  //第一区 初始化单片机
  208. {
  209.   led_dr=0; //关Led灯
  210.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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


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

  220. }

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

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

  227. }
复制代码

总结陈词:
这一节讲了常用的自定义串口通讯协议的程序框架,这种框架在判断一串数据是否接收完毕的时候,都是靠“超过规定的时间内,没有发现串口数据”来判定的,这是我做绝大多数项目的串口程序框架,但是在少数要求实时反应非常快的项目中,我会用另外一种响应速度更快的串口程序框架,这种程序框架是什么样的?欲知详情,请听下回分解-----在串口接收中断里即时解析数据头的特殊程序框架。

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

出0入0汤圆

发表于 2014-4-7 21:58:32 | 显示全部楼层
吴坚鸿 发表于 2014-4-6 12:18
你要把else if(ucKeyLock1==0)前面的语句if(key_sr==1)连起来看就会明白。首先你必须理解C语言中
if(条件 ...

else if(ucKeyLock1==0)
若这句成立表明之前没有按键按下,此语句中在计时变量到达设定值后,置位ucKeyLock1,表明有按键了.主程序完成设定操作后,清0.
之后又开始一次新的按键检测

出0入0汤圆

发表于 2014-4-7 21:59:59 | 显示全部楼层
吴坚鸿 发表于 2014-4-7 13:01
第四十节:常用的自定义串口通讯协议。

开场白:

快点哟,鸿哥,等你 .

出0入0汤圆

发表于 2014-4-8 00:38:53 | 显示全部楼层
让无数单片机初学者都获益,
非常感谢楼主分享经验,搬个板凳,认真学习!

出0入0汤圆

发表于 2014-4-9 10:29:37 | 显示全部楼层
写得很好,看了都是用数码管来做显示的,现在很多产品都是用定做的段码 HT1621 +  LCD   来做显示了,希望鸿哥有时间可以写一些段码  HT1621 +  LCD 这方面的。现在很多市面上的产品我都看到了是用定做的段码做显示了。

出0入0汤圆

发表于 2014-4-9 11:03:15 | 显示全部楼层
很好的设计思想和程序,学习中,谢谢奉献

出0入0汤圆

发表于 2014-4-9 12:47:24 | 显示全部楼层
支持一下

出0入0汤圆

发表于 2014-4-9 12:55:18 | 显示全部楼层
强悍啊!太高瞻远瞩了!

出0入0汤圆

 楼主| 发表于 2014-4-9 16:18:29 | 显示全部楼层
jeoo8888 发表于 2014-4-9 10:29
写得很好,看了都是用数码管来做显示的,现在很多产品都是用定做的段码 HT1621 +  LCD   来做显示了,希望 ...

段码定制的液晶屏项目我做过很多,只要是显示界面的,不管是用数码管,还是液晶屏模块,它的基本程序框架都是一样的。后续我会分享ht1621的驱动程序代码。但是程序框架,你可以借鉴我的数码管或者以后的12864液晶屏的程序框架。

出0入0汤圆

 楼主| 发表于 2014-4-9 16:23:05 | 显示全部楼层
signal_12345 发表于 2014-4-9 12:55
强悍啊!太高瞻远瞩了!

呵呵,感谢你给的好评。

出0入0汤圆

发表于 2014-4-9 16:37:14 | 显示全部楼层
谢谢奉献

出0入0汤圆

发表于 2014-4-9 16:53:10 | 显示全部楼层
支持一下

出0入0汤圆

发表于 2014-4-9 17:20:03 | 显示全部楼层
谢谢楼主分享,学习

出50入0汤圆

发表于 2014-4-10 00:15:43 来自手机 | 显示全部楼层
哥,能告诉我们怎样才能练就你这般神奇机巧的算法来?

出50入0汤圆

发表于 2014-4-10 00:24:41 来自手机 | 显示全部楼层
跟着你的思路学到按键和一二级菜单来,确实很巧妙,但如菜单纵深到三级四级时,switch语句似乎不怎么灵光了,前后级跳够让人头晕的,可否有更好的方式来解呢

出0入0汤圆

 楼主| 发表于 2014-4-10 10:24:02 | 显示全部楼层
261854681 发表于 2014-4-10 00:15
哥,能告诉我们怎样才能练就你这般神奇机巧的算法来?

我06年一毕业就开始做项目,那时候没有任何人带,没有任何人教,而且我那时候才第一次知道可以用C语言写单片机程序,于是我自学了C语言,就开始天马行空编写程序,那时从按键,数码管最底层的程序开始不断摸索调试,我的第一个程序没有任何框架可言,想到什么就写什么,糟糕透了。在后续长年累月的项目中,我不断改进程序框架,不断优化程序框架,不断总结它的规律,终于成了你所说的“这般神奇机巧的算法”。

出0入0汤圆

 楼主| 发表于 2014-4-10 10:29:04 | 显示全部楼层
261854681 发表于 2014-4-10 00:24
跟着你的思路学到按键和一二级菜单来,确实很巧妙,但如菜单纵深到三级四级时,switch语句似乎不怎么灵光了 ...


我不赞成你的观点。switch语句是万能的。2级菜单的时候,我就用2个窗口变量。如果有5级菜单的时候,我就用5个窗口变量。它的切换是非常清晰和灵光的,我一直以来就是这么干的。当然,在实际项目中,用的菜单级数越少越好,菜单级数越少就意味着用户体验越好,操作越简单,一般不要玩太花俏的多级菜单。

出0入0汤圆

发表于 2014-4-10 10:32:31 | 显示全部楼层
不错,顶!

出0入0汤圆

发表于 2014-4-10 10:36:00 | 显示全部楼层
楼主以前被拍砖很惨……现在清理了一下注释里无处不在的大名,感觉好多了……

出0入264汤圆

发表于 2014-4-10 10:58:43 | 显示全部楼层
吴坚鸿 发表于 2014-4-10 10:29
我不赞成你的观点。switch语句是万能的。2级菜单的时候,我就用2个窗口变量。如果有5级菜单的时候,我就 ...

这样就谈不上结构了,更不能称之为框架。只能叫做代码示例。
也注定只能适应小的应用。而且每次应用都需要重新构建。
人机交互是一门很深很深的学问。可以多了解一下MVC模式,对如何编写合格的人机交互代码大有裨益。
如果一个程序可以称之为框架,则一定是你设计应用代码,框架来调用。这样框架就可以复用,框架才有框架存在的意义。你也写过VC的程序,应该知道这点。
另外,关于状态机,这个坛子里有非常多精彩的的例子和讲解。switch只是最基础的一式,并不是万能的。这一点上你需要多了解下。
程序讲究低耦合,高内聚,你这样写出来的程序,复用性为0.

我是专门负责拍砖头的,不要介意。

出0入0汤圆

发表于 2014-4-10 11:07:16 | 显示全部楼层
楼主也是高手!!

出0入0汤圆

 楼主| 发表于 2014-4-10 12:04:39 | 显示全部楼层
mhw 发表于 2014-4-10 10:36
楼主以前被拍砖很惨……现在清理了一下注释里无处不在的大名,感觉好多了…… ...

那是我个人的心态在转变。以前就是想过过出名的瘾。

出0入0汤圆

 楼主| 发表于 2014-4-10 12:11:22 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-10 12:14 编辑
mcu_lover 发表于 2014-4-10 10:58
这样就谈不上结构了,更不能称之为框架。只能叫做代码示例。
也注定只能适应小的应用。而且每次应用都需 ...


我这里所说的框架,就是有规律的编程思路,我认为这种编程思路是可以复用的,我现在做什么项目都是按照这种思路来编程的,实践已经证明了这一点,包括很多初学我这种程序框架的网友,他们的反馈都非常好。我现在分享的表面上是代码示例,实际上我把我固有的编程模式融进了每个程序示例里,让初学者更加容易学习理解。至于你所说的,我觉得很专业,虽然我看不懂你讲的是什么意思,但是等我有时间的时候还是会去学习学习。

出0入0汤圆

发表于 2014-4-10 12:17:31 | 显示全部楼层
很好,学习下

出0入0汤圆

发表于 2014-4-10 12:27:41 | 显示全部楼层
mark......

出0入0汤圆

发表于 2014-4-10 12:42:18 | 显示全部楼层
记号,留着以后慢慢看

出0入0汤圆

发表于 2014-4-10 13:40:03 | 显示全部楼层
欢迎牛人

出0入0汤圆

发表于 2014-4-10 19:18:32 来自手机 | 显示全部楼层
精彩纷呈,让我写了几年程序的受益匪浅啊,不顶不是人

出0入4汤圆

发表于 2014-4-10 21:41:47 | 显示全部楼层
看了下部分代码,思路都很清晰呢,不错。Mark。

出0入0汤圆

发表于 2014-4-10 22:32:56 | 显示全部楼层
这个可以有

出0入0汤圆

 楼主| 发表于 2014-4-10 23:17:04 | 显示全部楼层
heiyuu1 发表于 2014-4-10 19:18
精彩纷呈,让我写了几年程序的受益匪浅啊,不顶不是人

感谢你给的好评,让我倍受鼓舞。

出0入0汤圆

发表于 2014-4-11 08:55:45 | 显示全部楼层
一直都想写一个可以通用的按键程序,在网上找了很多都感觉不是很好,有些看不明,有些用了delay,自从看了鸿哥的这个贴后
让我很快就心领神会.。学鸿哥的这个贴可以说是让我们这些入门不久的学者有一种座飞机的感觉。以前是走路现在可以座飞机了。
鸿哥这种写法很好,以后我就学鸿哥的这样写法了,不用再棵跑了。真心感谢鸿哥。

出0入0汤圆

发表于 2014-4-11 14:24:06 | 显示全部楼层
吴坚鸿 发表于 2014-3-10 12:32
谁说“一女不可以侍二夫”?
我到处混,没有许身于任何论坛网站。
我是很开放的,目的就是分 ...

赞一个!!!

出0入0汤圆

发表于 2014-4-11 16:21:18 | 显示全部楼层
diao!!支持支持

出0入0汤圆

 楼主| 发表于 2014-4-11 17:23:46 | 显示全部楼层
jeoo8888 发表于 2014-4-11 08:55
一直都想写一个可以通用的按键程序,在网上找了很多都感觉不是很好,有些看不明,有些用了delay,自从看了 ...

呵呵,让每个单片机初学者都能“坐上飞机”是我最大的梦想 。希望你能把我的程序介绍给更多初学者。

出0入0汤圆

发表于 2014-4-11 18:52:08 | 显示全部楼层
楼主是那个辞职创业的兄弟吗

出0入0汤圆

发表于 2014-4-11 19:55:45 | 显示全部楼层
mark                                                

出0入8汤圆

发表于 2014-4-11 22:27:31 | 显示全部楼层
支持一下,mark学习!

出0入0汤圆

 楼主| 发表于 2014-4-11 22:59:55 | 显示全部楼层
本帖最后由 吴坚鸿 于 2014-4-11 23:01 编辑
ericdai 发表于 2014-4-11 18:52
楼主是那个辞职创业的兄弟吗


你说的那个辞职创业的兄弟是不是发明液晶万能测试仪并且获得了国家发明专利那个牛人?没错,那个兄弟就是我本人。
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

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

GMT+8, 2024-4-19 06:10

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

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