OLED屏实现任意位置绘制图形

本文系博主原创,若转载请标明出处!

我们在LCD/OLED点阵屏上显示内容,纵坐标上都是以页(page)为单位进行操作。拿128x64的点阵屏为例,纵向为8个page,若有一个图标占用了2个page,那么在这个图标2个page的上下空白部分,不能显示其它内容。因为要在这个page的空白部分显示其它内容的话,会擦除这个page上已存在的内容。

有一种方法可以实现在空白部分显示其它内容,就是用并行接口去控制点阵屏,并行接口可以读取屏RAM的显示内容。我们进行写page之前,先把page里面的数据读出来,进行或操作之后再写,这样就保留了之前的图标内容。这种方法会占用MCU很多的IO口。

但是大多数产品设计的话都是使用SPI接口或者I2C接口去控制屏,很少用并行接口去控制屏,因为这样占用的IO资源更少。可是使用SPI或者I2C接口进行控制的话,是不能读取屏RAM数据的。这个时候想要在空白部分显示内容的话还有什么办法?

我现在所使用的办法是在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时,画的是横线。当然也可以用这个函数绘制长方形区域。

这个函数和清除显示内容的函数是一样的,只不过填充的数据不同。在使用的时候,可以修改一下,把这两个函数合并为一个函数。

你可能感兴趣的:(技术)