独闷闷网

 找回密码
 立即注册
搜索
楼主: jianhong_wu
打印 上一主题 下一主题
收起左侧

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

[复制链接]
101#
 楼主| 发表于 2014-10-30 15:22:11 | 显示全部楼层
第七十九节:通过主菜单移动光标来进入子菜单窗口的液晶屏程序。

开场白:
    其实主菜单窗口与子菜单窗口本质都是多窗口菜单程序,只不过我在按键服务程序里面建立起来了一条主窗口与子窗口的关系链。这个关系链还是用switch语句搭建起来的,在某个窗口某个局部显示上,操作某个按键就会切换到不同的窗口显示。
继续巩固上一节教给大家的两个知识点:
   第一个知识点:我在前面讲数码管显示的时候就提出了一个 “一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucWdxPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候才需要更新的,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要刷新显示的内容,这种内容放在局部更新显示的括号里。
    第二个知识点:按键如何跟液晶屏显示有机的结合起来?只要遵循鸿哥总结出来的一个规律“在不同的窗口下,根据不同的局部变量来操作不同的参数”,这样再复杂的人机交互程序都会显得很简单清晰。

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

(1)硬件平台:基于坚鸿51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标”移动按键对应S9键,设置参数按键对应S13键。

(2)实现功能:
     通过按键设置6个不同的参数。
    有4个窗口。第1个窗口是主菜单界面,通过光标切换可以进去设置不同参数的子菜单界面。第2个窗口是设置时间范围界面。第3个窗口是设置速度范围界面。第4个窗口是设置频率范围界面。每个设置界面显示2个参数。每个参数的范围是从0到99。
    有4个按键:
(a)          一个是进入和退出S13按键,按一次进入选中的子菜单。再按一次退出子菜单。
(b)         一个是移动光标S9按键,依次按下此按键,液晶屏上的光标会从上往下移动,表示选中不同的参数。当移动到每个窗口最下边那一行时,再按下此按键会把光标移动到第一个参数。
(c)          一个是减数S5按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐减小。
(d)         一个是加数S1按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐加大。
3)源代码讲解如下:
  1. #include "REG52.H"

  2. /* 注释一:
  3. * 本程序用到的变量比较多,所以在keil编译模式里要设置一下编译模式memory model,
  4. * 否则编译会出错.右键单击Target选择“Options for Target'Target1'”就会出来一个框
  5. * 在memory model中选择compact:variables in pdata 就可以了。
  6. */

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

  8. #define const_key_time1  20    //按键去抖动延时的时间
  9. #define const_key_time2  20    //按键去抖动延时的时间
  10. #define const_key_time3  20    //按键去抖动延时的时间
  11. #define const_key_time4  20    //按键去抖动延时的时间


  12. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  13. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  14. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  15. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

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

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

  18. sbit  LCDCS_dr  = P1^6;  //片选线
  19. sbit  LCDSID_dr = P1^7;  //串行数据线
  20. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  21. sbit  LCDRST_dr = P3^4;  //复位线

  22. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  23. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  24. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  25. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  26. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  27. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  28. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
  29. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
  30. unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
  31. void delay_short(unsigned int uiDelayshort); //延时
  32. void delay_long(unsigned int uiDelayLong);

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

  36. void initial_myself();   
  37. void initial_peripheral();


  38. void lcd_display_service(void); //应用层面的液晶屏显示程序
  39. void clear_all_canvas(void);  //把画布全部清零

  40. void wd1(void);//窗口1  主菜单
  41. void wd2(void);//窗口2  设置时间
  42. void wd3(void);//窗口3  设置速度
  43. void wd4(void);//窗口4  设置频率

  44. code unsigned char Zf816_0[]=
  45. {
  46. /*--  文字:  0  --*/
  47. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  48. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  49. };

  50. code unsigned char Zf816_1[]=
  51. {
  52. /*--  文字:  1  --*/
  53. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  54. 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
  55. };

  56. code unsigned char Zf816_2[]=
  57. {
  58. /*--  文字:  2  --*/
  59. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  60. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
  61. };

  62. code unsigned char Zf816_3[]=
  63. {
  64. /*--  文字:  3  --*/
  65. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  66. 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  67. };

  68. code unsigned char Zf816_4[]=
  69. {
  70. /*--  文字:  4  --*/
  71. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  72. 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
  73. };

  74. code unsigned char Zf816_5[]=
  75. {
  76. /*--  文字:  5  --*/
  77. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  78. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  79. };

  80. code unsigned char Zf816_6[]=
  81. {
  82. /*--  文字:  6  --*/
  83. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  84. 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  85. };


  86. code unsigned char Zf816_7[]=
  87. {
  88. /*--  文字:  7  --*/
  89. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  90. 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
  91. };

  92. code unsigned char Zf816_8[]=
  93. {
  94. /*--  文字:  8  --*/
  95. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  96. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
  97. };

  98. code unsigned char Zf816_9[]=
  99. {
  100. /*--  文字:  9  --*/
  101. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  102. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
  103. };


  104. code unsigned char Zf816_nc[]=  //空字模
  105. {
  106. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  107. };

  108. code unsigned char Zf816_mao_hao[]=  //冒号
  109. {
  110. /*--  文字:  :  --*/
  111. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  112. 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
  113. };


  114. code unsigned char Hz1616_zhu[]=
  115. {
  116. /*--  文字:  主  --*/
  117. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  118. 0x02,0x00,0x01,0x80,0x01,0x00,0x00,0x08,0x3F,0xFC,0x01,0x00,0x01,0x00,0x01,0x08,
  119. 0x3F,0xFC,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x04,0x7F,0xFE,0x00,0x00,0x00,0x00,
  120. };

  121. code unsigned char Hz1616_cai[]=
  122. {
  123. /*--  文字:  菜  --*/
  124. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  125. 0x04,0x40,0xFF,0xFE,0x04,0x40,0x04,0x40,0x3F,0xF8,0x22,0x08,0x11,0x10,0x08,0x20,
  126. 0x01,0x00,0x7F,0xFE,0x03,0x80,0x05,0x40,0x09,0x30,0x11,0x1C,0x61,0x08,0x01,0x00,
  127. };

  128. code unsigned char Hz1616_dan[]=
  129. {
  130. /*--  文字:  单  --*/
  131. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  132. 0x08,0x20,0x06,0x30,0x04,0x40,0x3F,0xF8,0x21,0x08,0x3F,0xF8,0x21,0x08,0x21,0x08,
  133. 0x3F,0xF8,0x21,0x08,0x01,0x00,0xFF,0xFE,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,
  134. };

  135. code unsigned char Hz1616_she[]=
  136. {
  137. /*--  文字:  设  --*/
  138. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  139. 0x40,0x00,0x21,0xF0,0x31,0x10,0x21,0x10,0x01,0x10,0x01,0x10,0xE2,0x0E,0x25,0xF8,
  140. 0x21,0x08,0x21,0x08,0x20,0x90,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x0C,0x04,
  141. };

  142. code unsigned char Hz1616_zhi[]=
  143. {
  144. /*--  文字:  置  --*/
  145. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  146. 0x3F,0xF8,0x24,0x48,0x24,0x48,0x3F,0xF8,0x01,0x00,0x7F,0xFC,0x02,0x00,0x1F,0xF0,
  147. 0x10,0x10,0x1F,0xF0,0x10,0x10,0x1F,0xF0,0x10,0x10,0x1F,0xF0,0x10,0x10,0xFF,0xFE,
  148. };

  149. code unsigned char Hz1616_su[]=
  150. {
  151. /*--  文字:  速  --*/
  152. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  153. 0x00,0x80,0x40,0x80,0x2F,0xFC,0x20,0x80,0x00,0x80,0x07,0xF8,0xE4,0x88,0x24,0x88,
  154. 0x27,0xF8,0x21,0xA0,0x22,0x98,0x2C,0x88,0x20,0x80,0x50,0x80,0x8F,0xFE,0x00,0x00,
  155. };

  156. code unsigned char Hz1616_du[]=
  157. {
  158. /*--  文字:  度  --*/
  159. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  160. 0x01,0x00,0x00,0x80,0x3F,0xFE,0x22,0x20,0x22,0x20,0x2F,0xFC,0x22,0x20,0x23,0xE0,
  161. 0x20,0x00,0x27,0xF8,0x22,0x10,0x21,0x20,0x20,0xC0,0x41,0x30,0x46,0x0E,0x98,0x04,
  162. };

  163. code unsigned char Hz1616_shi[]=
  164. {
  165. /*--  文字:  时  --*/
  166. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  167. 0x00,0x10,0x00,0x10,0x7C,0x10,0x44,0x10,0x47,0xFE,0x44,0x10,0x7C,0x10,0x45,0x10,
  168. 0x44,0x90,0x44,0x90,0x7C,0x10,0x00,0x10,0x00,0x10,0x00,0x10,0x00,0x50,0x00,0x20,
  169. };

  170. code unsigned char Hz1616_jian[]=
  171. {
  172. /*--  文字:  间  --*/
  173. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  174. 0x20,0x00,0x13,0xFC,0x10,0x04,0x40,0x04,0x47,0xE4,0x44,0x24,0x44,0x24,0x47,0xE4,
  175. 0x44,0x24,0x44,0x24,0x47,0xE4,0x40,0x04,0x40,0x04,0x40,0x04,0x40,0x14,0x40,0x08,
  176. };

  177. code unsigned char Hz1616_pin[]=
  178. {
  179. /*--  文字:  频  --*/
  180. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  181. 0x08,0x00,0x08,0xFE,0x4E,0x20,0x48,0x40,0x48,0xFC,0xFE,0x84,0x00,0xA4,0x08,0xA4,
  182. 0x4A,0xA4,0x4A,0xA4,0x84,0xA4,0x08,0x50,0x10,0x48,0x20,0x86,0xC3,0x02,0x00,0x00,
  183. };

  184. code unsigned char Hz1616_lv[]=
  185. {
  186. /*--  文字:  率  --*/
  187. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  188. 0x02,0x00,0x01,0x00,0x7F,0xFE,0x41,0x00,0x22,0x28,0x17,0xD0,0x04,0x80,0x11,0x10,
  189. 0x22,0x48,0x47,0xC4,0x01,0x20,0xFF,0xFE,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,
  190. };

  191. code unsigned char Hz1616_fan[]=
  192. {
  193. /*--  文字:  范  --*/
  194. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  195. 0x04,0x20,0x04,0x20,0xFF,0xFE,0x04,0x60,0x40,0x00,0x31,0xF8,0x91,0x08,0x61,0x08,
  196. 0x49,0x08,0x09,0x38,0x11,0x10,0xE1,0x00,0x21,0x04,0x21,0x04,0x20,0xFC,0x20,0x00,
  197. };

  198. code unsigned char Hz1616_wei[]=
  199. {
  200. /*--  文字:  围  --*/
  201. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  202. 0x7F,0xFC,0x42,0x04,0x42,0x04,0x5F,0xF4,0x42,0x04,0x4F,0xE4,0x42,0x04,0x5F,0xE4,
  203. 0x42,0x24,0x42,0x24,0x42,0x24,0x42,0xA4,0x42,0x44,0x40,0x04,0x7F,0xFC,0x40,0x04,
  204. };

  205. code unsigned char Hz1616_shang[]=
  206. {
  207. /*--  文字:  上  --*/
  208. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  209. 0x00,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xF8,0x01,0x00,
  210. 0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x04,0x7F,0xFE,0x00,0x00,
  211. };

  212. code unsigned char Hz1616_xia[]=
  213. {
  214. /*--  文字:  下  --*/
  215. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  216. 0x00,0x04,0x7F,0xFE,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0xC0,0x01,0x60,0x01,0x30,
  217. 0x01,0x20,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x00,0x00,
  218. };

  219. code unsigned char Hz1616_xian[]=
  220. {
  221. /*--  文字:  限  --*/
  222. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  223. 0x00,0x00,0xFB,0xF8,0x92,0x08,0x93,0xF8,0xA2,0x08,0xA2,0x08,0x93,0xF8,0x8A,0x80,
  224. 0x8A,0x48,0xAA,0x50,0x92,0x20,0x82,0x20,0x82,0x10,0x82,0x8E,0x83,0x04,0x82,0x00,
  225. };


  226. unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00
  227. {
  228. 0x00,0x00,0x00,0x00,  //上半屏
  229. 0x00,0x00,0x00,0x00,
  230. 0x00,0x00,0x00,0x00,
  231. 0x00,0x00,0x00,0x00,
  232. 0x00,0x00,0x00,0x00,
  233. 0x00,0x00,0x00,0x00,
  234. 0x00,0x00,0x00,0x00,
  235. 0x00,0x00,0x00,0x00,

  236. //------------上半屏和下半屏的分割线-----------

  237. 0x00,0x00,0x00,0x00,  //下半屏
  238. 0x00,0x00,0x00,0x00,
  239. 0x00,0x00,0x00,0x00,
  240. 0x00,0x00,0x00,0x00,
  241. 0x00,0x00,0x00,0x00,
  242. 0x00,0x00,0x00,0x00,
  243. 0x00,0x00,0x00,0x00,
  244. 0x00,0x00,0x00,0x00,
  245. };


  246. unsigned char ucKeySec=0;   //被触发的按键编号
  247. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器

  248. unsigned char ucWd=1; //窗口变量

  249. unsigned char ucWd1Part=1;  //窗口1的局部变量,代表选中某一行。
  250. unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  251. unsigned char ucWd1Part1Update=0; //窗口1的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零
  252. unsigned char ucWd1Part2Update=0; //窗口1的第2个局部更新显示变量  1代表更新显示,响应函数内部会清零
  253. unsigned char ucWd1Part3Update=0; //窗口1的第3个局部更新显示变量  1代表更新显示,响应函数内部会清零

  254. unsigned char ucWd2Part=1;  //窗口2的局部变量,代表选中某一行。
  255. unsigned char ucWd2Update=0; //窗口2的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  256. unsigned char ucWd2Part1Update=0; //窗口2的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零
  257. unsigned char ucWd2Part2Update=0; //窗口2的第2个局部更新显示变量  1代表更新显示,响应函数内部会清零

  258. unsigned char ucWd3Part=1;  //窗口3的局部变量,代表选中某一行。
  259. unsigned char ucWd3Update=0; //窗口3的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  260. unsigned char ucWd3Part1Update=0; //窗口3的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零
  261. unsigned char ucWd3Part2Update=0; //窗口3的第2个局部更新显示变量  1代表更新显示,响应函数内部会清零

  262. unsigned char ucWd4Part=1;  //窗口4的局部变量,代表选中某一行。
  263. unsigned char ucWd4Update=0; //窗口4的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  264. unsigned char ucWd4Part1Update=0; //窗口4的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零
  265. unsigned char ucWd4Part2Update=0; //窗口4的第2个局部更新显示变量  1代表更新显示,响应函数内部会清零


  266. unsigned char ucTimeH=2;  //设置时间的上限数据
  267. unsigned char ucTimeL=1;  //设置时间的下限数据

  268. unsigned char ucSpeedH=4;  //设置速度的上限数据
  269. unsigned char ucSpeedL=3;  //设置速度的下限数据

  270. unsigned char ucFreqH=6;  //设置频率的上限数据
  271. unsigned char ucFreqL=5;  //设置频率的下限数据

  272. void main()
  273.   {
  274.         initial_myself();      //第一区,上电后马上初始化
  275.         delay_long(100);       //一线,延时线。延时一段时间
  276.         initial_peripheral();  //第二区,上电后延时一段时间再初始化

  277.         while(1)   //第三区
  278.         {
  279.                     key_service(); //按键服务的应用程序
  280.             lcd_display_service(); //应用层面的液晶屏显示程序
  281.         }

  282. }


  283. void initial_myself()  //第一区 上电后马上初始化
  284. {
  285. /* 注释二:
  286. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  287. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  288. * 坚鸿51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。
  289. */
  290.    key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  291.    beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。

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

  293.    TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  294.    TL0=0x2f;
  295. }
  296. void initial_peripheral() //第二区 上电后延时一段时间再初始化
  297. {
  298.     LCDInit(); //初始化12864 内部包含液晶模块的复位


  299.     EA=1;     //开总中断
  300.     ET0=1;    //允许定时中断
  301.     TR0=1;    //启动定时中断

  302. }


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

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

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


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



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


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

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

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

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

  332.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  333.   {
  334.      ucKeyLock1=0; //按键自锁标志清零
  335.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  336.   }
  337.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  338.   {
  339.      uiKeyTimeCnt1++; //累加定时中断次数
  340.      if(uiKeyTimeCnt1>const_key_time1)
  341.      {
  342.         uiKeyTimeCnt1=0;
  343.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  344.         ucKeySec=1;    //触发1号键
  345.      }
  346.   }

  347.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  348.   {
  349.      ucKeyLock2=0; //按键自锁标志清零
  350.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  351.   }
  352.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  353.   {
  354.      uiKeyTimeCnt2++; //累加定时中断次数
  355.      if(uiKeyTimeCnt2>const_key_time2)
  356.      {
  357.         uiKeyTimeCnt2=0;
  358.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  359.         ucKeySec=2;    //触发2号键
  360.      }
  361.   }

  362.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  363.   {
  364.      ucKeyLock3=0; //按键自锁标志清零
  365.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  366.   }
  367.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  368.   {
  369.      uiKeyTimeCnt3++; //累加定时中断次数
  370.      if(uiKeyTimeCnt3>const_key_time3)
  371.      {
  372.         uiKeyTimeCnt3=0;
  373.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  374.         ucKeySec=3;    //触发3号键
  375.      }
  376.   }

  377.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  378.   {
  379.      ucKeyLock4=0; //按键自锁标志清零
  380.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  381.   }
  382.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  383.   {
  384.      uiKeyTimeCnt4++; //累加定时中断次数
  385.      if(uiKeyTimeCnt4>const_key_time4)
  386.      {
  387.         uiKeyTimeCnt4=0;
  388.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  389.         ucKeySec=4;    //触发4号键
  390.      }
  391.   }

  392. }


  393. void key_service(void) //按键服务的应用程序
  394. {
  395.   switch(ucKeySec) //按键服务状态切换
  396.   {
  397.     case 1:// 加按键 对应朱兆祺学习板的S1键
  398.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  399.           {
  400.               case 2:  //窗口2  设置时间
  401.                    switch(ucWd2Part)  //在窗口2下,根据不同的局部变量来设置不同的参数
  402.                    {

  403.                           case 1:   //设置时间上限
  404.                                 ucTimeH++;
  405.                                 if(ucTimeH>99)
  406.                                 {
  407.                                    ucTimeH=99;
  408.                                 }
  409.                                 ucWd2Part1Update=1; //1代表更新显示,响应函数内部会清零
  410.                                 break;
  411.                           case 2:   //设置时间下限
  412.                                 ucTimeL++;
  413.                                 if(ucTimeL>99)
  414.                                 {
  415.                                    ucTimeL=99;
  416.                                 }
  417.                                 ucWd2Part2Update=1; //1代表更新显示,响应函数内部会清零
  418.                                 break;
  419.                         
  420.                    }
  421.                    break;         
  422.               case 3:  //窗口3  设置速度
  423.                    switch(ucWd3Part)  //在窗口3下,根据不同的局部变量来设置不同的参数
  424.                    {

  425.                           case 1:   //设置速度上限
  426.                                 ucSpeedH++;
  427.                                 if(ucSpeedH>99)
  428.                                 {
  429.                                    ucSpeedH=99;
  430.                                 }
  431.                                 ucWd3Part1Update=1; //1代表更新显示,响应函数内部会清零
  432.                                 break;
  433.                           case 2:   //设置速度下限
  434.                                 ucSpeedL++;
  435.                                 if(ucSpeedL>99)
  436.                                 {
  437.                                    ucSpeedL=99;
  438.                                 }
  439.                                 ucWd3Part2Update=1; //1代表更新显示,响应函数内部会清零
  440.                                 break;
  441.                         
  442.                    }
  443.                    break;     
  444.               case 4:  //窗口4  设置速度
  445.                    switch(ucWd4Part)  //在窗口4下,根据不同的局部变量来设置不同的参数
  446.                    {

  447.                           case 1:   //设置频率上限
  448.                                 ucFreqH++;
  449.                                 if(ucFreqH>99)
  450.                                 {
  451.                                    ucFreqH=99;
  452.                                 }
  453.                                 ucWd4Part1Update=1; //1代表更新显示,响应函数内部会清零
  454.                                 break;
  455.                           case 2:   //设置频率下限
  456.                                 ucFreqL++;
  457.                                 if(ucFreqL>99)
  458.                                 {
  459.                                    ucFreqL=99;
  460.                                 }
  461.                                 ucWd4Part2Update=1; //1代表更新显示,响应函数内部会清零
  462.                                 break;
  463.                         
  464.                    }
  465.                    break;     
  466.           }     
  467.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  468.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  469.           break;   
  470.    
  471.     case 2:// 减按键 对应朱兆祺学习板的S5键
  472.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  473.           {         
  474.               case 2:  //窗口2 设置时间
  475.                    switch(ucWd2Part)  //在窗口2下,根据不同的局部变量来设置不同的参数
  476.                    {
  477.                           case 1:   //设置时间上限
  478.                                 ucTimeH--;
  479.                                 if(ucTimeH>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  480.                                 {
  481.                                    ucTimeH=0;
  482.                                 }
  483.                                 ucWd2Part1Update=1; //1代表更新显示,响应函数内部会清零
  484.                                 break;
  485.                           case 2:   //设置时间下限
  486.                                 ucTimeL--;
  487.                                 if(ucTimeL>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  488.                                 {
  489.                                    ucTimeL=0;
  490.                                 }
  491.                                 ucWd2Part2Update=1; //1代表更新显示,响应函数内部会清零
  492.                                 break;
  493.                    }
  494.                    break;         
  495.               case 3:  //窗口3  设置速度
  496.                    switch(ucWd3Part)  //在窗口3下,根据不同的局部变量来设置不同的参数
  497.                    {
  498.                           case 1:   //设置速度上限
  499.                                 ucSpeedH--;
  500.                                 if(ucSpeedH>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  501.                                 {
  502.                                    ucSpeedH=0;
  503.                                 }
  504.                                 ucWd3Part1Update=1; //1代表更新显示,响应函数内部会清零
  505.                                 break;
  506.                           case 2:   //设置速度下限
  507.                                 ucSpeedL--;
  508.                                 if(ucSpeedL>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  509.                                 {
  510.                                    ucSpeedL=0;
  511.                                 }
  512.                                 ucWd3Part2Update=1; //1代表更新显示,响应函数内部会清零
  513.                                 break;
  514.                    }
  515.                    break;      
  516.               case 4:  //窗口4  设置频率
  517.                    switch(ucWd4Part)  //在窗口4下,根据不同的局部变量来设置不同的参数
  518.                    {
  519.                           case 1:   //设置频率上限
  520.                                 ucFreqH--;
  521.                                 if(ucFreqH>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  522.                                 {
  523.                                    ucFreqH=0;
  524.                                 }
  525.                                 ucWd4Part1Update=1; //1代表更新显示,响应函数内部会清零
  526.                                 break;
  527.                           case 2:   //设置频率下限
  528.                                 ucFreqL--;
  529.                                 if(ucFreqL>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff)
  530.                                 {
  531.                                    ucFreqL=0;
  532.                                 }
  533.                                 ucWd4Part2Update=1; //1代表更新显示,响应函数内部会清零
  534.                                 break;
  535.                    }
  536.                    break;   
  537.           }     
  538.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  539.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  540.           break;  

  541.     case 3:// 切换"光标"移动按键 对应朱兆祺学习板的S9键
  542.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  543.           {
  544.               case 1: //窗口1 主菜单
  545.                    switch(ucWd1Part)  //在窗口1下,根据不同的局部变量来设置不同的参数
  546.                    {

  547.                           case 1:   //设置时间
  548.                                 ucWd1Part=2; //光标切换到下一行
  549.                                 ucWd1Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  550.                                 ucWd1Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态
  551.                                 break;
  552.                           case 2:   //设置速度
  553.                                 ucWd1Part=3; //光标切换到下一行
  554.                                 ucWd1Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  555.                                 ucWd1Part3Update=1; //更新显示下一行,    目的是更新反显光标的状态
  556.                                 break;
  557.                           case 3:   //设置第3行参数
  558.                                 ucWd1Part=1; //光标返回到第一行
  559.                                 ucWd1Part3Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  560.                                 ucWd1Part1Update=1; //更新显示下一行,    目的是更新反显光标的状态
  561.                                 break;


  562.                    }
  563.                    break;
  564.               case 2: //窗口2 设置时间
  565.                    switch(ucWd2Part)  //在窗口2下,根据不同的局部变量来设置不同的参数
  566.                    {

  567.                           case 1:   //时间上限
  568.                                 ucWd2Part=2; //光标切换到下一行
  569.                                 ucWd2Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  570.                                 ucWd2Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态
  571.                                 break;
  572.                           case 2:   //时间下限
  573.                                 ucWd2Part=1; //光标返回到第一行
  574.                                 ucWd2Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  575.                                 ucWd2Part1Update=1; //更新显示下一行,    目的是更新反显光标的状态
  576.                                 break;

  577.                    }
  578.                    break;      
  579.               case 3: //窗口3 设置速度
  580.                    switch(ucWd3Part)  //在窗口3下,根据不同的局部变量来设置不同的参数
  581.                    {

  582.                           case 1:   //速度上限
  583.                                 ucWd3Part=2; //光标切换到下一行
  584.                                 ucWd3Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  585.                                 ucWd3Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态
  586.                                 break;
  587.                           case 2:   //速度下限
  588.                                 ucWd3Part=1; //光标返回到第一行
  589.                                 ucWd3Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  590.                                 ucWd3Part1Update=1; //更新显示下一行,    目的是更新反显光标的状态
  591.                                 break;

  592.                    }
  593.                    break;      
  594.               case 4: //窗口4 设置频率
  595.                    switch(ucWd4Part)  //在窗口4下,根据不同的局部变量来设置不同的参数
  596.                    {

  597.                           case 1:   //频率上限
  598.                                 ucWd4Part=2; //光标切换到下一行
  599.                                 ucWd4Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  600.                                 ucWd4Part2Update=1; //更新显示下一行,    目的是更新反显光标的状态
  601.                                 break;
  602.                           case 2:   //频率下限
  603.                                 ucWd4Part=1; //光标返回到第一行
  604.                                 ucWd4Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态
  605.                                 ucWd4Part1Update=1; //更新显示下一行,    目的是更新反显光标的状态
  606.                                 break;

  607.                    }
  608.                    break;     
  609.           }         
  610.         
  611.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  612.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  613.           break;      
  614.    
  615.     case 4: // 进入和退出按键  对应朱兆祺学习板的S13键,按一次进入选中的子菜单。再按一次退出子菜单。
  616.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  617.           {
  618.               case 1:  //窗口1                               
  619.                    switch(ucWd1Part)  //在窗口1下,根据不同的局部变量来设置不同的参数
  620.                    {

  621.                           case 1:   //设置时间
  622.                                                         ucWd=2; //进入设置时间的窗口2
  623.                                                                 ucWd2Update=1; //窗口2整屏更新
  624.                                 break;
  625.                           case 2:   //设置速度
  626.                                                         ucWd=3; //进入设置速度的窗口3
  627.                                                                 ucWd3Update=1; //窗口3整屏更新
  628.                                 break;
  629.                           case 3:   //设置频率
  630.                                                         ucWd=4; //进入设置频率的窗口4
  631.                                                                 ucWd4Update=1; //窗口4整屏更新
  632.                                 break;


  633.                    }
  634.                    break;
  635.               case 2:  //窗口2
  636.                                    ucWd=1;        //返回主菜单窗口1
  637.                                    ucWd1Update=1; //窗口1整屏更新
  638.                    break;      
  639.               case 3:  //窗口3
  640.                                    ucWd=1;        //返回主菜单窗口1
  641.                                    ucWd1Update=1; //窗口1整屏更新
  642.                    break;   
  643.                case 4:  //窗口4
  644.                                    ucWd=1;        //返回主菜单窗口1
  645.                                    ucWd1Update=1; //窗口1整屏更新
  646.                    break;   

  647.           }   

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

  651.   }               
  652. }


  653. unsigned char *number_to_matrix(unsigned char  ucBitNumber)
  654. {
  655.     unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

  656.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
  657.         {
  658.             case 0:
  659.              p_ucAnyNumber=Zf816_0;
  660.                      break;
  661.             case 1:
  662.              p_ucAnyNumber=Zf816_1;
  663.                      break;
  664.             case 2:
  665.              p_ucAnyNumber=Zf816_2;
  666.                      break;
  667.             case 3:
  668.              p_ucAnyNumber=Zf816_3;
  669.                      break;
  670.             case 4:
  671.              p_ucAnyNumber=Zf816_4;
  672.                      break;
  673.             case 5:
  674.              p_ucAnyNumber=Zf816_5;
  675.                      break;
  676.             case 6:
  677.              p_ucAnyNumber=Zf816_6;
  678.                      break;
  679.             case 7:
  680.              p_ucAnyNumber=Zf816_7;
  681.                      break;
  682.             case 8:
  683.              p_ucAnyNumber=Zf816_8;
  684.                      break;
  685.             case 9:
  686.              p_ucAnyNumber=Zf816_9;
  687.                      break;
  688.             case 10:
  689.              p_ucAnyNumber=Zf816_nc;
  690.                      break;
  691.                 default:   //如果上面的条件都不符合,那么默认指向空字模
  692.              p_ucAnyNumber=Zf816_nc;
  693.                      break;
  694.         }

  695.     return p_ucAnyNumber;  //返回转换结束后的指针
  696. }



  697. void lcd_display_service(void) //应用层面的液晶屏显示程序
  698. {

  699.     switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  700.     {
  701.         case 1:  
  702.               wd1();  //主菜单
  703.               break;
  704.         case 2:  
  705.               wd2();  //设置时间
  706.               break;
  707.         case 3:  
  708.               wd3();  //设置速度
  709.               break;
  710.         case 4:  
  711.               wd4();  //设置频率
  712.               break;

  713.         //本程序只有4个窗口,所以只有4个case ,如果要增加窗口,就直接增加 case 5, case 6...        
  714.     }

  715. }


  716. void wd1(void)  //窗口1  主菜单
  717. {

  718.     unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

  719. /* 注释三:
  720. * 把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候
  721. * 才需要更新,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要
  722. * 刷新显示的内容,这种内容放在局部更新显示的括号里。
  723. */
  724.     if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
  725.     {
  726.         ucWd1Update=0;  //及时清零,避免一直更新

  727.         ucWd1Part1Update=1; //激活窗口1的第1个局部更新显示变量
  728.         ucWd1Part2Update=1; //激活窗口1的第2个局部更新显示变量
  729.         ucWd1Part3Update=1; //激活窗口1的第3个局部更新显示变量


  730.         display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  731.         clear_all_canvas();  //把画布全部清零
  732.         insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布

  733.         display_lattice(2,0,Hz1616_zhu,0,2,16,0);    //主菜单。这些内容不用经常更新,只有在切换窗口的时候才更新显示
  734.         display_lattice(3,0,Hz1616_cai,0,2,16,0);
  735.         display_lattice(4,0,Hz1616_dan,0,2,16,0);


  736.     }

  737. /* 注释四:
  738. * 注意!我前面讲数码管显示的时候有一句话讲错了,我那时说<局部更新应该写在整屏更新之前>,这是不对的。
  739. * 按照现在的显示程序框架<即整屏显示更新括号里包含了所有局部变量的激活>,应该是<整屏更新应该写在局部更新之前>
  740. * 这样才对。
  741. */
  742.     if(ucWd1Part1Update==1) //窗口1的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
  743.     {
  744.         ucWd1Part1Update=0; //及时清零,避免一直更新

  745.         if(ucWd1Part==1) //被选中
  746.         {
  747.              ucCursorFlag=1; //反显 显示
  748.         }
  749.         else //没被选中
  750.         {
  751.              ucCursorFlag=0; //正常 显示
  752.         }

  753.                 display_lattice(0,16,Hz1616_she,ucCursorFlag,2,16,0);    //设置时间范围
  754.         display_lattice(1,16,Hz1616_zhi,ucCursorFlag,2,16,0);   
  755.         display_lattice(2,16,Hz1616_shi,ucCursorFlag,2,16,0);   
  756.         display_lattice(3,16,Hz1616_jian,ucCursorFlag,2,16,0);
  757.         display_lattice(4,16,Hz1616_fan,ucCursorFlag,2,16,0);   
  758.         display_lattice(5,16,Hz1616_wei,ucCursorFlag,2,16,0);


  759.                           
  760.     }

  761.     if(ucWd1Part2Update==1) //窗口1的第2个局部更新显示变量,里面放一些经常需要刷新显示的内容
  762.     {
  763.          ucWd1Part2Update=0; //及时清零,避免一直更新

  764.          if(ucWd1Part==2) //被选中
  765.          {
  766.              ucCursorFlag=1; //反显 显示
  767.          }
  768.          else //没被选中
  769.          {
  770.              ucCursorFlag=0; //正常 显示
  771.          }

  772.          display_lattice(8,0,Hz1616_she,ucCursorFlag,2,16,0);      //设置速度范围
  773.          display_lattice(9,0,Hz1616_zhi,ucCursorFlag,2,16,0);   
  774.          display_lattice(10,0,Hz1616_su,ucCursorFlag,2,16,0);   
  775.          display_lattice(11,0,Hz1616_du,ucCursorFlag,2,16,0);
  776.          display_lattice(12,0,Hz1616_fan,ucCursorFlag,2,16,0);   
  777.          display_lattice(13,0,Hz1616_wei,ucCursorFlag,2,16,0);
  778.                           
  779.      }

  780.      if(ucWd1Part3Update==1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容
  781.      {
  782.          ucWd1Part3Update=0; //及时清零,避免一直更新

  783.          if(ucWd1Part==3) //被选中
  784.          {
  785.              ucCursorFlag=1; //反显 显示
  786.          }
  787.          else //没被选中
  788.          {
  789.              ucCursorFlag=0; //正常 显示
  790.          }

  791.          display_lattice(8,16,Hz1616_she,ucCursorFlag,2,16,0);    //设置频率范围
  792.          display_lattice(9,16,Hz1616_zhi,ucCursorFlag,2,16,0);   
  793.          display_lattice(10,16,Hz1616_pin,ucCursorFlag,2,16,0);   
  794.          display_lattice(11,16,Hz1616_lv,ucCursorFlag,2,16,0);
  795.          display_lattice(12,16,Hz1616_fan,ucCursorFlag,2,16,0);   
  796.          display_lattice(13,16,Hz1616_wei,ucCursorFlag,2,16,0);                           
  797.      }

  798.   
  799. }


  800. void wd2(void)  //窗口2 设置时间
  801. {
  802.     unsigned char ucAnyNumber_1; //分解变量的个位
  803.     unsigned char ucAnyNumber_10; //分解变量的十位


  804.     unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
  805.     unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址

  806.     unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

  807.     if(ucWd2Update==1)  //窗口2整屏更新,里面只放那些不用经常刷新显示的内容
  808.     {
  809.         ucWd2Update=0;  //及时清零,避免一直更新

  810.         ucWd2Part1Update=1; //激活窗口2的第1个局部更新显示变量,这里在前面数码管显示框架上有所改进
  811.         ucWd2Part2Update=1; //激活窗口2的第2个局部更新显示变量,这里在前面数码管显示框架上有所改进

  812.         display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  813.         clear_all_canvas();  //把画布全部清零
  814.         insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布


  815.         display_lattice(2,0,Hz1616_she,0,2,16,0);    //设置时间。这些内容不用经常更新,只有在切换窗口的时候才更新显示
  816.         display_lattice(3,0,Hz1616_zhi,0,2,16,0);
  817.         display_lattice(4,0,Hz1616_shi,0,2,16,0);
  818.         display_lattice(5,0,Hz1616_jian,0,2,16,0);


  819.                 display_lattice(0,16,Hz1616_shi,0,2,16,0);    //时间上限
  820.         display_lattice(1,16,Hz1616_jian,0,2,16,0);   
  821.         display_lattice(2,16,Hz1616_shang,0,2,16,0);   
  822.         display_lattice(3,16,Hz1616_xian,0,2,16,0);

  823.         display_lattice(8,0,Hz1616_shi,0,2,16,0);  //时间下限
  824.         display_lattice(9,0,Hz1616_jian,0,2,16,0);   
  825.         display_lattice(10,0,Hz1616_xia,0,2,16,0);   
  826.         display_lattice(11,0,Hz1616_xian,0,2,16,0);

  827.     }

  828.     if(ucWd2Part1Update==1) //窗口2的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
  829.     {
  830.         ucWd2Part1Update=0; //及时清零,避免一直更新

  831.         if(ucWd2Part==1) //被选中
  832.         {
  833.              ucCursorFlag=1; //反显 显示
  834.         }
  835.         else //没被选中
  836.         {
  837.              ucCursorFlag=0; //正常 显示
  838.         }

  839.         if(ucTimeH>=10) //有2位数以上
  840.         {
  841.              ucAnyNumber_10=ucTimeH/10;  //十位
  842.         }
  843.         else //否则显示空
  844.         {
  845.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  846.         }

  847.         ucAnyNumber_1=ucTimeH%10/1;  //个位

  848.    
  849.         p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  850.         p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  851.         insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  852.         insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  853.         display_lattice(4,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量

  854.                           
  855.     }

  856.     if(ucWd2Part2Update==1) //窗口2的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  857.     {
  858.          ucWd2Part2Update=0; //及时清零,避免一直更新

  859.          if(ucWd2Part==2) //被选中
  860.          {
  861.              ucCursorFlag=1; //反显 显示
  862.          }
  863.          else //没被选中
  864.          {
  865.              ucCursorFlag=0; //正常 显示
  866.          }

  867.          if(ucTimeL>=10) //有2位数以上
  868.          {
  869.              ucAnyNumber_10=ucTimeL/10;  //十位
  870.          }
  871.          else //否则显示空
  872.          {
  873.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  874.          }

  875.          ucAnyNumber_1=ucTimeL%10/1;  //个位

  876.    
  877.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  878.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  879.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  880.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  881.          display_lattice(12,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  882.                           
  883.      }

  884.    
  885. }



  886. void wd3(void)  //窗口3 设置速度
  887. {
  888.     unsigned char ucAnyNumber_1; //分解变量的个位
  889.     unsigned char ucAnyNumber_10; //分解变量的十位


  890.     unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
  891.     unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址

  892.     unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

  893.     if(ucWd3Update==1)  //窗口3整屏更新,里面只放那些不用经常刷新显示的内容
  894.     {
  895.         ucWd3Update=0;  //及时清零,避免一直更新

  896.         ucWd3Part1Update=1; //激活窗口3的第1个局部更新显示变量,这里在前面数码管显示框架上有所改进
  897.         ucWd3Part2Update=1; //激活窗口3的第2个局部更新显示变量,这里在前面数码管显示框架上有所改进

  898.         display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  899.         clear_all_canvas();  //把画布全部清零
  900.         insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布


  901.         display_lattice(2,0,Hz1616_she,0,2,16,0);    //设置速度。这些内容不用经常更新,只有在切换窗口的时候才更新显示
  902.         display_lattice(3,0,Hz1616_zhi,0,2,16,0);
  903.         display_lattice(4,0,Hz1616_su,0,2,16,0);
  904.         display_lattice(5,0,Hz1616_du,0,2,16,0);


  905.                 display_lattice(0,16,Hz1616_su,0,2,16,0);    //速度上限
  906.         display_lattice(1,16,Hz1616_du,0,2,16,0);   
  907.         display_lattice(2,16,Hz1616_shang,0,2,16,0);   
  908.         display_lattice(3,16,Hz1616_xian,0,2,16,0);

  909.         display_lattice(8,0,Hz1616_su,0,2,16,0);  //速度下限
  910.         display_lattice(9,0,Hz1616_du,0,2,16,0);   
  911.         display_lattice(10,0,Hz1616_xia,0,2,16,0);   
  912.         display_lattice(11,0,Hz1616_xian,0,2,16,0);

  913.     }

  914.     if(ucWd3Part1Update==1) //窗口3的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
  915.     {
  916.         ucWd3Part1Update=0; //及时清零,避免一直更新

  917.         if(ucWd3Part==1) //被选中
  918.         {
  919.              ucCursorFlag=1; //反显 显示
  920.         }
  921.         else //没被选中
  922.         {
  923.              ucCursorFlag=0; //正常 显示
  924.         }

  925.         if(ucSpeedH>=10) //有2位数以上
  926.         {
  927.              ucAnyNumber_10=ucSpeedH/10;  //十位
  928.         }
  929.         else //否则显示空
  930.         {
  931.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  932.         }

  933.         ucAnyNumber_1=ucSpeedH%10/1;  //个位

  934.    
  935.         p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  936.         p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  937.         insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  938.         insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  939.         display_lattice(4,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量

  940.                           
  941.     }

  942.     if(ucWd3Part2Update==1) //窗口3的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  943.     {
  944.          ucWd3Part2Update=0; //及时清零,避免一直更新

  945.          if(ucWd3Part==2) //被选中
  946.          {
  947.              ucCursorFlag=1; //反显 显示
  948.          }
  949.          else //没被选中
  950.          {
  951.              ucCursorFlag=0; //正常 显示
  952.          }

  953.          if(ucSpeedL>=10) //有2位数以上
  954.          {
  955.              ucAnyNumber_10=ucSpeedL/10;  //十位
  956.          }
  957.          else //否则显示空
  958.          {
  959.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  960.          }

  961.          ucAnyNumber_1=ucSpeedL%10/1;  //个位

  962.    
  963.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  964.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  965.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  966.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  967.          display_lattice(12,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  968.                           
  969.      }

  970.    
  971. }



  972. void wd4(void)  //窗口4 设置频率
  973. {
  974.     unsigned char ucAnyNumber_1; //分解变量的个位
  975.     unsigned char ucAnyNumber_10; //分解变量的十位


  976.     unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
  977.     unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址

  978.     unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的

  979.     if(ucWd4Update==1)  //窗口4整屏更新,里面只放那些不用经常刷新显示的内容
  980.     {
  981.         ucWd4Update=0;  //及时清零,避免一直更新

  982.         ucWd4Part1Update=1; //激活窗口4的第1个局部更新显示变量,这里在前面数码管显示框架上有所改进
  983.         ucWd4Part2Update=1; //激活窗口4的第2个局部更新显示变量,这里在前面数码管显示框架上有所改进

  984.         display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  985.         clear_all_canvas();  //把画布全部清零
  986.         insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布


  987.         display_lattice(2,0,Hz1616_she,0,2,16,0);    //设置频率。这些内容不用经常更新,只有在切换窗口的时候才更新显示
  988.         display_lattice(3,0,Hz1616_zhi,0,2,16,0);
  989.         display_lattice(4,0,Hz1616_pin,0,2,16,0);
  990.         display_lattice(5,0,Hz1616_lv,0,2,16,0);


  991.                 display_lattice(0,16,Hz1616_pin,0,2,16,0);    //频率上限
  992.         display_lattice(1,16,Hz1616_lv,0,2,16,0);   
  993.         display_lattice(2,16,Hz1616_shang,0,2,16,0);   
  994.         display_lattice(3,16,Hz1616_xian,0,2,16,0);

  995.         display_lattice(8,0,Hz1616_pin,0,2,16,0);  //频率下限
  996.         display_lattice(9,0,Hz1616_lv,0,2,16,0);   
  997.         display_lattice(10,0,Hz1616_xia,0,2,16,0);   
  998.         display_lattice(11,0,Hz1616_xian,0,2,16,0);

  999.     }

  1000.     if(ucWd4Part1Update==1) //窗口4的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
  1001.     {
  1002.         ucWd4Part1Update=0; //及时清零,避免一直更新

  1003.         if(ucWd4Part==1) //被选中
  1004.         {
  1005.              ucCursorFlag=1; //反显 显示
  1006.         }
  1007.         else //没被选中
  1008.         {
  1009.              ucCursorFlag=0; //正常 显示
  1010.         }

  1011.         if(ucFreqH>=10) //有2位数以上
  1012.         {
  1013.              ucAnyNumber_10=ucFreqH/10;  //十位
  1014.         }
  1015.         else //否则显示空
  1016.         {
  1017.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  1018.         }

  1019.         ucAnyNumber_1=ucFreqH%10/1;  //个位

  1020.    
  1021.         p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  1022.         p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  1023.         insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  1024.         insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  1025.         display_lattice(4,16,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量

  1026.                           
  1027.     }

  1028.     if(ucWd4Part2Update==1) //窗口4的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  1029.     {
  1030.          ucWd4Part2Update=0; //及时清零,避免一直更新

  1031.          if(ucWd4Part==2) //被选中
  1032.          {
  1033.              ucCursorFlag=1; //反显 显示
  1034.          }
  1035.          else //没被选中
  1036.          {
  1037.              ucCursorFlag=0; //正常 显示
  1038.          }

  1039.          if(ucFreqL>=10) //有2位数以上
  1040.          {
  1041.              ucAnyNumber_10=ucFreqL/10;  //十位
  1042.          }
  1043.          else //否则显示空
  1044.          {
  1045.              ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  1046.          }

  1047.          ucAnyNumber_1=ucFreqL%10/1;  //个位

  1048.    
  1049.          p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址
  1050.          p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址


  1051.          insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布
  1052.          insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布

  1053.          display_lattice(12,0,ucCanvasBuffer,0,4,16,0);   //显示整屏的画布,最后的参数0是偏移量
  1054.                           
  1055.      }

  1056.    
  1057. }


  1058. void clear_all_canvas(void)  //把画布全部清零
  1059. {
  1060.    unsigned int j=0;
  1061.    unsigned int i=0;

  1062.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  1063.    {
  1064.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  1065.       {
  1066.                   ucCanvasBuffer[j*4+i]=0x00;
  1067.       }
  1068.    }         

  1069. }





  1070. void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
  1071. {   

  1072.     unsigned char x,y;
  1073.     WriteCommand(0x34);  //关显示缓冲指令            
  1074.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  1075.     y=0;
  1076.     while(y<32)  //y轴的范围0至31
  1077.     {
  1078.          WriteCommand(y+0x80);        //垂直地址
  1079.          WriteCommand(0x80);          //水平地址
  1080.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  1081.          {  
  1082.             LCDWriteData(ucFillDate);
  1083.          }
  1084.          y++;
  1085.     }
  1086.     WriteCommand(0x36); //开显示缓冲指令

  1087. }

  1088. /* 注释五:
  1089. * 把字模插入画布的函数.
  1090. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  1091. * 第1,2个参数x,y是在画布中的坐标体系。
  1092. * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
  1093. * 第3个参数*ucArray是字模的数组。
  1094. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  1095. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  1096. */
  1097. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  1098. {
  1099.    unsigned int j=0;
  1100.    unsigned int i=0;
  1101.    unsigned char ucTemp;
  1102.    for(j=0;j<y_amount;j++)
  1103.    {
  1104.       for(i=0;i<x_amount;i++)
  1105.       {
  1106.                    ucTemp=ucArray[j*x_amount+i];
  1107.                    if(ucFbFlag==0)
  1108.                    {
  1109.               ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
  1110.                    }
  1111.                    else
  1112.                    {
  1113.               ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
  1114.                    }
  1115.       }
  1116.    }         

  1117. }

  1118. /* 注释六:
  1119. * 显示任意点阵函数.
  1120. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  1121. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  1122. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  1123. * 第3个参数*ucArray是字模的数组。
  1124. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  1125. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  1126. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  1127. */
  1128. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
  1129. {
  1130.    unsigned int j=0;
  1131.    unsigned int i=0;
  1132.    unsigned char ucTemp;

  1133. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  1134. //  WriteCommand(0x34);  //关显示缓冲指令            
  1135. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  1136.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  1137.    {
  1138.        WriteCommand(y+j+0x80);        //垂直地址
  1139.        WriteCommand(x+0x80);          //水平地址
  1140.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  1141.        {
  1142.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  1143.            if(ucFbFlag==1)  //反白显示
  1144.            {
  1145.                ucTemp=~ucTemp;
  1146.            }
  1147.            LCDWriteData(ucTemp);
  1148.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  1149.       }
  1150.    }
  1151.    WriteCommand(0x36); //开显示缓冲指令
  1152. }




  1153. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  1154. {
  1155.         unsigned char i;
  1156.         for ( i = 0; i < 8; i++ )
  1157.         {
  1158.                 if ( (ucData << i) & 0x80 )
  1159.                 {
  1160.                         LCDSID_dr = 1;
  1161.                 }
  1162.                 else
  1163.                 {
  1164.                         LCDSID_dr = 0;
  1165.                 }
  1166.                 LCDCLK_dr = 0;
  1167.                 LCDCLK_dr = 1;
  1168.         }
  1169. }

  1170. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  1171. {
  1172.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  1173.         SendByteToLcd( ucWData & 0xf0 );
  1174.         SendByteToLcd( (ucWData << 4) & 0xf0);
  1175. }


  1176. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  1177. {

  1178.         LCDCS_dr = 0;
  1179.         LCDCS_dr = 1;
  1180.         SPIWrite(ucCommand, 0);
  1181.         delay_short(90);
  1182. }

  1183. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  1184. {
  1185.         LCDCS_dr = 0;
  1186.         LCDCS_dr = 1;
  1187.         SPIWrite(ucData, 1);
  1188. }

  1189. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  1190. {
  1191.         LCDRST_dr = 1;  //复位
  1192.         LCDRST_dr = 0;
  1193.         LCDRST_dr = 1;
  1194. }



  1195. void delay_short(unsigned int uiDelayShort) //延时函数
  1196. {
  1197.    unsigned int i;  
  1198.    for(i=0;i<uiDelayShort;i++)
  1199.    {
  1200.      ;  
  1201.    }
  1202. }


  1203. void delay_long(unsigned int uiDelayLong)
  1204. {
  1205.    unsigned int i;
  1206.    unsigned int j;
  1207.    for(i=0;i<uiDelayLong;i++)
  1208.    {
  1209.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  1210.           {
  1211.              ; //一个分号相当于执行一条空语句
  1212.           }
  1213.    }
  1214. }
复制代码

总结陈词:
    我前面几节液晶屏程序的字模都是通过外围工具软件生成的,其实这款12864液晶模块本身就是自带字库,编程的时候只要在源代码里直接写入所需要的汉字或者字符,就可以自动调用相对应的字库了。但是细心的网友一定会问,为什么在源代码上直接写入某个汉字就可以调用到这个汉字的字库?在这个过程中,C51编译器到底还干了哪些鲜为人知的好事?欲知详情,请听下回分解-----液晶屏自带字库跟汉字机内码的关系。
(未完待续,下节更精彩,不要走开哦)
乐于分享,勇于质疑!
102#
 楼主| 发表于 2014-11-3 15:11:53 | 显示全部楼层
第八十节:调用液晶屏内部字库来显示汉字或字符的坐标体系和本质。

开场白:
前面章节讲的内容全部都是用自构字库的,相当于使用液晶屏的图像模式。其实这个款12864液晶屏的驱动芯片是st7920,它内部是自带16x16字库的,可以显示16x16的汉字或者8x16的字符。这一节开始就跟大家讲讲这方面的内容。要教会大家四个知识点:
第一个:内部字库的真实坐标体系的本质。当我们用内部字库的时候,它的坐标体系跟前面讲的自造字库坐标不一样,不再是256x32的液晶屏。它还原成为128x64的液晶屏,横坐标x轴坐标没办法精确到每个点,只能以16个点(2个字节)为一个单位,因此128个点的x轴坐标范围是0至8。而y轴的坐标也是以16个点(2个字节)为一个单位,因此64个点的x轴坐标范围是0至3。把12864液晶屏分成4行8列,每个数代表一个坐标点。
第二个:在使用内部字库时,C51编译器暗地里干了啥?如果使用液晶屏内部自带字库,编程的时候只要在源代码里直接写入所需要的汉字或者字符,就可以自动调用相对应的字库了。但是细心的网友一定会问,为什么在源代码上直接写入某个汉字就可以调用到这个汉字的字库?其实,表面上我们写下具体的某个汉字或者字符,但是C51编译器会自动对数组内的汉字翻译成 机内码(2字节),会自动对数组内的字符翻译成 ASCII码(1字节)。
第三个:12864的控制芯片st7920内部有两套驱动显示指令方式,一种是前面章节讲的自构字库模式,也是图像模式。另外一种就是本节讲的用内部字库模式。在切换模式的时候,发送命令字0x0c表示用内部字库模式,发送命令字0x36表示用自构字库模式。
第四个:12864整屏有4行8列,一共32个坐标点,每个坐标点可以显示一个16x16的汉字,但是在显示8x16字符时候,必须一次显示2个字符筹够16x16的点阵。例如,只想达到显示一个字符的时候,应该在另外一个空位置上显示空字符来填充。

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


(1)硬件平台:基于坚鸿51单片机学习板。

(2)实现功能:
     开机上电后,液晶屏第一行调用直接汉字书写方式的数组来显示(馒头V5)的内容。第四行调用机内码和ASCII码的数组来显示(馒头V5)的内容。

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

  2. sbit  LCDCS_dr  = P1^6;  //片选线
  3. sbit  LCDSID_dr = P1^7;  //串行数据线
  4. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  5. sbit  LCDRST_dr = P3^4;  //复位线

  6. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  7. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  8. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  9. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  10. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  11. void display_clear(void); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。

  12. void display_hz1616(unsigned int x,unsigned int y,const unsigned char  *ucArray);
  13. void display_double_zf816(unsigned int x,unsigned int y,const unsigned char  *ucArray1,const unsigned char  *ucArray2);

  14. void delay_short(unsigned int uiDelayshort); //延时

  15. /* 注释一:内部字库的真实坐标体系的本质。
  16. * 当我们用内部字库的时候,它的坐标体系跟前面讲的自造字库坐标不一样,不再是256x32的液晶屏。
  17. * 它还原成为128x64的液晶屏,横坐标x轴坐标没办法精确到每个点,只能以16个点(2个字节)为一个单位,
  18. * 因此128个点的x轴坐标范围是0至8。而y轴的坐标也是以16个点(2个字节)为一个单位,因此64个点的x轴
  19. * 坐标范围是0至3。以下是坐标地址的位置编码。把12864液晶屏分成4行8列,每个数代表一个坐标点,
  20. * 用深究具体含义,液晶驱动芯片ST7920的手册上有提到。
  21. */
  22. code unsigned char  ucAddrTable[]=  //调用内部字库时,液晶屏的坐标体系,位置编码,是驱动内容,读者可以不用深究它的含义。
  23. {     
  24. 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
  25. 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
  26. 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
  27. 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
  28. };


  29. /* 注释二:在使用内部字库时,C51编译器暗地里干了啥?
  30. * 如果使用液晶屏内部自带字库,以下编程的时候只要在源代码里直接写入所需要的汉字或者字符,
  31. * 就可以自动调用相对应的字库了。但是细心的网友一定会问,为什么在源代码上直接写入某个汉字
  32. * 就可以调用到这个汉字的字库?其实,表面上我们写下具体的某个汉字或者字符,但是C51编译器
  33. * 会自动对数组内的汉字翻译成 机内码(2字节),会自动对数组内的字符翻译成 ASCII码(1字节)。
  34. * 本节程序会做这个实验来验证它。以下两种书写方式不一样,但本质是一样的。
  35. */

  36. code unsigned char Hz1616_man[]="馒"; //对于数组内的汉字,编译会自动翻译成 机内码(2字节)
  37. code unsigned char JN1616_man[]=  //机内码  馒  网上有很多把汉字或者字符转换成相关编码的工具软件
  38. {
  39. 0xC2,
  40. 0xF8,
  41. };

  42. code unsigned char Hz1616_tou[]="头"; //对于数组内的汉字,编译会自动翻译成 机内码(2字节)
  43. code unsigned char JN1616_tou[]=  //机内码  头  网上有很多把汉字或者字符转换成相关编码的工具软件
  44. {
  45. 0xCD,
  46. 0xB7,
  47. };

  48. code unsigned char Zf816_V[]="V";     //对于数组内的字符,编译会自动翻译成 ASCII码(1字节)
  49. code unsigned char ASCII816_V[]= //ASCII码  V  网上有很多把汉字或者字符转换成相关编码的工具软件
  50. {
  51. 0x56,
  52. };

  53. code unsigned char Zf816_5[]="5";     //对于数组内的字符,编译会自动翻译成 ASCII码(1字节)
  54. code unsigned char ASCII816_5[]= //ASCII码  5  网上有很多把汉字或者字符转换成相关编码的工具软件
  55. {
  56. 0x35,
  57. };


  58. code unsigned char Zf816_nc[]=" ";     //对于数组内的字符,编译会自动翻译成 ASCII码(1字节)
  59. code unsigned char ASCII816_nc[]= //ASCII码  空字符  网上有很多把汉字或者字符转换成相关编码的工具软件
  60. {
  61. 0x20,
  62. };


  63. void main()
  64.   {
  65.         LCDInit(); //初始化12864 内部包含液晶模块的复位

  66. /* 注释三:
  67. * 12864的控制芯片st7920内部有两套驱动显示指令方式,一种是前面章节讲的自构字库模式,也是图像模式。
  68. * 另外一种就是本节讲的用内部字库模式。以下是切换模式的命令,命令字0x0c表示用内部字库模式。
  69. * 命令字0x36表示用自构字库模式。
  70. */
  71.         WriteCommand(0x0C); //命令字0x0c表示用内部字库模式。命令字0x36表示用自构字库模式。

  72.         display_clear(); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。


  73.         display_hz1616(0,0,Hz1616_man);  //第一行,调用直接汉字书写方式的数组来显示(馒头V5),
  74.         display_hz1616(1,0,Hz1616_tou);
  75.         display_double_zf816(2,0,Zf816_V,Zf816_5);

  76.         display_hz1616(0,3,JN1616_man);  //第四行,调用机内码和ASCII码的数组来显示(馒头V5),
  77.         display_hz1616(1,3,JN1616_tou);
  78.         display_double_zf816(2,3,ASCII816_V,Zf816_5);


  79.         while(1)  
  80.         {
  81.            ;
  82.         }

  83. }


  84. /* 注释四:在一个坐标点显示1个内部字库汉字的函数
  85. * 第1,2个参数x,y是坐标体系。x的范围是0至8,y的范围是0至3.
  86. * 第3个参数*ucArray是汉字机内码,是有2个字节的数组。
  87. */
  88. void display_hz1616(unsigned int x,unsigned int y,const unsigned char  *ucArray)
  89. {
  90.     WriteCommand(0x30);   //基本指令集
  91.         WriteCommand(ucAddrTable[8*y+x]);        //起始位置
  92.         LCDWriteData(ucArray[0]);
  93.         LCDWriteData(ucArray[1]);
  94. }

  95. /* 注释五:在一个坐标点显示2个内部字库字符的函数
  96. * 注意,由于一个坐标点是16x16点阵,而一个字符是8x16点阵的,所以务必要显示2个字符筹够1个坐标点。
  97. * 第1,2个参数x,y是坐标体系。x的范围是0至8,y的范围是0至3.
  98. * 第3个参数*ucArray1是左边第1个字符ASCII码,是有1个字节的数组。
  99. * 第4个参数*ucArray2是右边第2个字符ASCII码,是有1个字节的数组。
  100. */
  101. void display_double_zf816(unsigned int x,unsigned int y,const unsigned char *ucArray1,const unsigned char  *ucArray2)
  102. {
  103.     WriteCommand(0x30);   //基本指令集
  104.         WriteCommand(ucAddrTable[8*y+x]);        //起始位置
  105.         LCDWriteData(ucArray1[0]);
  106.         LCDWriteData(ucArray2[0]);
  107. }


  108. void display_clear(void) // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。
  109. {   

  110.     unsigned int i,j;
  111.         for(i=0;i<4;i++)
  112.         {
  113.                 for(j=0;j<8;j++)
  114.                 {
  115.                    display_double_zf816(j,i,Zf816_nc,ASCII816_nc);  //Zf816_nc与ASCII816_nc本质是一样的,只是书写方式不一样。
  116.                 }
  117.         }


  118. }

  119. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  120. {
  121.         unsigned char i;
  122.         for ( i = 0; i < 8; i++ )
  123.         {
  124.                 if ( (ucData << i) & 0x80 )
  125.                 {
  126.                         LCDSID_dr = 1;
  127.                 }
  128.                 else
  129.                 {
  130.                         LCDSID_dr = 0;
  131.                 }
  132.                 LCDCLK_dr = 0;
  133.                 LCDCLK_dr = 1;
  134.         }
  135. }

  136. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  137. {
  138.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  139.         SendByteToLcd( ucWData & 0xf0 );
  140.         SendByteToLcd( (ucWData << 4) & 0xf0);
  141. }


  142. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  143. {

  144.         LCDCS_dr = 0;
  145.         LCDCS_dr = 1;
  146.         SPIWrite(ucCommand, 0);
  147.         delay_short(90);
  148. }

  149. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  150. {
  151.         LCDCS_dr = 0;
  152.         LCDCS_dr = 1;
  153.         SPIWrite(ucData, 1);
  154. }

  155. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  156. {
  157.         LCDRST_dr = 1;  //复位
  158.         LCDRST_dr = 0;
  159.         LCDRST_dr = 1;
  160. }



  161. void delay_short(unsigned int uiDelayShort) //延时函数
  162. {
  163.    unsigned int i;  
  164.    for(i=0;i<uiDelayShort;i++)
  165.    {
  166.      ;  
  167.    }
  168. }
复制代码

总结陈词:
    通过本节的实验,我们发现汉字的识别本质是机内码,字符的识别本质是ASCII码。不管是机内码还是ASCII码,这些都是16进制的数字,也就是我们手机平时接收和发送的信息本质都是这些数字编码,但是机内码是2个字节,ASCII码是1个字节,如果在一串随机的信息中,同时包含汉字和字符两种数字信息,我们的程序又该如何能筛选和识别它们,会不会把机内码和ASCII码搞混乱了?不会的。其实这两种编码都是有规律可以筛选识别的,欲知详情,请听下回分解-----液晶屏显示串口发送过来的任意汉字和字符。

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

乐于分享,勇于质疑!
103#
 楼主| 发表于 2014-11-6 15:49:23 | 显示全部楼层
第八十一节:液晶屏显示串口发送过来的任意汉字和字符。

开场白:
通过上一节的学习,我们发现汉字的识别本质是机内码,字符的识别本质是ASCII码。不管是机内码还是ASCII码,这些都是16进制的数字,也就是我们手机平时接收和发送的信息本质都是这些数字编码,但是机内码是2个字节,ASCII码是1个字节,如果在一串随机的信息中,同时包含汉字和字符两种数字信息,我们的程序又该如何能筛选和识别它们,会不会把机内码和ASCII码搞混乱了?这一节要教大家三个知识点:
第一个:ASCII码与汉字机内码不一样的规律是,ASCII码都是小于128(0x80)的,根据这个特点可以编程序把它们区分开来。
第二个:当任意一串信息中既包含汉字机内码,又包含字符ASCII码时,并且当ASCII码左右相邻个数是以奇数存在的时候,如何巧妙地插入填充空格字符0x20使它们能够符合一个坐标点显示2个字符的要求。
第三个:本节程序串口部分是在第39节内容基础上移植修改而成,本节程序中多添加了如何通过结束标志0x0D 0x0A来提取有效数据的内容,读者可以学习一下其中的框架。

具体内容,请看源代码讲解。
1)硬件平台:基于朱兆祺51单片机学习板。

2)实现功能:
     开机上电后,液晶屏第1行显示“请发送信息”。 任意时刻,从电脑串口调试助手根据以下协议要求,发送一串不超过24个汉字或者字符的信息,液晶屏就实时把这些信息显示在第2,3,4行。并且蜂鸣器会鸣叫一声表示数据接收正确。
波特率是:9600
通讯协议:EB 00 55  XX XX XXXX …XX XX 0D 0A
最前面3个字节EB 00 55 表示数据头。
最后面2个字节0D 0A表示信息的结束标志。
中间的XX是机内码和ASCII码信息。比如:要发送“曹健1人学习51单片机”的信息,它们对应的指令是:
EB 00 55 B2 DC BD A1 31 C8 CB D1 A7 CF B0 3531 B5 A5 C6 AC BB FA 0D 0A

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


  2. /* 注释一:
  3. * 本程序的串口那部分内容是从《第三十九节:判断数据头来接收一串数据的串口通用程序框架。》
  4. * 移植过来的,但是以下要把接收缓冲区的数据从10改成60.同时,协议后面多增加了数据结束标志0x0d 0x0a。
  5. */

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

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

  9. sbit  LCDCS_dr  = P1^6;  //片选线
  10. sbit  LCDSID_dr = P1^7;  //串行数据线
  11. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  12. sbit  LCDRST_dr = P3^4;  //复位线

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

  14. void initial_myself(void);   
  15. void initial_peripheral(void);
  16. void delay_long(unsigned int uiDelaylong);
  17. void T0_time(void);  //定时中断函数
  18. void usart_receive(void); //串口接收中断函数
  19. void usart_service(void);  //串口服务程序,在main函数里

  20. void display_service(void); //显示服务程序,在main函数里
  21. void empty_diaplay_buffer(void); //把显示缓冲区全部填充空格字符0x20
  22. void diaplay_all_buffer(void); //显示第2,3,4行全部缓冲区的内容

  23. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  24. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  25. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  26. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  27. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  28. void display_clear(void); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。
  29. void display_double_code(unsigned int x,unsigned int y,const unsigned char ucArray1,const unsigned char  ucArray2); //在一个坐标点显示1个汉字或者2个字符的函数
  30. void delay_short(unsigned int uiDelayshort); //延时


  31. code unsigned char  ucAddrTable[]=  //调用内部字库时,液晶屏的坐标体系,位置编码,是驱动内容,读者可以不用深究它的含义。
  32. {     
  33. 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
  34. 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
  35. 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
  36. 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
  37. };

  38. code unsigned char JN1616_qing[]=  //机内码  请
  39. {
  40. 0xC7,0xEB, //请
  41. };

  42. code unsigned char JN1616_fa[]=  //机内码  发
  43. {
  44. 0xB7,0xA2,
  45. };

  46. code unsigned char JN1616_song[]=  //机内码  送
  47. {
  48. 0xCB,0xCD,
  49. };

  50. code unsigned char JN1616_xin[]=  //机内码  信
  51. {
  52. 0xD0,0xC5,
  53. };

  54. code unsigned char JN1616_xi[]=  //机内码  息
  55. {
  56. 0xCF,0xA2,
  57. };

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

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

  64. unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  65. unsigned char ucWd1Part1Update=0; //窗口1的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零

  66. unsigned char ucDispplayBuffer[48]; //第2,3,4行显示内容的缓冲区

  67. void main()
  68.   {
  69.         initial_myself();  
  70.         delay_long(100);   
  71.         initial_peripheral();

  72.         while(1)  
  73.         {
  74.             usart_service();  //串口服务程序
  75.                         display_service(); //显示服务程序
  76.         }

  77. }



  78. /* 注释二:在一个坐标点显示1个汉字或者2个字符的函数
  79. * 第1,2个参数x,y是坐标体系。x的范围是0至8,y的范围是0至3.
  80. * 第3个参数ucArray1是第1个汉字机内码或者ASCII码。
  81. * 第4个参数ucArray2是第2个汉字机内码或者ASCII码。
  82. */
  83. void display_double_code(unsigned int x,unsigned int y,const unsigned char ucArray1,const unsigned char  ucArray2)
  84. {
  85.     WriteCommand(0x30);   //基本指令集
  86.     WriteCommand(ucAddrTable[8*y+x]);        //起始位置
  87.     LCDWriteData(ucArray1);
  88.     LCDWriteData(ucArray2);
  89. }


  90. void display_clear(void) // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。
  91. {   

  92.     unsigned int i,j;
  93.         for(i=0;i<4;i++)
  94.         {
  95.                 for(j=0;j<8;j++)
  96.                 {
  97.                    display_double_code(j,i,0x20,0x20);  //0x20是空格的ASCII码
  98.                 }
  99.         }


  100. }

  101. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  102. {
  103.         unsigned char i;
  104.         for ( i = 0; i < 8; i++ )
  105.         {
  106.                 if ( (ucData << i) & 0x80 )
  107.                 {
  108.                         LCDSID_dr = 1;
  109.                 }
  110.                 else
  111.                 {
  112.                         LCDSID_dr = 0;
  113.                 }
  114.                 LCDCLK_dr = 0;
  115.                 LCDCLK_dr = 1;
  116.         }
  117. }

  118. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  119. {
  120.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  121.         SendByteToLcd( ucWData & 0xf0 );
  122.         SendByteToLcd( (ucWData << 4) & 0xf0);
  123. }


  124. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  125. {

  126.         LCDCS_dr = 0;
  127.         LCDCS_dr = 1;
  128.         SPIWrite(ucCommand, 0);
  129.         delay_short(90);
  130. }

  131. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  132. {
  133.         LCDCS_dr = 0;
  134.         LCDCS_dr = 1;
  135.         SPIWrite(ucData, 1);
  136. }

  137. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  138. {
  139.         LCDRST_dr = 1;  //复位
  140.         LCDRST_dr = 0;
  141.         LCDRST_dr = 1;
  142. }


  143. void empty_diaplay_buffer(void) //把显示缓冲区全部填充空格字符0x20
  144. {
  145.    unsigned int i;

  146.    for(i=0;i<48;i++)
  147.    {
  148.       ucDispplayBuffer[i]=0x20; //第2,3,4行显示内容的缓冲区全部填充0x20空格字符
  149.    }

  150. }

  151. void diaplay_all_buffer(void) //显示第2,3,4行全部缓冲区的内容
  152. {
  153.    unsigned int i,j;

  154.    for(i=0;i<3;i++) //i代表行数
  155.    {
  156.       for(j=0;j<8;j++) //j代表某行的某个坐标在第几列
  157.       {
  158.          display_double_code(j,i+1,ucDispplayBuffer[i*16+j*2],ucDispplayBuffer[i*16+j*2+1]); //这里的16代表一行可以显示16个字符
  159.       }
  160.    }

  161. }


  162. void display_service(void) //显示服务程序,在main函数里
  163. {
  164.   if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
  165.     {
  166.         ucWd1Update=0;  //及时清零,避免一直更新

  167.         ucWd1Part1Update=1; //激活窗口1的第1个局部更新显示变量

  168.         display_clear(); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。

  169.                 //显示第一行固定的内容:请发送信息
  170.         display_double_code(1,0,JN1616_qing[0],JN1616_qing[1]);      //请
  171.         display_double_code(2,0,JN1616_fa[0],JN1616_fa[1]);          //发
  172.         display_double_code(3,0,JN1616_song[0],JN1616_song[1]);      //送
  173.         display_double_code(4,0,JN1616_xin[0],JN1616_xin[1]);        //信
  174.         display_double_code(5,0,JN1616_xi[0],JN1616_xi[1]);          //息

  175.     }

  176.     if(ucWd1Part1Update==1) //窗口1的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
  177.     {
  178.         ucWd1Part1Update=0; //及时清零,避免一直更新

  179.         diaplay_all_buffer(); //显示第2,3,4行全部缓冲区的内容
  180.         }
  181. }


  182. /* 注释三:
  183. * 以下有效信息截取和如何判断机内码与ASCII码是本程序的核心,请仔细看讲解。
  184. * 凡是ASCII码都是小于0x80(128)的,根据这个特点可以把ASCII码和机内码分离出来,
  185. * 同时,由于液晶屏的1个坐标必须显示2个编码,对于单个存在的ASCII码,我们要在
  186. * 它的右边多插入一个空格字符0x20。至于如何插入空格0x20字符,请看以下代码。
  187. */
  188. void usart_service(void)  //串口服务程序,在main函数里
  189. {
  190.      unsigned int i;
  191.          unsigned int uiCodeCnt; //统计接收的有效编码数量
  192.          unsigned int uiCodeYu;  //对uiCodeCnt求2的余数,方便识别是否是1个ASCII码相邻
  193.      if(uiSendCnt>=const_receive_time&&ucSendLock==1) //说明超过了一定的时间内,再也没有新数据从串口来
  194.      {


  195.             ucSendLock=0;    //处理一次就锁起来,不用每次都进来,除非有新接收的数据
  196.             uiRcMoveIndex=0; //由于是判断数据头,所以下标移动变量从数组的0开始向最尾端移动  这个变量是用来抗干扰处理的

  197.             while(uiRcregTotal>=6&&uiRcMoveIndex<=(uiRcregTotal-6)) //这里的6表示有3个字节的数据头,至少1个有效数据,2个数据结束标志0x0d 0x0a
  198.             {
  199.                if(ucRcregBuf[uiRcMoveIndex+0]==0xeb&&ucRcregBuf[uiRcMoveIndex+1]==0x00&&ucRcregBuf[uiRcMoveIndex+2]==0x55)  //数据头eb 00 55的判断
  200.                {

  201.                               empty_diaplay_buffer(); //把显示缓冲区全部填充空格字符0x20
  202.                                   uiCodeCnt=0; //统计接收的有效编码数量清零
  203.                   for(i=0;i<(uiRcregTotal-uiRcMoveIndex-3)&&i<48;i++)//这里的3表示有3个字节的数据头。48表示最大只能接收24个汉字,一共48个字节的机内码.
  204.                                   {
  205.                       if(ucRcregBuf[uiRcMoveIndex+3+i]==0x0d&&ucRcregBuf[uiRcMoveIndex+4+i]==0x0a)  //结束标志0x0d 0x0a的判断
  206.                       {
  207.                            uiVoiceCnt=const_voice_short; //蜂鸣器发出声音,表示数据接收正确完毕
  208.                                                    ucWd1Part1Update=1; //及时更新显示第2,3,4行内容的信息
  209.                                                    break; //退出for循环
  210.                       }       
  211.                                        else  //收集有效信息编码进入显示缓冲区
  212.                                           {
  213.                                               uiCodeYu=uiCodeCnt%2; //对2求余数,用来识别相信的2个是否是机内码,否则要进行插入填充0x20处理
  214.                                                   if(uiCodeYu==1)
  215.                                                   {
  216.                                                      if(ucRcregBuf[uiRcMoveIndex+3+i]>=0x80&&ucRcregBuf[uiRcMoveIndex+3+i-1]<0x80) //如果当前的是机内码,而上一个不是机内码
  217.                                                          {
  218.                                                                  ucDispplayBuffer[uiCodeCnt]=0x20; //当前的先填充插入空格字符0x20
  219.                                                                 uiCodeCnt++;   //统计接收的有效编码数量
  220.                                                          }
  221.                                                   }
  222.                                               ucDispplayBuffer[uiCodeCnt]=ucRcregBuf[uiRcMoveIndex+3+i]; //收集有效信息编码进入显示缓冲区
  223.                                               uiCodeCnt++;   //统计接收的有效编码数量
  224.                                           }
  225.                                   }
  226.                   break;   //退出while循环
  227.                }
  228.                uiRcMoveIndex++; //因为是判断数据头,游标向着数组最尾端的方向移动
  229.            }
  230.                                          
  231.            uiRcregTotal=0;  //清空缓冲的下标,方便下次重新从0下标开始接受新数据
  232.   
  233.      }
  234.                         
  235. }


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


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

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

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


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


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

  261.    if(RI==1)  
  262.    {
  263.         RI = 0;

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


  279. void delay_short(unsigned int uiDelayShort)
  280. {
  281.    unsigned int i;  
  282.    for(i=0;i<uiDelayShort;i++)
  283.    {
  284.      ;  
  285.    }
  286. }

  287. void delay_long(unsigned int uiDelayLong)
  288. {
  289.    unsigned int i;
  290.    unsigned int j;
  291.    for(i=0;i<uiDelayLong;i++)
  292.    {
  293.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  294.           {
  295.              ; //一个分号相当于执行一条空语句
  296.           }
  297.    }
  298. }


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

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

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


  306.   //配置串口
  307.   SCON=0x50;
  308.   TMOD=0X21;
  309.   IP =0x10;  //把串口中断设置为最高优先级,必须的。
  310.   TH1=TL1=-(11059200L/12/32/9600);  //这段配置代码具体是什么意思,我也不太清楚,反正是跟串口波特率有关。
  311.   TR1=1;

  312. }

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

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


  319.    LCDInit(); //初始化12864 内部包含液晶模块的复位
  320.    WriteCommand(0x0C); //命令字0x0c表示用内部字库模式。命令字0x36表示用自构字库模式。
  321.    empty_diaplay_buffer(); //把显示缓冲区全部填充空格字符0x20
  322. }




复制代码

总结陈词:
我们现在是调用液晶屏内部字库来显示内容,如果要某行内容反显或者光标闪烁改怎么编程?欲知详情,请听下回分解-----如何在调用液晶屏内部字库时让某行内容反显或者光标闪烁。
(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
104#
 楼主| 发表于 2014-11-7 17:03:41 | 显示全部楼层
KR770906 发表于 2014-11-7 10:30
鸿哥,按键去抖动延时计数器uiKeyTimeCnt1、uiKeyTimeCnt2、uiKeyTimeCnt3……能否合并成一个变量?

好像不行。合并不好,感觉有隐患,还是分开来吧。
乐于分享,勇于质疑!
105#
 楼主| 发表于 2014-11-14 11:38:17 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-11-14 12:13 编辑

第八十二节:如何通过调用液晶屏内部字库把一个任意数值的变量显示出来。

开场白:
本来这一节打算开始讲调用液晶屏内部字库时的反显程序,但是我担心跳跃太大,恐怕很多初学者跟不上,所以多插入这一节讲讲后面菜单程序中经常用到的基本功能,在调用内部字库的情况下,如何把一个任意数值的变量显示在液晶屏上。这一节的功能需求跟前面第76节是一模一样的,只不过前面的不是用自带字库,现在的是用自带字库而已。我们还是需要做一个变量转换成ASCII码的函数,以后只要调用这个转换函数就可以了。这一节就要把这个转换函数和框架思路教给大家。

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

(1)硬件平台:
    基于坚鸿51单片机学习板。

(2)实现功能:我们定义一个char型的全局变量,把它默认初始化为218,开机上电后,能看到正中间恰好显示这个全局变量的数值218。大家也可以试着更改它的默认初始值,只要不超过char型最大数值255范围,我们就会看到它上电后显示的就是这个初始值。


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


  2. sbit  LCDCS_dr  = P1^6;  //片选线
  3. sbit  LCDSID_dr = P1^7;  //串行数据线
  4. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  5. sbit  LCDRST_dr = P3^4;  //复位线

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

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

  10. unsigned char *number_to_ASCII(unsigned char  ucBitNumber);
  11. void display_service(void); //显示服务程序,在main函数里


  12. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  13. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  14. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  15. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  16. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  17. void display_clear(void); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。
  18. void display_double_code(unsigned int x,unsigned int y,const unsigned char ucArray1,const unsigned char  ucArray2); //在一个坐标点显示1个汉字或者2个字符的函数
  19. void delay_short(unsigned int uiDelayshort); //延时


  20. code unsigned char  ucAddrTable[]=  //调用内部字库时,液晶屏的坐标体系,位置编码,是驱动内容,读者可以不用深究它的含义。
  21. {     
  22. 0x80,0x81,0x82,0x83,0x84,0x85,0x86,0x87,
  23. 0x90,0x91,0x92,0x93,0x94,0x95,0x96,0x97,
  24. 0x88,0x89,0x8a,0x8b,0x8c,0x8d,0x8e,0x8f,
  25. 0x98,0x99,0x9a,0x9b,0x9c,0x9d,0x9e,0x9f,
  26. };

  27. code unsigned char ASCII816_0[]="0";   //0  对于数组内的字符,编译会自动翻译成 ASCII码(1字节)
  28. code unsigned char ASCII816_1[]="1";   //1
  29. code unsigned char ASCII816_2[]="2";   //2
  30. code unsigned char ASCII816_3[]="3";   //3  
  31. code unsigned char ASCII816_4[]="4";   //4
  32. code unsigned char ASCII816_5[]="5";   //5  
  33. code unsigned char ASCII816_6[]="6";   //6  
  34. code unsigned char ASCII816_7[]="7";   //7
  35. code unsigned char ASCII816_8[]="8";   //8
  36. code unsigned char ASCII816_9[]="9";   //9  
  37. code unsigned char ASCII816_nc[]=" ";  //空格

  38. /* 注释一:
  39. * 以下变量就是本程序的任意变量,网友可以自己更改它的大小来测试本程序,不要超过255.
  40. */
  41. unsigned char ucAnyNumber=218;  //任意变量默认初始化为218。
  42. unsigned char ucWd1Part1Update=1; //窗口1的第1个局部更新显示变量  1代表更新显示,响应函数内部会清零


  43. void main()
  44.   {
  45.         initial_myself();  
  46.         delay_long(100);   
  47.         initial_peripheral();

  48.         while(1)  
  49.         {
  50.            display_service(); //显示服务程序
  51.         }

  52. }



  53. /* 注释二:在一个坐标点显示1个汉字或者2个字符的函数
  54. * 第1,2个参数x,y是坐标体系。x的范围是0至8,y的范围是0至3.
  55. * 第3个参数ucArray1是第1个汉字机内码或者ASCII码。
  56. * 第4个参数ucArray2是第2个汉字机内码或者ASCII码。
  57. */
  58. void display_double_code(unsigned int x,unsigned int y,const unsigned char ucArray1,const unsigned char  ucArray2)
  59. {
  60.     WriteCommand(0x30);   //基本指令集
  61.     WriteCommand(ucAddrTable[8*y+x]);        //起始位置
  62.     LCDWriteData(ucArray1);
  63.     LCDWriteData(ucArray2);
  64. }


  65. void display_clear(void) // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。
  66. {   

  67.     unsigned int i,j;
  68.         for(i=0;i<4;i++)
  69.         {
  70.                 for(j=0;j<8;j++)
  71.                 {
  72.                    display_double_code(j,i,0x20,0x20);  //0x20是空格的ASCII码
  73.                 }
  74.         }


  75. }

  76. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  77. {
  78.         unsigned char i;
  79.         for ( i = 0; i < 8; i++ )
  80.         {
  81.                 if ( (ucData << i) & 0x80 )
  82.                 {
  83.                         LCDSID_dr = 1;
  84.                 }
  85.                 else
  86.                 {
  87.                         LCDSID_dr = 0;
  88.                 }
  89.                 LCDCLK_dr = 0;
  90.                 LCDCLK_dr = 1;
  91.         }
  92. }

  93. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  94. {
  95.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  96.         SendByteToLcd( ucWData & 0xf0 );
  97.         SendByteToLcd( (ucWData << 4) & 0xf0);
  98. }


  99. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  100. {

  101.         LCDCS_dr = 0;
  102.         LCDCS_dr = 1;
  103.         SPIWrite(ucCommand, 0);
  104.         delay_short(90);
  105. }

  106. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  107. {
  108.         LCDCS_dr = 0;
  109.         LCDCS_dr = 1;
  110.         SPIWrite(ucData, 1);
  111. }

  112. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  113. {
  114.         LCDRST_dr = 1;  //复位
  115.         LCDRST_dr = 0;
  116.         LCDRST_dr = 1;
  117. }



  118. /* 注释三:
  119. * 本程序的核心转换函数。
  120. * 是可以把一位任意数字变量的函数转换成对应的ASCII码,由于ASCII码放在数组里,所以返回的是指针,代表数组的首地址。
  121. */
  122. unsigned char *number_to_ASCII(unsigned char  ucBitNumber)
  123. {
  124.         unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的ASCII码。

  125.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的ASCII码。
  126.         {
  127.             case 0:
  128.                   p_ucAnyNumber=ASCII816_0;
  129.                   break;
  130.             case 1:
  131.                   p_ucAnyNumber=ASCII816_1;
  132.                   break;
  133.             case 2:
  134.                   p_ucAnyNumber=ASCII816_2;
  135.                   break;
  136.             case 3:
  137.                   p_ucAnyNumber=ASCII816_3;
  138.                   break;
  139.             case 4:
  140.                   p_ucAnyNumber=ASCII816_4;
  141.                   break;
  142.             case 5:
  143.                   p_ucAnyNumber=ASCII816_5;
  144.                   break;
  145.             case 6:
  146.                   p_ucAnyNumber=ASCII816_6;
  147.                   break;
  148.             case 7:
  149.                   p_ucAnyNumber=ASCII816_7;
  150.                   break;
  151.             case 8:
  152.                   p_ucAnyNumber=ASCII816_8;
  153.                   break;
  154.             case 9:
  155.                   p_ucAnyNumber=ASCII816_9;
  156.                   break;
  157.             case 10:
  158.                   p_ucAnyNumber=ASCII816_nc;
  159.                   break;
  160.             default:   //如果上面的条件都不符合,那么默认指向空格ASCII码
  161.                   p_ucAnyNumber=ASCII816_nc;
  162.                   break;
  163.         }

  164.         return p_ucAnyNumber;  //返回转换结束后的指针
  165. }


  166. void display_service(void) //显示服务程序,在main函数里
  167. {
  168. /* 注释四:
  169. * 这里的局部变量用static关键词修饰,是因为这个函数一直在主函数while(1)里循环扫描,我不希望它每次进来这个函数
  170. * 都多花几条指令去初始化这些局部变量,这样会多耗掉几个指令,所以我就用static关键字避免了这种情况,让这些局部变量
  171. * 只在上电那一刻就初始化了,以后每次进来这个函数不用再初始化这些变量。
  172. */
  173.     static unsigned char ucAnyNumber_1; //分解变量的个位
  174.     static unsigned char ucAnyNumber_10; //分解变量的十位
  175.     static unsigned char ucAnyNumber_100; //分解变量的百位

  176.     static unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址
  177.     static unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址
  178.     static unsigned char *p_ucAnyNumber_100; //经过数字转换成字模后,分解变量的百位字模首地址


  179.     if(ucWd1Part1Update==1) //窗口1的第1个局部更新显示变量,里面放一些经常需要刷新显示的内容
  180.     {
  181.         ucWd1Part1Update=0; //及时清零,避免一直更新

  182.         if(ucAnyNumber>=100) //有3位数以上
  183.         {
  184.            ucAnyNumber_100=ucAnyNumber/100; //百位
  185.         }
  186.         else //否则显示空
  187.         {
  188.            ucAnyNumber_100=10;  //在下面的转换函数中,代码10表示空字模
  189.         }

  190.         if(ucAnyNumber>=10) //有2位数以上
  191.         {
  192.            ucAnyNumber_10=ucAnyNumber%100/10;  //十位
  193.         }
  194.         else //否则显示空
  195.         {
  196.            ucAnyNumber_10=10;  //在下面的转换函数中,代码10表示空字模
  197.         }

  198.         ucAnyNumber_1=ucAnyNumber%10/1;  //个位

  199.         p_ucAnyNumber_100=number_to_ASCII(ucAnyNumber_100); //把数字转换成字符ASCII码      
  200.         p_ucAnyNumber_10=number_to_ASCII(ucAnyNumber_10);   //把数字转换成字符ASCII码
  201.         p_ucAnyNumber_1=number_to_ASCII(ucAnyNumber_1);     //把数字转换成字符ASCII码

  202.         display_double_code(2,1,ASCII816_nc[0],p_ucAnyNumber_100[0]);//液晶屏的显示驱动函数  这里的ASCII816_nc[0]代表填充显示一个空格字符
  203.         display_double_code(3,1,p_ucAnyNumber_10[0],p_ucAnyNumber_1[0]);//液晶屏的显示驱动函数

  204.     }
  205. }


  206. void delay_short(unsigned int uiDelayShort)
  207. {
  208.    unsigned int i;  
  209.    for(i=0;i<uiDelayShort;i++)
  210.    {
  211.      ;  
  212.    }
  213. }

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


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

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



  229. }

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

  232.    LCDInit(); //初始化12864 内部包含液晶模块的复位
  233.    WriteCommand(0x0C); //命令字0x0c表示用内部字库模式。命令字0x36表示用自构字库模式。
  234.    display_clear(); // 清屏。4行8列的坐标点全部显示2个空字符相当于清屏了。

  235. }



复制代码

总结陈词:
在液晶屏程序里,经常要用到反显的功能来表示选中某一项菜单。在调用内部字库时,这样的驱动程序又该怎么写?欲知详情,请听下回分解-----如何在调用液晶屏内部字库时让某行内容反显。

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

乐于分享,勇于质疑!
106#
 楼主| 发表于 2014-12-3 18:13:42 | 显示全部楼层
waphaoyun 发表于 2014-12-3 16:36
鸿哥,有几天木有更新啦

这两个星期有点忙。
乐于分享,勇于质疑!
107#
 楼主| 发表于 2014-12-9 13:16:37 | 显示全部楼层
华菲 发表于 2014-12-9 12:42
等鸿哥的第九页呢

这两天一直在等待好的状态来临再动手。
乐于分享,勇于质疑!
108#
 楼主| 发表于 2014-12-17 13:08:01 | 显示全部楼层
第八十三节:矩阵键盘输入任意数字或小数点的液晶屏显示程序。

开场白:
本来这节打算讲调用液晶屏内部字库时让某行内容反显的,但是在昨天调试过程中,发现一个很奇怪的问题,当调用内部字库时,按照数据手册,我执行一条反显指令时,应该是仅仅某一行反显,但是却同时出现两行反显。比如,当我执行
       WriteCommand(0x34); //扩充指令集
       WriteCommand(0x04); //第1行反显
指令时,发现第一行和第三行反显,后来想想,我猜测这种12864的屏应该是25632折成左右半屏,左半屏在上面,右半屏在下面。经过这次经验,我觉得大家以后尽量不要用液晶屏的内部字库模式,应该用自构字库的模式(图形模式)。因为我觉得用内部字库模式的时候,这个集成的反显扩展指令不好用。而用自构字库的模式(图形模式),却可以顺心所欲的灵活运用,适合做菜单程序。
既然发现内部字库不好用,所以不再讲内部字库模式,这节仅仅接着前面第79节内容,继续讲在自构字库的模式(图形模式)下,如何通过矩阵键盘直接输入数字和小数点,就像普通的计算器一样键盘输入。这个功能表面简单,其实有以下四个地方值得注意:
第一:如何用数组接收按键输入的BCD码数据。
第二:如何限制输入参数的小数点个数和数组的有效个数。
第三:如果第0个位置是0,那么继续输入的数据直接覆盖0,否则就移位再输入。
第四:如果第0个位置是0,那么继续输入的小数点要移位输入。
要仔细了解以上提到的关键点,必须好好研究本程序中的void set_data(…)函数。同时也要温习一下之前讲的自构字库模式的液晶屏显示内容,尤其是插入画布显示的内容。

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

(1)     硬件平台:
基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,清零键对应S16,其它按键不用。

(2)     实现功能:
用矩阵键盘输入任意数字或小数点。小数点不能超过2位,一旦超过2位,再按其它按键则输入无效。有效数字也不能超过6位(包括小数点),一旦超过6位,再按其它按键则输入无效。
想重新输入,必须按S16清零按键才能重新输入。

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


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

  3. #define const_key_time  10    //按键去抖动延时的时间

  4. sbit key_sr1=P0^0; //第一行输入
  5. sbit key_sr2=P0^1; //第二行输入
  6. sbit key_sr3=P0^2; //第三行输入
  7. sbit key_sr4=P0^3; //第四行输入

  8. sbit key_dr1=P0^4; //第一列输出
  9. sbit key_dr2=P0^5; //第二列输出
  10. sbit key_dr3=P0^6; //第三列输出
  11. sbit key_dr4=P0^7; //第四列输出

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

  13. sbit  LCDCS_dr  = P1^6;  //片选线
  14. sbit  LCDSID_dr = P1^7;  //串行数据线
  15. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  16. sbit  LCDRST_dr = P3^4;  //复位线

  17. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  18. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  19. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  20. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  21. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  22. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  23. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
  24. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
  25. unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
  26. void delay_short(unsigned int uiDelayshort); //延时
  27. void delay_long(unsigned int uiDelayLong);

  28. void key_number_input(unsigned char ucKeyNumber); //输入数字按键
  29. void set_data(unsigned char ucKeyNumberTemp,unsigned char ucDotBitMax,unsigned char ucDataCntMax,unsigned char *p_ucDotCnt,unsigned char *p_ucDotBitS,unsigned char *p_ucWdPartCnt,unsigned char *p_ucSetDataBuffer);
  30. void key_delete_input(void); //删除按键

  31. void T0_time(); //定时中断函数
  32. void key_service();
  33. void key_scan(); //按键扫描函数 放在定时中断里

  34. void initial_myself();   
  35. void initial_peripheral();


  36. void lcd_display_service(void); //应用层面的液晶屏显示程序
  37. void clear_all_canvas(void);  //把画布全部清零

  38. code unsigned char Zf816_0[]=
  39. {
  40. /*--  文字:  0  --*/
  41. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  42. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  43. };

  44. code unsigned char Zf816_1[]=
  45. {
  46. /*--  文字:  1  --*/
  47. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  48. 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
  49. };

  50. code unsigned char Zf816_2[]=
  51. {
  52. /*--  文字:  2  --*/
  53. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  54. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
  55. };

  56. code unsigned char Zf816_3[]=
  57. {
  58. /*--  文字:  3  --*/
  59. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  60. 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  61. };

  62. code unsigned char Zf816_4[]=
  63. {
  64. /*--  文字:  4  --*/
  65. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  66. 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
  67. };

  68. code unsigned char Zf816_5[]=
  69. {
  70. /*--  文字:  5  --*/
  71. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  72. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  73. };

  74. code unsigned char Zf816_6[]=
  75. {
  76. /*--  文字:  6  --*/
  77. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  78. 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  79. };


  80. code unsigned char Zf816_7[]=
  81. {
  82. /*--  文字:  7  --*/
  83. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  84. 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
  85. };

  86. code unsigned char Zf816_8[]=
  87. {
  88. /*--  文字:  8  --*/
  89. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  90. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
  91. };

  92. code unsigned char Zf816_9[]=
  93. {
  94. /*--  文字:  9  --*/
  95. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  96. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
  97. };


  98. code unsigned char Zf816_nc[]=  //空字模
  99. {
  100. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  101. };

  102. code unsigned char Zf816_dot[]=  //小数点
  103. {
  104. /*--  文字:  .  --*/
  105. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  106. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,
  107. };

  108. code unsigned char Zf816_mao_hao[]=  //冒号
  109. {
  110. /*--  文字:  :  --*/
  111. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  112. 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
  113. };

  114. code unsigned char Hz1616_yi[]=
  115. {
  116. /*--  文字:  一  --*/
  117. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  118. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
  119. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  120. };

  121. code unsigned char Hz1616_xiang[]=
  122. {
  123. /*--  文字:  项  --*/
  124. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  125. 0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24,
  126. 0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00,
  127. };

  128. code unsigned char Hz1616_shu[]=
  129. {
  130. /*--  文字:  数  --*/
  131. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  132. 0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4,
  133. 0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00,
  134. };

  135. code unsigned char Hz1616_zhu[]=
  136. {
  137. /*--  文字:  组  --*/
  138. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  139. 0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08,
  140. 0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00,
  141. };

  142. /* 注释一:
  143. * 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字.
  144. *  注意,这节内容的画布跟前面章节的画布大小不一样,前面章节的横向是4个字节,这节的横向是6个字节。
  145. */
  146. unsigned char ucCanvasBuffer[]=
  147. {
  148. 0x00,0x00,0x00,0x00,0x00,0x00,  //上半屏
  149. 0x00,0x00,0x00,0x00,0x00,0x00,
  150. 0x00,0x00,0x00,0x00,0x00,0x00,
  151. 0x00,0x00,0x00,0x00,0x00,0x00,
  152. 0x00,0x00,0x00,0x00,0x00,0x00,
  153. 0x00,0x00,0x00,0x00,0x00,0x00,
  154. 0x00,0x00,0x00,0x00,0x00,0x00,
  155. 0x00,0x00,0x00,0x00,0x00,0x00,

  156. //------------上半屏和下半屏的分割线-----------

  157. 0x00,0x00,0x00,0x00,0x00,0x00,  //下半屏
  158. 0x00,0x00,0x00,0x00,0x00,0x00,
  159. 0x00,0x00,0x00,0x00,0x00,0x00,
  160. 0x00,0x00,0x00,0x00,0x00,0x00,
  161. 0x00,0x00,0x00,0x00,0x00,0x00,
  162. 0x00,0x00,0x00,0x00,0x00,0x00,
  163. 0x00,0x00,0x00,0x00,0x00,0x00,
  164. 0x00,0x00,0x00,0x00,0x00,0x00,
  165. };


  166. /* 注释二:
  167. * 以下4个变量记录一个参数的4种信息,包括小数点的数量,个数,数据的位置,数组具体值.
  168. */
  169. unsigned char ucDotCnt_1=0;  //记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
  170. unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个量如果超过规定2位,此时再按任何输入按键则无效
  171. unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。
  172. unsigned char ucDataBuffer_1[6]={0,10,10,10,10,10}; //一项的BCD码数组缓冲


  173. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  174. unsigned char ucKeySec=0;   //被触发的按键编号
  175. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  176. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  177. unsigned char ucRowRecord=1; //记录当前扫描到第几列了


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


  179. unsigned char ucWd=1; //窗口变量
  180. unsigned char ucPart=1; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


  181. unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  182. unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会清零


  183. void main()
  184.   {
  185.         initial_myself();      //第一区,上电后马上初始化
  186.         delay_long(100);       //一线,延时线。延时一段时间
  187.         initial_peripheral();  //第二区,上电后延时一段时间再初始化

  188.         while(1)   //第三区
  189.         {
  190.                     key_service(); //按键服务程序
  191.             lcd_display_service(); //应用层面的液晶屏显示程序
  192.         }

  193. }


  194. void initial_myself()  //第一区 上电后马上初始化
  195. {

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

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

  198.    TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  199.    TL0=0x2f;
  200. }
  201. void initial_peripheral() //第二区 上电后延时一段时间再初始化
  202. {
  203.     LCDInit(); //初始化12864 内部包含液晶模块的复位


  204.     EA=1;     //开总中断
  205.     ET0=1;    //允许定时中断
  206.     TR0=1;    //启动定时中断

  207. }


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

  212.   key_scan();//按键扫描函数 放在定时中断里

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


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


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

  229.   switch(ucKeyStep)
  230.   {
  231.      case 1:   //按键扫描输出第ucRowRecord列低电平
  232.               if(ucRowRecord==1)  //第一列输出低电平
  233.                   {
  234.              key_dr1=0;      
  235.              key_dr2=1;
  236.              key_dr3=1;   
  237.              key_dr4=1;
  238.                   }
  239.               else if(ucRowRecord==2)  //第二列输出低电平
  240.                   {
  241.              key_dr1=1;      
  242.              key_dr2=0;
  243.              key_dr3=1;   
  244.              key_dr4=1;
  245.                   }
  246.               else if(ucRowRecord==3)  //第三列输出低电平
  247.                   {
  248.              key_dr1=1;      
  249.              key_dr2=1;
  250.              key_dr3=0;   
  251.              key_dr4=1;
  252.                   }
  253.               else   //第四列输出低电平
  254.                   {
  255.              key_dr1=1;      
  256.              key_dr2=1;
  257.              key_dr3=1;   
  258.              key_dr4=0;
  259.                   }

  260.           uiKeyTimeCnt=0;  //延时计数器清零
  261.           ucKeyStep++;     //切换到下一个运行步骤
  262.               break;

  263.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  264.           uiKeyTimeCnt++;
  265.                   if(uiKeyTimeCnt>1)
  266.                   {
  267.                      uiKeyTimeCnt=0;
  268.              ucKeyStep++;     //切换到下一个运行步骤
  269.                   }
  270.               break;

  271.      case 3:
  272.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  273.           {  
  274.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  275.              ucKeyLock=0;  //按键自锁标志清零
  276.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  277.    
  278.                          ucRowRecord++;  //输出下一列
  279.                          if(ucRowRecord>4)  
  280.                          {
  281.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  282.                          }

  283.           }
  284.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  285.                   {
  286.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  287.                          {
  288.                             uiKeyTimeCnt++;  //去抖动延时计数器
  289.                                 if(uiKeyTimeCnt>const_key_time)
  290.                                 {
  291.                                    uiKeyTimeCnt=0;
  292.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  293.                        if(ucRowRecord==1)  //第一列输出低电平
  294.                            {
  295.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  296.                            }
  297.                        else if(ucRowRecord==2)  //第二列输出低电平
  298.                            {
  299.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  300.                            }
  301.                        else if(ucRowRecord==3)  //第三列输出低电平
  302.                            {
  303.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  304.                            }
  305.                        else   //第四列输出低电平
  306.                            {
  307.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  308.                            }

  309.                                 }
  310.                         
  311.                          }
  312.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  313.                          {
  314.                             uiKeyTimeCnt++;  //去抖动延时计数器
  315.                                 if(uiKeyTimeCnt>const_key_time)
  316.                                 {
  317.                                    uiKeyTimeCnt=0;
  318.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  319.                        if(ucRowRecord==1)  //第一列输出低电平
  320.                            {
  321.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  322.                            }
  323.                        else if(ucRowRecord==2)  //第二列输出低电平
  324.                            {
  325.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  326.                            }
  327.                        else if(ucRowRecord==3)  //第三列输出低电平
  328.                            {
  329.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  330.                            }
  331.                        else   //第四列输出低电平
  332.                            {
  333.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  334.                            }
  335.                                 }
  336.                         
  337.                          }
  338.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  339.                          {
  340.                             uiKeyTimeCnt++;  //去抖动延时计数器
  341.                                 if(uiKeyTimeCnt>const_key_time)
  342.                                 {
  343.                                    uiKeyTimeCnt=0;
  344.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  345.                        if(ucRowRecord==1)  //第一列输出低电平
  346.                            {
  347.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  348.                            }
  349.                        else if(ucRowRecord==2)  //第二列输出低电平
  350.                            {
  351.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  352.                            }
  353.                        else if(ucRowRecord==3)  //第三列输出低电平
  354.                            {
  355.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  356.                            }
  357.                        else   //第四列输出低电平
  358.                            {
  359.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  360.                            }
  361.                                 }
  362.                         
  363.                          }
  364.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  365.                          {
  366.                             uiKeyTimeCnt++;  //去抖动延时计数器
  367.                                 if(uiKeyTimeCnt>const_key_time)
  368.                                 {
  369.                                    uiKeyTimeCnt=0;
  370.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  371.                        if(ucRowRecord==1)  //第一列输出低电平
  372.                            {
  373.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  374.                            }
  375.                        else if(ucRowRecord==2)  //第二列输出低电平
  376.                            {
  377.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  378.                            }
  379.                        else if(ucRowRecord==3)  //第三列输出低电平
  380.                            {
  381.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  382.                            }
  383.                        else   //第四列输出低电平
  384.                            {
  385.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  386.                            }
  387.                                 }
  388.                         
  389.                          }
  390.                   
  391.                   }
  392.               break;

  393.   }


  394. }


  395. void key_service() //按键服务的应用程序
  396. {
  397.   switch(ucKeySec) //按键服务状态切换
  398.   {
  399.     case 1:// 数字1 对应朱兆祺学习板的S1键
  400.           key_number_input(1); //输入数字按键
  401.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  402.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  403.           break;        
  404.     case 2:// 数字2 对应朱兆祺学习板的S2键
  405.           key_number_input(2); //输入数字按键
  406.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  407.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  408.           break;     
  409.     case 3:// 数字3 对应朱兆祺学习板的S3键
  410.           key_number_input(3); //输入数字按键
  411.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  412.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  413.           break;         
  414.     case 4:// 数字4 对应朱兆祺学习板的S4键
  415.           key_number_input(4); //输入数字按键
  416.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  417.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  418.           break;   
  419.     case 5:// 数字5 对应朱兆祺学习板的S5键
  420.           key_number_input(5); //输入数字按键
  421.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  422.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  423.           break;   
  424.     case 6:// 数字6 对应朱兆祺学习板的S6键
  425.           key_number_input(6); //输入数字按键
  426.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  427.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  428.           break;   
  429.     case 7:// 数字7 对应朱兆祺学习板的S7键
  430.           key_number_input(7); //输入数字按键
  431.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  432.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  433.           break;   
  434.     case 8: //数字8 对应朱兆祺学习板的S8键
  435.           key_number_input(8); //输入数字按键
  436.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  437.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  438.           break;   
  439.     case 9:// 数字9 对应朱兆祺学习板的S9键
  440.           key_number_input(9); //输入数字按键
  441.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  442.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  443.           break;   
  444.     case 10:// 数字0  对应朱兆祺学习板的S10键
  445.           key_number_input(0); //输入数字按键
  446.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  447.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  448.           break;   
  449.     case 11:// 小数点按键 对应朱兆祺学习板的S11键
  450.           key_number_input(11); //输入数字按键  11代表小数点
  451.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  452.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  453.           break;   
  454.     case 12:// 本节暂时不用 对应朱兆祺学习板的S12键  

  455.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  456.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  457.           break;   
  458.     case 13:// 本节暂时不用 对应朱兆祺学习板的S13键   

  459.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  460.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  461.           break;   
  462.     case 14:// 本节暂时不用  对应朱兆祺学习板的S14键   
  463.         
  464.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  465.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  466.           break;   
  467.     case 15:// 本节暂时不用 对应朱兆祺学习板的S15键

  468.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  469.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  470.           break;   
  471.     case 16:// 清除按键 对应朱兆祺学习板的S16键
  472.           key_delete_input(); //删除按键
  473.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  474.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  475.           break;   
  476.   }               
  477. }



  478. void key_number_input(unsigned char ucKeyNumber) //输入数字按键
  479. {

  480.         switch(ucWd)
  481.         {
  482.           case 1:   //第1窗口。本节程序只有1个窗口
  483.                    switch(ucPart)
  484.                    {

  485.              case 1:  //1窗口第1项
  486.                     set_data(ucKeyNumber,2,6,&ucDotCnt_1,&ucDotBitS_1,&ucWdPartCnt_1,ucDataBuffer_1); //设置参数,请看本函数具体内容。本节的核心内容,值得好好研究!               
  487.                                     ucWd1Part1Update=1;//更新显示
  488.                                     break;               
  489.           }
  490.                                        
  491.                     break;
  492.     }                       
  493.                                
  494. }


  495. /* 注释三:
  496. * 本节的核心函数,值得好好研究!
  497. * 涉及到参数的4种信息,包括小数点的数量,个数,数据的位置,数组具体值。以及它们之间的相互作用关系。
  498. * 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。
  499. * 第1个参数ucKeyNumberTemp是当前按键输入的数值。
  500. * 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。
  501. * 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。
  502. * 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
  503. * 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个量如果超过规定2位,此时再按任何输入按键则无效
  504. * 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
  505. * 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。
  506. */
  507. void set_data(unsigned char ucKeyNumberTemp,unsigned char ucDotBitMax,unsigned char ucDataCntMax,unsigned char *p_ucDotCnt,unsigned char *p_ucDotBitS,unsigned char *p_ucWdPartCnt,unsigned char *p_ucSetDataBuffer)
  508. {
  509.                     unsigned int i;

  510.                     if(ucKeyNumberTemp==11) //等于小数点
  511.                     {
  512.                        if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。
  513.                        {
  514.                            return; //直接返回退出
  515.                        }
  516.                        else if(*p_ucDotCnt>0)  //小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。
  517.                        {
  518.                            return; //直接返回退出
  519.                        }
  520.                        else  //否则有效,记录当前已经包含一个小数点的信息。
  521.                        {
  522.                            *p_ucDotCnt=1;  //只能包含一个小数点
  523.                        }
  524.                     }
  525.                     else if(*p_ucDotCnt==1) //如果输入的不是小数点,并且之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据
  526.                     {
  527.                         if(*p_ucDotBitS<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
  528.                         {
  529.                             *p_ucDotBitS=(*p_ucDotBitS)+1;
  530.                         }
  531.                         else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
  532.                         {
  533.                             return; //直接返回退出
  534.                         }
  535.                     }

  536.         
  537.                                     if(*p_ucWdPartCnt<(ucDataCntMax-1))  //当输入的有效BCD码不超过最大数组缓冲时
  538.                                     {               
  539.                        if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer[0]==0&&ucKeyNumberTemp!=11)  //如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
  540.                        {
  541.                            ;
  542.                        }       
  543.                        else  //否则,移位
  544.                        {               
  545.                            for(i=0;i<(ucDataCntMax-1);i++)  //移位
  546.                            {
  547.                               p_ucSetDataBuffer[ucDataCntMax-1-i]=p_ucSetDataBuffer[ucDataCntMax-2-i];
  548.                            }
  549.                                                *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
  550.                        }
  551.                        p_ucSetDataBuffer[0]=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
  552.                                                                
  553.                     }

  554. }


  555. void key_delete_input(void) //删除按键
  556. {
  557.         static unsigned int i;

  558.         switch(ucWd)
  559.         {
  560.           case 1:   //第1窗口。本节程序只有1个窗口
  561.                    switch(ucPart)
  562.                    {

  563.              case 1:  //1窗口第1项
  564.                     
  565.                                 //清零
  566.                     ucDotBitS_1=0;  
  567.                     ucDotCnt_1=0;  
  568.                     ucWdPartCnt_1=0;               
  569.                     for(i=0;i<6;i++)  
  570.                     {
  571.                        ucDataBuffer_1[i]=10;
  572.                     }
  573.                                         ucDataBuffer_1[0]=0; //第0个位置填入0
  574.        
  575.                                     ucWd1Part1Update=1;//更新显示
  576.                                     break;               
  577.       
  578.           }
  579.                                        
  580.                     break;
  581.        
  582.     }                       
  583.                                
  584. }

  585. unsigned char *number_to_matrix(unsigned char  ucBitNumber)
  586. {
  587.     unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

  588.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
  589.         {
  590.             case 0:
  591.              p_ucAnyNumber=Zf816_0;
  592.                      break;
  593.             case 1:
  594.              p_ucAnyNumber=Zf816_1;
  595.                      break;
  596.             case 2:
  597.              p_ucAnyNumber=Zf816_2;
  598.                      break;
  599.             case 3:
  600.              p_ucAnyNumber=Zf816_3;
  601.                      break;
  602.             case 4:
  603.              p_ucAnyNumber=Zf816_4;
  604.                      break;
  605.             case 5:
  606.              p_ucAnyNumber=Zf816_5;
  607.                      break;
  608.             case 6:
  609.              p_ucAnyNumber=Zf816_6;
  610.                      break;
  611.             case 7:
  612.              p_ucAnyNumber=Zf816_7;
  613.                      break;
  614.             case 8:
  615.              p_ucAnyNumber=Zf816_8;
  616.                      break;
  617.             case 9:
  618.              p_ucAnyNumber=Zf816_9;
  619.                      break;
  620.             case 10:  //空格
  621.              p_ucAnyNumber=Zf816_nc;
  622.                      break;
  623.                         case 11:   //小数点
  624.              p_ucAnyNumber=Zf816_dot;
  625.                      break;
  626.                 default:   //如果上面的条件都不符合,那么默认指向空字模
  627.              p_ucAnyNumber=Zf816_nc;
  628.                      break;
  629.         }

  630.     return p_ucAnyNumber;  //返回转换结束后的指针
  631. }



  632. void lcd_display_service(void) //应用层面的液晶屏显示程序
  633. {


  634.     static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
  635.     static unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
  636.     static unsigned int i;

  637.     switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  638.     {
  639.         case 1:   //显示窗口1的数据
  640.                if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
  641.                {
  642.                      ucWd1Update=0;  //及时清零,避免一直更新

  643.                      ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进


  644.                      display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  645.                      clear_all_canvas();  //把画布全部清零

  646.                      display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示
  647.                      display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
  648.                      display_lattice(2,0,Hz1616_shu,0,2,16,0);   
  649.                      display_lattice(3,0,Hz1616_zhu,0,2,16,0);
  650.                      display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

  651.                

  652.                }

  653.                if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
  654.                {
  655.                         ucWd1Part1Update=0; //及时清零,避免一直更新

  656.                         if(ucPart==1) //被选中
  657.                         {
  658.                            ucCursorFlag=1; //反显 显示
  659.                         }
  660.                         else //没被选中
  661.                         {
  662.                             ucCursorFlag=0; //正常 显示
  663.                         }

  664.                         
  665.                                                 for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  666.                         {
  667.                                                     p_ucAnyNumber=number_to_matrix(ucDataBuffer_1[5-i]);
  668.                           insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  669.                         }

  670.                         display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  671.                }

  672.                      
  673.                break;
  674.         //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...        
  675.     }

  676. }



  677. void clear_all_canvas(void)  //把画布全部清零
  678. {
  679.    unsigned int j=0;
  680.    unsigned int i=0;

  681.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  682.    {
  683.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  684.       {
  685.                   ucCanvasBuffer[j*4+i]=0x00;
  686.       }
  687.    }         

  688. }





  689. void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
  690. {   

  691.     unsigned char x,y;
  692.     WriteCommand(0x34);  //关显示缓冲指令            
  693.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  694.     y=0;
  695.     while(y<32)  //y轴的范围0至31
  696.     {
  697.          WriteCommand(y+0x80);        //垂直地址
  698.          WriteCommand(0x80);          //水平地址
  699.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  700.          {  
  701.             LCDWriteData(ucFillDate);
  702.          }
  703.          y++;
  704.     }
  705.     WriteCommand(0x36); //开显示缓冲指令

  706. }

  707. /* 注释四:
  708. * 注意,这节内容的画布跟前面章节的画布大小不一样,前面章节的横向是4个字节,这节的横向是6个字节。
  709. * 把字模插入画布的函数.
  710. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  711. * 第1,2个参数x,y是在画布中的坐标体系。
  712. * x的范围是0至5,因为画布的横向只要6个字节。y的范围是0至15,因为画布的纵向只有16行。
  713. * 第3个参数*ucArray是字模的数组。
  714. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  715. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  716. */
  717. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  718. {
  719.    unsigned int j=0;
  720.    unsigned int i=0;
  721.    unsigned char ucTemp;
  722.    for(j=0;j<y_amount;j++)
  723.    {
  724.       for(i=0;i<x_amount;i++)
  725.       {
  726.               ucTemp=ucArray[j*x_amount+i];
  727.               if(ucFbFlag==0)
  728.               {
  729.                  ucCanvasBuffer[(y+j)*6+x+i]=ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
  730.               }
  731.               else
  732.               {
  733.                  ucCanvasBuffer[(y+j)*6+x+i]=~ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
  734.               }
  735.       }
  736.    }         

  737. }

  738. /* 注释五:
  739. * 显示任意点阵函数.
  740. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  741. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  742. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  743. * 第3个参数*ucArray是字模的数组。
  744. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  745. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  746. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  747. */
  748. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
  749. {
  750.    unsigned int j=0;
  751.    unsigned int i=0;
  752.    unsigned char ucTemp;

  753. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  754. //  WriteCommand(0x34);  //关显示缓冲指令            
  755. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  756.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  757.    {
  758.        WriteCommand(y+j+0x80);        //垂直地址
  759.        WriteCommand(x+0x80);          //水平地址
  760.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  761.        {
  762.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  763.            if(ucFbFlag==1)  //反白显示
  764.            {
  765.                ucTemp=~ucTemp;
  766.            }
  767.            LCDWriteData(ucTemp);
  768.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  769.       }
  770.    }
  771.    WriteCommand(0x36); //开显示缓冲指令
  772. }




  773. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  774. {
  775.         unsigned char i;
  776.         for ( i = 0; i < 8; i++ )
  777.         {
  778.                 if ( (ucData << i) & 0x80 )
  779.                 {
  780.                         LCDSID_dr = 1;
  781.                 }
  782.                 else
  783.                 {
  784.                         LCDSID_dr = 0;
  785.                 }
  786.                 LCDCLK_dr = 0;
  787.                 LCDCLK_dr = 1;
  788.         }
  789. }

  790. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  791. {
  792.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  793.         SendByteToLcd( ucWData & 0xf0 );
  794.         SendByteToLcd( (ucWData << 4) & 0xf0);
  795. }


  796. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  797. {

  798.         LCDCS_dr = 0;
  799.         LCDCS_dr = 1;
  800.         SPIWrite(ucCommand, 0);
  801.         delay_short(90);
  802. }

  803. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  804. {
  805.         LCDCS_dr = 0;
  806.         LCDCS_dr = 1;
  807.         SPIWrite(ucData, 1);
  808. }

  809. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  810. {
  811.         LCDRST_dr = 1;  //复位
  812.         LCDRST_dr = 0;
  813.         LCDRST_dr = 1;
  814. }



  815. void delay_short(unsigned int uiDelayShort) //延时函数
  816. {
  817.    unsigned int i;  
  818.    for(i=0;i<uiDelayShort;i++)
  819.    {
  820.      ;  
  821.    }
  822. }


  823. void delay_long(unsigned int uiDelayLong)
  824. {
  825.    unsigned int i;
  826.    unsigned int j;
  827.    for(i=0;i<uiDelayLong;i++)
  828.    {
  829.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  830.           {
  831.              ; //一个分号相当于执行一条空语句
  832.           }
  833.    }
  834. }



复制代码

总结陈词:
这节讲的是键盘输入数字或者小数点的BCD码用来显示,实际项目中,我们经常要知道所输入的BCD码数组到底有效数值是多少,这个该怎么办?欲知详情,请听下回分解----
实时同步把键盘输入的BCD码数组转换成数值的液晶屏显示程序。


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

乐于分享,勇于质疑!
109#
 楼主| 发表于 2014-12-19 15:40:11 | 显示全部楼层
第八十四节:实时同步把键盘输入的BCD码数组转换成数值的液晶屏显示程序。

开场白:
    键盘直接输入的是带小数点的BCD码数组,要把它们转换成具体的数值才可以更好的在程序里运算或者处理。如何把BCD码数组实时同步转换成数值?这一节主要跟大家讲这方面的算法程序。另外,有一个地方值得注意:上一节键盘输入的小数点个数可以限制成最大2位,但是整数部分没有限制。这节为了也能限制整数部分的最大个数为3位,我修改了上一节的void set_data(…)函数。所以这节的void set_data(…)函数跟上一节的void set_data(…)函数有点不一样,需要特别注意。

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

(1)     硬件平台:
基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,清零键对应S16,其它按键不用。

(2)     实现功能:
用矩阵键盘输入任意数字或小数点。小数点不能超过2位,一旦超过2位,再按其它按键则输入无效。整数部分不能超过3位,一旦超过3位,再按其它按键则输入无效。想重新输入,必须按S16清零按键才能重新输入。每次键盘输入的第一行BCD码数组会同步更新显示在第二行的数值上。

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


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

  3. #define const_key_time  10    //按键去抖动延时的时间

  4. sbit key_sr1=P0^0; //第一行输入
  5. sbit key_sr2=P0^1; //第二行输入
  6. sbit key_sr3=P0^2; //第三行输入
  7. sbit key_sr4=P0^3; //第四行输入

  8. sbit key_dr1=P0^4; //第一列输出
  9. sbit key_dr2=P0^5; //第二列输出
  10. sbit key_dr3=P0^6; //第三列输出
  11. sbit key_dr4=P0^7; //第四列输出

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

  13. sbit  LCDCS_dr  = P1^6;  //片选线
  14. sbit  LCDSID_dr = P1^7;  //串行数据线
  15. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  16. sbit  LCDRST_dr = P3^4;  //复位线

  17. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  18. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  19. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  20. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  21. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  22. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  23. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
  24. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
  25. unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
  26. void delay_short(unsigned int uiDelayshort); //延时
  27. void delay_long(unsigned int uiDelayLong);

  28. void key_number_input(unsigned char ucKeyNumber); //输入数字按键
  29. void set_data(unsigned char ucKeyNumberTemp, //设置参数
  30.               unsigned char ucDotBitMax,
  31.               unsigned char ucDataCntMax,
  32.               unsigned char *p_ucDotCnt,
  33.               unsigned char *p_ucDotBitS,
  34.                           unsigned char *p_ucWdPartCnt,
  35.                           unsigned char *p_ucSetDataBuffer,
  36.                           unsigned char ucIntCntMax,
  37.                           unsigned char *p_ucIntCnt);

  38. unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer); //把带小数点的BCD数组转换成long类型的数值。

  39. void key_delete_input(void); //删除按键

  40. void T0_time(); //定时中断函数
  41. void key_service();
  42. void key_scan(); //按键扫描函数 放在定时中断里

  43. void initial_myself();   
  44. void initial_peripheral();


  45. void lcd_display_service(void); //应用层面的液晶屏显示程序
  46. void clear_all_canvas(void);  //把画布全部清零

  47. code unsigned char Zf816_0[]=
  48. {
  49. /*--  文字:  0  --*/
  50. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  51. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  52. };

  53. code unsigned char Zf816_1[]=
  54. {
  55. /*--  文字:  1  --*/
  56. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  57. 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
  58. };

  59. code unsigned char Zf816_2[]=
  60. {
  61. /*--  文字:  2  --*/
  62. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  63. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
  64. };

  65. code unsigned char Zf816_3[]=
  66. {
  67. /*--  文字:  3  --*/
  68. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  69. 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  70. };

  71. code unsigned char Zf816_4[]=
  72. {
  73. /*--  文字:  4  --*/
  74. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  75. 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
  76. };

  77. code unsigned char Zf816_5[]=
  78. {
  79. /*--  文字:  5  --*/
  80. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  81. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  82. };

  83. code unsigned char Zf816_6[]=
  84. {
  85. /*--  文字:  6  --*/
  86. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  87. 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  88. };


  89. code unsigned char Zf816_7[]=
  90. {
  91. /*--  文字:  7  --*/
  92. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  93. 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
  94. };

  95. code unsigned char Zf816_8[]=
  96. {
  97. /*--  文字:  8  --*/
  98. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  99. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
  100. };

  101. code unsigned char Zf816_9[]=
  102. {
  103. /*--  文字:  9  --*/
  104. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  105. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
  106. };


  107. code unsigned char Zf816_nc[]=  //空字模
  108. {
  109. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  110. };

  111. code unsigned char Zf816_dot[]=  //小数点
  112. {
  113. /*--  文字:  .  --*/
  114. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  115. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,
  116. };

  117. code unsigned char Zf816_mao_hao[]=  //冒号
  118. {
  119. /*--  文字:  :  --*/
  120. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  121. 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
  122. };

  123. code unsigned char Hz1616_yi[]=
  124. {
  125. /*--  文字:  一  --*/
  126. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  127. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
  128. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  129. };

  130. code unsigned char Hz1616_xiang[]=
  131. {
  132. /*--  文字:  项  --*/
  133. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  134. 0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24,
  135. 0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00,
  136. };

  137. code unsigned char Hz1616_shu[]=
  138. {
  139. /*--  文字:  数  --*/
  140. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  141. 0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4,
  142. 0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00,
  143. };

  144. code unsigned char Hz1616_zhu[]=
  145. {
  146. /*--  文字:  组  --*/
  147. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  148. 0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08,
  149. 0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00,
  150. };

  151. code unsigned char Hz1616_zhi[]=
  152. {
  153. /*--  文字:  值  --*/
  154. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  155. 0x10,0x40,0x18,0x60,0x17,0xFC,0x10,0x40,0x20,0x80,0x33,0xF8,0x62,0x08,0xA3,0xF8,
  156. 0x22,0x08,0x23,0xF8,0x22,0x08,0x23,0xF8,0x22,0x08,0x22,0x08,0x2F,0xFE,0x20,0x00,
  157. };

  158. /* 注释一:
  159. * 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字.
  160. *  注意,这节内容的画布跟前面79章节的画布大小不一样,79节前面的横向是4个字节,这节的横向是6个字节。
  161. */
  162. unsigned char ucCanvasBuffer[]=
  163. {
  164. 0x00,0x00,0x00,0x00,0x00,0x00,  //上半屏
  165. 0x00,0x00,0x00,0x00,0x00,0x00,
  166. 0x00,0x00,0x00,0x00,0x00,0x00,
  167. 0x00,0x00,0x00,0x00,0x00,0x00,
  168. 0x00,0x00,0x00,0x00,0x00,0x00,
  169. 0x00,0x00,0x00,0x00,0x00,0x00,
  170. 0x00,0x00,0x00,0x00,0x00,0x00,
  171. 0x00,0x00,0x00,0x00,0x00,0x00,

  172. //------------上半屏和下半屏的分割线-----------

  173. 0x00,0x00,0x00,0x00,0x00,0x00,  //下半屏
  174. 0x00,0x00,0x00,0x00,0x00,0x00,
  175. 0x00,0x00,0x00,0x00,0x00,0x00,
  176. 0x00,0x00,0x00,0x00,0x00,0x00,
  177. 0x00,0x00,0x00,0x00,0x00,0x00,
  178. 0x00,0x00,0x00,0x00,0x00,0x00,
  179. 0x00,0x00,0x00,0x00,0x00,0x00,
  180. 0x00,0x00,0x00,0x00,0x00,0x00,
  181. };


  182. /* 注释二:
  183. * 以下5个变量记录一个参数的5种信息,包括小数点的数量,小数点个数,数据的位置,数组具体值,整数个数
  184. */
  185. unsigned char ucDotCnt_1=0;  //记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
  186. unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  187. unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。
  188. unsigned char ucDataBuffer_1[6]={0,10,10,10,10,10}; //一项的BCD码数组缓冲
  189. unsigned char ucIntCnt_1=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效

  190. unsigned long ulData_1=0; //用一个long变量表示BCD码的具体数值。


  191. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  192. unsigned char ucKeySec=0;   //被触发的按键编号
  193. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  194. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  195. unsigned char ucRowRecord=1; //记录当前扫描到第几列了


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


  197. unsigned char ucWd=1; //窗口变量
  198. unsigned char ucPart=1; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


  199. unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  200. unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零
  201. unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零

  202. void main()
  203.   {
  204.         initial_myself();      //第一区,上电后马上初始化
  205.         delay_long(100);       //一线,延时线。延时一段时间
  206.         initial_peripheral();  //第二区,上电后延时一段时间再初始化

  207.         while(1)   //第三区
  208.         {
  209.                     key_service(); //按键服务程序
  210.             lcd_display_service(); //应用层面的液晶屏显示程序
  211.         }

  212. }


  213. void initial_myself()  //第一区 上电后马上初始化
  214. {

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

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

  217.    TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  218.    TL0=0x2f;
  219. }
  220. void initial_peripheral() //第二区 上电后延时一段时间再初始化
  221. {
  222.     LCDInit(); //初始化12864 内部包含液晶模块的复位


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

  226. }


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

  231.   key_scan();//按键扫描函数 放在定时中断里

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


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


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

  248.   switch(ucKeyStep)
  249.   {
  250.      case 1:   //按键扫描输出第ucRowRecord列低电平
  251.               if(ucRowRecord==1)  //第一列输出低电平
  252.                   {
  253.              key_dr1=0;      
  254.              key_dr2=1;
  255.              key_dr3=1;   
  256.              key_dr4=1;
  257.                   }
  258.               else if(ucRowRecord==2)  //第二列输出低电平
  259.                   {
  260.              key_dr1=1;      
  261.              key_dr2=0;
  262.              key_dr3=1;   
  263.              key_dr4=1;
  264.                   }
  265.               else if(ucRowRecord==3)  //第三列输出低电平
  266.                   {
  267.              key_dr1=1;      
  268.              key_dr2=1;
  269.              key_dr3=0;   
  270.              key_dr4=1;
  271.                   }
  272.               else   //第四列输出低电平
  273.                   {
  274.              key_dr1=1;      
  275.              key_dr2=1;
  276.              key_dr3=1;   
  277.              key_dr4=0;
  278.                   }

  279.           uiKeyTimeCnt=0;  //延时计数器清零
  280.           ucKeyStep++;     //切换到下一个运行步骤
  281.               break;

  282.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  283.           uiKeyTimeCnt++;
  284.                   if(uiKeyTimeCnt>1)
  285.                   {
  286.                      uiKeyTimeCnt=0;
  287.              ucKeyStep++;     //切换到下一个运行步骤
  288.                   }
  289.               break;

  290.      case 3:
  291.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  292.           {  
  293.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  294.              ucKeyLock=0;  //按键自锁标志清零
  295.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  296.    
  297.                          ucRowRecord++;  //输出下一列
  298.                          if(ucRowRecord>4)  
  299.                          {
  300.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  301.                          }

  302.           }
  303.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  304.                   {
  305.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  306.                          {
  307.                             uiKeyTimeCnt++;  //去抖动延时计数器
  308.                                 if(uiKeyTimeCnt>const_key_time)
  309.                                 {
  310.                                    uiKeyTimeCnt=0;
  311.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  312.                        if(ucRowRecord==1)  //第一列输出低电平
  313.                            {
  314.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  315.                            }
  316.                        else if(ucRowRecord==2)  //第二列输出低电平
  317.                            {
  318.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  319.                            }
  320.                        else if(ucRowRecord==3)  //第三列输出低电平
  321.                            {
  322.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  323.                            }
  324.                        else   //第四列输出低电平
  325.                            {
  326.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  327.                            }

  328.                                 }
  329.                         
  330.                          }
  331.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  332.                          {
  333.                             uiKeyTimeCnt++;  //去抖动延时计数器
  334.                                 if(uiKeyTimeCnt>const_key_time)
  335.                                 {
  336.                                    uiKeyTimeCnt=0;
  337.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  338.                        if(ucRowRecord==1)  //第一列输出低电平
  339.                            {
  340.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  341.                            }
  342.                        else if(ucRowRecord==2)  //第二列输出低电平
  343.                            {
  344.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  345.                            }
  346.                        else if(ucRowRecord==3)  //第三列输出低电平
  347.                            {
  348.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  349.                            }
  350.                        else   //第四列输出低电平
  351.                            {
  352.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  353.                            }
  354.                                 }
  355.                         
  356.                          }
  357.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  358.                          {
  359.                             uiKeyTimeCnt++;  //去抖动延时计数器
  360.                                 if(uiKeyTimeCnt>const_key_time)
  361.                                 {
  362.                                    uiKeyTimeCnt=0;
  363.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  364.                        if(ucRowRecord==1)  //第一列输出低电平
  365.                            {
  366.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  367.                            }
  368.                        else if(ucRowRecord==2)  //第二列输出低电平
  369.                            {
  370.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  371.                            }
  372.                        else if(ucRowRecord==3)  //第三列输出低电平
  373.                            {
  374.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  375.                            }
  376.                        else   //第四列输出低电平
  377.                            {
  378.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  379.                            }
  380.                                 }
  381.                         
  382.                          }
  383.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  384.                          {
  385.                             uiKeyTimeCnt++;  //去抖动延时计数器
  386.                                 if(uiKeyTimeCnt>const_key_time)
  387.                                 {
  388.                                    uiKeyTimeCnt=0;
  389.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  390.                        if(ucRowRecord==1)  //第一列输出低电平
  391.                            {
  392.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  393.                            }
  394.                        else if(ucRowRecord==2)  //第二列输出低电平
  395.                            {
  396.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  397.                            }
  398.                        else if(ucRowRecord==3)  //第三列输出低电平
  399.                            {
  400.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  401.                            }
  402.                        else   //第四列输出低电平
  403.                            {
  404.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  405.                            }
  406.                                 }
  407.                         
  408.                          }
  409.                   
  410.                   }
  411.               break;

  412.   }


  413. }


  414. void key_service() //按键服务的应用程序
  415. {
  416.   switch(ucKeySec) //按键服务状态切换
  417.   {
  418.     case 1:// 数字1 对应朱兆祺学习板的S1键
  419.           key_number_input(1); //输入数字按键
  420.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  421.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  422.           break;        
  423.     case 2:// 数字2 对应朱兆祺学习板的S2键
  424.           key_number_input(2); //输入数字按键
  425.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  426.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  427.           break;     
  428.     case 3:// 数字3 对应朱兆祺学习板的S3键
  429.           key_number_input(3); //输入数字按键
  430.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  431.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  432.           break;         
  433.     case 4:// 数字4 对应朱兆祺学习板的S4键
  434.           key_number_input(4); //输入数字按键
  435.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  436.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  437.           break;   
  438.     case 5:// 数字5 对应朱兆祺学习板的S5键
  439.           key_number_input(5); //输入数字按键
  440.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  441.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  442.           break;   
  443.     case 6:// 数字6 对应朱兆祺学习板的S6键
  444.           key_number_input(6); //输入数字按键
  445.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  446.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  447.           break;   
  448.     case 7:// 数字7 对应朱兆祺学习板的S7键
  449.           key_number_input(7); //输入数字按键
  450.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  451.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  452.           break;   
  453.     case 8: //数字8 对应朱兆祺学习板的S8键
  454.           key_number_input(8); //输入数字按键
  455.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  456.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  457.           break;   
  458.     case 9:// 数字9 对应朱兆祺学习板的S9键
  459.           key_number_input(9); //输入数字按键
  460.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  461.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  462.           break;   
  463.     case 10:// 数字0  对应朱兆祺学习板的S10键
  464.           key_number_input(0); //输入数字按键
  465.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  466.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  467.           break;   
  468.     case 11:// 小数点按键 对应朱兆祺学习板的S11键
  469.           key_number_input(11); //输入数字按键  11代表小数点
  470.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  471.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  472.           break;   
  473.     case 12:// 本节暂时不用 对应朱兆祺学习板的S12键  

  474.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  475.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  476.           break;   
  477.     case 13:// 本节暂时不用 对应朱兆祺学习板的S13键   

  478.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  479.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  480.           break;   
  481.     case 14:// 本节暂时不用  对应朱兆祺学习板的S14键   
  482.         
  483.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  484.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  485.           break;   
  486.     case 15:// 本节暂时不用 对应朱兆祺学习板的S15键

  487.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  488.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  489.           break;   
  490.     case 16:// 清除按键 对应朱兆祺学习板的S16键
  491.           key_delete_input(); //删除按键
  492.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  493.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  494.           break;   
  495.   }               
  496. }



  497. void key_number_input(unsigned char ucKeyNumber) //输入数字按键
  498. {

  499.     switch(ucWd)
  500.     {
  501.        case 1:   //第1窗口。本节程序只有1个窗口
  502.              switch(ucPart)
  503.              {

  504.                  case 1:  //1窗口第1项
  505.                       set_data(ucKeyNumber,  //本函数跟前面第83节内容有所改动,请看本函数具体内容。本节的核心内容,值得好好研究!     
  506.                                                    2,  //小数点最大个数
  507.                                                            6,  //数组缓冲最大个数
  508.                                                        &ucDotCnt_1,
  509.                                                            &ucDotBitS_1,
  510.                                                            &ucWdPartCnt_1,
  511.                                                            ucDataBuffer_1,
  512.                                                            3, //整数部分的最大个数
  513.                                                            &ucIntCnt_1);

  514.                                           ulData_1=buffer_to_data(6,2,ucDataBuffer_1); //把带小数点的BCD码数组转换成long数值。
  515.                       ucWd1Part1Update=1;//第一行局部更新显示
  516.                                           ucWd1Part2Update=1;//第二行局部更新显示
  517.                       break;               
  518.              }
  519.                                        
  520.              break;
  521.     }                        
  522.                                 
  523. }


  524. /* 注释三:
  525. * 本函数在前面第83节内容的函数上有改动,为了限制整数部分的个数,多添加了第8和第9这两个参数。
  526. * 本节的核心函数,值得好好研究!
  527. * 涉及到参数的4种信息,包括小数点的数量,小数点的个数,数据的位置,数组具体值,整数的数量,整数的个数,以及它们之间的相互作用关系。
  528. * 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。
  529. * 第1个参数ucKeyNumberTemp是当前按键输入的数值。
  530. * 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。
  531. * 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。
  532. * 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
  533. * 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  534. * 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
  535. * 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。
  536. * 第8个参数ucIntCntMax是限定被设置参数的整数部分的最大位数。
  537. * 第9个参数*p_ucIntCnt是记录当前输入的整数部分个数,如果整数部分的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
  538. */
  539. void set_data(unsigned char ucKeyNumberTemp,
  540.               unsigned char ucDotBitMax,
  541.               unsigned char ucDataCntMax,
  542.               unsigned char *p_ucDotCnt,
  543.               unsigned char *p_ucDotBitS,
  544.                           unsigned char *p_ucWdPartCnt,
  545.                           unsigned char *p_ucSetDataBuffer,
  546.                           unsigned char ucIntCntMax,
  547.                           unsigned char *p_ucIntCnt)
  548. {
  549.                     unsigned int i;

  550.                     if(ucKeyNumberTemp==11) //等于小数点
  551.                     {
  552.                        if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。
  553.                        {
  554.                            return; //直接返回退出
  555.                        }
  556.                        else if(*p_ucDotCnt>0)  //小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。
  557.                        {
  558.                            return; //直接返回退出
  559.                        }
  560.                        else  //否则有效,记录当前已经包含一个小数点的信息。
  561.                        {
  562.                            *p_ucDotCnt=1;  //只能包含一个小数点
  563.                        }
  564.                     }
  565.                     else  //如果输入的不是小数点
  566.                     {
  567.                         if(*p_ucDotCnt==1) //如果之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据
  568.                                                 {
  569.                            if(*p_ucDotBitS<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
  570.                            {
  571.                                *p_ucDotBitS=(*p_ucDotBitS)+1;
  572.                            }
  573.                            else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
  574.                            {
  575.                               return; //直接返回退出
  576.                            }
  577.                                             }
  578.                                                 else if(*p_ucIntCnt<ucIntCntMax)//如果之前没有输入小数点,那么输入的就是整数个数超,整数个数没有超过极限
  579.                                                 {
  580.                                                     *p_ucIntCnt=(*p_ucIntCnt)+1;
  581.                                                 }
  582.                                                 else //整数个数超过极限
  583.                                                 {
  584.                              return; //直接返回退出
  585.                                                 }
  586.                     }

  587.             
  588.             
  589.                     if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer[0]==0&&ucKeyNumberTemp!=11)  //如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
  590.                     {
  591.                         ;
  592.                     }        
  593.                     else  //否则,移位
  594.                     {               
  595.                        for(i=0;i<(ucDataCntMax-1);i++)  //移位
  596.                        {
  597.                           p_ucSetDataBuffer[ucDataCntMax-1-i]=p_ucSetDataBuffer[ucDataCntMax-2-i];
  598.                        }
  599.                        *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
  600.                     }
  601.                     p_ucSetDataBuffer[0]=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
  602.                                                                
  603.         

  604. }


  605. /* 注释四:
  606. * 本节的核心函数,值得好好研究!
  607. * 功能:把一个带小数点的BCD码数组转换成一个long类型的数值。
  608. * 第1个参数ucConverDataSize是这个数组的最大有效个数。
  609. * 第2个参数ucConverDotCnt是这个数组要转换成的long数值带几个小数点
  610. * 第3个参数*p_ucConverBuffer是具体此数组的数据
  611. * 函数最后返回被转换的long数值。
  612. */
  613. unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer)
  614. {
  615.    unsigned long ulConverResult=0;
  616.    unsigned long ulConverResultTemp=0;
  617.    unsigned char ucConverResultBuffer[6]; //因为本节内容的ucConverDataSize是6,所以取6.
  618.    unsigned char i;
  619.    unsigned char j;
  620.    unsigned char ucConverFlag;

  621.    for(i=0;i<ucConverDataSize;i++)
  622.    {
  623.       ucConverResultBuffer[i]=0;  //先把临时缓冲区清零
  624.    }

  625.    j=0;
  626.    ucConverFlag=0;
  627.    for(i=0;i<ucConverDataSize;i++)
  628.    {
  629.        if(p_ucConverBuffer[i]==11) //小数点
  630.        {
  631.           ucConverFlag=i; //记录小数点的位置
  632.        }
  633.        else if(p_ucConverBuffer[i]<10)
  634.        {
  635.           ucConverResultBuffer[j]=p_ucConverBuffer[i];  //提取数组中的有效数字
  636.           j++;
  637.        }


  638.    }


  639.    for(i=0;i<ucConverDataSize;i++)   //通过处理每一位从而合成一个long类型的数值
  640.    {
  641.        ulConverResultTemp=0;
  642.        ulConverResultTemp=ucConverResultBuffer[i];
  643.        for(j=0;j<i;j++)
  644.        {
  645.            ulConverResultTemp=ulConverResultTemp*10;  //把每一位对应的进位扩大到对应的倍数
  646.        }
  647.        ulConverResult=ulConverResult+ulConverResultTemp;
  648.    }


  649.    for(i=ucConverFlag;i<ucConverDotCnt;i++) //根据数组小数点的位置和实际要转换成的小数点个数,来扩大到对应的倍数。
  650.    {
  651.       ulConverResult=ulConverResult*10;
  652.    }

  653.    return ulConverResult;
  654. }

  655. void key_delete_input(void) //删除按键
  656. {
  657.     static unsigned int i;

  658.    switch(ucWd)
  659.    {
  660.       case 1:   //第1窗口。本节程序只有1个窗口
  661.            switch(ucPart)
  662.            {
  663.               case 1:  //1窗口第1项
  664.                     //清零
  665.                                         ulData_1=0; //long数值清零
  666.                                          ucIntCnt_1=0;
  667.                     ucDotBitS_1=0;  
  668.                     ucDotCnt_1=0;  
  669.                     ucWdPartCnt_1=0;               
  670.                     for(i=0;i<6;i++)  
  671.                     {
  672.                        ucDataBuffer_1[i]=10;
  673.                     }
  674.                     ucDataBuffer_1[0]=0; //第0个位置填入0
  675.         
  676.                     ucWd1Part1Update=1;//第一行局部更新显示
  677.                                     ucWd1Part2Update=1;//第二行局部更新显示
  678.                     break;               
  679.       
  680.            }
  681.                                        
  682.            break;
  683.         
  684.    }                        
  685.                                 
  686. }

  687. unsigned char *number_to_matrix(unsigned char  ucBitNumber)
  688. {
  689.     unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

  690.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
  691.         {
  692.             case 0:
  693.              p_ucAnyNumber=Zf816_0;
  694.                      break;
  695.             case 1:
  696.              p_ucAnyNumber=Zf816_1;
  697.                      break;
  698.             case 2:
  699.              p_ucAnyNumber=Zf816_2;
  700.                      break;
  701.             case 3:
  702.              p_ucAnyNumber=Zf816_3;
  703.                      break;
  704.             case 4:
  705.              p_ucAnyNumber=Zf816_4;
  706.                      break;
  707.             case 5:
  708.              p_ucAnyNumber=Zf816_5;
  709.                      break;
  710.             case 6:
  711.              p_ucAnyNumber=Zf816_6;
  712.                      break;
  713.             case 7:
  714.              p_ucAnyNumber=Zf816_7;
  715.                      break;
  716.             case 8:
  717.              p_ucAnyNumber=Zf816_8;
  718.                      break;
  719.             case 9:
  720.              p_ucAnyNumber=Zf816_9;
  721.                      break;
  722.             case 10:  //空格
  723.              p_ucAnyNumber=Zf816_nc;
  724.                      break;
  725.                         case 11:   //小数点
  726.              p_ucAnyNumber=Zf816_dot;
  727.                      break;
  728.                 default:   //如果上面的条件都不符合,那么默认指向空字模
  729.              p_ucAnyNumber=Zf816_nc;
  730.                      break;
  731.         }

  732.     return p_ucAnyNumber;  //返回转换结束后的指针
  733. }



  734. void lcd_display_service(void) //应用层面的液晶屏显示程序
  735. {


  736.     static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
  737.     static unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
  738.     static unsigned int i;
  739.         static unsigned char ucDataBuffer_temp[6]; //分解一个10进制的long类型数据的每一位

  740.     switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  741.     {
  742.         case 1:   //显示窗口1的数据
  743.                if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
  744.                {
  745.                      ucWd1Update=0;  //及时清零,避免一直更新

  746.                      ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
  747.                      ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进

  748.                      display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  749.                      clear_all_canvas();  //把画布全部清零

  750.                      display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一项数组
  751.                      display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
  752.                      display_lattice(2,0,Hz1616_shu,0,2,16,0);   
  753.                      display_lattice(3,0,Hz1616_zhu,0,2,16,0);
  754.                      display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

  755.                      display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一项数值
  756.                      display_lattice(1,16,Hz1616_xiang,0,2,16,0);   
  757.                      display_lattice(2,16,Hz1616_shu,0,2,16,0);   
  758.                      display_lattice(3,16,Hz1616_zhi,0,2,16,0);
  759.                      display_lattice(4,16,Zf816_mao_hao,0,1,16,0); //冒号              

  760.                }

  761.                if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
  762.                {
  763.                         ucWd1Part1Update=0; //及时清零,避免一直更新

  764.                         if(ucPart==1) //被选中
  765.                         {
  766.                            ucCursorFlag=1; //反显 显示
  767.                         }
  768.                         else //没被选中
  769.                         {
  770.                             ucCursorFlag=0; //正常 显示
  771.                         }

  772.                         
  773.                         for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  774.                         {
  775.                             p_ucAnyNumber=number_to_matrix(ucDataBuffer_1[5-i]);
  776.                             insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  777.                         }

  778.                         display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  779.                }

  780.                if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  781.                {
  782.                         ucWd1Part2Update=0; //及时清零,避免一直更新

  783.                         if(ucPart==2) //被选中
  784.                         {
  785.                            ucCursorFlag=1; //反显 显示
  786.                         }
  787.                         else //没被选中
  788.                         {
  789.                             ucCursorFlag=0; //正常 显示
  790.                         }

  791.                         if(ulData_1>=10000)
  792.                                                 {
  793.                                                    ucDataBuffer_temp[5]=ulData_1%100000/10000;
  794.                                                 }
  795.                                                 else
  796.                                                 {
  797.                                                    ucDataBuffer_temp[5]=10; //空格
  798.                                                 }

  799.                         if(ulData_1>=1000)
  800.                                                 {
  801.                                                       ucDataBuffer_temp[4]=ulData_1%10000/1000;
  802.                         }
  803.                                                 else
  804.                                                 {
  805.                                                       ucDataBuffer_temp[4]=10; //空格
  806.                         }

  807.                                                 ucDataBuffer_temp[3]=ulData_1%1000/100;
  808.                                                 ucDataBuffer_temp[2]=11;  //11代表小数点
  809.                                                 ucDataBuffer_temp[1]=ulData_1%100/10;
  810.                                                 ucDataBuffer_temp[0]=ulData_1%10/1;
  811.                         
  812.                         for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  813.                         {
  814.                             p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp[5-i]);
  815.                             insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  816.                         }

  817.                         display_lattice(5,16,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  818.                }

  819.                      
  820.                break;
  821.         //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...        
  822.     }

  823. }



  824. void clear_all_canvas(void)  //把画布全部清零
  825. {
  826.    unsigned int j=0;
  827.    unsigned int i=0;

  828.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  829.    {
  830.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  831.       {
  832.                   ucCanvasBuffer[j*4+i]=0x00;
  833.       }
  834.    }         

  835. }





  836. void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
  837. {   

  838.     unsigned char x,y;
  839.     WriteCommand(0x34);  //关显示缓冲指令            
  840.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  841.     y=0;
  842.     while(y<32)  //y轴的范围0至31
  843.     {
  844.          WriteCommand(y+0x80);        //垂直地址
  845.          WriteCommand(0x80);          //水平地址
  846.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  847.          {  
  848.             LCDWriteData(ucFillDate);
  849.          }
  850.          y++;
  851.     }
  852.     WriteCommand(0x36); //开显示缓冲指令

  853. }

  854. /* 注释五:
  855. * 注意,这节内容的画布跟第79节前面的画布大小不一样,第79节前面的横向是4个字节,这节的横向是6个字节。
  856. * 把字模插入画布的函数.
  857. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  858. * 第1,2个参数x,y是在画布中的坐标体系。
  859. * x的范围是0至5,因为画布的横向只要6个字节。y的范围是0至15,因为画布的纵向只有16行。
  860. * 第3个参数*ucArray是字模的数组。
  861. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  862. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  863. */
  864. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  865. {
  866.    unsigned int j=0;
  867.    unsigned int i=0;
  868.    unsigned char ucTemp;
  869.    for(j=0;j<y_amount;j++)
  870.    {
  871.       for(i=0;i<x_amount;i++)
  872.       {
  873.               ucTemp=ucArray[j*x_amount+i];
  874.               if(ucFbFlag==0)
  875.               {
  876.                  ucCanvasBuffer[(y+j)*6+x+i]=ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
  877.               }
  878.               else
  879.               {
  880.                  ucCanvasBuffer[(y+j)*6+x+i]=~ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
  881.               }
  882.       }
  883.    }         

  884. }

  885. /* 注释六:
  886. * 显示任意点阵函数.
  887. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  888. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  889. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  890. * 第3个参数*ucArray是字模的数组。
  891. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  892. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  893. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  894. */
  895. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
  896. {
  897.    unsigned int j=0;
  898.    unsigned int i=0;
  899.    unsigned char ucTemp;

  900. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  901. //  WriteCommand(0x34);  //关显示缓冲指令            
  902. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  903.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  904.    {
  905.        WriteCommand(y+j+0x80);        //垂直地址
  906.        WriteCommand(x+0x80);          //水平地址
  907.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  908.        {
  909.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  910.            if(ucFbFlag==1)  //反白显示
  911.            {
  912.                ucTemp=~ucTemp;
  913.            }
  914.            LCDWriteData(ucTemp);
  915.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  916.       }
  917.    }
  918.    WriteCommand(0x36); //开显示缓冲指令
  919. }




  920. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  921. {
  922.         unsigned char i;
  923.         for ( i = 0; i < 8; i++ )
  924.         {
  925.                 if ( (ucData << i) & 0x80 )
  926.                 {
  927.                         LCDSID_dr = 1;
  928.                 }
  929.                 else
  930.                 {
  931.                         LCDSID_dr = 0;
  932.                 }
  933.                 LCDCLK_dr = 0;
  934.                 LCDCLK_dr = 1;
  935.         }
  936. }

  937. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  938. {
  939.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  940.         SendByteToLcd( ucWData & 0xf0 );
  941.         SendByteToLcd( (ucWData << 4) & 0xf0);
  942. }


  943. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  944. {

  945.         LCDCS_dr = 0;
  946.         LCDCS_dr = 1;
  947.         SPIWrite(ucCommand, 0);
  948.         delay_short(90);
  949. }

  950. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  951. {
  952.         LCDCS_dr = 0;
  953.         LCDCS_dr = 1;
  954.         SPIWrite(ucData, 1);
  955. }

  956. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  957. {
  958.         LCDRST_dr = 1;  //复位
  959.         LCDRST_dr = 0;
  960.         LCDRST_dr = 1;
  961. }



  962. void delay_short(unsigned int uiDelayShort) //延时函数
  963. {
  964.    unsigned int i;  
  965.    for(i=0;i<uiDelayShort;i++)
  966.    {
  967.      ;  
  968.    }
  969. }


  970. void delay_long(unsigned int uiDelayLong)
  971. {
  972.    unsigned int i;
  973.    unsigned int j;
  974.    for(i=0;i<uiDelayLong;i++)
  975.    {
  976.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  977.           {
  978.              ; //一个分号相当于执行一条空语句
  979.           }
  980.    }
  981. }


复制代码

总结陈词:
    这节讲了把BCD码数组同步实时转换成数值的算法程序,相反,把数值转换成BCD码数组的逆运算程序应该怎么写?欲知详情,请听下回分解----实时同步把加减按键输入的数值转换成BCD码数组的液晶屏显示程序。

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

乐于分享,勇于质疑!
110#
 楼主| 发表于 2014-12-19 23:14:52 | 显示全部楼层
szdzjs 发表于 2014-12-19 22:26
来欣赏鸿哥的大作

欢迎
乐于分享,勇于质疑!
111#
 楼主| 发表于 2014-12-24 10:41:02 | 显示全部楼层
第八十五节:实时同步把加减按键输入的数值转换成BCD码数组的液晶屏显示程序。

开场白:
    把运算处理完的数值转换成BCD码数组才可以更好方便显示和数字按键的输入编辑。这一节主要跟大家讲这方面的算法程序。本节的核心转换函数是void data_to_buffer(…)

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

(1)     硬件平台:
基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,S13按键是加按键,S14按键是减按键,清零键对应S16,其它按键不用。

(2)     实现功能:
通过S13,S14这两个加减按键更改第2行显示的数值,此数值会同步更新显示在第1行的BCD码数组上。

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


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

  3. #define const_key_time  10    //按键去抖动延时的时间

  4. sbit key_sr1=P0^0; //第一行输入
  5. sbit key_sr2=P0^1; //第二行输入
  6. sbit key_sr3=P0^2; //第三行输入
  7. sbit key_sr4=P0^3; //第四行输入

  8. sbit key_dr1=P0^4; //第一列输出
  9. sbit key_dr2=P0^5; //第二列输出
  10. sbit key_dr3=P0^6; //第三列输出
  11. sbit key_dr4=P0^7; //第四列输出

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

  13. sbit  LCDCS_dr  = P1^6;  //片选线
  14. sbit  LCDSID_dr = P1^7;  //串行数据线
  15. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  16. sbit  LCDRST_dr = P3^4;  //复位线

  17. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  18. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  19. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  20. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  21. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  22. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  23. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
  24. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
  25. unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
  26. void delay_short(unsigned int uiDelayshort); //延时
  27. void delay_long(unsigned int uiDelayLong);

  28. void key_number_input(unsigned char ucKeyNumber); //输入数字按键
  29. void set_data(unsigned char ucKeyNumberTemp, //设置参数
  30.               unsigned char ucDotBitMax,
  31.               unsigned char ucDataCntMax,
  32.               unsigned char *p_ucDotCnt,
  33.               unsigned char *p_ucDotBitS,
  34.                           unsigned char *p_ucWdPartCnt,
  35.                           unsigned char *p_ucSetDataBuffer,
  36.                           unsigned char ucIntCntMax,
  37.                           unsigned char *p_ucIntCnt);

  38. void data_to_buffer(unsigned long ulWillConverData,  //把数值转换成数组
  39.                     unsigned char ucConverDotCnt,
  40.                                         unsigned char ucConverDataSize,
  41.                                         unsigned char *p_ucDotCnt,
  42.                                         unsigned char *p_ucDotBitS,
  43.                                         unsigned char *p_ucWdPartCnt,
  44.                                         unsigned char *p_ucConverBuffer);
  45. unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer); //把带小数点的BCD数组转换成long类型的数值。

  46. void key_delete_input(void); //删除按键

  47. void T0_time(); //定时中断函数
  48. void key_service();
  49. void key_scan(); //按键扫描函数 放在定时中断里

  50. void initial_myself();   
  51. void initial_peripheral();


  52. void lcd_display_service(void); //应用层面的液晶屏显示程序
  53. void clear_all_canvas(void);  //把画布全部清零

  54. code unsigned char Zf816_0[]=
  55. {
  56. /*--  文字:  0  --*/
  57. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  58. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  59. };

  60. code unsigned char Zf816_1[]=
  61. {
  62. /*--  文字:  1  --*/
  63. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  64. 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
  65. };

  66. code unsigned char Zf816_2[]=
  67. {
  68. /*--  文字:  2  --*/
  69. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  70. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
  71. };

  72. code unsigned char Zf816_3[]=
  73. {
  74. /*--  文字:  3  --*/
  75. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  76. 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  77. };

  78. code unsigned char Zf816_4[]=
  79. {
  80. /*--  文字:  4  --*/
  81. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  82. 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
  83. };

  84. code unsigned char Zf816_5[]=
  85. {
  86. /*--  文字:  5  --*/
  87. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  88. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  89. };

  90. code unsigned char Zf816_6[]=
  91. {
  92. /*--  文字:  6  --*/
  93. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  94. 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  95. };


  96. code unsigned char Zf816_7[]=
  97. {
  98. /*--  文字:  7  --*/
  99. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  100. 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
  101. };

  102. code unsigned char Zf816_8[]=
  103. {
  104. /*--  文字:  8  --*/
  105. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  106. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
  107. };

  108. code unsigned char Zf816_9[]=
  109. {
  110. /*--  文字:  9  --*/
  111. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  112. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
  113. };


  114. code unsigned char Zf816_nc[]=  //空字模
  115. {
  116. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  117. };

  118. code unsigned char Zf816_dot[]=  //小数点
  119. {
  120. /*--  文字:  .  --*/
  121. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  122. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,
  123. };

  124. code unsigned char Zf816_mao_hao[]=  //冒号
  125. {
  126. /*--  文字:  :  --*/
  127. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  128. 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
  129. };

  130. code unsigned char Hz1616_yi[]=
  131. {
  132. /*--  文字:  一  --*/
  133. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  134. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
  135. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  136. };

  137. code unsigned char Hz1616_xiang[]=
  138. {
  139. /*--  文字:  项  --*/
  140. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  141. 0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24,
  142. 0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00,
  143. };

  144. code unsigned char Hz1616_shu[]=
  145. {
  146. /*--  文字:  数  --*/
  147. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  148. 0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4,
  149. 0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00,
  150. };

  151. code unsigned char Hz1616_zhu[]=
  152. {
  153. /*--  文字:  组  --*/
  154. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  155. 0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08,
  156. 0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00,
  157. };

  158. code unsigned char Hz1616_zhi[]=
  159. {
  160. /*--  文字:  值  --*/
  161. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  162. 0x10,0x40,0x18,0x60,0x17,0xFC,0x10,0x40,0x20,0x80,0x33,0xF8,0x62,0x08,0xA3,0xF8,
  163. 0x22,0x08,0x23,0xF8,0x22,0x08,0x23,0xF8,0x22,0x08,0x22,0x08,0x2F,0xFE,0x20,0x00,
  164. };

  165. /* 注释一:
  166. * 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字.
  167. *  注意,这节内容的画布跟前面79章节的画布大小不一样,79节前面的横向是4个字节,这节的横向是6个字节。
  168. */
  169. unsigned char ucCanvasBuffer[]=
  170. {
  171. 0x00,0x00,0x00,0x00,0x00,0x00,  //上半屏
  172. 0x00,0x00,0x00,0x00,0x00,0x00,
  173. 0x00,0x00,0x00,0x00,0x00,0x00,
  174. 0x00,0x00,0x00,0x00,0x00,0x00,
  175. 0x00,0x00,0x00,0x00,0x00,0x00,
  176. 0x00,0x00,0x00,0x00,0x00,0x00,
  177. 0x00,0x00,0x00,0x00,0x00,0x00,
  178. 0x00,0x00,0x00,0x00,0x00,0x00,

  179. //------------上半屏和下半屏的分割线-----------

  180. 0x00,0x00,0x00,0x00,0x00,0x00,  //下半屏
  181. 0x00,0x00,0x00,0x00,0x00,0x00,
  182. 0x00,0x00,0x00,0x00,0x00,0x00,
  183. 0x00,0x00,0x00,0x00,0x00,0x00,
  184. 0x00,0x00,0x00,0x00,0x00,0x00,
  185. 0x00,0x00,0x00,0x00,0x00,0x00,
  186. 0x00,0x00,0x00,0x00,0x00,0x00,
  187. 0x00,0x00,0x00,0x00,0x00,0x00,
  188. };


  189. /* 注释二:
  190. * 以下5个变量记录一个参数的5种信息,包括小数点的数量,小数点个数,数据的位置,数组具体值,整数个数
  191. */
  192. unsigned char ucDotCnt_1=0;  //记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
  193. unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  194. unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。
  195. unsigned char ucDataBuffer_1[6]={0,10,10,10,10,10}; //一项的BCD码数组缓冲
  196. unsigned char ucIntCnt_1=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效

  197. unsigned long ulData_1=0; //用一个long变量表示BCD码的具体数值。


  198. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  199. unsigned char ucKeySec=0;   //被触发的按键编号
  200. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  201. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  202. unsigned char ucRowRecord=1; //记录当前扫描到第几列了


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


  204. unsigned char ucWd=1; //窗口变量
  205. unsigned char ucPart=2; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


  206. unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  207. unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零
  208. unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零

  209. void main()
  210.   {
  211.         initial_myself();      //第一区,上电后马上初始化
  212.         delay_long(100);       //一线,延时线。延时一段时间
  213.         initial_peripheral();  //第二区,上电后延时一段时间再初始化

  214.         while(1)   //第三区
  215.         {
  216.                     key_service(); //按键服务程序
  217.             lcd_display_service(); //应用层面的液晶屏显示程序
  218.         }

  219. }


  220. void initial_myself()  //第一区 上电后马上初始化
  221. {

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

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

  224.    TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  225.    TL0=0x2f;
  226. }
  227. void initial_peripheral() //第二区 上电后延时一段时间再初始化
  228. {
  229.     LCDInit(); //初始化12864 内部包含液晶模块的复位


  230.     EA=1;     //开总中断
  231.     ET0=1;    //允许定时中断
  232.     TR0=1;    //启动定时中断

  233. }


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

  238.   key_scan();//按键扫描函数 放在定时中断里

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


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


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

  255.   switch(ucKeyStep)
  256.   {
  257.      case 1:   //按键扫描输出第ucRowRecord列低电平
  258.               if(ucRowRecord==1)  //第一列输出低电平
  259.                   {
  260.              key_dr1=0;      
  261.              key_dr2=1;
  262.              key_dr3=1;   
  263.              key_dr4=1;
  264.                   }
  265.               else if(ucRowRecord==2)  //第二列输出低电平
  266.                   {
  267.              key_dr1=1;      
  268.              key_dr2=0;
  269.              key_dr3=1;   
  270.              key_dr4=1;
  271.                   }
  272.               else if(ucRowRecord==3)  //第三列输出低电平
  273.                   {
  274.              key_dr1=1;      
  275.              key_dr2=1;
  276.              key_dr3=0;   
  277.              key_dr4=1;
  278.                   }
  279.               else   //第四列输出低电平
  280.                   {
  281.              key_dr1=1;      
  282.              key_dr2=1;
  283.              key_dr3=1;   
  284.              key_dr4=0;
  285.                   }

  286.           uiKeyTimeCnt=0;  //延时计数器清零
  287.           ucKeyStep++;     //切换到下一个运行步骤
  288.               break;

  289.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  290.           uiKeyTimeCnt++;
  291.                   if(uiKeyTimeCnt>1)
  292.                   {
  293.                      uiKeyTimeCnt=0;
  294.              ucKeyStep++;     //切换到下一个运行步骤
  295.                   }
  296.               break;

  297.      case 3:
  298.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  299.           {  
  300.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  301.              ucKeyLock=0;  //按键自锁标志清零
  302.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  303.    
  304.                          ucRowRecord++;  //输出下一列
  305.                          if(ucRowRecord>4)  
  306.                          {
  307.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  308.                          }

  309.           }
  310.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  311.                   {
  312.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  313.                          {
  314.                             uiKeyTimeCnt++;  //去抖动延时计数器
  315.                                 if(uiKeyTimeCnt>const_key_time)
  316.                                 {
  317.                                    uiKeyTimeCnt=0;
  318.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  319.                        if(ucRowRecord==1)  //第一列输出低电平
  320.                            {
  321.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  322.                            }
  323.                        else if(ucRowRecord==2)  //第二列输出低电平
  324.                            {
  325.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  326.                            }
  327.                        else if(ucRowRecord==3)  //第三列输出低电平
  328.                            {
  329.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  330.                            }
  331.                        else   //第四列输出低电平
  332.                            {
  333.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  334.                            }

  335.                                 }
  336.                         
  337.                          }
  338.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  339.                          {
  340.                             uiKeyTimeCnt++;  //去抖动延时计数器
  341.                                 if(uiKeyTimeCnt>const_key_time)
  342.                                 {
  343.                                    uiKeyTimeCnt=0;
  344.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  345.                        if(ucRowRecord==1)  //第一列输出低电平
  346.                            {
  347.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  348.                            }
  349.                        else if(ucRowRecord==2)  //第二列输出低电平
  350.                            {
  351.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  352.                            }
  353.                        else if(ucRowRecord==3)  //第三列输出低电平
  354.                            {
  355.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  356.                            }
  357.                        else   //第四列输出低电平
  358.                            {
  359.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  360.                            }
  361.                                 }
  362.                         
  363.                          }
  364.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  365.                          {
  366.                             uiKeyTimeCnt++;  //去抖动延时计数器
  367.                                 if(uiKeyTimeCnt>const_key_time)
  368.                                 {
  369.                                    uiKeyTimeCnt=0;
  370.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  371.                        if(ucRowRecord==1)  //第一列输出低电平
  372.                            {
  373.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  374.                            }
  375.                        else if(ucRowRecord==2)  //第二列输出低电平
  376.                            {
  377.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  378.                            }
  379.                        else if(ucRowRecord==3)  //第三列输出低电平
  380.                            {
  381.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  382.                            }
  383.                        else   //第四列输出低电平
  384.                            {
  385.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  386.                            }
  387.                                 }
  388.                         
  389.                          }
  390.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  391.                          {
  392.                             uiKeyTimeCnt++;  //去抖动延时计数器
  393.                                 if(uiKeyTimeCnt>const_key_time)
  394.                                 {
  395.                                    uiKeyTimeCnt=0;
  396.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  397.                        if(ucRowRecord==1)  //第一列输出低电平
  398.                            {
  399.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  400.                            }
  401.                        else if(ucRowRecord==2)  //第二列输出低电平
  402.                            {
  403.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  404.                            }
  405.                        else if(ucRowRecord==3)  //第三列输出低电平
  406.                            {
  407.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  408.                            }
  409.                        else   //第四列输出低电平
  410.                            {
  411.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  412.                            }
  413.                                 }
  414.                         
  415.                          }
  416.                   
  417.                   }
  418.               break;

  419.   }


  420. }


  421. void key_service() //按键服务的应用程序
  422. {
  423.   switch(ucKeySec) //按键服务状态切换
  424.   {
  425.     case 1:// 数字1 对应朱兆祺学习板的S1键
  426.           key_number_input(1); //输入数字按键
  427.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  428.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  429.           break;        
  430.     case 2:// 数字2 对应朱兆祺学习板的S2键
  431.           key_number_input(2); //输入数字按键
  432.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  433.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  434.           break;     
  435.     case 3:// 数字3 对应朱兆祺学习板的S3键
  436.           key_number_input(3); //输入数字按键
  437.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  438.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  439.           break;         
  440.     case 4:// 数字4 对应朱兆祺学习板的S4键
  441.           key_number_input(4); //输入数字按键
  442.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  443.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  444.           break;   
  445.     case 5:// 数字5 对应朱兆祺学习板的S5键
  446.           key_number_input(5); //输入数字按键
  447.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  448.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  449.           break;   
  450.     case 6:// 数字6 对应朱兆祺学习板的S6键
  451.           key_number_input(6); //输入数字按键
  452.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  453.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  454.           break;   
  455.     case 7:// 数字7 对应朱兆祺学习板的S7键
  456.           key_number_input(7); //输入数字按键
  457.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  458.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  459.           break;   
  460.     case 8: //数字8 对应朱兆祺学习板的S8键
  461.           key_number_input(8); //输入数字按键
  462.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  463.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  464.           break;   
  465.     case 9:// 数字9 对应朱兆祺学习板的S9键
  466.           key_number_input(9); //输入数字按键
  467.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  468.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  469.           break;   
  470.     case 10:// 数字0  对应朱兆祺学习板的S10键
  471.           key_number_input(0); //输入数字按键
  472.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  473.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  474.           break;   
  475.     case 11:// 小数点按键 对应朱兆祺学习板的S11键
  476.           key_number_input(11); //输入数字按键  11代表小数点
  477.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  478.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  479.           break;   
  480.     case 12:// 本节暂时不用 对应朱兆祺学习板的S12键  

  481.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  482.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  483.           break;   
  484.     case 13:// 加按键 对应朱兆祺学习板的S13键   
  485.           ulData_1++;
  486.           if(ulData_1>99999)
  487.           {
  488.               ulData_1=99999;
  489.           }

  490.           data_to_buffer(ulData_1,  //把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
  491.                                              2,  //小数点最大个数
  492.                                                  6,  //数组缓冲最大个数
  493.                                                  &ucDotCnt_1,
  494.                                                  &ucDotBitS_1,
  495.                                                  &ucWdPartCnt_1,
  496.                                                  ucDataBuffer_1);  //被转换成的数组

  497.           ucWd1Part1Update=1;   //实时更新显示数组
  498.           ucWd1Part2Update=1;   //实时更新显示数值

  499.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  500.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  501.           break;   
  502.     case 14:// 减按键  对应朱兆祺学习板的S14键   
  503.           ulData_1--;
  504.           if(ulData_1>99999) //unsigned long类型的变量0减去1会变成0xffffffff
  505.           {
  506.               ulData_1=0;
  507.           }

  508.           data_to_buffer(ulData_1,  //把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
  509.                                              2,  //小数点最大个数
  510.                                                  6,  //数组缓冲最大个数
  511.                                                  &ucDotCnt_1,
  512.                                                  &ucDotBitS_1,
  513.                                                  &ucWdPartCnt_1,
  514.                                                  ucDataBuffer_1);  //被转换成的数组

  515.           ucWd1Part1Update=1;   //实时更新显示数组
  516.           ucWd1Part2Update=1;   //实时更新显示数值

  517.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  518.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  519.           break;   
  520.     case 15:// 本节暂时不用 对应朱兆祺学习板的S15键

  521.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  522.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  523.           break;   
  524.     case 16:// 清除按键 对应朱兆祺学习板的S16键
  525.           key_delete_input(); //删除按键
  526.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  527.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  528.           break;   
  529.   }               
  530. }



  531. void key_number_input(unsigned char ucKeyNumber) //输入数字按键
  532. {

  533.     switch(ucWd)
  534.     {
  535.        case 1:   //第1窗口。本节程序只有1个窗口
  536.              switch(ucPart)
  537.              {

  538.                  case 1:  //1窗口第1项
  539.                       set_data(ucKeyNumber,
  540.                                                    2,  //小数点最大个数
  541.                                                            6,  //数组缓冲最大个数
  542.                                                        &ucDotCnt_1,
  543.                                                            &ucDotBitS_1,
  544.                                                            &ucWdPartCnt_1,
  545.                                                            ucDataBuffer_1,
  546.                                                            3, //整数部分的最大个数
  547.                                                            &ucIntCnt_1);

  548.                                           ulData_1=buffer_to_data(6,2,ucDataBuffer_1); //把带小数点的BCD码数组转换成long数值。
  549.                       ucWd1Part1Update=1;//第一行局部更新显示
  550.                                           ucWd1Part2Update=1;//第二行局部更新显示
  551.                       break;               
  552.              }
  553.                                        
  554.              break;
  555.     }                        
  556.                                 
  557. }


  558. /* 注释三:
  559. * 涉及到参数的4种信息,包括小数点的数量,小数点的个数,数据的位置,数组具体值,整数的数量,整数的个数,以及它们之间的相互作用关系。
  560. * 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。
  561. * 第1个参数ucKeyNumberTemp是当前按键输入的数值。
  562. * 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。
  563. * 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。
  564. * 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
  565. * 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  566. * 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
  567. * 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。
  568. * 第8个参数ucIntCntMax是限定被设置参数的整数部分的最大位数。
  569. * 第9个参数*p_ucIntCnt是记录当前输入的整数部分个数,如果整数部分的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
  570. */
  571. void set_data(unsigned char ucKeyNumberTemp,
  572.               unsigned char ucDotBitMax,
  573.               unsigned char ucDataCntMax,
  574.               unsigned char *p_ucDotCnt,
  575.               unsigned char *p_ucDotBitS,
  576.                           unsigned char *p_ucWdPartCnt,
  577.                           unsigned char *p_ucSetDataBuffer,
  578.                           unsigned char ucIntCntMax,
  579.                           unsigned char *p_ucIntCnt)
  580. {
  581.                     unsigned int i;

  582.                     if(ucKeyNumberTemp==11) //等于小数点
  583.                     {
  584.                        if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。
  585.                        {
  586.                            return; //直接返回退出
  587.                        }
  588.                        else if(*p_ucDotCnt>0)  //小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。
  589.                        {
  590.                            return; //直接返回退出
  591.                        }
  592.                        else  //否则有效,记录当前已经包含一个小数点的信息。
  593.                        {
  594.                            *p_ucDotCnt=1;  //只能包含一个小数点
  595.                        }
  596.                     }
  597.                     else  //如果输入的不是小数点
  598.                     {
  599.                         if(*p_ucDotCnt==1) //如果之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据
  600.                                                 {
  601.                            if(*p_ucDotBitS<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
  602.                            {
  603.                                *p_ucDotBitS=(*p_ucDotBitS)+1;
  604.                            }
  605.                            else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
  606.                            {
  607.                               return; //直接返回退出
  608.                            }
  609.                                             }
  610.                                                 else if(*p_ucIntCnt<ucIntCntMax)//如果之前没有输入小数点,那么输入的就是整数个数超,整数个数没有超过极限
  611.                                                 {
  612.                                                     *p_ucIntCnt=(*p_ucIntCnt)+1;
  613.                                                 }
  614.                                                 else //整数个数超过极限
  615.                                                 {
  616.                              return; //直接返回退出
  617.                                                 }
  618.                     }

  619.             
  620.             
  621.                     if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer[0]==0&&ucKeyNumberTemp!=11)  //如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
  622.                     {
  623.                         ;
  624.                     }        
  625.                     else  //否则,移位
  626.                     {               
  627.                        for(i=0;i<(ucDataCntMax-1);i++)  //移位
  628.                        {
  629.                           p_ucSetDataBuffer[ucDataCntMax-1-i]=p_ucSetDataBuffer[ucDataCntMax-2-i];
  630.                        }
  631.                        *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
  632.                     }
  633.                     p_ucSetDataBuffer[0]=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
  634.                                                                
  635.         

  636. }


  637. /* 注释四:
  638. * 功能:把一个带小数点的BCD码数组转换成一个long类型的数值。
  639. * 第1个参数ucConverDataSize是这个数组的最大有效个数。
  640. * 第2个参数ucConverDotCnt是这个数组要转换成的long数值带几个小数点
  641. * 第3个参数*p_ucConverBuffer是具体此数组的数据
  642. * 函数最后返回被转换的long数值。
  643. */
  644. unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer)
  645. {
  646.    unsigned long ulConverResult=0;
  647.    unsigned long ulConverResultTemp=0;
  648.    unsigned char ucConverResultBuffer[6]; //因为本节内容的ucConverDataSize是6,所以取6.
  649.    unsigned char i;
  650.    unsigned char j;
  651.    unsigned char ucConverFlag;

  652.    for(i=0;i<ucConverDataSize;i++)
  653.    {
  654.       ucConverResultBuffer[i]=0;  //先把临时缓冲区清零
  655.    }

  656.    j=0;
  657.    ucConverFlag=0;
  658.    for(i=0;i<ucConverDataSize;i++)
  659.    {
  660.        if(p_ucConverBuffer[i]==11) //小数点
  661.        {
  662.           ucConverFlag=i; //记录小数点的位置
  663.        }
  664.        else if(p_ucConverBuffer[i]<10)
  665.        {
  666.           ucConverResultBuffer[j]=p_ucConverBuffer[i];  //提取数组中的有效数字
  667.           j++;
  668.        }


  669.    }


  670.    for(i=0;i<ucConverDataSize;i++)   //通过处理每一位从而合成一个long类型的数值
  671.    {
  672.        ulConverResultTemp=0;
  673.        ulConverResultTemp=ucConverResultBuffer[i];
  674.        for(j=0;j<i;j++)
  675.        {
  676.            ulConverResultTemp=ulConverResultTemp*10;  //把每一位对应的进位扩大到对应的倍数
  677.        }
  678.        ulConverResult=ulConverResult+ulConverResultTemp;
  679.    }


  680.    for(i=ucConverFlag;i<ucConverDotCnt;i++) //根据数组小数点的位置和实际要转换成的小数点个数,来扩大到对应的倍数。
  681.    {
  682.       ulConverResult=ulConverResult*10;
  683.    }

  684.    return ulConverResult;
  685. }


  686. /* 注释五:
  687. * 本节的核心函数,值得好好研究!
  688. * 功能:把一个long类型的数值转换成一个带小数点的BCD码数组
  689. * 第1个参数ulWillConverData是即将被转换的unsigned long类型数值。
  690. * 第2个参数ucConverDotCnt是这个数值带几个小数点
  691. * 第3个参数ucConverDataSize是这个数组的最大有效个数。
  692. * 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
  693. * 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  694. * 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
  695. * 第7个参数*p_ucConverBuffer是具体此数组的数据。
  696. */

  697. void data_to_buffer(unsigned long ulWillConverData,
  698.                     unsigned char ucConverDotCnt,
  699.                                         unsigned char ucConverDataSize,
  700.                                         unsigned char *p_ucDotCnt,
  701.                                         unsigned char *p_ucDotBitS,
  702.                                         unsigned char *p_ucWdPartCnt,
  703.                                         unsigned char *p_ucConverBuffer)
  704. {

  705.    unsigned char ucConverResultBuffer[6]; //因为本节内容的ucConverDataSize是6,所以取6.
  706.    unsigned char i;
  707.    unsigned char ucValidaDotCnt=0;

  708.    if(ucConverDotCnt==0)  //没有小数点
  709.    {
  710.       *p_ucDotCnt=0;   //当前没有输入小数点的标志
  711.       *p_ucDotBitS=0;  //当前输入的小数点个数是0

  712.       ucConverResultBuffer[5]=10;  //没有小数点的时候,第5位必然是显示空格

  713.           //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
  714.       if(ulWillConverData>=10000)
  715.       {
  716.          ucConverResultBuffer[4]=ulWillConverData%100000/10000;
  717.       }
  718.       else
  719.       {
  720.          ucConverResultBuffer[4]=10;
  721.       }   

  722.       if(ulWillConverData>=1000)
  723.       {
  724.          ucConverResultBuffer[3]=ulWillConverData%10000/1000;
  725.       }
  726.       else
  727.       {
  728.          ucConverResultBuffer[3]=10;
  729.       }   

  730.       if(ulWillConverData>=100)
  731.       {
  732.          ucConverResultBuffer[2]=ulWillConverData%1000/100;
  733.       }
  734.       else
  735.       {
  736.          ucConverResultBuffer[2]=10;
  737.       }  

  738.       if(ulWillConverData>=10)
  739.       {
  740.          ucConverResultBuffer[1]=ulWillConverData%100/10;
  741.       }
  742.       else
  743.       {
  744.          ucConverResultBuffer[1]=10;
  745.       }  


  746.       ucConverResultBuffer[0]=ulWillConverData%10;


  747.    }
  748.    else if(ucConverDotCnt==1)  //1位小数点
  749.    {
  750.       *p_ucDotCnt=1;  //当前已经有输入小数点的标志
  751.       *p_ucDotBitS=1; //当前输入的小数点个数是1
  752.       ucConverResultBuffer[1]=11;  //第1位填入小数点11

  753.           //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
  754.       if(ulWillConverData>=10000)
  755.       {
  756.          ucConverResultBuffer[5]=ulWillConverData%100000/10000;
  757.       }
  758.       else
  759.       {
  760.          ucConverResultBuffer[5]=10;
  761.       }   

  762.       if(ulWillConverData>=1000)
  763.       {
  764.          ucConverResultBuffer[4]=ulWillConverData%10000/1000;
  765.       }
  766.       else
  767.       {
  768.          ucConverResultBuffer[4]=10;
  769.       }   

  770.       if(ulWillConverData>=100)
  771.       {
  772.          ucConverResultBuffer[3]=ulWillConverData%1000/100;
  773.       }
  774.       else
  775.       {
  776.          ucConverResultBuffer[3]=10;
  777.       }  


  778.       ucConverResultBuffer[2]=ulWillConverData%100/10;
  779.       ucConverResultBuffer[0]=ulWillConverData%10;


  780.    }
  781.    else if(ucConverDotCnt==2)  //2位小数点
  782.    {
  783.       *p_ucDotCnt=1;  //当前已经有输入小数点的标志
  784.       *p_ucDotBitS=2; //当前输入的小数点个数是2

  785.       ucConverResultBuffer[2]=11; //第2位填入小数点11

  786.           //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
  787.       if(ulWillConverData>=10000)
  788.       {
  789.          ucConverResultBuffer[5]=ulWillConverData%100000/10000;
  790.       }
  791.       else
  792.       {
  793.          ucConverResultBuffer[5]=10;
  794.       }   

  795.       if(ulWillConverData>=1000)
  796.       {
  797.          ucConverResultBuffer[4]=ulWillConverData%10000/1000;
  798.       }
  799.       else
  800.       {
  801.          ucConverResultBuffer[4]=10;
  802.       }   


  803.       ucConverResultBuffer[3]=ulWillConverData%1000/100;
  804.       ucConverResultBuffer[1]=ulWillConverData%100/10;
  805.       ucConverResultBuffer[0]=ulWillConverData%10;
  806.    }


  807.    ucValidaDotCnt=0;
  808.    for(i=0;i<ucConverDataSize;i++)
  809.    {
  810.       if(ucConverResultBuffer[i]!=10)  //统计数组有效的BCD码位数
  811.       {
  812.          ucValidaDotCnt++;
  813.       }
  814.       p_ucConverBuffer[i]=ucConverResultBuffer[i];  //把转换的结果传输给实际的数组用来外部显示
  815.      
  816.    }

  817.    *p_ucWdPartCnt=ucValidaDotCnt-1;  //当前显示的实际位置
  818. }

  819. void key_delete_input(void) //删除按键
  820. {
  821.     static unsigned int i;

  822.    switch(ucWd)
  823.    {
  824.       case 1:   //第1窗口。本节程序只有1个窗口
  825.            switch(ucPart)
  826.            {
  827.               case 1:  //1窗口第1行
  828.                     //清零
  829.                                         ulData_1=0; //long数值清零
  830.                                          ucIntCnt_1=0;
  831.                     ucDotBitS_1=0;  
  832.                     ucDotCnt_1=0;  
  833.                     ucWdPartCnt_1=0;               
  834.                     for(i=0;i<6;i++)  
  835.                     {
  836.                        ucDataBuffer_1[i]=10;
  837.                     }
  838.                     ucDataBuffer_1[0]=0; //第0个位置填入0
  839.         
  840.                     ucWd1Part1Update=1;//第一行局部更新显示
  841.                                     ucWd1Part2Update=1;//第二行局部更新显示
  842.                     break;               
  843.               case 2:  //1窗口第2行
  844.                     //清零
  845.                                         ulData_1=0; //long数值清零
  846.                                          ucIntCnt_1=0;
  847.                     ucDotBitS_1=0;  
  848.                     ucDotCnt_1=0;  
  849.                     ucWdPartCnt_1=0;               
  850.                     for(i=0;i<6;i++)  
  851.                     {
  852.                        ucDataBuffer_1[i]=10;
  853.                     }
  854.                     ucDataBuffer_1[0]=0; //第0个位置填入0
  855.         
  856.                     ucWd1Part1Update=1;//第一行局部更新显示
  857.                                     ucWd1Part2Update=1;//第二行局部更新显示
  858.                     break;
  859.            }
  860.                                        
  861.            break;
  862.         
  863.    }                        
  864.                                 
  865. }

  866. unsigned char *number_to_matrix(unsigned char  ucBitNumber)
  867. {
  868.     unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

  869.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
  870.         {
  871.             case 0:
  872.              p_ucAnyNumber=Zf816_0;
  873.                      break;
  874.             case 1:
  875.              p_ucAnyNumber=Zf816_1;
  876.                      break;
  877.             case 2:
  878.              p_ucAnyNumber=Zf816_2;
  879.                      break;
  880.             case 3:
  881.              p_ucAnyNumber=Zf816_3;
  882.                      break;
  883.             case 4:
  884.              p_ucAnyNumber=Zf816_4;
  885.                      break;
  886.             case 5:
  887.              p_ucAnyNumber=Zf816_5;
  888.                      break;
  889.             case 6:
  890.              p_ucAnyNumber=Zf816_6;
  891.                      break;
  892.             case 7:
  893.              p_ucAnyNumber=Zf816_7;
  894.                      break;
  895.             case 8:
  896.              p_ucAnyNumber=Zf816_8;
  897.                      break;
  898.             case 9:
  899.              p_ucAnyNumber=Zf816_9;
  900.                      break;
  901.             case 10:  //空格
  902.              p_ucAnyNumber=Zf816_nc;
  903.                      break;
  904.                         case 11:   //小数点
  905.              p_ucAnyNumber=Zf816_dot;
  906.                      break;
  907.                 default:   //如果上面的条件都不符合,那么默认指向空字模
  908.              p_ucAnyNumber=Zf816_nc;
  909.                      break;
  910.         }

  911.     return p_ucAnyNumber;  //返回转换结束后的指针
  912. }



  913. void lcd_display_service(void) //应用层面的液晶屏显示程序
  914. {


  915.     static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
  916.     static unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
  917.     static unsigned int i;
  918.         static unsigned char ucDataBuffer_temp[6]; //分解一个10进制的long类型数据的每一位

  919.     switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  920.     {
  921.         case 1:   //显示窗口1的数据
  922.                if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
  923.                {
  924.                      ucWd1Update=0;  //及时清零,避免一直更新

  925.                      ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
  926.                      ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进

  927.                      display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  928.                      clear_all_canvas();  //把画布全部清零

  929.                      display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一项数组
  930.                      display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
  931.                      display_lattice(2,0,Hz1616_shu,0,2,16,0);   
  932.                      display_lattice(3,0,Hz1616_zhu,0,2,16,0);
  933.                      display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

  934.                      display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一项数值
  935.                      display_lattice(1,16,Hz1616_xiang,0,2,16,0);   
  936.                      display_lattice(2,16,Hz1616_shu,0,2,16,0);   
  937.                      display_lattice(3,16,Hz1616_zhi,0,2,16,0);
  938.                      display_lattice(4,16,Zf816_mao_hao,0,1,16,0); //冒号              

  939.                }

  940.                if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
  941.                {
  942.                         ucWd1Part1Update=0; //及时清零,避免一直更新

  943.                         if(ucPart==1) //被选中
  944.                         {
  945.                            ucCursorFlag=1; //反显 显示
  946.                         }
  947.                         else //没被选中
  948.                         {
  949.                             ucCursorFlag=0; //正常 显示
  950.                         }

  951.                         
  952.                         for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  953.                         {
  954.                             p_ucAnyNumber=number_to_matrix(ucDataBuffer_1[5-i]);
  955.                             insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  956.                         }

  957.                         display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  958.                }

  959.                if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  960.                {
  961.                         ucWd1Part2Update=0; //及时清零,避免一直更新

  962.                         if(ucPart==2) //被选中
  963.                         {
  964.                            ucCursorFlag=1; //反显 显示
  965.                         }
  966.                         else //没被选中
  967.                         {
  968.                             ucCursorFlag=0; //正常 显示
  969.                         }

  970.                         if(ulData_1>=10000)
  971.                                                 {
  972.                                                    ucDataBuffer_temp[5]=ulData_1%100000/10000;
  973.                                                 }
  974.                                                 else
  975.                                                 {
  976.                                                    ucDataBuffer_temp[5]=10; //空格
  977.                                                 }

  978.                         if(ulData_1>=1000)
  979.                                                 {
  980.                                                       ucDataBuffer_temp[4]=ulData_1%10000/1000;
  981.                         }
  982.                                                 else
  983.                                                 {
  984.                                                       ucDataBuffer_temp[4]=10; //空格
  985.                         }

  986.                                                 ucDataBuffer_temp[3]=ulData_1%1000/100;
  987.                                                 ucDataBuffer_temp[2]=11;  //11代表小数点
  988.                                                 ucDataBuffer_temp[1]=ulData_1%100/10;
  989.                                                 ucDataBuffer_temp[0]=ulData_1%10/1;
  990.                         
  991.                         for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  992.                         {
  993.                             p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp[5-i]);
  994.                             insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  995.                         }

  996.                         display_lattice(5,16,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  997.                }

  998.                      
  999.                break;
  1000.         //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...        
  1001.     }

  1002. }



  1003. void clear_all_canvas(void)  //把画布全部清零
  1004. {
  1005.    unsigned int j=0;
  1006.    unsigned int i=0;

  1007.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  1008.    {
  1009.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  1010.       {
  1011.                   ucCanvasBuffer[j*4+i]=0x00;
  1012.       }
  1013.    }         

  1014. }





  1015. void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
  1016. {   

  1017.     unsigned char x,y;
  1018.     WriteCommand(0x34);  //关显示缓冲指令            
  1019.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  1020.     y=0;
  1021.     while(y<32)  //y轴的范围0至31
  1022.     {
  1023.          WriteCommand(y+0x80);        //垂直地址
  1024.          WriteCommand(0x80);          //水平地址
  1025.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  1026.          {  
  1027.             LCDWriteData(ucFillDate);
  1028.          }
  1029.          y++;
  1030.     }
  1031.     WriteCommand(0x36); //开显示缓冲指令

  1032. }

  1033. /* 注释六:
  1034. * 注意,这节内容的画布跟第79节前面的画布大小不一样,第79节前面的横向是4个字节,这节的横向是6个字节。
  1035. * 把字模插入画布的函数.
  1036. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  1037. * 第1,2个参数x,y是在画布中的坐标体系。
  1038. * x的范围是0至5,因为画布的横向只要6个字节。y的范围是0至15,因为画布的纵向只有16行。
  1039. * 第3个参数*ucArray是字模的数组。
  1040. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  1041. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  1042. */
  1043. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  1044. {
  1045.    unsigned int j=0;
  1046.    unsigned int i=0;
  1047.    unsigned char ucTemp;
  1048.    for(j=0;j<y_amount;j++)
  1049.    {
  1050.       for(i=0;i<x_amount;i++)
  1051.       {
  1052.               ucTemp=ucArray[j*x_amount+i];
  1053.               if(ucFbFlag==0)
  1054.               {
  1055.                  ucCanvasBuffer[(y+j)*6+x+i]=ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
  1056.               }
  1057.               else
  1058.               {
  1059.                  ucCanvasBuffer[(y+j)*6+x+i]=~ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
  1060.               }
  1061.       }
  1062.    }         

  1063. }

  1064. /* 注释七:
  1065. * 显示任意点阵函数.
  1066. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  1067. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  1068. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  1069. * 第3个参数*ucArray是字模的数组。
  1070. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  1071. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  1072. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  1073. */
  1074. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
  1075. {
  1076.    unsigned int j=0;
  1077.    unsigned int i=0;
  1078.    unsigned char ucTemp;

  1079. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  1080. //  WriteCommand(0x34);  //关显示缓冲指令            
  1081. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  1082.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  1083.    {
  1084.        WriteCommand(y+j+0x80);        //垂直地址
  1085.        WriteCommand(x+0x80);          //水平地址
  1086.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  1087.        {
  1088.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  1089.            if(ucFbFlag==1)  //反白显示
  1090.            {
  1091.                ucTemp=~ucTemp;
  1092.            }
  1093.            LCDWriteData(ucTemp);
  1094.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  1095.       }
  1096.    }
  1097.    WriteCommand(0x36); //开显示缓冲指令
  1098. }




  1099. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  1100. {
  1101.         unsigned char i;
  1102.         for ( i = 0; i < 8; i++ )
  1103.         {
  1104.                 if ( (ucData << i) & 0x80 )
  1105.                 {
  1106.                         LCDSID_dr = 1;
  1107.                 }
  1108.                 else
  1109.                 {
  1110.                         LCDSID_dr = 0;
  1111.                 }
  1112.                 LCDCLK_dr = 0;
  1113.                 LCDCLK_dr = 1;
  1114.         }
  1115. }

  1116. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  1117. {
  1118.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  1119.         SendByteToLcd( ucWData & 0xf0 );
  1120.         SendByteToLcd( (ucWData << 4) & 0xf0);
  1121. }


  1122. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  1123. {

  1124.         LCDCS_dr = 0;
  1125.         LCDCS_dr = 1;
  1126.         SPIWrite(ucCommand, 0);
  1127.         delay_short(90);
  1128. }

  1129. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  1130. {
  1131.         LCDCS_dr = 0;
  1132.         LCDCS_dr = 1;
  1133.         SPIWrite(ucData, 1);
  1134. }

  1135. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  1136. {
  1137.         LCDRST_dr = 1;  //复位
  1138.         LCDRST_dr = 0;
  1139.         LCDRST_dr = 1;
  1140. }



  1141. void delay_short(unsigned int uiDelayShort) //延时函数
  1142. {
  1143.    unsigned int i;  
  1144.    for(i=0;i<uiDelayShort;i++)
  1145.    {
  1146.      ;  
  1147.    }
  1148. }


  1149. void delay_long(unsigned int uiDelayLong)
  1150. {
  1151.    unsigned int i;
  1152.    unsigned int j;
  1153.    for(i=0;i<uiDelayLong;i++)
  1154.    {
  1155.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  1156.           {
  1157.              ; //一个分号相当于执行一条空语句
  1158.           }
  1159.    }
  1160. }


复制代码

总结陈词:
前面两节都讲了数组和数值的相互转换函数,结合前面的基础,下一节讲数字键盘与液晶菜单的综合程序,欲知详情,请听下回分解----数字键盘与液晶菜单的综合程序。

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

乐于分享,勇于质疑!
112#
 楼主| 发表于 2014-12-24 16:07:32 | 显示全部楼层
安之 发表于 2014-12-24 15:20
吴哥,我准备跟着你学习,但是一开始我就遇到一个问题就是:第二区的外围初始化一加进去就有警告,而且程序 ...

有什么警告?我这边没有。你加 这个QQ群:110291944,我在群里帮你解答。
乐于分享,勇于质疑!
113#
 楼主| 发表于 2014-12-24 23:45:35 | 显示全部楼层
szdzjs 发表于 2014-12-24 19:38
看到鸿哥新出的大作很兴奋,但想想85都出了,是不是到86就封贴了,总觉得意犹未尽。。。

这个不会那么快封贴的,我大概写到100左右会暂时停顿一会,专注去开一个零基础的技术贴,等零基础的贴写完了,出书了,录视频了,然后再回来从100节内容继续更新下去,因为我还有很多东西要分享出去。
乐于分享,勇于质疑!
114#
 楼主| 发表于 2014-12-26 11:05:08 | 显示全部楼层
第八十六节:数字键盘与液晶菜单的综合程序。

开场白:
    前面已经介绍完数值跟BCD码数组相互转换的算法,但是按键只能设置一项数据。如果多增加一项数据,变成两项数据,按键与显示菜单之间该如何关联起来,这样的程序框架是什么样的?其实很简单很有规律的,只需要在前面的基础上,在按键和显示函数里,根据不同的uPart行变量添加进不同的代码,即可完成。这就是鸿哥写的程序代码,不管添加多少代码,都是有一个“道”可循,非常有规律性。
  
具体内容,请看源代码讲解。

(1)     硬件平台:
基于坚鸿51单片机学习板。数字1键对应S1键,数字2键对应S2键,数字3键对应S3键…. 数字9键对应S9键, 数字0键对应S10键。小数键对应S11,S12按键是光标移动按键,S13按键是加按键,S14按键是减按键,清零键对应S16,其它按键不用。

(2)     实现功能:
通过S12光标移动按键,可以把负显光标切换到不同的行里面,根据光标所在位置,
通过S13,S14这两个加减按键可以更改对应的数。第1行和第2行的数据会彼此有关联,只要改其中一个,另外一个就会同步被更新。同理,第3行和第4行的数据也会彼此有关联,只要改其中一个,另外一个也会同步被更新。

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


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

  3. #define const_key_time  10    //按键去抖动延时的时间

  4. sbit key_sr1=P0^0; //第一行输入
  5. sbit key_sr2=P0^1; //第二行输入
  6. sbit key_sr3=P0^2; //第三行输入
  7. sbit key_sr4=P0^3; //第四行输入

  8. sbit key_dr1=P0^4; //第一列输出
  9. sbit key_dr2=P0^5; //第二列输出
  10. sbit key_dr3=P0^6; //第三列输出
  11. sbit key_dr4=P0^7; //第四列输出

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

  13. sbit  LCDCS_dr  = P1^6;  //片选线
  14. sbit  LCDSID_dr = P1^7;  //串行数据线
  15. sbit  LCDCLK_dr = P3^2;  //串行时钟线
  16. sbit  LCDRST_dr = P3^4;  //复位线

  17. void SendByteToLcd(unsigned char ucData);  //发送一个字节数据到液晶模块
  18. void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  19. void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
  20. void LCDWriteData(unsigned char ucData);   //发送一个字节的数据给液晶模块
  21. void LCDInit(void);  //初始化  函数内部包括液晶模块的复位
  22. void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00   全部显示点阵用0xff
  23. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount);//把字模插入画布.
  24. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr); //显示任意点阵函数
  25. unsigned char *number_to_matrix(unsigned char  ucBitNumber); //把一位数字转换成字模首地址的函数
  26. void delay_short(unsigned int uiDelayshort); //延时
  27. void delay_long(unsigned int uiDelayLong);

  28. void key_number_input(unsigned char ucKeyNumber); //输入数字按键
  29. void set_data(unsigned char ucKeyNumberTemp, //设置参数
  30.               unsigned char ucDotBitMax,
  31.               unsigned char ucDataCntMax,
  32.               unsigned char *p_ucDotCnt,
  33.               unsigned char *p_ucDotBitS,
  34.                           unsigned char *p_ucWdPartCnt,
  35.                           unsigned char *p_ucSetDataBuffer,
  36.                           unsigned char ucIntCntMax,
  37.                           unsigned char *p_ucIntCnt);

  38. void data_to_buffer(unsigned long ulWillConverData,  //把数值转换成数组
  39.                     unsigned char ucConverDotCnt,
  40.                                         unsigned char ucConverDataSize,
  41.                                         unsigned char *p_ucDotCnt,
  42.                                         unsigned char *p_ucDotBitS,
  43.                                         unsigned char *p_ucWdPartCnt,
  44.                                         unsigned char *p_ucConverBuffer);
  45. unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer); //把带小数点的BCD数组转换成long类型的数值。

  46. void key_delete_input(void); //删除按键

  47. void T0_time(); //定时中断函数
  48. void key_service();
  49. void key_scan(); //按键扫描函数 放在定时中断里

  50. void initial_myself();   
  51. void initial_peripheral();


  52. void lcd_display_service(void); //应用层面的液晶屏显示程序
  53. void clear_all_canvas(void);  //把画布全部清零

  54. code unsigned char Zf816_0[]=
  55. {
  56. /*--  文字:  0  --*/
  57. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  58. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  59. };

  60. code unsigned char Zf816_1[]=
  61. {
  62. /*--  文字:  1  --*/
  63. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  64. 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00,
  65. };

  66. code unsigned char Zf816_2[]=
  67. {
  68. /*--  文字:  2  --*/
  69. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  70. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00,
  71. };

  72. code unsigned char Zf816_3[]=
  73. {
  74. /*--  文字:  3  --*/
  75. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  76. 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  77. };

  78. code unsigned char Zf816_4[]=
  79. {
  80. /*--  文字:  4  --*/
  81. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  82. 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00,
  83. };

  84. code unsigned char Zf816_5[]=
  85. {
  86. /*--  文字:  5  --*/
  87. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  88. 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00,
  89. };

  90. code unsigned char Zf816_6[]=
  91. {
  92. /*--  文字:  6  --*/
  93. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  94. 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00,
  95. };


  96. code unsigned char Zf816_7[]=
  97. {
  98. /*--  文字:  7  --*/
  99. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  100. 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00,
  101. };

  102. code unsigned char Zf816_8[]=
  103. {
  104. /*--  文字:  8  --*/
  105. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  106. 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00,
  107. };

  108. code unsigned char Zf816_9[]=
  109. {
  110. /*--  文字:  9  --*/
  111. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  112. 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00,
  113. };


  114. code unsigned char Zf816_nc[]=  //空字模
  115. {
  116. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  117. };

  118. code unsigned char Zf816_dot[]=  //小数点
  119. {
  120. /*--  文字:  .  --*/
  121. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  122. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x60,0x60,0x00,0x00,
  123. };

  124. code unsigned char Zf816_mao_hao[]=  //冒号
  125. {
  126. /*--  文字:  :  --*/
  127. /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
  128. 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,
  129. };

  130. code unsigned char Hz1616_yi[]=
  131. {
  132. /*--  文字:  一  --*/
  133. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  134. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,
  135. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  136. };

  137. code unsigned char Hz1616_er[]=
  138. {
  139. /*--  文字:  二  --*/
  140. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  141. 0x00,0x00,0x00,0x10,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
  142. 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00,
  143. };

  144. code unsigned char Hz1616_xiang[]=
  145. {
  146. /*--  文字:  项  --*/
  147. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  148. 0x00,0x00,0x03,0xFE,0xFC,0x20,0x10,0x40,0x11,0xFC,0x11,0x04,0x11,0x24,0x11,0x24,
  149. 0x11,0x24,0x11,0x24,0x1D,0x24,0xE1,0x34,0x00,0x48,0x01,0x86,0x06,0x02,0x00,0x00,
  150. };

  151. code unsigned char Hz1616_shu[]=
  152. {
  153. /*--  文字:  数  --*/
  154. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  155. 0x08,0x20,0x49,0x30,0x2A,0x20,0x1C,0x20,0xFF,0x7E,0x1C,0x44,0x2B,0x44,0x48,0xC4,
  156. 0x08,0x28,0xFF,0x28,0x12,0x10,0x34,0x10,0x0C,0x28,0x32,0x4E,0xC0,0x84,0x00,0x00,
  157. };

  158. code unsigned char Hz1616_zhu[]=
  159. {
  160. /*--  文字:  组  --*/
  161. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  162. 0x10,0x00,0x19,0xF8,0x11,0x08,0x25,0x08,0x25,0x08,0x79,0xF8,0x09,0x08,0x11,0x08,
  163. 0x21,0x08,0x7D,0xF8,0x01,0x08,0x01,0x08,0x0D,0x08,0x73,0xFE,0x00,0x00,0x00,0x00,
  164. };

  165. code unsigned char Hz1616_zhi[]=
  166. {
  167. /*--  文字:  值  --*/
  168. /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
  169. 0x10,0x40,0x18,0x60,0x17,0xFC,0x10,0x40,0x20,0x80,0x33,0xF8,0x62,0x08,0xA3,0xF8,
  170. 0x22,0x08,0x23,0xF8,0x22,0x08,0x23,0xF8,0x22,0x08,0x22,0x08,0x2F,0xFE,0x20,0x00,
  171. };

  172. /* 注释一:
  173. * 以下是画布显示数组。横向是6个字节,纵向16行,可以显示3个16x16的汉字.
  174. *  注意,这节内容的画布跟前面79章节的画布大小不一样,79节前面的横向是4个字节,这节的横向是6个字节。
  175. */
  176. unsigned char ucCanvasBuffer[]=
  177. {
  178. 0x00,0x00,0x00,0x00,0x00,0x00,  //上半屏
  179. 0x00,0x00,0x00,0x00,0x00,0x00,
  180. 0x00,0x00,0x00,0x00,0x00,0x00,
  181. 0x00,0x00,0x00,0x00,0x00,0x00,
  182. 0x00,0x00,0x00,0x00,0x00,0x00,
  183. 0x00,0x00,0x00,0x00,0x00,0x00,
  184. 0x00,0x00,0x00,0x00,0x00,0x00,
  185. 0x00,0x00,0x00,0x00,0x00,0x00,

  186. //------------上半屏和下半屏的分割线-----------

  187. 0x00,0x00,0x00,0x00,0x00,0x00,  //下半屏
  188. 0x00,0x00,0x00,0x00,0x00,0x00,
  189. 0x00,0x00,0x00,0x00,0x00,0x00,
  190. 0x00,0x00,0x00,0x00,0x00,0x00,
  191. 0x00,0x00,0x00,0x00,0x00,0x00,
  192. 0x00,0x00,0x00,0x00,0x00,0x00,
  193. 0x00,0x00,0x00,0x00,0x00,0x00,
  194. 0x00,0x00,0x00,0x00,0x00,0x00,
  195. };


  196. /* 注释二:
  197. * 以下5个变量记录一个参数的5种信息,包括小数点的数量,小数点个数,数据的位置,数组具体值,整数个数
  198. */
  199. unsigned char ucDotCnt_1=0;  //记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
  200. unsigned char ucDotBitS_1=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  201. unsigned char ucWdPartCnt_1=0; //记录当前输入的数据在数组中的位置。
  202. unsigned char ucDataBuffer_1[6]={0,10,10,10,10,10}; //一项的BCD码数组缓冲
  203. unsigned char ucIntCnt_1=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
  204. unsigned long ulData_1=0; //用一个long变量表示BCD码的具体数值。

  205. unsigned char ucDotCnt_2=0;  //记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效
  206. unsigned char ucDotBitS_2=0; //记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  207. unsigned char ucWdPartCnt_2=0; //记录当前输入的数据在数组中的位置。
  208. unsigned char ucDataBuffer_2[6]={0,10,10,10,10,10}; //一项的BCD码数组缓冲
  209. unsigned char ucIntCnt_2=0; //记录当前输入的整数个数,如果整数的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
  210. unsigned long ulData_2=0; //用一个long变量表示BCD码的具体数值。


  211. unsigned char ucKeyStep=1;  //按键扫描步骤变量

  212. unsigned char ucKeySec=0;   //被触发的按键编号
  213. unsigned int  uiKeyTimeCnt=0; //按键去抖动延时计数器
  214. unsigned char ucKeyLock=0; //按键触发后自锁的变量标志

  215. unsigned char ucRowRecord=1; //记录当前扫描到第几列了


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


  217. unsigned char ucWd=1; //窗口变量
  218. unsigned char ucPart=1; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行


  219. unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量      1代表更新显示,响应函数内部会清零
  220. unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零
  221. unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零
  222. unsigned char ucWd1Part3Update=0; //窗口1的第1行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零
  223. unsigned char ucWd1Part4Update=0; //窗口1的第2行局部更新显示变量  1代表更新显示,响应函数内部会自动把它清零
  224. void main()
  225.   {
  226.         initial_myself();      //第一区,上电后马上初始化
  227.         delay_long(100);       //一线,延时线。延时一段时间
  228.         initial_peripheral();  //第二区,上电后延时一段时间再初始化

  229.         while(1)   //第三区
  230.         {
  231.                     key_service(); //按键服务程序
  232.             lcd_display_service(); //应用层面的液晶屏显示程序
  233.         }

  234. }


  235. void initial_myself()  //第一区 上电后马上初始化
  236. {

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

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

  239.    TH0=0xf8;   //重装初始值(65535-2000)=63535=0xf82f
  240.    TL0=0x2f;
  241. }
  242. void initial_peripheral() //第二区 上电后延时一段时间再初始化
  243. {
  244.     LCDInit(); //初始化12864 内部包含液晶模块的复位


  245.     EA=1;     //开总中断
  246.     ET0=1;    //允许定时中断
  247.     TR0=1;    //启动定时中断

  248. }


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

  253.   key_scan();//按键扫描函数 放在定时中断里

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


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


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

  270.   switch(ucKeyStep)
  271.   {
  272.      case 1:   //按键扫描输出第ucRowRecord列低电平
  273.               if(ucRowRecord==1)  //第一列输出低电平
  274.                   {
  275.              key_dr1=0;      
  276.              key_dr2=1;
  277.              key_dr3=1;   
  278.              key_dr4=1;
  279.                   }
  280.               else if(ucRowRecord==2)  //第二列输出低电平
  281.                   {
  282.              key_dr1=1;      
  283.              key_dr2=0;
  284.              key_dr3=1;   
  285.              key_dr4=1;
  286.                   }
  287.               else if(ucRowRecord==3)  //第三列输出低电平
  288.                   {
  289.              key_dr1=1;      
  290.              key_dr2=1;
  291.              key_dr3=0;   
  292.              key_dr4=1;
  293.                   }
  294.               else   //第四列输出低电平
  295.                   {
  296.              key_dr1=1;      
  297.              key_dr2=1;
  298.              key_dr3=1;   
  299.              key_dr4=0;
  300.                   }

  301.           uiKeyTimeCnt=0;  //延时计数器清零
  302.           ucKeyStep++;     //切换到下一个运行步骤
  303.               break;

  304.      case 2:     //此处的小延时用来等待刚才列输出信号稳定,再判断输入信号。不是去抖动延时。
  305.           uiKeyTimeCnt++;
  306.                   if(uiKeyTimeCnt>1)
  307.                   {
  308.                      uiKeyTimeCnt=0;
  309.              ucKeyStep++;     //切换到下一个运行步骤
  310.                   }
  311.               break;

  312.      case 3:
  313.           if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  314.           {  
  315.              ucKeyStep=1;  //如果没有按键按下,返回到第一个运行步骤重新开始扫描
  316.              ucKeyLock=0;  //按键自锁标志清零
  317.              uiKeyTimeCnt=0; //按键去抖动延时计数器清零,此行非常巧妙     
  318.    
  319.                          ucRowRecord++;  //输出下一列
  320.                          if(ucRowRecord>4)  
  321.                          {
  322.                             ucRowRecord=1; //依次输出完四列之后,继续从第一列开始输出低电平
  323.                          }

  324.           }
  325.                   else if(ucKeyLock==0)  //有按键按下,且是第一次触发
  326.                   {
  327.                      if(key_sr1==0&&key_sr2==1&&key_sr3==1&&key_sr4==1)
  328.                          {
  329.                             uiKeyTimeCnt++;  //去抖动延时计数器
  330.                                 if(uiKeyTimeCnt>const_key_time)
  331.                                 {
  332.                                    uiKeyTimeCnt=0;
  333.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零

  334.                        if(ucRowRecord==1)  //第一列输出低电平
  335.                            {
  336.                                       ucKeySec=1;  //触发1号键 对应朱兆祺学习板的S1键
  337.                            }
  338.                        else if(ucRowRecord==2)  //第二列输出低电平
  339.                            {
  340.                                       ucKeySec=2;  //触发2号键 对应朱兆祺学习板的S2键
  341.                            }
  342.                        else if(ucRowRecord==3)  //第三列输出低电平
  343.                            {
  344.                                       ucKeySec=3;  //触发3号键 对应朱兆祺学习板的S3键
  345.                            }
  346.                        else   //第四列输出低电平
  347.                            {
  348.                                       ucKeySec=4;  //触发4号键 对应朱兆祺学习板的S4键
  349.                            }

  350.                                 }
  351.                         
  352.                          }
  353.                      else if(key_sr1==1&&key_sr2==0&&key_sr3==1&&key_sr4==1)
  354.                          {
  355.                             uiKeyTimeCnt++;  //去抖动延时计数器
  356.                                 if(uiKeyTimeCnt>const_key_time)
  357.                                 {
  358.                                    uiKeyTimeCnt=0;
  359.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  360.                        if(ucRowRecord==1)  //第一列输出低电平
  361.                            {
  362.                                       ucKeySec=5;  //触发5号键 对应朱兆祺学习板的S5键
  363.                            }
  364.                        else if(ucRowRecord==2)  //第二列输出低电平
  365.                            {
  366.                                       ucKeySec=6;  //触发6号键 对应朱兆祺学习板的S6键
  367.                            }
  368.                        else if(ucRowRecord==3)  //第三列输出低电平
  369.                            {
  370.                                       ucKeySec=7;  //触发7号键 对应朱兆祺学习板的S7键
  371.                            }
  372.                        else   //第四列输出低电平
  373.                            {
  374.                                       ucKeySec=8;  //触发8号键 对应朱兆祺学习板的S8键
  375.                            }
  376.                                 }
  377.                         
  378.                          }
  379.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==0&&key_sr4==1)
  380.                          {
  381.                             uiKeyTimeCnt++;  //去抖动延时计数器
  382.                                 if(uiKeyTimeCnt>const_key_time)
  383.                                 {
  384.                                    uiKeyTimeCnt=0;
  385.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  386.                        if(ucRowRecord==1)  //第一列输出低电平
  387.                            {
  388.                                       ucKeySec=9;  //触发9号键 对应朱兆祺学习板的S9键
  389.                            }
  390.                        else if(ucRowRecord==2)  //第二列输出低电平
  391.                            {
  392.                                       ucKeySec=10;  //触发10号键 对应朱兆祺学习板的S10键
  393.                            }
  394.                        else if(ucRowRecord==3)  //第三列输出低电平
  395.                            {
  396.                                       ucKeySec=11;  //触发11号键 对应朱兆祺学习板的S11键
  397.                            }
  398.                        else   //第四列输出低电平
  399.                            {
  400.                                       ucKeySec=12;  //触发12号键 对应朱兆祺学习板的S12键
  401.                            }
  402.                                 }
  403.                         
  404.                          }
  405.                      else if(key_sr1==1&&key_sr2==1&&key_sr3==1&&key_sr4==0)
  406.                          {
  407.                             uiKeyTimeCnt++;  //去抖动延时计数器
  408.                                 if(uiKeyTimeCnt>const_key_time)
  409.                                 {
  410.                                    uiKeyTimeCnt=0;
  411.                                    ucKeyLock=1;//自锁按键置位,避免一直触发,只有松开按键,此标志位才会被清零
  412.                        if(ucRowRecord==1)  //第一列输出低电平
  413.                            {
  414.                                       ucKeySec=13;  //触发13号键 对应朱兆祺学习板的S13键
  415.                            }
  416.                        else if(ucRowRecord==2)  //第二列输出低电平
  417.                            {
  418.                                       ucKeySec=14;  //触发14号键 对应朱兆祺学习板的S14键
  419.                            }
  420.                        else if(ucRowRecord==3)  //第三列输出低电平
  421.                            {
  422.                                       ucKeySec=15;  //触发15号键 对应朱兆祺学习板的S15键
  423.                            }
  424.                        else   //第四列输出低电平
  425.                            {
  426.                                       ucKeySec=16;  //触发16号键 对应朱兆祺学习板的S16键
  427.                            }
  428.                                 }
  429.                         
  430.                          }
  431.                   
  432.                   }
  433.               break;

  434.   }


  435. }


  436. void key_service() //按键服务的应用程序
  437. {
  438.   switch(ucKeySec) //按键服务状态切换
  439.   {
  440.     case 1:// 数字1 对应朱兆祺学习板的S1键
  441.           key_number_input(1); //输入数字按键
  442.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  443.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  444.           break;        
  445.     case 2:// 数字2 对应朱兆祺学习板的S2键
  446.           key_number_input(2); //输入数字按键
  447.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  448.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  449.           break;     
  450.     case 3:// 数字3 对应朱兆祺学习板的S3键
  451.           key_number_input(3); //输入数字按键
  452.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  453.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  454.           break;         
  455.     case 4:// 数字4 对应朱兆祺学习板的S4键
  456.           key_number_input(4); //输入数字按键
  457.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  458.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  459.           break;   
  460.     case 5:// 数字5 对应朱兆祺学习板的S5键
  461.           key_number_input(5); //输入数字按键
  462.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  463.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  464.           break;   
  465.     case 6:// 数字6 对应朱兆祺学习板的S6键
  466.           key_number_input(6); //输入数字按键
  467.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  468.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  469.           break;   
  470.     case 7:// 数字7 对应朱兆祺学习板的S7键
  471.           key_number_input(7); //输入数字按键
  472.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  473.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  474.           break;   
  475.     case 8: //数字8 对应朱兆祺学习板的S8键
  476.           key_number_input(8); //输入数字按键
  477.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  478.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  479.           break;   
  480.     case 9:// 数字9 对应朱兆祺学习板的S9键
  481.           key_number_input(9); //输入数字按键
  482.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  483.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  484.           break;   
  485.     case 10:// 数字0  对应朱兆祺学习板的S10键
  486.           key_number_input(0); //输入数字按键
  487.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  488.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  489.           break;   
  490.     case 11:// 小数点按键 对应朱兆祺学习板的S11键
  491.           key_number_input(11); //输入数字按键  11代表小数点
  492.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  493.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  494.           break;   
  495.     case 12:// 光标移动按键 对应朱兆祺学习板的S12键  
  496.           //ucPart++;
  497.                   //if(ucPart>4)
  498.                   //{
  499.                   //   ucPart=1;
  500.                   //}

  501.                   switch(ucPart)  //根据不同的行来进行不同的操作
  502.                   {
  503.                      case 1:
  504.                               ucPart=2;  
  505.                                   
  506.                   ucWd1Part1Update=1;   //实时更新显示数组
  507.                   ucWd1Part2Update=1;   //实时更新显示数值
  508.                               break;
  509.                      case 2:
  510.                               ucPart=3;  
  511.                   ucWd1Part2Update=1;   //实时更新显示数组
  512.                   ucWd1Part3Update=1;   //实时更新显示数值
  513.                               break;
  514.                      case 3:
  515.                               ucPart=4;  
  516.                   ucWd1Part3Update=1;   //实时更新显示数组
  517.                   ucWd1Part4Update=1;   //实时更新显示数值
  518.                               break;
  519.                      case 4:
  520.                               ucPart=1;  
  521.                   ucWd1Part4Update=1;   //实时更新显示数组
  522.                   ucWd1Part1Update=1;   //实时更新显示数值
  523.                               break;
  524.                   }
  525.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  526.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  527.           break;   
  528.     case 13:// 加按键 对应朱兆祺学习板的S13键   
  529.                   switch(ucPart)  //根据不同的行来进行不同的操作
  530.                   {

  531.                      case 2:
  532.                   ulData_1++;
  533.                   if(ulData_1>99999)
  534.                   {
  535.                      ulData_1=99999;
  536.                   }

  537.                   data_to_buffer(ulData_1,  //把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
  538.                                                      2,  //小数点最大个数
  539.                                                          6,  //数组缓冲最大个数
  540.                                                          &ucDotCnt_1,
  541.                                                          &ucDotBitS_1,
  542.                                                          &ucWdPartCnt_1,
  543.                                                          ucDataBuffer_1);  //被转换成的数组

  544.                   ucWd1Part1Update=1;   //实时更新显示数组
  545.                   ucWd1Part2Update=1;   //实时更新显示数值
  546.                               break;
  547.                      case 4:
  548.                   ulData_2++;
  549.                   if(ulData_2>99999)
  550.                   {
  551.                      ulData_2=99999;
  552.                   }

  553.                   data_to_buffer(ulData_2,  //把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
  554.                                                      2,  //小数点最大个数
  555.                                                          6,  //数组缓冲最大个数
  556.                                                          &ucDotCnt_2,
  557.                                                          &ucDotBitS_2,
  558.                                                          &ucWdPartCnt_2,
  559.                                                          ucDataBuffer_2);  //被转换成的数组

  560.                   ucWd1Part3Update=1;   //实时更新显示数组
  561.                   ucWd1Part4Update=1;   //实时更新显示数值
  562.                               break;
  563.                   }


  564.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  565.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  566.           break;   
  567.     case 14:// 减按键  对应朱兆祺学习板的S14键   
  568.                   switch(ucPart)  //根据不同的行来进行不同的操作
  569.                   {

  570.                      case 2:
  571.                   ulData_1--;
  572.                   if(ulData_1>99999) //unsigned long类型的变量0减去1会变成0xffffffff
  573.                   {
  574.                      ulData_1=0;
  575.                   }

  576.                   data_to_buffer(ulData_1,  //把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
  577.                                                      2,  //小数点最大个数
  578.                                                          6,  //数组缓冲最大个数
  579.                                                          &ucDotCnt_1,
  580.                                                            &ucDotBitS_1,
  581.                                                          &ucWdPartCnt_1,
  582.                                                          ucDataBuffer_1);  //被转换成的数组

  583.                   ucWd1Part1Update=1;   //实时更新显示数组
  584.                   ucWd1Part2Update=1;   //实时更新显示数值
  585.                               break;
  586.                      case 4:
  587.                   ulData_2--;
  588.                   if(ulData_2>99999) //unsigned long类型的变量0减去1会变成0xffffffff
  589.                   {
  590.                      ulData_2=0;
  591.                   }

  592.                   data_to_buffer(ulData_2,  //把数值转换成数组,这是本节核心函数,请好好研究此函数的具体功能。
  593.                                                      2,  //小数点最大个数
  594.                                                          6,  //数组缓冲最大个数
  595.                                                          &ucDotCnt_2,
  596.                                                            &ucDotBitS_2,
  597.                                                          &ucWdPartCnt_2,
  598.                                                          ucDataBuffer_2);  //被转换成的数组

  599.                   ucWd1Part3Update=1;   //实时更新显示数组
  600.                   ucWd1Part4Update=1;   //实时更新显示数值
  601.                               break;
  602.                   }



  603.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  604.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  605.           break;   
  606.     case 15:// 本节暂时不用 对应朱兆祺学习板的S15键

  607.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  608.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  609.           break;   
  610.     case 16:// 清除按键 对应朱兆祺学习板的S16键
  611.           key_delete_input(); //删除按键
  612.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  613.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  614.           break;   
  615.   }               
  616. }



  617. void key_number_input(unsigned char ucKeyNumber) //输入数字按键
  618. {

  619.     switch(ucWd)
  620.     {
  621.        case 1:   //第1窗口。本节程序只有1个窗口
  622.              switch(ucPart)
  623.              {

  624.                  case 1:  //1窗口第1行
  625.                       set_data(ucKeyNumber,
  626.                                                    2,  //小数点最大个数
  627.                                                            6,  //数组缓冲最大个数
  628.                                                        &ucDotCnt_1,
  629.                                                            &ucDotBitS_1,
  630.                                                            &ucWdPartCnt_1,
  631.                                                            ucDataBuffer_1,
  632.                                                            3, //整数部分的最大个数
  633.                                                            &ucIntCnt_1);

  634.                                           ulData_1=buffer_to_data(6,2,ucDataBuffer_1); //把带小数点的BCD码数组转换成long数值。
  635.                       ucWd1Part1Update=1;//第一行局部更新显示
  636.                                           ucWd1Part2Update=1;//第二行局部更新显示
  637.                       break;   
  638.                  case 3:  //1窗口第3行
  639.                       set_data(ucKeyNumber,
  640.                                                    2,  //小数点最大个数
  641.                                                            6,  //数组缓冲最大个数
  642.                                                        &ucDotCnt_2,
  643.                                                            &ucDotBitS_2,
  644.                                                            &ucWdPartCnt_2,
  645.                                                            ucDataBuffer_2,
  646.                                                            3, //整数部分的最大个数
  647.                                                            &ucIntCnt_2);

  648.                                           ulData_2=buffer_to_data(6,2,ucDataBuffer_2); //把带小数点的BCD码数组转换成long数值。
  649.                       ucWd1Part3Update=1;//第三行局部更新显示
  650.                                           ucWd1Part4Update=1;//第四行局部更新显示
  651.                       break;

  652.              }
  653.                                        
  654.              break;
  655.     }                        
  656.                                 
  657. }


  658. /* 注释三:
  659. * 涉及到参数的4种信息,包括小数点的数量,小数点的个数,数据的位置,数组具体值,整数的数量,整数的个数,以及它们之间的相互作用关系。
  660. * 以下参数,指针类型的参数是让代入的全局变量在退出函数后维持它当前最新更改的数值不变。
  661. * 第1个参数ucKeyNumberTemp是当前按键输入的数值。
  662. * 第2个参数ucDotBitMax是限定被设置参数的小数点最大位数。
  663. * 第3个参数ucDataCntMax是限定被设置参数的最大数组个数。
  664. * 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
  665. * 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  666. * 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
  667. * 第7个参数*p_ucSetDataBuffer是BCD码数组缓冲的具体数字内容。
  668. * 第8个参数ucIntCntMax是限定被设置参数的整数部分的最大位数。
  669. * 第9个参数*p_ucIntCnt是记录当前输入的整数部分个数,如果整数部分的个数如果超过规定ucIntCntMax位,此时再按任何输入按键则无效
  670. */
  671. void set_data(unsigned char ucKeyNumberTemp,
  672.               unsigned char ucDotBitMax,
  673.               unsigned char ucDataCntMax,
  674.               unsigned char *p_ucDotCnt,
  675.               unsigned char *p_ucDotBitS,
  676.                           unsigned char *p_ucWdPartCnt,
  677.                           unsigned char *p_ucSetDataBuffer,
  678.                           unsigned char ucIntCntMax,
  679.                           unsigned char *p_ucIntCnt)
  680. {
  681.                     unsigned int i;

  682.                     if(ucKeyNumberTemp==11) //等于小数点
  683.                     {
  684.                        if(ucDotBitMax==0) //如果限定的小数点最大数是0,就意味着此数据不允许带小数点,必须是整数。
  685.                        {
  686.                            return; //直接返回退出
  687.                        }
  688.                        else if(*p_ucDotCnt>0)  //小数点个数大于0,意味着当前数组已经包含了小数点,此时再输入小数点则无效。
  689.                        {
  690.                            return; //直接返回退出
  691.                        }
  692.                        else  //否则有效,记录当前已经包含一个小数点的信息。
  693.                        {
  694.                            *p_ucDotCnt=1;  //只能包含一个小数点
  695.                        }
  696.                     }
  697.                     else  //如果输入的不是小数点
  698.                     {
  699.                         if(*p_ucDotCnt==1) //如果之前已经输入了一个小数点,那么此时输入的数字就是小数点后的数据
  700.                                                 {
  701.                            if(*p_ucDotBitS<ucDotBitMax) //如果小数点位数还没超过最大限制位数,则继续加1记录当前小数点位数。
  702.                            {
  703.                                *p_ucDotBitS=(*p_ucDotBitS)+1;
  704.                            }
  705.                            else //如果小数点位数已经超过允许的范围,则输入的按键无效,直接退出。
  706.                            {
  707.                               return; //直接返回退出
  708.                            }
  709.                                             }
  710.                                                 else if(*p_ucIntCnt<ucIntCntMax)//如果之前没有输入小数点,那么输入的就是整数个数超,整数个数没有超过极限
  711.                                                 {
  712.                                                     *p_ucIntCnt=(*p_ucIntCnt)+1;
  713.                                                 }
  714.                                                 else //整数个数超过极限
  715.                                                 {
  716.                              return; //直接返回退出
  717.                                                 }
  718.                     }

  719.             
  720.             
  721.                     if(*p_ucWdPartCnt==0&&p_ucSetDataBuffer[0]==0&&ucKeyNumberTemp!=11)  //如果当前默认位置是第0个位置,并且默认第0个数据是0,并且当前的按键输入不是小数点,则不用移位
  722.                     {
  723.                         ;
  724.                     }        
  725.                     else  //否则,移位
  726.                     {               
  727.                        for(i=0;i<(ucDataCntMax-1);i++)  //移位
  728.                        {
  729.                           p_ucSetDataBuffer[ucDataCntMax-1-i]=p_ucSetDataBuffer[ucDataCntMax-2-i];
  730.                        }
  731.                        *p_ucWdPartCnt=(*p_ucWdPartCnt)+1;
  732.                     }
  733.                     p_ucSetDataBuffer[0]=ucKeyNumberTemp; //当前输入的数字或者小数点永远在第右边第0个位置。
  734.                                                                
  735.         

  736. }


  737. /* 注释四:
  738. * 功能:把一个带小数点的BCD码数组转换成一个long类型的数值。
  739. * 第1个参数ucConverDataSize是这个数组的最大有效个数。
  740. * 第2个参数ucConverDotCnt是这个数组要转换成的long数值带几个小数点
  741. * 第3个参数*p_ucConverBuffer是具体此数组的数据
  742. * 函数最后返回被转换的long数值。
  743. */
  744. unsigned long buffer_to_data(unsigned char ucConverDataSize,unsigned char ucConverDotCnt,unsigned char *p_ucConverBuffer)
  745. {
  746.    unsigned long ulConverResult=0;
  747.    unsigned long ulConverResultTemp=0;
  748.    unsigned char ucConverResultBuffer[6]; //因为本节内容的ucConverDataSize是6,所以取6.
  749.    unsigned char i;
  750.    unsigned char j;
  751.    unsigned char ucConverFlag;

  752.    for(i=0;i<ucConverDataSize;i++)
  753.    {
  754.       ucConverResultBuffer[i]=0;  //先把临时缓冲区清零
  755.    }

  756.    j=0;
  757.    ucConverFlag=0;
  758.    for(i=0;i<ucConverDataSize;i++)
  759.    {
  760.        if(p_ucConverBuffer[i]==11) //小数点
  761.        {
  762.           ucConverFlag=i; //记录小数点的位置
  763.        }
  764.        else if(p_ucConverBuffer[i]<10)
  765.        {
  766.           ucConverResultBuffer[j]=p_ucConverBuffer[i];  //提取数组中的有效数字
  767.           j++;
  768.        }


  769.    }


  770.    for(i=0;i<ucConverDataSize;i++)   //通过处理每一位从而合成一个long类型的数值
  771.    {
  772.        ulConverResultTemp=0;
  773.        ulConverResultTemp=ucConverResultBuffer[i];
  774.        for(j=0;j<i;j++)
  775.        {
  776.            ulConverResultTemp=ulConverResultTemp*10;  //把每一位对应的进位扩大到对应的倍数
  777.        }
  778.        ulConverResult=ulConverResult+ulConverResultTemp;
  779.    }


  780.    for(i=ucConverFlag;i<ucConverDotCnt;i++) //根据数组小数点的位置和实际要转换成的小数点个数,来扩大到对应的倍数。
  781.    {
  782.       ulConverResult=ulConverResult*10;
  783.    }

  784.    return ulConverResult;
  785. }


  786. /* 注释五:
  787. * 本节的核心函数,值得好好研究!
  788. * 功能:把一个long类型的数值转换成一个带小数点的BCD码数组
  789. * 第1个参数ulWillConverData是即将被转换的unsigned long类型数值。
  790. * 第2个参数ucConverDotCnt是这个数值带几个小数点
  791. * 第3个参数ucConverDataSize是这个数组的最大有效个数。
  792. * 第4个参数*p_ucDotCnt是记录当前输入的小数点数量,如果小数点的数量不为0,说明当前数组已包含小数点,此时再按小数点按键则无效。
  793. * 第5个参数*p_ucDotBitS是记录当前输入的小数点个数,如果小数点的个数如果超过规定ucDotBitMax位,此时再按任何输入按键则无效
  794. * 第6个参数*p_ucWdPartCnt是记录当前输入的数据在数组中的位置,方便锁定每次按键输入的数字显示位置。
  795. * 第7个参数*p_ucConverBuffer是具体此数组的数据。
  796. */

  797. void data_to_buffer(unsigned long ulWillConverData,
  798.                     unsigned char ucConverDotCnt,
  799.                                         unsigned char ucConverDataSize,
  800.                                         unsigned char *p_ucDotCnt,
  801.                                         unsigned char *p_ucDotBitS,
  802.                                         unsigned char *p_ucWdPartCnt,
  803.                                         unsigned char *p_ucConverBuffer)
  804. {

  805.    unsigned char ucConverResultBuffer[6]; //因为本节内容的ucConverDataSize是6,所以取6.
  806.    unsigned char i;
  807.    unsigned char ucValidaDotCnt=0;

  808.    if(ucConverDotCnt==0)  //没有小数点
  809.    {
  810.       *p_ucDotCnt=0;   //当前没有输入小数点的标志
  811.       *p_ucDotBitS=0;  //当前输入的小数点个数是0

  812.       ucConverResultBuffer[5]=10;  //没有小数点的时候,第5位必然是显示空格

  813.           //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
  814.       if(ulWillConverData>=10000)
  815.       {
  816.          ucConverResultBuffer[4]=ulWillConverData%100000/10000;
  817.       }
  818.       else
  819.       {
  820.          ucConverResultBuffer[4]=10;
  821.       }   

  822.       if(ulWillConverData>=1000)
  823.       {
  824.          ucConverResultBuffer[3]=ulWillConverData%10000/1000;
  825.       }
  826.       else
  827.       {
  828.          ucConverResultBuffer[3]=10;
  829.       }   

  830.       if(ulWillConverData>=100)
  831.       {
  832.          ucConverResultBuffer[2]=ulWillConverData%1000/100;
  833.       }
  834.       else
  835.       {
  836.          ucConverResultBuffer[2]=10;
  837.       }  

  838.       if(ulWillConverData>=10)
  839.       {
  840.          ucConverResultBuffer[1]=ulWillConverData%100/10;
  841.       }
  842.       else
  843.       {
  844.          ucConverResultBuffer[1]=10;
  845.       }  


  846.       ucConverResultBuffer[0]=ulWillConverData%10;


  847.    }
  848.    else if(ucConverDotCnt==1)  //1位小数点
  849.    {
  850.       *p_ucDotCnt=1;  //当前已经有输入小数点的标志
  851.       *p_ucDotBitS=1; //当前输入的小数点个数是1
  852.       ucConverResultBuffer[1]=11;  //第1位填入小数点11

  853.           //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
  854.       if(ulWillConverData>=10000)
  855.       {
  856.          ucConverResultBuffer[5]=ulWillConverData%100000/10000;
  857.       }
  858.       else
  859.       {
  860.          ucConverResultBuffer[5]=10;
  861.       }   

  862.       if(ulWillConverData>=1000)
  863.       {
  864.          ucConverResultBuffer[4]=ulWillConverData%10000/1000;
  865.       }
  866.       else
  867.       {
  868.          ucConverResultBuffer[4]=10;
  869.       }   

  870.       if(ulWillConverData>=100)
  871.       {
  872.          ucConverResultBuffer[3]=ulWillConverData%1000/100;
  873.       }
  874.       else
  875.       {
  876.          ucConverResultBuffer[3]=10;
  877.       }  


  878.       ucConverResultBuffer[2]=ulWillConverData%100/10;
  879.       ucConverResultBuffer[0]=ulWillConverData%10;


  880.    }
  881.    else if(ucConverDotCnt==2)  //2位小数点
  882.    {
  883.       *p_ucDotCnt=1;  //当前已经有输入小数点的标志
  884.       *p_ucDotBitS=2; //当前输入的小数点个数是2

  885.       ucConverResultBuffer[2]=11; //第2位填入小数点11

  886.           //以下是具体把数值转换成数组,不需要显示的高位填入10表示显示空格
  887.       if(ulWillConverData>=10000)
  888.       {
  889.          ucConverResultBuffer[5]=ulWillConverData%100000/10000;
  890.       }
  891.       else
  892.       {
  893.          ucConverResultBuffer[5]=10;
  894.       }   

  895.       if(ulWillConverData>=1000)
  896.       {
  897.          ucConverResultBuffer[4]=ulWillConverData%10000/1000;
  898.       }
  899.       else
  900.       {
  901.          ucConverResultBuffer[4]=10;
  902.       }   


  903.       ucConverResultBuffer[3]=ulWillConverData%1000/100;
  904.       ucConverResultBuffer[1]=ulWillConverData%100/10;
  905.       ucConverResultBuffer[0]=ulWillConverData%10;
  906.    }


  907.    ucValidaDotCnt=0;
  908.    for(i=0;i<ucConverDataSize;i++)
  909.    {
  910.       if(ucConverResultBuffer[i]!=10)  //统计数组有效的BCD码位数
  911.       {
  912.          ucValidaDotCnt++;
  913.       }
  914.       p_ucConverBuffer[i]=ucConverResultBuffer[i];  //把转换的结果传输给实际的数组用来外部显示
  915.      
  916.    }

  917.    *p_ucWdPartCnt=ucValidaDotCnt-1;  //当前显示的实际位置
  918. }

  919. void key_delete_input(void) //删除按键
  920. {
  921.     static unsigned int i;

  922.    switch(ucWd)
  923.    {
  924.       case 1:   //第1窗口。本节程序只有1个窗口
  925.            switch(ucPart)
  926.            {
  927.               case 1:  //1窗口第1行
  928.                     //清零
  929.                                         ulData_1=0; //long数值清零
  930.                                          ucIntCnt_1=0;
  931.                     ucDotBitS_1=0;  
  932.                     ucDotCnt_1=0;  
  933.                     ucWdPartCnt_1=0;               
  934.                     for(i=0;i<6;i++)  
  935.                     {
  936.                        ucDataBuffer_1[i]=10;
  937.                     }
  938.                     ucDataBuffer_1[0]=0; //第0个位置填入0
  939.         
  940.                     ucWd1Part1Update=1;//第一行局部更新显示
  941.                                     ucWd1Part2Update=1;//第二行局部更新显示
  942.                     break;               
  943.               case 2:  //1窗口第2行
  944.                     //清零
  945.                                         ulData_1=0; //long数值清零
  946.                                          ucIntCnt_1=0;
  947.                     ucDotBitS_1=0;  
  948.                     ucDotCnt_1=0;  
  949.                     ucWdPartCnt_1=0;               
  950.                     for(i=0;i<6;i++)  
  951.                     {
  952.                        ucDataBuffer_1[i]=10;
  953.                     }
  954.                     ucDataBuffer_1[0]=0; //第0个位置填入0
  955.         
  956.                     ucWd1Part1Update=1;//第一行局部更新显示
  957.                                     ucWd1Part2Update=1;//第二行局部更新显示
  958.                     break;
  959.               case 3:  //1窗口第3行
  960.                     //清零
  961.                                         ulData_2=0; //long数值清零
  962.                                          ucIntCnt_2=0;
  963.                     ucDotBitS_2=0;  
  964.                     ucDotCnt_2=0;  
  965.                     ucWdPartCnt_2=0;               
  966.                     for(i=0;i<6;i++)  
  967.                     {
  968.                        ucDataBuffer_2[i]=10;
  969.                     }
  970.                     ucDataBuffer_2[0]=0; //第0个位置填入0
  971.         
  972.                     ucWd1Part3Update=1;//第三行局部更新显示
  973.                                     ucWd1Part4Update=1;//第四行局部更新显示
  974.                     break;               
  975.               case 4:  //1窗口第4行
  976.                     //清零
  977.                                         ulData_2=0; //long数值清零
  978.                                          ucIntCnt_2=0;
  979.                     ucDotBitS_2=0;  
  980.                     ucDotCnt_2=0;  
  981.                     ucWdPartCnt_2=0;               
  982.                     for(i=0;i<6;i++)  
  983.                     {
  984.                        ucDataBuffer_2[i]=10;
  985.                     }
  986.                     ucDataBuffer_2[0]=0; //第0个位置填入0
  987.         
  988.                     ucWd1Part3Update=1;//第三行局部更新显示
  989.                                     ucWd1Part4Update=1;//第四行局部更新显示
  990.                     break;
  991.            }
  992.                                        
  993.            break;
  994.         
  995.    }                        
  996.                                 
  997. }

  998. unsigned char *number_to_matrix(unsigned char  ucBitNumber)
  999. {
  1000.     unsigned char *p_ucAnyNumber;  //此指针根据ucBitNumber数值的大小,分别调用不同的字库。

  1001.         switch(ucBitNumber)  //根据ucBitNumber数值的大小,分别调用不同的字库。
  1002.         {
  1003.             case 0:
  1004.              p_ucAnyNumber=Zf816_0;
  1005.                      break;
  1006.             case 1:
  1007.              p_ucAnyNumber=Zf816_1;
  1008.                      break;
  1009.             case 2:
  1010.              p_ucAnyNumber=Zf816_2;
  1011.                      break;
  1012.             case 3:
  1013.              p_ucAnyNumber=Zf816_3;
  1014.                      break;
  1015.             case 4:
  1016.              p_ucAnyNumber=Zf816_4;
  1017.                      break;
  1018.             case 5:
  1019.              p_ucAnyNumber=Zf816_5;
  1020.                      break;
  1021.             case 6:
  1022.              p_ucAnyNumber=Zf816_6;
  1023.                      break;
  1024.             case 7:
  1025.              p_ucAnyNumber=Zf816_7;
  1026.                      break;
  1027.             case 8:
  1028.              p_ucAnyNumber=Zf816_8;
  1029.                      break;
  1030.             case 9:
  1031.              p_ucAnyNumber=Zf816_9;
  1032.                      break;
  1033.             case 10:  //空格
  1034.              p_ucAnyNumber=Zf816_nc;
  1035.                      break;
  1036.                         case 11:   //小数点
  1037.              p_ucAnyNumber=Zf816_dot;
  1038.                      break;
  1039.                 default:   //如果上面的条件都不符合,那么默认指向空字模
  1040.              p_ucAnyNumber=Zf816_nc;
  1041.                      break;
  1042.         }

  1043.     return p_ucAnyNumber;  //返回转换结束后的指针
  1044. }



  1045. void lcd_display_service(void) //应用层面的液晶屏显示程序
  1046. {


  1047.     static unsigned char *p_ucAnyNumber; //经过数字转换成字模后,分解变量的某位字模首地址
  1048.     static unsigned char ucCursorFlag;  //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的
  1049.     static unsigned int i;
  1050.         static unsigned char ucDataBuffer_temp[6]; //分解一个10进制的long类型数据的每一位

  1051.     switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  1052.     {
  1053.         case 1:   //显示窗口1的数据
  1054.                if(ucWd1Update==1)  //窗口1整屏更新,里面只放那些不用经常刷新显示的内容
  1055.                {
  1056.                      ucWd1Update=0;  //及时清零,避免一直更新

  1057.                      ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
  1058.                      ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进
  1059.                      ucWd1Part3Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进
  1060.                      ucWd1Part4Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进

  1061.                      display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。
  1062.                      clear_all_canvas();  //把画布全部清零

  1063.                      display_lattice(0,0,Hz1616_yi,0,2,16,0);    //一项数组
  1064.                      display_lattice(1,0,Hz1616_xiang,0,2,16,0);   
  1065.                      display_lattice(2,0,Hz1616_shu,0,2,16,0);   
  1066.                      display_lattice(3,0,Hz1616_zhu,0,2,16,0);
  1067.                      display_lattice(4,0,Zf816_mao_hao,0,1,16,0); //冒号

  1068.                      display_lattice(0,16,Hz1616_yi,0,2,16,0);    //一项数值
  1069.                      display_lattice(1,16,Hz1616_xiang,0,2,16,0);   
  1070.                      display_lattice(2,16,Hz1616_shu,0,2,16,0);   
  1071.                      display_lattice(3,16,Hz1616_zhi,0,2,16,0);
  1072.                      display_lattice(4,16,Zf816_mao_hao,0,1,16,0); //冒号         
  1073.      
  1074.                      display_lattice(8,0,Hz1616_er,0,2,16,0);    //二项数组
  1075.                      display_lattice(9,0,Hz1616_xiang,0,2,16,0);   
  1076.                      display_lattice(10,0,Hz1616_shu,0,2,16,0);   
  1077.                      display_lattice(11,0,Hz1616_zhu,0,2,16,0);
  1078.                      display_lattice(12,0,Zf816_mao_hao,0,1,16,0); //冒号

  1079.                      display_lattice(8,16,Hz1616_er,0,2,16,0);    //二项数值
  1080.                      display_lattice(9,16,Hz1616_xiang,0,2,16,0);   
  1081.                      display_lattice(10,16,Hz1616_shu,0,2,16,0);   
  1082.                      display_lattice(11,16,Hz1616_zhi,0,2,16,0);
  1083.                      display_lattice(12,16,Zf816_mao_hao,0,1,16,0); //冒号     

  1084.                }

  1085.                if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容
  1086.                {
  1087.                         ucWd1Part1Update=0; //及时清零,避免一直更新

  1088.                         if(ucPart==1) //被选中
  1089.                         {
  1090.                            ucCursorFlag=1; //反显 显示
  1091.                         }
  1092.                         else //没被选中
  1093.                         {
  1094.                             ucCursorFlag=0; //正常 显示
  1095.                         }

  1096.                         
  1097.                         for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  1098.                         {
  1099.                             p_ucAnyNumber=number_to_matrix(ucDataBuffer_1[5-i]);
  1100.                             insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  1101.                         }

  1102.                         display_lattice(5,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  1103.                }

  1104.                if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容
  1105.                {
  1106.                         ucWd1Part2Update=0; //及时清零,避免一直更新

  1107.                         if(ucPart==2) //被选中
  1108.                         {
  1109.                            ucCursorFlag=1; //反显 显示
  1110.                         }
  1111.                         else //没被选中
  1112.                         {
  1113.                             ucCursorFlag=0; //正常 显示
  1114.                         }

  1115.                         if(ulData_1>=10000)
  1116.                                                 {
  1117.                                                    ucDataBuffer_temp[5]=ulData_1%100000/10000;
  1118.                                                 }
  1119.                                                 else
  1120.                                                 {
  1121.                                                    ucDataBuffer_temp[5]=10; //空格
  1122.                                                 }

  1123.                         if(ulData_1>=1000)
  1124.                                                 {
  1125.                                                       ucDataBuffer_temp[4]=ulData_1%10000/1000;
  1126.                         }
  1127.                                                 else
  1128.                                                 {
  1129.                                                       ucDataBuffer_temp[4]=10; //空格
  1130.                         }

  1131.                                                 ucDataBuffer_temp[3]=ulData_1%1000/100;
  1132.                                                 ucDataBuffer_temp[2]=11;  //11代表小数点
  1133.                                                 ucDataBuffer_temp[1]=ulData_1%100/10;
  1134.                                                 ucDataBuffer_temp[0]=ulData_1%10/1;
  1135.                         
  1136.                         for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  1137.                         {
  1138.                             p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp[5-i]);
  1139.                             insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  1140.                         }

  1141.                         display_lattice(5,16,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  1142.                }


  1143.                if(ucWd1Part3Update==1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容
  1144.                {
  1145.                         ucWd1Part3Update=0; //及时清零,避免一直更新

  1146.                         if(ucPart==3) //被选中
  1147.                         {
  1148.                            ucCursorFlag=1; //反显 显示
  1149.                         }
  1150.                         else //没被选中
  1151.                         {
  1152.                             ucCursorFlag=0; //正常 显示
  1153.                         }

  1154.                         
  1155.                         for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  1156.                         {
  1157.                             p_ucAnyNumber=number_to_matrix(ucDataBuffer_2[5-i]);
  1158.                             insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  1159.                         }

  1160.                         display_lattice(13,0,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  1161.                }

  1162.                if(ucWd1Part4Update==1) //窗口1的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容
  1163.                {
  1164.                         ucWd1Part4Update=0; //及时清零,避免一直更新

  1165.                         if(ucPart==4) //被选中
  1166.                         {
  1167.                            ucCursorFlag=1; //反显 显示
  1168.                         }
  1169.                         else //没被选中
  1170.                         {
  1171.                             ucCursorFlag=0; //正常 显示
  1172.                         }

  1173.                         if(ulData_2>=10000)
  1174.                                                 {
  1175.                                                    ucDataBuffer_temp[5]=ulData_2%100000/10000;
  1176.                                                 }
  1177.                                                 else
  1178.                                                 {
  1179.                                                    ucDataBuffer_temp[5]=10; //空格
  1180.                                                 }

  1181.                         if(ulData_2>=1000)
  1182.                                                 {
  1183.                                                       ucDataBuffer_temp[4]=ulData_2%10000/1000;
  1184.                         }
  1185.                                                 else
  1186.                                                 {
  1187.                                                       ucDataBuffer_temp[4]=10; //空格
  1188.                         }

  1189.                                                 ucDataBuffer_temp[3]=ulData_2%1000/100;
  1190.                                                 ucDataBuffer_temp[2]=11;  //11代表小数点
  1191.                                                 ucDataBuffer_temp[1]=ulData_2%100/10;
  1192.                                                 ucDataBuffer_temp[0]=ulData_2%10/1;
  1193.                         
  1194.                         for(i=0;i<6;i++) //把每个数组缓冲的字模依次插入画布
  1195.                         {
  1196.                             p_ucAnyNumber=number_to_matrix(ucDataBuffer_temp[5-i]);
  1197.                             insert_buffer_to_canvas(i,0,p_ucAnyNumber,0,1,16);  //这里的i是画布的横向地址,一共可以显示6个字符,因此取值范围是0到5
  1198.                         }

  1199.                         display_lattice(13,16,ucCanvasBuffer,ucCursorFlag,6,16,0);   //显示整屏的画布,最后的参数0是偏移量
  1200.                }
  1201.                      
  1202.                break;
  1203.         //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3...        
  1204.     }

  1205. }



  1206. void clear_all_canvas(void)  //把画布全部清零
  1207. {
  1208.    unsigned int j=0;
  1209.    unsigned int i=0;

  1210.    for(j=0;j<16;j++)  //这里的16表示画布有16行
  1211.    {
  1212.       for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
  1213.       {
  1214.                   ucCanvasBuffer[j*4+i]=0x00;
  1215.       }
  1216.    }         

  1217. }





  1218. void display_clear(unsigned char ucFillDate) // 清屏  全部显示空填充0x00   全部显示点阵用0xff
  1219. {   

  1220.     unsigned char x,y;
  1221.     WriteCommand(0x34);  //关显示缓冲指令            
  1222.     WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  1223.     y=0;
  1224.     while(y<32)  //y轴的范围0至31
  1225.     {
  1226.          WriteCommand(y+0x80);        //垂直地址
  1227.          WriteCommand(0x80);          //水平地址
  1228.          for(x=0;x<32;x++)  //256个横向点,有32个字节
  1229.          {  
  1230.             LCDWriteData(ucFillDate);
  1231.          }
  1232.          y++;
  1233.     }
  1234.     WriteCommand(0x36); //开显示缓冲指令

  1235. }

  1236. /* 注释六:
  1237. * 注意,这节内容的画布跟第79节前面的画布大小不一样,第79节前面的横向是4个字节,这节的横向是6个字节。
  1238. * 把字模插入画布的函数.
  1239. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  1240. * 第1,2个参数x,y是在画布中的坐标体系。
  1241. * x的范围是0至5,因为画布的横向只要6个字节。y的范围是0至15,因为画布的纵向只有16行。
  1242. * 第3个参数*ucArray是字模的数组。
  1243. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  1244. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  1245. */
  1246. void insert_buffer_to_canvas(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount)
  1247. {
  1248.    unsigned int j=0;
  1249.    unsigned int i=0;
  1250.    unsigned char ucTemp;
  1251.    for(j=0;j<y_amount;j++)
  1252.    {
  1253.       for(i=0;i<x_amount;i++)
  1254.       {
  1255.               ucTemp=ucArray[j*x_amount+i];
  1256.               if(ucFbFlag==0)
  1257.               {
  1258.                  ucCanvasBuffer[(y+j)*6+x+i]=ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
  1259.               }
  1260.               else
  1261.               {
  1262.                  ucCanvasBuffer[(y+j)*6+x+i]=~ucTemp; //这里的6代表画布每一行只有6个字节。前面章节的横向是4个字节,要稍微注意的。
  1263.               }
  1264.       }
  1265.    }         

  1266. }

  1267. /* 注释七:
  1268. * 显示任意点阵函数.
  1269. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
  1270. * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
  1271. * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
  1272. * 第3个参数*ucArray是字模的数组。
  1273. * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
  1274. * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
  1275. * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
  1276. */
  1277. void display_lattice(unsigned int x,unsigned int y,const unsigned char  *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount,unsigned int uiOffSetAddr)
  1278. {
  1279.    unsigned int j=0;
  1280.    unsigned int i=0;
  1281.    unsigned char ucTemp;

  1282. //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
  1283. //  WriteCommand(0x34);  //关显示缓冲指令            
  1284. //  WriteCommand(0x34);  //关显示缓冲指令  故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
  1285.    for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
  1286.    {
  1287.        WriteCommand(y+j+0x80);        //垂直地址
  1288.        WriteCommand(x+0x80);          //水平地址
  1289.        for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
  1290.        {
  1291.            ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
  1292.            if(ucFbFlag==1)  //反白显示
  1293.            {
  1294.                ucTemp=~ucTemp;
  1295.            }
  1296.            LCDWriteData(ucTemp);
  1297.           //         delay_short(30000);  //把上一节这个延时函数去掉,加快刷屏速度
  1298.       }
  1299.    }
  1300.    WriteCommand(0x36); //开显示缓冲指令
  1301. }




  1302. void SendByteToLcd(unsigned char ucData)  //发送一个字节数据到液晶模块
  1303. {
  1304.         unsigned char i;
  1305.         for ( i = 0; i < 8; i++ )
  1306.         {
  1307.                 if ( (ucData << i) & 0x80 )
  1308.                 {
  1309.                         LCDSID_dr = 1;
  1310.                 }
  1311.                 else
  1312.                 {
  1313.                         LCDSID_dr = 0;
  1314.                 }
  1315.                 LCDCLK_dr = 0;
  1316.                 LCDCLK_dr = 1;
  1317.         }
  1318. }

  1319. void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
  1320. {
  1321.         SendByteToLcd( 0xf8 + (ucWRS << 1) );
  1322.         SendByteToLcd( ucWData & 0xf0 );
  1323.         SendByteToLcd( (ucWData << 4) & 0xf0);
  1324. }


  1325. void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
  1326. {

  1327.         LCDCS_dr = 0;
  1328.         LCDCS_dr = 1;
  1329.         SPIWrite(ucCommand, 0);
  1330.         delay_short(90);
  1331. }

  1332. void LCDWriteData(unsigned char ucData)  //发送一个字节的数据给液晶模块
  1333. {
  1334.         LCDCS_dr = 0;
  1335.         LCDCS_dr = 1;
  1336.         SPIWrite(ucData, 1);
  1337. }

  1338. void LCDInit(void) //初始化  函数内部包括液晶模块的复位
  1339. {
  1340.         LCDRST_dr = 1;  //复位
  1341.         LCDRST_dr = 0;
  1342.         LCDRST_dr = 1;
  1343. }



  1344. void delay_short(unsigned int uiDelayShort) //延时函数
  1345. {
  1346.    unsigned int i;  
  1347.    for(i=0;i<uiDelayShort;i++)
  1348.    {
  1349.      ;  
  1350.    }
  1351. }


  1352. void delay_long(unsigned int uiDelayLong)
  1353. {
  1354.    unsigned int i;
  1355.    unsigned int j;
  1356.    for(i=0;i<uiDelayLong;i++)
  1357.    {
  1358.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  1359.           {
  1360.              ; //一个分号相当于执行一条空语句
  1361.           }
  1362.    }
  1363. }


复制代码

总结陈词:
    液晶屏显示的内容到这一节为止基本讲完。前面第38节到第45节是讲串口的,我的串口程序大部分都是通过靠时间来识别每一串数据是否接收完毕,只要第41节内容不是靠时间来判断,而是根据特定关键字来快速识别数据串是否接收完毕,下一节我打算结合我最新的一个项目经验,继续讲一个这方面的例子。欲知详情,请听下回分解----当主机连续不断地发送一串串数据给从机时,从机串口如何快速截取有效数据串。

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

乐于分享,勇于质疑!
115#
 楼主| 发表于 2014-12-26 18:28:47 | 显示全部楼层
第八十七节:郑文显捐赠的工控项目源代码。

开场白:
根据上一节的预告,本来这节要讲关于串口的一个小项目,但是今天中午的时候,有个厦门客户的出现,让我决定先插入这节内容。
他叫郑文显,是做PLC开发的。今天中午他要我帮他写一个工控程序让他来学习,也是基于坚鸿51单片机学习板的,他想把这个源代码经过自己修改后移植到他自己做的工控板上。我一开始报价4000元,被他砍价到1000元,我看一下也不算很难就答应了下来。刚才下午花了3个小时终于做好了。郑文显爽快的付了款,并且在电话那里跟我讲,他说独乐乐不如众乐乐,资源只有分享才能发挥它的最大价值,因此他决定要把这个源代码捐赠出来给大家一起学。非常感谢他的慈善壮举。种善因,得善果。好人一生平安。他的这个项目不难,跟我第25节内容很类似,略加修改就可以了。具体功能需求请看以下第(2)点。

(1)     硬件平台:
基于坚鸿51单片机学习板。
(2)     实现功能:
他的系统要控制2个气缸,没有任何传感器。第1个气缸先伸出去,1秒钟后再收回来。然后第2个气缸再伸出去,1秒钟后再收回来,算完成一个过程,然后重头开始循环下去。每一个过程要计数加1显示在右边的4位数码管上,左边的4位数码管显示设定的最大计数上限,一旦超过这个计数上限就自动停止。有4个按键,一个按键用来启动,一个按键用来急停。另外两个按键是加减按键,用来设置左边显示的最大计数上限。断电要求数据不丢失。如果同时按下加减两个按键,可以清零当前计数的内容。
这4个按键都是独立按键。S1键是加键,S5键是减键,S9键是启动键,S13键是急停键。其中74HC595驱动丝印为D1的LED灯模拟第1个气缸,丝印为D2的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_key_time3    20    //按键去抖动延时的时间
  6. #define const_key_time4    20    //按键去抖动延时的时间
  7. #define const_key_time12   20   //按键去抖动延时的时间

  8. #define const_1s  500  //1秒钟大概的定时中断次数



  9. void start24(void);  //开始位
  10. void ack24(void);  //确认位
  11. void stop24(void);  //停止位
  12. unsigned char read24(void);  //读取一个字节的时序
  13. void write24(unsigned char dd); //发送一个字节的时序
  14. unsigned char read_eeprom(unsigned int address);   //从一个地址读取出一个字节数据
  15. void write_eeprom(unsigned int address,unsigned char dd); //往一个地址存入一个字节数据
  16. unsigned int read_eeprom_int(unsigned int address);   //从一个地址读取出一个int类型的数据
  17. void write_eeprom_int(unsigned int address,unsigned int uiWriteData); //往一个地址存入一个int类型的数据


  18. void initial_myself();   
  19. void initial_peripheral();
  20. void delay_short(unsigned int uiDelayShort);
  21. void delay_long(unsigned int uiDelaylong);
  22. //驱动数码管的74HC595
  23. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  24. void display_drive(); //显示数码管字模的驱动函数
  25. void display_service(); //显示的窗口菜单服务程序
  26. //驱动LED的74HC595
  27. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);
  28. void T0_time();  //定时中断函数
  29. void key_service(); //按键服务的应用程序
  30. void key_scan();//按键扫描函数 放在定时中断里

  31. void run(); //设备自动控制程序
  32. void left_to_right();  //从左边移动到右边
  33. void right_to_left(); //从右边返回到左边
  34. void up_to_down();   //从上边移动到下边
  35. void down_to_up();    //从下边返回到上边
  36. void led_update();  //LED更新函数

  37. void delay_timer(unsigned int uiDelayTimerTemp);

  38. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  39. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  40. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  41. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

  42. sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平
  43. sbit beep_dr=P2^7; //蜂鸣器的驱动IO口
  44. sbit led_dr=P3^5;  //作为中途暂停指示灯 亮的时候表示中途暂停

  45. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  46. sbit dig_hc595_st_dr=P2^1;  
  47. sbit dig_hc595_ds_dr=P2^2;  
  48. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  49. sbit hc595_st_dr=P2^4;  
  50. sbit hc595_ds_dr=P2^5;  

  51. sbit eeprom_scl_dr=P3^7;    //时钟线
  52. sbit eeprom_sda_dr_sr=P3^6; //数据的输出线和输入线


  53. //根据原理图得出的共阴数码管字模表
  54. code unsigned char dig_table[]=
  55. {
  56. 0x3f,  //0       序号0
  57. 0x06,  //1       序号1
  58. 0x5b,  //2       序号2
  59. 0x4f,  //3       序号3
  60. 0x66,  //4       序号4
  61. 0x6d,  //5       序号5
  62. 0x7d,  //6       序号6
  63. 0x07,  //7       序号7
  64. 0x7f,  //8       序号8
  65. 0x6f,  //9       序号9
  66. 0x00,  //无      序号10
  67. 0x40,  //-       序号11
  68. 0x73,  //P       序号12
  69. };

  70. unsigned char ucKeySec=0;   //被触发的按键编号
  71. unsigned int  uiVoiceCnt=0;  //蜂鸣器鸣叫的持续时间计数器


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

  80. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  81. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  82. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  83. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  84. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  85. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  86. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  87. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  88. unsigned char ucDigShowTemp=0; //临时中间变量
  89. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量

  90. unsigned char ucWd1Part1Update=1; //左边4位数码管更新显示标志
  91. unsigned char ucWd1Part2Update=1; //右边4位数码管更新显示标志

  92. unsigned int  uiSetData=18;  //需要被设置的计数上限
  93. unsigned int  uiRunCnt=0; //实际运行的计数值

  94. unsigned char ucRunTimeFlag=0; //延时计数器的开关
  95. unsigned int  uiRunTimeCnt=0;  //运动中的时间延时计数器变量

  96. unsigned char ucRunStep=1;  //运动控制的步骤变量
  97. unsigned char ucRunFlag=0;  //是否启动运行的标志   1代表运行

  98. unsigned char ucDelayTimerFlag=0; //计时器的开关
  99. unsigned int  uiDelayTimer=0;


  100. unsigned char ucLed_dr1=0;   //代表16个灯的亮灭状态,0代表灭,1代表亮
  101. unsigned char ucLed_dr2=0;
  102. unsigned char ucLed_dr3=0;
  103. unsigned char ucLed_dr4=0;
  104. unsigned char ucLed_dr5=0;
  105. unsigned char ucLed_dr6=0;
  106. unsigned char ucLed_dr7=0;
  107. unsigned char ucLed_dr8=0;
  108. unsigned char ucLed_dr9=0;
  109. unsigned char ucLed_dr10=0;
  110. unsigned char ucLed_dr11=0;
  111. unsigned char ucLed_dr12=0;
  112. unsigned char ucLed_dr13=0;
  113. unsigned char ucLed_dr14=0;
  114. unsigned char ucLed_dr15=0;
  115. unsigned char ucLed_dr16=0;

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


  117. void main()
  118.   {
  119.    initial_myself();  
  120.    delay_long(100);   
  121.    initial_peripheral();
  122.    while(1)  
  123.    {
  124.       key_service(); //按键服务的应用程序
  125.           run(); //设备自动控制程序
  126.       display_service(); //显示的窗口菜单服务程序
  127.       led_update();  //LED更新函数
  128.    }
  129. }


  130. void left_to_right()  //从左边移动到右边
  131. {
  132.    ucLed_dr1=1;   // 1代表左右气缸从左边移动到右边

  133.    ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
  134. }
  135. void right_to_left() //从右边返回到左边
  136. {
  137.    ucLed_dr1=0;   // 0代表左右气缸从右边返回到左边

  138.    ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
  139. }
  140. void up_to_down()   //从上边移动到下边
  141. {
  142.    ucLed_dr2=1;   // 1代表上下气缸从上边移动到下边

  143.    ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
  144. }
  145. void down_to_up()    //从下边返回到上边
  146. {
  147.    ucLed_dr2=0;   // 0代表上下气缸从下边返回到上边

  148.    ucLed_update=1;  //刷新变量。每次更改LED灯的状态都要更新一次。
  149. }


  150. void run() //设备自动控制程序
  151. {
  152.    if(ucRunFlag==1)  //是否启动运行的标志
  153.    {
  154.        switch(ucRunStep)
  155.        {

  156.           case 1:    //机械手从左边往右边移动
  157.                left_to_right();
  158.                            ucRunTimeFlag=0; //延时计数器关  在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
  159.                              uiRunTimeCnt=0;  //时间计数器清零,为接下来延时1秒钟做准备
  160.                ucRunTimeFlag=1; //延时计数器开    感谢郑文显捐助本节源代码
  161.                ucRunStep=2;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  162.                break;
  163.           case 2:    //延时1秒
  164.                if(uiRunTimeCnt>const_1s)  //延时1秒
  165.                {
  166.                   ucRunStep=3;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  167.                }
  168.                break;
  169.           case 3:    //机械手从右边往左边移动
  170.                right_to_left();
  171.                            ucRunTimeFlag=0; //延时计数器关  在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
  172.                              uiRunTimeCnt=0;  //时间计数器清零,为接下来延时1秒钟做准备
  173.                ucRunTimeFlag=1; //延时计数器开
  174.                ucRunStep=4;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  175.                break;
  176.           case 4:    //延时1秒
  177.                if(uiRunTimeCnt>const_1s)  //延时1秒
  178.                {
  179.                   ucRunStep=5;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  180.                }
  181.                break;
  182.           case 5:    //机械手//从上边移动到下边
  183.                up_to_down();   
  184.                            ucRunTimeFlag=0; //延时计数器关  在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
  185.                              uiRunTimeCnt=0;  //时间计数器清零,为接下来延时1秒钟做准备
  186.                ucRunTimeFlag=1; //延时计数器开
  187.                ucRunStep=6;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  188.                break;
  189.           case 6:    //延时1秒
  190.                if(uiRunTimeCnt>const_1s)  //延时1秒
  191.                {
  192.                   ucRunStep=7;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  193.                }
  194.                break;
  195.           case 7:    //机械手从下边返回到上边
  196.                down_to_up();   
  197.                            ucRunTimeFlag=0; //延时计数器关  在清零uiRunTimeCnt变量前,最好先关闭计时器开关,起到跟中断互锁作用
  198.                              uiRunTimeCnt=0;  //时间计数器清零,为接下来延时1秒钟做准备
  199.                ucRunTimeFlag=1; //延时计数器开  感谢郑文显捐助本节源代码
  200.                ucRunStep=8;  //这就是鸿哥传说中的怎样灵活控制步骤变量
  201.                break;
  202.           case 8:    //延时1秒
  203.                if(uiRunTimeCnt>const_1s)  //延时1秒
  204.                {
  205.                   uiRunCnt++; //实际运行的计数值累加
  206.                                   if(uiRunCnt>9999)  //数码管最大显示4位9999,如果超过了,继续默认为9999
  207.                   {
  208.                                      uiRunCnt=9999;
  209.                                   }
  210.                   ucWd1Part2Update=1;  //右边4位数码管更新显示

  211.                   write_eeprom_int(2,uiRunCnt); //及时把数据存进EEPROM,避免掉电丢失数据

  212.                                   if(uiRunCnt>=uiSetData) //如果实际的计数大于或者等于设定上限,则停止
  213.                                   {
  214.                                      ucRunFlag=0;  //停止
  215.                                          ucRunStep=1;  //切换到第一步为下一次准备
  216.                                   }
  217.                                   else
  218.                                   {
  219.                      ucRunStep=1;  //切换到第一步继续运行
  220.                                   }
  221.                }
  222.                break;
  223.        }
  224.    }
  225. }


  226. void display_service() //显示的窗口菜单服务程序
  227. {
  228.   //加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样减少了一点指令消耗的时间。
  229.             static unsigned char ucTemp4;   //中间过渡变量
  230.             static unsigned char ucTemp3;   //中间过渡变量
  231.             static unsigned char ucTemp2;   //中间过渡变量
  232.             static unsigned char ucTemp1;   //中间过渡变量

  233.             //左边4位数码管显示设置的计数上限
  234.             if(ucWd1Part1Update==1)  //左边4位数码管要全部更新显示
  235.             {
  236.                ucWd1Part1Update=0;  //及时清零标志,避免一直进来扫描

  237.               //先分解数据用来显示每一位
  238.                ucTemp4=uiSetData/1000;     
  239.                ucTemp3=uiSetData%1000/100;
  240.                ucTemp2=uiSetData%100/10;
  241.                ucTemp1=uiSetData%10;
  242.   

  243.                if(uiSetData<1000)   
  244.                {
  245.                   ucDigShow8=10;  //如果小于1000,千位显示无
  246.                }
  247.                else
  248.                {
  249.                   ucDigShow8=ucTemp4;  //第8位数码管要显示的内容
  250.                }

  251.                if(uiSetData<100)
  252.                {
  253.                   ucDigShow7=10;  //如果小于100,百位显示无
  254.                }
  255.                else
  256.                {
  257.                   ucDigShow7=ucTemp3;  //第7位数码管要显示的内容
  258.                }

  259.                if(uiSetData<10)
  260.                {
  261.                   ucDigShow6=10;  //如果小于10,十位显示无
  262.                }
  263.                else
  264.                {
  265.                   ucDigShow6=ucTemp2;  //第6位数码管要显示的内容
  266.                }

  267.                 ucDigShow5=ucTemp1;  //第5位数码管要显示的内容
  268.             }

  269.             //右边4位数码管显示实际的计数
  270.             if(ucWd1Part2Update==1)  //右边4位数码管要全部更新显示
  271.             {
  272.                ucWd1Part2Update=0;  //及时清零标志,避免一直进来扫描


  273.               //先分解数据用来显示每一位
  274.                ucTemp4=uiRunCnt/1000;     
  275.                ucTemp3=uiRunCnt%1000/100;
  276.                ucTemp2=uiRunCnt%100/10;
  277.                ucTemp1=uiRunCnt%10;
  278.   

  279.                if(uiRunCnt<1000)   
  280.                {
  281.                   ucDigShow4=10;  //如果小于1000,千位显示无
  282.                }
  283.                else
  284.                {
  285.                   ucDigShow4=ucTemp4;  //第8位数码管要显示的内容
  286.                }

  287.                if(uiRunCnt<100)
  288.                {
  289.                   ucDigShow3=10;  //如果小于100,百位显示无
  290.                }
  291.                else
  292.                {
  293.                   ucDigShow3=ucTemp3;  //第7位数码管要显示的内容
  294.                }

  295.                if(uiRunCnt<10)
  296.                {
  297.                   ucDigShow2=10;  //如果小于10,十位显示无
  298.                }
  299.                else
  300.                {
  301.                   ucDigShow2=ucTemp2;  //第6位数码管要显示的内容
  302.                }

  303.                 ucDigShow1=ucTemp1;  //第5位数码管要显示的内容
  304.             }
  305. }

  306. void key_scan()//按键扫描函数 放在定时中断里
  307. {  
  308.   //加了static关键字后,此局部变量不会每次进来函数都被初始化一次,这样可以记录保存上一次执行本函数后的数值
  309. static unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  310. static unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  311. static unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  312. static unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  313. static unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  314. static unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志
  315. static unsigned int  uiKeyTimeCnt4=0; //按键去抖动延时计数器
  316. static unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志

  317. static unsigned int  uiKeyTimeCnt12=0; //按键去抖动延时计数器
  318. static unsigned char ucKeyLock12=0; //按键触发后自锁的变量标志

  319.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  320.   {
  321.      ucKeyLock1=0; //按键自锁标志清零
  322.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  323.   }
  324.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  325.   {
  326.      uiKeyTimeCnt1++; //累加定时中断次数
  327.      if(uiKeyTimeCnt1>const_key_time1)
  328.      {
  329.         uiKeyTimeCnt1=0;
  330.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  331.         ucKeySec=1;    //触发1号键
  332.      }
  333.   }
  334.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  335.   {
  336.      ucKeyLock2=0; //按键自锁标志清零
  337.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  338.   }
  339.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  340.   {
  341.      uiKeyTimeCnt2++; //累加定时中断次数
  342.      if(uiKeyTimeCnt2>const_key_time2)
  343.      {
  344.         uiKeyTimeCnt2=0;
  345.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  346.         ucKeySec=2;    //触发2号键
  347.      }
  348.   }
  349.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  350.   {
  351.      ucKeyLock3=0; //按键自锁标志清零
  352.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  353.   }
  354.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  355.   {
  356.      uiKeyTimeCnt3++; //累加定时中断次数
  357.      if(uiKeyTimeCnt3>const_key_time3)
  358.      {
  359.         uiKeyTimeCnt3=0;
  360.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  361.         ucKeySec=3;    //触发3号键
  362.      }
  363.   }

  364.   if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  365.   {
  366.      ucKeyLock4=0; //按键自锁标志清零
  367.      uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  368.   }
  369.   else if(ucKeyLock4==0)//有按键按下,且是第一次被按下
  370.   {
  371.      uiKeyTimeCnt4++; //累加定时中断次数
  372.      if(uiKeyTimeCnt4>const_key_time4)
  373.      {
  374.         uiKeyTimeCnt4=0;
  375.         ucKeyLock4=1;  //自锁按键置位,避免一直触发
  376.         ucKeySec=4;    //触发4号键
  377.      }
  378.   }

  379. //5号组合键
  380. if(key_sr1==1||key_sr2==1)//IO是高电平,说明两个按键没有全部被按下,这时要及时清零一些标志位
  381.   {
  382.          ucKeyLock12=0; //按键自锁标志清零
  383.          uiKeyTimeCnt12=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  384.   }
  385.   else if(ucKeyLock12==0)//有按键按下,且是第一次被按下
  386.   {
  387.      uiKeyTimeCnt12++; //累加定时中断次数
  388.      if(uiKeyTimeCnt12>const_key_time12)
  389.      {
  390.         uiKeyTimeCnt12=0;
  391.         ucKeyLock12=1;  //自锁按键置位,避免一直触发
  392.         ucKeySec=5;    //触发5号组合键
  393.               
  394.      }
  395.   }


  396. }

  397. void key_service() //按键服务的应用程序
  398. {
  399.   switch(ucKeySec) //按键服务状态切换
  400.   {
  401.     case 1:// 加按键 对应朱兆祺学习板的S1键
  402.           if(ucRunFlag==0)  //如果系统还没运行
  403.                   {
  404.              uiSetData++;    //被设置的计数上限
  405.              if(uiSetData>9999) //最大值是9999
  406.              {
  407.                 uiSetData=9999;
  408.              }
  409.              ucWd1Part1Update=1;  //左边4位数码管更新显示
  410.             
  411.                   write_eeprom_int(0,uiSetData); //及时保存数据进EEPROM,避免掉电丢失
  412.              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  413.                   }
  414.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  415.           break;   
  416.    
  417.     case 2:// 减按键 对应朱兆祺学习板的S5键
  418.           if(ucRunFlag==0)  //如果系统还没运行
  419.                   {
  420.              uiSetData--;   
  421.              if(uiSetData>9999)  //unsigned int 类型的0减去1会变成65535(0xffff)
  422.              {
  423.                 uiSetData=0;  //最小值是0
  424.              }
  425.              ucWd1Part1Update=1;  //左边4位数码管更新显示

  426.              write_eeprom_int(0,uiSetData); //及时保存数据进EEPROM,避免掉电丢失                  
  427.              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  428.                   }
  429.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  430.           break;  
  431.     case 3://启动按键 对应朱兆祺学习板的S9键
  432.           if(ucRunFlag==0&&uiRunCnt<uiSetData)  //如果系统还没运行,并且实际运行的次数小于设定的最大次数,则启动
  433.                   {
  434.                       ucRunFlag=1;
  435.               ucRunStep=1;
  436.               uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  437.                   }

  438.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  439.           break;  
  440.     case 4://急停按键 对应朱兆祺学习板的S9键
  441.           ucRunFlag=0; //急停
  442.           ucRunStep=1;
  443.           right_to_left(); //从右边返回到左边
  444.           down_to_up();    //从下边返回到上边

  445.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  446.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  447.           break;
  448.    
  449.     case 5://清零的组合按键 对应朱兆祺学习板的(S1+S5)组合键
  450.           if(ucRunFlag==0)  //如果系统还没运行
  451.                   {
  452.              uiRunCnt=0; //实际计数清零
  453.              ucWd1Part2Update=1;  //右边4位数码管更新显示

  454.              write_eeprom_int(2,uiRunCnt); //存入uiRunCnt,内部占用2个字节地址
  455.              uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  456.                   }
  457.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  458.           break;        
  459.   }               
  460. }



  461. void led_update()  //LED更新函数
  462. {
  463.   //加了static关键字后,此局部变量不会每次进来函数都被初始化一次,这样可以记录保存上一次执行本函数后的数值
  464. static unsigned char ucLedStatus16_09=0;   //代表底层74HC595输出状态的中间变量
  465. static unsigned char ucLedStatus08_01=0;   //代表底层74HC595输出状态的中间变量

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

  469.        if(ucLed_dr1==1)
  470.            {
  471.               ucLedStatus08_01=ucLedStatus08_01|0x01;
  472.            }
  473.            else
  474.            {
  475.               ucLedStatus08_01=ucLedStatus08_01&0xfe;
  476.            }

  477.        if(ucLed_dr2==1)
  478.            {
  479.               ucLedStatus08_01=ucLedStatus08_01|0x02;
  480.            }
  481.            else
  482.            {
  483.               ucLedStatus08_01=ucLedStatus08_01&0xfd;
  484.            }

  485.        if(ucLed_dr3==1)
  486.            {
  487.               ucLedStatus08_01=ucLedStatus08_01|0x04;
  488.            }
  489.            else
  490.            {
  491.               ucLedStatus08_01=ucLedStatus08_01&0xfb;
  492.            }

  493.        if(ucLed_dr4==1)
  494.            {
  495.               ucLedStatus08_01=ucLedStatus08_01|0x08;
  496.            }
  497.            else
  498.            {
  499.               ucLedStatus08_01=ucLedStatus08_01&0xf7;
  500.            }


  501.        if(ucLed_dr5==1)
  502.            {
  503.               ucLedStatus08_01=ucLedStatus08_01|0x10;
  504.            }
  505.            else
  506.            {
  507.               ucLedStatus08_01=ucLedStatus08_01&0xef;
  508.            }


  509.        if(ucLed_dr6==1)
  510.            {
  511.               ucLedStatus08_01=ucLedStatus08_01|0x20;
  512.            }
  513.            else
  514.            {
  515.               ucLedStatus08_01=ucLedStatus08_01&0xdf;
  516.            }


  517.        if(ucLed_dr7==1)
  518.            {
  519.               ucLedStatus08_01=ucLedStatus08_01|0x40;
  520.            }
  521.            else
  522.            {
  523.               ucLedStatus08_01=ucLedStatus08_01&0xbf;
  524.            }


  525.        if(ucLed_dr8==1)
  526.            {
  527.               ucLedStatus08_01=ucLedStatus08_01|0x80;
  528.            }
  529.            else
  530.            {
  531.               ucLedStatus08_01=ucLedStatus08_01&0x7f;
  532.            }

  533.        if(ucLed_dr9==1)
  534.            {
  535.               ucLedStatus16_09=ucLedStatus16_09|0x01;
  536.            }
  537.            else
  538.            {
  539.               ucLedStatus16_09=ucLedStatus16_09&0xfe;
  540.            }

  541.        if(ucLed_dr10==1)
  542.            {
  543.               ucLedStatus16_09=ucLedStatus16_09|0x02;
  544.            }
  545.            else
  546.            {
  547.               ucLedStatus16_09=ucLedStatus16_09&0xfd;
  548.            }

  549.        if(ucLed_dr11==1)
  550.            {
  551.               ucLedStatus16_09=ucLedStatus16_09|0x04;
  552.            }
  553.            else
  554.            {
  555.               ucLedStatus16_09=ucLedStatus16_09&0xfb;
  556.            }

  557.        if(ucLed_dr12==1)
  558.            {
  559.               ucLedStatus16_09=ucLedStatus16_09|0x08;
  560.            }
  561.            else
  562.            {
  563.               ucLedStatus16_09=ucLedStatus16_09&0xf7;
  564.            }


  565.        if(ucLed_dr13==1)
  566.            {
  567.               ucLedStatus16_09=ucLedStatus16_09|0x10;
  568.            }
  569.            else
  570.            {
  571.               ucLedStatus16_09=ucLedStatus16_09&0xef;
  572.            }


  573.        if(ucLed_dr14==1)
  574.            {
  575.               ucLedStatus16_09=ucLedStatus16_09|0x20;
  576.            }
  577.            else
  578.            {
  579.               ucLedStatus16_09=ucLedStatus16_09&0xdf;
  580.            }


  581.        if(ucLed_dr15==1)
  582.            {
  583.               ucLedStatus16_09=ucLedStatus16_09|0x40;
  584.            }
  585.            else
  586.            {
  587.               ucLedStatus16_09=ucLedStatus16_09&0xbf;
  588.            }


  589.        if(ucLed_dr16==1)
  590.            {
  591.               ucLedStatus16_09=ucLedStatus16_09|0x80;
  592.            }
  593.            else
  594.            {
  595.               ucLedStatus16_09=ucLedStatus16_09&0x7f;
  596.            }

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

  598.    }
  599. }


  600. void display_drive()  
  601. {
  602.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  603.    switch(ucDisplayDriveStep)
  604.    {
  605.       case 1:  //显示第1位
  606.            ucDigShowTemp=dig_table[ucDigShow1];
  607.                    if(ucDigDot1==1)
  608.                    {
  609.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  610.                    }
  611.            dig_hc595_drive(ucDigShowTemp,0xfe);
  612.                break;
  613.       case 2:  //显示第2位
  614.            ucDigShowTemp=dig_table[ucDigShow2];
  615.                    if(ucDigDot2==1)
  616.                    {
  617.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  618.                    }
  619.            dig_hc595_drive(ucDigShowTemp,0xfd);
  620.                break;
  621.       case 3:  //显示第3位
  622.            ucDigShowTemp=dig_table[ucDigShow3];
  623.                    if(ucDigDot3==1)
  624.                    {
  625.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  626.                    }
  627.            dig_hc595_drive(ucDigShowTemp,0xfb);
  628.                break;
  629.       case 4:  //显示第4位
  630.            ucDigShowTemp=dig_table[ucDigShow4];
  631.                    if(ucDigDot4==1)
  632.                    {
  633.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  634.                    }
  635.            dig_hc595_drive(ucDigShowTemp,0xf7);
  636.                break;
  637.       case 5:  //显示第5位
  638.            ucDigShowTemp=dig_table[ucDigShow5];
  639.                    if(ucDigDot5==1)
  640.                    {
  641.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  642.                    }
  643.            dig_hc595_drive(ucDigShowTemp,0xef);
  644.                break;
  645.       case 6:  //显示第6位
  646.            ucDigShowTemp=dig_table[ucDigShow6];
  647.                    if(ucDigDot6==1)
  648.                    {
  649.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  650.                    }
  651.            dig_hc595_drive(ucDigShowTemp,0xdf);
  652.                break;
  653.       case 7:  //显示第7位
  654.            ucDigShowTemp=dig_table[ucDigShow7];
  655.                    if(ucDigDot7==1)
  656.                    {
  657.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  658.            }
  659.            dig_hc595_drive(ucDigShowTemp,0xbf);
  660.                break;
  661.       case 8:  //显示第8位
  662.            ucDigShowTemp=dig_table[ucDigShow8];
  663.                    if(ucDigDot8==1)
  664.                    {
  665.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  666.                    }
  667.            dig_hc595_drive(ucDigShowTemp,0x7f);
  668.                break;
  669.    }
  670.    ucDisplayDriveStep++;
  671.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  672.    {
  673.      ucDisplayDriveStep=1;
  674.    }

  675. }

  676. //数码管的74HC595驱动函数
  677. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  678. {
  679.    unsigned char i;
  680.    unsigned char ucTempData;
  681.    dig_hc595_sh_dr=0;
  682.    dig_hc595_st_dr=0;
  683.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  684.    for(i=0;i<8;i++)
  685.    {
  686.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  687.          else dig_hc595_ds_dr=0;
  688.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  689.          delay_short(1);
  690.          dig_hc595_sh_dr=1;
  691.          delay_short(1);
  692.          ucTempData=ucTempData<<1;
  693.    }
  694.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  695.    for(i=0;i<8;i++)
  696.    {
  697.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  698.          else dig_hc595_ds_dr=0;
  699.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  700.          delay_short(1);
  701.          dig_hc595_sh_dr=1;
  702.          delay_short(1);
  703.          ucTempData=ucTempData<<1;
  704.    }
  705.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  706.    delay_short(1);
  707.    dig_hc595_st_dr=1;
  708.    delay_short(1);
  709.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  710.    dig_hc595_st_dr=0;
  711.    dig_hc595_ds_dr=0;
  712. }

  713. //LED灯的74HC595驱动函数
  714. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  715. {
  716.    unsigned char i;
  717.    unsigned char ucTempData;
  718.    hc595_sh_dr=0;
  719.    hc595_st_dr=0;
  720.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  721.    for(i=0;i<8;i++)
  722.    {
  723.          if(ucTempData>=0x80)hc595_ds_dr=1;
  724.          else hc595_ds_dr=0;
  725.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  726.          delay_short(1);
  727.          hc595_sh_dr=1;
  728.          delay_short(1);
  729.          ucTempData=ucTempData<<1;
  730.    }
  731.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  732.    for(i=0;i<8;i++)
  733.    {
  734.          if(ucTempData>=0x80)hc595_ds_dr=1;
  735.          else hc595_ds_dr=0;
  736.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  737.          delay_short(1);
  738.          hc595_sh_dr=1;
  739.          delay_short(1);
  740.          ucTempData=ucTempData<<1;
  741.    }
  742.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  743.    delay_short(1);
  744.    hc595_st_dr=1;
  745.    delay_short(1);
  746.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  747.    hc595_st_dr=0;
  748.    hc595_ds_dr=0;
  749. }



  750. //AT24C02驱动程序
  751. void start24(void)  //开始位
  752. {

  753.     eeprom_sda_dr_sr=1;
  754.     eeprom_scl_dr=1;
  755.         delay_short(15);
  756.     eeprom_sda_dr_sr=0;
  757.         delay_short(15);
  758.     eeprom_scl_dr=0;   
  759. }


  760. void ack24(void)  //确认位时序
  761. {
  762.     eeprom_sda_dr_sr=1; //51单片机在读取数据之前要先置一,表示数据输入

  763.     eeprom_scl_dr=1;
  764.         delay_short(15);
  765.     eeprom_scl_dr=0;
  766.         delay_short(15);

  767. //在本驱动程序中,我没有对ACK信号进行出错判断,因为我这么多年一直都是这样用也没出现过什么问题。
  768. //有兴趣的朋友可以自己增加出错判断,不一定非要按我的方式去做。
  769. }

  770. void stop24(void)  //停止位
  771. {
  772.     eeprom_sda_dr_sr=0;
  773.     eeprom_scl_dr=1;
  774.         delay_short(15);
  775.     eeprom_sda_dr_sr=1;
  776. }



  777. unsigned char read24(void)  //读取一个字节的时序
  778. {
  779.         unsigned char outdata,tempdata;


  780.         outdata=0;
  781.                 eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  782.         delay_short(2);
  783.         for(tempdata=0;tempdata<8;tempdata++)
  784.         {
  785.             eeprom_scl_dr=0;
  786.             delay_short(2);
  787.             eeprom_scl_dr=1;
  788.             delay_short(2);
  789.             outdata<<=1;
  790.             if(eeprom_sda_dr_sr==1)outdata++;      
  791.             eeprom_sda_dr_sr=1; //51单片机的IO口在读取数据之前要先置一,表示数据输入
  792.             delay_short(2);
  793.         }
  794.     return(outdata);
  795.      
  796. }

  797. void write24(unsigned char dd) //发送一个字节的时序
  798. {

  799.         unsigned char tempdata;
  800.         for(tempdata=0;tempdata<8;tempdata++)
  801.         {
  802.                 if(dd>=0x80)eeprom_sda_dr_sr=1;
  803.                 else eeprom_sda_dr_sr=0;
  804.                 dd<<=1;
  805.                 delay_short(2);
  806.                 eeprom_scl_dr=1;
  807.                 delay_short(4);
  808.                 eeprom_scl_dr=0;
  809.         }


  810. }



  811. unsigned char read_eeprom(unsigned int address)   //从一个地址读取出一个字节数据
  812. {

  813.    unsigned char dd,cAddress;  

  814.    cAddress=address; //把低字节地址传递给一个字节变量。

  815.    EA=0; //禁止中断

  816.    start24(); //IIC通讯开始

  817.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  818.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定

  819.    ack24(); //发送应答信号   
  820.    write24(cAddress); //发送读取的存储地址(范围是0至255)
  821.    ack24(); //发送应答信号

  822.    start24(); //开始
  823.    write24(0xA1); //此字节包含读写指令和芯片地址两方面的内容。
  824.                   //指令为读指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  825.    ack24(); //发送应答信号
  826.    dd=read24(); //读取一个字节
  827.    ack24(); //发送应答信号
  828.    stop24();  //停止
  829.    EA=1; //允许中断
  830.    delay_timer(2); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

  831.    return(dd);
  832. }

  833. void write_eeprom(unsigned int address,unsigned char dd) //往一个地址存入一个字节数据
  834. {
  835.    unsigned char cAddress;   

  836.    cAddress=address; //把低字节地址传递给一个字节变量。


  837.    EA=0; //禁止中断

  838.    start24(); //IIC通讯开始

  839.    write24(0xA0); //此字节包含读写指令和芯片地址两方面的内容。
  840.                   //指令为写指令。地址为"000"的信息,此信息由A0,A1,A2的引脚决定
  841.    ack24(); //发送应答信号
  842.    write24(cAddress);   //发送写入的存储地址(范围是0至255)
  843.    ack24(); //发送应答信号
  844.    write24(dd);  //写入存储的数据
  845.    ack24(); //发送应答信号
  846.    stop24();  //停止
  847.    EA=1; //允许中断
  848.    delay_timer(4); //一气呵成的定时器延时方式,在延时的时候还可以动态扫描数码管

  849. }


  850. unsigned int read_eeprom_int(unsigned int address)   //从一个地址读取出一个int类型的数据
  851. {
  852.    unsigned char ucReadDataH;
  853.    unsigned char ucReadDataL;
  854.    unsigned int  uiReadDate;

  855.    ucReadDataH=read_eeprom(address);    //读取高字节
  856.    ucReadDataL=read_eeprom(address+1);  //读取低字节

  857.    uiReadDate=ucReadDataH;  //把两个字节合并成一个int类型数据
  858.    uiReadDate=uiReadDate<<8;
  859.    uiReadDate=uiReadDate+ucReadDataL;

  860.    return uiReadDate;

  861. }

  862. void write_eeprom_int(unsigned int address,unsigned int uiWriteData) //往一个地址存入一个int类型的数据
  863. {
  864.    unsigned char ucWriteDataH;
  865.    unsigned char ucWriteDataL;

  866.    ucWriteDataH=uiWriteData>>8;
  867.    ucWriteDataL=uiWriteData;

  868.    write_eeprom(address,ucWriteDataH); //存入高字节
  869.    write_eeprom(address+1,ucWriteDataL); //存入低字节

  870. }


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

  875.   if(ucRunTimeFlag==1) //void run函数中的延时计数器开关
  876.   {
  877.      uiRunTimeCnt++; //延时计数器
  878.   }

  879.   if(ucDelayTimerFlag==1)//delay_timer函数中的延时计数器开关
  880.   {
  881.      if(uiDelayTimer>0)
  882.          {
  883.            uiDelayTimer--;   //一气呵成的定时器延时方式的计时器
  884.          }
  885.   
  886.   }

  887.   key_scan(); //按键扫描函数
  888.   if(uiVoiceCnt!=0)
  889.   {
  890.      uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫
  891.      beep_dr=0;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  892. //     beep_dr=1;  //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。
  893.   }
  894.   else
  895.   {
  896.      ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。
  897.      beep_dr=1;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  898. //     beep_dr=0;  //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。
  899.   }
  900.   display_drive();  //数码管字模的驱动函数

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

  905. void delay_timer(unsigned int uiDelayTimerTemp)
  906. {
  907.     ucDelayTimerFlag=0; //延时计时器关  在设置参数前,先关闭计时器
  908.     uiDelayTimer=uiDelayTimerTemp;
  909.     ucDelayTimerFlag=1; //延时计时器开  

  910.     while(uiDelayTimer!=0);  //一气呵成的定时器方式延时等待

  911. }

  912. void delay_short(unsigned int uiDelayShort)
  913. {
  914.    unsigned int i;  
  915.    for(i=0;i<uiDelayShort;i++)
  916.    {
  917.      ;   //一个分号相当于执行一条空语句
  918.    }
  919. }

  920. void delay_long(unsigned int uiDelayLong)
  921. {
  922.    unsigned int i;
  923.    unsigned int j;
  924.    for(i=0;i<uiDelayLong;i++)
  925.    {
  926.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  927.           {
  928.              ; //一个分号相当于执行一条空语句
  929.           }
  930.    }
  931. }

  932. void initial_myself()  //第一区 初始化单片机
  933. {
  934. /* 注释一:
  935. * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平,
  936. * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。
  937. * 坚鸿51学习板的S1就是本程序中用到的一个独立按键。
  938. */
  939.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  940.   led_dr=0;  //关闭独立LED灯 感谢郑文显捐助本节源代码
  941.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  942.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  943.   TMOD=0x01;  //设置定时器0为工作方式1
  944.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  945.   TL0=0x0b;
  946. }
  947. void initial_peripheral() //第二区 初始化外围
  948. {

  949.    ucDigDot8=0;   //小数点全部不显示
  950.    ucDigDot7=0;  
  951.    ucDigDot6=0;
  952.    ucDigDot5=0;  
  953.    ucDigDot4=0;
  954.    ucDigDot3=0;  
  955.    ucDigDot2=0;
  956.    ucDigDot1=0;
  957.    EA=1;     //开总中断
  958.    ET0=1;    //允许定时中断
  959.    TR0=1;    //启动定时中断

  960.    uiSetData=read_eeprom_int(0);  //读取uiSetData,内部占用2个字节地址
  961.    if(uiSetData>9999)   //不在范围内
  962.    {
  963.        uiSetData=0;   //填入一个初始化数据
  964.        write_eeprom_int(0,uiSetData); //存入uiSetData,内部占用2个字节地址
  965.    }

  966.    uiRunCnt=read_eeprom_int(2);  //读取uiRunCnt,内部占用2个字节地址
  967.    if(uiRunCnt>9999)//不在范围内
  968.    {
  969.        uiRunCnt=0;  //填入一个初始化数据
  970.        write_eeprom_int(2,uiRunCnt); //存入uiRunCnt,内部占用2个字节地址
  971.    }

  972. }
复制代码

总结陈词:
    再次感谢郑文显的无私奉献。前面第38节到第45节是讲串口的,我的串口程序大部分都是通过靠时间来识别每一串数据是否接收完毕,只要第41节内容不是靠时间来判断,而是根据特定关键字来快速识别数据串是否接收完毕,下一节我打算结合我最新的一个项目经验,继续讲一个这方面的例子。欲知详情,请听下回分解----当主机连续不断地发送一串串数据给从机时,从机串口如何快速截取有效数据串。

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

乐于分享,勇于质疑!
116#
 楼主| 发表于 2014-12-30 12:44:56 | 显示全部楼层
第八十八节:电子称连续不断从串口对外发送数据,单片机靠关键字快速截取有效数据串。

开场白:
我前面串口程序大部分都是通过靠时间来识别每一串数据是否接收完毕,有一些串口项目的协议是固定不变的,而且也不需要从机反馈任何应答信号,这类项目只需根据特定关键字来快速识别数据串是否接收完毕即可。比如现在有一种电子称,它的测量范围是0.00克到500.00克,他是靠串口不断对外发送当前重量数据的,每串数据固定长度26个字节,最后两个字节是回车换行符0x0d 0x0a,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,其它的数据可以忽略不计。这类串口框架的思路是:根据数据尾是否有0x0d 0x0a来判断数据串是否有效的,一旦发现有此关键字,再判断总的数据长度是否等于或者大于一串数据的固定长度,如果满足,则把相关标志位置位,通知主函数中的串口服务程序进行处理。同时也及时关闭串口中断,避免在处理串口数据期间受到串口数据的中断干扰,等串口服务程序处理完毕再打开。
具体内容,请看源代码讲解。

(1)     硬件平台:
基于坚鸿51单片机学习板。
(2)     实现功能:
波特率是:9600。把当前电子称的重量数据显示在数码管上,在电脑上用串口助手软件来模拟电子称发送以下格式协议的3串数据,它的协议很简单,每串数据固定长度26个字节,最后两个字节是回车换行符0x0d 0x0a,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,其它的数据可以忽略不计。
(a)字符是:
ST,GS,+      0.77    g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 20 20 30 2E 37 37 20 2020 20 20 67 0D 0A
数码管显示:0.77
(b)
字符是:
ST,GS,+    136.39    g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 31 33 36 2E 33 39 20 2020 20 20 67 0D 0A
数码管显示:136.39
(c)
字符是:
ST,GS,+      0.00    g
转换成16进制是:
20 53 54 2C 47 53 2C 2B 20 20 20 20 20 20 30 2E 30 30 20 2020 20 20 67 0D 0A
数码管显示:0.00

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


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

  3. #define const_least_size 26   //一串标准数据的大小

  4. void initial_myself();   
  5. void initial_peripheral();
  6. void delay_short(unsigned int uiDelayShort);
  7. void delay_long(unsigned int uiDelaylong);
  8. //驱动数码管的74HC595
  9. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  10. void display_drive(); //显示数码管字模的驱动函数
  11. void display_service(); //显示的窗口菜单服务程序

  12. //驱动LED的74HC595
  13. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

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


  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 beep_dr=P2^7; //蜂鸣器的驱动IO口

  24. sbit led_dr=P3^5;  //独立LED灯


  25. //根据原理图得出的共阴数码管字模表
  26. code unsigned char dig_table[]=
  27. {
  28. 0x3f,  //0       序号0
  29. 0x06,  //1       序号1
  30. 0x5b,  //2       序号2
  31. 0x4f,  //3       序号3
  32. 0x66,  //4       序号4
  33. 0x6d,  //5       序号5
  34. 0x7d,  //6       序号6
  35. 0x07,  //7       序号7
  36. 0x7f,  //8       序号8
  37. 0x6f,  //9       序号9
  38. 0x00,  //无      序号10
  39. 0x40,  //-       序号11
  40. 0x73,  //P       序号12
  41. };


  42. unsigned int  uiRcregTotal=0;  //代表当前缓冲区已经接收了多少个数据
  43. unsigned int  uiRcregTotalTemp=0;  //代表当前缓冲区已经接收了多少个数据的中间变量
  44. unsigned char ucRcregBuf[const_rc_size]; //接收串口中断数据的缓冲区数组
  45. unsigned char ucReceiveFlag=0; //接收成功标志


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

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

  64. unsigned char ucWd1Part1Update=1; //8位数码管更新显示标志

  65. unsigned long ulWeightCurrent=12345; //显示当前实际的重量

  66. void main()
  67.   {
  68.    initial_myself();  
  69.    delay_long(100);   
  70.    initial_peripheral();
  71.    while(1)  
  72.    {
  73.       usart_service();  //串口接收服务程序
  74.       display_service(); //显示的窗口菜单服务程序
  75.    }
  76. }

  77. /* 注释一:
  78. * 本节内容处理串口数据是根据数据尾是否有0x0d 0x0a来判断数据串是否有效的,一旦发现有此关键字,
  79. * 再判断总的数据长度是否等于或者大于一串数据的固定长度,如果满足,则把相关标志位置位,通知主函数中
  80. * 的串口服务程序进行处理。同时也及时关闭串口中断,避免在处理串口数据期间受到串口数据的中断干扰,
  81. * 等串口服务程序处理完毕再打开。
  82. */
  83. void usart_receive(void) interrupt 4   //串口接收数据中断函数      
  84. {        

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

  88.         ++uiRcregTotal;
  89.         ucRcregBuf[uiRcregTotal-1]=SBUF;   //将串口接收到的数据缓存到接收缓冲区里
  90.         if(uiRcregTotal>=2&&ucRcregBuf[uiRcregTotal-2]==0x0d&&ucRcregBuf[uiRcregTotal-1]==0x0a)  //一旦发现后缀是0x0d 0x0a关键字的就进去处理判断
  91.         {
  92.            if(uiRcregTotal<const_least_size)  //如果发现总的接收数据小于一串数据的固定长度。则无效,清零重新开始。
  93.            {
  94.                uiRcregTotal=0;
  95.            }
  96.            else
  97.            {
  98.                uiRcregTotalTemp=uiRcregTotal; //把接收到的总数据传递给一个中间变量,在主函数那边处理这个中间变量
  99.                ucReceiveFlag=1;            //通知主程序接收成功
  100.                            ES=0;      // 禁止接收中断,等主函数处理完接收的数据后再打开串口中断,避免在处理串口数据期间受到串口数据的中断干扰。
  101.            }
  102.         }
  103.         else if(uiRcregTotal>=const_rc_size)  //超过缓冲区
  104.         {
  105.            uiRcregTotal=0;   
  106.         }  


  107.      
  108.    
  109.    }
  110.    else    //如果不是串口接收中断,那么必然是串口发送中断,及时清除发送中断的标志,否则一直发送中断
  111.    {
  112.         TI = 0;
  113.    }
  114.                                                          
  115. }  

  116. void usart_service(void)  //串口接收服务程序,在main函数里
  117. {
  118.   //加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样有可能减少了一点指令消耗的时间。
  119.     static unsigned long ulReceiveData10000; //定义成long类型,是为了方便后面换算的乘法运算,让它不会溢出而出错。
  120.     static unsigned long ulReceiveData1000;
  121.     static unsigned long ulReceiveData100;
  122.     static unsigned long ulReceiveData10;
  123.     static unsigned long ulReceiveData1;


  124.     if(ucReceiveFlag==1)  //说明有数据接收成功,进入数据处理分析
  125.     {
  126.        ulReceiveData10000=0;
  127.        ulReceiveData1000=0;
  128.        ulReceiveData100=0;
  129.        ulReceiveData10=0;
  130.        ulReceiveData1=0;

  131. /* 注释二:
  132. * 根据协议,倒数第9,10,11,12,13,14为有效的ASCII码数字,其中倒数第11位为固定的小数点,因此省略不写。
  133. */

  134.        if(ucRcregBuf[uiRcregTotalTemp-9]>=0x30)  
  135.        {
  136.           ulReceiveData1=ucRcregBuf[uiRcregTotalTemp-9]-0x30; //接收到的ASCII码数字减去0x30变成实际数值.
  137.        }

  138.        if(ucRcregBuf[uiRcregTotalTemp-10]>=0x30)  
  139.        {
  140.           ulReceiveData10=ucRcregBuf[uiRcregTotalTemp-10]-0x30;
  141.           ulReceiveData10=ulReceiveData10*10;
  142.        }

  143.        if(ucRcregBuf[uiRcregTotalTemp-12]>=0x30)  
  144.        {
  145.           ulReceiveData100=ucRcregBuf[uiRcregTotalTemp-12]-0x30;
  146.           ulReceiveData100=ulReceiveData100*100;
  147.        }

  148.        if(ucRcregBuf[uiRcregTotalTemp-13]>=0x30)  
  149.        {
  150.           ulReceiveData1000=ucRcregBuf[uiRcregTotalTemp-13]-0x30;
  151.           ulReceiveData1000=ulReceiveData1000*1000;
  152.        }

  153.        if(ucRcregBuf[uiRcregTotalTemp-14]>=0x30)  
  154.        {
  155.           ulReceiveData10000=ucRcregBuf[uiRcregTotalTemp-14]-0x30;
  156.           ulReceiveData10000=ulReceiveData10000*10000;
  157.        }


  158.        ulWeightCurrent=ulReceiveData10000+ulReceiveData1000+ulReceiveData100+ulReceiveData10+ulReceiveData1;
  159.        ucWd1Part1Update=1; //更新显示

  160.        uiRcregTotalTemp=0;  //清零实际接收到的字节数的中间变量
  161.        uiRcregTotal=0;  //清零实际接收到的字节数
  162.        ucReceiveFlag=0;  //清零完成标志

  163.            ES = 1;            // 允许接收中断
  164.     }            
  165. }



  166. void display_service() //显示的窗口菜单服务程序
  167. {
  168.   //加了static关键字后,此局部变量不会每次进来函数都初始化一次,这样有可能减少了一点指令消耗的时间。
  169.             static unsigned char ucTemp5;   //中间过渡变量
  170.             static unsigned char ucTemp4;   //中间过渡变量
  171.             static unsigned char ucTemp3;   //中间过渡变量
  172.             static unsigned char ucTemp2;   //中间过渡变量
  173.             static unsigned char ucTemp1;   //中间过渡变量


  174.             if(ucWd1Part1Update==1)  //更新显示
  175.             {
  176.                ucWd1Part1Update=0;  //及时清零标志,避免一直进来扫描

  177.               //先分解数据用来显示每一位
  178.                ucTemp5=ulWeightCurrent%100000/10000;  
  179.                ucTemp4=ulWeightCurrent%10000/1000;     
  180.                ucTemp3=ulWeightCurrent%1000/100;
  181.                ucTemp2=ulWeightCurrent%100/10;
  182.                ucTemp1=ulWeightCurrent%10;

  183.                ucDigDot3=1;  //显示第3位数码管的小数点,实际数据带2位小数点。

  184.                ucDigShow8=10;  //没有用到第8位数码管,因此显示无。10代表显示空。
  185.                ucDigShow7=10;  //没有用到第7位数码管,因此显示无。10代表显示空。
  186.                ucDigShow6=10;  //没有用到第6位数码管,因此显示无。10代表显示空。

  187.                if(ulWeightCurrent<10000)   
  188.                {
  189.                   ucDigShow5=10;  //如果小于1000,千位显示无
  190.                }
  191.                else
  192.                {
  193.                   ucDigShow5=ucTemp5;  //第5位数码管要显示的内容
  194.                }
  195.   

  196.                if(ulWeightCurrent<1000)   
  197.                {
  198.                   ucDigShow4=10;  //如果小于1000,千位显示无
  199.                }
  200.                else
  201.                {
  202.                   ucDigShow4=ucTemp4;  //第4位数码管要显示的内容
  203.                }

  204.                 //因为带2位小数点,因此最前面3位数据都是有效数,必然要显示,不要判断去0的空显示处理。
  205.                 ucDigShow3=ucTemp3;  //第3位数码管要显示的内容
  206.                 ucDigShow2=ucTemp2;  //第2位数码管要显示的内容
  207.                 ucDigShow1=ucTemp1;  //第1位数码管要显示的内容
  208.             }

  209.      
  210. }




  211. void display_drive()  
  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() 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()  //第一区 初始化单片机
  391. {

  392.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  393.   led_dr=0;  //关闭独立LED灯
  394.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯

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

  398.   //配置串口
  399.   SCON=0x50;
  400.   TMOD=0X21;

  401. /* 注释三:
  402. * 为了保证串口中断接收的数据不丢失,必须设置IP = 0x10,相当于把串口中断设置为最高优先级,
  403. * 这个时候,串口中断可以打断任何其他的中断服务函数实现嵌套,
  404. */
  405.   IP =0x10;  //把串口中断设置为最高优先级,必须的。

  406.   TH1=TL1=-(11059200L/12/32/9600);  //串口波特率为9600。
  407.   TR1=1;
  408. }

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

  411.    ucDigDot8=0;   //初始化小数点全部不显示
  412.    ucDigDot7=0;  
  413.    ucDigDot6=0;
  414.    ucDigDot5=0;  
  415.    ucDigDot4=0;
  416.    ucDigDot3=0;  
  417.    ucDigDot2=0;
  418.    ucDigDot1=0;

  419.    EA=1;     //开总中断
  420.    ES=1;     //允许串口中断
  421.    ET0=1;    //允许定时中断
  422.    TR0=1;    //启动定时中断
  423. }
复制代码

总结陈词:
    前面我在第48节里讲过用ds1302做的时钟程序,但是后来很多网友建议,为了方便初学者学习编程思路,我应该用单片机定时器做一个时钟程序。因此,我决定下一节讲这方面的内容。欲知详情,请听下回分解----用单片机内部定时器做一个时钟。

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

乐于分享,勇于质疑!
117#
 楼主| 发表于 2015-1-4 11:10:55 | 显示全部楼层
第八十九节:用单片机内部定时器做一个时钟。
开场白:
很多网友建议,为了方便初学者学习编程思路,我应该用单片机定时器做一个时钟程序供大家参考学习。其实我前面第48节就已经用ds1302做了一个可以显示和更高时间的时钟,这一节只要在第48节的源代码基础上,大的框架不用动,只需要把ds1302产生的时间改成用定时中断产生的时间就可以了,改动的地方非常小。但是为了让时间的精度更高,最后必须跟标准时间进行校验,来修正系统中一秒钟需要多个定时中断的误差,这个误差决定了系统的时间精度,其实这个校验方法我在前面很多章节上跟大家介绍过了:
    第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
    第二步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表,当手机的标准时间跑了780(这个标准时间跑得越长校验精度越高),而此时单片机仅仅跑了1632秒。那么最终得出1秒钟需要的定时中断次数是:const_time_1s=(200*1632)/780=418
第三步:如果发现时钟还是不太准,可以继续返回第一步根据最新1秒钟的时间是418次,多校验几次,来不断调整const_time_1s的数值,直到找到相对精度的时间为止。
本系统仅供学习,精度不可能做得很好,因为影响时间精度的因素还有定时中断的重装值,定时中断里面的代码尽量少,以及晶振等不好控制的因素。所以鸿哥一直不推荐在实际项目中用单片机的内部定时器做实时时钟,因为精度有限。真正想要准确的时钟时间,还是强烈建议大家用外部专用的时钟芯片或者用CPLD/FPGA来做。
    具体内容,请看源代码讲解。
1 硬件平台.
    基于朱兆祺51单片机学习板。
2)实现功能:
     本程序有2两个窗口。
     1个窗口显示日期。显示格式“年--日”。注意中间有“-”分开。
     2个窗口显示时间。显示格式“时 分 秒”。注意中间没“-”,只有空格分开。
     系统上电后,默认显示第2个窗口,实时显示动态的“时 分 秒”时间。此时按下S13按键不松手就会切换到显示日期的第1个窗口。松手后自动切换回第2个显示动态时间的窗口。
     需要更改时间的时候,长按S9按键不松手超过3秒后,系统将进入修改时间的状态,切换到第1个日期窗口,并且显示“年”的两位数码管会闪烁,此时可以按S1或者S5加减按键修改年的参数,修改完年后,继续短按S9按键,会切换到“月”的参数闪烁状态,只要依次不断按下S9按键,就会依次切换年,月,日,时,分,秒的参数闪烁状态,最后修改完秒的参数后,系统会自动把我们修改设置的日期时间一次性更改到定时中断函数内部的时间变量,达到修改日期时间的目的。
S13是电平变化按键,用来切换窗口的,专门用来查看当前日期。按下S13按键时显示日期窗口,松手后返回到显示实时时间的窗口。
[size=10.5000pt](3)源代码讲解如下:
  1. #include "REG52.H"

  2. #define const_dpy_time_half  200  //数码管闪烁时间的半值
  3. #define const_dpy_time_all   400  //数码管闪烁时间的全值 一定要比const_dpy_time_half 大

  4. #define const_voice_short  40   //蜂鸣器短叫的持续时间
  5. #define const_key_time1  20    //按键去抖动延时的时间
  6. #define const_key_time2  20    //按键去抖动延时的时间
  7. #define const_key_time3  20    //按键去抖动延时的时间
  8. #define const_key_time4  20    //按键去抖动延时的时间

  9. #define const_key_time17  1200  //长按超过3秒的时间


  10. /* 注释一:
  11.   * const_timer_1s这个是产生多少次定时中断才算1秒钟的标准。这个标准决定了时钟的精度。这个标准最后是需要校验的。
  12.   * 那么是如何检验的呢?根据我们前面介绍的校验时间方法:
  13.   * 步骤:
  14.   * 第一步:在程序代码上先写入1秒钟大概需要200个定时中断。
  15.   * 第二步:把程序烧录进单片机后,上电开始测试,手上同步打开手机里的秒表,当手机的标准时间跑了780秒(这个标准时间跑得越长校验精度越高),
  16.   *         而此时单片机仅仅跑了1632秒。那么最终得出1秒钟需要的定时中断次数是:const_time_1s=(200*1632)/780=418。
  17.   * 第三步:如果发现时钟还是不太准,可以继续返回第一步根据最新1秒钟的时间是418次,多校验几次。本系统仅供学习,精度不可能做得很好,因为
  18.   *         影响时间精度的因素还有定时中断的重装值,定时中断里面的代码尽量少,以及晶振等不好控制的因素。所以鸿哥一直不推荐在实际项目中
  19.   *         用单片机的内部定时器做实时时钟,因为精度有限。真正想要准确的时钟时间,还是强烈建议大家用外部专用的时钟芯片或者用CPLD/FPGA来做。
  20.   */


  21. //#define const_timer_1s  200   //第一次假设大概1秒的时间需要200个定时中断

  22. #define const_timer_1s  418  //第二次校验后,最终选定大概1秒的时间需要418个定时中断。如果发现时间还是不准,可以在此基础上继续校验来调整此数据。

  23. void initial_myself(void);   
  24. void initial_peripheral(void);
  25. void delay_short(unsigned int uiDelayShort);
  26. void delay_long(unsigned int uiDelaylong);


  27. //驱动数码管的74HC595
  28. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01);  
  29. void display_drive(void); //显示数码管字模的驱动函数
  30. void display_service(void); //显示的窗口菜单服务程序
  31. //驱动LED的74HC595
  32. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01);

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

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

  36. void timer_sampling(void); //定时器采样程序,内部每秒钟采集更新一次


  37. unsigned char get_date(unsigned char ucYearTemp,unsigned char ucMonthTemp);  //获取当前月份的最大天数

  38. //日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
  39. unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp); //日调整


  40. sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键
  41. sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键
  42. sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键
  43. sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键

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

  46. sbit dig_hc595_sh_dr=P2^0;     //数码管的74HC595程序
  47. sbit dig_hc595_st_dr=P2^1;  
  48. sbit dig_hc595_ds_dr=P2^2;  
  49. sbit hc595_sh_dr=P2^3;    //LED灯的74HC595程序
  50. sbit hc595_st_dr=P2^4;  
  51. sbit hc595_ds_dr=P2^5;  

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

  53. unsigned int  uiKeyTimeCnt1=0; //按键去抖动延时计数器
  54. unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志
  55. unsigned int  uiKeyTimeCnt2=0; //按键去抖动延时计数器
  56. unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志
  57. unsigned int  uiKeyTimeCnt3=0; //按键去抖动延时计数器
  58. unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志

  59. unsigned int uiKey4Cnt1=0;  //在软件滤波中,用到的变量
  60. unsigned int uiKey4Cnt2=0;
  61. unsigned char ucKey4Sr=1;  //实时反映按键的电平状态
  62. unsigned char ucKey4SrRecord=0; //记录上一次按键的电平状态

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

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

  73. unsigned char ucDigDot8;  //数码管8的小数点是否显示的标志
  74. unsigned char ucDigDot7;  //数码管7的小数点是否显示的标志
  75. unsigned char ucDigDot6;  //数码管6的小数点是否显示的标志
  76. unsigned char ucDigDot5;  //数码管5的小数点是否显示的标志
  77. unsigned char ucDigDot4;  //数码管4的小数点是否显示的标志
  78. unsigned char ucDigDot3;  //数码管3的小数点是否显示的标志
  79. unsigned char ucDigDot2;  //数码管2的小数点是否显示的标志
  80. unsigned char ucDigDot1;  //数码管1的小数点是否显示的标志
  81. unsigned char ucDigShowTemp=0; //临时中间变量
  82. unsigned char ucDisplayDriveStep=1;  //动态扫描数码管的步骤变量


  83. unsigned char ucWd=2;  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  84. unsigned char ucPart=0;//本程序的核心变量,局部显示变量。类似于二级菜单的变量。代表显示不同的局部。

  85. unsigned char ucWd1Update=0; //窗口1更新显示标志
  86. unsigned char ucWd2Update=1; //窗口2更新显示标志

  87. unsigned char ucWd1Part1Update=0;  //在窗口1中,局部1的更新显示标志
  88. unsigned char ucWd1Part2Update=0; //在窗口1中,局部2的更新显示标志
  89. unsigned char ucWd1Part3Update=0; //在窗口1中,局部3的更新显示标志

  90. unsigned char ucWd2Part1Update=0;  //在窗口2中,局部1的更新显示标志
  91. unsigned char ucWd2Part2Update=0; //在窗口2中,局部2的更新显示标志
  92. unsigned char ucWd2Part3Update=0; //在窗口2中,局部3的更新显示标志

  93. unsigned char  ucYear=15;    //用来显示和设置的时间变量
  94. unsigned char  ucMonth=1;  
  95. unsigned char  ucDate=1;  
  96. unsigned char  ucHour=12;  
  97. unsigned char  ucMinute=0;  
  98. unsigned char  ucSecond=0;  


  99. unsigned int uiTimerCnt=0;   //计时器的时基

  100. unsigned char  ucTimerYear=15;   //在定时器内部时基产生的时间变量
  101. unsigned char  ucTimerMonth=1;  
  102. unsigned char  ucTimerDate=1;  
  103. unsigned char  ucTimerHour=12;  
  104. unsigned char  ucTimerMinute=0;  
  105. unsigned char  ucTimerSecond=0;  

  106. unsigned char  ucTimerDateMax=31; //当前月份的最大天数

  107. unsigned char  ucTimerUpdate=0; //定时器每1秒钟所产生的标志
  108. unsigned char  ucTimerStart=1;  //是否打开定时器内部时间的标志,在本程序相当于原子锁的作用。

  109. unsigned char ucTemp1=0;  //中间过渡变量
  110. unsigned char ucTemp2=0;  //中间过渡变量

  111. unsigned char ucTemp4=0;  //中间过渡变量
  112. unsigned char ucTemp5=0;  //中间过渡变量

  113. unsigned char ucTemp7=0;  //中间过渡变量
  114. unsigned char ucTemp8=0;  //中间过渡变量

  115. unsigned char ucDelayTimerLock=0; //原子锁
  116. unsigned int  uiDelayTimer=0;


  117. unsigned char ucDpyTimeLock=0; //原子锁
  118. unsigned int  uiDpyTimeCnt=0;  //数码管的闪烁计时器,放在定时中断里不断累加

  119. //根据原理图得出的共阴数码管字模表
  120. code unsigned char dig_table[]=
  121. {
  122. 0x3f,  //0       序号0
  123. 0x06,  //1       序号1
  124. 0x5b,  //2       序号2
  125. 0x4f,  //3       序号3
  126. 0x66,  //4       序号4
  127. 0x6d,  //5       序号5
  128. 0x7d,  //6       序号6
  129. 0x07,  //7       序号7
  130. 0x7f,  //8       序号8
  131. 0x6f,  //9       序号9
  132. 0x00,  //无      序号10
  133. 0x40,  //-       序号11
  134. 0x73,  //P       序号12
  135. };
  136. void main()
  137.   {
  138.    initial_myself();  
  139.    delay_long(100);   
  140.    initial_peripheral();
  141.    while(1)  
  142.    {
  143.       key_service(); //按键服务的应用程序
  144.       timer_sampling(); //定时器采样程序,内部每秒钟采集更新一次
  145.       display_service(); //显示的窗口菜单服务程序
  146.    }
  147. }


  148. /* 注释二:
  149.   * 系统不用时时刻刻采集定时器的内部数据,每隔1秒钟的时间更新采集一次就可以了。
  150.   * 这个1秒钟的时间是根据定时器内部ucTimerUpdate变量来判断。
  151.   */
  152. void timer_sampling(void) //采样定时器的程序,内部每秒钟采集更新一次
  153. {
  154.    if(ucPart==0)  //当系统不是处于设置日期和时间的情况下
  155.    {
  156.       if(ucTimerUpdate==1)  //每隔1秒钟时间就更新采集一次定时器的时间数据
  157.       {
  158.           ucTimerUpdate=0;  //及时清零,避免一直更新。

  159.           ucYear=ucTimerYear; //读取定时器内部的年
  160.           ucMonth=ucTimerMonth; //读取定时器内部的月
  161.           ucDate=ucTimerDate;  //读取定时器内部的日
  162.           ucHour=ucTimerHour;   //读取定时器内部的时
  163.           ucMinute=ucTimerMinute;  //读取定时器内部的分
  164.           ucSecond=ucTimerSecond;  //读取定时器内部的秒

  165.           ucWd2Update=1; //窗口2更新显示时间
  166.       }

  167.    }
  168. }

  169. /* 注释三:
  170.   * 根据年份和月份来获取当前这个月的最大天数。每个月份的天数最大取值不同,有的最大28日,
  171.   * 有的最大29日,有的最大30,有的最大31。
  172.   */
  173. unsigned char get_date(unsigned char ucYearTemp,unsigned char ucMonthTemp)
  174. {


  175.    unsigned char ucDayResult;
  176.    unsigned int uiYearTemp;
  177.    unsigned int uiYearYu;

  178.    ucDayResult=31; //默认最大是31天,以下根据不同的年份和月份来决定是否需要修正这个值

  179.    switch(ucMonthTemp)  //根据不同的月份来获取当前月份天数的最大值
  180.    {
  181.       case 2:  //二月份要计算是否是闰年
  182.            uiYearTemp=2000+ucYearTemp;  
  183.            uiYearYu=uiYearTemp%4;
  184.            if(uiYearYu==0) //闰年
  185.            {
  186.               ucDayResult=29;
  187.            }
  188.            else
  189.            {
  190.               ucDayResult=28;
  191.            }
  192.            break;
  193.       case 4:
  194.       case 6:
  195.       case 9:
  196.       case 11:
  197.               ucDayResult=30;
  198.            break;

  199.    }

  200.    return ucDayResult;

  201. }

  202. //日调整 每个月份的日最大取值不同,有的最大28日,有的最大29日,有的最大30,有的最大31
  203. unsigned char date_adjust(unsigned char ucYearTemp,unsigned char ucMonthTemp,unsigned char ucDateTemp) //日调整
  204. {


  205.    unsigned char ucDayResult;
  206.    unsigned int uiYearTemp;
  207.    unsigned int uiYearYu;
  208.    

  209.    ucDayResult=ucDateTemp;

  210.    switch(ucMonthTemp)  //根据不同的月份来修正不同的日最大值
  211.    {
  212.       case 2:  //二月份要计算是否是闰年
  213.            uiYearTemp=2000+ucYearTemp;  
  214.            uiYearYu=uiYearTemp%4;
  215.            if(uiYearYu==0) //闰年
  216.            {
  217.                if(ucDayResult>29)
  218.                {
  219.                   ucDayResult=29;
  220.                }
  221.            }
  222.            else
  223.            {
  224.                if(ucDayResult>28)
  225.                {
  226.                   ucDayResult=28;
  227.                }
  228.            }
  229.            break;
  230.       case 4:
  231.       case 6:
  232.       case 9:
  233.       case 11:
  234.            if(ucDayResult>30)
  235.            {
  236.               ucDayResult=30;
  237.            }
  238.            break;

  239.    }

  240.    return ucDayResult;

  241. }


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

  244.    switch(ucWd)  //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。
  245.    {
  246.        case 1:   //显示日期窗口的数据  数据格式 NN-YY-RR 年-月-日
  247.             if(ucWd1Update==1)  //窗口1要全部更新显示
  248.             {
  249.                ucWd1Update=0;  //及时清零标志,避免一直进来扫描

  250.                ucDigShow6=11;  //显示一杠"-"
  251.                ucDigShow3=11;  //显示一杠"-"

  252.                ucWd1Part1Update=1;  //局部年更新显示
  253.                ucWd1Part2Update=1;  //局部月更新显示
  254.                ucWd1Part3Update=1;  //局部日更新显示
  255.             }

  256.             if(ucWd1Part1Update==1)//局部年更新显示
  257.             {
  258.                ucWd1Part1Update=0;
  259.                ucTemp8=ucYear/10;  //年
  260.                ucTemp7=ucYear%10;

  261.                ucDigShow8=ucTemp8; //数码管显示实际内容
  262.                ucDigShow7=ucTemp7;
  263.             }


  264.             if(ucWd1Part2Update==1)//局部月更新显示
  265.             {
  266.                ucWd1Part2Update=0;
  267.                ucTemp5=ucMonth/10;  //月
  268.                ucTemp4=ucMonth%10;

  269.                ucDigShow5=ucTemp5; //数码管显示实际内容
  270.                ucDigShow4=ucTemp4;
  271.             }


  272.             if(ucWd1Part3Update==1) //局部日更新显示
  273.             {
  274.                ucWd1Part3Update=0;
  275.                ucTemp2=ucDate/10;  //日
  276.                ucTemp1=ucDate%10;
  277.                         
  278.                ucDigShow2=ucTemp2; //数码管显示实际内容
  279.                ucDigShow1=ucTemp1;
  280.             }
  281.               //数码管闪烁
  282.             switch(ucPart)  //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
  283.             {
  284.                 case 0:  //都不闪烁
  285.                      break;
  286.                 case 1:  //年参数闪烁
  287.                      if(uiDpyTimeCnt==const_dpy_time_half)
  288.                      {
  289.                            ucDigShow8=ucTemp8; //数码管显示实际内容
  290.                            ucDigShow7=ucTemp7;
  291.                       }
  292.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  293.                      {
  294.                            ucDpyTimeLock=1; //原子锁加锁
  295.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  296.                            ucDpyTimeLock=0;  //原子锁解锁

  297.                            ucDigShow8=10;   //数码管显示空,什么都不显示
  298.                            ucDigShow7=10;

  299.                      }
  300.                      break;
  301.                 case 2:   //月参数闪烁
  302.                      if(uiDpyTimeCnt==const_dpy_time_half)
  303.                      {
  304.                            ucDigShow5=ucTemp5; //数码管显示实际内容
  305.                            ucDigShow4=ucTemp4;
  306.                       }
  307.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  308.                      {
  309.                            ucDpyTimeLock=1; //原子锁加锁
  310.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  311.                            ucDpyTimeLock=0;  //原子锁解锁

  312.                            ucDigShow5=10;   //数码管显示空,什么都不显示
  313.                            ucDigShow4=10;

  314.                      }
  315.                     break;
  316.                 case 3:   //日参数闪烁
  317.                      if(uiDpyTimeCnt==const_dpy_time_half)
  318.                      {
  319.                            ucDigShow2=ucTemp2; //数码管显示实际内容
  320.                            ucDigShow1=ucTemp1;
  321.                       }
  322.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  323.                      {
  324.                            ucDpyTimeLock=1; //原子锁加锁
  325.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  326.                            ucDpyTimeLock=0;  //原子锁解锁

  327.                            ucDigShow2=10;   //数码管显示空,什么都不显示
  328.                            ucDigShow1=10;

  329.                      }
  330.                     break;      
  331.             }

  332.             break;
  333.        case 2:   //显示时间窗口的数据  数据格式 SS FF MM 时 分 秒
  334.             if(ucWd2Update==1)  //窗口2要全部更新显示
  335.             {
  336.                ucWd2Update=0;  //及时清零标志,避免一直进来扫描

  337.                ucDigShow6=10;  //显示空
  338.                ucDigShow3=10;  //显示空

  339.                ucWd2Part3Update=1;  //局部时更新显示
  340.                ucWd2Part2Update=1;  //局部分更新显示
  341.                ucWd2Part1Update=1;  //局部秒更新显示
  342.             }

  343.             if(ucWd2Part1Update==1)//局部时更新显示
  344.             {
  345.                ucWd2Part1Update=0;
  346.                ucTemp8=ucHour/10;  //时
  347.                ucTemp7=ucHour%10;

  348.                ucDigShow8=ucTemp8; //数码管显示实际内容
  349.                ucDigShow7=ucTemp7;
  350.             }


  351.             if(ucWd2Part2Update==1)//局部分更新显示
  352.             {
  353.                ucWd2Part2Update=0;
  354.                ucTemp5=ucMinute/10;  //分
  355.                ucTemp4=ucMinute%10;

  356.                ucDigShow5=ucTemp5; //数码管显示实际内容
  357.                ucDigShow4=ucTemp4;
  358.             }


  359.             if(ucWd2Part3Update==1) //局部秒更新显示
  360.             {
  361.                ucWd2Part3Update=0;
  362.                ucTemp2=ucSecond/10;  //秒
  363.                ucTemp1=ucSecond%10;               
  364.         
  365.                ucDigShow2=ucTemp2; //数码管显示实际内容
  366.                ucDigShow1=ucTemp1;
  367.             }
  368.               //数码管闪烁
  369.             switch(ucPart)  //相当于二级菜单,根据局部变量的值,使对应的参数产生闪烁的动态效果。
  370.             {
  371.                 case 0:  //都不闪烁
  372.                      break;
  373.                 case 1:  //时参数闪烁
  374.                      if(uiDpyTimeCnt==const_dpy_time_half)
  375.                      {
  376.                            ucDigShow8=ucTemp8; //数码管显示实际内容
  377.                            ucDigShow7=ucTemp7;
  378.                       }
  379.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  380.                      {
  381.                            ucDpyTimeLock=1; //原子锁加锁
  382.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  383.                            ucDpyTimeLock=0;  //原子锁解锁

  384.                            ucDigShow8=10;   //数码管显示空,什么都不显示
  385.                            ucDigShow7=10;

  386.                      }
  387.                      break;
  388.                 case 2:   //分参数闪烁
  389.                      if(uiDpyTimeCnt==const_dpy_time_half)
  390.                      {
  391.                            ucDigShow5=ucTemp5; //数码管显示实际内容
  392.                            ucDigShow4=ucTemp4;
  393.                      }
  394.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  395.                      {
  396.                            ucDpyTimeLock=1; //原子锁加锁
  397.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  398.                            ucDpyTimeLock=0;  //原子锁解锁

  399.                            ucDigShow5=10;   //数码管显示空,什么都不显示
  400.                            ucDigShow4=10;

  401.                      }
  402.                     break;
  403.                 case 3:   //秒参数闪烁
  404.                      if(uiDpyTimeCnt==const_dpy_time_half)
  405.                      {
  406.                            ucDigShow2=ucTemp2; //数码管显示实际内容
  407.                            ucDigShow1=ucTemp1;
  408.                      }
  409.                      else if(uiDpyTimeCnt>const_dpy_time_all) //const_dpy_time_all一定要比const_dpy_time_half 大
  410.                      {
  411.                            ucDpyTimeLock=1; //原子锁加锁
  412.                            uiDpyTimeCnt=0;   //及时把闪烁记时器清零
  413.                            ucDpyTimeLock=0;  //原子锁解锁

  414.                            ucDigShow2=10;   //数码管显示空,什么都不显示
  415.                            ucDigShow1=10;

  416.                      }
  417.                     break;      
  418.             }


  419.             break;
  420.       }
  421.    

  422. }

  423. void key_scan(void)//按键扫描函数 放在定时中断里
  424. {  
  425.   if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  426.   {
  427.      ucKeyLock1=0; //按键自锁标志清零
  428.      uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  429.   }
  430.   else if(ucKeyLock1==0)//有按键按下,且是第一次被按下
  431.   {
  432.      uiKeyTimeCnt1++; //累加定时中断次数
  433.      if(uiKeyTimeCnt1>const_key_time1)
  434.      {
  435.         uiKeyTimeCnt1=0;
  436.         ucKeyLock1=1;  //自锁按键置位,避免一直触发
  437.         ucKeySec=1;    //触发1号键
  438.      }
  439.   }

  440.   if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  441.   {
  442.      ucKeyLock2=0; //按键自锁标志清零
  443.      uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  444.   }
  445.   else if(ucKeyLock2==0)//有按键按下,且是第一次被按下
  446.   {
  447.      uiKeyTimeCnt2++; //累加定时中断次数
  448.      if(uiKeyTimeCnt2>const_key_time2)
  449.      {
  450.         uiKeyTimeCnt2=0;
  451.         ucKeyLock2=1;  //自锁按键置位,避免一直触发
  452.         ucKeySec=2;    //触发2号键
  453.      }
  454.   }



  455. /* 注释四:
  456.   * 注意,此处把一个按键的短按和长按的功能都实现了。
  457.   */

  458.   if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位
  459.   {
  460.      ucKeyLock3=0; //按键自锁标志清零
  461.      uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。      
  462.   }
  463.   else if(ucKeyLock3==0)//有按键按下,且是第一次被按下
  464.   {
  465.      uiKeyTimeCnt3++; //累加定时中断次数
  466.      if(uiKeyTimeCnt3>const_key_time3)
  467.      {
  468.         uiKeyTimeCnt3=0;
  469.         ucKeyLock3=1;  //自锁按键置位,避免一直触发
  470.         ucKeySec=3;    //短按触发3号键
  471.      }
  472.   }
  473.   else if(uiKeyTimeCnt3<const_key_time17)   //长按3秒
  474.   {
  475.      uiKeyTimeCnt3++; //累加定时中断次数
  476.          if(uiKeyTimeCnt3==const_key_time17)  //等于3秒钟,触发17号长按按键
  477.          {
  478.             ucKeySec=17;    //长按3秒触发17号键  
  479.          }
  480.   }


  481. /* 注释五:
  482.   * 注意,此处是电平按键的滤波抗干扰处理
  483.   */
  484.    if(key_sr4==1)  //对应朱兆祺学习板的S13键  
  485.    {
  486.        uiKey4Cnt1=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  487.        uiKey4Cnt2++; //类似独立按键去抖动的软件抗干扰处理
  488.        if(uiKey4Cnt2>const_key_time4)
  489.        {
  490.            uiKey4Cnt2=0;
  491.            ucKey4Sr=1;  //实时反映按键松手时的电平状态
  492.        }
  493.    }
  494.    else   
  495.    {
  496.        uiKey4Cnt2=0; //在软件滤波中,非常关键的语句!!!类似按键去抖动程序的及时清零
  497.        uiKey4Cnt1++;
  498.        if(uiKey4Cnt1>const_key_time4)
  499.        {
  500.           uiKey4Cnt1=0;
  501.           ucKey4Sr=0;  //实时反映按键按下时的电平状态
  502.        }
  503.    }


  504. }

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

  507.   switch(ucKeySec) //按键服务状态切换
  508.   {
  509.     case 1:// 加按键 对应朱兆祺学习板的S1键
  510.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  511.           {
  512.                case 1:
  513.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  514.                     {
  515.                         case 1:  //年
  516.                              ucYear++;
  517.                              if(ucYear>99)
  518.                              {
  519.                                 ucYear=99;
  520.                              }
  521.                              ucWd1Part1Update=1;  //更新显示
  522.                              break;
  523.                          case 2: //月
  524.                              ucMonth++;
  525.                              if(ucMonth>12)
  526.                              {
  527.                                  ucMonth=12;
  528.                              }
  529.                              ucWd1Part2Update=1;  //更新显示                                               
  530.                              break;
  531.                          case 3: //日
  532.                              ucDate++;
  533.                              if(ucDate>31)
  534.                              {
  535.                                 ucDate=31;
  536.                              }
  537.                              ucWd1Part3Update=1;  //更新显示               
  538.                              break;                                       

  539.                     }


  540.                     break;
  541.                case 2:
  542.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  543.                     {
  544.                          case 1:  //时
  545.                              ucHour++;
  546.                              if(ucHour>23)
  547.                              {
  548.                                 ucHour=23;
  549.                              }
  550.                              ucWd2Part1Update=1;  //更新显示                                               
  551.                              break;
  552.                          case 2: //分
  553.                              ucMinute++;
  554.                              if(ucMinute>59)
  555.                              {
  556.                                 ucMinute=59;
  557.                              }
  558.                              ucWd2Part2Update=1;  //更新显示                                                      
  559.                              break;
  560.                          case 3: //秒
  561.                              ucSecond++;
  562.                              if(ucSecond>59)
  563.                              {
  564.                                  ucSecond=59;
  565.                              }
  566.                              ucWd2Part3Update=1;  //更新显示        
  567.                              break;                                       

  568.                     }
  569.                     break;
  570.          
  571.           }

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

  575.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  576.           break;   
  577.    
  578.     case 2:// 减按键 对应朱兆祺学习板的S5键
  579.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  580.           {
  581.                case 1:
  582.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  583.                     {
  584.                         case 1:  //年
  585.                              ucYear--;
  586.                              if(ucYear>99)
  587.                              {
  588.                                 ucYear=0;
  589.                              }
  590.                              ucWd1Part1Update=1;  //更新显示
  591.                              break;
  592.                         case 2: //月
  593.                              ucMonth--;
  594.                              if(ucMonth<1)
  595.                              {
  596.                                 ucMonth=1;
  597.                              }
  598.                              ucWd1Part2Update=1;  //更新显示                                               
  599.                              break;
  600.                         case 3: //日
  601.                              ucDate--;
  602.                              if(ucDate<1)
  603.                              {
  604.                                 ucDate=1;
  605.                              }
  606.                              ucWd1Part3Update=1;  //更新显示               
  607.                              break;                                       

  608.                     }


  609.                     break;
  610.                case 2:
  611.                     switch(ucPart) //在不同的局部变量下,相当于二级菜单
  612.                     {
  613.                          case 1:  //时
  614.                              ucHour--;
  615.                              if(ucHour>23)
  616.                              {
  617.                                 ucHour=0;
  618.                              }
  619.                              ucWd2Part1Update=1;  //更新显示                                               
  620.                              break;
  621.                          case 2: //分
  622.                              ucMinute--;
  623.                              if(ucMinute>59)
  624.                              {
  625.                                 ucMinute=0;
  626.                              }
  627.                              ucWd2Part2Update=1;  //更新显示                                                      
  628.                              break;
  629.                          case 3: //秒
  630.                              ucSecond--;
  631.                              if(ucSecond>59)
  632.                              {
  633.                                 ucSecond=0;
  634.                              }
  635.                              ucWd2Part3Update=1;  //更新显示        
  636.                              break;                                       

  637.                     }
  638.                     break;
  639.          
  640.           }

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

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

  646.     case 3://短按设置按键 对应朱兆祺学习板的S9键
  647.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  648.           {
  649.                case 1:
  650.                     ucPart++;
  651.                     if(ucPart>3)
  652.                     {
  653.                          ucPart=1;
  654.                          ucWd=2; //切换到第二个窗口,设置时分秒
  655.                          ucWd2Update=1;  //窗口2更新显示
  656.                     }
  657.                     ucWd1Update=1;  //窗口1更新显示
  658.                     break;
  659.                case 2:
  660.                     if(ucPart>0) //在窗口2的时候,要第一次激活设置时间,必须是长按3秒才可以,这里短按激活不了第一次
  661.                     {
  662.                        ucPart++;
  663.                        if(ucPart>3)  //设置时间结束
  664.                        {
  665.                            ucPart=0;



  666. /* 注释六:
  667.   * 每个月份的天数最大值是不一样的,在写入ds1302时钟芯片内部数据前,应该做一次调整。
  668.   * 有的月份最大28天,有的月份最大29天,有的月份最大30天,有的月份最大31天,
  669.   */                                                   
  670.                            ucDate=date_adjust(ucYear,ucMonth,ucDate); //日调整 避免日的数值在某个月份超范围

  671.                            ucTimerStart=0;  //关闭定时器的时间。在更改定时器内部时间数据时,先关闭它,相当于原子锁的加锁作用。

  672.                            ucTimerYear=ucYear;  //把设置和显示的数据更改到定时器内部的时间变量
  673.                            ucTimerMonth=ucMonth;
  674.                            ucTimerDate=ucDate;  
  675.                            ucTimerHour=ucHour;  
  676.                            ucTimerMinute=ucMinute;  
  677.                            ucTimerSecond=ucSecond;  

  678.                            ucTimerStart=1;  //打开定时器的时间。在更改定时器内部时间数据后,再打开它,相当于原子锁的解锁作用。

  679.                        }
  680.                        ucWd2Update=1;  //窗口2更新显示
  681.                     }

  682.                     break;
  683.          
  684.           }

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

  689.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  690.           break;         
  691.     case 17://长按3秒设置按键 对应朱兆祺学习板的S9键
  692.           switch(ucWd)  //在不同的窗口下,设置不同的参数
  693.           {
  694.                case 2:
  695.                     if(ucPart==0) //处于非设置时间的状态下,要第一次激活设置时间,必须是长按3秒才可以
  696.                     {
  697.                        ucWd=1;
  698.                        ucPart=1;  //进入到设置日期的状态下
  699.                        ucWd1Update=1;  //窗口1更新显示
  700.                     }
  701.                     break;
  702.          
  703.           }
  704.           ucVoiceLock=1;  //原子锁加锁,保护主函数与中断函数的共享变量uiVoiceCnt
  705.           uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。
  706.           ucVoiceLock=0;  //原子锁解锁,保护主函数与中断函数的共享变量uiVoiceCnt

  707.           ucKeySec=0;  //响应按键服务处理程序后,按键编号清零,避免一致触发
  708.           break;   
  709.          
  710.   }         
  711.   

  712. /* 注释七:
  713.   * 注意,此处就是第一次出现的电平按键程序,跟以往的下降沿按键不一样。
  714.   * ucKey4Sr是经过软件滤波处理后,直接反应IO口电平状态的变量.当电平发生
  715.   * 变化时,就会切换到不同的显示界面,这里多用了一个ucKey4SrRecord变量
  716.   * 记录上一次的电平状态,是为了避免一直刷新显示。
  717.   */
  718.   if(ucKey4Sr!=ucKey4SrRecord)  //说明S13的切换按键电平状态发生变化
  719.   {
  720.      ucKey4SrRecord=ucKey4Sr;//及时记录当前最新的按键电平状态  避免一直进来触发

  721.          if(ucKey4Sr==1) //松手后切换到显示时间的窗口
  722.          {
  723.             ucWd=2;    //显示时分秒的窗口
  724.             ucPart=0;  //进入到非设置时间的状态下
  725.             ucWd2Update=1;  //窗口2更新显示
  726.          }
  727.          else  //按下去切换到显示日期的窗口
  728.          {
  729.             ucWd=1;   //显示年月日的窗口
  730.             ucPart=0;  //进入到非设置时间的状态下
  731.             ucWd1Update=1;  //窗口1更新显示
  732.          }
  733.   
  734.   }
  735. }

  736. void display_drive(void)  
  737. {
  738.    //以下程序,如果加一些数组和移位的元素,还可以压缩容量。但是鸿哥追求的不是容量,而是清晰的讲解思路
  739.    switch(ucDisplayDriveStep)
  740.    {
  741.       case 1:  //显示第1位
  742.            ucDigShowTemp=dig_table[ucDigShow1];
  743.                    if(ucDigDot1==1)
  744.                    {
  745.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  746.                    }
  747.            dig_hc595_drive(ucDigShowTemp,0xfe);
  748.                break;
  749.       case 2:  //显示第2位
  750.            ucDigShowTemp=dig_table[ucDigShow2];
  751.                    if(ucDigDot2==1)
  752.                    {
  753.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  754.                    }
  755.            dig_hc595_drive(ucDigShowTemp,0xfd);
  756.                break;
  757.       case 3:  //显示第3位
  758.            ucDigShowTemp=dig_table[ucDigShow3];
  759.                    if(ucDigDot3==1)
  760.                    {
  761.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  762.                    }
  763.            dig_hc595_drive(ucDigShowTemp,0xfb);
  764.                break;
  765.       case 4:  //显示第4位
  766.            ucDigShowTemp=dig_table[ucDigShow4];
  767.                    if(ucDigDot4==1)
  768.                    {
  769.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  770.                    }
  771.            dig_hc595_drive(ucDigShowTemp,0xf7);
  772.                break;
  773.       case 5:  //显示第5位
  774.            ucDigShowTemp=dig_table[ucDigShow5];
  775.                    if(ucDigDot5==1)
  776.                    {
  777.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  778.                    }
  779.            dig_hc595_drive(ucDigShowTemp,0xef);
  780.                break;
  781.       case 6:  //显示第6位
  782.            ucDigShowTemp=dig_table[ucDigShow6];
  783.                    if(ucDigDot6==1)
  784.                    {
  785.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  786.                    }
  787.            dig_hc595_drive(ucDigShowTemp,0xdf);
  788.                break;
  789.       case 7:  //显示第7位
  790.            ucDigShowTemp=dig_table[ucDigShow7];
  791.                    if(ucDigDot7==1)
  792.                    {
  793.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  794.            }
  795.            dig_hc595_drive(ucDigShowTemp,0xbf);
  796.                break;
  797.       case 8:  //显示第8位
  798.            ucDigShowTemp=dig_table[ucDigShow8];
  799.                    if(ucDigDot8==1)
  800.                    {
  801.                       ucDigShowTemp=ucDigShowTemp|0x80;  //显示小数点
  802.                    }
  803.            dig_hc595_drive(ucDigShowTemp,0x7f);
  804.                break;
  805.    }
  806.    ucDisplayDriveStep++;
  807.    if(ucDisplayDriveStep>8)  //扫描完8个数码管后,重新从第一个开始扫描
  808.    {
  809.      ucDisplayDriveStep=1;
  810.    }

  811. }

  812. //数码管的74HC595驱动函数
  813. void dig_hc595_drive(unsigned char ucDigStatusTemp16_09,unsigned char ucDigStatusTemp08_01)
  814. {
  815.    unsigned char i;
  816.    unsigned char ucTempData;
  817.    dig_hc595_sh_dr=0;
  818.    dig_hc595_st_dr=0;
  819.    ucTempData=ucDigStatusTemp16_09;  //先送高8位
  820.    for(i=0;i<8;i++)
  821.    {
  822.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  823.          else dig_hc595_ds_dr=0;
  824.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  825.          delay_short(1);
  826.          dig_hc595_sh_dr=1;
  827.          delay_short(1);
  828.          ucTempData=ucTempData<<1;
  829.    }
  830.    ucTempData=ucDigStatusTemp08_01;  //再先送低8位
  831.    for(i=0;i<8;i++)
  832.    {
  833.          if(ucTempData>=0x80)dig_hc595_ds_dr=1;
  834.          else dig_hc595_ds_dr=0;
  835.          dig_hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  836.          delay_short(1);
  837.          dig_hc595_sh_dr=1;
  838.          delay_short(1);
  839.          ucTempData=ucTempData<<1;
  840.    }
  841.    dig_hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  842.    delay_short(1);
  843.    dig_hc595_st_dr=1;
  844.    delay_short(1);
  845.    dig_hc595_sh_dr=0;    //拉低,抗干扰就增强
  846.    dig_hc595_st_dr=0;
  847.    dig_hc595_ds_dr=0;
  848. }

  849. //LED灯的74HC595驱动函数
  850. void hc595_drive(unsigned char ucLedStatusTemp16_09,unsigned char ucLedStatusTemp08_01)
  851. {
  852.    unsigned char i;
  853.    unsigned char ucTempData;
  854.    hc595_sh_dr=0;
  855.    hc595_st_dr=0;
  856.    ucTempData=ucLedStatusTemp16_09;  //先送高8位
  857.    for(i=0;i<8;i++)
  858.    {
  859.          if(ucTempData>=0x80)hc595_ds_dr=1;
  860.          else hc595_ds_dr=0;
  861.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  862.          delay_short(1);
  863.          hc595_sh_dr=1;
  864.          delay_short(1);
  865.          ucTempData=ucTempData<<1;
  866.    }
  867.    ucTempData=ucLedStatusTemp08_01;  //再先送低8位
  868.    for(i=0;i<8;i++)
  869.    {
  870.          if(ucTempData>=0x80)hc595_ds_dr=1;
  871.          else hc595_ds_dr=0;
  872.          hc595_sh_dr=0;     //SH引脚的上升沿把数据送入寄存器
  873.          delay_short(1);
  874.          hc595_sh_dr=1;
  875.          delay_short(1);
  876.          ucTempData=ucTempData<<1;
  877.    }
  878.    hc595_st_dr=0;  //ST引脚把两个寄存器的数据更新输出到74HC595的输出引脚上并且锁存起来
  879.    delay_short(1);
  880.    hc595_st_dr=1;
  881.    delay_short(1);
  882.    hc595_sh_dr=0;    //拉低,抗干扰就增强
  883.    hc595_st_dr=0;
  884.    hc595_ds_dr=0;
  885. }



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


  890. /* 注释八:
  891.   * 以下是本节内容的核心程序,是定时器内部产生的时间。const_timer_1s这个是产生多少次定时中断才
  892.   * 算1秒钟的标准。这个标准决定了时钟的精度。这个标准最后是需要校验的。
  893.   */
  894.   if(ucTimerStart==1)  //定时器的时间已经打开
  895.   {
  896.      uiTimerCnt++;  //产生1秒钟的时基
  897.      if(uiTimerCnt>=const_timer_1s) //一秒钟的时间到。这个const_timer_1s具体数值最后需要校验得出。
  898.      {
  899.         uiTimerCnt=0; //清零为产生下一个1秒钟准备
  900.         ucTimerUpdate=1; //定时器每1秒钟所产生的标志,通知主函数及时更新采集时间数据

  901.         ucTimerSecond++; //秒时间累加1
  902.         if(ucTimerSecond>=60)
  903.         {
  904.            ucTimerSecond=0;
  905.            ucTimerMinute++; //分时间累加1
  906.            if(ucTimerMinute>=60)
  907.            {
  908.                ucTimerMinute=0;
  909.                ucTimerHour++;  //小时的时间累加1,为了避免if的嵌套过多,把小时的判断放到外面两层的if来继续判断
  910.            }
  911.         }

  912.         if(ucTimerHour>=24)
  913.         {
  914.            ucTimerHour=0;
  915.            ucTimerDate++; //天时间累加1
  916.            ucTimerDateMax=get_date(ucTimerYear,ucTimerMonth);  //根据年和月获取当前月份的最大天数
  917.            if(ucTimerDate>ucTimerDateMax)  //
  918.            {
  919.                ucTimerDate=1; //每个月都是从1号开始
  920.                ucTimerMonth++;  //月时间累加1
  921.                if(ucTimerMonth>12)
  922.                {
  923.                    ucTimerMonth=1; //每年从1月份开始
  924.                    ucTimerYear++; //年时间累加1
  925.                    if(ucTimerYear>99) //本系统的最高有效年份是2099年
  926.                    {
  927.                       ucTimerYear=99;
  928.                    }
  929.                }
  930.            }
  931.         }

  932.      }
  933.    

  934.   }

  935.   if(ucVoiceLock==0) //原子锁判断
  936.   {
  937.      if(uiVoiceCnt!=0)
  938.      {

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

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




  950.   if(ucDpyTimeLock==0) //原子锁判断
  951.   {
  952.      uiDpyTimeCnt++;  //数码管的闪烁计时器
  953.   }



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

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

  960. void delay_short(unsigned int uiDelayShort)
  961. {
  962.    unsigned int i;  
  963.    for(i=0;i<uiDelayShort;i++)
  964.    {
  965.      ;   //一个分号相当于执行一条空语句
  966.    }
  967. }

  968. void delay_long(unsigned int uiDelayLong)
  969. {
  970.    unsigned int i;
  971.    unsigned int j;
  972.    for(i=0;i<uiDelayLong;i++)
  973.    {
  974.       for(j=0;j<500;j++)  //内嵌循环的空指令数量
  975.           {
  976.              ; //一个分号相当于执行一条空语句
  977.           }
  978.    }
  979. }

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

  982.   key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平
  983.   beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。
  984.   hc595_drive(0x00,0x00);  //关闭所有经过另外两个74HC595驱动的LED灯
  985.   TMOD=0x01;  //设置定时器0为工作方式1
  986.   TH0=0xfe;   //重装初始值(65535-500)=65035=0xfe0b
  987.   TL0=0x0b;

  988. }
  989. void initial_peripheral(void) //第二区 初始化外围
  990. {

  991.    ucDigDot8=0;   //小数点全部不显示
  992.    ucDigDot7=0;  
  993.    ucDigDot6=0;
  994.    ucDigDot5=0;  
  995.    ucDigDot4=0;
  996.    ucDigDot3=0;  
  997.    ucDigDot2=0;
  998.    ucDigDot1=0;

  999.    EA=1;     //开总中断
  1000.    ET0=1;    //允许定时中断
  1001.    TR0=1;    //启动定时中断


  1002. }
复制代码

总结陈词:
任何一个电子产品在投入生产的时候都要考虑到生产的测试,朱兆祺51单片机学习板在生产加工后也一样要进行测试。那么这个测试的程序如何能够做到快速,全面,易用这三个要求呢?欲知详情,请听下回分解-----生产朱兆祺51学习板的从机自检测试程序源代码.
(未完待续,下节更精彩,不要走开哦)

乐于分享,勇于质疑!
118#
 楼主| 发表于 2015-1-9 22:09:53 | 显示全部楼层
wysh_2004 发表于 2015-1-9 17:13
鸿哥的单片机程序框架有点类似于PLC的顺序程序控制思想!

有可能。
乐于分享,勇于质疑!
119#
 楼主| 发表于 2015-1-14 22:52:36 | 显示全部楼层
又一个暑假 发表于 2015-1-13 20:08
年底大家都比较忙,鸿哥的巨作也放慢了脚步

最近大家都挺忙。
乐于分享,勇于质疑!
120#
 楼主| 发表于 2015-1-24 20:31:54 | 显示全部楼层
jianghong891011 发表于 2015-1-24 20:01
鸿哥您的代码写的太复杂,我多看几个章节就醉了,初学者看很难跟上节奏。

我准备另外开新的入门基础技术贴。
乐于分享,勇于质疑!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|独闷闷网 ( 粤ICP备12007667号-2 )

GMT+8, 2024-5-4 05:25 , Processed in 0.253307 second(s), 17 queries .

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