概述
本文系博主原创,若转载请标明出处!
我们在LCD/OLED点阵屏上显示内容,纵坐标上都是以页(page)为单位进行操作。拿128x64的点阵屏为例,纵向为8个page,若有一个图标占用了2个page,那么在这个图标2个page的上下空白部分,就不能显示其它内容了。因为要在这2个page的空白部分显示其它内容的话,会擦除这2个page上已存在的内容。
有一种方法可以实现在已使用page空白部分显示其它内容,就是用并行接口去控制点阵屏。并行接口可以读取屏RAM的显示内容,我们对page空白区域写数据之前,先把page里面的数据读出来,进行或操作之后再写,这样就保留了之前的图标内容。这种方法会占用MCU很多的IO口。
但是大多数产品设计都是使用SPI或者I2C接口控制屏,很少使用并行接口控制屏,因为这样占用CPU的IO资源更少。当使用SPI或者I2C接口进行控制时,是不能读取屏RAM数据的。这个时候想要在page空白部分显示内容的话还有什么办法?
我现在所使用的办法是在MCU里面创建一个和屏大小相同的数组
#define LCD_WIDTH 128
#define LCD_WIDTH_OFFSET 0
#define LCD_HIGH 64
/* 全局变量定义 */
/* clone lcd ddram */
unsigned char m_lcdVirtualRam[LCD_WIDTH * LCD_HIGH / 8]; /* 注意写数据时不要超过此数组最大值,否则会内存泄露,程序崩溃 */
写数据时同步写入到这个数组里。这个数组相当于屏RAM的镜像。当我需要读page内容的时候,只需要从这个数组里面读数据即可。
绘制点阵图形的函数如下
void LcdDriver_draw(
uint8_t _ucX,
uint8_t _ucY,
uint8_t _ucWidth,
uint8_t _ucHigh,
const uint8_t *_ptr
)
{
uint8_t i, j, h, y0, y1, hight;
uint8_t leftShift, rightShift;
uint8_t ucPageAddr;
uint8_t ucColAddr;
uint8_t ucValue;
uint8_t ucRam, ucData;
ucPageAddr = _ucY / 8;
ucColAddr = _ucX;
/* 计算高度值 */
hight = _ucHigh + _ucY;
leftShift = _ucY % 8;
rightShift = (8 - leftShift);
for (i = 0 ; i < (((_ucHigh + _ucY % 8) <= ((_ucHigh + 7) / 8) * 8) ? ( (_ucHigh + 7) / 8) : ((_ucHigh + 7) / 8 + 1)); i++)
{
LCD_Write_cmd (0xB0 + ucPageAddr); /* 设置页地址(0~7) */
LCD_Write_cmd (0x00 + ((ucColAddr + LCD_WIDTH_OFFSET) & 0x0F)); /* 设置列地址的低地址 */
LCD_Write_cmd (0x10 + (((ucColAddr + LCD_WIDTH_OFFSET) >> 4) & 0x0F)); /* 设置列地址的高地址 */
for (j = 0; j < _ucWidth && (j + _ucX) < LCD_WIDTH; j++)
{
/* 计算ucValue值 */
ucValue = 0x00;
ucValue += ((i > _ucHigh / 8) ? 0 : (_ptr[i * _ucWidth + j] << leftShift)) + ((i > 0) ? (_ptr[(i - 1) * _ucWidth + j] >> rightShift) : 0);
/* 读取虚拟屏内存中的数据 */
if ((ucPageAddr * LCD_WIDTH + ucColAddr + j) < (LCD_WIDTH * LCD_HIGH / 8))
{
ucRam = m_lcdVirtualRam[ucPageAddr * LCD_WIDTH + ucColAddr + j];
}
/* 根据行位置做与操作 */
ucData = 0;
y0 = ((i == 0) ? (_ucY % 8) : 0);
y1 = hight >= ((ucPageAddr + 1) * 8) ? 8 : (hight % 8);
for (h = 0; h < 8; h++)
{
if (h < y0 || h >= y1)
{
ucData += ucRam & (1 << h);
}
else
{
ucData += ucValue & (1 << h);
}
}
LCD_Write_data(ucData);
/* 更新虚拟屏内存数据 */
if ((ucPageAddr * LCD_WIDTH + ucColAddr + j) < (LCD_WIDTH * LCD_HIGH / 8))
{
m_lcdVirtualRam[ucPageAddr * LCD_WIDTH + ucColAddr + j] = ucData;
}
}
ucPageAddr++;
//_ucY = ucPageAddr * 8;
}
}
这个函数实现了在显示屏任意位置,绘制点阵图形的功能。点阵图形的高度不必为8的倍数,可以为任意值。如果一个图标的显示高度为13bit,那么这个图标会占用2个page,且这2个page的上或者下部会有空白数据。使用这个函数的话,这个图标只需要加载显示内容高度的数据即可,而不必加载2个page的多余空白数据再进行显示。又或者,我们现在常用的字符集高度为8bit或者16bit,使用这个函数的话,可以很方便的调用高度为12bit的字符集。
清除任意位置显示内容的函数如下
void LcdDriver_clear(
uint8_t _ucX,
uint8_t _ucY,
uint8_t _ucWidth,
uint8_t _ucHigh
)
{
uint8_t i, j, h, y0, y1, hight;
uint8_t leftShift, rightShift;
uint8_t ucPageAddr;
uint8_t ucColAddr;
uint8_t ucValue;
uint8_t ucRam, ucData;
ucPageAddr = _ucY / 8;
ucColAddr = _ucX;
/* 计算高度值 */
hight = _ucHigh + _ucY;
leftShift = _ucY % 8;
rightShift = (8 - leftShift);
for (i = 0 ; i < (((_ucHigh + _ucY % 8) <= ((_ucHigh + 7) / 8) * 8) ? ( (_ucHigh + 7) / 8) : ((_ucHigh + 7) / 8 + 1)); i++)
{
LCD_Write_cmd (0xB0 + ucPageAddr); /* 设置页地址(0~7) */
LCD_Write_cmd (0x00 + ((ucColAddr + LCD_WIDTH_OFFSET) & 0x0F)); /* 设置列地址的低地址 */
LCD_Write_cmd (0x10 + (((ucColAddr + LCD_WIDTH_OFFSET) >> 4) & 0x0F)); /* 设置列地址的高地址 */
for (j = 0; j < _ucWidth && (j + _ucX) < LCD_WIDTH; j++)
{
/* 计算ucValue值 */
ucValue = 0x00;
//ucValue += ((i > _ucHigh / 8) ? 0 : (_ptr[i * _ucWidth + j] << leftShift)) + ((i > 0) ? (_ptr[(i - 1) * _ucWidth + j] >> rightShift) : 0);
/* 读取虚拟屏内存中的数据 */
if ((ucPageAddr * LCD_WIDTH + ucColAddr + j) < (LCD_WIDTH * LCD_HIGH / 8))
{
ucRam = m_lcdVirtualRam[ucPageAddr * LCD_WIDTH + ucColAddr + j];
}
/* 根据行位置做与操作 */
ucData = 0;
y0 = ((i == 0) ? (_ucY % 8) : 0);
y1 = hight >= ((ucPageAddr + 1) * 8) ? 8 : (hight % 8);
for (h = 0; h < 8; h++)
{
if (h < y0 || h >= y1)
{
ucData += ucRam & (1 << h);
}
else
{
ucData += ucValue & (1 << h);
}
}
LCD_Write_data(ucData);
/* 更新虚拟屏内存数据 */
if ((ucPageAddr * LCD_WIDTH + ucColAddr + j) < (LCD_WIDTH * LCD_HIGH / 8))
{
m_lcdVirtualRam[ucPageAddr * LCD_WIDTH + ucColAddr + j] = ucData;
}
}
ucPageAddr++;
//_ucY = ucPageAddr * 8;
}
}
使用此方法的唯一缺点是要占用MCU内存,128x64的屏会占用1K的内存。如果是小内存的单片机,还是老老实实用并口连接的方式去读取page的内容。
另外,再增加一个画线的函数
void LcdDriver_drawLine(
uint8_t _ucX,
uint8_t _ucY,
uint8_t _ucWidth,
uint8_t _ucHigh
)
{
uint8_t i, j, h, y0, y1, hight;
uint8_t leftShift, rightShift;
uint8_t ucPageAddr;
uint8_t ucColAddr;
uint8_t ucValue;
uint8_t ucRam, ucData;
ucPageAddr = _ucY / 8;
ucColAddr = _ucX;
/* 计算高度值 */
hight = _ucHigh + _ucY;
leftShift = _ucY % 8;
rightShift = (8 - leftShift);
for (i = 0 ; i < (((_ucHigh + _ucY % 8) <= ((_ucHigh + 7) / 8) * 8) ? ( (_ucHigh + 7) / 8) : ((_ucHigh + 7) / 8 + 1)); i++)
{
LCD_Write_cmd (0xB0 + ucPageAddr); /* 设置页地址(0~7) */
LCD_Write_cmd (0x00 + ((ucColAddr + LCD_WIDTH_OFFSET) & 0x0F)); /* 设置列地址的低地址 */
LCD_Write_cmd (0x10 + (((ucColAddr + LCD_WIDTH_OFFSET) >> 4) & 0x0F)); /* 设置列地址的高地址 */
for (j = 0; j < _ucWidth && (j + _ucX) < LCD_WIDTH; j++)
{
/* 计算ucValue值 */
ucValue = 0xFF;
//ucValue += ((i > _ucHigh / 8) ? 0 : (_ptr[i * _ucWidth + j] << leftShift)) + ((i > 0) ? (_ptr[(i - 1) * _ucWidth + j] >> rightShift) : 0);
/* 读取虚拟屏内存中的数据 */
if ((ucPageAddr * LCD_WIDTH + ucColAddr + j) < (LCD_WIDTH * LCD_HIGH / 8))
{
ucRam = m_lcdVirtualRam[ucPageAddr * LCD_WIDTH + ucColAddr + j];
}
/* 根据行位置做与操作 */
ucData = 0;
y0 = ((i == 0) ? (_ucY % 8) : 0);
y1 = hight >= ((ucPageAddr + 1) * 8) ? 8 : (hight % 8);
for (h = 0; h < 8; h++)
{
if (h < y0 || h >= y1)
{
ucData += ucRam & (1 << h);
}
else
{
ucData += ucValue & (1 << h);
}
}
LCD_Write_data(ucData);
/* 更新虚拟屏内存数据 */
if ((ucPageAddr * LCD_WIDTH + ucColAddr + j) < (LCD_WIDTH * LCD_HIGH / 8))
{
m_lcdVirtualRam[ucPageAddr * LCD_WIDTH + ucColAddr + j] = ucData;
}
}
ucPageAddr++;
//_ucY = ucPageAddr * 8;
}
}
设置宽度为1时,画的是竖线;设置高度为1时,画的是横线。当然也可以用这个函数绘制长方形区域。
这个函数和清除显示内容的函数是一样的,只不过填充的数据不同。在使用的时候,可以修改一下,把这两个函数合并为一个函数。
图像取模方式设置如下:
上面的函数只适配此种取模方式。若是其它的取模方式,请自行修改函数内部的数据计算方法。
最后
以上就是害羞荷花为你收集整理的OLED屏实现任意位置绘制图形的全部内容,希望文章能够帮你解决OLED屏实现任意位置绘制图形所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复