STM32之CubeMX学习笔记(7)SPI驱动OLED及其优化


SPI通信

购买了逻辑分析仪后,总想着把所有的通讯信号全都看一遍。之前一篇笔记讲的是串口通信,做了一些小实验,搞清楚了如何基于底层利用串口传一些“非标”的数据。关于通信协议的第二篇,我想来看看SPI通信。

SPI简述

SPI通信是Serial peripheral interface的缩写,中文是串行外设接口,它可以使单片机与各种外围设备以串行的方式进行通信和交换信息,外围设备包括FlashRAM、网络控制器、LCD屏幕、AD转换器、甚至是其他的MCU。最早是摩托罗拉提出的三线SPI全双工协议,由一根SCK时钟线,一根主出从入MOSI线,一根主入从出MISO线。其中从线可以供多个从机使用,这时就需要片选线来进行选择,总共需要四根线。我们只需要确定是时钟上升沿为数据还是下降沿为数据即可。

一个关于时钟系“小”通信协议的问题

对比IIC来说,由于全双工的属性,SPI的传输速率是很高的,但是这样也会使用比较多的IO资源,IIC需要的IO口少,只要两个,但是IIC传输速度较慢。但这只是很多攻略上说的那样,其实SPI也可以只用两根线进行通信,一根时钟线,一根半双工通讯线。当然串口也有同步传输模式,不过它会在RX和TX的基础上再多加一个时钟线,这看起来又和SPI的全双工的三线模式很像了。这些“小”通讯协议虽然在底层的处理数据的逻辑上有些不同,但是其本质上还是服务于单片机等一些不需要大量传输数据的芯片间通信场景。
但是这些相似而不全相同的通讯协议间,在使用相同的系统资源后,性能和优缺点到底是怎么样的,这些问题可能已经有人做过了,但是耳闻不如躬行,所以还是要通过实验来验真知。
但是在本篇文章中我们不进行对比实验,我们要通过用SPI通信OLED屏幕来看一下双线SPI工作情况和对OLED驱动的优化。

7针OLED使用SPI协议显示

在我的操作习惯中,一般使用7针的0.96寸OLED屏幕进行对一些数字信息进行实时显示。首先这个传统是由和我对接的实验室师兄传给我的,而且在以后的一段时间中,因为驱动一只能用,就没有深究,只知道因为SPI传输速率快,所以一直用7针的。当然,实验室还有一批人是用4针,甚至我还认识一位兄弟用的是6针的OLED,4针和6针的OLED都是用IIC通信的,这种协议我们可以今后再做一个小实验来验证显示速率问题。
这个7针中景园OLED的驱动比较简单,管脚除了GND和VCC之外,还有D0(时钟线),D1(MOSI),RES(低电平复位),DC(决定发送的是命令还是数据),CS(片选口)。

OLED软件SPI

我们先在CubeMX中设置一下GPIO输出,并且将其速率调整到非常高,Very High。
在这里插入图片描述
在这里插入图片描述
在程序中只有一个写字节的底层函数,中景园用的是软件SPI,程序如下。

void OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{	
	uint8_t i;			  
	OLED_DC=cmd; //写命令 
	OLED_CS=0;		  
	for(i=0;i<8;i++)
	{			  
		OLED_D0=0;
		if(dat&0x80)OLED_D1=1;
		else OLED_D1=0;
		OLED_D0=1;
		dat<<=1;   
	}				 
	OLED_CS=1;		  
	OLED_DC=1; 
}

我们发现当片选CS拉低后,开始发送,D0模拟一个8个脉冲的时钟,D1则在这8个脉冲的时间内表现出“我是个8位数据”的样子,其中DC为0时是写命令给OLED,DC为1时是写显示数据给OLED。写完一个Byte后,DC和CS拉高。

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

我们通过上图可以发现,将STM32F401RCT6(84MHz)的IO速率设置为最高后,做出的D0时钟周期要100ns,再加上DC和CS的拉高,整个SPI发送8位数据的时间需要1.6us。当刷新整块OLED屏幕需要128*8=1024个8位数据,所需的时间需要近2ms的时间。所以不能将其放入1ms级的定时器中断,会导致单片机死机。

OLED硬件SPI

软件SPI在84MHz主频的单片机手下可以做到略高于5Mbit/s的传输速率,虽然这已经很快了,但是STM32F401RCT6的SPI1和4可以做到42Mbit/s的速率,SPI2和3也可以做到24Mbit/s的传输速率,我们为何不用硬件SPI呢。
在这里插入图片描述

并且对原代码进行修改,原代码是发送一个字节,我们这里也设置size为1

void OLED_WR_Byte(uint8_t dat,uint8_t cmd)
{	
	uint8_t *data = &dat;		  
	if(cmd)
		OLED_DC_Set();
	else
		OLED_DC_Clr();
	OLED_CS_Clr();
	
  	HAL_SPI_Transmit(&hspi1, (uint8_t *)data, 1, 200); 
	
	OLED_CS_Set();
	OLED_DC_Set();
}

很可惜的是,由于HAL库中对各种功能的判断处理操作过多,发送每个字节之间的间隔被延长到3.25us左右。如图。
在这里插入图片描述

HAL库在这方面是真的比较慢。改成16分频是因为这个速度不仅和GPIO控制的速度差不多(5.25Mbit/s),这也是我的逻辑分析仪能承受速度的极限(稳定采样率12MHz)。2分频的42Mbit/s也是可以用的,但只是不好观察。
在这里插入图片描述

我们拉长时间轴,可以发现这每个字节多用的3.25us让整个刷新OLED显存操作的时间上升到5ms,就算是换成42Mbit/s,这3us的硬伤还是无法解决。

OLED硬件SPI优化版

我们发现HAL_SPI是可以一次发送多个数据的,这样就不会一直做很多判断操作。我们从这个点出发,对程序进行一些修改。中景园使用的是一个二维数组来存放显存,但是数组的行是Y轴,数组的列是X轴,其刷新操作又是按照、X轴去刷新的。所以我们要将X轴变为横向的数组,这样我们就可以用spi一次发128个字节了。由于要写命令不能1024个字节一起发,但是128个连发已经省了不少时间。

uint8_t OLED_GRAM[128][8];//[x][y]
uint8_t OLED_XGRAM[128];

void OLED_Refresh_Gram(void)
{
	uint8_t i;
	uint8_t n;		    
	for(i=0;i<8;i++)  
	{  
		OLED_WR_Byte (0xb0+i,OLED_CMD);//设置页地址(0~7)
		OLED_WR_Byte (0x00,OLED_CMD);//设置显示位置—列低地址
		OLED_WR_Byte (0x10,OLED_CMD);//设置显示位置—列高地址   
		for(n=0;n<128;n++)
		{
//			OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
			OLED_XGRAM[n] = OLED_GRAM[n][i];
//			OLED_WR_Byte(OLED_XGRAM[n], OLED_DATA);
		}
    OLED_WR_128Byte(OLED_XGRAM, OLED_DATA);
	}   
}

void OLED_WR_128Byte(uint8_t *dat,uint8_t cmd)
{	 

//	uint8_t *data = &dat;		  
	if(cmd)
		OLED_DC_Set();
	else
		OLED_DC_Clr();
	OLED_CS_Clr();
	
  HAL_SPI_Transmit(&hspi1, dat, 128, 200); //需根据实际情况修改 
	
	OLED_CS_Set();
	OLED_DC_Set();

}

在调试程序的时候,我直接改成了数组首地址传送,不然当单个数据传过来时,它的地址已经去了堆栈,根本找不到下一个数据的地址。
在这里插入图片描述

我们发现,现在硬件SPI的速度终于赶上了软件SPI的1.8ms,但是这才是16分频,2分频的42Mbit/s才是真正的王道,大家请欣赏。
在这里插入图片描述

SPI的速度已经超过了我的逻辑分析仪采样速度,时钟读取和数据翻译已经完全乱套了,但是我们通过时间还可以看到整个屏幕显存刷新显示时间已经降低到了0.55ms。甚至数组转存的时间都在图中显而易见了。

总结

通过小幅改进中景园的OLED程序,我们熟悉了SPI数据的传输结构,尝试了软件和硬件spi的使用,也领略了42Mbit/s的硬件SPI的速度。SPI是一款实用的,简单的,高速的通讯协议,它的硬件传输直接与你的芯片主频挂钩,没有什么奇奇怪怪的速率协议,说的就是你,IIC。下一期,我会从软件和硬件的两个维度,来用逻辑分析仪看IIC协议。

  • 7
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值