嵌入式STM32深入之基于IIC和SPI协议的OLED显示

基于IIC和SPI协议的OLED显示

  • 一、前言
  • 二、SPI协议
    • (一)SPI介绍
    • (二)SPI优缺点
    • (三)SPI通信过程
  • 三、OLED介绍
  • 四、STM32+OLED显示
    • 实验准备
    • 1、显示自己的学号和姓名
      • (1)汉字取模
      • (2)提取显示字的数据
      • (3)代码书写
      • (4)效果展示
    • 2、显示AHT20的温度和湿度
      • (1)汉字取模
      • (2)代码撰写
      • (3)效果显示
    • 3、上下或左右的滑动显示长字符(最好使用硬件刷屏模式)
      • (1)滚屏设置
      • (2)OLED显示函数test.c
      • (3)主函数
      • (4)效果演示
  • 五、总结
  • 六、参考

一、前言

结合上次实验,我们学习了定时器的相关操作,进行了定时器的通过定时器的方式实现时间的精准控制,定时完成了点灯和PWM脉冲调制,并且我们介绍深入的第一个实验就是使用外设(外部传感器)进行利用简单的温度传感器采集温度
接下来我们学习第二个实验,利用OLED显示屏进行显示。具体实现进行一下实验:

- 理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:

  1. 显示自己的学号和姓名;
  2. 显示AHT20的温度和湿度;
  3. 上下或左右的滑动显示长字符,比如“Hello,欢迎来到物联网205实训室!”或者一段歌词或诗词(最好使用硬件刷屏模式)。

二、SPI协议

**SPI(Serial Peripheral interface)**是串行外围设备接口。串行总线技术可以使系统的硬件设计大大简化、系统的体积减小、可靠性提高。同时系统的更改和扩充极为容易。SPI是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为 PCB 的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议,STM32 也有 SPI 接口。
常用的串行扩展总线有:I2C(Inter IC Bus)总线、单总线(1-WIRE BUS)、SPI(Serial Peripheral Interface)总线及Microwire/PLUS等。

(一)SPI介绍

SPI总线是微控制器四线的外部总线。SPI没有明文标准,是一种事实总线,对通信操作的实现由芯片厂商和驱动开发者通过data sheet和application notes沟通实现的细节。SPI是四根信号线协议,如下:
嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第1张图片
SCLK:Serial Clock(Output from master);
时钟信号,由主设备产生。
MOSI:Master Output Slave Input(Outpt from Master);
主设备数据输出,从设备数据输入。
MISO:Master Input Slave Output(Output from Slave);
主设备数据输入,从设备数据输出。
CS:Slave Select(Active low,Output from Master);
从设备片选信号,由主设备控制。

SPI 主要特点有:
可以同时发出和接收串行数据;可以当作主机或从机工作;提供频率可编程时钟;发送结束中断标志;写冲突保护;总线竞争保护等。
SPI 总线四种工作方式 SPI 模块为了和外设进行数据交换,根据外设工作要求,其输出串行同步时钟极性和相位可以进行配置,时钟极性(CPOL)对传输协议没有重大的影响。

(二)SPI优缺点

优点

  • 支持全双工,信号完整性好;
  • 支持高速(100MHz以上);
  • 协议支持字长不限于8bits,可根据应用特点灵活选择消息字长,(高位先行还是低位先行,需要看外设芯片的手册,主要是保证两个 SPI通讯设备之间使用同样的协定);
  • 硬件连接简单;

缺点

  • 相比IIC多两根线,有4根线;
  • 没有寻址机制,只能靠片选选择不同设备。意思就是发送数据前,要先通过IO拉低设备片选信号,然后在发送数据,操作完成后将片选信号拉高;
  • 没有从设备接受ACK,主设备对于发送成功与否不得而知;
  • 典型应用只支持单主控;
  • 相比RS232 RS485和CAN总线,SPI传输距离短,局限于PCB板子;

(三)SPI通信过程

这是一张野火STM32F103手册上的图片,我们参考这种图片来分析通信过程
嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第2张图片
(1) 拉低NSS信号线,产生起始信号(图中没有画出);(需要软件操作)
(2) 把要发送的数据写入到“数据寄存器 DR”中,该数据会被存储到发送缓冲区;(需要软件操作)
(3) 通讯开始,SCK 时钟开始运行。MOSI 把发送缓冲区中的数据一位一位地传输出去;MISO 则把数据一位一位地存储进接收缓冲区中;(我们不用管,单片机会自动帮我们完成!)
(4) 当发送完一帧数据的时候,“状态寄存器 SR”中的“TXE 标志位”会被置 1,表示传输完一帧,发送缓冲区已空;类似地,当接收完一帧数据的时候,“RXNE标志位”会被置 1,表示传输完一帧,接收缓冲区非空;(需要软件操作,因为我们要做状态查询,通常是while死循环来保证数据被发送或接收)
(5) 等待到“TXE标志位”为1时,若还要继续发送数据,则再次往“数据寄存器DR”写入数据即可;等待到“RXNE 标志位”为 1时,通过读取“数据寄存器 DR”可以获取接收缓冲区中的内容;
(6) 拉高 NSS信号线,产生结束信号(需要软件操作)

三、OLED介绍

OLED即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第3张图片
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示,OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。

我们使用的是 ALINETEK 的 OLED 显示模块,该模块有以下特点:
1)模块有单色和双色两种可选,单色为纯蓝色,而双色则为黄蓝双色。
2)尺寸小,显示尺寸为 0.96 寸,而模块的尺寸仅为 27mmx26mm 大小。
3)高分辨率,该模块的分辨率为128x64。
4)多种接口方式,该模块提供了总共 5 种接口包括:6800、8080 两种并行接口方式、3线或 4 线的穿行 SPI 接口方式、IIC 接口方式(只需要 2 根线就可以控制 OLED 了)。
5)不需要高压,直接接 3.3V 就可以工作了。
注意该模块不和 5.0V 接口兼容,所以在使用的时候一定要小心,勿直接接到 5V 的系统上去,否则可能烧坏模块。

OLED与STM32连接
嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第4张图片
如图:

四、STM32+OLED显示

实验准备

  • OLED屏幕
  • STM32F103
  • USB转接口
  • 面包板、杜邦线等
  • 汉字转软件(也可以使用编程自己实现)
    软件地址:

1、显示自己的学号和姓名

(1)汉字取模

这里会用到汉字取模(网站):

  • https://www.23bei.com/tool/216.html
    也可以自己去我的网盘下载链接:https://pan.baidu.com/s/19ZSU-jMOTKi5sTwqc5WLnQ?pwd=w666
    提取码:w666
    打开我提供的安装包,点击运行程序:
    嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第5张图片
    我们这里要取得16*16的矩阵:
    对于 1616 的矩阵来说,它所需要的位数共是 1616=256 个位,每个字为 8 位,因此,每个汉字都需要用 256/8=32 个字节来表示。
    每两个字节代表一行的 16 个点,共需要 16 行,显示汉字时,只需一次性读取 32 个字节,并将每两个字节为一行打印出来,即可形成一个汉字。

这里1)是软件的设置,我们下一步详细介绍该点,2)是输入框我们要提取汉字的模就是输入在这里,3)点击即可生成在4)框里面。
嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第6张图片
点击设置:我们需要16*16的表示汉字(按照下图修改设置,并且确定保存)

嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第7张图片

(2)提取显示字的数据

嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第8张图片
将数据复制出来


,0x00,0x04,0x7F,0x04,0x41,0x04,0x41,0x24,0x65,0x24,0x55,0x24,0x55,0x24,0x49,0x24,
,0x49,0x24,0x55,0x24,0x55,0x24,0x65,0x24,0x41,0x04,0x41,0x04,0x45,0x14,0x42,0x08,/*"刚",0*/
,0x00,0x00,0x7F,0xF8,0x00,0x10,0x00,0x20,0x00,0x40,0x01,0x80,0x01,0x00,0xFF,0xFE,
,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x05,0x00,0x02,0x00,/*"子",1*/

我们在每一个子的前面加上这个字的注释就好了,如图:
在这里插入图片描述

(3)代码书写

内容显示 TEST_MainPage函数->test.c文件

void TEST_MainPage(void)
{	
	GUI_ShowString(28,0,"HG",16,1);//英文姓名
	GUI_ShowCHinese(28,20,16,"刚子",1);//中文姓名
	GUI_ShowString(4,48,"632007030126",16,1);//数字详细
	delay_ms(1500);		
	delay_ms(1500);
}

嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第9张图片
主函数->main.c文件

int main(void)
{	
	delay_init();	    	       //延时函数初始化	  
	OLED_Init();			         //初始化OLED  
	OLED_Clear(0);             //清屏(全黑)
	while(1) 
	{	
		TEST_MainPage();         //界面显示
	}
}

嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第10张图片
文字存储(举例)->oledfont.h文件
例子:

const typFNT_GB16 cfont16[] = 
{
	
	"设",0x00,0x00,0x21,0xF0,0x11,0x10,0x11,0x10,0x01,0x10,0x02,0x0E,0xF4,0x00,0x13,0xF8,
	0x11,0x08,0x11,0x10,0x10,0x90,0x14,0xA0,0x18,0x40,0x10,0xA0,0x03,0x18,0x0C,0x06,/*"设",2*/
	"置",0x7F,0xFC,0x44,0x44,0x7F,0xFC,0x01,0x00,0x7F,0xFC,0x01,0x00,0x1F,0xF0,0x10,0x10,
	0x1F,0xF0,0x10,0x10,0x1F,0xF0,0x10,0x10,0x1F,0xF0,0x10,0x10,0xFF,0xFE,0x00,0x00,/*"置",3*/
	"音",0x02,0x00,0x01,0x00,0x3F,0xF8,0x00,0x00,0x08,0x20,0x04,0x40,0xFF,0xFE,0x00,0x00,
	0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10,/*"音",4*/
	"量",0x00,0x00,0x1F,0xF0,0x10,0x10,0x1F,0xF0,0x10,0x10,0xFF,0xFE,0x00,0x00,0x1F,0xF0,
	0x11,0x10,0x1F,0xF0,0x11,0x10,0x1F,0xF0,0x01,0x00,0x1F,0xF0,0x01,0x00,0x7F,0xFC,/*"量",5*/
	"颜",0x10,0x00,0x08,0xFE,0x7F,0x10,0x22,0x20,0x14,0x7C,0x7F,0x44,0x44,0x54,0x48,0x54,
	0x52,0x54,0x44,0x54,0x48,0x54,0x51,0x54,0x42,0x28,0x44,0x24,0x88,0x42,0x30,0x82,/*"颜",6*/
	"色",0x08,0x00,0x08,0x00,0x1F,0xE0,0x20,0x20,0x40,0x40,0xBF,0xF8,0x21,0x08,0x21,0x08,
	0x21,0x08,0x3F,0xF8,0x20,0x00,0x20,0x02,0x20,0x02,0x20,0x02,0x1F,0xFE,0x00,0x00,/*"色",7*/
	"网",0x00,0x00,0x7F,0xFC,0x40,0x04,0x40,0x04,0x42,0x14,0x52,0x94,0x4A,0x54,0x44,0x24,
	0x44,0x24,0x4A,0x54,0x4A,0x54,0x52,0x94,0x61,0x04,0x40,0x04,0x40,0x14,0x40,0x08,/*"网",8*/
	"络",0x10,0x80,0x10,0x80,0x20,0xF8,0x21,0x08,0x4B,0x10,0xFC,0xA0,0x10,0x40,0x20,0xA0,
	0x41,0x18,0xFA,0x06,0x45,0xF8,0x01,0x08,0x19,0x08,0xE1,0x08,0x41,0xF8,0x01,0x08,/*"络",9*/
};

嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第11张图片

(4)效果展示

嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第12张图片

2、显示AHT20的温度和湿度

(1)汉字取模

与之前的相同,这里也是先对汉字取模,主要就是:
“温”、“度”、“湿”、“显”,“示”
嵌入式STM32深入之基于IIC和SPI协议的OLED显示_第13张图片
具体数据:

	"温",0x00,0x00,0x23,0xF8,0x12,0x08,0x12,0x08,0x83,0xF8,0x42,0x08,0x42,0x08,0x13,0xF8,
  0x10,0x00,0x27,0xFC,0xE4,0xA4,0x24,0xA4,0x24,0xA4,0x24,0xA4,0x2F,0xFE,0x00,0x00,/*"温",0*/
	"度",0x01,0x00,0x00,0x80,0x3F,0xFE,0x22,0x20,0x22,0x20,0x3F,0xFC,0x22,0x20,0x22,0x20,
  0x23,0xE0,0x20,0x00,0x2F,0xF0,0x24,0x10,0x42,0x20,0x41,0xC0,0x86,0x30,0x38,0x0E,/*"度",0*/
	"湿",0x00,0x00,0x27,0xF8,0x14,0x08,0x14,0x08,0x87,0xF8,0x44,0x08,0x44,0x08,0x17,0xF8,
  0x11,0x20,0x21,0x20,0xE9,0x24,0x25,0x28,0x23,0x30,0x21,0x20,0x2F,0xFE,0x00,0x00,/*"湿",0*/
	"显",0x00,0x00,0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,0x10,0x10,0x10,0x10,0x1F,0xF0,
  0x04,0x40,0x44,0x44,0x24,0x44,0x14,0x48,0x14,0x50,0x04,0x40,0xFF,0xFE,0x00,0x00,/*"显",0*/
	"示",0x00,0x00,0x3F,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFE,0x01,0x00,
  0x01,0x00,0x11,0x10,0x11,0x08,0x21,0x04,0x41,0x02,0x81,0x02,0x05,0x00,0x02,0x00,/*"示",0*/

(2)代码撰写

温湿度显示read_AHT20函数->bsp_i2c.c文件

void read_AHT20(void)
{
	uint8_t   i;
	for(i=0; i<6; i++)
	{
		readByte[i]=0;
	}

	//-------------
	I2C_Start();

	I2C_WriteByte(0x71);
	ack_status = Receive_ACK();
	readByte[0]= I2C_ReadByte();
	Send_ACK();

	readByte[1]= I2C_ReadByte();
	Send_ACK();

	readByte[2]= I2C_ReadByte();
	Send_ACK();

	readByte[3]= I2C_ReadByte();
	Send_ACK();

	readByte[4]= I2C_ReadByte();
	Send_ACK();

	readByte[5]= I2C_ReadByte();
	SendNot_Ack();
	//Send_ACK();

	I2C_Stop();

	//--------------
	if( (readByte[0] & 0x68) == 0x08 )
	{
		H1 = readByte[1];
		H1 = (H1<<8) | readByte[2];
		H1 = (H1<<8) | readByte[3];
		H1 = H1>>4;

		H1 = (H1*1000)/1024/1024;

		T1 = readByte[3];
		T1 = T1 & 0x0000000F;
		T1 = (T1<<8) | readByte[4];
		T1 = (T1<<8) | readByte[5];

		T1 = (T1*2000)/1024/1024 - 500;

		AHT20_OutData[0] = (H1>>8) & 0x000000FF;
		AHT20_OutData[1] = H1 & 0x000000FF;

		AHT20_OutData[2] = (T1>>8) & 0x000000FF;
		AHT20_OutData[3] = T1 & 0x000000FF;
	}
	else
	{
		AHT20_OutData[0] = 0xFF;
		AHT20_OutData[1] = 0xFF;

		AHT20_OutData[2] = 0xFF;
		AHT20_OutData[3] = 0xFF;
		printf("lyy");

	}
	/*通过串口显示采集得到的温湿度
	printf("\r\n");
	printf("温度:%d%d.%d",T1/100,(T1/10)%10,T1%10);
	printf("湿度:%d%d.%d",H1/100,(H1/10)%10,H1%10);
	printf("\r\n");*/
	t=T1/10;
	t1=T1%10;
	a=(float)(t+t1*0.1);
	h=H1/10;
	h1=H1%10;
	b=(float)(h+h1*0.1);
	sprintf(strTemp,"%.1f",a);   //调用Sprintf函数把DHT11的温度数据格式化到字符串数组变量strTemp中  
    sprintf(strHumi,"%.1f",b);    //调用Sprintf函数把DHT11的湿度数据格式化到字符串数组变量strHumi中  
	GUI_ShowCHinese(16,00,16,"温湿度显示",1);
	GUI_ShowCHinese(16,20,16,"温度",1);
	GUI_ShowString(53,20,strTemp,16,1);
	GUI_ShowCHinese(16,38,16,"湿度",1);
	GUI_ShowString(53,38,strHumi,16,1);
	delay_ms(1500);		
	delay_ms(1500);
}


主函数main.c

#include "delay.h"
#include "usart.h"
#include "bsp_i2c.h"
#include "sys.h"

#include "oled.h"
#include "gui.h"
#include "test.h"

int main(void)
{	
	delay_init();	    	       //延时函数初始化    	  
	uart_init(115200);	 
	IIC_Init();
		  
	NVIC_Configuration(); 	   //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 	
	OLED_Init();			         //初始化OLED  
	OLED_Clear(0); 
	while(1)
	{
		//printf("温度湿度显示");
		read_AHT20_once();
		OLED_Clear(0); 
		delay_ms(1500);
  }
}


(3)效果显示

此时是刷屏的换屏

3、上下或左右的滑动显示长字符(最好使用硬件刷屏模式)

与之前基本一样,就是在显示上不同,依然依次汉字取模,然后在添加文字字模代码->oledfont.h文件

(1)滚屏设置

水平左右移动

OLED_WR_Byte(0x2E,OLED_CMD);        //关闭滚动
OLED_WR_Byte(0x26,OLED_CMD);        //水平向左或者右滚动 26/27
OLED_WR_Byte(0x00,OLED_CMD);        //虚拟字节
OLED_WR_Byte(0x00,OLED_CMD);        //起始页 0
OLED_WR_Byte(0x07,OLED_CMD);        //滚动时间间隔
OLED_WR_Byte(0x07,OLED_CMD);        //终止页 7
OLED_WR_Byte(0x00,OLED_CMD);        //虚拟字节
OLED_WR_Byte(0xFF,OLED_CMD);        //虚拟字节
OLED_WR_Byte(0x2F,OLED_CMD);        //开启滚动

(2)OLED显示函数test.c

void TEST_MainPage(void)
{	
	GUI_ShowCHinese(10,20,16,"一切都是最好的安排",1);
	delay_ms(1500);		
	delay_ms(1500);
}


(3)主函数

#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
int main(void)
{	
	delay_init();	    	       //延时函数初始化	  
	NVIC_Configuration(); 	   //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 	
	OLED_Init();			         //初始化OLED  
	OLED_Clear(0);             //清屏(全黑)
	OLED_WR_Byte(0x2E,OLED_CMD);        //关闭滚动
    OLED_WR_Byte(0x27,OLED_CMD);        //水平向左或者右滚动 26/27
    OLED_WR_Byte(0x00,OLED_CMD);        //虚拟字节
	OLED_WR_Byte(0x00,OLED_CMD);        //起始页 0
	OLED_WR_Byte(0x07,OLED_CMD);        //滚动时间间隔
	OLED_WR_Byte(0x07,OLED_CMD);        //终止页 7
	OLED_WR_Byte(0x00,OLED_CMD);        //虚拟字节
	OLED_WR_Byte(0xFF,OLED_CMD);        //虚拟字节
	TEST_MainPage();
	OLED_WR_Byte(0x2F,OLED_CMD);        //开启滚动
}


(4)效果演示

五、总结

OLED是一个比较有意思的外设,在之后完成更多硬件项目时,可以利用OLED进行调试显示,帮助会很大,所以要好好掌握OLED的使用,多加练习,受益匪浅。
通过三个实验基本熟练了STM32+OLED的操作显示,代码和管脚配置没有问题的情况下,完成三个应用并不困难。
注意OLED显示时要对字长进行设置,否则无法完全显示出来。字模取模时,注意横向取模、纵向取模、倒序的差别,否则会得到一片模糊的点点,而不是正常清晰的汉字。作者在汉字取模这里吃了很大的亏,推荐大家两到三个字这样取模,不然就会运行一下反应过来30+的错误,但是你觉得自己思路没有什么问题。

六、参考

  1. https://blog.csdn.net/u010632165/article/details/109460814
  2. https://blog.csdn.net/qq_46467126/article/details/121439142
  3. https://blog.csdn.net/zcc_123/article/details/113446933

你可能感兴趣的:(stm32,单片机,arm)