STM32基于SPI的OLED显示

目录

一、解决的问题

二、SPI介绍 

1、什么是SPI: 

2、SPI主要特征:

3、SPI模式:

 4、SPI信号线:

5、SPI物理层连接方式:

6、SPI的极性和时钟相位:

7、SPI的通讯方式:

三、OLED屏幕介绍 

1、OLED介绍:

2、 基于SPI协议的7排针OLED实物图与原理图:

3、OLED上点阵编码原理与显示 :

四、OLED显示自己的学号和姓名

 五、OLED滚动显示长字符

 六、OLED显示的温湿度

七、总结

八、参考资料


一、解决的问题

 理解OLED屏显和汉字点阵编码原理,使用STM32F103的SPI或IIC接口实现以下功能:

1) 显示自己的学号和姓名; 

2) 显示AHT20的温度和湿度;

3) 上下或左右的滑动显示长字符,比如“Hello,欢迎来到重庆交通大学物联网205实训室!”或者一段歌词或诗词(最好使用硬件刷屏模式)。

二、SPI介绍 

1、什么是SPI: 

       SPI,是英语Serial Peripheral interface的缩写,顾名思义就是串行外围设备接口。是Motorola首先在其MC68HCXX系列处理器上定义的。SPI接口主要应用在 EEPROM,FLASH,实时时钟,AD转换器,还有数字信号处理器和数字信号解码器之间。SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚,同时为PCB的布局上节省空间,提供方便,正是出于这种简单易用的特性,现在越来越多的芯片集成了这种通信协议。

2、SPI主要特征:

● 3 线全双工同步传输
● 带或不带第三根双向数据线的双线单工同步传输
● 8 或 16 位传输帧格式选择
● 主或从操作
● 支持多主模式
● 8 个主模式波特率预分频系数(最大为 f PCLK /2)
● 从模式频率 (最大为 f PCLK /2)
● 主模式和从模式的快速通信:最大 SPI 速度达到 18MHz
● 主模式和从模式下均可以由软件或硬件进行 NSS 管理:主/从操作模式的动
态改变
● 可编程的时钟极性和相位
● 可编程的数据顺序,MSB 在前或 LSB 在前
● 可触发中断的专用发送和接收标志
● SPI 总线忙状态标志
● 支持可靠通信的硬件 CRC
− 在发送模式下,CRC 值可以被作为最后一个字节发送
− 在全双工模式中对接收到的最后一个字节自动进行 CRC 校验
● 可触发中断的主模式故障、过载以及 CRC 错误标志
● 支持 DMA 功能的 1 字节发送和接收缓冲器:产生发送和接受请求

3、SPI模式:

      SPI分为主、从两种模式,一个SPI通讯系统需要包含一个(且只能是一个)主设备,一个或多个从设备。提供时钟的为主设备(Master),接收时钟的设备为从设备(Slave),SPI接口的读写操作,都是由主设备发起。当存在多个从设备时,通过各自的片选信号进行管理。

       SPI是全双工且SPI没有定义速度限制,一般的实现通常能达到甚至超过10 Mbps

 4、SPI信号线:

SPI接口一般使用四条信号线通信:

SDI(数据输入),SDO(数据输出),SCK(时钟),CS(片选):

  • MISO: 主设备输入/从设备输出引脚。该引脚在从模式下发送数据,在主模式下接收数据。

  • MOSI: 主设备输出/从设备输入引脚。该引脚在主模式下发送数据,在从模式下接收数据。

  • SCLK:串行时钟信号,由主设备产生。

  • CS/SS:从设备片选信号,由主设备控制。它的功能是用来作为“片选引脚”,也就是选择指定的从设备,让主设备可以单独地与特定从设备通讯,避免数据线上的冲突。

 (1)SPI一对一形式:

(2)SPI一对多形式:

5、SPI物理层连接方式:

SPI通讯设备之间的常用连接方式见下图:

     SPI通讯使用 3 条总线及片选线,3条总线分别为 SCK、MOSI、MISO,片选线为 

SS:

  • SS( Slave Select):从设备选择信号线,常称为片选信号线。
  • SCK (Serial Clock):时钟信号线,用于通讯数据同步。
  • MOSI (Master Output, Slave Input):主设备输出/从设备输入引脚。
  • MISO(Master Input,,Slave Output):主设备输入/从设备输出引脚。

6、SPI的极性和时钟相位:

       SPI接口没有定义数据交换协议,限制了开销并允许高速数据流。时钟极性(CPOL)和时钟相位(CPHA)可以指定为“0”或“1”,形成四种独特的模式,以提供主从通信的灵活性,如下图所示:

        如果CPOLCPHA都为’ 0 ‘(定义为模式0),则在时钟的前上升沿采样数据。目前,模式0是SPI总线通信最常见的模式。如果CPOL为’ 1 ‘,CPHA为’ 0 '(模式2),则在时钟的前降边缘采样数据。同样,CPOL = ’ 0 '和CPHA = ’ 1 ’ (Mode 1)在尾降边缘采样,CPOL = ’ 1 '和CPHA = ’ 1 ’ (Mode 3)在尾升边缘采样。下表总结了可用的模式 :

注:

CPOL:时钟极性,表示时钟线空闲时是高电平1还是低电平0
CPHA:时钟相位,表示是在时钟的前沿0还是尾沿1采样数据。

ModeCPOLCPHA
000
101
210
311

7、SPI的通讯方式:

       下图一个主机的通讯时序。NSS、SCK、MOSI 信号都由主机控制产生,而 MISO 的信号由从机产生,主机通过该信号线读取从机的数据。MOSIMISO的信号只在NSS为低电平的时候才有效,在SCK的每个时钟周期MOSIMISO传输一位数据。

三、OLED屏幕介绍 

1、OLED介绍:

       OLED(OrganicLight-Emitting Diode),又称为有机电激光显示、有机发光半导体(OrganicElectroluminesence Display,OLED)。OLED属于一种电流型的有机发光器件,是通过载流子的注入和复合而致发光的现象,发光强度与注入的电流成正比。OLED在电场的作用下,阳极产生的空穴和阴极产生的电子就会发生移动,分别向空穴传输层和电子传输层注入,迁移到发光层。当二者在发光层相遇时,产生能量激子,从而激发发光分子最终产生可见光。

2、 基于SPI协议的7排针OLED实物图与原理图:

(1)实物图: 

                                      

七个排针引脚说明:

序号模块引脚引脚说明
1GNDOLED电源地
2VCCOLED电源正(3.3V~5V)
3D0OLED SPI和IIC总线时钟信号
4D1OLED SPI和IIC总线数据信号
5RESOLED复位信号,低电平复位(选择IIC总线时,该引脚需要接高电平(可以接VCC))
6DCOLED命令/数据输入选择信号,高电平:数据,低电平:命令

(选择3线制SPI总线时,该引脚不需要使用(可以不接);选择IIC总线时,该引脚需要接电源地)

7CSOLED片选信号,低电平使能(选择IIC总线时,该引脚需要接电源地)

(2)原理图: 

3、OLED上点阵编码原理与显示 :

(1)点阵编码:
      在汉字的点阵字库中,每个字节的每个位都代表一个汉字的一个点,每个汉字都是由一个矩形的点阵组成,0 代表没有点,1 代表有点,将 0 和 1 分别用不同颜色画出,就形成了一个汉字,常用的点阵矩阵有 1212, 1414, 16*16 三 种字库。
      字库根据字节所表示点的不同有分为横向矩阵和纵向矩阵,目前多数的字库都是横向矩阵的存储方式(用得最多的应该是早期 UCDOS 字库),纵向矩阵一 般是因为有某些液晶是采用纵向扫描显示法,为了提高显示速度,于是便把字库 矩阵做成纵向,省得在显示时还要做矩阵转换。
(2)点阵显示:
        点阵屏像素按128列X64行组织,每一行128个像素单元的阴极是连接在一起,作为公共极(COM),每一列64个像素单元的阳极也连接在一起,作为一段(SEG)。行列交叉点上的LED就是一个显示单元,即一个像素。要点亮一个像素,只要在该像素所在列电极上加上正电压、行电极接地。同样,要驱动一整行图像,就需要同时把128列信号加载到列电极上,把该行行电极接地。该行显示时,其他63行均不能显示,其行电极应为高电平或悬空。
         可见,整屏的显示,只能分时扫描进行,一行一行的显示,每次显示一行。行驱依次产生低电平扫描各行,列驱动读取显示数据依次加载到列电极上。扫描一行的时间称为行周期,完成一次全屏扫描,就叫做一帧。一般帧频大于60,人眼观察不到逐行显示。每行扫描显示用时叫占空比,占空比小,为达到相同的显示亮度,驱动电流就大。SSD1306段驱动最大电流为100uA,当整行128个像素全部点亮时,行电极就要流过12.8mA的电流。 

四、OLED显示自己的学号和姓名

 1、实验准备:

      下载官方0.96 寸 OLED 显示屏厂家给出的 Demo 程序,这里给出下载连接,点击直接进行下载即可:http://www.lcdwiki.com/res/Program/OLED/0.96inch/SPI_SSD1306_MSP096X_V1.0/0.96inch_SPI_OLED_Module_SSD1306_MSP096X_V1.0.zip

 2、解压之后,按下图所示进入到指定的文件:

接着打开取模软件:

 3、取模软件设置:

首先,如下图所示选择字符模式:

接着自定义格式为C51格式:

 4、生成字模:

 以“洪朝银”为例:

 5、按下图所示进入到所下载的 Demo 程序中,打开已有的工程文件:

 6、打开oledfont.h文件:

 如下图所示:

7、修改 oledfont.h文件:

  首先在前面取模软件里面复制自己设置的字模点阵:

 复制在oledfont.h这里,如下图所示:

注:将字模16进制中文点阵粘贴在这里,同时去除原有的大括号,最后在图中蓝色区域输入点阵的内容(符号要用英文输入模式):

8、接着,如下图所示打开test.c文件,按照图示步骤进行修改:

  9、main函数修改(程序到此已经完成了):

10、电路连接:

OLED与STM32连线:
在这里插入图片描述
 USB转TTL与STM32连线:

实物电路图展示:  

11、编译烧录:

 12、运行,结果展示: 

 五、OLED滚动显示长字符

  1、打开取模软件:

 2、取模软件设置:

首先,如下图所示选择字符模式:

接着自定义格式为C51格式:

 3、生成字模:

 以“长太息以掩涕兮”为例:

 4、按下图所示进入到所下载的 Demo 程序中,打开已有的工程文件:

 5、打开oledfont.h文件:

 如下图所示:

6、修改 oledfont.h文件:

  首先在前面取模软件里面复制自己设置的字模点阵:

 复制在oledfont.h这里,如下图所示:

注:将字模16进制中文点阵粘贴在这里,同时去除原有的大括号,最后在图中蓝色区域输入点阵的内容(符号要用英文输入模式):

7、接着,如下图所示打开test.c文件,按照图示步骤进行修改:

  8、main函数修改(程序到此已经完成了):

 首先,注释掉整个while循环:

加上滚动设置代码: 

	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); //终止页 2
	OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节
	OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节
	TEST_MainPage();
	OLED_WR_Byte(0x2F,OLED_CMD); //开启滚动

9、电路连接:

OLED与STM32连线:
在这里插入图片描述
 USB转TTL与STM32连线:

实物电路图展示:  

10、编译烧录:

11、运行,结果展示: 

 六、OLED显示的温湿度

   1、打开取模软件:

 2、取模软件设置:

首先,如下图所示选择字符模式:

接着自定义格式为C51格式:

 3、生成字模:

 以“温度湿度”为例:

 4、按下图所示进入到所下载的 Demo 程序中,打开已有的工程文件:

 5、打开oledfont.h文件:

 如下图所示:

6、修改 oledfont.h文件:

  首先在前面取模软件里面复制自己设置的字模点阵:

 复制在oledfont.h这里,如下图所示:

注:将字模16进制中文点阵粘贴在这里,同时去除原有的大括号,最后在图中蓝色区域输入点阵的内容(符号要用英文输入模式):

7、 去奥松官网下载AHT20芯片代码与芯片的相关信息介绍文档:
软件下载-温湿度传感器 温湿度芯片 温湿度变送器模块 气体传感器 流量传感器 广州奥松电子股份有限公司

官方代码使用的是PB14,PB15引脚,需要修改引脚为PB6,PB7 才可以正常使用。 

 8、复制第7步下载的文件夹里面的两个文件: 

9、将刚刚复制的两个文件粘贴在工程文件夹里面的USER文件夹下: 

 10、进入到 keil5工程文件中,双击如图所示处进行添加AHT20-21_DEMO_V1_3.c文件:

 11、修改AHT20-21_DEMO_V1_3.c文件的代码,修改后的完整代码如下:

#include "AHT20-21_DEMO_V1_3.h" 




void Delay_N10us(uint32_t t)//延时函数
{
  uint32_t k;

   while(t--)
  {
    for (k = 0; k < 2; k++);//110
  }
}

void SensorDelay_us(uint32_t t)//延时函数
{
		
	for(t = t-2; t>0; t--)
	{
		Delay_N10us(1);
	}
}

void Delay_4us(void)		//延时函数
{	
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);
}
void Delay_5us(void)		//延时函数
{	
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);
	Delay_N10us(1);

}

void Delay_1ms(uint32_t t)		//延时函数
{
   while(t--)
  {
    SensorDelay_us(1000);//延时1ms
  }
}


//void AHT20_Clock_Init(void)		//延时函数
//{
//	RCC_APB2PeriphClockCmd(CC_APB2Periph_GPIOB,ENABLE);
//}

void SDA_Pin_Output_High(void)   //将PB7配置为输出 , 并设置为高电平, PB7作为I2C的SDA
{
	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,& GPIO_InitStruct);
	GPIO_SetBits(GPIOB,GPIO_Pin_7);
}

void SDA_Pin_Output_Low(void)  //将P7配置为输出  并设置为低电平
{

	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,& GPIO_InitStruct);
	GPIO_ResetBits(GPIOB,GPIO_Pin_7);
}

void SDA_Pin_IN_FLOATING(void)  //SDA配置为浮空输入
{

	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;//
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init( GPIOB,&GPIO_InitStruct);
}

void SCL_Pin_Output_High(void) //SCL输出高电平,PB6作为I2C的SCL
{
	GPIO_SetBits(GPIOB,GPIO_Pin_6);
}

void SCL_Pin_Output_Low(void) //SCL输出低电平
{
	GPIO_ResetBits(GPIOB,GPIO_Pin_6);
}

void Init_I2C_Sensor_Port(void) //初始化I2C接口,输出为高电平
{	
	GPIO_InitTypeDef  GPIO_InitStruct;
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_7;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,& GPIO_InitStruct);
	GPIO_SetBits(GPIOB,GPIO_Pin_15);//输出高电平
	
	GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;//推挽输出
	GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_Init(GPIOB,& GPIO_InitStruct);
	GPIO_SetBits(GPIOB,GPIO_Pin_15);//输出高电平
	
}
void I2C_Start(void)		 //I2C主机发送START信号
{
	SDA_Pin_Output_High();
	SensorDelay_us(8);
	SCL_Pin_Output_High();
	SensorDelay_us(8);
	SDA_Pin_Output_Low();
	SensorDelay_us(8);
	SCL_Pin_Output_Low();
	SensorDelay_us(8);   
}


void AHT20_WR_Byte(uint8_t Byte) //往AHT20写一个字节
{
	uint8_t Data,N,i;	
	Data=Byte;
	i = 0x80;
	for(N=0;N<8;N++)
	{
		SCL_Pin_Output_Low(); 
		Delay_4us();	
		if(i&Data)
		{
			SDA_Pin_Output_High();
		}
		else
		{
			SDA_Pin_Output_Low();
		}	
			
    SCL_Pin_Output_High();
		Delay_4us();
		Data <<= 1;
		 
	}
	SCL_Pin_Output_Low();
	SensorDelay_us(8);   
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);	
}	


uint8_t AHT20_RD_Byte(void)//从AHT20读取一个字节
{
	uint8_t Byte,i,a;
	Byte = 0;
	SCL_Pin_Output_Low();
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);	
	for(i=0;i<8;i++)
	{
    SCL_Pin_Output_High();		
		Delay_5us();
		a=0;
		if(GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7)) a=1;
		Byte = (Byte<<1)|a;
		SCL_Pin_Output_Low();
		Delay_5us();
	}
  SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);	
	return Byte;
}


uint8_t Receive_ACK(void)   //看AHT20是否有回复ACK
{
	uint16_t CNT;
	CNT = 0;
	SCL_Pin_Output_Low();	
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);	
	SCL_Pin_Output_High();	
	SensorDelay_us(8);	
	while((GPIO_ReadInputDataBit(GPIOB,GPIO_Pin_7))  && CNT < 100) 
	CNT++;
	if(CNT == 100)
	{
		return 0;
	}
 	SCL_Pin_Output_Low();	
	SensorDelay_us(8);	
	return 1;
}

void Send_ACK(void)		  //主机回复ACK信号
{
	SCL_Pin_Output_Low();	
	SensorDelay_us(8);	
	SDA_Pin_Output_Low();
	SensorDelay_us(8);	
	SCL_Pin_Output_High();	
	SensorDelay_us(8);
	SCL_Pin_Output_Low();	
	SensorDelay_us(8);
	SDA_Pin_IN_FLOATING();
	SensorDelay_us(8);
}

void Send_NOT_ACK(void)	//主机不回复ACK
{
	SCL_Pin_Output_Low();	
	SensorDelay_us(8);
	SDA_Pin_Output_High();
	SensorDelay_us(8);
	SCL_Pin_Output_High();	
	SensorDelay_us(8);		
	SCL_Pin_Output_Low();	
	SensorDelay_us(8);
    SDA_Pin_Output_Low();
	SensorDelay_us(8);
}

void Stop_I2C(void)	  //一条协议结束
{
	SDA_Pin_Output_Low();
	SensorDelay_us(8);
	SCL_Pin_Output_High();	
	SensorDelay_us(8);
	SDA_Pin_Output_High();
	SensorDelay_us(8);
}

uint8_t AHT20_Read_Status(void)//读取AHT20的状态寄存器
{

	uint8_t Byte_first;	
	I2C_Start();
	AHT20_WR_Byte(0x71);
	Receive_ACK();
	Byte_first = AHT20_RD_Byte();
	Send_NOT_ACK();
	Stop_I2C();
	return Byte_first;
}

uint8_t AHT20_Read_Cal_Enable(void)  //查询cal enable位有没有使能
{
	uint8_t val = 0;//ret = 0,
  val = AHT20_Read_Status();
	 if((val & 0x68)==0x08)
		 return 1;
   else  return 0;
 }

void AHT20_SendAC(void) //向AHT20发送AC命令
{

	I2C_Start();
	AHT20_WR_Byte(0x70);
	Receive_ACK();
	AHT20_WR_Byte(0xac);//0xAC采集命令
	Receive_ACK();
	AHT20_WR_Byte(0x33);
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	Stop_I2C();

}

//CRC校验类型:CRC8/MAXIM
//多项式:X8+X5+X4+1
//Poly:0011 0001  0x31
//高位放到后面就变成 1000 1100 0x8c
//C现实代码:
uint8_t Calc_CRC8(uint8_t *message,uint8_t Num)
{
        uint8_t i;
        uint8_t byte;
        uint8_t crc=0xFF;
  for(byte=0; byte<Num; byte++)
  {
    crc^=(message[byte]);
    for(i=8;i>0;--i)
    {
      if(crc&0x80) crc=(crc<<1)^0x31;
      else crc=(crc<<1);
    }
  }
        return crc;
}

void AHT20_Read_CTdata(uint32_t *ct) //没有CRC校验,直接读取AHT20的温度和湿度数据
{
	volatile uint8_t  Byte_1th=0;
	volatile uint8_t  Byte_2th=0;
	volatile uint8_t  Byte_3th=0;
	volatile uint8_t  Byte_4th=0;
	volatile uint8_t  Byte_5th=0;
	volatile uint8_t  Byte_6th=0;
	 uint32_t RetuData = 0;
	uint16_t cnt = 0;
	AHT20_SendAC();//向AHT10发送AC命令
	Delay_1ms(80);//延时80ms左右	
    cnt = 0;
	while(((AHT20_Read_Status()&0x80)==0x80))//直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态
	{
		SensorDelay_us(1508);
		if(cnt++>=100)
		{
		 break;
		 }
	}
	I2C_Start();
	AHT20_WR_Byte(0x71);
	Receive_ACK();
	Byte_1th = AHT20_RD_Byte();//状态字,查询到状态为0x98,表示为忙状态,bit[7]为1;状态为0x1C,或者0x0C,或者0x08表示为空闲状态,bit[7]为0
	Send_ACK();
	Byte_2th = AHT20_RD_Byte();//湿度
	Send_ACK();
	Byte_3th = AHT20_RD_Byte();//湿度
	Send_ACK();
	Byte_4th = AHT20_RD_Byte();//湿度/温度
	Send_ACK();
	Byte_5th = AHT20_RD_Byte();//温度
	Send_ACK();
	Byte_6th = AHT20_RD_Byte();//温度
	Send_NOT_ACK();
	Stop_I2C();

	RetuData = (RetuData|Byte_2th)<<8;
	RetuData = (RetuData|Byte_3th)<<8;
	RetuData = (RetuData|Byte_4th);
	RetuData =RetuData >>4;
	ct[0] = RetuData;//湿度
	RetuData = 0;
	RetuData = (RetuData|Byte_4th)<<8;
	RetuData = (RetuData|Byte_5th)<<8;
	RetuData = (RetuData|Byte_6th);
	RetuData = RetuData&0xfffff;
	ct[1] =RetuData; //温度

}


void AHT20_Read_CTdata_crc(uint32_t *ct) //CRC校验后,读取AHT20的温度和湿度数据
{
	volatile uint8_t  Byte_1th=0;
	volatile uint8_t  Byte_2th=0;
	volatile uint8_t  Byte_3th=0;
	volatile uint8_t  Byte_4th=0;
	volatile uint8_t  Byte_5th=0;
	volatile uint8_t  Byte_6th=0;
	volatile uint8_t  Byte_7th=0;
	 uint32_t RetuData = 0;
	 uint16_t cnt = 0;
	// uint8_t  CRCDATA=0;
	 uint8_t  CTDATA[6]={0};//用于CRC传递数组
	
	AHT20_SendAC();//向AHT10发送AC命令
	Delay_1ms(80);//延时80ms左右	
    cnt = 0;
	while(((AHT20_Read_Status()&0x80)==0x80))//直到状态bit[7]为0,表示为空闲状态,若为1,表示忙状态
	{
		SensorDelay_us(1508);
		if(cnt++>=100)
		{
		 break;
		}
	}
	
	I2C_Start();

	AHT20_WR_Byte(0x71);
	Receive_ACK();
	CTDATA[0]=Byte_1th = AHT20_RD_Byte();//状态字,查询到状态为0x98,表示为忙状态,bit[7]为1;状态为0x1C,或者0x0C,或者0x08表示为空闲状态,bit[7]为0
	Send_ACK();
	CTDATA[1]=Byte_2th = AHT20_RD_Byte();//湿度
	Send_ACK();
	CTDATA[2]=Byte_3th = AHT20_RD_Byte();//湿度
	Send_ACK();
	CTDATA[3]=Byte_4th = AHT20_RD_Byte();//湿度/温度
	Send_ACK();
	CTDATA[4]=Byte_5th = AHT20_RD_Byte();//温度
	Send_ACK();
	CTDATA[5]=Byte_6th = AHT20_RD_Byte();//温度
	Send_ACK();
	Byte_7th = AHT20_RD_Byte();//CRC数据
	Send_NOT_ACK();                           //注意: 最后是发送NAK
	Stop_I2C();
	
	if(Calc_CRC8(CTDATA,6)==Byte_7th)
	{
	RetuData = (RetuData|Byte_2th)<<8;
	RetuData = (RetuData|Byte_3th)<<8;
	RetuData = (RetuData|Byte_4th);
	RetuData =RetuData >>4;
	ct[0] = RetuData;//湿度
	RetuData = 0;
	RetuData = (RetuData|Byte_4th)<<8;
	RetuData = (RetuData|Byte_5th)<<8;
	RetuData = (RetuData|Byte_6th);
	RetuData = RetuData&0xfffff;
	ct[1] =RetuData; //温度
		
	}
	else
	{
		ct[0]=0x00;
		ct[1]=0x00;//校验错误返回值,客户可以根据自己需要更改
	}//CRC数据
}


void AHT20_Init(void)   //初始化AHT20
{	
	Init_I2C_Sensor_Port();
	I2C_Start();
	AHT20_WR_Byte(0x70);
	Receive_ACK();
	AHT20_WR_Byte(0xa8);//0xA8进入NOR工作模式
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	Stop_I2C();

	Delay_1ms(10);//延时10ms左右

	I2C_Start();
	AHT20_WR_Byte(0x70);
	Receive_ACK();
	AHT20_WR_Byte(0xbe);//0xBE初始化命令,AHT20的初始化命令是0xBE,   AHT10的初始化命令是0xE1
	Receive_ACK();
	AHT20_WR_Byte(0x08);//相关寄存器bit[3]置1,为校准输出
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	Stop_I2C();
	Delay_1ms(10);//延时10ms左右
	
	
}
void JH_Reset_REG(uint8_t addr)
{
	
	uint8_t Byte_first,Byte_second,Byte_third;
	I2C_Start();
	AHT20_WR_Byte(0x70);//原来是0x70
	Receive_ACK();
	AHT20_WR_Byte(addr);
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	AHT20_WR_Byte(0x00);
	Receive_ACK();
	Stop_I2C();

	Delay_1ms(5);//延时5ms左右
	I2C_Start();
	AHT20_WR_Byte(0x71);//
	Receive_ACK();
	Byte_first = AHT20_RD_Byte();
	Send_ACK();
	Byte_second = AHT20_RD_Byte();
	Send_ACK();
	Byte_third = AHT20_RD_Byte();
	Send_NOT_ACK();
	Stop_I2C();
	
    Delay_1ms(10);//延时10ms左右
	I2C_Start();
	AHT20_WR_Byte(0x70);///
	Receive_ACK();
	AHT20_WR_Byte(0xB0|addr);//寄存器命令
	Receive_ACK();
	AHT20_WR_Byte(Byte_second);
	Receive_ACK();
	AHT20_WR_Byte(Byte_third);
	Receive_ACK();
	Stop_I2C();
	
	Byte_second=0x00;
	Byte_third =0x00;
}

void AHT20_Start_Init(void)
{
	JH_Reset_REG(0x1b);
	JH_Reset_REG(0x1c);
	JH_Reset_REG(0x1e);
}

//int32_t main(void)
//{
//    uint32_t CT_data[2];
//	volatile int  c1,t1;
//	/***********************************************************************************/
//	/**///①刚上电,产品芯片内部就绪需要时间,延时100~500ms,建议500ms
//	/***********************************************************************************/
//	Delay_1ms(500);
//	/***********************************************************************************/
//	/**///②上电第一次发0x71读取状态字,判断状态字是否为0x18,如果不是0x18,进行寄存器初始化
//	/***********************************************************************************/
//	if((AHT20_Read_Status()&0x18)!=0x18)
//	{
//	AHT20_Start_Init(); //重新初始化寄存器
//	Delay_1ms(10);
//	}
//	
//	/***********************************************************************************/
//	/**///③根据客户自己需求发测量命令读取温湿度数据,当前while(1)循环发测量命令读取温湿度数据,仅供参考
//	/***********************************************************************************/
//	while(1)
//	{
//	 AHT20_Read_CTdata(CT_data);       //不经过CRC校验,直接读取AHT20的温度和湿度数据    推荐每隔大于1S读一次
//    //AHT20_Read_CTdata_crc(CT_data);  //crc校验后,读取AHT20的温度和湿度数据 
//	

//	 c1 = CT_data[0]*100*10/1024/1024;  //计算得到湿度值c1(放大了10倍)
//	 t1 = CT_data[1]*200*10/1024/1024-500;//计算得到温度值t1(放大了10倍)
//	下一步客户处理显示数据,
//	 }

// }	

12、修改mian.c文件的代码,修改后的完整代码如下:

#include "delay.h"
#include "sys.h"
#include "oled.h"
#include "gui.h"
#include "test.h"
#include "AHT20-21_DEMO_V1_3.h" 


//存放温度和湿度
uint32_t CT_data[2]={0,0};
//湿度和温度
volatile int  c1,t1;

//用于LED显示的温度和湿度
u8 temp[10];  
u8 hum[10];

//初始化PC13用于测试
void GPIOC13_Init(void){
	GPIO_InitTypeDef  GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC, ENABLE);	
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_2MHz;
	GPIO_Init(GPIOC, &GPIO_InitStructure);	
	GPIO_ResetBits(GPIOC,GPIO_Pin_13);

}
//初始化以及前期准备
void Init(void);

//读取温湿度
void getData(void);

//显示温湿度
void showData(void);

int main(void)
{	
	//初始化
	Init();
	while(1){

		//获取数据
		getData();
		//显示数据
		showData();

		//开启滚动
		OLED_WR_Byte(0x2F,OLED_CMD);
		
		//延时
		Delay_1ms(3100);
		//OLED_Clear(0); 
	}
	
}

//初始化以及前期准备
void Init(void){
	//初始化PC12
	GPIOC13_Init();		
	
	//延时函数初始化	  
	delay_init();	   
	
	//初始化OLED 
	OLED_Init();

	//清屏(全黑)	
	OLED_Clear(0);    

	//开机显示信息	

	GUI_ShowCHinese(10,0,16,"杨海东",1);
	
	GUI_ShowString(10,24,"631807060431",16,1);
	
	
	Delay_1ms(1000);
	
	AHT20_Init();
	/***********************************************************************************/
	/**///①刚上电,产品芯片内部就绪需要时间,延时100~500ms,建议500ms
	/***********************************************************************************/
	
	Delay_1ms(1000);
	
	OLED_Clear(0); 
	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(0x02,OLED_CMD); //终止页 2

	OLED_WR_Byte(0x00,OLED_CMD); //虚拟字节

	OLED_WR_Byte(0xFF,OLED_CMD); //虚拟字节
	
	
	GUI_ShowCHinese(10,0,16,"一片孤城万仞山",1);	
}

//读取温湿度
void getData(){
	//AHT20_Read_CTdata(CT_data);       //不经过CRC校验,直接读取AHT20的温度和湿度数据    推荐每隔大于1S读一次
		AHT20_Read_CTdata_crc(CT_data);;  //crc校验后,读取AHT20的温度和湿度数据 
		c1 = CT_data[0]*1000/1024/1024;  //计算得到湿度值c1(放大了10倍)
		t1 = CT_data[1]*2000/1024/1024-500;//计算得到温度值t1(放大了10倍)

		//转为字符串易于显示
		temp[0]=t1/100+'0';
		temp[1]=(t1/10)%10+'0';
		temp[2]='.';
		temp[3]=t1%10+'0';
		temp[4]='\0';
		
		hum[0]=c1/100+'0';
		hum[1]=(c1/10)%10+'0';
		hum[2]='.';
		hum[3]=c1%10+'0';
		hum[4]=32;
		hum[5]='%';
		hum[6]='\0';
}


//显示温湿度
void showData(){
		//显示温度
		GUI_ShowCHinese(16,20,16,"温度",1);
		GUI_ShowString(47,20,":",16,1);
		GUI_ShowString(62,20,temp,16,1);
		
		

		//显示湿度
		GUI_ShowCHinese(16,38,16,"湿度",1);
		GUI_ShowString(47,38,":",16,1);
		GUI_ShowString(62,38,hum,16,1);
}

13、电路连接:

(1)OLED与STM32连线:
在这里插入图片描述
(2)USB转TTL与STM32连线:

(3)温湿度采集模块AHT20与STM32的连接如下图所示: 

这里附上AHT20的引脚简介图:

(4)实物电路图展示:  

 14、编译烧录:

15、运行,结果展示: 

七、总结

      通过本次实验,本人学习并且实践了SPI协议、OLED的使用以及字模的生成。通过三个要求的实践,本人基本熟练了STM32+OLED的操作显示,代码和管脚配置没有问题的情况下,完成三个要求的应用并不困难。注意OLED显示时要对字长进行设置,否则无法完全显示出来。字模取模时,注意横向取模、纵向取模、倒序的差别,否则会得到一片模糊的点点,而不是正常清晰的汉字。OLED是一个比较有意思的外设模块,在之后完成更多硬件项目时,可以利用OLED进行调试显示,帮助会很大,所以要好好掌握OLED的使用,多加练习,受益匪浅。

八、参考资料

 1、【精选】【嵌入式16】STM32+OLED屏显应用实例_基于stm32智能实现播放音乐,同时oled上显示播放音乐-CSDN博客

2、【精选】STM32F103基于SPI接口的OLED显示数据_stm32f103电压采集oled-CSDN博客 

3、【精选】stm32+(4SPI)OLED显示数据_4线spioled_机智的橙子的博客-CSDN博客 

  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
### 回答1: STM32F103是一款常用的ARM Cortex-M3系列的单片机,通过SPI接口来实现与外部设备的通信是常见的应用之一。以下是关于如何使用STM32F103的SPI接口来读写OLED显示屏的简要概述。 首先,确保已经按照需要的电气连接,将OLED显示屏与STM32F103连接起来,其中包括SCK、MOSI、CS(片选)以及DC(数据/命令切换)等信号线。 其次,配置SPI接口。首先,使能SPI时钟,选择合适的SPI通道,配置SPI模式以及时钟分频等参数。可以使用STM32CubeMX工具来简化配置过程。然后,根据OLED显示屏的规格,配置SPI通信的特殊需求,如数据宽度、传输模式、控制信号等。 接下来,编写相关的代码来控制OLED显示屏。首先,确保OLED显示屏处于可用状态,并准备好相应的初始设置和功能配置。然后,使用SPI接口的读写函数将数据发送到OLED显示屏或从中读取数据。SPI接口的读写函数可以通过库或者使用直接读写寄存器的方式实现。 在具体的数据传输过程中,需要根据OLED显示屏使用的协议来设置相应的数据格式和控制信号。例如,发送命令和数据之前,需要将DC信号切换到相应的状态;同时,在SPI通信完成之后需要切换CS信号的状态以结束通信。 最后,记得适时进行相关的错误处理和调试。可以根据实际需要来添加适当的延迟等待SPI数据传输完成以及OLED显示屏的响应。 总之,通过配置STM32F103的SPI接口以及编写相应的代码,可以实现与OLED显示屏的数据读写。具体的实现方式需要根据OLED显示屏的规格和通信协议来确定。 ### 回答2: 在使用STM32F103芯片进行SPI读写OLED时,我们首先需要了解一些基本概念。 SPI(Serial Peripheral Interface)是一种串行通信协议,用于在微控制器或其他数字芯片之间进行全双工的数据通信。在SPI总线上,有一个主设备(通常是微控制器)和一个或多个从设备(如OLED显示屏)。主设备负责控制总线并发送数据,从设备负责接收数据并做出响应。 要在STM32F103芯片上使用SPI读写OLED,我们需要按照以下步骤进行操作: 1. 硬件连接:首先将STM32F103芯片的SPI引脚与OLED显示屏的SPI引脚连接。通常,STM32F103的SPI引脚由标有SCK(时钟线)、MISO(主设备输入,从设备输出)、MOSI(主设备输出,从设备输入)和NSS(片选信号)的引脚组成。 2. 初始化:在代码中,我们需要初始化SPI配置寄存器,设置SPI时钟相位、极性等参数,以及设置NSS引脚的控制方式。还需要初始化OLED显示屏,设置OLED的工作模式和其他参数。 3. 发送数据:通过SPI发送数据到OLED。我们可以使用SPI发送一个字节的数据或一串字节的数据,具体取决于要显示的内容。发送数据的函数通常会等待数据传输完成,然后返回结果。 4. 接收数据(如果需要):如果OLED显示屏返回一些数据,我们可以通过SPI接收数据的功能来读取这些数据。接收数据的函数通常会等待数据传输完成,然后将接收到的数据返回。 通过以上步骤,我们就可以实现STM32F103芯片对OLED显示屏的SPI读写操作。具体的实现方法和代码可以参考STM32F103的相关文档或参考其他开源项目。 ### 回答3: STM32F103是意法半导体生产的一款32位微控制器,具有强大的性能和丰富的外设支持。SPI(Serial Peripheral Interface)是一种串行外设接口协议,用于在微控制器与外部设备之间进行高速数据传输。 要实现STM32F103与OLEDSPI读写,可以按照以下步骤操作: 1. 首先,需要将OLED连接到STM32F103的SPI端口。OLED通常具有SDA(数据线)、SCL(时钟线)、CS(片选线)、RES(复位线)等引脚,需要将它们连接到STM32F103对应的引脚(如PB14、PB13、PB12、PB11)。 2. 在STM32F103的代码中配置SPI外设。可以使用STM32CubeMX进行外设配置,选择SPI模块,并设置相应的引脚和参数(如波特率、数据位宽等)。 3. 编写代码来初始化SPI外设。在代码中,需要初始化SPI控制寄存器的各个参数,如使能SPI、选择主从模式、设置数据传输顺序等。 4. 编写代码来控制OLED的初始化。在初始化时,可以设置OLED显示参数、清空显示缓存等。 5. 编写代码来实现SPI写入数据到OLED。通过编写SPI发送数据的函数,将要显示的数据发送给OLED。 6. 编写代码来实现SPIOLED读取数据。通过编写SPI接收数据的函数,可以读取OLED的状态信息或其他返回的数据。 7. 在主函数中调用相应的函数,完成SPI读写操作。可以先调用初始化函数,然后通过写入数据和读取数据函数进行SPI读写操作。 综上所述,通过对STM32F103的SPI外设配置和编写相应的代码,可以实现与OLED之间的高速数据读写,可以灵活地控制OLED显示

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值