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”即可免费下载。