玩转oled屏(基于SPI协议)


一、简介

(一)SPI协议简介

在这里插入图片描述
通讯开始/停止

  • 标号①处,NSS信号线由高变低,是SPI通讯的起始信号。NSS是每个从机各自独占的信号线,当从机检在自己的NSS线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。

  • 在图中⑥的标号处,NSS信号由低变高,是SPI通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。

数据有效传输

  • SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线来进行数据同步。

  • MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。

在这里插入图片描述
图片来源:SPI协议详解(图文并茂+超详细)

从上图可以看到传输数据0X53时,数据是一位一位开始传输的,并且在时钟的上升沿采集数据。

时钟极性与时钟周期

时钟极性CPOL是指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前、 NSS线为高电平时SCK的状态)。CPOL=0时, SCK在空闲状态时为低电平,CPOL=1时,则相反。

时钟相位CPHA是指数据的采样的时刻,当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样。当CPHA=1时,数据线在SCK的“偶数边沿”采样。

在这里插入图片描述

在这里插入图片描述

(二)OLED简介

OLED(OrganicLight-Emitting Diode),又称为有机电激光显示、有机发光半导体(OrganicElectroluminesence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。

实物图
在这里插入图片描述
模块引脚介绍

在这里插入图片描述
有关0.96寸OLED显示屏更多介绍,可参考下面链接

http://www.lcdwiki.com/zh/0.96inch_SPI_OLED_Module

标准例程接线方式

在这里插入图片描述

二、OLED滚动显示长字符

(一)常用OLED滚屏命令

1.水平左/右移

在这里插入图片描述

  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);        //虚拟字节                
  OLED_WR_Byte(0x2F,OLED_CMD);        //开启滚动

注意:此指令用于配置水平滚动参数和确定滚动起始页、终止页和滚动速度
水平滚动需要在调用此命令前禁用(2Eh), 否则RAM中的内容将会出错。

2.垂直和水平移动

在这里插入图片描述

OLED_WR_Byte(0x2e,OLED_CMD);        //关闭滚动
OLED_WR_Byte(0x2a,OLED_CMD);        //垂直和水平滚动 29(垂直向上,水平向右)/2a(垂直向上,水平向左)
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);        //终止页 1
OLED_WR_Byte(0x01,OLED_CMD);        //垂直滚动偏移量
OLED_WR_Byte(0x2F,OLED_CMD);        //开启滚动

此指令用于配置垂直和水平滚动参数和确定滚动起始页、终止页、滚动速度和垂直滚动偏移。

OLED_WR_Byte(0x01,OLED_CMD);        //垂直滚动偏移量

注意:如果垂直滚动偏移为0, 那么只将发生水平滚动(和命令26/27h一样)
滚动需要在调用此命令前禁用(2Eh), 否则RAM中的内容将会出错

(二)取字模

对想要显示的字体进行取模

取模选项
在这里插入图片描述
修改相应的格式如下并添加到对应程序中去


"山",0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08
0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08,0x21,0x08,0x3F,0xF8,0x00,0x08,0x00,0x00/*"山",0*/

"有",0x02,0x00,0x02,0x00,0xFF,0xFE,0x04,0x00,0x04,0x00,0x0F,0xF0,0x08,0x10,0x18,0x10
0x2F,0xF0,0x48,0x10,0x88,0x10,0x0F,0xF0,0x08,0x10,0x08,0x10,0x08,0x50,0x08,0x20/*"有",1*/

"木",0x01,0x00,0x01,0x00,0x01,0x00,0x01,0x00,0x7F,0xFC,0x03,0x80,0x05,0x40,0x05,0x40
0x09,0x20,0x11,0x10,0x21,0x08,0x41,0x04,0x81,0x02,0x01,0x00,0x01,0x00,0x01,0x00/*"木",2*/

"兮",0x04,0x40,0x04,0x40,0x08,0x20,0x08,0x20,0x10,0x10,0x20,0x08,0xDF,0xF6,0x04,0x00
0x08,0x00,0x0F,0xE0,0x00,0x20,0x00,0x20,0x00,0x20,0x00,0x20,0x01,0x40,0x00,0x80/*"兮",3*/

(三)OLED屏滑动演示

显示数据函数

void TEST_MainPage(void)
{	
	GUI_ShowCHinese(28,20,16,"山有木兮",1);
	GUI_ShowString(4,38,"631904110219",16,1);
	delay_ms(1500);		
	delay_ms(1500);
}

垂直和水平滚动

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(0x2a,OLED_CMD);        //垂直和水平滚动 29(垂直向上,水平向右)/2a(垂直向上,水平向左)
	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);        //终止页 1
	OLED_WR_Byte(0x01,OLED_CMD);        //垂直滚动偏移量
	TEST_MainPage();                    //测试函数
	OLED_WR_Byte(0x2F,OLED_CMD);        //开启滚动
	while(1)
	{
	
	}

演示效果

在这里插入图片描述

水平滑动显示歌词

参考以上方式,修改代码,水平滑动显示歌词

在这里插入图片描述

三、OLED显示温湿度

  • main函数
int main(void)
{	
	delay_init();	    	       //延时函数初始化	  
	NVIC_Configuration(); 	   //设置NVIC中断分组2:2位抢占优先级,2位响应优先级 	
	OLED_Init();			         //初始化OLED  
	OLED_Clear(0);             //清屏(全黑)

	while(1) 
	{		
    	TEST_MainPage();      //学号、姓名显示
		OLED_Clear(0); 
        for(i=0;i<5;i++)
        {
            TEST_Menu2();            //AHT20温湿度显示
            delay_ms(1000);
        }
        
        OLED_Clear(0); 
    }
}
  • TEST_Menu2()函数
void TEST_Menu2(void)
{
	u32 CT_data[2];
    volatile int  c1=0,t1=0;    
	srand(123456);
	delay_ms(40);
	AHT20_Read_CTdata(CT_data);       //不经过CRC校验,直接读取AHT20的温度和湿度数据    推荐每隔大于1S读一次
	c1 = CT_data[0]*100*10/1024/1024;    //湿度
    t1 = CT_data[1]*200*10/1024/1024-500; //温度
	
	GUI_DrawLine(0, 10, WIDTH-1, 10,1);
	GUI_DrawLine(WIDTH/2-1,11,WIDTH/2-1,HEIGHT-1,1);
	GUI_DrawLine(WIDTH/2-1,10+(HEIGHT-10)/2-1,WIDTH-1,10+(HEIGHT-10)/2-1,1);
	
	GUI_ShowString(0,1,"2021-11-21",8,1);
	GUI_ShowString(74,1,"Sunday",8,1);
	GUI_ShowString(14,HEIGHT-1-10,"Cloudy",8,1);
	GUI_ShowString(WIDTH/2+1,13,"TEMP",8,1);	//显示温度
	GUI_DrawCircle(WIDTH-20, 25, 1,2);
	GUI_ShowString(WIDTH-15,20,"C",16,1);
	GUI_ShowNum(WIDTH/2+8,20,t1/10,2,16,1);
	GUI_ShowString(WIDTH-41,26,".",8,1);
	GUI_ShowNum(WIDTH-35,20,t1%10,1,16,1);
	
	GUI_ShowString(WIDTH/2+1,39,"HUM",8,1);		//显示湿度
	GUI_ShowNum(WIDTH/2+8,46,c1/10,2,16,1);
	GUI_ShowString(WIDTH-41,52,".",8,1);
	GUI_ShowNum(WIDTH-35,46,c1%10,1,16,1);
	GUI_ShowString(WIDTH-21,46,"%",16,1);
	GUI_DrawBMP(6,16,51,32, BMP5, 1);
	delay_ms(1000);
}

其中AHT20温湿度采集的部分,可参考小编的另一篇博客

AHT20温湿度采集(I2C协议)

演示效果

在这里插入图片描述


总结

对比分析I2C与SPI协议,两者都有其优缺点。SPI协议支持全双工、传输速率较高、有很好的扩展性;但是SPI只支持一个主机模式,并且设备越多,所要占用主机的接口就越多。相比而言,I2C用很轻盈的架构实现了多主设备仲裁和设备路由,没有固定的主机,便于转换;但是数据大小限制在8位、传输速度低于SPI。
总的来说,I2C、SPI协议是用于系统内各设备芯片的主流协议,这些协议是如此的优美,以至于我们从系统内的“小协议”到理解系统间的“大协议”是很有帮助的。

以上若有不当之处,敬请指教。

参考

《零死角玩转STM32-基于野火指南者开发板PDF》及系列资料

SSD1306(OLED驱动芯片)指令详解

基于SPI通信方式的OLED显示

SSD1306-0.96寸oled屏-滚动指令介绍

基于STM32的0.96寸OLED屏滚动显示长字符

SPI协议详解(图文并茂+超详细)

  • 17
    点赞
  • 136
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是一份基于SPI协议驱动OLED显示的示例代码,供参考: ```c #include <reg51.h> // 定义OLED的引脚 sbit OLED_RST = P1^0; sbit OLED_DC = P1^1; sbit OLED_CS = P1^4; sbit OLED_SCL = P1^6; sbit OLED_SDA = P1^7; // 定义SPI通信命令 #define CMD_SET_COL_ADDR 0x15 #define CMD_SET_PAGE_ADDR 0x75 #define CMD_WRITE_RAM 0x5C // 定义幕分辨率 #define OLED_WIDTH 128 #define OLED_HEIGHT 64 // 定义字体 const unsigned char F6x8[][6] = { {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, // ' ' {0x00, 0x00, 0x00, 0x2f, 0x00, 0x00}, // '!' {0x00, 0x00, 0x07, 0x00, 0x07, 0x00}, // '"' // ... }; // 定义函数 void spi_write_byte(unsigned char dat); void spi_write_cmd(unsigned char cmd); void spi_write_data(unsigned char dat); void oled_init(); void oled_set_pos(unsigned char x, unsigned char y); void oled_clear(); void oled_show_string(unsigned char x, unsigned char y, const char *str); // 主函数 void main() { // 初始化OLED显示 oled_init(); // 在幕上显示字符串 oled_show_string(0, 0, "Hello, world!"); } // SPI写入一个字节 void spi_write_byte(unsigned char dat) { unsigned char i; for (i = 0; i < 8; i++) { OLED_SDA = (dat & 0x80) >> 7; dat <<= 1; OLED_SCL = 1; OLED_SCL = 0; } } // SPI写入一个命令 void spi_write_cmd(unsigned char cmd) { OLED_DC = 0; OLED_CS = 0; spi_write_byte(cmd); OLED_CS = 1; } // SPI写入一个数据 void spi_write_data(unsigned char dat) { OLED_DC = 1; OLED_CS = 0; spi_write_byte(dat); OLED_CS = 1; } // 初始化OLED显示 void oled_init() { OLED_RST = 0; delay(200); OLED_RST = 1; spi_write_cmd(0xAE); // 关闭显示 spi_write_cmd(0xD5); // 设置时钟分频因子 spi_write_cmd(0x80); // 时钟分频因子,越小越快 spi_write_cmd(0xA8); // 设置驱动路数 spi_write_cmd(0x3F); // 默认值(1/64) spi_write_cmd(0xD3); // 设置显示偏移 spi_write_cmd(0x00); // 默认值 spi_write_cmd(0x40); // 设置显示起始行 spi_write_cmd(0x8D); // 电荷泵设置 spi_write_cmd(0x14); // bit2,开启/关闭 spi_write_cmd(0x20); // 设置内存地址模式 spi_write_cmd(0x02); // 按列地址,从左到右,从上到下 spi_write_cmd(0xA1); // 设置列映射 spi_write_cmd(0xC8); // 设置行映射 spi_write_cmd(0xDA); // 设置COM硬件引脚配置 spi_write_cmd(0x12); // 默认值 spi_write_cmd(0x81); // 对比度设置 spi_write_cmd(0xCF); // 默认值 spi_write_cmd(0xD9); // 设置预充电周期 spi_write_cmd(0xF1); // 默认值 spi_write_cmd(0xDB); // 设置VCOMH电压倍率 spi_write_cmd(0x40); // 默认值 spi_write_cmd(0xA4); // 全局显示开启 spi_write_cmd(0xA6); // 设置显示方式,白色显示 spi_write_cmd(0xAF); // 开启显示 oled_clear(); // 清 } // 设置显示位置 void oled_set_pos(unsigned char x, unsigned char y) { spi_write_cmd(CMD_SET_COL_ADDR); spi_write_cmd(x); spi_write_cmd(OLED_WIDTH - 1); spi_write_cmd(CMD_SET_PAGE_ADDR); spi_write_cmd(y / 8); spi_write_cmd(OLED_HEIGHT / 8 - 1); } // 清 void oled_clear() { unsigned char i, j; for (i = 0; i < 8; i++) { oled_set_pos(0, i); for (j = 0; j < OLED_WIDTH; j++) { spi_write_data(0x00); } } } // 在指定位置显示字符串 void oled_show_string(unsigned char x, unsigned char y, const char *str) { unsigned char i = 0; while (str[i] != '\0') { oled_show_char(x + i * 6, y, str[i]); i++; } } // 在指定位置显示一个字符 void oled_show_char(unsigned char x, unsigned char y, unsigned char ch) { unsigned char i, j; for (i = 0; i < 6; i++) { unsigned char byte = F6x8[ch - ' '][i]; for (j = 0; j < 8; j++) { if (byte & 0x01) { spi_write_data(0xff << (j % 8)); } else { spi_write_data(0x00); } byte >>= 1; } oled_set_pos(x + i, y); } } ``` 在上述代码中,我们使用了51单片机的SPI接口来驱动OLED显示。在初始化函数中,我们通过向OLED发送一系列的命令来配置OLED的参数,以及打开显示。在显示函数中,我们通过内置的字体表来逐个显示字符,并使用`spi_write_data()`函数向OLED发送相应的数据。需要注意的是,OLED的显示是按页来进行的,每页8个像素。因此,在设置显示位置时,需要将纵坐标除以8。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值