基于IIC和SPI协议的温湿度采集与OLED显示

I2C的物理层

(1) 它是一个支持设备的总线。“总线”指多个设备共用的信号线。在一个 I2C 通讯总线

中,可连接多个 I2C 通讯设备,支持多个通讯主机及多个通讯从机。

(2) 一个 I2C 总线只使用两条总线线路,一条双向串行数据线(SDA) ,一条串行时钟线

(SCL)。数据线即用来表示数据,时钟线用于数据收发同步。

(3) 每个连接到总线的设备都有一个独立的地址,主机可以利用这个地址进行不同设备之

间的访问。

(4) 总线通过上拉电阻接到电源。当 I2C 设备空闲时,会输出高阻态,而当所有设备都空

闲,都输出高阻态时,由上拉电阻把总线拉成高电平。

(5) 多个主机同时使用总线时,为了防止数据冲突,会利用仲裁方式决定由哪个设备占用

总线。

(6) 具有三种传输模式:标准模式传输速率为 100kbit/s ,快速模式为 400kbit/s ,高速模式

下可达 3.4Mbit/s,但目前大多 I 2C 设备尚不支持高速模式。

(7) 连接到相同总线的 IC 数量受到总线的最大电容 400pF 限制 。

I2C的协议层

I2C 的协议定义了通讯的起始和停止信号、数据有效性、响应、仲裁、时钟同步和地

址广播等环节。

I2C的通信过程

主机的 I2C 接口产生的传输起始信号(S),这时连接到 I2C 总线上的所有从机都会接收

到这个信号。

起始信号产生后,所有从机就开始等待主机紧接下来 广播 的从机地址信号(SLAVE_ADDRESS)。

在 I2C 总线上,每个设备的地址都是唯一的,当主机广播的地址与某个设备地址相同时,这

个设备就被选中了,没被选中的设备将会忽略之后的数据信号。

根据 I2C 协议,这个从机地址可以是 7 位或 10 位。

在地址位之后,是传输方向的选择位,该位为 0 时,表示后面的数据传输方向是由主

机传输至从机,即主机向从机写数据。该位为 1 时,则相反,即主机由从机读数据。

从机接收到匹配的地址后,主机或从机会返回一个应答(ACK)或非应答(NACK)信号,

只有接收到应答信号后,主机才能继续发送或接收数据。

写数据

主机开始正式向从机传输数据(DATA),数据包的大小为 8 位,主机每发送完一个字节数

据,都要等待从机的应答信号(ACK),重复这个过程。当数据传输结束时,主机向从机发送

一个停止传输信号§,表示不再传输数据。

读数据

接收到应答信号后,从机开始向主机返回数据(DATA),数据包大小也为 8 位,从机每发送完一个数

据,都会等待主机的应答信号(ACK)。主机希望停止接收数据时,就向从机返回一个非应答信号(NACK),则从机自动停止数据传输。

读和写

主机通过 SLAVE_ADDRESS 寻找到从设备后,发送一段“数据”,这段数据通常用于表示从设备

内部的寄存器或存储器地址(注意区分它与 SLAVE_ADDRESS 的区别);在第二次的传输中,对该地址的内容进行读或写。也就是说,第一次通讯是告诉从机读写地址,第二次则是读写的实际内容。

通讯的起始和停止信号

在这里插入图片描述
在这里插入图片描述

应答响应

在这里插入图片描述

软件I2C

直接控制 STM32的两个 GPIO 引脚,分别用作 SCL及 SDA,按照上述信号的时序要求,直接

像控制 LED 灯那样控制引脚的输出(若是接收数据时则读取 SDA 电平),就可以实现 I2C 通讯。

硬件I2C

STM32 的 I2C 片上外设专门负责实现 I2C 通讯协议,只要配置好该外设,它就会自动根据协议

要求产生通讯信号,收发数据并缓存起来,CPU只要检测该外设的状态和访问数据寄存器,就能完成数据收发。

基于I2C的温度采集

使用cube MX创建工程

在这里插入图片描述
其余的配置和正常配置差不多

向工程树中,添加两个文件

AHT20.h

#ifndef INC_AHT20_H_
#define INC_AHT20_H_

#include "i2c.h"

//****************************************
// 定义 AHT20 命令
//****************************************
#define AHT20_Write             0x00    //读取
#define AHT20_Read              0x01    //写入



//****************************************
// 定义 AHT20 地址
//****************************************
#define AHT20_SLAVE_ADDRESS		0x70		/* I2C从机地址 */



//****************************************
// 定义 AHT20 命令
//****************************************
#define	AHT20_INIT_COMD  		0xBE	//初始化 寄存器地址
#define	AHT20_SoftReset			0xBA	//软复位 单指令
#define	AHT20_TrigMeasure_COMD	0xAC	//触发测量 寄存器地址

// 存储AHT20传感器信息的结构体

typedef struct m_AHT20
{
	uint8_t alive;	// 0-器件不存在; 1-器件存在
	uint8_t flag;	// 读取/计算错误标志位。0-读取/计算数据正常; 1-读取/计算设备失败
	uint32_t HT[2];	// 湿度、温度 原始传感器的值(20Bit).

	float RH;		// 湿度,转换单位后的实际值,标准单位%
	float Temp;		// 温度,转换单位后的实际值,标准单位°C
}AHT20_StructureTypedef;

extern  AHT20_StructureTypedef  Humiture;

uint8_t AHT20_Init(void);
void    AHT20_Start_Init(void);
uint8_t AHT20_ReadHT(uint32_t *HT);
uint8_t StandardUnitCon(AHT20_StructureTypedef *aht);
uint8_t AHT20_Get_Value(AHT20_StructureTypedef *p);

#endif /* INC_AHT20_H_ */

AHT20.c

#include "AHT20.h"

AHT20_StructureTypedef  Humiture;


/**
  * @brief  读AHT20 设备状态字
  * @param  void
  * @retval uint8_t 设备状态字
  */
static uint8_t AHT20_ReadStatusCmd(void)
{
	uint8_t tmp = 0;
    HAL_I2C_Master_Receive(&hi2c1, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
	return tmp;
}

/**
  * @brief  读AHT20 设备状态字 中的Bit3: 校准使能位
  * @param  void
  * @retval uint8_t 校准使能位:1 - 已校准; 0 - 未校准
  */
static uint8_t AHT20_ReadCalEnableCmd(void)
{
	uint8_t tmp = 0;
	tmp = AHT20_ReadStatusCmd();
	return (tmp>>3)&0x01;
}

/**
  * @brief  读AHT20 设备状态字 中的Bit7: 忙标志
  * @param  void
  * @retval uint8_t 忙标志:1 - 设备忙; 0 - 设备空闲
  */
static uint8_t AHT20_ReadBusyCmd(void)
{
	uint8_t tmp = 0;
	tmp = AHT20_ReadStatusCmd();
	return (tmp>>7)&0x01;
}

/**
  * @brief  AHT20 芯片初始化命令
  * @param  void
  * @retval void
  */
static void AHT20_IcInitCmd(void)
{
	uint8_t tmp = AHT20_INIT_COMD;
	HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
}

/**
  * @brief  AHT20 软复位命令
  * @param  void
  * @retval void
  */
static void AHT20_SoftResetCmd(void)
{
	uint8_t tmp = AHT20_SoftReset;
	HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, &tmp, 1, 0xFFFF);
}

/**
  * @brief  AHT20 触发测量命令
  * @param  void
  * @retval void
  */
static void AHT20_TrigMeasureCmd(void)
{
    static uint8_t tmp[3] = {AHT20_TrigMeasure_COMD, 0x33, 0x00};
	HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, tmp, 3, 0xFFFF);
}


/**
  * @brief  AHT20 设备初始化
  * @param  void
  * @retval uint8_t:0 - 初始化AHT20设备成功; 1 - 初始化AHT20失败,可能设备不存在或器件已损坏
  */
uint8_t AHT20_Init(void)
{
	uint8_t rcnt = 2+1;//软复位命令 重试次数,2次
	uint8_t icnt = 2+1;//初始化命令 重试次数,2次

	while(--rcnt)
	{
		icnt = 2+1;

		HAL_Delay(40);//上电后要等待40ms
		// 读取温湿度之前,首先检查[校准使能位]是否为1
		while((!AHT20_ReadCalEnableCmd()) && (--icnt))// 2次重试机会
		{
			HAL_Delay(1);
			// 如果不为1,要发送初始化命令
			AHT20_IcInitCmd();
			HAL_Delay(40);//这个时间手册没说,按上电时间算40ms
		}

		if(icnt)//[校准使能位]为1,校准正常
		{
			break;//退出rcnt循环
		}
		else//[校准使能位]为0,校准错误
		{
			AHT20_SoftResetCmd();//软复位AHT20器件,重试
			HAL_Delay(20);//这个时间手册标明不超过20ms.
		}
	}

	if(rcnt)
	{
		return 0;// AHT20设备初始化正常
	}
	else
	{
		return 1;// AHT20设备初始化失败
	}
}

/**
  * @brief  AHT20 寄存器复位
  * @param  void
  * @retval void
  */
static void AHT20_Register_Reset(uint8_t addr)
{
  uint8_t  iic_tx[3] = {0}, iic_rx[3] = {0};
  
  iic_tx[0] = addr;
  HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, iic_tx, 3, 0xFFFF);
  HAL_Delay(5);
  HAL_I2C_Master_Receive(&hi2c1, AHT20_SLAVE_ADDRESS, iic_rx, 3, 0xFFFF);
  HAL_Delay(10);
  iic_tx[0] = 0xB0 | addr;
  iic_tx[1] = iic_rx[1];
  iic_tx[2] = iic_rx[2];
  HAL_I2C_Master_Transmit(&hi2c1, AHT20_SLAVE_ADDRESS, iic_tx, 3, 0xFFFF);
  HAL_Delay(10);
}


/**
  * @brief  AHT20 设备开始初始化
  * @param  void
  * @retval void
  */
void AHT20_Start_Init(void)
{
  static uint8_t    temp[3] = {0x1B, 0x1C, 0x1E}, i;
  for(i = 0; i < 3; i++)
  {
    AHT20_Register_Reset(temp[i]);
  }
}



/**
  * @brief  AHT20 设备读取 相对湿度和温度(原始数据20Bit)
  * @param  uint32_t *HT:存储20Bit原始数据的uint32数组
  * @retval uint8_t:0-读取数据正常; 1-读取设备失败,设备一直处于忙状态,不能获取数据
  */
uint8_t AHT20_ReadHT(uint32_t *HT)
{
	uint8_t cnt=3+1;//忙标志 重试次数,3次
	uint8_t tmp[6];
	uint32_t RetuData = 0;

	// 发送触发测量命令
	AHT20_TrigMeasureCmd();

	do{
		HAL_Delay(75);//等待75ms待测量完成,忙标志Bit7为0
	}while(AHT20_ReadBusyCmd() && (--cnt));//重试3次

	if(cnt)//设备闲,可以读温湿度数据
	{
		HAL_Delay(5);
		// 读温湿度数据
        HAL_I2C_Master_Receive(&hi2c1, AHT20_SLAVE_ADDRESS, tmp, 6, 0XFFFF);
		// 计算相对湿度RH。原始值,未计算为标准单位%。
		RetuData = 0;
		RetuData = (RetuData|tmp[1]) << 8;
		RetuData = (RetuData|tmp[2]) << 8;
		RetuData = (RetuData|tmp[3]);
		RetuData = RetuData >> 4;
		HT[0] = RetuData;

		// 计算温度T。原始值,未计算为标准单位°C。
		RetuData = 0;
		RetuData = (RetuData|tmp[3]) << 8;
		RetuData = (RetuData|tmp[4]) << 8;
		RetuData = (RetuData|tmp[5]);
		RetuData = RetuData&0xfffff;
		HT[1] = RetuData;

		return 0;
	}
	else//设备忙,返回读取失败
	{
		return 1;
	}
}

/**
  * @brief  AHT20 温湿度信号转换(由20Bit原始数据,转换为标准单位RH=%,T=°C)
  * @param  struct m_AHT20* aht:存储AHT20传感器信息的结构体
  * @retval uint8_t:0-计算数据正常; 1-计算数据失败,计算值超出元件手册规格范围
  */
uint8_t StandardUnitCon(AHT20_StructureTypedef *aht)
{
	aht->RH = (double)aht->HT[0] *100 / 1048576;//2^20=1048576 //原式:(double)aht->HT[0] / 1048576 *100,为了浮点精度改为现在的
	aht->Temp = (double)aht->HT[1] *200 / 1048576 -50;

	//限幅,RH=0~100%; Temp=-40~85°C
	if((aht->RH >=0)&&(aht->RH <=100) && (aht->Temp >=-40)&&(aht->Temp <=85))
	{
		aht->flag = 0;
		return 0;//测量数据正常
	}
	else
	{
		aht->flag = 1;
		return 1;//测量数据超出范围,错误
	}
}


/**
  * @brief  AHT20 温湿度信号转换(由20Bit原始数据,转换为标准单位RH=%,T=°C)
  * @param  struct m_AHT20* aht:存储AHT20传感器信息的结构体
  * @retval uint8_t:0-计算数据正常; 1-计算数据失败,计算值超出元件手册规格范围
  */
uint8_t AHT20_Get_Value(AHT20_StructureTypedef *p)
{
  uint8_t   temp = 0;
  
  temp = AHT20_ReadHT(p->HT);
  
  if(temp == 0)
  {
    temp = StandardUnitCon(p);
  }
  
  return temp;
}

修改main.c为

int main(void)
{
  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */
 
  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
   MX_I2C1_Init();
  /* USER CODE BEGIN 2 */
  if(AHT20_Init() != 0)
  {
    Humiture.alive = 0;
    printf("AHT20 Initialization failed\r\n");
  }
  else
  {
    Humiture.alive = 1;
    printf("AHT20 Initialization succeeded\r\n");
  }  
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {   
    AHT20_Get_Value(&Humiture);
    printf("environment temperature is : %.2f;\r\nenvironment humidity is : %.2f",Humiture.Temp, Humiture.RH);
    HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

注意这里的printif需要重定向,如果不了解的可以用HAL的transmit.

还有就是在做这个温湿度采集的时候,需要添加.c和.h文件到工程下。我最开始的时候是直接在文件资源管理器中去添加的,但是一直都不能引用头文件里的东西。更新工程、添加新的头文件路径、用绝对路径寻找都不行,最后在AHT20.c文件中找到一个函数右键打开AHT20.h发现里面居然是空白的,明明我在编辑的时候就放了代码进去的,在文件管理器里面打开也有内容。然后添加代码进文件才行。
在这里插入图片描述

OLED和点阵屏显

SPI

SPI(Serial Peripheral Interface)就是串行外围设备接口。
SPI,是一种高速的,全双工,同步的通信总线,并且在芯片的管脚上只占用四根线,节约了芯片的管脚。SPI 是一个环形总线结构,由 ss(cs)、sck、sdi、sdo 构成,时序主要是在 sck 的控制下,两个双向移位寄存器进行数据交换。
上升沿发送、下降沿接收、高位先发送。
上升沿到来的时候,sdo 上的电平将被发送到从设备的寄存器中。
下降沿到来的时候,sdi 上的电平将被接收到主设备的寄存器中。
在这里插入图片描述

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

SPI的通信过程

在这里插入图片描述

MOSI 与 MISO 的信号只在 NSS 为低电平的时候才有效,在 SCK 的每个时钟周期 MOSI 和 MISO 传输一位数据。

OLED显示屏显示数据

首先配置SPI

在这里插入图片描述
分配GPIO
在这里插入图片描述

生成工程以后进行代码移植

向项目里添加文件led,c
在这里插入图片描述

向这个文件夹里添加头文件

在这里插入图片描述

然后修改led.h为
在这里插入图片描述

修改led.c
在这里插入图片描述

向头文件里添加代码

delay_init();	    	 	  
	 NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 
	 OLED_Init(); //OLED³õʼ»¯
	 OLED_Clear();
	

	  OLED_WR_Byte(0x2E,OLED_CMD);       
   OLED_WR_Byte(0x26,OLED_CMD);        
   OLED_WR_Byte(0x00,OLED_CMD);       
   OLED_WR_Byte(0x00,OLED_CMD);        
   OLED_WR_Byte(0x07,OLED_CMD);       
   OLED_WR_Byte(0x07,OLED_CMD);       
   OLED_WR_Byte(0x00,OLED_CMD);       
   OLED_WR_Byte(0xFF,OLED_CMD);        
   OLED_WR_Byte(0x2F,OLED_CMD);       
   

	
	OLED_ShowChar(0,16,'&',1,12);
  OLED_ShowString(0,16,"632007030116",12);

	
/*	OLED_ShowNum(0,16,ss[0],1,24);
	 OLED_ShowNum(8,16,ss[1],1,24);
	 OLED_ShowNum(16,16,ss[2],1,24);
	 OLED_ShowNum(24,16,ss[3],1,24);
	OLED_ShowNum(32,16,ss[4],1,24);
	 OLED_ShowNum(40,16,ss[5],1,24);*/
	

	
	 OLED_ShowChinese(0,0,0,16);
	OLED_ShowChinese(16,0,1,16);
	OLED_ShowChinese(32,0,2,16);

	 OLED_Refresh_Gram();
	
	
	
   while(1)
	 {

	 }
}     

OLED_Showchinese里写入的是取模软件生成的,取模软件直接网上搜索或者找淘宝店家要一个就行

这里这个我不知道那个中景源给的代码咋回事,我明明以及调了显示方式为正常显示,但是显示的数字依旧是反相,对其他的地址修改就能正常,比如说是白底显示就能正常。但是修改显示方式就是不行。还有就是这个keil经常不更新hex文件,改完源文件以后生成的hex文件居然和之前是一模一样的。烧录也是起码要连续烧录三遍,OLED屏幕的才会发生变化。

;

/* OLED_ShowNum(0,16,ss[0],1,24);
OLED_ShowNum(8,16,ss[1],1,24);
OLED_ShowNum(16,16,ss[2],1,24);
OLED_ShowNum(24,16,ss[3],1,24);
OLED_ShowNum(32,16,ss[4],1,24);
OLED_ShowNum(40,16,ss[5],1,24);*/

 OLED_ShowChinese(0,0,0,16);
OLED_ShowChinese(16,0,1,16);
OLED_ShowChinese(32,0,2,16);

 OLED_Refresh_Gram();

while(1)
{

 }

}


OLED_Showchinese里写入的是取模软件生成的,取模软件直接网上搜索或者找淘宝店家要一个就行

这里这个我不知道那个中景源给的代码咋回事,我明明以及调了显示方式为正常显示,但是显示的数字依旧是反相,对其他的地址修改就能正常,比如说是白底显示就能正常。但是修改显示方式就是不行。还有就是这个keil经常不更新hex文件,改完源文件以后生成的hex文件居然和之前是一模一样的。烧录也是起码要连续烧录三遍,OLED屏幕的才会发生变化。

![在这里插入图片描述](https://img-blog.csdnimg.cn/dfbab94c86f746d184bacd0ea70b7ba8.gif)

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值