OLED12864(SSD1306)驱动代码

1. 准备工作介绍

我所使用的是 0.96 寸,I2C 接口的 OLED 屏幕。这款屏幕所使用的驱动芯片是 SSD1306 ,关于这款 OLED 驱动芯片的详细介绍可以参考下面这篇文章的介绍。

SSD1306(OLED驱动芯片介绍)

硬件平台测试平台我使用的是 STM32F407ZGT6 芯片的开发板。开发板和 OLED 屏幕的硬件连接引脚如下:

硬件平台连接描述:
    
SSD1306(屏幕)    |STM32F4xx(开发板)    |DESCRIPTION
VCC             |3.3V                |
GND             |GND                 |
SCL             |PB10                |Serial clock line
SDA             |PB11                |Serial data line

关于 STM32F407 芯片 I2C 外设初始化相关的代码,直接使用 CubeMX 生成即可。

下面关于OLED绘图显示的相关代码,是参考了国外一位大佬的github仓库的,链接如下:

https://github.com/MaJerle/stm32f429

2. OLED驱动代码

我所使用的 OLED 屏幕是 I2C 接口的,首先需要把通过 I2C 接口发送数据给 SSD1306 的驱动代码写好,这样才好实现后面的显示功能的代码。

使用 HAL 库驱动 SSD1306 的代码如下:

/*
 * 函数作用    : 通过I2C接口发送一个字节数据给SSD1306
 * 参数  reg   : 发送数据之前,需要先发送一个字节数据用于区分发送的是命令还是数据。其中先发送0x00,表示发送命令;先发送0x40,表示发送数据
 * 参数  data  : 要发送的一字节数据
 * 返回值      : 无
 */
static void SSD1306_I2C_Write(uint8_t reg, uint8_t data)
{
    
    
    uint8_t tmp_array[2] = {
    
    reg, data};
    HAL_I2C_Master_Transmit(&hi2c2, SSD1306_I2C_ADDR, tmp_array, sizeof(tmp_array), 1);
}

static void SSD1306_Write_Cmd(uint8_t cmd)
{
    
    
    SSD1306_I2C_Write(0x00, cmd);
}

static void SSD1306_Write_Data(uint8_t data)
{
    
    
    SSD1306_I2C_Write(0x40, data);
}

static void SSD1306_Write_MultiData(uint8_t *data, uint16_t size)
{
    
    
	uint8_t tmp_array[SSD1306_WIDTH+1] = {
    
    0x40, 0};

    memcpy(&tmp_array[1], data, size);
	
    HAL_I2C_Master_Transmit(&hi2c2, SSD1306_I2C_ADDR, tmp_array, sizeof(tmp_array), 10);
}

/* 打开显示 */
void SSD1306_ON(void) 
{
    
    
    SSD1306_Write_Cmd(0x8D);
    SSD1306_Write_Cmd(0x14);
    SSD1306_Write_Cmd(0xAF);
}

/* 关闭显示 */
void SSD1306_OFF(void) 
{
    
    
    SSD1306_Write_Cmd(0x8D);
    SSD1306_Write_Cmd(0x10);
    SSD1306_Write_Cmd(0xAE);
}

3. OLED显示字符串代码

3.1 OLED屏幕显示原理

屏幕是由一个个的像素点构成的,要想让屏幕显示出内容,只要按照规律点亮屏幕像素点即可。

所以,要想让 OLED 显示出文字或者绘图等功能,我们可以通过 I2C 接口不断发送要显示的数据给 SSD1306 驱动芯片即可。这种方法好处是可以节省 MCU 的内存,因为不用在MCU内部使用 RAM 建立一块显存,只是要显示什么内容,立即通过 I2C 接口发送数据过去即可。

还有一种方式是,在 MCU 内部建立一块用于显示的缓存,之后我们要显示什么内存,先修改这块显存的数据,然后一次性的通过 I2C 接口把全部显示数据发送到 SSD1306 。这种方式不好的地方就是费内存,但是优势是可以一次性的吧显存数据全部发送到屏幕上显示。

我下面写的代码,就是在 MCU 内部建立一个显存,以后要显示什么内容都操作这块内存了,然后一次性的把显存数据发送到 SSD1306。代码如下:

/* SSD1306 width in pixels */
#define SSD1306_WIDTH            128

/* SSD1306 LCD height in pixels */
#define SSD1306_HEIGHT           64

/* SSD1306 data buffer */
static uint8_t SSD1306_Buffer[SSD1306_WIDTH * SSD1306_HEIGHT / 8];

0.96寸的OLED一共有 128x64 个像素点(128x64 bit),所以需要建立一块 1Kb 的内存用作显存使用。

3.2 OLED初始化代码

初始化相关代码看厂商提供的手册拿过来用就行。

void SSD1306_Init(void) 
{
    
    
    /* A little delay */
    HAL_Delay(100);

    /* Init LCD */
    SSD1306_Write_Cmd(0xAE); //display off
    SSD1306_Write_Cmd(0x20); //Set Memory Addressing Mode
    SSD1306_Write_Cmd(0x10); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid
    SSD1306_Write_Cmd(0xB0); //Set Page Start Address for Page Addressing Mode,0-7
    SSD1306_Write_Cmd(0xC8); //Set COM Output Scan Direction
    SSD1306_Write_Cmd(0x00); //---set low column address
    SSD1306_Write_Cmd(0x10); //---set high column address
    SSD1306_Write_Cmd(0x40); //--set start line address
    SSD1306_Write_Cmd(0x81); //--set contrast control register
    SSD1306_Write_Cmd(0xFF);
    SSD1306_Write_Cmd(0xA1); //--set segment re-map 0 to 127
    SSD1306_Write_Cmd(0xA6); //--set normal display
    SSD1306_Write_Cmd(0xA8); //--set multiplex ratio(1 to 64)
    SSD1306_Write_Cmd(0x3F); //
    SSD1306_Write_Cmd(0xA4); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content
    SSD1306_Write_Cmd(0xD3); //-set display offset
    SSD1306_Write_Cmd(0x00); //-not offset
    SSD1306_Write_Cmd(0xD5); //--set display clock divide ratio/oscillator frequency
    SSD1306_Write_Cmd(0xF0); //--set divide ratio
    SSD1306_Write_Cmd(0xD9); //--set pre-charge period
    SSD1306_Write_Cmd(0x22); //
    SSD1306_Write_Cmd(0xDA); //--set com pins hardware configuration
    SSD1306_Write_Cmd(0x12);
    SSD1306_Write_Cmd(0xDB); //--set vcomh
    SSD1306_Write_Cmd(0x20); //0x20,0.77xVcc
    SSD1306_Write_Cmd(0x8D); //--set DC-DC enable
    SSD1306_Write_Cmd(0x14); //
    SSD1306_Write_Cmd(0xAF); //--turn on SSD1306 panel

    /* Clear screen */
    SSD1306_Fill(SSD1306_COLOR_BLACK);

    /* Update screen */
    SSD1306_UpdateScreen();

    /* Set default values */
    SSD1306.CurrentX = 0;
    SSD1306.CurrentY = 0;

    /* Initialized OK */
    SSD1306.Initialized = 1;
}

3.3 操作显示缓冲区的几个重要函数

/* 更新屏幕显示,当我们把显示缓冲区数据改写了之后,必须调用这个函数才能在屏幕更新显示 */
void SSD1306_UpdateScreen(void) 
{
    
    
    uint8_t page;

    for (page = 0; page < 8; page++) 
    {
    
    
        SSD1306_Write_Cmd(0xB0 + page);
        SSD1306_Write_Cmd(0x00);
        SSD1306_Write_Cmd(0x10);

        /* Write multi data */
        SSD1306_Write_MultiData(&SSD1306_Buffer[SSD1306_WIDTH * page], SSD1306_WIDTH);
    }
}

/* 反转屏幕显示(就是黑白颜色对调) */
void SSD1306_ToggleInvert(void) 
{
    
    
    uint16_t i;

    /* Toggle invert */
    SSD1306.Inverted = !SSD1306.Inverted;

    /* Do memory toggle */
    for (i = 0; i < sizeof(SSD1306_Buffer); i++) 
    {
    
    
        SSD1306_Buffer[i] = ~SSD1306_Buffer[i];
    }
}

/* 填充显存,比如让屏幕全部点亮或者熄灭 */
void SSD1306_Fill(SSD1306_COLOR_t color) 
{
    
    
    /* Set memory */
    memset(SSD1306_Buffer, (color == SSD1306_COLOR_BLACK) ? 0x00 : 0xFF, sizeof(SSD1306_Buffer));
}

3.4 绘制任意像素点函数

绘制任意点的像素函数,这是后面写的所有绘图函数的基础,后面的显示字符、画线、画圆、填充图案等等绘图函数都是基于这个绘制任意点函数的基础上的。

/**
 * @brief  SSD1306 color enumeration
 */
typedef enum {
    
    
    SSD1306_COLOR_BLACK = 0x00, /*!< Black color, no pixel */
    SSD1306_COLOR_WHITE = 0x01  /*!< Pixel is set. Color depends on LCD */
} SSD1306_COLOR_t;

void SSD1306_DrawPixel(uint16_t x, uint16_t y, SSD1306_COLOR_t color) 
{
    
    
    if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) 
    {
    
    
        /* Error */
        return;
    }

    /* Check if pixels are inverted */
    if (SSD1306.Inverted) 
    {
    
    
        color = (SSD1306_COLOR_t)!color;
    }

    /* Set color */
    if (color == SSD1306_COLOR_WHITE) 
    {
    
    
        SSD1306_Buffer[x + (y / 8) * SSD1306_WIDTH] |= 1 << (y % 8);
    } 
    else 
    {
    
    
        SSD1306_Buffer[x + (y / 8) * SSD1306_WIDTH] &= ~(1 << (y % 8));
    }
}

3.5 在屏幕上显示一个字符

要想在屏幕显示一个字符,需要有字体库。字体库的定义不同,对于显示字符的函数实现也是不一样的,如果用了其他的字体库定义,可能导致该函数并不能完成显示字符的功能。

字体库这里使用一个结构体对字库进行封装起来,包括字库的宽高,还有指向字库数组的指针。

/**
 * @brief  Font structure used on my LCD libraries
 */
typedef struct {
    
    
    uint8_t FontWidth;    /*!< Font width in pixels */
    uint8_t FontHeight;   /*!< Font height in pixels */
    const uint16_t* data; /*!< Pointer to data font data array */
} TM_FontDef_t;

/*
 * 函数作用 : 在屏幕上显示一个字符
 * 参数  ch    : 要显示的字符
 * 参数  Font  : 字体
 * 参数  color : 颜色
 * 返回值 : 无
 */
char SSD1306_Putc(char ch, TM_FontDef_t* Font, SSD1306_COLOR_t color) 
{
    
    
    uint32_t i, b, j;

    /* Check available space in LCD */
    if (SSD1306_WIDTH <= (SSD1306.CurrentX + Font->FontWidth) ||
        SSD1306_HEIGHT <= (SSD1306.CurrentY + Font->FontHeight)) 
    {
    
    
        /* Error */
        return 0;
    }

    /* Go through font */
    for (i = 0; i < Font->FontHeight; i++) 
    {
    
    
        b = Font->data[(ch - 32) * Font->FontHeight + i];
        for (j = 0; j < Font->FontWidth; j++) 
        {
    
    
            if ((b << j) & 0x8000) 
            {
    
    
                SSD1306_DrawPixel(SSD1306.CurrentX + j, (SSD1306.CurrentY + i), (SSD1306_COLOR_t) color);
            } 
            else 
            {
    
    
                SSD1306_DrawPixel(SSD1306.CurrentX + j, (SSD1306.CurrentY + i), (SSD1306_COLOR_t)!color);
            }
        }
    }

    /* Increase pointer */
    SSD1306.CurrentX += Font->FontWidth;

    /* Return character written */
    return ch;
}

3.6 显示字符串

有了显示字符的函数,显示字符串就简单多了。

char SSD1306_Puts(char* str, TM_FontDef_t* Font, SSD1306_COLOR_t color) 
{
    
    
    /* Write characters */
    while (*str) 
    {
    
    
        /* Write character by character */
        if (SSD1306_Putc(*str, Font, color) != *str) {
    
    
            /* Return error */
            return *str;
        }

        /* Increase string pointer */
        str++;
    }

    /* Everything OK, zero should be returned */
    return *str;
}

4. OLED绘图相关代码

绘图的实现,主要就是操作显存的数据,然后再把数据一次性的更新到屏幕上。这里绘图的代码主要实现了画点、任意线、四边形、三角形、圆形等。

这部分的代码实现,都要依赖于前面实现的绘制任意点函数。

4.1 绘制任意线

/* 
 * 参数:x0, y0是起点坐标
 *      x1, y1是终点坐标
 *      c 是绘制的颜色 
 */
void SSD1306_DrawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, SSD1306_COLOR_t c) 
{
    
    
    int16_t dx, dy, sx, sy, err, e2, i, tmp;

    /* Check for overflow */
    if (x0 >= SSD1306_WIDTH) 
    {
    
    
        x0 = SSD1306_WIDTH - 1;
    }
    if (x1 >= SSD1306_WIDTH) 
    {
    
    
        x1 = SSD1306_WIDTH - 1;
    }
    if (y0 >= SSD1306_HEIGHT) 
    {
    
    
        y0 = SSD1306_HEIGHT - 1;
    }
    if (y1 >= SSD1306_HEIGHT) 
    {
    
    
        y1 = SSD1306_HEIGHT - 1;
    }

    dx = (x0 < x1) ? (x1 - x0) : (x0 - x1);
    dy = (y0 < y1) ? (y1 - y0) : (y0 - y1);
    sx = (x0 < x1) ? 1 : -1;
    sy = (y0 < y1) ? 1 : -1;
    err = ((dx > dy) ? dx : -dy) / 2;

    if (dx == 0) 
    {
    
    
        if (y1 < y0) 
        {
    
    
            tmp = y1;
            y1 = y0;
            y0 = tmp;
        }

        if (x1 < x0) 
        {
    
    
            tmp = x1;
            x1 = x0;
            x0 = tmp;
        }

        /* Vertical line */
        for (i = y0; i <= y1; i++) 
        {
    
    
            SSD1306_DrawPixel(x0, i, c);
        }

        /* Return from function */
        return;
    }

    if (dy == 0) 
    {
    
    
        if (y1 < y0) 
        {
    
    
            tmp = y1;
            y1 = y0;
            y0 = tmp;
        }

        if (x1 < x0) 
        {
    
    
            tmp = x1;
            x1 = x0;
            x0 = tmp;
        }

        /* Horizontal line */
        for (i = x0; i <= x1; i++) 
        {
    
    
            SSD1306_DrawPixel(i, y0, c);
        }

        /* Return from function */
        return;
    }

    while (1) 
    {
    
    
        SSD1306_DrawPixel(x0, y0, c);
        if (x0 == x1 && y0 == y1) 
        {
    
    
            break;
        }
        e2 = err;
        if (e2 > -dx) 
        {
    
    
            err -= dy;
            x0 += sx;
        }
        if (e2 < dy) 
        {
    
    
            err += dx;
            y0 += sy;
        }
    }
}

4.2 绘制四边形

void SSD1306_DrawRectangle(uint16_t x, uint16_t y, uint16_t w, uint16_t h, SSD1306_COLOR_t c) 
{
    
    
    /* Check input parameters */
    if (x >= SSD1306_WIDTH || y >= SSD1306_HEIGHT) 
    {
    
    
        /* Return error */
        return;
    }

    /* Check width and height */
    if ((x + w) >= SSD1306_WIDTH) 
    {
    
    
        w = SSD1306_WIDTH - x;
    }
    if ((y + h) >= SSD1306_HEIGHT) 
    {
    
    
        h = SSD1306_HEIGHT - y;
    }

    /* Draw 4 lines */
    SSD1306_DrawLine(x, y, x + w, y, c);         /* Top line */
    SSD1306_DrawLine(x, y + h, x + w, y + h, c); /* Bottom line */
    SSD1306_DrawLine(x, y, x, y + h, c);         /* Left line */
    SSD1306_DrawLine(x + w, y, x + w, y + h, c); /* Right line */
}

4.3 绘制三角形

void SSD1306_DrawTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, SSD1306_COLOR_t color) 
{
    
    
    /* Draw lines */
    SSD1306_DrawLine(x1, y1, x2, y2, color);
    SSD1306_DrawLine(x2, y2, x3, y3, color);
    SSD1306_DrawLine(x3, y3, x1, y1, color);
}

4.4 三角形填充

void SSD1306_DrawFilledTriangle(uint16_t x1, uint16_t y1, uint16_t x2, uint16_t y2, uint16_t x3, uint16_t y3, SSD1306_COLOR_t color) 
{
    
    
    int16_t deltax = 0, deltay = 0, x = 0, y = 0, xinc1 = 0, xinc2 = 0,
            yinc1 = 0, yinc2 = 0, den = 0, num = 0, numadd = 0, numpixels = 0,
            curpixel = 0;

    deltax = ABS(x2 - x1);
    deltay = ABS(y2 - y1);
    x = x1;
    y = y1;

    if (x2 >= x1) 
    {
    
    
        xinc1 = 1;
        xinc2 = 1;
    } 
    else 
    {
    
    
        xinc1 = -1;
        xinc2 = -1;
    }

    if (y2 >= y1) 
    {
    
    
        yinc1 = 1;
        yinc2 = 1;
    } 
    else 
    {
    
    
        yinc1 = -1;
        yinc2 = -1;
    }

    if (deltax >= deltay) 
    {
    
    
        xinc1 = 0;
        yinc2 = 0;
        den = deltax;
        num = deltax / 2;
        numadd = deltay;
        numpixels = deltax;
    } 
    else 
    {
    
    
        xinc2 = 0;
        yinc1 = 0;
        den = deltay;
        num = deltay / 2;
        numadd = deltax;
        numpixels = deltay;
    }

    for (curpixel = 0; curpixel <= numpixels; curpixel++) 
    {
    
    
        SSD1306_DrawLine(x, y, x3, y3, color);

        num += numadd;
        if (num >= den) 
        {
    
    
            num -= den;
            x += xinc1;
            y += yinc1;
        }
        x += xinc2;
        y += yinc2;
    }
}

4.5 绘制圆形

void SSD1306_DrawCircle(int16_t x0, int16_t y0, int16_t r, SSD1306_COLOR_t c) 
{
    
    
    int16_t f = 1 - r;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * r;
    int16_t x = 0;
    int16_t y = r;

    SSD1306_DrawPixel(x0, y0 + r, c);
    SSD1306_DrawPixel(x0, y0 - r, c);
    SSD1306_DrawPixel(x0 + r, y0, c);
    SSD1306_DrawPixel(x0 - r, y0, c);

    while (x < y) 
    {
    
    
        if (f >= 0) 
        {
    
    
            y--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;

        SSD1306_DrawPixel(x0 + x, y0 + y, c);
        SSD1306_DrawPixel(x0 - x, y0 + y, c);
        SSD1306_DrawPixel(x0 + x, y0 - y, c);
        SSD1306_DrawPixel(x0 - x, y0 - y, c);

        SSD1306_DrawPixel(x0 + y, y0 + x, c);
        SSD1306_DrawPixel(x0 - y, y0 + x, c);
        SSD1306_DrawPixel(x0 + y, y0 - x, c);
        SSD1306_DrawPixel(x0 - y, y0 - x, c);
    }
}

4.6 圆形填充

void SSD1306_DrawFilledCircle(int16_t x0, int16_t y0, int16_t r, SSD1306_COLOR_t c) 
{
    
    
    int16_t f = 1 - r;
    int16_t ddF_x = 1;
    int16_t ddF_y = -2 * r;
    int16_t x = 0;
    int16_t y = r;

    SSD1306_DrawPixel(x0, y0 + r, c);
    SSD1306_DrawPixel(x0, y0 - r, c);
    SSD1306_DrawPixel(x0 + r, y0, c);
    SSD1306_DrawPixel(x0 - r, y0, c);
    SSD1306_DrawLine(x0 - r, y0, x0 + r, y0, c);

    while (x < y) 
    {
    
    
        if (f >= 0) 
        {
    
    
            y--;
            ddF_y += 2;
            f += ddF_y;
        }
        x++;
        ddF_x += 2;
        f += ddF_x;

        SSD1306_DrawLine(x0 - x, y0 + y, x0 + x, y0 + y, c);
        SSD1306_DrawLine(x0 + x, y0 - y, x0 - x, y0 - y, c);

        SSD1306_DrawLine(x0 + y, y0 + x, x0 - y, y0 + x, c);
        SSD1306_DrawLine(x0 + y, y0 - x, x0 - y, y0 - x, c);
    }
}

完整的工程源码我已经上传了CSDN,点击下面连接跳转下载:

https://download.csdn.net/download/luobeihai/86501329

猜你喜欢

转载自blog.csdn.net/luobeihai/article/details/126562719