1.驱动介绍

驱动器:SSD1306B
分辨率:128*64
屏幕类型:OLED屏
物理接口:6800/8080并行接口、I2C、SPI;
电压:最低支持2.2V
应用:便携式设备,消费者电器,便携式医疗设备。
在这里插入图片描述

2.正文讲解

在这里插入图片描述

RES:复位线:低电平有效; CS:片选:芯片使能; D/C:数据/命令控制接口:拉高表示数据,拉低表示命令;
E:使能信号,(6800/8080)并行接口时使用; R/W:读/写的控制接口,拉高表示读数据,拉低写数据; D0~D7:数据线;
以上需要看oled屏驱动手册,这里我总结了一下用到的引脚对应功能

BS0~BS2:

在这里插入图片描述
根据驱动手册发现是由BS0~BS2来选择物理接口模式。

B0~B2 物理接口选择
0 0 0 4线SPI接口
0 1 0 I2C
0 0 1 6800
0 1 1 8080
1 0 0 3线SPI

通过第二张图可以发现我的S0~S2都接地故使用4线SPI物理接口
在这里插入图片描述
根据这个图我们发现4线的SPI用到了:

D0,D1传输数据
CS 片选
D/C:数据/命令控制接口
RES:复位线

插播一下SPI的模式:
在这里插入图片描述

MOSI:主器件数据输出,从器件数据输入
MISO:主器件数据输入,从器件数据输出
SCLK :时钟信号,由主器件产生
/SS:从器件使能信号,由主器件控制
SPI 总线有四种工作方式(SP0, SP1, SP2, SP3),其中使用的最为广泛的是 SPI0 和 SPI3 方式。
在这里插入图片描述
CPOL是用来决定SCK时钟信号空闲时的电平
CPOL=0,空闲电平为低电平,
CPOL=1 时,空闲电平为高电平。CPHA是用来决定采样时刻的,
CPHA=0,在每个周期的第一个时钟沿采样,
CPHA=1,在每个周期的第二个时钟沿采样




在这里插入图片描述
通过上图可以看到:

D0:为时钟
D1:数据输入
写命令,D/C拉低
写数据,D/C拉高

在这里插入图片描述
通过此图可以发现在4线SPI下是通过上升沿采样,下降沿发送。
一共有8个数据位,高位先出。
因此我们可以总结到

1、片选CS拉低
2、DC根据传输内容决定拉低拉高
3、发送数据
4、片选拉高

到目前为止我们已经可以开始将SPI配置了

OLED_CS    PB7
OLED_SCL   PB13
OLED_SI    PB15
OLED_D/C   PA15
OLED_RES   PA4

注意配置spi时候是上升边沿采样,下降输出。
查看stm32手册发现我PB13和PB15对应的是SPI2
在这里插入图片描述
所以我们模拟的是SPI2

void spi2_config(void)
{
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
 //OLED_CS  PB7
 GPIO_InitTypeDef spi2_GPIO={0};
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_7;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_Out_PP;
 GPIO_Init(GPIOB,&spi2_GPIO);
 //OLED_SCL   PB13
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_13;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_AF_PP;
 GPIO_Init(GPIOB,&spi2_GPIO);
 //OLED_SI    PB15
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_15;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_AF_PP;
 GPIO_Init(GPIOB,&spi2_GPIO);
 //OLED_D/C   PA15
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_15;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_Out_PP;
 GPIO_Init(GPIOA,&spi2_GPIO);
//OLED_RES   PA4
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_4;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_Out_PP;
 GPIO_Init(GPIOA,&spi2_GPIO);
 
 //spi2配置
 SPI_InitTypeDef SPI2_CONFIG;
 SPI2_CONFIG.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_4;//
 SPI2_CONFIG.SPI_CPHA=SPI_CPHA_1Edge;//第一个跳变沿上升
 SPI2_CONFIG.SPI_CPOL=SPI_CPOL_Low;//空闲电平为低
 SPI2_CONFIG.SPI_CRCPolynomial=7;
 SPI2_CONFIG.SPI_DataSize=SPI_DataSize_8b;
 SPI2_CONFIG.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
 SPI2_CONFIG.SPI_FirstBit=SPI_FirstBit_MSB;
 SPI2_CONFIG.SPI_Mode=SPI_Mode_Master;
 SPI2_CONFIG.SPI_NSS=SPI_NSS_Soft;
 SPI_Init(SPI1,&SPI2_CONFIG);
 SPI_Cmd(SPI1,ENABLE);
}
//读写函数
uint8_t SPI2_sendrecvdate(uint8_t dat)
{
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);
	 SPI_I2S_SendData(SPI2,dat);
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);
	 return SPI_I2S_ReceiveData(SPI2);
}

接下来我们看一下屏幕的显示:
在这里插入图片描述
屏幕横向是128(0~127)个点,竖向是64(0-63)个点
我们传输的是一个字节是8位,能不能一个数据控制8个点?
单色屏一个位表示一个数据。0/1表示亮灭。
我们规定竖着8个点为一个数据的话,0x01不就是表示开头竖着的8个点最后一个点亮,0xff不就是表示全量,长度竖向长度是64,那么竖着就是表示8页(0-7),每页竖向长度为8嘛,这样我们要显示的话,知道某一页和某一列不就行了吗!
0————–————->127列
|
|
|
v 8页
在这里插入图片描述
可以看到,一个数据把当前页的当前列的所有位全部初始化。(英文翻译)
指令表:
1、基本指令表
在这里插入图片描述
部分命令如第一行的,设置对比度。先发送0x81命令,然后再发送一个数据表示对比度。
在这里插入图片描述
这个就是具体OLED屏幕的配置流程图。
1、先复位RES
2、延时100ms
3、上电RES拉高
4、0XA1
5、0xd5,0x80

这样写是不是太费事了,还容易出错。官方数据手册里面已经给了初始化的函数,我们只需要修改就行。

Internal setting(Charge pump)
{ 
RES=1; 
 delay(1000); 
 RES=0; 
 delay(1000); 
 RES=1; 
 delay(1000); 
write_i(0xAE); /*display off*/ 
 
 write_i(0x00); /*set lower column address*/ 
 write_i(0x10); /*set higher column address*/ 
 write_i(0x40); /*set display start line*/ 
 write_i(0xB0); /*set page address*/ 
 write_i(0x81); /*contract control*/ 
 write_i(0x66); /*128*/ 
 write_i(0xA1); /*set segment remap*/ 
 write_i(0xA6); /*normal / reverse*/ 
 write_i(0xA8); /*multiplex ratio*/ 
 write_i(0x3F); /*duty = 1/64*/ 
 write_i(0xC8); /*Com scan direction*/ 
 write_i(0xD3); /*set display offset*/ 
 write_i(0x00); 
 write_i(0xD5); /*set osc division*/ 
 write_i(0x80); 
 write_i(0xD9); /*set pre-charge period*/ 
 write_i(0x1f); 
 write_i(0xDA); /*set COM pins*/ 
 write_i(0x12); 
 write_i(0xdb); /*set vcomh*/ 
 write_i(0x30); 
 write_i(0x8d); /*set charge pump enable*/ 
 write_i(0x14); 
 write_i(0xAF); /*display ON*/ 
 } 
void write_i(unsigned char ins) 
{ 
 DC=0; 
 CS=0; 
 WR=1; 
 P1=ins; /*inst*/ 
 WR=0; 
 WR=1; 
 CS=1; 

} 
void write_d(unsigned char dat) 
{ 
 DC=1; 
 CS=0; 
 WR=1; 
 P1=dat; /*data*/ 
 WR=0; 
 WR=1; 
 CS=1; 
} 
void delay(unsigned int i) 
{ 
 while(i>0) 
 { 
 i--; 
 } 
}

寻址模式:0x20指令
我们如何找寻想要的屏幕地址呢?地址如何划分的呢?
在这里插入图片描述

(1)页寻址
在这里插入图片描述
给定一个页后,他会再当前页你指定的列地址开始往后刷,列自动往后偏移,到当前页刷完它又回到初始位子。页不变。看图就懂。
设置页地址:0xB0-0xB7
设置列地址:0x00-0x0F,0x10-0x1F

(2)水平寻址(方便)
在这里插入图片描述
设置列地址:0x21+页的起始地址+列的结束地址
设置页地址:0x22+页的起始地址+页的结束地址
(3)垂直寻址
在这里插入图片描述

驱动编程

硬件接口:
下面再次说一次我的接口

CS    OLED_CS    PB7    通用推挽输出
D0    OLED_SCL   PB13  SPI2_SCK 复用推挽输出
D1    OLED_SI    PB15  SPI2_MOSI 复用推挽输出
D/C   OLED_D/C   PA15   通用推挽输出
RES   OLED_RES   PA4    通用推挽输出

io关于模拟SPI代码配置上面已经说了,这里就不在写了!

效果

首先放一下整体代码:

oled.c

#include "oled.h"
#include "stdio.h"
/*
CS    OLED_CS    PB7    通用推挽输出
D0    OLED_SCL   PB13  SPI2_SCK 复用推挽输出
D1    OLED_SI    PB15  SPI2_MOSI 复用推挽输出
D/C   OLED_D/C   PA15   通用推挽输出
RES   OLED_RES   PA4    通用推挽输出
*/
void spi2_config(void)
{
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
 RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2,ENABLE);
 GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable,ENABLE);//PA15
 //OLED_CS  PB7
 GPIO_InitTypeDef spi2_GPIO={0};
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_7;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_Out_PP;
 GPIO_Init(GPIOB,&spi2_GPIO);
 //OLED_SCL   PB13
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_13;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_AF_PP;
 GPIO_Init(GPIOB,&spi2_GPIO);
 //OLED_SI    PB15
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_15;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_AF_PP;
 GPIO_Init(GPIOB,&spi2_GPIO);
 //OLED_D/C   PA15
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_15;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_Out_PP;
 GPIO_Init(GPIOA,&spi2_GPIO);
//OLED_RES   PA4
 spi2_GPIO.GPIO_Speed=GPIO_Speed_50MHz;
 spi2_GPIO.GPIO_Pin=GPIO_Pin_4;
 spi2_GPIO.GPIO_Mode=GPIO_Mode_Out_PP;
 GPIO_Init(GPIOA,&spi2_GPIO);
 
 //spi2配置
 SPI_InitTypeDef SPI2_CONFIG;
 SPI2_CONFIG.SPI_BaudRatePrescaler=SPI_BaudRatePrescaler_4;//
 SPI2_CONFIG.SPI_CPHA=SPI_CPHA_1Edge;
 SPI2_CONFIG.SPI_CPOL=SPI_CPOL_Low;
 SPI2_CONFIG.SPI_CRCPolynomial=7;
 SPI2_CONFIG.SPI_DataSize=SPI_DataSize_8b;
 SPI2_CONFIG.SPI_Direction=SPI_Direction_2Lines_FullDuplex;
 SPI2_CONFIG.SPI_FirstBit=SPI_FirstBit_MSB;
 SPI2_CONFIG.SPI_Mode=SPI_Mode_Master;
 SPI2_CONFIG.SPI_NSS=SPI_NSS_Soft;
 SPI_Init(SPI2,&SPI2_CONFIG);
 SPI_Cmd(SPI2,ENABLE);
 
}

uint8_t SPI2_sendrecvdate(uint8_t dat)
{
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_TXE)==RESET);
	 SPI_I2S_SendData(SPI2,dat);
	while(SPI_I2S_GetFlagStatus(SPI2,SPI_I2S_FLAG_RXNE)==RESET);
	 return SPI_I2S_ReceiveData(SPI2);
	
}
void oled_config(void)
{
	spi2_config();
	
	CS_TTL(1);//片选默认高
	DC_TTL(1);//DC高数据,低命令
	
	//启动前的复位设置
	systick_ms(100);
	 RES_TTL(1);
  systick_ms(100);
  RES_TTL(0);
  systick_ms(100); 
  RES_TTL(1);
  systick_ms(100);
	
 write_i(0,0xAE); /*display off*/ 
 write_i(0,0x00); /*set lower column address*/ 
 write_i(0,0x10); /*set higher column address*/ 
 write_i(0,0x40); /*set display start line*/ 
 write_i(0,0xB0); /*set page address*/ 
 write_i(0,0x81); /*contract control*/ 
 write_i(0,0x66); /*128*/ 
 write_i(0,0xA1); /*set segment remap*/ 
 write_i(0,0xA6); /*normal / reverse*/ 
 write_i(0,0xA8); /*multiplex ratio*/ 
 write_i(0,0x3F); /*duty = 1/64*/ 
 write_i(0,0xC8); /*Com scan direction*/ 
 write_i(0,0xD3); /*set display offset*/ 
 write_i(0,0x00); 
 write_i(0,0xD5); /*set osc division*/ 
 write_i(0,0x80); 
 write_i(0,0xD9); /*set pre-charge period*/ 
 write_i(0,0x1f); 
 write_i(0,0xDA); /*set COM pins*/ 
 write_i(0,0x12); 
 write_i(0,0xdb); /*set vcomh*/ 
 write_i(0,0x30); 
 write_i(0,0x8d); /*set charge pump enable*/ 
 write_i(0,0x14); 
 write_i(0,0xAF); /*display ON*/
 OLED_Clear();

}

//写入函数选择,可写命令和数据
void write_i(uint8_t flag,unsigned char ins) 
{
	DC_TTL(flag);//flag:0命令 1 数据
	CS_TTL(0);//片选选中拉低
  SPI2_sendrecvdate(ins);
	CS_TTL(1);//写完再拉高
 
}


/*
寻址模式和寻址范围

设置列地址:0x21+页的起始地址+列的结束地址
设置页地址:0x22+页的起始地址+页的结束地址
*/
void OLED_SetPos(uint8_t col,uint8_t page,uint8_t width,uint8_t high)
{
/*设置坐标,和宽度和高度*/
/*初始化屏幕*/
	write_i(0,0x20);//设置扫描模式
	write_i(0,0x00);//设置为水平扫描
	
	write_i(0,0x21);//列地址
	write_i(0,col);//列起始地址
	
	write_i(0,col+width-1);//列终止地址
	/*
	列是竖着的,宽是8是固定的必须取整,起始列号+数据的宽度-1就是结束列号,-1是起始列也存的有东西哈
	*/
	
	write_i(0,0x22);//页地址
	write_i(0,page);//页起始地址
	
	write_i(0,page+high/8-1);
	/*
	页是横着的一行一行的
	页终止地址 当前页地址+数据长度/8=结束页 第一页第一列是8个位---竖着的哈
	假设我一个数据是16位,那就要用到两个页,当前页+16/8得到结束页地址	
	当前页也存东西所以-1
	*/

}                          

//清屏函数
void OLED_Clear(void)
{
	uint16_t i;
	OLED_SetPos(0,0,128,64);
	
	for(i=0;i<128*64/8;i++)//清每一个像素点,一个像素点是8位
	{
		write_i(1,0x01);//ff是每个像素点全亮,00是全黑
	}

}

oled.h

#ifndef _O_LED_H_
#define _O_LED_H_
#include "stm32f10x.h"
#include "systick.h"
#define RES_TTL(x) (x?(GPIO_SetBits(GPIOA,GPIO_Pin_4)):(GPIO_ResetBits(GPIOA,GPIO_Pin_4)))
#define CS_TTL(x) (x?(GPIO_SetBits(GPIOB,GPIO_Pin_7)):(GPIO_ResetBits(GPIOB,GPIO_Pin_7)))
#define DC_TTL(x) (x?(GPIO_SetBits(GPIOA,GPIO_Pin_15)):(GPIO_ResetBits(GPIOA,GPIO_Pin_15)))


void spi2_config(void);
uint8_t SPI2_sendrecvdate(uint8_t dat);
void oled_config(void);
void write_i(uint8_t flag,unsigned char ins); 

void OLED_SetPos(uint8_t col,uint8_t page,uint8_t width,uint8_t high);
void OLED_Clear(void);
#endif

实现效果:
在这里插入图片描述
汉字测试:

字模:
uint8_t hzStr[][32]={

{0x04,0x44,0x44,0xFC,0x44,0x44,0x44,0x20,0xD8,0x17,0x10,0x10,0xF0,0x10,0x10,0x00,
0x10,0x30,0x10,0x1F,0x08,0x08,0x88,0x40,0x21,0x16,0x08,0x16,0x21,0x40,0x80,0x00},/*"玫",0*/
/* (16 X 16 , 宋体 )*/

{0x84,0x84,0xFC,0x84,0x84,0x00,0xFC,0x24,0x24,0xA6,0x7D,0x24,0x24,0xFC,0x00,0x00,
0x10,0x30,0x1F,0x08,0x08,0x80,0x41,0x31,0x0D,0x03,0x7D,0x99,0x95,0x99,0xE0,0x00},/*"瑰",1*/
/* (16 X 16 , 宋体 )*/
};

显示函数:

void OLED_ShowData(uint8_t col,uint8_t page,uint8_t width,uint8_t high,uint8_t *str)
{
  uint16_t i;
	OLED_SetPos(col,page,width,high);
	for(i=0;i<width*high/8;i++)
	{
	write_i(1,str[i]);
	}
}

调用:

void character(void)
{
 uint16_t i;
 for(i=0;i<2;i++)
	{
	OLED_ShowData(0+i*16,0,16,16,&hzStr[i][0]);
	}

}

在这里插入图片描述
在这里插入图片描述
图片:

void pic(void)
{
  OLED_ShowData(0,0,128,64,(uint8_t *)gImage_3);
}

图片的模太长了我就截图显示一部分:

在这里插入图片描述

在这里插入图片描述
显示:
在这里插入图片描述
感谢评阅,欢迎在评论区或者私聊交流指正!
Q:918619587

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐