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

[复制链接]
299786|913
solarddd 发表于 2014-11-14 08:16 | 显示全部楼层
期待........
 楼主| jianhong_wu 发表于 2014-11-14 12:04 | 显示全部楼层
本帖最后由 jianhong_wu 于 2014-11-14 12:06 编辑

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

开场白:
本来这一节打算开始讲调用液晶屏内部字库时的反显程序,但是我担心跳跃太大,恐怕很多初学者跟不上,所以多插入这一节讲讲后面菜单程序中经常用到的基本功能,在调用内部字库的情况下,如何把一个任意数值的变量显示在液晶屏上。这一节的功能需求跟前面第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. }




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

(未完待续,下节更精彩,不要走开哦)
蓝魔大师弟 发表于 2014-11-14 21:22 | 显示全部楼层
好东西!!。。。。。。。。
a15851404012 发表于 2014-11-16 13:56 | 显示全部楼层
jianhong_wu 发表于 2014-5-6 00:36
第四十五节:主机的串口收发综合程序框架

开场白:

                                                ucSendregBuf[6]=uiSetData2>>8;  //把int类型的参数分解成两个字节的数据
                                                ucSendregBuf[7]=uiSetData2;
吴老师,这句话我想不通,44节中将2个UC合成一个int可以理解,比如1111111101111111右移8位后11111111,将这个字节放入BUF【6】中,【7】里直接存放uiSetData2.。。。。。   不是应该存放后8位吗?新手求解答
 楼主| jianhong_wu 发表于 2014-11-16 17:04 | 显示全部楼层
a15851404012 发表于 2014-11-16 13:56
ucSendregBuf[6]=uiSetData2>>8;  //把int类型的参数分解成两个字节的数据
                                                ucSendregBuf[7]= ...

ucSendregBuf[6]=uiSetData2>>8;  //把uiSetData2的高8位给ucSendregBuf[6],
//注意,执行完上面的语句后,uiSetData2本身并没有改变,改变的仅仅是ucSendregBuf[6]而已
ucSendregBuf[7]=uiSetData2;     //把uiSetData2的低8位给ucSendregBuf[7]

a15851404012 发表于 2014-11-16 19:28 | 显示全部楼层
jianhong_wu 发表于 2014-11-16 17:04
ucSendregBuf[6]=uiSetData2>>8;  //把uiSetData2的高8位给ucSendregBuf[6],
//注意,执行完上面的语句 ...

吴老师,ucSendregBuf[7]=uiSetData2;uiSetData2为2个字节的数据也就是16位2进制数,将这个值直接赋给ucSendregBuf[7]?    这样怎么把底8位给[7]?    还是我哪里理解错了?
我的想法是  假如ucSendregBuf为1111111111110101,右移8位后,变为0000000011111111,这样把高8位赋给[6],求吴老师指导:)
a15851404012 发表于 2014-11-16 19:30 | 显示全部楼层
a15851404012 发表于 2014-11-16 19:28
吴老师,ucSendregBuf[7]=uiSetData2;uiSetData2为2个字节的数据也就是16位2进制数,将这个值直接赋给uc ...

是uiSetData2为1111111111110101:loveliness:
xlsbz 发表于 2014-11-16 19:52 来自手机 | 显示全部楼层
牛啊!以前就觉得牛!
早点推出就好了
b147038606 发表于 2014-11-17 13:32 | 显示全部楼层
新手研究下。。。。。。。。。
JKM99 发表于 2014-11-17 16:04 | 显示全部楼层
elec921 发表于 2014-3-6 08:26
基本功很好,再看点程序结构、算法、和技巧的书    变大虾分分钟的事啊

...

大侠推荐几本啊:lol
JKM99 发表于 2014-11-17 16:09 | 显示全部楼层
jianhong_wu 发表于 2014-3-5 21:55
第二节:delay()延时实现LED灯的闪烁。

开场白:

a15851404012 发表于 2014-11-17 18:02 | 显示全部楼层
jianhong_wu 发表于 2014-11-16 17:04
ucSendregBuf[6]=uiSetData2>>8;  //把uiSetData2的高8位给ucSendregBuf[6],
//注意,执行完上面的语句 ...

是不是直接给的话,高8位就没有了,只存底8位?
无影行者 发表于 2014-11-17 22:26 | 显示全部楼层
jianhong_wu 发表于 2014-3-7 11:48
第十二节:按住一个独立按键不松手的连续步进触发。

开场白:

你好鸿哥,在这片帖子中你在按键短发和连发的时候都把ucKeySec的值赋值为1,在led_run()中用了uiSetnumber<10的语句,在此我有个疑问。单片机在运行时很快加到10了,时间很短暂,是不是演示效果不明显啊?
 楼主| jianhong_wu 发表于 2014-11-18 16:19 | 显示全部楼层
a15851404012 发表于 2014-11-16 19:28
吴老师,ucSendregBuf[7]=uiSetData2;uiSetData2为2个字节的数据也就是16位2进制数,将这个值直接赋给uc ...

没错,你的想法是对的。 “假如ucSendregBuf为1111111111110101,左移8位后,变为0000000011111111,这样把高8位赋给[6]”。
 楼主| jianhong_wu 发表于 2014-11-18 16:20 | 显示全部楼层
a15851404012 发表于 2014-11-17 18:02
是不是直接给的话,高8位就没有了,只存底8位?

是的。int类型的变量直接给char类型的变量,char的变量只存低8位。你的C语言基础要补补呀。
 楼主| jianhong_wu 发表于 2014-11-18 16:27 | 显示全部楼层
无影行者 发表于 2014-11-17 22:26
你好鸿哥,在这片帖子中你在按键短发和连发的时候都把ucKeySec的值赋值为1,在led_run()中用了uiSetnumbe ...

效果很明显的。它是一步步的往上加,能看到很有节奏的。你如果在实物电路板上试过就知道。我所有的程序都亲自上板子上运行测试过的。
a15851404012 发表于 2014-11-18 20:18 | 显示全部楼层
jianhong_wu 发表于 2014-11-18 16:20
是的。int类型的变量直接给char类型的变量,char的变量只存低8位。你的C语言基础要补补呀。 ...

谢谢吴老师,刚开学习单片机,以前也没学过C:L能不能推荐下好的C教程,还有您的每一章节我都打印认真的反复的看,刚开始看的时候觉得觉得程序前面好多的变量,感觉很晃,不像别的视频里教的那么简单,可是越往后看越觉的明智,每个变量和宏定义都好美(不知道怎么表达),真心的赞,很适合我们初学者,感谢你的无私分享,还有很期待你的另一个C51教程的帖子:$
armxu 发表于 2014-11-18 21:11 来自手机 | 显示全部楼层
赶快出书吧!!!发打印出来好厚,只打印了一部分,二百多页。希望出书时程序也能打印,要分多本出,好出差时带着。
261854681 发表于 2014-11-19 08:06 来自手机 | 显示全部楼层
其实,吴工出书必定会火,大多数初学者会感受到吴工的**与真诚,至少,坛子里没发现第二人
xrg0228 发表于 2014-11-19 08:28 | 显示全部楼层
好好学习一下,谢谢楼主!
您需要登录后才可以回帖 登录 | 注册

本版积分规则

快速回复 在线客服 返回列表 返回顶部
0