标号①处,NSS信号线由高变低,是SPI通讯的起始信号。NSS是每个从机各自独占的信号线,当从机检在自己的NSS线检测到起始信号后,就知道自己被主机选中了,开始准备与主机通讯。
在图中⑥的标号处,NSS信号由低变高,是SPI通讯的停止信号,表示本次通讯结束,从机的选中状态被取消。
数据有效传输
SPI使用MOSI及MISO信号线来传输数据,使用SCK信号线来进行数据同步。
MOSI及MISO数据线在SCK的每个时钟周期传输一位数据,且数据输入输出是同时进行的。
从上图可以看到传输数据0X53
时,数据是一位一位开始传输的,并且在时钟的上升沿采集数据。
时钟极性与时钟周期
时钟极性CPOL是指SPI通讯设备处于空闲状态时,SCK信号线的电平信号(即SPI通讯开始前、 NSS线为高电平时SCK的状态)。CPOL=0时, SCK在空闲状态时为低电平,CPOL=1时,则相反。
时钟相位CPHA是指数据的采样的时刻,当CPHA=0时,MOSI或MISO数据线上的信号将会在SCK时钟线的“奇数边沿”被采样。当CPHA=1时,数据线在SCK的“偶数边沿”采样。
OLED(OrganicLight-Emitting Diode),又称为有机电激光显示、有机发光半导体(OrganicElectroluminesence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。
http://www.lcdwiki.com/zh/0.96inch_SPI_OLED_Module
标准例程接线方式
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中的内容将会出错。
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*/
显示数据函数
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)
{
}
演示效果
水平滑动显示歌词
参考以上方式,修改代码,水平滑动显示歌词
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);
}
}
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协议详解(图文并茂+超详细)