当前位置: 首页 > news >正文

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

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); //默认为0OLED_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  		PB6GPIOB->MODER &= ~(3<<12);GPIOB->MODER |= 1<<12;GPIOB->OTYPER &= ~(1<<6);GPIOB->OSPEEDR &= ~(3<<12);GPIOB->OSPEEDR |= 2<<12;	//OLED_CS  		PB7GPIOB->MODER &= ~(3<<14);GPIOB->MODER |= 1<<14;GPIOB->OTYPER &= ~(1<<7);GPIOB->OSPEEDR &= ~(3<<14);GPIOB->OSPEEDR |= 2<<14;	//OLED_RES 		PB8GPIOB->MODER &= ~(3<<16);GPIOB->MODER |= 1<<16;GPIOB->OTYPER &= ~(1<<8);GPIOB->OSPEEDR &= ~(3<<16);GPIOB->OSPEEDR |= 2<<16;//初始状态 res=1;DC=1;CS=1GPIOB->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); //默认为0OLED_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的功能都还没写全,先这样吧,后面有空再补充吧,文中如有不足欢迎批评指正。

相关文章:

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

SPI通信的应用 前言屏幕分类1.3OLED概述驱动芯片框图原理图通信时序显示的方式页地址、列地址初始化指令 程序设计初始化代码初始化写数据与写命令清屏函数 初始化代码字符显示函数 总结 前言 上一篇中介绍了STM32的SPI通信&#xff0c;并根据框图和寄存器进行了SPI通信的初始…...

.Net下企业应用系统架构构建心得

在开始架构设计之前&#xff0c;需要了解一下架构是什么&#xff0c;按照IEEE标准的定义是&#xff1a; Architecture 是一个系统的基本组织&#xff0c;它蕴含于系统的组件中、组件之间的相互关系中、组件与环境的相互关系中、以及呈现于其设计和演进的原则中。 (The embodied…...

【社区图书馆】关于Mybatis原理学习的读后感

1、为什么会看原理书籍 Mybatis是我们Java后端开发中的主流ORM框架&#xff0c;基本都会在工作中用到。所以&#xff0c;是既熟悉&#xff0c;又陌生。熟悉是因为一直都在使用&#xff0c;而陌生则是对于其内部原理还不够深入。刚好近期的工作中&#xff0c;又遇到了一个需求&a…...

C++ Primer阅读笔记--表达式和运算符的使用

1--左值和右值 C 的表达式有右值&#xff08;rvalue, are-value&#xff09;和左值&#xff08;lvalue, ell-value&#xff09;两个形式&#xff1b;当一个对象被用作右值时&#xff0c;使用的是对象的值&#xff08;内容&#xff09;&#xff1b;当对象被用作左值时&#xff0…...

npm install xxx的执行过程及示例

当你在终端中执行npm install xxx命令时&#xff0c;npm会执行以下步骤来安装软件包&#xff1a; 检查本地npm缓存中是否有该软件包。 如果本地npm缓存中已经存在该软件包&#xff0c;npm将直接从缓存中提取软件包并安装。这将显著加快安装速度&#xff0c;因为npm无需从网络下…...

excel数据分析比赛

基础 sql:百度网盘 请输入提取码 excel函数 <...

Git使用GitHub说明

GitHub为公网代码托管仓库&#xff0c;Git可以将本地仓库推送到GitHub管理。 步骤&#xff1a;1、注册GitHub账号 2、创建仓库&#xff08;会得到一个仓库地址&#xff09; 3、推送本地仓库 git remote add origin https://github.com/jianshengchuanqi/xuesezhanjiang.git…...

这些不可不知的JVM知识

JVM是面试中必问的部分&#xff0c;本文通过思维导图以面向面试的角度整理JVM中不可不知的知识。 先上图&#xff1a; JVM必备知识 1、JVM基本概念 1.1、JVM是什么 JVM 的全称是 「Java Virtual Machine」&#xff0c;也就是我们耳熟能详的 Java 虚拟机。 JVM具备着计算机的…...

基于RK3568的Linux驱动开发——GPIO知识点(一)

authordaisy.skye的博客_CSDN博客-Qt,嵌入式,Linux领域博主系列基于RK3568的Linux驱动开发—— GPIO知识点&#xff08;二&#xff09;_daisy.skye的博客-CSDN博客 gpio bank RK3568 有 5 组 GPIO bank&#xff1a;GPIO0~GPIO4&#xff0c;每组又以 A0-A7、B0-B7、 C0-C7、 D0…...

5.2.1二叉树的定义和基本术语

二叉树的基本概念&#xff1a; 二叉树是递归定义的二叉树 下面我们来看几个特殊的二叉树&#xff1a; 特点&#xff1a; 1&#xff09;只有最后一层有叶子节点 2&#xff09;不存在度为1的结点 3&#xff09;按层序从1开始编号&#xff0c;结点i的左孩子为2i&#xff0c;右孩…...

动态组件、keep-alive的使用及自定义指令

目录 1. 动态组件 2.如何实现动态组件渲染 3. 使用keep-alive保持状态 4. keep-alive对应的生命周期函数 5. keep-alive的include属性 自定义指令 1.什么是自定义指令 2. 自定义指令的分类 3. 私有自定义指令 4. update函数 5. 函数简写 全局自定义指令&#xff1a; …...

基于JavaSpringMVC+Mybatis+Jquery高校毕业设计管理系统设计和实现

基于JavaSpringMVCMybatisJquery高校毕业设计管理系统设计和实现 博主介绍&#xff1a;5年java开发经验&#xff0c;专注Java开发、定制、远程、指导等,csdn特邀作者、专注于Java技术领域 作者主页 超级帅帅吴 Java项目精品实战案例《500套》 欢迎点赞 收藏 ⭐留言 文末获取源码…...

问题排查记录-ffmpeg链接libavfilter和libavcodec:未定义的引用

目录 一、问题背景 二、问题现象 2.1 ffmpeg测试例程 2.2 编译脚本 2.3 错误提示 三、问题排查 3.1 关于提示找不到“stdio" "iostream"头文件的问题 3.1.1查看工具链头文件检索位置 3.1.2 根据工具链路径查找头文件 3.1.3 在编译脚本中指定头文件路径…...

打印流,Properties类

打印流只有输出流&#xff0c;没有输入流 package com.hspedu.printstream;import java.io.IOException; import java.io.PrintStream;/*** author 韩顺平* version 1.0* 演示PrintStream &#xff08;字节打印流/输出流&#xff09;*/ public class PrintStream_ {public stat…...

TinyOS 配置教程

系列文章目录 TinyOS 系列文章【一】&#xff1a;TinyOS 配置教程 TinyOS 系列文章【二】&#xff1a;Tossim 教程 文章目录 系列文章目录前言1. 安装1.1. 实验环境1.2. TinyOS基础工作1.3. TinyOS 的配置1.4. 安装 java1.5. 安装编译器 2. 测试仿真程序总结 前言 本文主要用…...

【工作总结】后端开发人员的坏习惯

文章目录 前言一、不遵循项目规范二、用复杂SQL语句来解决问题三、缺少全局把控思维&#xff0c;只关注某一块业务四、函数复杂冗长,逻辑混乱五、缺乏主动思考&#xff0c;拿来主义六、核心业务逻辑,缺少相关日志和注释七、修改代码&#xff0c;缺少必要测试八、需求没理清&…...

review

review post提交方式下的设置编码&#xff0c;防止中文乱码 request.setCharaterEncoding(“utf-8”); get提交方式&#xff0c;tomcat8开始&#xff0c;编码不需要设置 tomcat8之前&#xff0c;get方式设置比较麻烦&#xff1a; String fname request.getParameter("f…...

【人工智能概论】 用Python实现数据的归一化

【人工智能概论】 用Python实现数据的归一化 文章目录 【人工智能概论】 用Python实现数据的归一化一. 数据归一化处理的意义二. 常见的归一化方法2.1 最大最小标准化&#xff08;Min-Max Normalization&#xff09;2.2 z-score 标准化 三. 用sklearn实现归一化 一. 数据归一化…...

【Python】matplotlib设置图片边缘距离和plt.lengend图例放在图像的外侧

一、问题提出 我有这样一串代码&#xff1a; import matplotlib.pyplot as plt plt.figure(figsize (10, 6)) " 此处省略代码 " legend.append("J") plt.legend(legend) plt.xlabel(recall) plt.ylabel(precision) plt.grid() plt.show()我们得到的图像…...

oracle 11g等保加固

有个单机环境需要做个等保加固 1、执行如下sql ?/rdbms/admin/utlpwdmg.sql --alter profile default limit password_verify_function null; Alter PROFILE DEFAULT LIMIT PASSWORD_LIFE_TIME 90; alter profile DEFAULT limit password_lock_time 30; alter profile DEFAU…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

STM32+rt-thread判断是否联网

一、根据NETDEV_FLAG_INTERNET_UP位判断 static bool is_conncected(void) {struct netdev *dev RT_NULL;dev netdev_get_first_by_flags(NETDEV_FLAG_INTERNET_UP);if (dev RT_NULL){printf("wait netdev internet up...");return false;}else{printf("loc…...

GitHub 趋势日报 (2025年06月08日)

&#x1f4ca; 由 TrendForge 系统生成 | &#x1f310; https://trendforge.devlive.org/ &#x1f310; 本日报中的项目描述已自动翻译为中文 &#x1f4c8; 今日获星趋势图 今日获星趋势图 884 cognee 566 dify 414 HumanSystemOptimization 414 omni-tools 321 note-gen …...

selenium学习实战【Python爬虫】

selenium学习实战【Python爬虫】 文章目录 selenium学习实战【Python爬虫】一、声明二、学习目标三、安装依赖3.1 安装selenium库3.2 安装浏览器驱动3.2.1 查看Edge版本3.2.2 驱动安装 四、代码讲解4.1 配置浏览器4.2 加载更多4.3 寻找内容4.4 完整代码 五、报告文件爬取5.1 提…...

使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台

🎯 使用 Streamlit 构建支持主流大模型与 Ollama 的轻量级统一平台 📌 项目背景 随着大语言模型(LLM)的广泛应用,开发者常面临多个挑战: 各大模型(OpenAI、Claude、Gemini、Ollama)接口风格不统一;缺乏一个统一平台进行模型调用与测试;本地模型 Ollama 的集成与前…...

Unsafe Fileupload篇补充-木马的详细教程与木马分享(中国蚁剑方式)

在之前的皮卡丘靶场第九期Unsafe Fileupload篇中我们学习了木马的原理并且学了一个简单的木马文件 本期内容是为了更好的为大家解释木马&#xff08;服务器方面的&#xff09;的原理&#xff0c;连接&#xff0c;以及各种木马及连接工具的分享 文件木马&#xff1a;https://w…...

elementUI点击浏览table所选行数据查看文档

项目场景&#xff1a; table按照要求特定的数据变成按钮可以点击 解决方案&#xff1a; <el-table-columnprop"mlname"label"名称"align"center"width"180"><template slot-scope"scope"><el-buttonv-if&qu…...

Python竞赛环境搭建全攻略

Python环境搭建竞赛技术文章大纲 竞赛背景与意义 竞赛的目的与价值Python在竞赛中的应用场景环境搭建对竞赛效率的影响 竞赛环境需求分析 常见竞赛类型&#xff08;算法、数据分析、机器学习等&#xff09;不同竞赛对Python版本及库的要求硬件与操作系统的兼容性问题 Pyth…...

Ubuntu系统多网卡多相机IP设置方法

目录 1、硬件情况 2、如何设置网卡和相机IP 2.1 万兆网卡连接交换机&#xff0c;交换机再连相机 2.1.1 网卡设置 2.1.2 相机设置 2.3 万兆网卡直连相机 1、硬件情况 2个网卡n个相机 电脑系统信息&#xff0c;系统版本&#xff1a;Ubuntu22.04.5 LTS&#xff1b;内核版本…...

鸿蒙(HarmonyOS5)实现跳一跳小游戏

下面我将介绍如何使用鸿蒙的ArkUI框架&#xff0c;实现一个简单的跳一跳小游戏。 1. 项目结构 src/main/ets/ ├── MainAbility │ ├── pages │ │ ├── Index.ets // 主页面 │ │ └── GamePage.ets // 游戏页面 │ └── model │ …...