【硬件模块】SSD1306 OLED屏幕(含GD32ESP32驱动代码)

OLED屏幕

最近在写GD32的文章,之前STM32有OLED屏幕来展示数据,ESP32可以直接打印到电脑屏幕上,GD32的话手上就没有办法了。

虽然是可以直接把STM32的OLED屏幕的驱动代码改改移植到GD32上面,不过想了想干脆写一个文章来说说如何驱动,也当是锻炼一下看文档的能力了。

没错,SSD1306也是只有英文文档,找网站直接翻译中文的效果不太好,不过我还是把译文结合原文再看看江科大的STM32的OLED驱动代码给啃了啃,因此这很考验我的耐心。

OLED,全称为Organic Light-Emitting Diode,中文名称为有机发光显示器或有机电激光显示。它属于一种电流型的有机发光器件,工作原理主要是通过载流子的注入和复合来实现发光现象,发光强度与注入的电流成正比。

OLED的基本结构包括ITO透明电极和金属电极,分别作为器件的阳极和阴极。在一定的电压驱动下,电子和空穴分别从阴极和阳极注入到电子和空穴传输层,然后经过各自的传输层迁移到发光层。在发光层中,电子和空穴相遇并产生能量激子,这些激子激发发光分子,最终发出可见光。

OLED具有许多显著的优势。首先,它的厚度可以小于1毫米,仅为LCD屏幕的1/3,并且重量也更轻。其次,由于其固态结构,没有液体物质,因此抗震性能更好,不怕摔。此外,OLED几乎没有可视角度的问题,即使在很大的视角下观看,画面仍然不失真。它的响应时间也非常快,是LCD的千分之一,因此在显示运动画面时绝对不会有拖影的现象。同时,OLED的低温特性好,在零下40度时仍能正常显示,而LCD则无法做到。再者,OLED的发光效率更高,能耗比LCD还要低。最后,OLED还能够在不同材质的基板上制造,因此可以做成能弯曲的柔软显示器。

然而,OLED也存在一些缺点。例如,其寿命通常只有5000小时,要低于LCD至少1万小时的寿命。此外,目前的技术还不能实现大尺寸屏幕的量产,因此,目前OLED主要适用于便携类的数码类产品。同时,OLED还存在色彩纯度不够的问题,不容易显示出鲜艳、浓郁的色彩。

目前,OLED已经广泛应用于多个领域。在移动设备中,如智能手机、平板电脑和可穿戴设备,OLED屏幕的应用非常普遍。在汽车领域,OLED技术被用于车载显示屏、仪表盘和车内照明等,提供清晰、生动的车载信息显示。在数字广告领域,OLED屏幕的透明度和灵活性使其成为理想的广告展示工具。在医疗设备中,OLED屏幕的高对比度和细腻的图像质量使得医疗专业人员能够清晰地观察和分析相关数据和图像。此外,OLED还在航空航天、智能家居、游戏设备和虚拟现实等领域得到了应用。

总的来说,OLED作为一种先进的显示技术,具有许多独特的优势,并在多个领域得到了广泛的应用。随着技术的不断进步,相信OLED在未来会有更加广阔的发展前景。

以上介绍来自文心一言。

看看文档

SSD1306支持SPI和I2C,不过我们买的基本上都是封装好的模块,具体用SPI还是I2C就得看模块的引脚了。四个引脚的就是I2C,七个引脚(也不一定)的是SPI。

我手上的是I2C的,因此就主要看看I2C的部分。

STM32的I2C的软件驱动可以参考我之前的文章,我直接贴下面(小改了一点,删了一部分用不到的),复制粘贴即可使用。

#define SCL_Pin GPIO_Pin_0
#define SDA_Pin GPIO_Pin_1void Z_I2C_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);GPIO_InitTypeDef itd;itd.GPIO_Mode=GPIO_Mode_Out_OD;        itd.GPIO_Pin=SCL_Pin|SDA_Pin;    itd.GPIO_Speed=GPIO_Speed_50MHz;                   GPIO_Init(GPIOA,&itd);GPIO_WriteBit(GPIOA,SCL_Pin,Bit_SET);       //SCL和SDA默认都是高电平GPIO_WriteBit(GPIOA,SDA_Pin,Bit_SET);       //因此初始化后设为高电平
}void Z_I2C_SetSCL(uint8_t signal){if(signal==1) GPIO_WriteBit(GPIOA,SCL_Pin,Bit_SET);else GPIO_WriteBit(GPIOA,SCL_Pin,Bit_RESET);
}void Z_I2C_SetSDA(uint8_t signal){if(signal==1) GPIO_WriteBit(GPIOA,SDA_Pin,Bit_SET);else GPIO_WriteBit(GPIOA,SDA_Pin,Bit_RESET);
}uint8_t Z_I2C_GetSDA(void){return GPIO_ReadInputDataBit(GPIOA,SDA_Pin);
}void Z_I2C_Start(void){Z_I2C_SetSDA(1);Z_I2C_SetSCL(1);Z_I2C_SetSDA(0);Z_I2C_SetSCL(0);
}void Z_I2C_End(){Z_I2C_SetSDA(0);Z_I2C_SetSCL(1);Z_I2C_SetSDA(1);
}void Z_I2C_SendByte(uint8_t byte){Z_I2C_SetSCL(0);for(int i=0;i<8;++i){if((byte&0x80)==0) Z_I2C_SetSDA(0);else Z_I2C_SetSDA(1);byte<<=1;Z_I2C_SetSCL(1);Z_I2C_SetSCL(0);}//多一个时钟时序,跳过ACK应答Z_I2C_SetSCL(1);Z_I2C_SetSCL(0);
}

获取到了从机地址,可以是0111100(七位地址,0x78),也可以是0111101(0x7A),这个需要先记下,一会通信的时候需要。

下一个是数据的格式。

首先是I2C的起始时序,接着是发送Control byte(我这里理解为控制位)和数据,最后再一个I2C的结束时序。这是很标准的I2C时序。

另外关于Control byte中的C0和D/C#是什么意思我们还需要接着往下看。

主要看红框框出来的就行,因为我们基本上不需要向SSD1306读取数据。

当D/C#为0以及R/W#为0时属于写命令的模式,当D/C#为1以及R/W#为0时属于写数据的模式。

根据上面几点我们就可以封装一下向SSD1306写命令和写数据的函数了。

#define Z_OLED_COMMAND  0
#define Z_OLED_DATA     1void Z_OLED_Write(uint8_t Data,uint8_t command)
{Z_I2C_Start();Z_I2C_SendByte(0x78);       //0111 1000Z_I2C_SendByte( command == Z_OLED_COMMAND ? 0x00 : 0x40);   //根据参数来决定是写命令还是写数据Z_I2C_SendByte(Data);Z_I2C_End();
}

命令

了解了上面的时序以及如何发送命令或数据之后,接下来最重要的就是了解我们能用哪些命令去让OLED屏幕亮起来了。

参考一下我们之前是如何让ST7735S驱动TFT屏幕亮起来的。

我们首先是框选出了一个区域,然后再传入要塞满这个区域的数据,因此我们首先是要框选区域。

使用上面这俩命令可以指定我们我们选定区域的列。

这边我们需要先回头看看SSD1306是怎么划分整个屏幕的。

我们屏幕大小是64*128像素的。

其中128就是每一行都有128个像素,而其中的64分为了8个页,也就是说每个页都是8*128像素的。

上面两个命令就是指定从哪一列(128像素中的)开始框选区域的。

两个命令它们都不是单纯的指令,它们的命令是一个范围,第一次碰到的小伙伴可能会迷糊。我们可以理解成它们不只是命令,而是命令加数据的。

这俩命令的前四位才是指令,后四位是传递的数据。

比如说我现在要指定第100列(0110 0100)为起始列。

那么我需要发送上面两条指令来指定这个100列为起始列。

首先是数据低4位,100的二进制低四位是(0100),那么我配合上上面的命令就是需要发送0x04(0x00 | 0x04(0100))。

同理,发送数据高四位则是0x16(0x10 | 0x06(0110))。

这样,发送了两条命令我们就指定好了框选区域的起始列。

那么接下来我们还需要指定结束列才可以选择一段范围对吧。不过实际上我们不需要指定,因为每次写入数据,都会写8列。

指定完列范围,接下来就是指定行范围了。

首先我们需要知道SSD1306的三种寻址模式。

第一个,也是默认的。每次都是只写入一页,换页的话需要额外发送指定列的命令。 

第二个,写完一页会自动换到下一页。 

 第三个,从列开始写,写完一列就换下一列(基本不用)。

我们默认是第一个,通常也不需要修改。第一种寻址模式一次写入一页。那么对应到我们选取区域,我们就选择一个页号即可,因为一页是高8像素的,指定了页号就算是把起始行和结束行都指定了。

我们使用下面这个命令。

那么了解了上面几个命令之后我们就可以去显示一些东西了。

只需要先发送命令来指定区域,然后再发送数据(像素点数据)去塞满这区域即可。

不过在此之前我们还有一个步骤需要完成,那就是初始化代码,一般来说卖屏幕的都会提供给咱,我们直接拿来用,把对应的函数名改改就行。那么我这边就不展示了,我直接把江科大的初始化命令拿过来用。

STM32实操代码

下面代码非常简略,只有显示一个大写字符的功能(因为我懒,并且其他功能都可以由写一个字符延伸来开,因此没有写其他的函数了)。

并且写的非常不规范,比如说字模数组我直接放在了.c文件里(还是因为懒)。

很大一部分是抄的江科大的,因为这类模块驱动我们基本上不需要自己写,大概率早有先驱给我们写好了,商家一般不止提供初始化命令的代码,甚至驱动都会直接给我们,这边写一写主要还是以了解、学习模块为主要目的,因此写个大概意思意思就行了。

 STM32的代码我就写一点,完整的可以去b站找江科大,下面GD32和ESP32的代码我就直接把江科大的代码完整移植过去了。全部代码包括取字模的软件我都会打包放公众号,关注公众号“折途想要敲代码”回复关键词“OLED”即可免费下载。

Z_OLED.c

#include "Z_OLED.h"
const uint8_t Z_OLED_FONT_CHAR[][16] ={0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,//A 0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00,//B 0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00,//C 0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00,//D 0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00,//E 0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00,//F 0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00,//G 0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20,//H0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00,//I 0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00,//J 0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00,//K 0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00,//L 0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x00,0x3F,0x00,0x3F,0x20,0x00,//M 0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00,//N 0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00,//O 0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00,//P 0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x18,0x24,0x24,0x38,0x50,0x4F,0x00,//Q 0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20,//R 0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00,//S 0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//T 0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00,//U 0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00,//V 0xF8,0x08,0x00,0xF8,0x00,0x08,0xF8,0x00,0x03,0x3C,0x07,0x00,0x07,0x3C,0x03,0x00,//W 0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20,//X 0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,//Y 0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00,//Z 
};#define SCL_Pin GPIO_Pin_8
#define SDA_Pin GPIO_Pin_9
#define OLED_GPIO   GPIOB void Z_I2C_Init(void){RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);GPIO_InitTypeDef itd;itd.GPIO_Mode=GPIO_Mode_Out_OD;        itd.GPIO_Pin=SCL_Pin|SDA_Pin;    itd.GPIO_Speed=GPIO_Speed_50MHz;                   GPIO_Init(OLED_GPIO,&itd);GPIO_WriteBit(OLED_GPIO,SCL_Pin,Bit_SET);       //SCL和SDA默认都是高电平GPIO_WriteBit(OLED_GPIO,SDA_Pin,Bit_SET);       //因此初始化后设为高电平
}void Z_I2C_SetSCL(uint8_t signal){if(signal==1) GPIO_WriteBit(OLED_GPIO,SCL_Pin,Bit_SET);else GPIO_WriteBit(OLED_GPIO,SCL_Pin,Bit_RESET);
}void Z_I2C_SetSDA(uint8_t signal){if(signal==1) GPIO_WriteBit(OLED_GPIO,SDA_Pin,Bit_SET);else GPIO_WriteBit(OLED_GPIO,SDA_Pin,Bit_RESET);
}void Z_I2C_Start(void){Z_I2C_SetSDA(1);Z_I2C_SetSCL(1);Z_I2C_SetSDA(0);Z_I2C_SetSCL(0);
}void Z_I2C_End(){Z_I2C_SetSDA(0);Z_I2C_SetSCL(1);Z_I2C_SetSDA(1);
}void Z_I2C_SendByte(uint8_t byte){Z_I2C_SetSCL(0);for(int i=0;i<8;++i){if((byte&0x80)==0) Z_I2C_SetSDA(0);else Z_I2C_SetSDA(1);byte<<=1;Z_I2C_SetSCL(1);Z_I2C_SetSCL(0);}//多一个时钟时序,跳过ACK应答Z_I2C_SetSCL(1);Z_I2C_SetSCL(0);
}void Z_I2C_SendACK(uint8_t ack){if(ack==0) Z_I2C_SetSDA(0);else Z_I2C_SetSDA(1);Z_I2C_SetSCL(1);Z_I2C_SetSCL(0);
}#define Z_OLED_COMMAND  0
#define Z_OLED_DATA     1void Z_OLED_Write(uint8_t Data,uint8_t command)
{Z_I2C_Start();Z_I2C_SendByte(0x78);       //0111 1000Z_I2C_SendByte( command == Z_OLED_COMMAND ? 0x00 : 0x40);   //根据参数来决定是写命令还是写数据Z_I2C_SendByte(Data);Z_I2C_End();
}void Z_OLED_SpecifyScope(uint8_t page,uint8_t col){Z_OLED_Write(0xB0|page,Z_OLED_COMMAND);Z_OLED_Write(0x00|(col&0x0F),Z_OLED_COMMAND);Z_OLED_Write(0x10|((col&0xF0)>>4),Z_OLED_COMMAND);
}void Z_OLED_Clear(void){for (uint8_t j=0;j<8;j++){Z_OLED_SpecifyScope(j, 0);for(uint8_t i=0;i < 128; i++){Z_OLED_Write(0x00,Z_OLED_DATA);}}
}void Z_OLED_ShowChar(uint8_t row,uint8_t col,char ch){Z_OLED_SpecifyScope((row-1)*2,col);for (uint8_t i=0;i<8;i++){Z_OLED_Write(Z_OLED_FONT_CHAR[ch-'A'][i],Z_OLED_DATA);}Z_OLED_SpecifyScope((row-1)*2+1,col);for (uint8_t i=0;i<8;i++){Z_OLED_Write(Z_OLED_FONT_CHAR[ch-'A'][i+8],Z_OLED_DATA);}    
}void Z_OLED_Init(void){Z_I2C_Init();			//端口初始化Z_OLED_Write(0xAE,Z_OLED_COMMAND);	//关闭显示Z_OLED_Write(0xD5,Z_OLED_COMMAND);	//设置显示时钟分频比/振荡器频率Z_OLED_Write(0x80,Z_OLED_COMMAND);Z_OLED_Write(0xA8,Z_OLED_COMMAND);	//设置多路复用率Z_OLED_Write(0x3F,Z_OLED_COMMAND);Z_OLED_Write(0xD3,Z_OLED_COMMAND);	//设置显示偏移Z_OLED_Write(0x00,Z_OLED_COMMAND);Z_OLED_Write(0x40,Z_OLED_COMMAND);	//设置显示开始行Z_OLED_Write(0xA1,Z_OLED_COMMAND);	//设置左右方向,0xA1正常 0xA0左右反置Z_OLED_Write(0xC8,Z_OLED_COMMAND);	//设置上下方向,0xC8正常 0xC0上下反置Z_OLED_Write(0xDA,Z_OLED_COMMAND);	//设置COM引脚硬件配置Z_OLED_Write(0x12,Z_OLED_COMMAND);Z_OLED_Write(0x81,Z_OLED_COMMAND);	//设置对比度控制Z_OLED_Write(0xCF,Z_OLED_COMMAND);Z_OLED_Write(0xD9,Z_OLED_COMMAND);	//设置预充电周期Z_OLED_Write(0xF1,Z_OLED_COMMAND);Z_OLED_Write(0xDB,Z_OLED_COMMAND);	//设置VCOMH取消选择级别Z_OLED_Write(0x30,Z_OLED_COMMAND);Z_OLED_Write(0xA4,Z_OLED_COMMAND);	//设置整个显示打开/关闭Z_OLED_Write(0xA6,Z_OLED_COMMAND);	//设置正常/倒转显示Z_OLED_Write(0x8D,Z_OLED_COMMAND);	//设置充电泵Z_OLED_Write(0x14,Z_OLED_COMMAND);Z_OLED_Write(0xAF,Z_OLED_COMMAND);	//开启显示Z_OLED_Clear();				//OLED清屏
}

Z_OLED.h

#ifndef Z_OLED_H
#define Z_OLED_H#include "stm32f10x.h" void Z_OLED_Init(void);
void Z_OLED_ShowChar(uint8_t row,uint8_t col,char ch);
void Z_OLED_Clear(void);#endif

main.c

#include "stm32f10x.h"                  // Device header
#include "Delay.h"
#include "Z_OLED.h"int main(void){Z_OLED_Init();while(1){Z_OLED_ShowChar(1,0,'A');Delay_ms(500);Z_OLED_ShowChar(1,10,'B');Delay_ms(500);Z_OLED_ShowChar(1,20,'C');}}

 

GD32驱动移植代码

OLED.h

#ifndef __OLED_H
#define __OLED_Hvoid 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);#endif

OLED.c

#include "gd32e23x.h"
#include "systick.h"
#include "OLED_Font.h"#define OLED_SCL_GPIO   GPIO_PIN_0
#define OLED_SDA_GPIO   GPIO_PIN_1
/*引脚配置*/
#define OLED_W_SCL(x)       gpio_bit_write(GPIOA,OLED_SCL_GPIO,x)
#define OLED_W_SDA(x)       gpio_bit_write(GPIOA,OLED_SDA_GPIO,x)/*引脚初始化*/
void OLED_I2C_Init(void){rcu_periph_clock_enable(RCU_GPIOA);gpio_mode_set(GPIOA,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,OLED_SCL_GPIO);gpio_mode_set(GPIOA,GPIO_MODE_OUTPUT,GPIO_PUPD_NONE,OLED_SDA_GPIO);gpio_output_options_set(GPIOA,GPIO_OTYPE_OD,GPIO_OSPEED_50MHZ,OLED_SCL_GPIO);gpio_output_options_set(GPIOA,GPIO_OTYPE_OD,GPIO_OSPEED_50MHZ,OLED_SDA_GPIO);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 OLED_WriteCommand(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)
{OLED_WriteCommand(0xB0 | Y);					//设置Y位置OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}/*** @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(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容}OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分for (i = 0; i < 8; i++){OLED_WriteData(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  无* @retval 无*/
void OLED_Init(void)
{uint32_t i, j;for (i = 0; i < 1000; i++)			//上电延时{for (j = 0; j < 1000; j++);}OLED_I2C_Init();			//端口初始化OLED_WriteCommand(0xAE);	//关闭显示OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率OLED_WriteCommand(0x80);OLED_WriteCommand(0xA8);	//设置多路复用率OLED_WriteCommand(0x3F);OLED_WriteCommand(0xD3);	//设置显示偏移OLED_WriteCommand(0x00);OLED_WriteCommand(0x40);	//设置显示开始行OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置OLED_WriteCommand(0x12);OLED_WriteCommand(0x81);	//设置对比度控制OLED_WriteCommand(0xCF);OLED_WriteCommand(0xD9);	//设置预充电周期OLED_WriteCommand(0xF1);OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别OLED_WriteCommand(0x30);OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭OLED_WriteCommand(0xA6);	//设置正常/倒转显示OLED_WriteCommand(0x8D);	//设置充电泵OLED_WriteCommand(0x14);OLED_WriteCommand(0xAF);	//开启显示OLED_Clear();				//OLED清屏
}

main.c

#include "gd32e23x.h"
#include "OLED.h"int main(void){OLED_Init();OLED_ShowString(1,1,"Hello World!");while(1){}
}

移植成功。其实改动不大,主要就是把GPIO的初始化和GPIO的电平操作适配一下就行。

ESP32驱动移植代码

这边只贴一个OLED.c的代码,因为其他的和上面GD32的一样。 

OLED.c

#include "driver/gpio.h"
#include "OLED_Font.h"#define OLED_SCL_GPIO 18
#define OLED_SDA_GPIO 17/*引脚配置*/
#define OLED_W_SCL(x)		gpio_set_level(OLED_SCL_GPIO,x)
#define OLED_W_SDA(x)		gpio_set_level(OLED_SDA_GPIO,x)/*引脚初始化*/
void OLED_I2C_Init(void){gpio_config_t init={.intr_type=GPIO_INTR_DISABLE,             //失能中断;.mode=GPIO_MODE_OUTPUT_OD,                //开漏输出模式.pin_bit_mask=(1ULL<<OLED_SCL_GPIO),    .pull_down_en=GPIO_PULLDOWN_DISABLE,      //失能下拉模式.pull_up_en=GPIO_PULLUP_ENABLE,           //使能上拉模式};gpio_config(&init);init.pin_bit_mask=(1ULL<<OLED_SDA_GPIO);gpio_config(&init);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 OLED_WriteCommand(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)
{OLED_WriteCommand(0xB0 | Y);					//设置Y位置OLED_WriteCommand(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位OLED_WriteCommand(0x00 | (X & 0x0F));			//设置X位置低4位
}/*** @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(OLED_F8x16[Char - ' '][i]);			//显示上半部分内容}OLED_SetCursor((Line - 1) * 2 + 1, (Column - 1) * 8);	//设置光标位置在下半部分for (i = 0; i < 8; i++){OLED_WriteData(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  无* @retval 无*/
void OLED_Init(void)
{uint32_t i, j;for (i = 0; i < 1000; i++)			//上电延时{for (j = 0; j < 1000; j++);}OLED_I2C_Init();			//端口初始化OLED_WriteCommand(0xAE);	//关闭显示OLED_WriteCommand(0xD5);	//设置显示时钟分频比/振荡器频率OLED_WriteCommand(0x80);OLED_WriteCommand(0xA8);	//设置多路复用率OLED_WriteCommand(0x3F);OLED_WriteCommand(0xD3);	//设置显示偏移OLED_WriteCommand(0x00);OLED_WriteCommand(0x40);	//设置显示开始行OLED_WriteCommand(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置OLED_WriteCommand(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置OLED_WriteCommand(0xDA);	//设置COM引脚硬件配置OLED_WriteCommand(0x12);OLED_WriteCommand(0x81);	//设置对比度控制OLED_WriteCommand(0xCF);OLED_WriteCommand(0xD9);	//设置预充电周期OLED_WriteCommand(0xF1);OLED_WriteCommand(0xDB);	//设置VCOMH取消选择级别OLED_WriteCommand(0x30);OLED_WriteCommand(0xA4);	//设置整个显示打开/关闭OLED_WriteCommand(0xA6);	//设置正常/倒转显示OLED_WriteCommand(0x8D);	//设置充电泵OLED_WriteCommand(0x14);OLED_WriteCommand(0xAF);	//开启显示OLED_Clear();				//OLED清屏
}

也移植成功啦。 

全部代码包括取字模的软件我都会打包放公众号,关注公众号“折途想要敲代码”回复关键词“OLED”即可免费下载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/27306.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

2024牛客网高频精选Java面试八股文整理(附答案)

Java 面试 Java 作为编程语言中的 NO.1,选择入行做 IT 做编程开发的人&#xff0c;基本都把它作为首选语言,进大厂拿高薪也是大多数小伙伴们的梦想。以前 Java 岗位人才的空缺&#xff0c;而需求量又大&#xff0c;所以这种人才供不应求的现状&#xff0c;就是 Java 工程师的薪…

公用nacos,实现只调用本机相应服务,不出现负载均衡到别人机器上

当我们有两个研发同时在调试一个微服务模块时&#xff0c;你和对方本地都会启动服务&#xff0c;这就导致在nacos会同时注册两个实例。默认情况下请求这个服务&#xff0c;具体处理请求的程序会在你和对方之间来回轮询&#xff0c;即一下你的服务一下对方的服务。 其结果就导…

栈(Stack)汇总

栈简介 栈&#xff08;Stack&#xff09;是只允许在一端进行插入或者删除操作的线性表。它的操作特性可以概括为——后进先出&#xff08;Last In First Out&#xff0c;LIFO&#xff09;。栈顶&#xff08;Top&#xff09;——线性表允许进行插入删除的一端&#xff1b; 栈底…

【Linux】基础IO——文件描述符,重定向

话接上篇&#xff1a; 1.文件描述符fd 磁盘文件 VS 内存文件&#xff1f; 当文件存储在磁盘当中时&#xff0c;我们将其称之为磁盘文件&#xff0c;而当磁盘文件被加载到内存当中后&#xff0c;我们将加载到内存当中的文件称之为内存文件。磁盘文件和内存文件之间的关系就像程…

红队内网攻防渗透:内网渗透之Linux内网权限提升技术:udf提权Capability权限LD_PRELOAD环境变量

红队内网攻防渗透 1. 内网权限提升技术1.1 Linux系统提权-Web&用户-数据库udf提权1.1.1 信息收集1.1.2 Web权限获取1.1.3 MYSQL-UDF提权1.1.4 下载到目标上1.1.5 连接确认是否有条件进行导出调用1.1.6 开始进行写入导出调用1.2 Linux系统提权-Web&用户-Capability能力1…

ThinkBook 16 2024 Ubuntu 触控板问题解决

sudo insmod goodix-gt7868q.ko sudo cp local-overrides.quirks /etc/libinput/local-overrides.quirks sudo systemctl restart gdm 有偿解决&#xff0c;无效退款 联系前&#xff0c;请写明笔记本型号和ubuntu版本

生命在于学习——Python人工智能原理(3.3)

三、深度学习 4、激活函数 激活函数的主要作用是对神经元获得的输入进行非线性变换&#xff0c;以此反映神经元的非线性特性。常见的激活函数有线性激活函数、符号激活函数、Sigmod激活函数、双曲正切激活函数、高斯激活函数、ReLU激活函数。 &#xff08;1&#xff09;线性…

【elementui源码解析】如何实现自动渲染md文档-第二篇

目录 1.概要 2.引用文件 1&#xff09;components.json 2&#xff09;json-template/string 3&#xff09;os.EOL 3.变量定义 4.模版填充 5.MAIN_TEMPLATE填充 6.src下的index.js文件 1&#xff09;install 2&#xff09;export 7.总结 1.概要 今天看第二个命令no…

videoJS 视频 + 独一无二皮肤 + mp4/m3u8

推荐和参考文章&#xff1a; video.js调用-腾讯云开发者社区-腾讯云> 一、总结&#xff08;点击显示或隐藏总结内容&#xff09;一句话总结&#xff1a;网上有各种细致的现成的代码可以拿来用&#xff0c;没必要自己死专1、video.js有两种初始化方式&#xff1f;一种是在v…

C++并发之条件变量(std::condition_variable)

目录 1 概述2 使用实例3 接口使用3.1 wait3.2 wait_for3.3 wait_until3.4 notify_one3.5 notiry_all3.5 notify_all_at_thread_exit1 概述 条件变量是一个能够阻塞调用线程直到被通知恢复的对象。   当调用其中一个等待函数时,它使用unique_lock(通过互斥锁)来锁定线程。线程…

hadoop和hbase对应版本关系

https://hbase.apache.org/book.html#configuration

DuDuTalk语音工牌:如何帮助房企打造数字化的案场接待体验

房地产案场接待作为客户体验的第一站&#xff0c;其服务质量直接影响客户的购房决策。然而&#xff0c;传统的案场接待方式存在诸多挑战&#xff0c;如信息记录不准确、服务流程难以标准化、客户反馈收集困难等。语音工牌作为一种创新的智能设备&#xff0c;凭借其独特的功能和…

Office 2021 mac/win版:智慧升级,办公新风尚

Office 2021是微软公司推出的一款高效、智能且功能丰富的办公软件套件。它集成了Word、Excel、PowerPoint等多个经典应用程序&#xff0c;旨在为用户提供更出色的办公体验。 Office 2021 mac/win版获取 Office 2021在继承了前代版本优点的基础上&#xff0c;进行了大量的优化…

接口测试之用Fiddler对手机app进行抓包

Fiddler是一款非常流行并且实用的http抓包工具&#xff0c;它的原理是在本机开启了一个http的代理服务器&#xff0c;然后它会转发所有的http请求和响应&#xff0c;因此&#xff0c;它比一般的firebug或者是chrome自带的抓包工具要好用的多。不仅如此&#xff0c;它还可以支持…

JVC摄像机SD卡变成RAW的恢复方法

JVC小日本胜利公司&#xff0c;公司名字绕口且产品线极广&#xff0c;涉及汽车、影音、娱乐……&#xff0c;而JVC在摄像机产品方面也有涉及&#xff0c;不过市场上极为少见。下边我们来看下这个JVC摄像机MP4恢复案例。 故障存储: 32G存储卡 RAW文件系统 故障现象: 客户无…

万字长文讲解如何快速搭建一个Spring Cloud项目

文章目录 概念基本概念微服务七大组件 初始化Maven父工程整合注册中心组件整合远程调用与负载均衡组件组件整合网关组件整合配置中心组件以gateway模块为例 整合分布式事务组件操作数据库模拟创建订单和扣减库存整合Seata 整合熔断降级组件整合链路追踪组件源码地址参考来源 概…

springcloud gateway转发websocket请求的404问题定位

一、问题 前端小程序通过springcloud gateway接入并访问后端的诸多微服务&#xff0c;几十个微服务相关功能均正常&#xff0c;只有小程序到后端推送服务的websocket连接建立不起来&#xff0c;使用whireshark抓包&#xff0c;发现在小程序通过 GET ws://192.168.6.100:8888/w…

Robot Operating System (ROS)中,发布与订阅

在Robot Operating System (ROS)中&#xff0c;发布与订阅是一种基于主题的异步消息传递机制&#xff0c;用于节点间的通信。ROS的设计是围绕着这一概念&#xff0c;它允许不同节点之间解耦&#xff0c;每个节点专注于自己的任务&#xff0c;通过发布和订阅消息来与其他节点交互…

计算机体系结构重点学习

从外部I/O与上层应用交互的整体软硬件过程 上层应用发出I/O请求&#xff1a;上层应用程序&#xff0c;如一个文本编辑器、网络浏览器或者任何软件应用&#xff0c;需要读取或写入数据时&#xff0c;会通过调用操作系统提供的API&#xff08;如文件操作API、网络操作API等&…

SpringBoot之请求映射原理

前言 我们发出的请求&#xff0c;SpringMVC是如何精准定位到那个Controller以及具体方法&#xff1f;其实这都是 HandlerMapping 发挥的作用&#xff0c;这篇博文我们以 RequestMappingHandlerMapping 为例并结合源码一步步进行分析。 定义HandlerMapping 默认 HandlerMappi…