第七十一节:液晶屏的字符,16点阵,24点阵和32点阵的显示程序。
开场白:
这一节要教会大家二个知识点:
第一个:如何利用任意点阵字体显示函数display_lattice来显示8x16的字符,16点阵汉字,24点阵汉字和32点阵汉字。
第二个:纠正上一节的一个小错误。C51编译器跟其它单片机的编译器有点不一样。想把常量数据保存在ROM程序存储区里并不是用const关键字,而是是用code关键字。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:开机上电后,可以看到液晶屏分别显示32点阵,24点阵和16点阵的“馒头”两个字,还有“V5”这两个8x16点阵的字符。
(3)源代码讲解如下:
#include "REG52.H" sbit LCDCS_dr = P1^6; //片选线 sbit LCDSID_dr = P1^7; //串行数据线 sbit LCDCLK_dr = P3^2; //串行时钟线 sbit LCDRST_dr = P3^4; //复位线 void SendByteToLcd(unsigned char ucData); //发送一个字节数据到液晶模块 void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块 void LCDWriteData(unsigned char ucData); //发送一个字节的数据给液晶模块 void LCDInit(void); //初始化 函数内部包括液晶模块的复位 void display_lattice(unsigned int x,unsigned int y,const unsigned char *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数 void display_clear(void); // 清屏 void delay_short(unsigned int uiDelayshort); //延时 /* 注释一: * 纠正上一节的一个小错误。C51编译器跟其它的编译器有点不一样。 * 存在ROM程序存储区里的常量数据并不是用const关键字,而是是用code关键字。 */ code unsigned char Hz3232_man[]= /*馒 横向取模 32x32点阵 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x07,0x03,0x00,0x0F,0x87,0xFF,0x80, 0x0F,0x07,0x03,0x80,0x0E,0x07,0x03,0x80,0x0E,0x37,0xFF,0x80,0x1C,0x7F,0x03,0x80, 0x1F,0xFF,0x03,0x80,0x18,0x77,0xFF,0x00,0x38,0xE0,0x00,0xC0,0x36,0xDF,0xFF,0xF0, 0x77,0x9C,0xCE,0xE0,0x67,0x1C,0xCE,0xE0,0xC7,0x1C,0xCE,0xE0,0x07,0x1C,0xCE,0xE0, 0x07,0x1F,0xFF,0xE0,0x07,0x18,0x00,0x00,0x07,0x00,0x03,0x80,0x07,0x0F,0xFF,0xC0, 0x07,0x71,0x8F,0x00,0x07,0xE0,0xDE,0x00,0x07,0xC0,0xFC,0x00,0x07,0x80,0x78,0x00, 0x0F,0x01,0xFE,0x00,0x07,0x03,0x8F,0xE0,0x00,0x1E,0x03,0xF0,0x00,0xF8,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz3232_tou[]= /*头 横向取模 32x32点阵 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0xC0,0x00,0x00,0x03,0xE0,0x00, 0x03,0xC3,0xC0,0x00,0x00,0xF3,0x80,0x00,0x00,0x7B,0x80,0x00,0x00,0x7B,0x80,0x00, 0x00,0x3B,0x80,0x00,0x0E,0x03,0x80,0x00,0x07,0x83,0x80,0x00,0x03,0xC3,0x80,0x00, 0x01,0xE3,0x80,0x00,0x01,0xE3,0x80,0x00,0x00,0xC3,0x80,0x00,0x00,0x03,0x81,0xE0, 0x7F,0xFF,0xFF,0xF0,0x00,0x07,0x80,0x30,0x00,0x07,0x00,0x00,0x00,0x07,0x80,0x00, 0x00,0x0E,0xE0,0x00,0x00,0x1E,0x7C,0x00,0x00,0x3C,0x1F,0x00,0x00,0x78,0x0F,0xC0, 0x00,0xF0,0x03,0xC0,0x03,0xC0,0x01,0xE0,0x0F,0x00,0x00,0xE0,0x78,0x00,0x00,0x00, 0x60,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz2424_man[]= /*馒 横向取模 24x24点阵 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x0C,0x18,0x30,0x1E,0x1F,0xF8,0x1C,0x1C,0x38,0x1C, 0x1F,0xF8,0x19,0xFC,0x38,0x3F,0xFF,0xF8,0x31,0x98,0x30,0x7B,0xE0,0x0E,0x6F,0x7F, 0xFE,0x6E,0x76,0xEE,0xCC,0x76,0xEE,0x0C,0x7F,0xFE,0x0C,0x70,0x0C,0x0C,0x00,0x38, 0x0C,0x3F,0xF8,0x0D,0xCE,0x70,0x0F,0x87,0xE0,0x0F,0x03,0x80,0x1E,0x07,0xE0,0x0C, 0x1C,0x7E,0x01,0xF0,0x1F,0x00,0x00,0x00, }; code unsigned char Hz2424_tou[]= /*头 横向取模 24x24点阵 */ { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x0E,0x00,0x06,0x0F,0x00,0x07,0x8E,0x00,0x01, 0xEE,0x00,0x00,0xEE,0x00,0x00,0xEC,0x00,0x1C,0x0C,0x00,0x0F,0x0C,0x00,0x07,0x9C, 0x00,0x03,0x9C,0x00,0x00,0x1C,0x0C,0x00,0x1C,0x1E,0x7F,0xFF,0xF6,0x00,0x1C,0x00, 0x00,0x3C,0x00,0x00,0x3F,0x80,0x00,0x71,0xE0,0x00,0xE0,0xF8,0x01,0xC0,0x3C,0x07, 0x00,0x1C,0x3C,0x00,0x0C,0x70,0x00,0x00, }; code unsigned char Hz1616_man[]= /*馒 横向取模 16X16点阵 */ { 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94, 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00, }; code unsigned char Hz1616_tou[]= /*头 横向取模 16X16点阵 */ { 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80, 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08, }; code unsigned char Zf816_V[]= /*V 横向取模 8x16点阵 */ { 0x00,0x00,0x00,0xE7,0x42,0x42,0x44,0x24,0x24,0x28,0x28,0x18,0x10,0x10,0x00,0x00, }; code unsigned char Zf816_5[]= /*5 横向取模 8x16点阵 */ { 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; void main() { LCDInit(); //初始化12864 内部包含液晶模块的复位 display_clear(); // 清屏 display_lattice(0,0,Hz3232_man,0,4,32); //显示32点阵的<馒>字 display_lattice(2,0,Hz3232_tou,0,4,32); //显示32点阵的<头>字 display_lattice(4,0,Hz2424_man,0,3,24); //显示24点阵的<馒>字 display_lattice(6,0,Hz2424_tou,0,3,24); //显示24点阵的<头>字 display_lattice(8,0,Hz1616_man,0,2,16); //显示16点阵的<馒>字 display_lattice(9,0,Hz1616_tou,0,2,16); //显示16点阵的<头>字 display_lattice(11,0,Zf816_V,0,1,16); //显示8x16点阵的<V>字符 display_lattice(12,0,Zf816_5,0,1,16); //显示8x16点阵的<5>字符 while(1) { ; } } void display_clear(void) // 清屏 { unsigned char x,y; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 y=0; while(y<32) //y轴的范围0至31 { WriteCommand(y+0x80); //垂直地址 WriteCommand(0x80); //水平地址 for(x=0;x<32;x++) //256个横向点,有32个字节 { LCDWriteData(0x00); } y++; } WriteCommand(0x36); //开显示缓冲指令 } /* 注释二:本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31. * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 */ 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 j=0; unsigned int i=0; unsigned char ucTemp; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横 { WriteCommand(y+j+0x80); //垂直地址 WriteCommand(x+0x80); //水平地址 for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列 { ucTemp=ucArray[j*x_amount+i]; if(ucFbFlag==1) //反白显示 { ucTemp=~ucTemp; } LCDWriteData(ucTemp); // delay_short(30000); //把上一节这个延时函数去掉,加快刷屏速度 } } WriteCommand(0x36); //开显示缓冲指令 } void SendByteToLcd(unsigned char ucData) //发送一个字节数据到液晶模块 { unsigned char i; for ( i = 0; i < 8; i++ ) { if ( (ucData << i) & 0x80 ) { LCDSID_dr = 1; } else { LCDSID_dr = 0; } LCDCLK_dr = 0; LCDCLK_dr = 1; } } void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 { SendByteToLcd( 0xf8 + (ucWRS << 1) ); SendByteToLcd( ucWData & 0xf0 ); SendByteToLcd( (ucWData << 4) & 0xf0); } void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucCommand, 0); delay_short(90); } void LCDWriteData(unsigned char ucData) //发送一个字节的数据给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucData, 1); } void LCDInit(void) //初始化 函数内部包括液晶模块的复位 { LCDRST_dr = 1; //复位 LCDRST_dr = 0; LCDRST_dr = 1; } void delay_short(unsigned int uiDelayShort) //延时函数 { unsigned int i; for(i=0;i<uiDelayShort;i++) { ; } }
总结陈词:
我们现在讲的字体显示都是横向的,如果某个项目要把整个液晶屏顺时针旋转90度,要求像对联一样纵向显示一串字体的时候,该怎么办?我前两个月就遇到了这样的项目,当时我的做法就是把字体的字库数组通过算法旋转90度就达到了目的。这种算法程序是怎样编写的?欲知详情,请听下回分解-----把字体顺时针旋转90度显示的算法程序。
(未完待续,下节更精彩,不要走开哦)
第七十二节:在液晶屏中把字体顺时针旋转90度显示的算法程序。
开场白:
我曾经遇到过这样的项目,客户由于外壳结果的原因,故意把液晶屏物理位置逆时针旋转了90度,在这种情况下,如果按之前的显示驱动就会发现字体也跟着倒了过来,影响了阅读。当时我的解决办法就是把字体的字库数组通过算法顺时针旋转90度就达到了目的。这一节把这个算法教给大家。
这个算法的本质是:请看以下附图1,附图2,附图3.
第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。先把代表每一行16个点阵数的2个char型数据合并成1个int型数据。
第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:把液晶屏物理位置逆时针旋转了90度,开机上电后,可以看到液晶屏像对联的显示顺序一样,从上往下分别显示“馒头V5”四个字。
(3)源代码讲解如下:
#include "REG52.H" sbit LCDCS_dr = P1^6; //片选线 sbit LCDSID_dr = P1^7; //串行数据线 sbit LCDCLK_dr = P3^2; //串行时钟线 sbit LCDRST_dr = P3^4; //复位线 void SendByteToLcd(unsigned char ucData); //发送一个字节数据到液晶模块 void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块 void LCDWriteData(unsigned char ucData); //发送一个字节的数据给液晶模块 void LCDInit(void); //初始化 函数内部包括液晶模块的复位 void display_lattice(unsigned int x,unsigned int y,const unsigned char *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数 void display_clear(void); // 清屏 void hz1616_s90(const unsigned char *p_ucHz,unsigned char *p_ucResult); //把16x16汉字字模顺时针旋转90度的转换函数 void hz816_s90(const unsigned char *p_ucHz,unsigned char *p_ucResult); //把8x16字符字模顺时针旋转90度的转换函数 void delay_short(unsigned int uiDelayshort); //延时 code unsigned char Hz1616_man[]= /*馒 横向取模 16X16点阵 */ { 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94, 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00, }; code unsigned char Hz1616_tou[]= /*头 横向取模 16X16点阵 */ { 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80, 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08, }; code unsigned char Zf816_V[]= /*V 横向取模 8x16点阵 */ { 0x00,0x00,0x00,0xE7,0x42,0x42,0x44,0x24,0x24,0x28,0x28,0x18,0x10,0x10,0x00,0x00, }; code unsigned char Zf816_5[]= /*5 横向取模 8x16点阵 */ { 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; unsigned char ucBufferResult[32]; //用于临时存放转换结束后的字模数组 void main() { LCDInit(); //初始化12864 内部包含液晶模块的复位 display_clear(); // 清屏 /* 注释一: * (1)把原来的液晶屏物理位置逆时针旋转90度后,从上往下阅读,类似对联的阅读习惯。所以请注意坐标体系参数的变化。 * (2)为了让字符居中显示,请注意在显示V和5两个字符时坐标体系的变化。 * (3)字符8x16经过旋转处理后,变成了16x8,在调用display_lattice函数时,要注意修改响应的参数。 */ hz1616_s90(Hz1616_man,ucBufferResult); //把<馒>字顺时针旋转90度放到ucBufferResult临时变量里。 display_lattice(7,0,ucBufferResult,0,2,16); //显示旋转90度后的<馒>字 hz1616_s90(Hz1616_tou,ucBufferResult); //把<头>字顺时针旋转90度放到ucBufferResult临时变量里。 display_lattice(6,0,ucBufferResult,0,2,16); //显示旋转90度后的<头>字 hz816_s90(Zf816_V,ucBufferResult); //把<V>字符顺时针旋转90度放到ucBufferResult临时变量里。 display_lattice(5,4,ucBufferResult,0,2,8); //显示旋转90度后的<V>字符。注意在最后两个个参数,2表示每一行有2个字节,8表示8列。第二个坐标参数4是为了偏移居中显示。 hz816_s90(Zf816_5,ucBufferResult); //把<5>字符顺时针旋转90度放到ucBufferResult临时变量里。 display_lattice(4,4,ucBufferResult,0,2,8); //显示旋转90度后的<5>字符。注意在最后两个个参数,2表示每一行有2个字节,8表示8列。第二个坐标参数4是为了偏移居中显示。 while(1) { ; } } void display_clear(void) // 清屏 { unsigned char x,y; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 y=0; while(y<32) //y轴的范围0至31 { WriteCommand(y+0x80); //垂直地址 WriteCommand(0x80); //水平地址 for(x=0;x<32;x++) //256个横向点,有32个字节 { LCDWriteData(0x00); } y++; } WriteCommand(0x36); //开显示缓冲指令 } /* 注释二: * 把16x16汉字字模顺时针旋转90度的步骤:请看附图1,附图2,附图3. * 第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。先把代表每一行16个点阵数的2个char型数据合并成1个int型数据。 * 第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。以下程序int型数据每取8个数据的最高位, * 就左移一次,本质就是纵向取模的过程。 */ void hz1616_s90(const unsigned char *p_ucHz,unsigned char *p_ucResult) //把16x16汉字字模顺时针旋转90度的转换函数 { unsigned char a; unsigned char b; unsigned char c; unsigned int uiBuffer[16]; //注意,是int类型数据,一个数据包含2个字节。 for(a=0;a<16;a++) //把原来以字节为单位的字库每一行的2个字节合并成1个int型数据。放到一个包含16个int类型的数组里,为旋转90度算法处理做准备 { uiBuffer[a]=p_ucHz[a*2]; uiBuffer[a]=uiBuffer[a]<<8; uiBuffer[a]=uiBuffer[a]+p_ucHz[a*2+1]; } c=0; for(a=0;a<16;a++) //这里的16代表16列 { for(b=0;b<8;b++) //每一列中有16个点,有2个字节,这里的8代表第一个字节的8个位或点。 { p_ucResult[c]=p_ucResult[c]<<1; p_ucResult[c]=p_ucResult[c]&0xfe; if(uiBuffer[15-b]>=0x8000) //注意,int类型数据的判断是0x8000,char型的是0x80 { p_ucResult[c]=p_ucResult[c]+1; } uiBuffer[15-b]=uiBuffer[15-b]<<1; } c++; for(b=0;b<8;b++) //每一列中有16个点,有2个字节,这里的8代表第二个字节的8个位或点。 { p_ucResult[c]=p_ucResult[c]<<1; p_ucResult[c]=p_ucResult[c]&0xfe; if(uiBuffer[7-b]>=0x8000) { p_ucResult[c]=p_ucResult[c]+1; } uiBuffer[7-b]=uiBuffer[7-b]<<1; } c++; } } /* 注释三: * 把8x16字符字模顺时针旋转90度的步骤: * 第一步:旋转90度的本质,就是把原来横向取模改成纵向去模。由于原来的字库存放在带code关键字的ROM区,只能读不能写,所以 * 先把原来的字模数组读取出来,放到一个变量缓冲区里。 * 第二步:再把每一列的16个点阵按2个字节分别取到一个数组里,就是纵向取模的过程了。以下程序int型数据每取8个数据的最高位, 就左移一次,本质就是纵向取模的过程。 */ void hz816_s90(const unsigned char *p_ucHz,unsigned char *p_ucResult) //把8x16字符字模顺时针旋转90度的转换函数 { unsigned char a; unsigned char b; unsigned char c; unsigned char uiBuffer[16]; //注意,跟16x16点阵不一样,这里是char数据。因为横向的只有8个点 for(a=0;a<16;a++) //把存放在ROM的字库放到一个16个char类型的数组里 { uiBuffer[a]=p_ucHz[a]; } c=0; for(a=0;a<8;a++) //这里的8代表8列 { for(b=0;b<8;b++) //每一列中有16个点,有2个字节,这里的8代表第一个字节的8个位或点。 { p_ucResult[c]=p_ucResult[c]<<1; p_ucResult[c]=p_ucResult[c]&0xfe; if(uiBuffer[15-b]>=0x80) //注意,int类型数据的判断是0x8000,char型的是0x80 { p_ucResult[c]=p_ucResult[c]+1; } uiBuffer[15-b]=uiBuffer[15-b]<<1; } c++; for(b=0;b<8;b++) //每一列中有16个点,有2个字节,这里的8代表第二个字节的8个位或点。 { p_ucResult[c]=p_ucResult[c]<<1; p_ucResult[c]=p_ucResult[c]&0xfe; if(uiBuffer[7-b]>=0x80) //注意,int类型数据的判断是0x8000,char型的是0x80 { p_ucResult[c]=p_ucResult[c]+1; } uiBuffer[7-b]=uiBuffer[7-b]<<1; } c++; } } /* 注释四:本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31. * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 */ 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 j=0; unsigned int i=0; unsigned char ucTemp; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横 { WriteCommand(y+j+0x80); //垂直地址 WriteCommand(x+0x80); //水平地址 for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列 { ucTemp=ucArray[j*x_amount+i]; if(ucFbFlag==1) //反白显示 { ucTemp=~ucTemp; } LCDWriteData(ucTemp); // delay_short(30000); //把上一节这个延时函数去掉,加快刷屏速度 } } WriteCommand(0x36); //开显示缓冲指令 } void SendByteToLcd(unsigned char ucData) //发送一个字节数据到液晶模块 { unsigned char i; for ( i = 0; i < 8; i++ ) { if ( (ucData << i) & 0x80 ) { LCDSID_dr = 1; } else { LCDSID_dr = 0; } LCDCLK_dr = 0; LCDCLK_dr = 1; } } void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 { SendByteToLcd( 0xf8 + (ucWRS << 1) ); SendByteToLcd( ucWData & 0xf0 ); SendByteToLcd( (ucWData << 4) & 0xf0); } void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucCommand, 0); delay_short(90); } void LCDWriteData(unsigned char ucData) //发送一个字节的数据给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucData, 1); } void LCDInit(void) //初始化 函数内部包括液晶模块的复位 { LCDRST_dr = 1; //复位 LCDRST_dr = 0; LCDRST_dr = 1; } void delay_short(unsigned int uiDelayShort) //延时函数 { unsigned int i; for(i=0;i<uiDelayShort;i++) { ; } }
总结陈词:
有的项目会要求把字体或者图像进行镜像显示处理,这种算法程序是怎样编写的?欲知详情,请听下回分解-----在液晶屏中把字体镜像显示的算法程序。
(未完待续,下节更精彩,不要走开哦)
第七十三节:在液晶屏中把字体镜像显示的算法程序。
开场白:
有的项目会要求把字体或者图像进行镜像显示处理,这一节把这个算法教给大家。
这个算法的本质是:
16x16点阵的图像或者字体有16行,每行有2个字节,如果把这2个字节看成是一个16位int型数据,那么就是要这个数据从原来左边是高位,右边是低位的顺序颠倒过来。本程序没有把2个字节合并成一个int型数据,而是直接在一个字节数据内把高低位顺序颠倒过来,然后把第1字节数据跟第2字节数据交换。
8x16点阵的图像或者字体有16行,每行有1个字节,把这个数据从原来左边是高位,右边是低位的顺序颠倒过来。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:开机上电后,从上往下分别显示“馒头V5”四个字以及右边镜像后的“馒头V5”四个字。
(3)源代码讲解如下:
#include "REG52.H" sbit LCDCS_dr = P1^6; //片选线 sbit LCDSID_dr = P1^7; //串行数据线 sbit LCDCLK_dr = P3^2; //串行时钟线 sbit LCDRST_dr = P3^4; //复位线 void SendByteToLcd(unsigned char ucData); //发送一个字节数据到液晶模块 void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块 void LCDWriteData(unsigned char ucData); //发送一个字节的数据给液晶模块 void LCDInit(void); //初始化 函数内部包括液晶模块的复位 void display_lattice(unsigned int x,unsigned int y,const unsigned char *ucArray,unsigned char ucFbFlag,unsigned int x_amount,unsigned int y_amount); //显示任意点阵函数 void display_clear(void); // 清屏 void hz1616_mirror(const unsigned char *p_ucHz,unsigned char *p_ucResult); //把16x16点阵字库镜像 void hz816_mirror(const unsigned char *p_ucHz,unsigned char *p_ucResult); //把8x16点阵字库镜像 void delay_short(unsigned int uiDelayshort); //延时 code unsigned char Hz1616_man[]= /*馒 横向取模 16X16点阵 */ { 0x21,0xF8,0x21,0x08,0x21,0xF8,0x3D,0x08,0x45,0xF8,0x48,0x00,0x83,0xFC,0x22,0x94, 0x23,0xFC,0x20,0x00,0x21,0xF8,0x20,0x90,0x28,0x60,0x30,0x90,0x23,0x0E,0x00,0x00, }; code unsigned char Hz1616_tou[]= /*头 横向取模 16X16点阵 */ { 0x00,0x80,0x10,0x80,0x0C,0x80,0x04,0x80,0x10,0x80,0x0C,0x80,0x08,0x80,0x00,0x80, 0xFF,0xFE,0x00,0x80,0x01,0x40,0x02,0x20,0x04,0x30,0x08,0x18,0x10,0x0C,0x20,0x08, }; code unsigned char Zf816_V[]= /*V 横向取模 8x16点阵 */ { 0x00,0x00,0x00,0xE7,0x42,0x42,0x44,0x24,0x24,0x28,0x28,0x18,0x10,0x10,0x00,0x00, }; code unsigned char Zf816_5[]= /*5 横向取模 8x16点阵 */ { 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; unsigned char ucBufferResult[32]; //用于临时存放转换结束后的字模数组 void main() { LCDInit(); //初始化12864 内部包含液晶模块的复位 display_clear(); // 清屏 display_lattice(0,0,Hz1616_man,0,2,16); //显示镜像前的<馒>字 hz1616_mirror(Hz1616_man,ucBufferResult); //把<馒>字镜像后放到ucBufferResult临时变量里。 display_lattice(1,0,ucBufferResult,0,2,16); //显示镜像后的<馒>字 display_lattice(0,16,Hz1616_tou,0,2,16); //显示镜像前的<头>字 hz1616_mirror(Hz1616_tou,ucBufferResult); //把<头>字镜像后放到ucBufferResult临时变量里。 display_lattice(1,16,ucBufferResult,0,2,16); //显示镜像后的<头>字 display_lattice(8,0,Zf816_V,0,1,16); //显示镜像前的<V>字符 hz816_mirror(Zf816_V,ucBufferResult); //把<V>字符镜像后放到ucBufferResult临时变量里。 display_lattice(9,0,ucBufferResult,0,1,16); //显示镜像后的<V>字符 display_lattice(8,16,Zf816_5,0,1,16); //显示镜像前的<5>字符 hz816_mirror(Zf816_5,ucBufferResult); //把<5>字符镜像后放到ucBufferResult临时变量里。 display_lattice(9,16,ucBufferResult,0,1,16); //显示镜像后的<5>字符 while(1) { ; } } void display_clear(void) // 清屏 { unsigned char x,y; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 y=0; while(y<32) //y轴的范围0至31 { WriteCommand(y+0x80); //垂直地址 WriteCommand(0x80); //水平地址 for(x=0;x<32;x++) //256个横向点,有32个字节 { LCDWriteData(0x00); } y++; } WriteCommand(0x36); //开显示缓冲指令 } /* 注释一: * 16x16点阵镜像的本质: * 16x16点阵有16行,每行有2个字节,如果把这2个字节看成是一个16位int型数据, * 那么就是要这个数据从原来左边是高位,右边是低位的顺序颠倒过来。本程序没有把2个字节 * 合并成一个int型数据,而是直接在一个字节数据内把高低位顺序颠倒过来,然后把第1字节数据跟第2字节数据交换。 */ void hz1616_mirror(const unsigned char *p_ucHz,unsigned char *p_ucResult) //把16x16点阵字库镜像的函数 { unsigned char a; unsigned char b; unsigned char c; unsigned char d; for(a=0;a<16;a++) //这里16代表有16行。每一行有2个字节。把每一个字节看做一列,这里先把第1列字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。 { b=p_ucHz[a*2+0]; //这里的2代表16x16点阵每行有2列字节,0代表从第1列开始。 c=0; for(d=0;d<8;d++) //把一个字节调换顺序 { c=c>>1; if((b&0x80)==0x80) { c=c|0x80; } b=b<<1; } p_ucResult[a*2+1]=c; //注意,因为是镜像,所以要把颠倒顺序后的字节从原来是第1列的调换到第2列 } for(a=0;a<16;a++) //这里16代表有16行。每一行有2个字节。把每一个字节看做一列,这里先把第2列字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。 { b=p_ucHz[a*2+1]; //这里的2代表16x16点阵每行有2列字节,1代表从第2列开始。 c=0; for(d=0;d<8;d++) //把一个字节调换顺序 { c=c>>1; if((b&0x80)==0x80) { c=c|0x80; } b=b<<1; } p_ucResult[a*2+0]=c; //注意,因为是镜像,所以要把颠倒顺序后的字节从原来是第2列的调换到第1列 } } /* 注释二: * 8x16点阵镜像的本质: * 8x16点阵有16行,每行有1个字节,把这个数据从原来左边是高位,右边是低位的顺序颠倒过来。 */ void hz816_mirror(const unsigned char *p_ucHz,unsigned char *p_ucResult) //把8x16点阵字库镜像的函数 { unsigned char a; unsigned char b; unsigned char c; unsigned char d; for(a=0;a<16;a++) //这里16代表有16行。每一行有1个字节。这里先把每一行字节的数据从原来左边是高位,右边是低位的顺序颠倒过来,相当于镜像。 { b=p_ucHz[a*1+0]; //这里的1代表8x16点阵每行有1列字节,0代表从第1列开始。 c=0; for(d=0;d<8;d++) //把一个字节调换顺序 { c=c>>1; if((b&0x80)==0x80) { c=c|0x80; } b=b<<1; } p_ucResult[a*1+0]=c; //注意,因为每一行只有一列,所以不用像16x16点阵那样把第1列跟第2列对调交换。 } } /* 注释三:本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31. * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 */ 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 j=0; unsigned int i=0; unsigned char ucTemp; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横 { WriteCommand(y+j+0x80); //垂直地址 WriteCommand(x+0x80); //水平地址 for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列 { ucTemp=ucArray[j*x_amount+i]; if(ucFbFlag==1) //反白显示 { ucTemp=~ucTemp; } LCDWriteData(ucTemp); // delay_short(30000); //把上一节这个延时函数去掉,加快刷屏速度 } } WriteCommand(0x36); //开显示缓冲指令 } void SendByteToLcd(unsigned char ucData) //发送一个字节数据到液晶模块 { unsigned char i; for ( i = 0; i < 8; i++ ) { if ( (ucData << i) & 0x80 ) { LCDSID_dr = 1; } else { LCDSID_dr = 0; } LCDCLK_dr = 0; LCDCLK_dr = 1; } } void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 { SendByteToLcd( 0xf8 + (ucWRS << 1) ); SendByteToLcd( ucWData & 0xf0 ); SendByteToLcd( (ucWData << 4) & 0xf0); } void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucCommand, 0); delay_short(90); } void LCDWriteData(unsigned char ucData) //发送一个字节的数据给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucData, 1); } void LCDInit(void) //初始化 函数内部包括液晶模块的复位 { LCDRST_dr = 1; //复位 LCDRST_dr = 0; LCDRST_dr = 1; } void delay_short(unsigned int uiDelayShort) //延时函数 { unsigned int i; for(i=0;i<uiDelayShort;i++) { ; } }
总结陈词:
细心的网友一定会发现,这种12864液晶屏普遍有个毛病,在坐标轴x,y方向上不能完全做到以一个点阵为单位进行随心所欲的显示,比如横向的至少是一个字节8个点阵为单位,而第1,2行跟第3,4行又做不到无缝对接显示,假如我要把汉字一半显示在第2行一半显示在第3行,行不行?当然可以。但是需要我们编写额外的算法程序。这种算法程序是怎样编写的?欲知详情,请听下回分解-----在液晶屏中让字体可以跨区域无缝对接显示的算法程序。
(未完待续,下节更精彩,不要走开哦)
第七十四节:在液晶屏中让字体可以跨区域无缝对接显示的算法程序。
开场白:
细心的网友会发现,这种12864液晶屏在显示自造字库时普遍有个毛病,在坐标轴x方向上是以每16个点阵为一个单位的,如果显示两个8x16字符”V”和”5”,虽然它们的x坐标轴是相邻的,但是实际显示的效果是中间隔了8个点阵。另外,这种12864液晶屏是由上半屏和下半屏组成的,软件上的坐标体系并没有做到跟物理的坐标体系一致,需要转换的。如果我们想把一个整体字符的一半显示在上半屏,另一半显示在下半屏,那怎么办?
这一节就要教给大家这个算法程序:
为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,就可以达到跨区域无缝显示的目的。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:开机上电后,看到液晶屏所有的点阵都显示。正中间露出一小方块空白的32x16点阵画布,从左到右分别显示“V5”两个字符。这两个字符是紧紧挨在一起的,中间并没有8个点阵的空格,同时这两个字符的上半部分显示在上半屏,下半部分显示在下半屏。实现了真正的跨区域无缝对接显示。
(3)源代码讲解如下:
#include "REG52.H" sbit LCDCS_dr = P1^6; //片选线 sbit LCDSID_dr = P1^7; //串行数据线 sbit LCDCLK_dr = P3^2; //串行时钟线 sbit LCDRST_dr = P3^4; //复位线 void SendByteToLcd(unsigned char ucData); //发送一个字节数据到液晶模块 void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块 void LCDWriteData(unsigned char ucData); //发送一个字节的数据给液晶模块 void LCDInit(void); //初始化 函数内部包括液晶模块的复位 void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00 全部显示点阵用0xff 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);//把字模插入画布. 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); //显示任意点阵函数 void delay_short(unsigned int uiDelayshort); //延时 code unsigned char Zf816_V[]= /*V 横向取模 8x16点阵 每一行只要1个字节,共16行 */ { 0x00, 0x00, 0x00, 0xE7, 0x42, 0x42, 0x44, 0x24, 0x24, 0x28, 0x28, 0x18, 0x10, 0x10, 0x00, 0x00, }; code unsigned char Zf816_5[]= /*5 横向取模 8x16点阵 每一行只要1个字节,共16行 */ { 0x00, 0x00, 0x00, 0x7E, 0x40, 0x40, 0x40, 0x58, 0x64, 0x02, 0x02, 0x42, 0x44, 0x38, 0x00, 0x00, }; /* 注释一: * 为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组, * 就可以达到跨区域无缝显示的目的。根据上几节的介绍,12864液晶屏由上下两半屏组成,以下这块画布 * 显示在上半屏和下半屏之间。横向4个字节,纵向16行。其中上半屏显示8行,下半屏显示8行。注意,这个数组 * 不带code关键字,是全局变量,这样可读可写。画布的横向x坐标范围是0至3,因为画布的横向只要4个字节。 * 画布的纵向y坐标范围是0至15,因为画布的纵向只有16行。 */ unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00 { 0x00,0x00,0x00,0x00, //上半屏 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, //------------上半屏和下半屏的分割线----------- 0x00,0x00,0x00,0x00, //下半屏 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, }; void main() { LCDInit(); //初始化12864 内部包含液晶模块的复位 display_clear(0xff); // 清屏 全部显示空填充0x00 全部显示点阵用0xff insert_buffer_to_canvas(0,0,Zf816_V,0,1,16);//把<V>的字模插入画布 insert_buffer_to_canvas(1,0,Zf816_5,0,1,16);//把<5>的字模插入画布 display_lattice(3,24,ucCanvasBuffer,0,4,8,0); //显示上半屏的画布,最后的参数0是偏移量 display_lattice(11,0,ucCanvasBuffer,0,4,8,32); //显示下半屏的画布,最后的参数32是偏移量 while(1) { ; } } void display_clear(unsigned char ucFillDate) // 清屏 全部显示空填充0x00 全部显示点阵用0xff { unsigned char x,y; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 y=0; while(y<32) //y轴的范围0至31 { WriteCommand(y+0x80); //垂直地址 WriteCommand(0x80); //水平地址 for(x=0;x<32;x++) //256个横向点,有32个字节 { LCDWriteData(ucFillDate); } y++; } WriteCommand(0x36); //开显示缓冲指令 } /* 注释二: * 把字模插入画布的函数. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是在画布中的坐标体系。 * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。 * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 */ 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) { unsigned int j=0; unsigned int i=0; unsigned char ucTemp; for(j=0;j<y_amount;j++) { for(i=0;i<x_amount;i++) { ucTemp=ucArray[j*x_amount+i]; if(ucFbFlag==0) { ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节 } else { ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节 } } } } /* 注释三: * 显示任意点阵函数. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。 * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31. * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。 */ 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) { unsigned int j=0; unsigned int i=0; unsigned char ucTemp; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横 { WriteCommand(y+j+0x80); //垂直地址 WriteCommand(x+0x80); //水平地址 for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列 { ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址 if(ucFbFlag==1) //反白显示 { ucTemp=~ucTemp; } LCDWriteData(ucTemp); // delay_short(30000); //把上一节这个延时函数去掉,加快刷屏速度 } } WriteCommand(0x36); //开显示缓冲指令 } void SendByteToLcd(unsigned char ucData) //发送一个字节数据到液晶模块 { unsigned char i; for ( i = 0; i < 8; i++ ) { if ( (ucData << i) & 0x80 ) { LCDSID_dr = 1; } else { LCDSID_dr = 0; } LCDCLK_dr = 0; LCDCLK_dr = 1; } } void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 { SendByteToLcd( 0xf8 + (ucWRS << 1) ); SendByteToLcd( ucWData & 0xf0 ); SendByteToLcd( (ucWData << 4) & 0xf0); } void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucCommand, 0); delay_short(90); } void LCDWriteData(unsigned char ucData) //发送一个字节的数据给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucData, 1); } void LCDInit(void) //初始化 函数内部包括液晶模块的复位 { LCDRST_dr = 1; //复位 LCDRST_dr = 0; LCDRST_dr = 1; } void delay_short(unsigned int uiDelayShort) //延时函数 { unsigned int i; for(i=0;i<uiDelayShort;i++) { ; } }
总结陈词:
经过这一节的算法处理后,字符终于可以在x轴上紧紧挨着显示了。也就是把原来x坐标是16个点阵为一个单位,改成了以8个点阵为一个单位。如果要求以1个点阵为单位显示,那该怎么办?这个还真有点难度,因为横向的最小显示单位就是一个字节8个点,不过鸿哥在下一节中照样有办法实现这个功能。欲知详情,请听下回分解-----在12864液晶屏中让字体以1个点阵为单位进行移动显示的算法程序。
(未完待续,下节更精彩,不要走开哦)
第七十五节:在12864液晶屏中让字体以1个点阵为单位进行移动显示的算法程序。
开场白:
假设有一个固定的四方形透明窗口,在窗口里面放了一张画布,只要想办法让这个画布
往右边拖动,那么画布里面的内容就会跟着画布整体往右边移动,这个就是能以1个点阵为单位进行移动显示的本质。同理,这个画布有16行,每行有4个字节,我们只要把每行4个字节看作是一个首尾连接的二进制数据,把每一行的二进制数据每次整体往右边移动一位,就相当于移动一个点阵了。这一节就要把这个算法教给大家。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:开机上电后,能看到正中间显示的两个字符“V5”整体以1个点阵为单位向右边慢慢移动。
(3)源代码讲解如下:
#include "REG52.H"
#define const_MoveTime 400 //每移动一位后的延时时间
sbit LCDCS_dr = P1^6; //片选线
sbit LCDSID_dr = P1^7; //串行数据线
sbit LCDCLK_dr = P3^2; //串行时钟线
sbit LCDRST_dr = P3^4; //复位线
void SendByteToLcd(unsigned char ucData); //发送一个字节数据到液晶模块
void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块
void LCDWriteData(unsigned char ucData); //发送一个字节的数据给液晶模块
void LCDInit(void); //初始化 函数内部包括液晶模块的复位
void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00 全部显示点阵用0xff
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);//把字模插入画布.
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); //显示任意点阵函数
void delay_short(unsigned int uiDelayshort); //延时
void move_service(void); //整体画布移动的应用程序
void lcd_display_service(void); //应用层面的液晶屏显示程序
void move_canvas_to_one_bit(void); //把画布整体往右边移动一个点阵
void clear_all_canvas(void); //把画布全部清零
void T0_time(void); //定时中断函数
code unsigned char Zf816_V[]= /*V 横向取模 8x16点阵 每一行只要1个字节,共16行 */
{
0x00,
0x00,
0x00,
0xE7,
0x42,
0x42,
0x44,
0x24,
0x24,
0x28,
0x28,
0x18,
0x10,
0x10,
0x00,
0x00,
};
code unsigned char Zf816_5[]= /*5 横向取模 8x16点阵 每一行只要1个字节,共16行 */
{
0x00,
0x00,
0x00,
0x7E,
0x40,
0x40,
0x40,
0x58,
0x64,
0x02,
0x02,
0x42,
0x44,
0x38,
0x00,
0x00,
};
/* 注释一:
* 为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组,
* 就可以达到跨区域无缝显示的目的。根据上几节的介绍,12864液晶屏由上下两半屏组成,以下这块画布
* 显示在上半屏和下半屏之间。横向4个字节,纵向16行。其中上半屏显示8行,下半屏显示8行。注意,这个数组
* 不带code关键字,是全局变量,这样可读可写。画布的横向x坐标范围是0至3,因为画布的横向只要4个字节。
* 画布的纵向y坐标范围是0至15,因为画布的纵向只有16行。
*/
unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00
{
0x00,0x00,0x00,0x00, //上半屏
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
//------------上半屏和下半屏的分割线-----------
0x00,0x00,0x00,0x00, //下半屏
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,
};
unsigned char ucDisplayUpdate=1; //更新显示变量
unsigned char ucMoveStepReset=0; //这个变量是为了方便外部程序初始化应用程序内部后缀为step的步骤变量
unsigned char ucMoveTimeStart=0; //定时器的开关标志 也相当于原子锁或互斥量的功能
unsigned int uiMoveTime=0; //定时器累计时间
void main()
{
LCDInit(); //初始化12864 内部包含液晶模块的复位
display_clear(0xff); // 清屏 全部显示空填充0x00 全部显示点阵用0xff
TMOD=0x01; //设置定时器0为工作方式1
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
EA=1; //开总中断
ET0=1; //允许定时中断
TR0=1; //启动定时中断
while(1)
{
move_service(); //整体画布移动的应用程序
lcd_display_service(); //应用层面的液晶屏显示程序
}
}
void move_service(void) //整体画布移动的应用程序
{
static unsigned char ucMoveStep=0; //运行步骤。前面加关键字static表示上电后这个变量只初始化一次,以后每次进出函数此变量不会重新初始化,保存之前的更改数值不变。
static unsigned char ucMoveCnt=0; //统计当前已经往左边移动了多少位。关键字static表示此变量上电后只初始化一次,不会每次进入函数都初始化。
if(ucMoveStepReset==1) //运行步骤的复位标志,此段代码结构方便外部程序初始化函数内部的步骤变量ucMoveStep
{
ucMoveStepReset=0; //及时把复位标志清零。避免一直处于复位的状态、
ucMoveStep=0; //运行步骤变量被外部程序通过复位标志初始化。
}
switch(ucMoveStep)
{
case 0:
clear_all_canvas(); //把画布全部清零
insert_buffer_to_canvas(0,0,Zf816_V,0,1,16);//把<V>的字模插入画布
insert_buffer_to_canvas(1,0,Zf816_5,0,1,16);//把<5>的字模插入画布
ucDisplayUpdate=1; //更新液晶屏显示
uiMoveTime=0; //定时器清零
ucMoveTimeStart=1; //开定时器 也相当于原子锁或互斥量的功能
ucMoveCnt=0; //统计当前已经往左边移动了多少位
ucMoveStep=1; //切换到下一个运行步骤
break;
case 1:
if(uiMoveTime>const_MoveTime) //延时一定的时间后
{
ucMoveTimeStart=0; //关定时器 也相当于原子锁或互斥量的功能
uiMoveTime=0; //定时器清零
if(ucMoveCnt<16)
{
ucMoveCnt++;
move_canvas_to_one_bit(); //把画布整体往左边移动一个点阵
ucDisplayUpdate=1; //更新液晶屏显示
ucMoveTimeStart=1; //开定时器 也相当于原子锁或互斥量的功能
}
else
{
ucMoveStep=0; //移动了16个点阵后,返回上一个运行步骤,把字模重新插入画布
}
}
break;
}
}
void lcd_display_service(void) //应用层面的液晶屏显示程序
{
if(ucDisplayUpdate==1) //需要更新显示
{
ucDisplayUpdate=0; //及时把标志清零,避免一直处于不断更新的状态。
display_lattice(3,24,ucCanvasBuffer,0,4,8,0); //显示上半屏的画布,最后的参数0是偏移量
display_lattice(11,0,ucCanvasBuffer,0,4,8,32); //显示下半屏的画布,最后的参数32是偏移量
}
}
/* 注释二:
* 假设有一个固定的四方形透明窗口,在窗口里面放了一张画布,只要想办法让这个画布
* 往右边拖动,那么画布里面的内容就会跟着画布整体往右边移动,这个就是能以1个点阵为单位进行移动显示的本质。
* 同理,这个画布有16行,每行有4个字节,我们只要把每行4个字节看作是一个首尾连接的二进制数据,
* 把每一行的二进制数据每次整体往右边移动一位,就相当于移动一个点阵了。
*/
void move_canvas_to_one_bit(void) //把画布整体往右边移动一个点阵
{
unsigned int j=0;
unsigned int i=0;
unsigned char ucBitH; //临时保存一个字节中的最高位
unsigned char ucBitL; //临时保存一个字节中的最低位
for(j=0;j<16;j++) //这里的16表示画布有16行
{
ucBitH=0;
ucBitL=0;
for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
{
if((ucCanvasBuffer[j*4+i]&0x01)==0x01) //临时保存一个字节中的最低位
{
ucBitL=1;
}
else
{
ucBitL=0;
}
ucCanvasBuffer[j*4+i]=ucCanvasBuffer[j*4+i]>>1; //一行中的一个字节右移一位
if(ucBitH==1) //原来左边相邻的字节最低位移动到了当前字节的最高位
{
ucCanvasBuffer[j*4+i]=ucCanvasBuffer[j*4+i]|0x80; //把最高位补上
}
ucBitH=ucBitL; //把当前的最低位赋值给最高位,为下一个相邻字节做准备。
}
}
}
void clear_all_canvas(void) //把画布全部清零
{
unsigned int j=0;
unsigned int i=0;
for(j=0;j<16;j++) //这里的16表示画布有16行
{
for(i=0;i<4;i++) //这里的4表示画布每行有4个字节
{
ucCanvasBuffer[j*4+i]=0x00;
}
}
}
void T0_time(void) interrupt 1 //定时中断函数
{
TF0=0; //清除中断标志
TR0=0; //关中断
if(ucMoveTimeStart==1) //已经开了定时器 也相当于原子锁或互斥量的功能
{
uiMoveTime++; //定时器累加计时开始
}
TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f
TL0=0x2f;
TR0=1; //开中断
}
void display_clear(unsigned char ucFillDate) // 清屏 全部显示空填充0x00 全部显示点阵用0xff
{
unsigned char x,y;
WriteCommand(0x34); //关显示缓冲指令
WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
y=0;
while(y<32) //y轴的范围0至31
{
WriteCommand(y+0x80); //垂直地址
WriteCommand(0x80); //水平地址
for(x=0;x<32;x++) //256个横向点,有32个字节
{
LCDWriteData(ucFillDate);
}
y++;
}
WriteCommand(0x36); //开显示缓冲指令
}
/* 注释三:
* 把字模插入画布的函数.
* 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是在画布中的坐标体系。
* x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
*/
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)
{
unsigned int j=0;
unsigned int i=0;
unsigned char ucTemp;
for(j=0;j<y_amount;j++)
{
for(i=0;i<x_amount;i++)
{
ucTemp=ucArray[j*x_amount+i];
if(ucFbFlag==0)
{
ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节
}
else
{
ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节
}
}
}
}
/* 注释四:
* 显示任意点阵函数.
* 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。
* 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。
* 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31.
* 第3个参数*ucArray是字模的数组。
* 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。
* 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。
* 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。
*/
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)
{
unsigned int j=0;
unsigned int i=0;
unsigned char ucTemp;
//注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动
// WriteCommand(0x34); //关显示缓冲指令
// WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的
for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横
{
WriteCommand(y+j+0x80); //垂直地址
WriteCommand(x+0x80); //水平地址
for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列
{
ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址
if(ucFbFlag==1) //反白显示
{
ucTemp=~ucTemp;
}
LCDWriteData(ucTemp);
// delay_short(30000); //把上一节这个延时函数去掉,加快刷屏速度
}
}
WriteCommand(0x36); //开显示缓冲指令
}
void SendByteToLcd(unsigned char ucData) //发送一个字节数据到液晶模块
{
unsigned char i;
for ( i = 0; i < 8; i++ )
{
if ( (ucData << i) & 0x80 )
{
LCDSID_dr = 1;
}
else
{
LCDSID_dr = 0;
}
LCDCLK_dr = 0;
LCDCLK_dr = 1;
}
}
void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动
{
SendByteToLcd( 0xf8 + (ucWRS << 1) );
SendByteToLcd( ucWData & 0xf0 );
SendByteToLcd( (ucWData << 4) & 0xf0);
}
void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块
{
LCDCS_dr = 0;
LCDCS_dr = 1;
SPIWrite(ucCommand, 0);
delay_short(90);
}
void LCDWriteData(unsigned char ucData) //发送一个字节的数据给液晶模块
{
LCDCS_dr = 0;
LCDCS_dr = 1;
SPIWrite(ucData, 1);
}
void LCDInit(void) //初始化 函数内部包括液晶模块的复位
{
LCDRST_dr = 1; //复位
LCDRST_dr = 0;
LCDRST_dr = 1;
}
void delay_short(unsigned int uiDelayShort) //延时函数
{
unsigned int i;
for(i=0;i<uiDelayShort;i++)
{
;
}
}
总结陈词:
从下一节开始讲大家关注已久的液晶屏菜单程序。欲知详情,请听下回分解-----在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。
(未完待续,下节更精彩,不要走开哦)
第七十六节:如何把一个任意数值的变量显示在液晶屏上。
开场白:
本来这一节打算开始讲液晶屏的菜单程序,但是我担心跳跃太大,恐怕很多初学者跟不上,所以多插入这一节讲讲后面菜单程序中经常用到的基本功能,如何把一个任意数值的变量显示在液晶屏上。我们需要做一个变量转换成字模的函数,以后只要调用这个转换函数就可以了。这一节就要把这个转换函数教给大家。
具体内容,请看源代码讲解。
(1)硬件平台:
基于朱兆祺51单片机学习板。
(2)实现功能:我们定义一个char型的全局变量,把它默认初始化为218,开机上电后,能看到正中间恰好显示这个全局变量的数值218。大家也可以试着更改它的默认初始值,只要不超过char型最大数值255范围,我们就会看到它上电后显示的就是这个初始值。
(3)源代码讲解如下:
#include "REG52.H" sbit LCDCS_dr = P1^6; //片选线 sbit LCDSID_dr = P1^7; //串行数据线 sbit LCDCLK_dr = P3^2; //串行时钟线 sbit LCDRST_dr = P3^4; //复位线 void SendByteToLcd(unsigned char ucData); //发送一个字节数据到液晶模块 void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块 void LCDWriteData(unsigned char ucData); //发送一个字节的数据给液晶模块 void LCDInit(void); //初始化 函数内部包括液晶模块的复位 void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00 全部显示点阵用0xff 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);//把字模插入画布. 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); //显示任意点阵函数 unsigned char *number_to_matrix(unsigned char ucBitNumber); //把一位数字转换成字模首地址的函数 void delay_short(unsigned int uiDelayshort); //延时 void delay_long(unsigned int uiDelayLong); void initial_myself(); void initial_peripheral(); void lcd_display_service(void); //应用层面的液晶屏显示程序 void clear_all_canvas(void); //把画布全部清零 code unsigned char Zf816_0[]= { /*-- 文字: 0 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00, }; code unsigned char Zf816_1[]= { /*-- 文字: 1 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00, }; code unsigned char Zf816_2[]= { /*-- 文字: 2 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00, }; code unsigned char Zf816_3[]= { /*-- 文字: 3 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; code unsigned char Zf816_4[]= { /*-- 文字: 4 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00, }; code unsigned char Zf816_5[]= { /*-- 文字: 5 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; code unsigned char Zf816_6[]= { /*-- 文字: 6 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00, }; code unsigned char Zf816_7[]= { /*-- 文字: 7 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, }; code unsigned char Zf816_8[]= { /*-- 文字: 8 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00, }; code unsigned char Zf816_9[]= { /*-- 文字: 9 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00, }; code unsigned char Zf816_nc[]= //空字模 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; /* 注释一: * 为了实现跨区域无缝显示,就先在某个区域显示一块画布,我们只要在这块画布数组中插入字模数组, * 就可以达到跨区域无缝显示的目的。根据上几节的介绍,12864液晶屏由上下两半屏组成,以下这块画布 * 显示在上半屏和下半屏之间。横向4个字节,纵向16行。其中上半屏显示8行,下半屏显示8行。注意,这个数组 * 不带code关键字,是全局变量,这样可读可写。画布的横向x坐标范围是0至3,因为画布的横向只要4个字节。 * 画布的纵向y坐标范围是0至15,因为画布的纵向只有16行。 */ unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00 { 0x00,0x00,0x00,0x00, //上半屏 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, //------------上半屏和下半屏的分割线----------- 0x00,0x00,0x00,0x00, //下半屏 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, }; unsigned char ucDisplayUpdate=1; //更新显示变量 /* 注释二: * 以下变量就是本程序的任意变量,网友可以自己更改它的大小来测试本程序,不要超过255. */ unsigned char ucAnyNumber=218; //任意变量默认初始化为218。 void main() { initial_myself(); //第一区,上电后马上初始化 delay_long(100); //一线,延时线。延时一段时间 initial_peripheral(); //第二区,上电后延时一段时间再初始化 while(1) //第三区 { lcd_display_service(); //应用层面的液晶屏显示程序 } } void initial_myself() //第一区 上电后马上初始化 { ; } void initial_peripheral() //第二区 上电后延时一段时间再初始化 { LCDInit(); //初始化12864 内部包含液晶模块的复位 display_clear(0xff); // 清屏 全部显示空填充0x00 全部显示点阵用0xff } /* 注释三: * 本程序的核心转换函数。 * 是可以把一位任意数字变量的函数转换成对应的字模,由于字模是数组,所以返回的是指针,代表字模数组的首地址。 */ unsigned char *number_to_matrix(unsigned char ucBitNumber) { unsigned char *p_ucAnyNumber; //此指针根据ucBitNumber数值的大小,分别调用不同的字库。 switch(ucBitNumber) //根据ucBitNumber数值的大小,分别调用不同的字库。 { case 0: p_ucAnyNumber=Zf816_0; break; case 1: p_ucAnyNumber=Zf816_1; break; case 2: p_ucAnyNumber=Zf816_2; break; case 3: p_ucAnyNumber=Zf816_3; break; case 4: p_ucAnyNumber=Zf816_4; break; case 5: p_ucAnyNumber=Zf816_5; break; case 6: p_ucAnyNumber=Zf816_6; break; case 7: p_ucAnyNumber=Zf816_7; break; case 8: p_ucAnyNumber=Zf816_8; break; case 9: p_ucAnyNumber=Zf816_9; break; case 10: p_ucAnyNumber=Zf816_nc; break; default: //如果上面的条件都不符合,那么默认指向空字模 p_ucAnyNumber=Zf816_nc; break; } return p_ucAnyNumber; //返回转换结束后的指针 } void lcd_display_service(void) //应用层面的液晶屏显示程序 { static unsigned char ucAnyNumber_1; //分解变量的个位 static unsigned char ucAnyNumber_10; //分解变量的十位 static unsigned char ucAnyNumber_100; //分解变量的百位 static unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址 static unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址 static unsigned char *p_ucAnyNumber_100; //经过数字转换成字模后,分解变量的百位字模首地址 if(ucDisplayUpdate==1) //需要更新显示 { ucDisplayUpdate=0; //及时把标志清零,避免一直处于不断更新的状态。 if(ucAnyNumber>=100) //有3位数以上 { ucAnyNumber_100=ucAnyNumber/100; //百位 } else //否则显示空 { ucAnyNumber_100=10; //在下面的转换函数中,代码10表示空字模 } if(ucAnyNumber>=10) //有2位数以上 { ucAnyNumber_10=ucAnyNumber%100/10; //十位 } else //否则显示空 { ucAnyNumber_10=10; //在下面的转换函数中,代码10表示空字模 } ucAnyNumber_1=ucAnyNumber%10/1; //个位 p_ucAnyNumber_100=number_to_matrix(ucAnyNumber_100); //把数字转换成字模首地址 p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址 p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址 clear_all_canvas(); //把画布全部清零 insert_buffer_to_canvas(0,0,p_ucAnyNumber_100,0,1,16);//把百位的字模插入画布 insert_buffer_to_canvas(1,0,p_ucAnyNumber_10,0,1,16);//把十的字模插入画布 insert_buffer_to_canvas(2,0,p_ucAnyNumber_1,0,1,16);//把个的字模插入画布 display_lattice(3,24,ucCanvasBuffer,0,4,8,0); //显示上半屏的画布,最后的参数0是偏移量 display_lattice(11,0,ucCanvasBuffer,0,4,8,32); //显示下半屏的画布,最后的参数32是偏移量 } } void clear_all_canvas(void) //把画布全部清零 { unsigned int j=0; unsigned int i=0; for(j=0;j<16;j++) //这里的16表示画布有16行 { for(i=0;i<4;i++) //这里的4表示画布每行有4个字节 { ucCanvasBuffer[j*4+i]=0x00; } } } void display_clear(unsigned char ucFillDate) // 清屏 全部显示空填充0x00 全部显示点阵用0xff { unsigned char x,y; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 y=0; while(y<32) //y轴的范围0至31 { WriteCommand(y+0x80); //垂直地址 WriteCommand(0x80); //水平地址 for(x=0;x<32;x++) //256个横向点,有32个字节 { LCDWriteData(ucFillDate); } y++; } WriteCommand(0x36); //开显示缓冲指令 } /* 注释四: * 把字模插入画布的函数. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是在画布中的坐标体系。 * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。 * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 */ 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) { unsigned int j=0; unsigned int i=0; unsigned char ucTemp; for(j=0;j<y_amount;j++) { for(i=0;i<x_amount;i++) { ucTemp=ucArray[j*x_amount+i]; if(ucFbFlag==0) { ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节 } else { ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节 } } } } /* 注释五: * 显示任意点阵函数. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。 * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31. * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。 */ 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) { unsigned int j=0; unsigned int i=0; unsigned char ucTemp; //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动 // WriteCommand(0x34); //关显示缓冲指令 // WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横 { WriteCommand(y+j+0x80); //垂直地址 WriteCommand(x+0x80); //水平地址 for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列 { ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址 if(ucFbFlag==1) //反白显示 { ucTemp=~ucTemp; } LCDWriteData(ucTemp); // delay_short(30000); //把上一节这个延时函数去掉,加快刷屏速度 } } WriteCommand(0x36); //开显示缓冲指令 } void SendByteToLcd(unsigned char ucData) //发送一个字节数据到液晶模块 { unsigned char i; for ( i = 0; i < 8; i++ ) { if ( (ucData << i) & 0x80 ) { LCDSID_dr = 1; } else { LCDSID_dr = 0; } LCDCLK_dr = 0; LCDCLK_dr = 1; } } void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 { SendByteToLcd( 0xf8 + (ucWRS << 1) ); SendByteToLcd( ucWData & 0xf0 ); SendByteToLcd( (ucWData << 4) & 0xf0); } void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucCommand, 0); delay_short(90); } void LCDWriteData(unsigned char ucData) //发送一个字节的数据给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucData, 1); } void LCDInit(void) //初始化 函数内部包括液晶模块的复位 { LCDRST_dr = 1; //复位 LCDRST_dr = 0; LCDRST_dr = 1; } void delay_short(unsigned int uiDelayShort) //延时函数 { unsigned int i; for(i=0;i<uiDelayShort;i++) { ; } } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i<uiDelayLong;i++) { for(j=0;j<500;j++) //内嵌循环的空指令数量 { ; //一个分号相当于执行一条空语句 } } }
总结陈词:
有了这一节的基础,我们继续循序渐进,下一节将会讲到液晶屏的菜单程序。欲知详情,请听下回分解-----在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。
(未完待续,下节更精彩,不要走开哦)
第七十七节:在1个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。
开场白:
这一节要教会大家两个知识点:
第一个知识点:我在前面讲数码管显示的时候就提出了一个 “一二级菜单显示理论”:凡是人机界面显示,不管是数码管还是液晶屏,都可以把显示的内容分成不同的窗口来显示,每个显示的窗口中又可以分成不同的局部显示。其中窗口就是一级菜单,用ucWd变量表示。局部就是二级菜单,用ucPart来表示。不同的窗口,会有不同的更新显示变量ucWdXUpdate来对应,表示整屏全部更新显示。不同的局部,也会有不同的更新显示变量ucWdXPartYUpdate来对应,表示局部更新显示。把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候才需要更新的,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要刷新显示的内容,这种内容放在局部更新显示的括号里。
第二个知识点:按键如何跟液晶屏显示有机的结合起来?只要遵循鸿哥总结出来的一个规律“在不同的窗口下,根据不同的局部变量来操作不同的参数”,这样再复杂的人机交互程序都会显得很简单清晰。
具体内容,请看源代码讲解。
(1)硬件平台:基于朱兆祺51单片机学习板。加按键对应S1键,减按键对应S5键,切换“光标”移动按键对应S9键,设置参数按键对应S13键。
(2)实现功能:
通过按键设置4个不同的参数。
有1个窗口。每个窗口显示4个参数。每个参数的范围是从0到99。
有4个按键:
(a) 一个是设置参数S13按键,按下此按键,液晶屏的第一行会出现反显的光标,表示进入设置参数模式,再次按下此按键,反显光标会消失,表示退出设置参数模式。
(b) 一个是移动光标S9按键,在进入设置参数的模式下,依次按下此按键,液晶屏上的光标会从上往下移动,表示选中不同的参数。
(c) 一个是减数S5按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐减小。
(d) 一个是加数S1按键,在设置参数模式下,依次按下此按键,被选中的参数会逐渐加大。
#include "REG52.H" #define const_voice_short 40 //蜂鸣器短叫的持续时间 #define const_key_time1 20 //按键去抖动延时的时间 #define const_key_time2 20 //按键去抖动延时的时间 #define const_key_time3 20 //按键去抖动延时的时间 #define const_key_time4 20 //按键去抖动延时的时间 sbit key_sr1=P0^0; //对应朱兆祺学习板的S1键 sbit key_sr2=P0^1; //对应朱兆祺学习板的S5键 sbit key_sr3=P0^2; //对应朱兆祺学习板的S9键 sbit key_sr4=P0^3; //对应朱兆祺学习板的S13键 sbit key_gnd_dr=P0^4; //模拟独立按键的地GND,因此必须一直输出低电平 sbit beep_dr=P2^7; //蜂鸣器的驱动IO口 sbit LCDCS_dr = P1^6; //片选线 sbit LCDSID_dr = P1^7; //串行数据线 sbit LCDCLK_dr = P3^2; //串行时钟线 sbit LCDRST_dr = P3^4; //复位线 void SendByteToLcd(unsigned char ucData); //发送一个字节数据到液晶模块 void SPIWrite(unsigned char ucWData, unsigned char ucWRS); //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 void WriteCommand(unsigned char ucCommand); //发送一个字节的命令给液晶模块 void LCDWriteData(unsigned char ucData); //发送一个字节的数据给液晶模块 void LCDInit(void); //初始化 函数内部包括液晶模块的复位 void display_clear(unsigned char ucFillDate); // 清屏 全部显示空填充0x00 全部显示点阵用0xff 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);//把字模插入画布. 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); //显示任意点阵函数 unsigned char *number_to_matrix(unsigned char ucBitNumber); //把一位数字转换成字模首地址的函数 void delay_short(unsigned int uiDelayshort); //延时 void delay_long(unsigned int uiDelayLong); void T0_time(); //定时中断函数 void key_service(void); //按键服务的应用程序 void key_scan(void);//按键扫描函数 放在定时中断里 void initial_myself(); void initial_peripheral(); void lcd_display_service(void); //应用层面的液晶屏显示程序 void clear_all_canvas(void); //把画布全部清零 code unsigned char Zf816_0[]= { /*-- 文字: 0 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x42,0x42,0x42,0x42,0x24,0x18,0x00,0x00, }; code unsigned char Zf816_1[]= { /*-- 文字: 1 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x10,0x70,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x7C,0x00,0x00, }; code unsigned char Zf816_2[]= { /*-- 文字: 2 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x04,0x04,0x08,0x10,0x20,0x42,0x7E,0x00,0x00, }; code unsigned char Zf816_3[]= { /*-- 文字: 3 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x3C,0x42,0x42,0x04,0x18,0x04,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; code unsigned char Zf816_4[]= { /*-- 文字: 4 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x04,0x0C,0x14,0x24,0x24,0x44,0x44,0x7E,0x04,0x04,0x1E,0x00,0x00, }; code unsigned char Zf816_5[]= { /*-- 文字: 5 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x7E,0x40,0x40,0x40,0x58,0x64,0x02,0x02,0x42,0x44,0x38,0x00,0x00, }; code unsigned char Zf816_6[]= { /*-- 文字: 6 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x1C,0x24,0x40,0x40,0x58,0x64,0x42,0x42,0x42,0x24,0x18,0x00,0x00, }; code unsigned char Zf816_7[]= { /*-- 文字: 7 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x7E,0x44,0x44,0x08,0x08,0x10,0x10,0x10,0x10,0x10,0x10,0x00,0x00, }; code unsigned char Zf816_8[]= { /*-- 文字: 8 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x3C,0x42,0x42,0x42,0x24,0x18,0x24,0x42,0x42,0x42,0x3C,0x00,0x00, }; code unsigned char Zf816_9[]= { /*-- 文字: 9 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x18,0x24,0x42,0x42,0x42,0x26,0x1A,0x02,0x02,0x24,0x38,0x00,0x00, }; code unsigned char Zf816_nc[]= //空字模 { 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Zf816_mao_hao[]= //冒号 { /*-- 文字: : --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x00,0x00, }; code unsigned char Hz1616_yi[]= { /*-- 文字: 一 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_er[]= { /*-- 文字: 二 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ 0x00,0x00,0x00,0x10,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x04,0x7F,0xFE,0x00,0x00,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_san[]= { /*-- 文字: 三 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ 0x00,0x00,0x00,0x00,0x7F,0xFC,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x3F,0xF8, 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7F,0xFE,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_si[]= { /*-- 文字: 四 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ 0x00,0x00,0x7F,0xFC,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84,0x44,0x84, 0x48,0x84,0x48,0x7C,0x50,0x04,0x60,0x04,0x40,0x04,0x7F,0xFC,0x40,0x04,0x00,0x00, }; code unsigned char Hz1616_chuang[]= { /*-- 文字: 窗 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ 0x01,0x00,0x00,0x80,0x7F,0xFE,0x40,0x22,0x09,0x18,0x12,0x06,0x7F,0xF8,0x11,0x08, 0x13,0xE8,0x14,0x48,0x1A,0x88,0x11,0x08,0x12,0x88,0x14,0x08,0x1F,0xF8,0x10,0x08, }; code unsigned char Hz1616_kou[]= { /*-- 文字: 口 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ 0x00,0x00,0x00,0x00,0x3F,0xF8,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08,0x20,0x08, 0x20,0x08,0x20,0x08,0x20,0x08,0x3F,0xF8,0x20,0x08,0x20,0x08,0x00,0x00,0x00,0x00, }; code unsigned char Hz1616_hang[]= { /*-- 文字: 行 --*/ /*-- 宋体12; 此字体下对应的点阵为:宽x高=16x16 --*/ 0x08,0x00,0x1C,0x00,0x31,0xFC,0x40,0x00,0x88,0x00,0x0C,0x00,0x1B,0xFE,0x30,0x20, 0x50,0x20,0x90,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0x20,0x10,0xA0,0x10,0x40, }; unsigned char ucCanvasBuffer[]= //画布显示数组。注意,这里没有code关键字,是全局变量。初始化全部填充0x00 { 0x00,0x00,0x00,0x00, //上半屏 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, //------------上半屏和下半屏的分割线----------- 0x00,0x00,0x00,0x00, //下半屏 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, 0x00,0x00,0x00,0x00, }; unsigned char ucKeySec=0; //被触发的按键编号 unsigned int uiVoiceCnt=0; //蜂鸣器鸣叫的持续时间计数器 unsigned char ucWd=1; //窗口变量 unsigned char ucPart=0; //局部变量 0代表没有选中任何一行,其它数值1到4代表选中某一行 unsigned char ucWd1Update=1; //窗口1的整屏更新显示变量 1代表更新显示,响应函数内部会清零 unsigned char ucWd1Part1Update=0; //窗口1的第1行局部更新显示变量 1代表更新显示,响应函数内部会清零 unsigned char ucWd1Part2Update=0; //窗口1的第2行局部更新显示变量 1代表更新显示,响应函数内部会清零 unsigned char ucWd1Part3Update=0; //窗口1的第3行局部更新显示变量 1代表更新显示,响应函数内部会清零 unsigned char ucWd1Part4Update=0; //窗口1的第4行局部更新显示变量 1代表更新显示,响应函数内部会清零 unsigned char ucData_1_1=8; //第1个窗口第1行的被设置数据 unsigned char ucData_1_2=9; //第1个窗口第2行的被设置数据 unsigned char ucData_1_3=10; //第1个窗口第3行的被设置数据 unsigned char ucData_1_4=11; //第1个窗口第4行的被设置数据 void main() { initial_myself(); //第一区,上电后马上初始化 delay_long(100); //一线,延时线。延时一段时间 initial_peripheral(); //第二区,上电后延时一段时间再初始化 while(1) //第三区 { key_service(); //按键服务的应用程序 lcd_display_service(); //应用层面的液晶屏显示程序 } } void initial_myself() //第一区 上电后马上初始化 { /* 注释一: * 矩阵键盘也可以做独立按键,前提是把某一根公共输出线输出低电平, * 模拟独立按键的触发地,本程序中,把key_gnd_dr输出低电平。 * 朱兆祺51学习板的S1和S5两个按键就是本程序中用到的两个独立按键。 */ key_gnd_dr=0; //模拟独立按键的地GND,因此必须一直输出低电平 beep_dr=1; //用PNP三极管控制蜂鸣器,输出高电平时不叫。 TMOD=0x01; //设置定时器0为工作方式1 TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f TL0=0x2f; } void initial_peripheral() //第二区 上电后延时一段时间再初始化 { LCDInit(); //初始化12864 内部包含液晶模块的复位 EA=1; //开总中断 ET0=1; //允许定时中断 TR0=1; //启动定时中断 } void T0_time() interrupt 1 { TF0=0; //清除中断标志 TR0=0; //关中断 key_scan(); //按键扫描函数 if(uiVoiceCnt!=0) { uiVoiceCnt--; //每次进入定时中断都自减1,直到等于零为止。才停止鸣叫 beep_dr=0; //蜂鸣器是PNP三极管控制,低电平就开始鸣叫。 } else { ; //此处多加一个空指令,想维持跟if括号语句的数量对称,都是两条指令。不加也可以。 beep_dr=1; //蜂鸣器是PNP三极管控制,高电平就停止鸣叫。 } TH0=0xf8; //重装初始值(65535-2000)=63535=0xf82f TL0=0x2f; TR0=1; //开中断 } void key_scan(void)//按键扫描函数 放在定时中断里 { static unsigned int uiKeyTimeCnt1=0; //按键去抖动延时计数器 static unsigned char ucKeyLock1=0; //按键触发后自锁的变量标志 static unsigned int uiKeyTimeCnt2=0; //按键去抖动延时计数器 static unsigned char ucKeyLock2=0; //按键触发后自锁的变量标志 static unsigned int uiKeyTimeCnt3=0; //按键去抖动延时计数器 static unsigned char ucKeyLock3=0; //按键触发后自锁的变量标志 static unsigned int uiKeyTimeCnt4=0; //按键去抖动延时计数器 static unsigned char ucKeyLock4=0; //按键触发后自锁的变量标志 if(key_sr1==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { ucKeyLock1=0; //按键自锁标志清零 uiKeyTimeCnt1=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。 } else if(ucKeyLock1==0)//有按键按下,且是第一次被按下 { uiKeyTimeCnt1++; //累加定时中断次数 if(uiKeyTimeCnt1>const_key_time1) { uiKeyTimeCnt1=0; ucKeyLock1=1; //自锁按键置位,避免一直触发 ucKeySec=1; //触发1号键 } } if(key_sr2==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { ucKeyLock2=0; //按键自锁标志清零 uiKeyTimeCnt2=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。 } else if(ucKeyLock2==0)//有按键按下,且是第一次被按下 { uiKeyTimeCnt2++; //累加定时中断次数 if(uiKeyTimeCnt2>const_key_time2) { uiKeyTimeCnt2=0; ucKeyLock2=1; //自锁按键置位,避免一直触发 ucKeySec=2; //触发2号键 } } if(key_sr3==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { ucKeyLock3=0; //按键自锁标志清零 uiKeyTimeCnt3=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。 } else if(ucKeyLock3==0)//有按键按下,且是第一次被按下 { uiKeyTimeCnt3++; //累加定时中断次数 if(uiKeyTimeCnt3>const_key_time3) { uiKeyTimeCnt3=0; ucKeyLock3=1; //自锁按键置位,避免一直触发 ucKeySec=3; //触发3号键 } } if(key_sr4==1)//IO是高电平,说明按键没有被按下,这时要及时清零一些标志位 { ucKeyLock4=0; //按键自锁标志清零 uiKeyTimeCnt4=0;//按键去抖动延时计数器清零,此行非常巧妙,是我实战中摸索出来的。 } else if(ucKeyLock4==0)//有按键按下,且是第一次被按下 { uiKeyTimeCnt4++; //累加定时中断次数 if(uiKeyTimeCnt4>const_key_time4) { uiKeyTimeCnt4=0; ucKeyLock4=1; //自锁按键置位,避免一直触发 ucKeySec=4; //触发4号键 } } } void key_service(void) //按键服务的应用程序 { switch(ucKeySec) //按键服务状态切换 { case 1:// 加按键 对应朱兆祺学习板的S1键 switch(ucWd) //在不同的窗口下,设置不同的参数 { case 1: switch(ucPart) //在窗口1下,根据不同的局部变量来设置不同的参数 { case 0: //无光标显示的状态 此处的case 0可以省略 break; case 1: //设置第1行参数 ucData_1_1++; if(ucData_1_1>99) { ucData_1_1=99; } ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零 break; case 2: //设置第2行参数 ucData_1_2++; if(ucData_1_2>99) { ucData_1_2=99; } ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零 break; case 3: //设置第3行参数 ucData_1_3++; if(ucData_1_3>99) { ucData_1_3=99; } ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零 break; case 4: //设置第4行参数 ucData_1_4++; if(ucData_1_4>99) { ucData_1_4=99; } ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零 break; } break; } uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。 ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发 break; case 2:// 减按键 对应朱兆祺学习板的S5键 switch(ucWd) //在不同的窗口下,设置不同的参数 { case 1: switch(ucPart) //在窗口1下,根据不同的局部变量来设置不同的参数 { case 0: //无光标显示的状态 此处的case 0可以省略 break; case 1: //设置第1行参数 ucData_1_1--; if(ucData_1_1>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff) { ucData_1_1=0; } ucWd1Part1Update=1; //1代表更新显示,响应函数内部会清零 break; case 2: //设置第2行参数 ucData_1_2--; if(ucData_1_2>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff) { ucData_1_2=0; } ucWd1Part2Update=1; //1代表更新显示,响应函数内部会清零 break; case 3: //设置第3行参数 ucData_1_3--; if(ucData_1_3>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff) { ucData_1_3=0; } ucWd1Part3Update=1; //1代表更新显示,响应函数内部会清零 break; case 4: //设置第4行参数 ucData_1_4--; if(ucData_1_4>99) //一直减到最后,单片机C语言编译器有一个特征,0减去1会溢出变成255(0xff) { ucData_1_4=0; } ucWd1Part4Update=1; //1代表更新显示,响应函数内部会清零 break; } break; } uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。 ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发 break; case 3:// 切换"光标"移动按键 对应朱兆祺学习板的S9键 switch(ucWd) //在不同的窗口下,设置不同的参数 { case 1: switch(ucPart) //在窗口1下,根据不同的局部变量来设置不同的参数 { case 0: //无光标显示的状态 此处的case 0可以省略 break; case 1: //设置第1行参数 ucPart=2; //光标切换到下一行 ucWd1Part1Update=1; //更新显示原来那一行,目的是更新反显光标的状态 ucWd1Part2Update=1; //更新显示下一行, 目的是更新反显光标的状态 break; case 2: //设置第2行参数 ucPart=3; //光标切换到下一行 ucWd1Part2Update=1; //更新显示原来那一行,目的是更新反显光标的状态 ucWd1Part3Update=1; //更新显示下一行, 目的是更新反显光标的状态 break; case 3: //设置第3行参数 ucPart=4; //光标切换到下一行 ucWd1Part3Update=1; //更新显示原来那一行,目的是更新反显光标的状态 ucWd1Part4Update=1; //更新显示下一行, 目的是更新反显光标的状态 break; case 4: //设置第4行参数 ucPart=1; //光标返回到最上面第一行 ucWd1Part4Update=1; //更新显示原来那一行,目的是更新反显光标的状态 ucWd1Part1Update=1; //更新显示最上面第一行, 目的是更新反显光标的状态 break; } break; } uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。 ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发 break; case 4: // 设置按键 对应朱兆祺学习板的S13键,按一次进入设置状态,出现反显光标。再按一次推出设置状态,消除反显光标 switch(ucWd) //在不同的窗口下,设置不同的参数 { case 1: switch(ucPart) //在窗口1下,根据不同的局部变量来设置不同的参数 { case 0: //无光标显示的状态 ucPart=1; //光标显示第一行,进入设置模式 ucWd1Part1Update=1; //更新显示 break; case 1: //设置第1行参数 ucPart=0; //无光标显示,退出设置模式 ucWd1Part1Update=1; //更新显示 break; case 2: //设置第2行参数 ucPart=0; //无光标显示,退出设置模式 ucWd1Part2Update=1; //更新显示 break; case 3: //设置第3行参数 ucPart=0; //无光标显示,退出设置模式 ucWd1Part3Update=1; //更新显示 break; case 4: //设置第4行参数 ucPart=0; //无光标显示,退出设置模式 ucWd1Part4Update=1; //更新显示 break; } break; } uiVoiceCnt=const_voice_short; //按键声音触发,滴一声就停。 ucKeySec=0; //响应按键服务处理程序后,按键编号清零,避免一致触发 break; } } unsigned char *number_to_matrix(unsigned char ucBitNumber) { unsigned char *p_ucAnyNumber; //此指针根据ucBitNumber数值的大小,分别调用不同的字库。 switch(ucBitNumber) //根据ucBitNumber数值的大小,分别调用不同的字库。 { case 0: p_ucAnyNumber=Zf816_0; break; case 1: p_ucAnyNumber=Zf816_1; break; case 2: p_ucAnyNumber=Zf816_2; break; case 3: p_ucAnyNumber=Zf816_3; break; case 4: p_ucAnyNumber=Zf816_4; break; case 5: p_ucAnyNumber=Zf816_5; break; case 6: p_ucAnyNumber=Zf816_6; break; case 7: p_ucAnyNumber=Zf816_7; break; case 8: p_ucAnyNumber=Zf816_8; break; case 9: p_ucAnyNumber=Zf816_9; break; case 10: p_ucAnyNumber=Zf816_nc; break; default: //如果上面的条件都不符合,那么默认指向空字模 p_ucAnyNumber=Zf816_nc; break; } return p_ucAnyNumber; //返回转换结束后的指针 } void lcd_display_service(void) //应用层面的液晶屏显示程序 { unsigned char ucAnyNumber_1; //分解变量的个位 unsigned char ucAnyNumber_10; //分解变量的十位 unsigned char *p_ucAnyNumber_1; //经过数字转换成字模后,分解变量的个位字模首地址 unsigned char *p_ucAnyNumber_10; //经过数字转换成字模后,分解变量的十位字模首地址 unsigned char ucCursorFlag; //光标标志,也就是反显的标志,它是根据局部变量ucPart来定的 switch(ucWd) //本程序的核心变量,窗口显示变量。类似于一级菜单的变量。代表显示不同的窗口。 { case 1: //显示窗口1的数据 /* 注释二: * 把每一个窗口的内容分为两种类型,一种类型是那些不用经常刷新显示的内容,只有在切换窗口的时候 * 才需要更新,这种内容放在整屏更新显示的括号里,比如清屏操作等内容。另外一种是那些经常需要 * 刷新显示的内容,这种内容放在局部更新显示的括号里。 */ if(ucWd1Update==1) //窗口1整屏更新,里面只放那些不用经常刷新显示的内容 { ucWd1Update=0; //及时清零,避免一直更新 ucWd1Part1Update=1; //激活窗口1的第1行局部更新显示变量,这里在前面数码管显示框架上有所改进 ucWd1Part2Update=1; //激活窗口1的第2行局部更新显示变量,这里在前面数码管显示框架上有所改进 ucWd1Part3Update=1; //激活窗口1的第3行局部更新显示变量,这里在前面数码管显示框架上有所改进 ucWd1Part4Update=1; //激活窗口1的第4行局部更新显示变量,这里在前面数码管显示框架上有所改进 display_clear(0x00); // 清屏操作, 全部显示空填充0x00,全部显示点阵用0xff。 clear_all_canvas(); //把画布全部清零 insert_buffer_to_canvas(0,0,Zf816_mao_hao,0,1,16);//把冒号的字模插入画布 display_lattice(0,0,Hz1616_yi,0,2,16,0); //一窗口一行,这些内容不用经常更新,只有在切换窗口的时候才更新显示 display_lattice(1,0,Hz1616_chuang,0,2,16,0); display_lattice(2,0,Hz1616_kou,0,2,16,0); display_lattice(3,0,Hz1616_yi,0,2,16,0); display_lattice(4,0,Hz1616_hang,0,2,16,0); display_lattice(0,16,Hz1616_yi,0,2,16,0); //一窗口二行 display_lattice(1,16,Hz1616_chuang,0,2,16,0); display_lattice(2,16,Hz1616_kou,0,2,16,0); display_lattice(3,16,Hz1616_er,0,2,16,0); display_lattice(4,16,Hz1616_hang,0,2,16,0); display_lattice(8,0,Hz1616_yi,0,2,16,0); //一窗口三行 display_lattice(9,0,Hz1616_chuang,0,2,16,0); display_lattice(10,0,Hz1616_kou,0,2,16,0); display_lattice(11,0,Hz1616_san,0,2,16,0); display_lattice(12,0,Hz1616_hang,0,2,16,0); display_lattice(8,16,Hz1616_yi,0,2,16,0); //一窗口四行 display_lattice(9,16,Hz1616_chuang,0,2,16,0); display_lattice(10,16,Hz1616_kou,0,2,16,0); display_lattice(11,16,Hz1616_si,0,2,16,0); display_lattice(12,16,Hz1616_hang,0,2,16,0); } /* 注释三: * 注意!我前面讲数码管显示的时候有一句话讲错了,我那时说<局部更新应该写在整屏更新之前>,这是不对的。 * 按照现在的显示程序框架<即整屏显示更新括号里包含了所有局部变量的激活>,应该是<整屏更新应该写在局部更新之前> * 这样才对。 */ if(ucWd1Part1Update==1) //窗口1的第1行局部更新显示变量,里面放一些经常需要刷新显示的内容 { ucWd1Part1Update=0; //及时清零,避免一直更新 if(ucPart==1) //被选中 { ucCursorFlag=1; //反显 显示 } else //没被选中 { ucCursorFlag=0; //正常 显示 } if(ucData_1_1>=10) //有2位数以上 { ucAnyNumber_10=ucData_1_1/10; //十位 } else //否则显示空 { ucAnyNumber_10=10; //在下面的转换函数中,代码10表示空字模 } ucAnyNumber_1=ucData_1_1%10/1; //个位 p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址 p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址 insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布 insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布 display_lattice(5,0,ucCanvasBuffer,0,4,16,0); //显示整屏的画布,最后的参数0是偏移量 } if(ucWd1Part2Update==1) //窗口1的第2行局部更新显示变量,里面放一些经常需要刷新显示的内容 { ucWd1Part2Update=0; //及时清零,避免一直更新 if(ucPart==2) //被选中 { ucCursorFlag=1; //反显 显示 } else //没被选中 { ucCursorFlag=0; //正常 显示 } if(ucData_1_2>=10) //有2位数以上 { ucAnyNumber_10=ucData_1_2/10; //十位 } else //否则显示空 { ucAnyNumber_10=10; //在下面的转换函数中,代码10表示空字模 } ucAnyNumber_1=ucData_1_2%10/1; //个位 p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址 p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址 insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布 insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布 display_lattice(5,16,ucCanvasBuffer,0,4,16,0); //显示整屏的画布,最后的参数0是偏移量 } if(ucWd1Part3Update==1) //窗口1的第3行局部更新显示变量,里面放一些经常需要刷新显示的内容 { ucWd1Part3Update=0; //及时清零,避免一直更新 if(ucPart==3) //被选中 { ucCursorFlag=1; //反显 显示 } else //没被选中 { ucCursorFlag=0; //正常 显示 } if(ucData_1_3>=10) //有2位数以上 { ucAnyNumber_10=ucData_1_3/10; //十位 } else //否则显示空 { ucAnyNumber_10=10; //在下面的转换函数中,代码10表示空字模 } ucAnyNumber_1=ucData_1_3%10/1; //个位 p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址 p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址 insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布 insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布 display_lattice(13,0,ucCanvasBuffer,0,4,16,0); //显示整屏的画布,最后的参数0是偏移量 } if(ucWd1Part4Update==1) //窗口1的第4行局部更新显示变量,里面放一些经常需要刷新显示的内容 { ucWd1Part4Update=0; //及时清零,避免一直更新 if(ucPart==4) //被选中 { ucCursorFlag=1; //反显 显示 } else //没被选中 { ucCursorFlag=0; //正常 显示 } if(ucData_1_4>=10) //有2位数以上 { ucAnyNumber_10=ucData_1_4/10; //十位 } else //否则显示空 { ucAnyNumber_10=10; //在下面的转换函数中,代码10表示空字模 } ucAnyNumber_1=ucData_1_4%10/1; //个位 p_ucAnyNumber_10=number_to_matrix(ucAnyNumber_10); //把数字转换成字模首地址 p_ucAnyNumber_1=number_to_matrix(ucAnyNumber_1); //把数字转换成字模首地址 insert_buffer_to_canvas(2,0,p_ucAnyNumber_10,ucCursorFlag,1,16);//把十的字模插入画布 insert_buffer_to_canvas(3,0,p_ucAnyNumber_1,ucCursorFlag,1,16);//把个的字模插入画布 display_lattice(13,16,ucCanvasBuffer,0,4,16,0); //显示整屏的画布,最后的参数0是偏移量 } break; //本程序只有1个窗口,所以只有一个case 1,如果要增加窗口,就直接增加 case 2, case 3... } } void clear_all_canvas(void) //把画布全部清零 { unsigned int j=0; unsigned int i=0; for(j=0;j<16;j++) //这里的16表示画布有16行 { for(i=0;i<4;i++) //这里的4表示画布每行有4个字节 { ucCanvasBuffer[j*4+i]=0x00; } } } void display_clear(unsigned char ucFillDate) // 清屏 全部显示空填充0x00 全部显示点阵用0xff { unsigned char x,y; WriteCommand(0x34); //关显示缓冲指令 WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 y=0; while(y<32) //y轴的范围0至31 { WriteCommand(y+0x80); //垂直地址 WriteCommand(0x80); //水平地址 for(x=0;x<32;x++) //256个横向点,有32个字节 { LCDWriteData(ucFillDate); } y++; } WriteCommand(0x36); //开显示缓冲指令 } /* 注释四: * 把字模插入画布的函数. * 这是本节的核心函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是在画布中的坐标体系。 * x的范围是0至3,因为画布的横向只要4个字节。y的范围是0至15,因为画布的纵向只有16行。 * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 */ 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) { unsigned int j=0; unsigned int i=0; unsigned char ucTemp; for(j=0;j<y_amount;j++) { for(i=0;i<x_amount;i++) { ucTemp=ucArray[j*x_amount+i]; if(ucFbFlag==0) { ucCanvasBuffer[(y+j)*4+x+i]=ucTemp; //这里的4代表画布每一行只有4个字节 } else { ucCanvasBuffer[(y+j)*4+x+i]=~ucTemp; //这里的4代表画布每一行只有4个字节 } } } } /* 注释五: * 显示任意点阵函数. * 注意,本函数在前几节的基础上多增加了第7个参数uiOffSetAddr,它是偏移地址。 * 对于这个函数,读者尤其要搞懂x_amount和y_amount对应的显示关系。 * 第1,2个参数x,y是坐标体系。x的范围是0至15,y的范围是0至31. * 第3个参数*ucArray是字模的数组。 * 第4个参数ucFbFlag是反白显示标志。0代表正常显示,1代表反白显示。 * 第5,6个参数x_amount,y_amount分别代表字模数组的横向有多少个字节,纵向有几横。 * 第7个参数uiOffSetAddr是偏移地址,代表字模数组的从第几个数据开始显示。 */ 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) { unsigned int j=0; unsigned int i=0; unsigned char ucTemp; //注意,要把以下两行指令屏蔽,否则屏幕在更新显示时会整屏闪动 // WriteCommand(0x34); //关显示缓冲指令 // WriteCommand(0x34); //关显示缓冲指令 故意写2次,怕1次关不了 这个是因为我参考到某厂家的驱动程序也是这样写的 for(j=0;j<y_amount;j++) //y_amount代表y轴有多少横 { WriteCommand(y+j+0x80); //垂直地址 WriteCommand(x+0x80); //水平地址 for(i=0;i<x_amount;i++) //x_amount代表x轴有多少列 { ucTemp=ucArray[j*x_amount+i+uiOffSetAddr]; //uiOffSetAddr是字模数组的偏移地址 if(ucFbFlag==1) //反白显示 { ucTemp=~ucTemp; } LCDWriteData(ucTemp); // delay_short(30000); //把上一节这个延时函数去掉,加快刷屏速度 } } WriteCommand(0x36); //开显示缓冲指令 } void SendByteToLcd(unsigned char ucData) //发送一个字节数据到液晶模块 { unsigned char i; for ( i = 0; i < 8; i++ ) { if ( (ucData << i) & 0x80 ) { LCDSID_dr = 1; } else { LCDSID_dr = 0; } LCDCLK_dr = 0; LCDCLK_dr = 1; } } void SPIWrite(unsigned char ucWData, unsigned char ucWRS) //模拟SPI发送一个字节的命令或者数据给液晶模块的底层驱动 { SendByteToLcd( 0xf8 + (ucWRS << 1) ); SendByteToLcd( ucWData & 0xf0 ); SendByteToLcd( (ucWData << 4) & 0xf0); } void WriteCommand(unsigned char ucCommand) //发送一个字节的命令给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucCommand, 0); delay_short(90); } void LCDWriteData(unsigned char ucData) //发送一个字节的数据给液晶模块 { LCDCS_dr = 0; LCDCS_dr = 1; SPIWrite(ucData, 1); } void LCDInit(void) //初始化 函数内部包括液晶模块的复位 { LCDRST_dr = 1; //复位 LCDRST_dr = 0; LCDRST_dr = 1; } void delay_short(unsigned int uiDelayShort) //延时函数 { unsigned int i; for(i=0;i<uiDelayShort;i++) { ; } } void delay_long(unsigned int uiDelayLong) { unsigned int i; unsigned int j; for(i=0;i<uiDelayLong;i++) { for(j=0;j<500;j++) //内嵌循环的空指令数量 { ; //一个分号相当于执行一条空语句 } } }
总结陈词:
这一节讲了在一个窗口里设置不同的参数,如果有几个窗口的情况下,该如何编程?欲知详情,请听下回分解-----在多个窗口里通过移动光标来设置不同参数的液晶屏菜单程序。
(未完待续,下节更精彩,不要走开哦)
回复
有奖活动 | |
---|---|
【有奖活动——B站互动赢积分】活动开启啦! | |
【有奖活动】分享技术经验,兑换京东卡 | |
话不多说,快进群! | |
请大声喊出:我要开发板! | |
【有奖活动】EEPW网站征稿正在进行时,欢迎踊跃投稿啦 | |
奖!发布技术笔记,技术评测贴换取您心仪的礼品 | |
打赏了!打赏了!打赏了! |