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

news2024/11/17 6:51:27

SPI通信的应用

  • 前言
  • 屏幕分类
    • 1.3OLED概述
      • 驱动芯片
      • 框图
      • 原理图
      • 通信时序
      • 显示的方式
      • 页地址、列地址
      • 初始化指令
  • 程序设计
    • 初始化代码
      • 初始化
      • 写数据与写命令
      • 清屏函数
    • 初始化代码
    • 字符显示函数
  • 总结

前言

上一篇中介绍了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
CLKPB3(SCK)
MOSIPB5(MOSI)
RESPB8
DCPB6
CSPB7

初始化

初始化伪代码:

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的功能都还没写全,先这样吧,后面有空再补充吧,文中如有不足欢迎批评指正。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/445606.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

轻松掌握k8s(使用docker)安装知识点

1、介绍 kubernetes具有以下特性&#xff1a; 服务发现和负载均衡 Kubernetes 可以使用 DNS 名称或自己的 IP 地址公开容器&#xff0c;如果进入容器的流量很大&#xff0c; Kubernetes 可以负载均衡并分配网络流量&#xff0c;从而使部署稳定。存储编排 Kubernetes 允许你自…

【数据库】— 无损连接、Chase算法、保持函数依赖

【数据库】— 无损连接、Chase算法 Chase算法Chase算法举例一种简便方法&#xff1a;分解为两个模式时无损连接和函数依赖的一个简单例子 Chase算法 形式化定义&#xff1a; 构造一个 k k k行 n n n列的表格&#xff0c;每行对应一个模式 R i ( 1 ≤ i ≤ k ) Ri (1≤i ≤ k)…

计算机组成原理汇总

提示&#xff1a;日落归山海&#xff0c;山海藏深情 文章目录 1.1 计算机的发展1.2 计算机硬件的基本组成1.3 计算机的性能指标2.1.1 进位计数制2.1.2 BCD码2.1.3 无符号整数的表示和运算2.1.4 带符号整数的表示和运算(原反补)2.1.5原反补码的特性对比2.1.6 移码2.1.7 定点小数…

【逗老师的无线电】骚活,GPS热点盒子自动上报APRS位置

逗老师最近整了个有意思的小活&#xff0c;组装了一个有4G网卡带GPS功能的热点盒子&#xff0c;让盒子基于GPS位置信息&#xff0c;自动上报APRS位置帧 全篇亮点 基于GPS和AGPS共同定位基于TCP直接上报APRS数据帧 别说&#xff0c;这小活整完之后&#xff0c;还是有点意思的&…

linux coredump

文章目录 是什么生成原理coredump 的“危害” reference: 一文读懂Coredump文件是如何生成的 GDB是什么&#xff1f; 是什么 简单的讲&#xff1a;当进程接收到某些信号而导致异常退出时&#xff0c;就会生成 coredump 文件 在程序发生某些错误而导致进程异常退出时&#x…

技术分析内核并发消杀器(KCSAN)一文解决!

一、KCSAN介绍 KCSAN(Kernel Concurrency Sanitizer)是一种动态竞态检测器&#xff0c;它依赖于编译时插装&#xff0c;并使用基于观察点的采样方法来检测竞态&#xff0c;其主要目的是检测数据竞争。 KCSAN是一种检测LKMM(Linux内核内存一致性模型)定义的数据竞争(data race…

亿发软件:中大型仓库进出货管理系统解决方案,定制软件让仓储作业高效便捷

中大型仓库出入库管理是传统厂家供应链管理流程的重要部分&#xff0c;直接关乎货物在仓库当中存储的安全&#xff0c;和员工工作的效率。一旦仓库管理当中出现了疏漏&#xff0c;那么货物的信息数据就会发生变动&#xff0c;导致实际与账目不符。人工带来的低效与不可控是传统…

软件测试行业到底有没有前景和出路?

我现在来跟你说说软件测试的真正情况。 首先一个软件做出来&#xff0c;最不能少的人是谁&#xff1f;不用说就是开发&#xff0c;因为开发是最了解软件运作的那个人&#xff0c;早期不少一人撸网站或者APP的例子&#xff0c;相当于一个人同时是产品、研发、测试、运维等等&am…

15-721 Chapter9 数据压缩

Background disk database的瓶颈在disk IO上的话&#xff08;也就是说数据压缩的好处很大&#xff0c;可以比较放心的做&#xff09;&#xff0c;那么内存数据库的瓶颈是多方面的&#xff0c;其中包含cpu。所以我们要在计算量和压缩率&#xff08;DRAM还是有点贵的&#xff0c…

需求管理实践四大流程的注意事项

需求管理实践包括四大流程&#xff1a;需求采集、需求分析、需求筛选和需求处理。 1、需求采集注意事项 需求采集需要通过多种形式对不同用户需求进行收集&#xff0c;并对需求的属性进行详细记录&#xff0c;并记录可追溯的反馈人员&#xff0c;以便后期跟踪修改。 需求管理实…

你不知道的Redis Search 以及安装指南

theme: orange 本文正在参加「金石计划」 这篇文章是为了使用Redis Search 的向量搜索功能提前做的环境准备工作。即讨论如何在准备生产的 linux 环境中安装 RediSearch 和 RedisJSON 模块。 什么是RediSearch&#xff1f; 根据RediSearch的官方文档 RediSearch是这样描述的。 …

线性表,栈和队列(2)

作者&#xff1a;额~我那个早过50了&#xff0c;忘记了 言归正传ca 什么是栈&#xff1f; 小李攒钱买了车&#xff0c;可是他家住在胡同的尽头。胡同很窄&#xff0c;只能通过一辆车&#xff0c;而且是死胡同。小李每天都为停车发愁&#xff0c;如果回家早了停在里面&#x…

pandas笔记:pandas 排序 (sort_values)

1 函数说明 DataFrame.sort_values(by,*, axis0, ascendingTrue, inplaceFalse, kindquicksort, na_positionlast, ignore_indexFalse, keyNone) 2 参数说明 by string或者一组string组成的list&#xff0c;根据什么进行排序 axis{0 or ‘index’, 1 or ‘columns’}ascendi…

其它 Composition API

1.shallowReactive 与 shallowRef shallow有浅的意思 首先在原有的页面上引入它&#xff0c;然后用shallowReactive包裹它 浅层次的意思就是像name&#xff0c;age这些是响应式的&#xff0c;而job就不处理&#xff0c;意思就是只处理第一层数据&#xff0c;像第二层的都不处理…

电气互联系统有功-无功协同优化模型

目录 1 主要内容 模型示意图 目标函数 程序亮点 2 部分程序 3 程序结果 4 程序链接 1 主要内容 本程序基本复现《“碳中和”目标下电气互联系统有功-无功协同优化模型》&#xff0c;文献模型提供了一个很好的创新思路&#xff0c;把常规电气互联系统的调度和有功无功优化…

【Linux】-关于Linux的指令(上)

作者&#xff1a;小树苗渴望变成参天大树 作者宣言&#xff1a;认真写好每一篇博客 作者gitee:gitee 如 果 你 喜 欢 作 者 的 文 章 &#xff0c;就 给 作 者 点 点 关 注 吧&#xff01; TOC 前言 今天我们来讲关于Linux的基本指令&#xff0c;博主讲的指令会对应着Windows…

Java 8中新特性Stream的详细理解和使用

Java 8中新特性Stream的基本理解和使用 Stream基本概念 Stream流是来自数据源的元素队列并支持聚合操作 **元素&#xff1a;**是特定类型的对象&#xff0c;是数据源形成的一个队列。Java中Stream不会存储元素&#xff0c;而是按需计算 数据源&#xff1a;Stream流数据的来…

自定义函数 | R语言批量计算组间差值

批量字符替换、数值大小比较并重新赋值 cal_repeat()函数的实际操作1.不考虑处理变量。考虑多变量和指定2列变量的情况&#xff08;长数据&#xff09;&#xff1a;2.考虑处理变量。考虑多处理&#xff0c;多变量的情况&#xff08;长数据&#xff09;&#xff1a;3.考虑处理变…

Node【Global全局对象】

文章目录 &#x1f31f;前言&#x1f31f;Global全局对象&#x1f31f;Global对象属性与方法&#x1f31f;Global对象属性&#x1f31f;process&#x1f31f;Buffer类&#x1f31f;console &#x1f31f;写在最后 &#x1f31f;前言 哈喽小伙伴们&#xff0c;新的专栏 Node 已…

华为OD机试真题(Java),最优策略组合下的总的系统消耗资源数(100%通过+复盘思路)

一、题目描述 在通信系统中有一个常见的问题是对用户进行不同策略的调度&#xff0c;会得到不同系统消耗的性能。 假设由N个待串行用户&#xff0c;每个用户可以使用A/B/C三种不同的调度策略&#xff0c;不同的策略会消耗不同的系统资源。 请你根据如下规则进行用户调度&…