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://www.toymoban.com/news/detail-423884.html
https://download.csdn.net/download/luobeihai/86501329文章来源地址https://www.toymoban.com/news/detail-423884.html
到了这里,关于OLED12864(SSD1306)驱动代码的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!