好记性不如铅笔头

编程

oLED绘制简单笔记:使用绘制缓存

一般我们绘制oLED时,都会考虑在内存中维护一个相同尺寸的缓存,在缓存上进行绘制,然后同步到oLED上。这里简单笔记下一些思路和代码。
假如我们需要绘制一个128*32大小的屏幕,每个像素点为单bit控制,那么我们可以建立一个如下buffer:

unsigned char oled_ram[128][4];

CONTENTS

绘制点

绘制点比较简单,直接找到对应的bit位置进行设置即可。

int oled_ram_point(int x, int y, oled_draw_mode_e mode)
{
    if((x < 0)||(x >= 128)||(y < 0)||(y >= (4*8)))
    {
        return -1;
    }
    if(mode == OLED_DRAW_FILL)
    {
        oled_ram[x][y>>3] |= (1<<(y&0x7));
    }else
    {
        oled_ram[x][y>>3] &= (0xFF-(1<<(y&0x7)));
    }
    return 0;
}

绘制线

绘制水平和垂直线比较简单,但是如果要绘制任意角度,就比较复杂了。这里笔记一种思路:
假如我们要绘制一条任意角度的线,
1 计算起点(x1,y1)和终点(x2,y2)的水平间距x_diff(x1 – x2)和垂直间距y_diff(y1 – y2)。
2 如果一个点(x,y)在该线上,那么肯定有 (x – x1)/(y – y1) = x_diff / y_diff。
3 假如x_diff和y_diff的最大公约数为m,即
x_diff = x_diff_uint * m; y_diff = y_diff_uint * m;
那么,对于任一点,只要
x = x1 – x_diff_uint * n; y = y1 – y_diff_uint * n; 0=<n<=m
那么该点(x,y)就会在该线上。

static int divisor(int a, int b) 
{
	int temp;
    //比较两个数的大小,值大的数为a,值小的数为b
	if (a < b) 
    {
		temp = a; a = b; b = temp;
	}
	//求余
	while (b != 0) 
    {
		temp = a % b;
		a = b;
		b = temp;
	}
	return a;
}

int oled_ram_line(int x1, int y1, int x2, int y2, oled_draw_mode_e mode)
{
    int x_diff = x1 - x2;
    int y_diff = y1 - y2;
    int node_cnt = 0;
    if(x_diff == 0)
    {
        node_cnt = y1 > y2 ? (y1-y2):(y2-y1);
    }else if(y_diff == 0)
    {
        node_cnt = x1 > x2 ? (x1-x2):(x2-x1);
    }else
    {
        node_cnt = divisor(x_diff, y_diff);
        node_cnt = (node_cnt >= 0 ? node_cnt : (0 - node_cnt)) ;
    }

    x_diff = x_diff / node_cnt;
    y_diff = y_diff / node_cnt;

    int index = 0;
    for (index = 0; index <= node_cnt; index++)
    {
        oled_ram_point(x1 - index*x_diff, y1 - index*y_diff, mode);
    }

    return 0;
}

绘制圆形

绘制圆形比较简单,这里用一张图片来显示思路,如下图,考虑到圆的对称性,对于任意一个在圆上的点(黄色),都有另外7个点也在圆上(蓝色)。因此我们只要找到45度内的点就可以了。从红色点(a,b)开始,沿着绿色箭头方向进行偏移,一直到达绿色点,符合条件的点就可以绘制为圆形。

int oled_ram_circle(int x, int y, int radius, oled_draw_mode_e mode)
{
	int a = 0;
    int b = radius;
    while(2 * b * b >= radius * radius)      
    {
        if(a*a + b*b >= radius*radius)
        {
            oled_ram_point(x + a, y - b, mode);
            oled_ram_point(x - a, y - b, mode);
            oled_ram_point(x + a, y + b, mode);
            oled_ram_point(x - a, y + b, mode);
            oled_ram_point(x + b, y + a, mode);
            oled_ram_point(x + b, y - a, mode);
            oled_ram_point(x - b, y - a, mode);
            oled_ram_point(x - b, y + a, mode);
            b--;
        }else
        {
            a++;
        }
    }
    return 0;
}

绘制ASCII

ASCII码有很多取模方式,这里简单起见,以 阴码 行列式 低位在前的方式取模,比如一个16*8的字A,在字库数组中,A使用一个16个字节长度的数组来表示,数组索引如下图:

代码如下:

int oled_ram_ascii(int x, int y, unsigned char ascii_value, oled_ascii_size_e ascii_size, oled_draw_mode_e mode)
{
    int asc_hight = 0; 
    int asc_width = 0; 
    unsigned char *p_asc_buf = NULL;

    ascii_value = ascii_value - ' ';
    switch (ascii_size)
    {
        default:
        case OLED_ASCII_8x6:   { asc_hight = 8;  asc_width = 6;  p_asc_buf = asc2_0806[ascii_value]; break; }
        case OLED_ASCII_12x6:  { asc_hight = 12; asc_width = 6;  p_asc_buf = asc2_1206[ascii_value]; break; }
        case OLED_ASCII_16x8:  { asc_hight = 16; asc_width = 8;  p_asc_buf = asc2_1608[ascii_value]; break; }
        case OLED_ASCII_24x12: { asc_hight = 24; asc_width = 12; p_asc_buf = asc2_2412[ascii_value]; break; }
    }
    int start_x = 0;
    int start_y = 0;
    for(start_x = 0; start_x < asc_width; start_x++)
    {
        for(start_y = 0; start_y < asc_hight; start_y++)
        {
            if((p_asc_buf[start_x + asc_width * (start_y >> 3)] >> (start_y & 0x07)) & 0x01 == 1)
            {
                oled_ram_point(x + start_x, y+ start_y, mode);
            }else
            {
                oled_ram_point(x + start_x, y+ start_y, OLED_DRAW_FILL - mode);
            }
        }
    }
    return 0;
}

int oled_ram_str(int x, int y, char *p_str, oled_ascii_size_e str_size, oled_draw_mode_e mode)
{ 
    int asc_width = 0; 
    switch (str_size)
    {
        default:
        case OLED_ASCII_8x6:   { asc_width = 6;  break; }
        case OLED_ASCII_12x6:  { asc_width = 6;  break; }
        case OLED_ASCII_16x8:  { asc_width = 8;  break; }
        case OLED_ASCII_24x12: { asc_width = 12; break; }
    }
    int start_x = x;
    while (*p_str != 0)
    {
        oled_ram_ascii(start_x, y, *p_str, str_size, mode);
        start_x += asc_width;
        p_str++;
    }
    return 0;
}

内存dump

为了方便调试,我们可以使用日志方式来模拟oLED输出,代码如下:

int oled_ram_dump(void)
{
    INFO("--- oled ram memory ---");
    int i,n,k;
	for(i=0; i<4; i++)
	{
        for(k = 0; k<8; k++)
        {
            RAW("<%2d>: ", i*8 + k);
            for(n=0; n<128; n++)
            {
                unsigned char value = oled_ram[n][i];
                RAW("%c", ((value>>k) & 0x1) ? '*':' ');
            }
            RAW("\r\n");
        }
    }

    RAW("\r\n\r\n");
    return 0;
}

打印输出如下:

< 0>:
< 1>:
< 2>:
< 3>:
< 4>:                                                                *                                                    *
< 5>:                                                      **      ***                                      *            **       ****
< 6>:                              *                       **       **                                     **           ***      **  *
< 7>:                              *                                **                                  *****           ***     **
< 8>:                             **                                **                                     **          * **     **
< 9>:                             **                                **                                     **         *  **    **
<10>:     ****        *******  ********   *****  ****   *****       **  ****       ****   *****  ****      **         *  **    **
<11>:   **   **      **    **     **         ** *  **      **       **   *       **   **     ** *  **      **        *   **    **
<12>:   **   **     **      *     **         ***           **       **  **       *     **    ***           **       *    **    **
<13>:  **    **     **            **         **            **       **  *       **     **    **            **       *    **    **
<14>:  **            ***          **         **            **       ** **       *********    **            **      *     **    **
<15>:  **             *****       **         **            **       *****       **           **            **      **********  **
<16>:  **                ***      **         **            **       *** **      **           **            **            **    **
<17>:  **      *    *      **     **         **            **       **  **      **           **            **            **     **
<18>:   **     *    *      **     **   *     **            **       **   **      **     *    **            **            **     **
<19>:   **    *     **    **      **   *     **            **       **   **      ***   *     **            **            **      **  *
<20>:     ****      *******        ****   ********      ********   ****  ****      ****   ********      ********       ******     ****
<21>:
<22>:
<23>:
<24>:               *            *   *                   *      *   ***  *****
<25>:               *                *                  **     **  *   *     *
<26>:   ***   ***  ***   * **   **   *  *   ***  * **    *    * *  *  **    *
<27>:  *     *      *    **  *   *   * *   *   * **  *   *   *  *  * * *   *
<28>:  *      ***   *    *       *   **    ***** *       *   ***** **  *  *
<29>:  *   *     *  *  * *       *   * *   *     *       *      *  *   *  *
<30>:   ***  ****    **  *      ***  *  *   ***  *      ***     *   ***   *
<31>:

 

发表评论

2 × 5 =

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据