Arduino 软件I2C驱动SSD1306/SH1106屏幕
✨代码是从其他平台移植过来的,在不依赖其他库的情况下,理论上可以做到对所有支持Arduino平台的开发板型号的兼容支持。由于不同单片机的容量的差异,可以自行删减不需要的字模和图模数据。
虽然软件I2C没有硬件I2C速度那么快,但是比硬件I2C更稳定和兼容性更高,可以很容易适配到任意单片机上使用,而不需要花费多少时间在驱动显示上。
⛳SSD1306/SH1106屏幕选择和引脚选择说明
- 🔧在
OLED_I2C.h
中,对SCREEN_TYPE
宏进行修改。 - 🔨在
OLED_I2C.h
中,对SDA_PIN
和SCL_PIN
宏进行修改。
🎉相关API函数
- 🔖目前移植实现的函数:
void OLED_WriteData(uint8_t Data);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *chr, uint8_t Char_Size);
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr, uint8_t Char_Size);
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len, uint8_t size2);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length,uint8_t size);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length,uint8_t size);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length,uint8_t size);
void OLED_ShowChinese(uint8_t Line, uint8_t Column, int Char);
uint64_t num_len(uint32_t Num);
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char pic[]);
void OLED_BMP(int i);
//*****使用图像绘制函数时,使用OLED_ShowFrame()显示,OLED_NewFrame();//清空图像
void OLED_SetCursor(uint8_t Y, uint8_t X);
void OLED_SetPixel(uint8_t x, uint8_t y, bool color);
void OLED_DrawLine(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, bool color);
void OLED_DrawRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, bool color);
void OLED_DrawFilledRectangle(uint8_t x, uint8_t y, uint8_t w, uint8_t h, bool color);
void OLED_DrawTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, bool color);
void OLED_DrawFilledTriangle(uint8_t x1, uint8_t y1, uint8_t x2, uint8_t y2, uint8_t x3, uint8_t y3, bool color);
void OLED_DrawCircle(uint8_t x, uint8_t y, uint8_t r, bool color);
void OLED_DrawFilledCircle(uint8_t x, uint8_t y, uint8_t r, bool color);
void OLED_DrawEllipse(uint8_t x, uint8_t y, uint8_t a, uint8_t b, bool color);
void OLED_ShowFrame();//更新缓存显示到屏幕上
void OLED_NewFrame();//清空图像缓存
📘软件底层驱动内容
- 🌿OLED_I2C.h
#ifndef __OLED_I2C_H
#define __OLED_I2C_H
#ifdef __cplusplus
extern "C" {
#endif
#include "stdint.h"
#define SSD1306 0
#define SH1106 1
#define SCREEN_TYPE SSD1306 //屏幕型号:0.96"->SSD1306/1.3" ->SH1106
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char);
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String);
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length);
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length);
void OLED_ShowChinese(uint8_t Line, uint8_t Column, int Char);
uint64_t num_len(uint32_t Num);
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char pic[]);
void OLED_BMP(int i);
#ifdef __cplusplus
}
#endif
#endif
- 🌿
OLED_I2C.cpp
#include "Arduino.h"
#include "OLED_I2C.h"
#include "OLED_Font.h"
/*软件I2C,可随意更改引脚*/
#define SDA_PIN D2 //ESP8266 D2->GPIO4
#define SCL_PIN D1 //ESP8266 D1->GPIO5
/*引脚配置*/
#define OLED_W_SCL(x) digitalWrite(SCL_PIN, x)
#define OLED_W_SDA(x) digitalWrite(SDA_PIN, x)
/*引脚初始化*/
void OLED_I2C_Init(void)
{
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C开始
* @param 无
* @retval 无
*/
void OLED_I2C_Start(void)
{
OLED_W_SDA(1);
OLED_W_SCL(1);
OLED_W_SDA(0);
OLED_W_SCL(0);
}
/**
* @brief I2C停止
* @param 无
* @retval 无
*/
void OLED_I2C_Stop(void)
{
OLED_W_SDA(0);
OLED_W_SCL(1);
OLED_W_SDA(1);
}
/**
* @brief I2C发送一个字节
* @param Byte 要发送的一个字节
* @retval 无
*/
void OLED_I2C_SendByte(uint8_t Byte)
{
uint8_t i;
for (i = 0; i < 8; i++)
{
OLED_W_SDA(Byte & (0x80 >> i));
OLED_W_SCL(1);
OLED_W_SCL(0);
}
OLED_W_SCL(1); //额外的一个时钟,不处理应答信号
OLED_W_SCL(0);
}
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void WriteCmd(uint8_t Command)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x00); //写命令
OLED_I2C_SendByte(Command);
OLED_I2C_Stop();
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
OLED_I2C_Start();
OLED_I2C_SendByte(0x78); //从机地址
OLED_I2C_SendByte(0x40); //写数据
OLED_I2C_SendByte(Data);
OLED_I2C_Stop();
}
/**
* @brief OLED设置光标位置
* @param Y 以左上角为原点,向下方向的坐标,范围:0~7
* @param X 以左上角为原点,向右方向的坐标,范围:0~127
* @retval 无
*/
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
WriteCmd(0xB0 | Y); //设置Y位置
#if(SCREEN_TYPE == SSD1306)
WriteCmd(((X & 0xf0) >> 4) | 0x10);//设置X位置高4位
WriteCmd((X & 0x0f) | 0x00);//设置X位置低4位
#elif(SCREEN_TYPE == SH1106)
WriteCmd((((X+2)&0xf0)>>4)|0x10);//设置X位置高4位
WriteCmd(((X+2)&0x0f));//设置X位置低4位
#endif
}
/**
* @brief OLED清屏
* @param 无
* @retval 无
*/
void OLED_Clear(void)
{
uint8_t i, j;
for (j = 0; j < 8; j++)
{
OLED_SetCursor(j, 0);
for(i = 0; i < 128; i++)
{
OLED_WriteData(0x00);
}
}
}
/**
* @brief OLED显示一个字符
* @param Line 行位置,范围:1~4
* @param Column 列位置,范围:1~16
* @param Char 要显示的一个字符,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowChar(uint8_t Line, uint8_t Column, char Char)
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(pgm_read_byte(&OLED_F8x16[Char - ' '][i])); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 8; i++)
{
OLED_WriteData(pgm_read_byte(&OLED_F8x16[Char - ' '][i + 8])); //显示下半部分内容
}
}
/**
* @brief OLED显示字符串
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param String 要显示的字符串,范围:ASCII可见字符
* @retval 无
*/
void OLED_ShowString(uint8_t Line, uint8_t Column, char *String)
{
uint8_t i;
for (i = 0; String[i] != '\0'; i++)
{
OLED_ShowChar(Line, Column + i, String[i]);
}
}
/**
* @brief OLED次方函数
* @retval 返回值等于X的Y次方
*/
uint32_t OLED_Pow(uint32_t X, uint32_t Y)
{
uint32_t Result = 1;
while (Y--)
{
Result *= X;
}
return Result;
}
/**
* @brief OLED显示数字(十进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~4294967295
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十进制,带符号数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:-2147483648~2147483647
* @param Length 要显示数字的长度,范围:1~10
* @retval 无
*/
void OLED_ShowSignedNum(uint8_t Line, uint8_t Column, int32_t Number, uint8_t Length)
{
uint8_t i;
uint32_t Number1;
if (Number >= 0)
{
OLED_ShowChar(Line, Column, '+');
Number1 = Number;
}
else
{
OLED_ShowChar(Line, Column, '-');
Number1 = -Number;
}
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i + 1, Number1 / OLED_Pow(10, Length - i - 1) % 10 + '0');
}
}
/**
* @brief OLED显示数字(十六进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~0xFFFFFFFF
* @param Length 要显示数字的长度,范围:1~8
* @retval 无
*/
void OLED_ShowHexNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i, SingleNumber;
for (i = 0; i < Length; i++)
{
SingleNumber = Number / OLED_Pow(16, Length - i - 1) % 16;
if (SingleNumber < 10)
{
OLED_ShowChar(Line, Column + i, SingleNumber + '0');
}
else
{
OLED_ShowChar(Line, Column + i, SingleNumber - 10 + 'A');
}
}
}
/**
* @brief OLED显示数字(二进制,正数)
* @param Line 起始行位置,范围:1~4
* @param Column 起始列位置,范围:1~16
* @param Number 要显示的数字,范围:0~1111 1111 1111 1111
* @param Length 要显示数字的长度,范围:1~16
* @retval 无
*/
void OLED_ShowBinNum(uint8_t Line, uint8_t Column, uint32_t Number, uint8_t Length)
{
uint8_t i;
for (i = 0; i < Length; i++)
{
OLED_ShowChar(Line, Column + i, Number / OLED_Pow(2, Length - i - 1) % 2 + '0');
}
}
/**
* @brief OLED初始化
* @param Line 1-4
@param Column 1-16
* @retval 无
*/
void OLED_ShowChinese(uint8_t Line, uint8_t Column, int Char)//输出自己的文字库
{
uint8_t i;
OLED_SetCursor((Line - 1) * 2, (Column - 1) * 8); //设置光标位置在上半部分
for (i = 0; i < 16; i++)
{
OLED_WriteData(pgm_read_byte(&OLED_F16x16[Char][i])); //显示上半部分内容
}
OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8); //设置光标位置在下半部分
for (i = 0; i < 16; i++)
{
OLED_WriteData(pgm_read_byte(&OLED_F16x16[Char][i + 16])); //显示下半部分内容
}
}
uint64_t num_len(uint32_t Num)
{
uint16_t len=1;
for(;Num>9;++len)
Num/=10;
return len;
}
void OLED_DrawBMP(unsigned char x0, unsigned char y0,unsigned char x1, unsigned char y1,unsigned char pic[])/*图片显示函数*/
{
unsigned int j=0;
unsigned char x,y;
if(y1%8==0) y=y1/8;
else y=y1/8+1;
for(y=y0;y<y1;y++)
{
OLED_SetCursor(y,x0); //第一个设置y,第二个设置x//设置光标位置左上角(0,0) 往下0-7 , 往右0-127
for(x=x0;x<x1;x++)
{
OLED_WriteData(pgm_read_byte(&pic[j++])); //写数据
}
}
}
void OLED_BMP(int i)/*已经设置好的图片*/
{
OLED_Clear();
OLED_DrawBMP(0,0,128,0,pic[i]);
OLED_DrawBMP(0,0,128,1,pic[i]);
OLED_DrawBMP(0,0,128,2,pic[i]);
OLED_DrawBMP(0,0,128,3,pic[i]);
OLED_DrawBMP(0,0,128,4,pic[i]);
OLED_DrawBMP(0,0,128,5,pic[i]);
OLED_DrawBMP(0,0,128,6,pic[i]);
OLED_DrawBMP(0,0,128,7,pic[i]);
}
void OLED_Init(void)
{
uint16_t i, j;
pinMode(SDA_PIN,OUTPUT);
pinMode(SCL_PIN,OUTPUT);
for (i = 0; i < 1000; i++) //上电延时
{
for (j = 0; j < 1000; j++);
}
OLED_I2C_Init(); //端口初始化
WriteCmd(0xAE); //关闭显示
WriteCmd(0xD5); //设置显示时钟分频比/振荡器频率
WriteCmd(0x80);
WriteCmd(0xA8); //设置多路复用率
WriteCmd(0x3F);
WriteCmd(0xD3); //设置显示偏移
WriteCmd(0x00);
WriteCmd(0x40); //设置显示开始行
WriteCmd(0xA1); //设置左右方向,0xA1正常 0xA0左右反置
WriteCmd(0xC8); //设置上下方向,0xC8正常 0xC0上下反置
WriteCmd(0xDA); //设置COM引脚硬件配置
WriteCmd(0x12);
WriteCmd(0x81); //设置对比度控制
WriteCmd(0xCF);
WriteCmd(0xD9); //设置预充电周期
WriteCmd(0xF1);
WriteCmd(0xDB); //设置VCOMH取消选择级别
WriteCmd(0x30);
WriteCmd(0xA4); //设置整个显示打开/关闭
WriteCmd(0xA6); //设置正常/倒转显示
WriteCmd(0x8D); //设置充电泵
WriteCmd(0x14);
WriteCmd(0xAF); //开启显示
OLED_Clear(); //OLED清屏
}
📝Demo测试代码
#include "OLED_I2C.h"
void setup() {
// put your setup code here, to run once:
// Serial.begin(9600);
OLED_Init();
OLED_Clear();
}
void loop() {
// put your main code here, to run repeatedly:
OLED_ShowString(1,1,(char*)"Hello World");//字符串显示
OLED_ShowChinese(2,1,0);//取模汉字显示
OLED_ShowChinese(2,3,1);
OLED_ShowChinese(2,5,2);
OLED_ShowChinese(2,7,3);
OLED_ShowChinese(2,9,4);
delay(3000);
OLED_Clear();
for(int i=0;i<17;i++){
OLED_BMP(i);//图片显示
delay(3000);
OLED_Clear();
}
}
- 📝图像demo测试代码:
OLED_SetPixel(5,3,1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
OLED_DrawLine(1, 1,128, 64, 1);
OLED_DrawLine(1, 64,128, 1, 1);
OLED_DrawLine(10, 5,128, 5, 1);
OLED_DrawLine(20, 20,128, 20, 1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
OLED_DrawRectangle(5, 5, 25, 25, 1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
OLED_DrawFilledRectangle(5, 5, 35, 35, 1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
OLED_DrawTriangle(5, 5, 5, 60, 120, 60, 1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
OLED_DrawFilledTriangle(5, 5, 5, 60, 60, 60, 1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
OLED_DrawCircle(64,32,25,1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
OLED_DrawFilledCircle(64,32,16,1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
OLED_DrawEllipse(64, 32, 32, 20, 1);
OLED_ShowFrame();
delay(1000);
OLED_NewFrame();
🛠软件I2C驱动转Wire库使用说明
- 🔖如果使用Arduino自带核心库Wire,包含Wire.h文件,改写写命令和写数据函数。
/**
* @brief OLED写命令
* @param Command 要写入的命令
* @retval 无
*/
void WriteCmd(uint8_t Command)
{
//从机地址
Wire.beginTransmission(0x3c);
Wire.write(0x00); //写命令
Wire.write(Command);
Wire.endTransmission();
}
/**
* @brief OLED写数据
* @param Data 要写入的数据
* @retval 无
*/
void OLED_WriteData(uint8_t Data)
{
//从机地址
Wire.beginTransmission(0x3c);
Wire.write(0x40); //写数据
Wire.write(Data);
Wire.endTransmission();
}
📚驱动程序
链接:https://pan.baidu.com/s/1Gfw4k5AzQKLCDSm4MKGbUQ?pwd=nd6f
提取码:nd6f
- 🌿更新版本V1:新增ASCII字符字体6X8,相关API函数与上一版本有差异,不通用。
链接:https://pan.baidu.com/s/1eMjKW8esnD0BQqSO2O-akQ?pwd=0ou7
提取码:0ou7
- 🌿更新版本V2:新增基本图像绘制函数。
- ✨使用图像绘制函数时,使用OLED_ShowFrame()显示,OLED_NewFrame();//清空图像
链接:https://pan.baidu.com/s/1uxnDV1UByh1ivaV_rWRVgA?pwd=k60w
提取码:k60w