嵌入式学习笔记——SPI通信的应用

前言

上一篇中介绍了STM32的SPI通信,并根据框图和寄存器进行了SPI通信的初始化配置,本文继续接着来运用SPI通信来做两个实际的需求,一个是常用的LCD屏幕,另一个是常用的外部存储器WAQXX。

屏幕分类

作为最常用的人机交互装置,在嵌入式系统开发过程中,屏幕显示始终是一个绕不开的内容,市面上关于屏幕这一块也是卷到飞起,各种尺寸,各种协议,各种规格的都有,最常见的有支持串口协议的屏幕,例如陶晶驰的串口屏、以及支持modbus协议的各类工控屏幕,这类屏幕都是内部集成了一个系统,可以使用厂商提供的上位机对屏幕进行编程以及界面设置,可以实现更多功能的同时也可以降低开发者的使用难度,关于这类屏幕,如果还没接触过的小伙伴可以去了解一下,这种集成好的屏幕,价格相对美好,而且功能丰富,准备电赛、自己做项目什么的都很方便,更重要的是串口屏在以后的工作中会经常遇到,关于串口屏我们后面有机会再来介绍,本文主要是使用SPI作为底层传输协议来驱动一款1.44寸的LCD屏幕,厂家是陶晶驰。
这里有一丢丢小知识需要大家做个了解,在嵌入式系统常用的屏幕类型除了上面的按照协议区分,还有就是按照其制作工艺来区分,在手只因(机)圈,发布会上最近几年高频出现的什么LCD,OLED,这俩就是不同的屏幕制作工艺,二者各有其优缺点,LCD屏幕由于使用了背光源,这导致它不能显示纯黑色,在黑色界面会漏光,而OLED是新技术,没有背光源,从根源上解决了漏光和无法显示纯黑的问题。但是LCD的使用寿命会比OLED好一些,同时价格也相对优惠。

1.3OLED概述

这里笔者选用的是中景园的1.3寸OLED来显示的,其驱动芯片是SH1106,分辨率是128*64,默认通信方式就是SPI。
在这里插入图片描述
在购买屏幕后,商家一般会给一个参考代码,大多数时候直接移植过来就可以直接使用了,但是这会让开发者对OLED的底层了解较少,换了屏幕或者需要特殊显示的时候会一头雾水,所以这里笔者带大家来稍稍细致的扒一下OLED的底层驱动代码以及配置流程。

驱动芯片

首先,通过上面商家的产品介绍,可以知道在这个屏幕内部有一个驱动芯片叫做SH1106,也就是说,在操作屏幕显示的时候,STM32是直接控制这个驱动芯片,然后再由驱动芯片去控制屏幕显示的。
既然是驱动外部芯片,那么就必须去看看这个芯片的手册了。这个手册可以直接在百度找或者去找商家要,或者有需要的可以私信。
在这里插入图片描述
首先第一页的产品特点,对于软件开发来说特别重要的就是红框框出来的位置,一个是告诉开发者,最大支持13264的分辨率,选用的这个屏幕是12864的,然后是下面的关于通信接口的介绍,这个驱动芯片支持多种协议。具体选用哪个需要看硬件接法,剩下就是一些关于电流电压的硬件参数了,如果是自己画板的话是需要去仔细查看的,这里笔者用的模块,所以就略过了这一部分。

框图

看完了芯片的产品特点,接下来再看看芯片的具体驱动框图,如下所示,上方的输出引脚是SEG0-SEG131与COM0–COM63,刚好与上面提到的最大分辨率132*64一一对应,这里就是直接控制屏幕各个点位的。
然后是左边的一系列电源以及滤波电路,主要作用就是为整个模块供电,然后是右侧的CL与CLS是用来选择时钟源的,这些我们大致了解就行了;最重要的是最下面的用户接口那部分。
在这里插入图片描述
需要特别注意的就是这里的用户接口的各个引脚,这里面有很多东西都是直接与编程相关的,最右边的CS是控制通信时的片选信号,然后A0 RD WR这些是在8080或者6800通信时用到的,这里不最了解,然后是RES是整个屏幕的硬件复位接口,需要我们控制,然后是IM0-IM2这三个脚是用来选择具体的通信方式的,剩下的D0-D7八个数据脚就是和主控进行数据传输用的,根据不同的协议会选用不同的引脚。
在这里插入图片描述
具体的引脚介绍如下图所示:这里重点了解M0-M2、CS、RES、D0、D1的作用。
在这里插入图片描述

原理图

根据数据手册的简介,就可以绘制硬件的原理图了,这里参考中景园的原理图,首先,为了能够兼容SPI(四线)和IIC,官方在设计原理图时使用了两个电阻来控制BS1的高低电平,这里得BS0-BS2也就是前面手册中提到的M0-M2 ,改变电阻选择拉高BS1,就需要使用IIC的通信;而拉低BS1就是使用SPI(4线)的通信方式,然后,由于CS、D/C(也就是上面提到的A0)、RES这三个线都需要开发者在编程时进行操作,所以也需要外接出来,用于控制。
在这里插入图片描述

通信时序

搞清楚了硬件的连接后,接下来就需要弄清楚软件的具体通讯流程了,如下图所示:首先通过时序图可以发现,当时钟线在高电平的时候,数据线的数据是处于平稳期的,也就是说,高电平时间是用来读取数据的,而时钟处于低电平的时候,数据处于跳变期,也就是此时是写入如数据的时刻;而且通过观察时钟线可以看出SCL的空闲电平是高电平,且是第二个边沿才开始进行操作,所以,SH1106的SPI通信使用的是模式3,在上一篇中我们提到过,模式3和模式0是可以通用,也就是说,在使用控制器配置过程中,可以选用模式三或者模式0。
而当使用IO模拟的时候,就需要根据时钟线拉高读取数据,时钟线拉低写入数据的时序来进行操作。在写入命令的时候需要将A0拉低(DC脚),写入数据的时候需要将A0拉高(DC脚)。
如下图所示,

显示的方式

在弄清楚了通信协议的具体方式后,接下来还需要进一步了解怎么控制OLED的显示;首先,整个屏幕是一个12864的矩阵,整个矩阵又被分为了八个页,其页地址分别是B0-B7;每一页都有8128个点阵,其写入方式就是每次写入一个八位数据到一页的一列,写入后列地址会自动偏移一次,继续写入下一列的数据,直到这页写完,然后才会跳转到下一页继续写。
在这里插入图片描述
举个例子,假设要在屏幕上显示一个1616的“中”,首先,需要对中进行取模
取模时是低位在前。
0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0xFF,0x10,0x10,0x10,0x10,0xF0,0x00,0x00,0x00,
0x00,0x00,0x0F,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,0x04,0x0F,0x00,0x00,0x00,/
“中”,0*/
这是取模后的数据,
具体的显示过程就是:
1.将0x00写入第一页的第一列,由于都是0,所以没有点亮的位置;
2.将0x00写入第一页的第二列;
3. …一直写入,直到把第一页写完;
4. 然后开始写入第二页的数据内容,也是一样,在第二页第一列写入0x00,第二列写入0x00,第三列写入0x0F,直到最后,一个“中”字就被显示出来了。
在这里插入图片描述
在这里主要要明白,整个显示的刷新方式是,以页为单位,从上到下,从左到右进行刷新的,每次写入的值就是对应想显示的内容的取模值,写入数据的每一位都对应着一个像素点,写入值是1时就点亮,写入值是0时就熄灭。

更形象的说刷现流程如下图所示,一开始是一整个花屏,然后屏幕开始由左上方第一页第一列开始刷新,以页为单位,按照从上到下,从左到右的顺序刷新,由于手机摄像头的刷新频率和屏幕的刷现频率不一致,所以会有频闪,有些影响效果。
在这里插入图片描述

页地址、列地址

在上面的中字显示介绍中,提到了也地址是从B0-B7一共八个,每个页占八行。每一次操作OLED屏幕的时钟,最少要操作1页
1页:8行 页地址不会自己偏移
OLED屏扫描方式:
在这里插入图片描述
传入数据:
同发送数据的函数
对相应的像素点写入1则点亮,对相应像素点写入0则熄灭
在这里插入图片描述
列地址:一个列地址由两个地址控制,高位和低位地址
低位地址的基地址:0x00 + 列的低4位数据
高位地址的基地址:0x10 + 列的高4位数据
列的取值返回:0~127
在实际显示数据的时候:列地址会基于传入的地址进行偏移
列地址会自动偏移:每次写入数据的时候只需要写入第一列即可
在这里插入图片描述
这样说起来可能不太清晰,举个栗子来说吧,假设我们对第15列写入数据,此时15列对应的十六进制是:0x0f,那么我们需要写入给芯片的地址就是:
低位地址: 0000 1111 =0x0f;
具体写入的低地址 = 0x00 + 列的低4位数据=0x00+0xf=0x0f;
高位地址:0001 0000 =0x10;
高位地址的基地址=0x10 + 列的高4位数据=0x10+0x00=0x10;
搞清楚对应的列地址计算方式有利于后面找到想要显示的位置。
根据上面的这个流程,假设要在第一页的第127(0x7f)列写入数据;就需要我们分别写入页地址:B0;
写入列低地址: 0x00+0x0f = 0x0f;
写入列高地址:0x10+0x07 =0x17;
根据上面的计算流程,可以总结一个OLED屏幕显示的设置显示位置的函数:

/************************************************
函数功能:OLED设置位置函数
函数名:Oled_Set_Addr
函数形参:u8 page,u8 col
函数返回值:None
备注:  
**************************************************/
void Oled_Set_Addr(u8 page,u8 col)
{
    
    
	OLED_Send_Commond(0xb0+ page);  //页的地址		
		//列的起始地址  列会自动偏移
	OLED_Send_Commond(0x00 + (col & 0x0f));   //列的低地址
	OLED_Send_Commond(0x10 + ((col & 0xf0)>>4));   //列的高地址
}

初始化指令

除了上面的行地址和列地址以外,在初始化过程中还需要写入很多其他命令,这些厂家都会给定,对应的指令在芯片手册也有介绍,由于数量有点多,想要了解的自己去对照看手册哈,这里笔者直接给出注释:

初始化需要写入的指令
//初始化序列
  OLED_Send_Commond(0xAE); //关闭显示
  OLED_Send_Commond(0xD5); //设置时钟分频因子,震荡频率
  OLED_Send_Commond(80);   //[3:0],分频因子;[7:4],震荡频率
  OLED_Send_Commond(0xA8); //设置驱动路数
  OLED_Send_Commond(0X3F); //默认0X3F(1/64)
  OLED_Send_Commond(0xD3); //设置显示偏移
  OLED_Send_Commond(0X00); //默认为0

  OLED_Send_Commond(0x40); //设置显示开始行 [5:0],行数.

  OLED_Send_Commond(0x8D); //电荷泵设置
  OLED_Send_Commond(0x14); //bit2,开启/关闭
  OLED_Send_Commond(0x20); //设置内存地址模式
  OLED_Send_Commond(0x02); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
  OLED_Send_Commond(0xA1); //段重定义设置,bit0:0,0->0;1,0->127;
  OLED_Send_Commond(0xC8); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
  OLED_Send_Commond(0xDA); //设置COM硬件引脚配置
  OLED_Send_Commond(0x12); //[5:4]配置

  OLED_Send_Commond(0x81); //对比度设置
  OLED_Send_Commond(0xEF); //1~255;默认0X7F (亮度设置,越大越亮)
  OLED_Send_Commond(0xD9); //设置预充电周期
  OLED_Send_Commond(0xf1); //[3:0],PHASE 1;[7:4],PHASE 2;
  OLED_Send_Commond(0xDB); //设置VCOMH 电压倍率
  OLED_Send_Commond(0x30); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;

  OLED_Send_Commond(0xA4); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
  OLED_Send_Commond(0xA6); //设置显示方式;bit0:1,反相显示;0,正常显示
  OLED_Send_Commond(0xAF); //开启显示
		

程序设计

关于屏幕的驱动的原理图以及芯片介绍就上面这些,接下来就是实际编写代码来实现屏幕的显示。

初始化代码

根据之前的硬件电路,可以知道,这个屏幕是使用的SPI通信接口来实现显示的,这里笔者使用的是学习板,几乎所有IO都是可以使用的;为了验证上一篇的SPI通信的配置是否正常,所以选用了SPI1,
也就是PB3、PB4、PB5这三个脚来作为SPI的通信引脚,在上一篇SPI控制器的介绍中提到过,SPI通信中,一般是不用控制器复用的GPIO口的,为了方便,SPI控制器的CS选用软件管理的方式,实际的CS片选脚由编程者根据硬件的连接,配置为通用推挽输出来控制即可。
在这里插入图片描述
除了SPI通信的三个脚以外,还需要对RES、DC进行控制,所以还需要配置管脚对它们进行驱动。
具体的引脚使用如下表:

OLED管脚 STM32F407VE
CLK PB3(SCK)
MOSI PB5(MOSI)
RES PB8
DC PB6
CS PB7

初始化

初始化伪代码:

OLED的初始化函数
{
    
    
	1.初始化SPI1的控制器,模式三或者模式零,主模式、八位数据、 双线单向、软件从器件管理、先发高位。
	2.初始化控制器OLEDRESCSDCIO口,配置为通用推挽输出
	3.设置空闲电平;
	4.对屏幕进行硬件复位;
	5.写入初始化序列;
	6.清屏;
}

写数据与写命令

关于初始化函数,前面的4步骤都好说,第五步的写入初始化序列与第六步的清屏函数还需要进一步进行封装。
根据之前的芯片手册介绍,我们知道写入的序列实际上使用命令,对于命令的写入,OLED是有要求的,具体的写入函数如下:


/************************************************
函数功能:OLED发送命令函数
函数名:OLED_Send_Commond
函数形参:u8 cmd
函数返回值:None
备注:  
**************************************************/
void OLED_Send_Commond(u8 cmd)
{
    
    
	OLED_CS_L;  //拉低片选
	OLED_DC_L;  //发送命令
	Spi_Send_Data(cmd);
	OLED_CS_H;  //结束通信
}
/************************************************
函数功能:OLED发送数据函数
函数名:OLED_Send_Data
函数形参:u8 data
函数返回值:None
备注:  
**************************************************/
void OLED_Send_Data(u8 data)
{
    
    
	OLED_CS_L;  //拉低片选
	OLED_DC_H;  //发送数据
	Spi_Send_Data(data);
	OLED_CS_H;  //结束通信
}

关于写数据和写命令的这两个函数,思路很好理解,首先,需要拉低CS片选,然后要通过控制DC脚的高低电平来告诉屏幕此时发送的是数据还是命令,然后调用SPI的发送函数进行发送就行了,由于OLED是不需要给STM32回数据的,所以就没有接收数据这个部分了。发送完成后将片选拉高,以便于下次的数据发送。

清屏函数

关于清屏函数,就涉及到了屏幕的具体显示了,这里也是有一个流程的,如下图所示,首先需要设置显示的起始位置,然后需要给定页地址,还要给定列地址。
在这里插入图片描述
根据这个流程,加上前面对于页地址,列地址的介绍,可以得出以下的清屏函数:

/************************************************
函数功能:OLED的清屏函数
函数名:OLED_Clear
函数形参:void
函数返回值:None
备注:  
**************************************************/
void OLED_Clear(void)
{
    
    
	u8 i,j;
	
	for(i=0;i<8;i++)  //页的循环一共是B0-B7八页
	{
    
    
		OLED_Send_Commond(0xb0+i);  //页的地址
		
		//列的起始地址  列会自动偏移
		OLED_Send_Commond(0x00);   //列的低地址
		OLED_Send_Commond(0x10);   //列的高地址
		for(j=0;j<132;j++)  //列的循环
		{
    
    
			OLED_Send_Data(0x00);//写入0x00就是全部默认熄灭,写入0XFF就是默认全点亮。
		}
	}
}

初始化代码

补充完上面这三个函数后,就可以对OLED进行初始化操作了,SPI1的初始化就是上一篇中的SPI控制器的初始化代码。


/*******************************************
*函数名    :OLED_Init
*函数功能  :OLED初始化配置
*函数参数  :无
*函数返回值:无
*函数描述  :
SCK------PB3   //复用输出 
MISO-----PB4   //复用输出
MOSI-----PB5   //复用输出

RES------PB8   //通用推挽输出
CS-------PB7	 //通用推挽输出
DC-------PB6   //通用推挽输出
*********************************************/
void OLED_Init(void)
{
    
    
	/*IO口控制器配置*/
//	//端口时钟使能	
	RCC->AHB1ENR |= 1<<1;// PB
	//OLED_DC  		PB6
	GPIOB->MODER &= ~(3<<12);
	GPIOB->MODER |= 1<<12;
	GPIOB->OTYPER &= ~(1<<6);
	GPIOB->OSPEEDR &= ~(3<<12);
	GPIOB->OSPEEDR |= 2<<12;	
	
	//OLED_CS  		PB7
	GPIOB->MODER &= ~(3<<14);
	GPIOB->MODER |= 1<<14;
	GPIOB->OTYPER &= ~(1<<7);
	GPIOB->OSPEEDR &= ~(3<<14);
	GPIOB->OSPEEDR |= 2<<14;	
	
	//OLED_RES 		PB8
	GPIOB->MODER &= ~(3<<16);
	GPIOB->MODER |= 1<<16;
	GPIOB->OTYPER &= ~(1<<8);
	GPIOB->OSPEEDR &= ~(3<<16);
	GPIOB->OSPEEDR |= 2<<16;
	//初始状态 res=1;DC=1;CS=1
	GPIOB->ODR |=(0X7<<6);//空闲状态为高
	
	Spi1_Init();
	
	OLED_RES_L;
	SysTick_Delay_ms(200);
	OLED_RES_H;//复位
	
		//初始化序列
  OLED_Send_Commond(0xAE); //关闭显示
  OLED_Send_Commond(0xD5); //设置时钟分频因子,震荡频率
  OLED_Send_Commond(80);   //[3:0],分频因子;[7:4],震荡频率
  OLED_Send_Commond(0xA8); //设置驱动路数
  OLED_Send_Commond(0X3F); //默认0X3F(1/64)
  OLED_Send_Commond(0xD3); //设置显示偏移
  OLED_Send_Commond(0X00); //默认为0

  OLED_Send_Commond(0x40); //设置显示开始行 [5:0],行数.

  OLED_Send_Commond(0x8D); //电荷泵设置
  OLED_Send_Commond(0x14); //bit2,开启/关闭
  OLED_Send_Commond(0x20); //设置内存地址模式
  OLED_Send_Commond(0x02); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;
  OLED_Send_Commond(0xA1); //段重定义设置,bit0:0,0->0;1,0->127;
  OLED_Send_Commond(0xC8); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数
  OLED_Send_Commond(0xDA); //设置COM硬件引脚配置
  OLED_Send_Commond(0x12); //[5:4]配置

  OLED_Send_Commond(0x81); //对比度设置
  OLED_Send_Commond(0xEF); //1~255;默认0X7F (亮度设置,越大越亮)
  OLED_Send_Commond(0xD9); //设置预充电周期
  OLED_Send_Commond(0xf1); //[3:0],PHASE 1;[7:4],PHASE 2;
  OLED_Send_Commond(0xDB); //设置VCOMH 电压倍率
  OLED_Send_Commond(0x30); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;

  OLED_Send_Commond(0xA4); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)
  OLED_Send_Commond(0xA6); //设置显示方式;bit0:1,反相显示;0,正常显示
  OLED_Send_Commond(0xAF); //开启显示
		
	//清屏函数
	OLED_Clear();
}

字符显示函数

同样的,在理解了显示原理后,还可以自己来尝试写一下函数,比如说显示英文字符,汉字,字符串,画点,画圆,画线等等的函数。
首先是字符的显示函数,根据上面显示汉字“中”的介绍,可以知道,要向显示对应的字符,首先是要进行取模,具体的取模方式应该与我们的显示顺序一致:详细的取模方式大家自己去搜索吧,CSDN很多大佬都写过详细的介绍。
在这里插入图片描述
例如我们这里想要显示一个“A”,它占得大小是168也就时需要两页才能显示,取模的数据如下0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,/“A”,0*/
/* (8 X 16 , 宋体 )*/
在这里插入图片描述
根据之前的清屏函数,稍微修改一下就可以显示出A了,具体的代码如下:


/************************************************
函数功能:OLED设置位置函数
函数名:Oled_Set_Addr
函数形参:u8 page,u8 col
函数返回值:None
备注:  
**************************************************/
void Oled_Set_Addr(u8 page,u8 col)
{
    
    
	OLED_Send_Commond(0xb0+ page);  //页的地址		
		//列的起始地址  列会自动偏移
	OLED_Send_Commond(0x00 + (col & 0x0f));   //列的低地址
	OLED_Send_Commond(0x10 + ((col & 0xf0)>>4));   //列的高地址
}



/************************************************
函数功能:OLED显示单个英文字符
函数名:Oled_Show_Ch
函数形参:u8 page,u8 col  决定字显示在什么位置
          u8 ch想要显示的字符

函数返回值:None
备注:  
**************************************************/
void Oled_Show_Ch(u8 page,u8 col)
{
    
    
	u8 i,j;
	u8 A_buff[16]={
    
    0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,
								0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20};//A的取模16*8的大小

	for(i=0;i<2;i++)  //页的循
	{
    
    
    Oled_Set_Addr(page+i,col);   //设置显示位置
		for(j=0;j<8;j++)  //列的循环
		{
    
    
			OLED_Send_Data(A_buff[i*8 + j]);
		}
	}
}


实际的显示效果如下:
在这里插入图片描述
利用相似的思路,还可以显示汉字,需要注意的是,汉字的一个字的大小是1616是英文字符的2倍。
中(0)
在这里插入图片描述
0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0xFF,0x10,0x10,0x10,0x10,0xF0,0x00,0x00,0x00,
0x00,0x00,0x0F,0x04,0x04,0x04,0x04,0xFF,0x04,0x04,0x04,0x04,0x0F,0x00,0x00,0x00,/
“中”,0*/
/* (16 X 16 , 宋体 )*/

// An highlighted block
/************************************************
函数功能:OLED显示单个中文字符
函数名:Oled_Show_Hz
函数形参:u8 page,u8 col  决定字显示在什么位置
          u8 *str 想要显示的汉字

函数返回值:None
备注:
只有添加了字模才能使用
**************************************************/
void Oled_Show_Hz(u8 page,u8 col)
{
    
    
	u8 i,j;
	u8 CH_buff[32]={
    
    0x00,0x00,0xF0,0x10,0x10,0x10,0x10,0xFF,
									0x10,0x10,0x10,0x10,0xF0,0x00,0x00,0x00,
									0x00,0x00,0x0F,0x04,0x04,0x04,0x04,0xFF,
									0x04,0x04,0x04,0x04,0x0F,0x00,0x00,0x00/*"中",0*//* (16 X 16 , 宋体 )*/
									};

	
	for(i=0;i<2;i++) //页循环
	{
    
    
		Oled_Set_Addr(page+i,col);   //设置显示位置
		for(j=0;j<16;j++)
		{
    
    
			OLED_Send_Data(CH_buff[i*16+j]);
		}
	}
}

在这里插入图片描述
在搞定了显示的思路后,还可以提前对字符和部分汉字进行取模,然后直接调用字符串显示,进一步封装后代码如下所示:


/************************************************
函数功能:OLED显示单个英文字符
函数名:Oled_Show_Ch
函数形参:u8 page,u8 col  决定字显示在什么位置
          u8 ch想要显示的字符

函数返回值:None
备注:  
**************************************************/
void Oled_Show_Ch(u8 page,u8 col,u8 ch)
{
    
    
	u8 i,j;
	u8 n=0;  //表示字符在数组中的位置
	
	n = ch - ' ';

	for(i=0;i<2;i++)  //页的循
	{
    
    
    Oled_Set_Addr(page+i,col);   //设置显示位置
		for(j=0;j<8;j++)  //列的循环
		{
    
    
			OLED_Send_Data(Aciss_8X16[n*16 + i*8 + j]);
		}
	}
}


/************************************************
函数功能:OLED显示单个中文字符
函数名:Oled_Show_Hz
函数形参:u8 page,u8 col  决定字显示在什么位置
          u8 *str 想要显示的汉字

函数返回值:None
备注:
只有添加了字模才能使用
**************************************************/
void Oled_Show_Hz(u8 page,u8 col,u8 *str)
{
    
    
	u8 i,j;
	u8 n=0;  //找到汉字在数组中的位置
	
	while(1)
	{
    
    
		if(*str==table[n*2] && *(str+1)==table[n*2+1])
		{
    
    
			//此时的N为字在数组中的位置
			break;
		}
		n++;
	}
	
	for(i=0;i<2;i++) //页循环
	{
    
    
		Oled_Set_Addr(page+i,col);   //设置显示位置
		for(j=0;j<16;j++)
		{
    
    
			OLED_Send_Data(Hz_16X16[n*32+i*16+j]);
		}
	}
}


/************************************************
函数功能:OLED显示字符串
函数名:Oled_Show_Str
函数形参:u8 page,u8 col  决定字显示在什么位置
          u8 *str 待显示的字符串
函数返回值:None
备注: 
可以显示中英结合的字符串
**************************************************/
void Oled_Show_Str(u8 page,u8 col,u8 *str)
{
    
    
	//"123云家居"
	while(1)
	{
    
    
		if(*str >0 && *str <127)  //英文字符
		{
    
    
			Oled_Show_Ch(page,col,*str); //‘1’
			str++;     //显示下一个字符
			col+=8;
			
			if(col>128-8)  //遇到屏幕边缘
			{
    
    
				page+=2;  //换页
				col=0;    //列回到起始位置
			}
			if(*str=='\0')
			{
    
    
				break;
			}
		}
		else                      //中文字符
		{
    
    
			Oled_Show_Hz(page,col,str);  //“云”
			str+=2;  //汉字是字符的两倍
			col+=16;
			if(col>128-16)  //遇到屏幕边缘
			{
    
    
				page+=2;  //换页
				col=0;    //列回到起始位置
			}
			if(*str=='\0')
			{
    
    
				break;
			}
		}
	}
}

//显示笔画简单
/************************************************
函数功能:OLED显示图片
函数名:Oled_Show_Pic
函数形参:u8 page,u8 col  页和列显示图片
          u8 w,u8 h,u8 *pic
         
函数返回值:None
备注: 
可以显示中英结合的字符串,自动换行
**************************************************/
void Oled_Show_Pic(u8 page,u8 col,u8 w,u8 h,u8 *pic)
{
    
    
	u8 i,j;

	for(i=0;i<h/8;i++)
	{
    
    
   Oled_Set_Addr(page+i,col);
		for(j=0;j<w;j++)
		{
    
    
			OLED_Send_Data(pic[i*w+j]);
		}
	}
}

在这里插入图片描述

总结

原本打算一篇就将OLED显示以及W25Qxx烧录字库一起介绍的,但是没想到篇幅这么大,而且OLED的功能都还没写全,先这样吧,后面有空再补充吧,文中如有不足欢迎批评指正。

M4系列目录

1.嵌入式学习笔记——概述
2.嵌入式学习笔记——基于Cortex-M的单片机介绍
3.嵌入式学习笔记——STM32单片机开发前的准备
4.嵌入式学习笔记——STM32硬件基础知识
5.嵌入式学习笔记——认识STM32的 GPIO口
6.嵌入式学习笔记——使用寄存器编程操作GPIO
7.嵌入式学习笔记——寄存器实现控制LED小灯
8.嵌入式学习笔记——使用寄存器编程实现按键输入功能
9.嵌入式学习笔记——STM32的USART通信概述
10.嵌入式学习笔记——STM32的USART相关寄存器介绍及其配置
11.嵌入式学习笔记——STM32的USART收发字符串及串口中断
12.嵌入式学习笔记——STM32的中断控制体系
13.嵌入式学习笔记——STM32寄存器编程实现外部中断
14.嵌入式学习笔记——STM32的时钟树
15.嵌入式学习笔记——SysTick(系统滴答)
16.嵌入式学习笔记——M4的基本定时器
17.嵌入式学习笔记——通用定时器
18.嵌入式学习笔记——PWM与输入捕获(上)
19.嵌入式学习笔记——PWM与输入捕获(下)
20.嵌入式学习笔记——ADC模数转换器
21.嵌入式学习笔记——DMA
22.嵌入式学习笔记——SPI通信
23.嵌入式学习笔记——SPI通信的应用
24嵌入式学习笔记——IIC通信

猜你喜欢

转载自blog.csdn.net/qq_41954556/article/details/130067994