可编辑12864液晶模组,也就是液晶显示屏是有128*64个点阵组成。12864是一种图形点阵液晶显示器,它主要采用动态驱动原理由行驱动—控制器和列驱动器两部分组成了128(列)×64(行)的全点阵液晶显示此显示器采用了COB的软封装方式,通过导电橡胶和压框连接LCD,使其寿命长,连接可靠。
产品特性编辑工作电压为+5V±10%,可自带驱动LCD所需的负电压,全屏幕点阵,点阵数为128(列)×64(行),可显示8(行)×4(行)个(16×16点阵)汉字,字符16(行)x8(行)个(8 .x 8)也可完成图形字符的显示,与CPU接口采用SPI通讯方式,驱动ST7567。
ST7567芯片可写入的页有0 ~ 8 共9页,列有132列,但LCD12864只用到了8页和64列
写入数据的点阵显示效果
通信方式有并行的6800和8080,以及串行的4线方式,本次实验使用了4线串行通信,CSB为片选引脚,数据传输前拉低CSB,A0引脚说明传输的是数据还是指令,如果A0为0,说明是指令,如果为1,说明是数据,SCL为时钟线,SDA为数据/指令线,SCL为低电平时放数据或指令,SCL为高电平是ST7567读取SDA上的电平信号
LX12864官方手册(带参考代码):https://download.csdn.net/download/weixin_46251230/86731882
1、LCD初始化函数中行扫描顺序的指令是0xC0,指令说明从上到下扫描,从屏幕上看则最下面是Page0,从下往上直到Page7
2、默认的g_ucComTable数组控制页的访问顺序,默认从Page3开始,若调用 LX12864_ShowChar(0,0,‘A’);函数显示一个字符时,因为字符A高16位,所以占两页,会在Page3开始显示,到Page2结束
3、若想Page0在屏幕的最上边,则初始化时行扫描顺序指令要改为0xC8
4、如果此时g_ucComTable数组不变,则也是从Page3开始显示,再往上显示到Page2,再调用LX12864_ShowChar(0,0,‘A’);则会显示倒着的A
5、如果把g_ucComTable数组改为{0,1,2,3,4,5,6,7},若调用LX12864_ShowChar(3,0,‘A’),则字符A也会倒着显示,并且字符A的上半部分倒着显示在Page3,下半部分倒着显示在Page4,不是想要的效果
6、重新用取模软件取模字符A,则会正常显示
对参考代码进行修改:
1、初始化函数中写入0xC8指令,控制Page0是在屏幕的最上面(根据屏幕实际摆放)
2、将g_ucComTable数组改为{0,1,2,3,4,5,6,7},或者后续可以把函数中的g_ucComTable操作去掉
3、把ASCII码全部重新取模一遍,因为参考代码的字模数组要用特定的数组来显示的,通用性不太好,自己进行修改
/**
* @name gpio_Init
* @brief GPIO初始化
* @param None
* @retval None
*/
void gpio_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
/* GPIOB Periph clock enable */
RCC_AHBPeriphClockCmd(CLK_GPIO, ENABLE);
/*Configure GPIO_PIN*/
GPIO_InitStructure.GPIO_Pin = BL|SI|SCL|A0|RS|CS;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(PORT, &GPIO_InitStructure);
GPIO_SetBits(PORT,CS);
GPIO_SetBits(PORT,SCL);
GPIO_SetBits(PORT,SI);
}
BL:背光
SI(SDA):数据/指令线
SCL:时钟线
A0:说明是数据还是指令
RS:复位
CS:片选
/**
* @name LX12864_WrCmd
* @brief 写命令
串口模式下只能写不能读,也不能查忙,因此用户要控制好速度不要太快
* @param ucCmd:命令字节
* @retval None
*/
void LX12864_WrCmd(uint8_t ucCmd)
{
uint8_t i;
GPIO_ResetBits(PORT,CS); /* CS片选引脚输出低电平 */
GPIO_ResetBits(PORT,A0); /* A0引脚输出低电平,说明是指令 */
for(i=0;i<8;i++)
{
GPIO_ResetBits(PORT,SCL);
if(ucCmd&0x80)
{
GPIO_SetBits(PORT,SI);
}
else
{
GPIO_ResetBits(PORT,SI);
}
GPIO_SetBits(PORT,SCL);
delay_us(10);
ucCmd=(ucCmd<<1);
}
GPIO_SetBits(PORT,CS);
}
/**
* @name LX12864_WrData
* @brief 写数据
* @param ucData:数据字节
* @retval None
*/
void LX12864_WrData(uint8_t ucData)
{
uint8_t i;
GPIO_ResetBits(PORT,CS); /* CS片选引脚输出低电平 */
GPIO_SetBits(PORT,A0); /* A0引脚输出高电平,说明是数据 */
for(i=0;i<8;i++)
{
GPIO_ResetBits(PORT,SCL);
if(ucData&0x80)
{
GPIO_SetBits(PORT,SI);
}
else
{
GPIO_ResetBits(PORT,SI);
}
GPIO_SetBits(PORT,SCL);
delay_us(10);
ucData=(ucData<<1);
}
GPIO_SetBits(PORT,CS);
}
/**
* @name LX12864_Init
* @brief LX12864初始化
* @param None
* @retval None
*/
void LX12864_Init(void)
{
GPIO_ResetBits(PORT,RS); /* 硬复位 */
delay_us(20);
GPIO_SetBits(PORT,RS);
delay_us(20);
LX12864_WrCmd(0xE2); /* 软复位 */
delay_us(50);
LX12864_WrCmd(0x2C); /* 升压步骤1 */
delay_us(50);
LX12864_WrCmd(0x2E); /* 升压步骤2 */
delay_us(50);
LX12864_WrCmd(0x2F); /* 升压步骤3 */
delay_us(50);
LX12864_WrCmd(0x24); /* 调整对比度,设置范围0x20~0x27 */
LX12864_WrCmd(0x81); /* 微调对比度 */
LX12864_WrCmd(0x1B); /* 微调对比度的值 */
LX12864_WrCmd(0xA3); /* 偏压比(bias),0xA2:1/9 0xA3:1=1/7 */
LX12864_WrCmd(0xA6); /* 正常显示 */
LX12864_WrCmd(0xA4); /* 全部点阵打开 */
LX12864_WrCmd(0xC8); /* 行扫描顺序:从上到下 */
LX12864_WrCmd(0xA0); /* 列扫描顺序:从左到右 */
LX12864_WrCmd(0x40); /* 起始行:第一行开始 */
LX12864_WrCmd(0xAF); /* 开显示 */
LX12864_Open_BL(); /* 显示背光 */
}
初始化命令中,设置对比度由两条指令组成,一定要先发送指令0x81,告诉ST7567接下来的一条指令是设置对比度的,再发送对比度的值,例如0x1B,由6位来控制;
设置行扫描顺序或者列扫描顺序时需要根据屏幕来设定,因为屏幕摆放的方向不同,则行列的显示也会不同
/**
* @name LX12864_FillScreen
* @brief 填充屏幕
* @param ucFillData:填充的数据
* @retval None
*/
void LX12864_FillScreen(uint8_t ucFillData)
{
uint16_t i,j;
for(i=0;i<8;i++)
{
LX12864_WrCmd(0xB0|i); /* 设置页地址 */
LX12864_WrCmd(0x10); /* 设置列地址的高位 */
LX12864_WrCmd(0x00); /* 设置列地址的低位 */
for(j=0;j<128;j++)
{
LX12864_WrData(ucFillData);
}
}
}
/**
* @name LX12864_FillPage
* @brief 填充一页
* @param ucFillData:填充的数据
* @retval None
*/
void LX12864_FillPage(uint8_t ucPage,uint8_t ucFillData)
{
uint16_t i;
LX12864_WrCmd(0xB0|ucPage); /* 设置页地址 */
LX12864_WrCmd(0x10); /* 设置列地址的高位 */
LX12864_WrCmd(0x00); /* 设置列地址的低位 */
for(i=0;i<128;i++)
{
LX12864_WrData(ucFillData);
}
}
/**
* @name LX12864_ShowHorLine
* @brief 显示一条横线
* @param ucPage:页地址 范围:0 ~ 7
* @param ucCol:列地址 范围:0 ~ 127
* @param ucLen:长度 范围:0 ~ 127
* @param ucData:数据
* @retval None
*/
void LX12864_ShowHorLine(uint8_t ucPage,
uint8_t ucCol,
uint8_t ucLen,
uint8_t ucData)
{
uint8_t i;
LX12864_WrCmd((ucPage&0x07)|0xB0); /* 设置页地址 */
LX12864_WrCmd((ucCol>>4)|0x10); /* 设置列地址高位 */
LX12864_WrCmd(ucCol&0x0F); /* 设置列地址低位 */
for(i=0;i<ucLen;i++)
{
LX12864_WrData(ucData);
}
}
/**
* @name LX12864_ShowVerLine
* @brief 显示一条竖线
* @param ucStartPage:起始页地址 范围:0 ~ 7
* @param ucCol:列地址
* @param ucEndPage:结束页地址 范围:0 ~ 7 注意:结束页地址要大于或等于起始页地址
* @retval None
*/
void LX12864_ShowVerLine(uint8_t ucStartPage,
uint8_t ucCol,
uint8_t ucEndPage)
{
uint8_t i,ucTemp;
ucTemp = (ucEndPage - ucStartPage)+1;
for(i=0;i<ucTemp;i++)
{
LX12864_WrCmd((ucStartPage&0x07)|0xB0); /* 设置页地址 */
LX12864_WrCmd((ucCol>>4)|0x10); /* 设置列地址高位 */
LX12864_WrCmd(ucCol&0x0F); /* 设置列地址低位 */
LX12864_WrData(0xFF);
ucStartPage++;
}
}
typedef enum
{
g_enBigNumber,
g_enSmallNumber,
}__ENCHARARRAY_T;
/**
* @name LX12864_ShowChar
* @brief 显示字符
* @param ucPage:页地址 范围:0 ~ 7
* @param ucCol:列地址 范围:0 ~ 127
* @param ucWidth:字体宽度
* @param ucHight:字体高度
* @param ucChar:字符
* @param ucCharArray:字模数组
* @retval None
*/
void LX12864_ShowChar(uint8_t ucPage,
uint8_t ucCol,
uint8_t ucWidth,
uint8_t ucHight,
uint8_t ucChar,
__ENCHARARRAY_T ucCharArray)
{
uint8_t i,j,ucNeedPage;
uint16_t ucIndex = 0;
ucNeedPage = ucHight / 8;
if((ucHight%8) != 0)
{
ucNeedPage = ucNeedPage+1; /* 加上不够一页的部分 */
}
for(i=0;i<ucNeedPage;i++)
{
LX12864_WrCmd((ucPage&0x07)|0xB0); /* 设置页地址 */
LX12864_WrCmd((ucCol>>4)|0x10); /* 设置列地址高位 */
LX12864_WrCmd(ucCol&0x0F); /* 设置列地址低位 */
for(j=0;j<ucWidth;j++)
{
if(ucCharArray == g_enBigNumber)
{
LX12864_WrData(g_ucBigNumber[ucChar][ucIndex]); /* 大字体字模显示字符 */
ucIndex++;
}
else if(ucCharArray == g_enSmallNumber)
{
LX12864_WrData(g_ucSmallNumber[ucChar][ucIndex]); /* 小字体字模显示字符 */
ucIndex++;
}
}
ucPage++;
}
}
设置页地址时,参数ucPage为想要显示字符的页数,因为页被分为了0 ~ 7页, 一共8页,只用3位二进制位即可遍历8页,28 = 3,所以&0x07是为了取出ucPage的低3位,再或上0xB0,就设置了想要显示的页
/* 小字体的字模数组 */
unsigned char g_ucSmallNumber[][24]={
{0xC0,0xE0,0x70,0x30,0x30,0x70,0xE0,0xC0,0x3F,0x7F,0xE0,0xC0,0xC0,0xE0,0x7F,0x3F,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"0",0*/
/* (8 X 17 , Arial, 加粗 )*/
{0x00,0x80,0xC0,0x60,0xF0,0xF0,0x00,0x00,0x00,0x01,0x00,0x00,0xFF,0xFF,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"1",1*/
/* (8 X 17 , Arial, 加粗 )*/
{0xC0,0xE0,0x70,0x30,0x30,0x30,0xE0,0xC0,0xC0,0xE0,0xF0,0xD8,0xDC,0xCE,0xC7,0xC1,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"2",2*/
…………………………
};
用PCtoLCD2002取模软件取出想要显示的字符,本次实验用二维数组保存字模数据,根据参数ucChar作为下标去找到要显示的字符的一位数组,再遍历数组写入数据
/**
* @name Pow
* @brief 求sBase的sPower次方
* @param sBase:底数
* @param sPower:次方数
* @retval 返回次方后的结果
*/
int16_t Pow(int16_t sBase,int16_t sPower)
{
uint8_t i;
int16_t sResult = 1;
for(i = 0; i < sPower; i++)
{
sResult *= sBase;
}
return sResult;
}
/**
* @name LX12864_ShowVariableNum
* @brief 显示有符号的整型数
* @param ucPage:页地址,范围:0 ~ 7
* @param ucCol:列地址,范围:0 ~ 127
* @param ucWidth:字符宽度
* @param ucHight:字符高度
* @param ucNumLen:待显示数字的位数
* @param sNum:待显示的数字
* @param ucCharArray:字模数组
* @retval None
*/
void LX12864_ShowSignedNum(uint8_t ucPage,
uint8_t ucCol,
uint8_t ucWidth,
uint8_t ucHight,
uint8_t ucNumLen,
int16_t sNum,
__ENCHARARRAY_T ucCharArray)
{
uint8_t i;
int16_t sNumTemp,sResult;
if(sNum >= 0)
{
LX12864_ShowChar(ucPage,ucCol-(ucWidth+2),ucWidth,ucHight,11,ucCharArray); /* 11对应空白,如果是正数则去掉负号 */
sNumTemp = sNum;
}
else
{
LX12864_ShowChar(ucPage,ucCol-(ucWidth+2),ucWidth,ucHight,10,ucCharArray); /* 10对应字符'-',负数则在前面加上负号 */
sNumTemp = -sNum;
}
for(i=ucNumLen;i>0;i--)
{
sResult = sNumTemp/Pow(10,i-1)%10; /* 从高位开始,取出数字的每一位 */
LX12864_ShowChar(ucPage,ucCol+((ucWidth+2)*(ucNumLen-i)),ucWidth,ucHight,sResult,ucCharArray);/*显示数字 */
}
}
其中Pow函数的用法参考了之前写的LCD1602代码,可以参考
http://t.csdn.cn/mMBIA
图形需要用取模软件生成
/**
* @name LX12864_ShowBmp
* @brief 显示图形
* @param ucPage:页地址 范围:0 ~ 7
* @param ucCol:列地址 范围:0 ~ 127
* @param ucWidth:图形宽度
* @param ucHight:图形高度
* @param pucBmp:图形字模数组指针
* @retval None
*/
void LX12864_ShowBmp(uint8_t ucPage,
uint8_t ucCol,
uint8_t ucWidth,
uint8_t ucHight,
uint8_t* pucBmp)
{
uint8_t i,j,ucNeedPage;
uint16_t ucIndex = 0;
ucNeedPage = ucHight / 8; /* 所需页数 */
if((ucHight%8) != 0)
{
ucNeedPage = ucNeedPage+1; /* 加上不够一页的部分 */
}
for(i=0;i<ucNeedPage;i++)
{
LX12864_WrCmd((ucPage&0x07)|0xB0); /* 设置页地址 */
LX12864_WrCmd((ucCol>>4)|0x10); /* 设置列地址高位 */
LX12864_WrCmd(ucCol&0x0F); /* 设置列地址低位 */
for(j=0;j<ucWidth;j++)
{
LX12864_WrData(pucBmp[ucIndex]); /* 显示图形 */
ucIndex++;
}
ucPage++;
}
}
实现亮一会,暗一会主要通过修改对比度来实现
在LCD_Init初始化函数中,有对比度的设置
LX12864_WrCmd(0x81); /* 微调对比度 */
LX12864_WrCmd(0x1B); /* 微调对比度的值 */
要先写入指令0x81,说明是后续的一个字节是修改对比度的
后续这个字节的是6位的,EV5和EV4的值会影响屏幕显示,有下面三种情况
0x1B:
正常显示(屏幕有点坏)
0x2B:
会显示填充整个128x64屏幕的黑点,“腾讯QQ”显示会比较暗,正面看看不清
如果从侧面看则可以看清
0x3B:
正面看看不到显示内容,屏幕全填充
所以调整对比度就只能设置EV3 ~ EV0这4位,高两位的EV4设置为1,EV5设置为0,才能正常显示
经过测试,0x1F对比度最高,显示内容很清晰,值越小则对比度越低,显示内容逐渐变暗,0x10时几乎看不见显示的内容
在主函数循环中,通过改变对比度的值,再加上适当的延时,就可以让显示内容亮一会,暗一会
/*
* @name Run
* @brief 系统运行
* @param None
* @retval None
*/
void Run(void)
{
LX12864_WrCmd(0x81); /* 微调对比度 */
LX12864_WrCmd(0x1B); /* 亮 */
LCD_ShowBmp(g_ucBmp1);
delay_ms(2000);
delay_ms(2000);
LX12864_WrCmd(0x81); /* 微调对比度 */
LX12864_WrCmd(0x15); /* 微亮 */
LCD_ShowBmp(g_ucBmp1);
delay_ms(2000);
delay_ms(2000);
}
右上角的WiFi显示一会亮,一会灭
实现思路:因为改变对比度是对一整个屏幕而言的,没发现有对某一部分屏幕改变对比度的方法,所以对WiFi显示的字模数组全部改为0x00,则WiFi图形这一部分就显示空白,WiFi图形显示1秒后再写入全是0x00的字模数组,则可以对WiFi图形擦除,擦除1秒后再显示,则可以达到闪烁的目标