一、温度传感器模块
(1)资源介绍
🔅原理图
蓝桥杯物联网竞赛实训平台提供了一个拓展接口 CN2 ,所有拓展模块均可直接安装在 Lora终端上使用;
图1 拓展接口
温度传感器模块电路原理图如下所示:
图2 温度传感器模块电路原理图
通过两张电路图连接可知,引脚资源配置情况为:
Temperature | MCU |
---|---|
SCL | PB6 |
SDA | PB7 |
ALE | PB0 |
[表1 引脚资源配置情况] |
🔅STS30-DIS-B(以下资料来源于STS30-DIS-B数据手册)
STS3x-DIS 是盛思锐最新的高精度数字温度传感器。它依赖于业界成熟的CMOSens® 技术,与其前身相比,提供更高的智能,可靠性和更高的精度规格。其功能包括增强的信号处理,两个独特的用户可选择的I2C 地址和高达1 MHz 的通信速度。DFN封装的占地面积为2.5 x 2.5 mm2,同时保持0.9 mm的高度。这允许将STS3x-DIS 集成到各种各样的应用中。此外,2.15 V至5.5 V的宽电源电压范围保证了广泛应用的兼容性。总而言之,STS3x-DIS融合了盛思锐超过15年的数字传感器专业知识。
图3 STS30-DIS 功能框图
🌙引脚分配
Pin | Name | Comments |
---|---|---|
1 | SDA | 串行数据;输入/输出 |
2 | ADDR | 地址引脚;输入;连接到逻辑高或低,不要浮空 |
3 | ALERT | 指示警告状态;输出;如果不使用,必须保持浮空 |
4 | SCL | 串行时钟;输入/输出 |
5 | VDD | 电源电压;输入 |
6 | nRESET | 复位引脚,低电平有效;输入;如果不使用,建议保持浮空状态;可通过串联电阻R≥2kΩ与VDD连接 |
7 | R | 无电气功能;连接到VSS |
8 | VSS | 接地 |
[表2 STS30-DIS-B引脚分配] |
🌙通信
向传感器发送命令后,在传感器接收另一条命令之前,++需要最小的等待时间为1ms++ 。
所有STS30-DIS-B 命令和数据都映射到16位地址空间。此外,数据和命令受到CRC 校验和的保护。这提高了通信的可靠性。对传感器的16位命令已经包括3位CRC校验和。传感器发送和接收的数据总是经过一个8位的CRC校验。
- 在写方向上,必须传输校验和,++因为STS30-DIS-B只接受后面跟着正确校验和的数据++;
- 在读方向上,它留给主机来读取和处理校验和;
🌙时钟拉伸 (Clock Stretching)
- 当没有时钟拉伸的命令发出时,如果没有数据存在,传感器响应一个带有不确认(NACK)的读报头;
- 当发出带有时钟拉伸的命令时,传感器响应一个带有ACK 的读报头,然后拉下SCL 线。将SCL 线拉下,直到测量完成。一旦测量完成,传感器释放SCL线并发送测量结果;
🌙单次触发模式
图4 单次触发模式下的命令
由图4的流程图可知,单次触发模式的数据获取流程如下:
- 第一部分
图5 发送流程第一部分
🟠️伪代码如下:
cpp
I2C开始;
发送7位I2C地址+写命令位;
等待传感器发送ACK;
发送高8位命令;
等待传感器发送ACK;
发送低8位命令;
等待传感器发送ACK;
I2C结束;
- 第二部分
图6 发送流程第二部分
🟠️伪代码如下:
cpp
/* 时钟拉伸不使能 */
释放时钟线;(传感器测量中)
I2C开始;
发送7位I2C地址+读命令位;
等待传感器发送NACK;
I2C结束;
释放时钟线;(传感器测量中->传感器测量完成)
I2C开始;
发送7位I2C地址+读命令位;
等待传感器发送ACK;
/* 时钟拉伸使能 */
释放时钟线;(传感器测量中)
I2C开始;
发送7位I2C地址+读命令位;
等待传感器发送ACK;
(传感器将时钟线拉低)
- 第三部分
图7 发送流程第三部分
🟠️伪代码如下:
cpp
接收高8位温度数据;
发送ACK;
接收低8位温度数据;
发送ACK;
接收CRC校验和;
发送NACK;
I2C结束;
🌙温度数据转换
(2)STM32CubeMX 软件配置
🔅"工程建立、时钟树配置、Debug 串行线配置、代码生成配置" 在下文中有讲解,这里不再赘述❗️
1️⃣点击引脚 PB6 → 选择 GPIO_Output 模式(此处默认为++推挽输出++);
点击引脚 PB7 → 选择 GPIO_Output 模式(此处配置为++开漏输出++);
图8 引脚配置
2️⃣初始化 OLED;(配置步骤在下文中有讲解,这里不再赘述);
【蓝桥杯------物联网设计与开发】基础模块6 - OLED_蓝桥杯物联网oled-CSDN博客
3️⃣生成代码即可;
(3)代码编写
🟢️main 函数
cpp
/* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN PV */
uint8_t H_VALUE, L_VALUE, CRC_VALUE; // 高8位数据、低8位数据、CRC校验数据
float temp; // 温度数据
uint16_t ui_dat_temp; // 显示数据
uint8_t puc_oled[17]; // OLED显示存储区
/* USER CODE END PV */
/**
* @brief The application entry point.
* @retval int
*/
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_GPIO_Init();
MX_I2C3_Init();
/* USER CODE BEGIN 2 */
OLED_Init();
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* 第一部分 */
I2CStart2();
I2CSendByte2(0x94);
I2CWaitAck2();
I2CSendByte2(0x24);
I2CWaitAck2();
I2CSendByte2(0x0B);
I2CWaitAck2();
I2CStop2();
/* 第二部分 */
HAL_Delay(2);
I2CStart2();
I2CSendByte2(0x95);
I2CWaitAck2();
I2CStop2();
HAL_Delay(2);
I2CStart2();
I2CSendByte2(0x95);
I2CWaitAck2();
/* 第三部分 */
H_VALUE = I2CReceiveByte2();
I2CSendAck2();
L_VALUE = I2CReceiveByte2();
I2CSendAck2();
CRC_VALUE = I2CReceiveByte2();
I2CSendNotAck2();
I2CStop2();
if(CRC_VALUE)
{
;
}
temp = ((H_VALUE << 8) | L_VALUE) * 175.0 / 65535 - 45;
ui_dat_temp = (uint16_t)(Temp_Read() * 10);
sprintf((char*)puc_oled, " Temperature ");
OLED_ShowString(0, puc_oled);
sprintf((char*)puc_oled, " %.1f ", ui_dat_temp / 10.0);
OLED_ShowString(2, puc_oled);
HAL_Delay(500); //延时一会儿
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
(4)实验现象
能够实时测量环境温度。
二、温度采集接口函数封装
🟡️软件i2c.c
cpp
#include "i2c_2.h"
#define DELAY_TIME 20
//I2C总线内部延时函数
static void delay1(unsigned int n)
{
uint32_t i;
for ( i = 0; i < n; ++i);
}
//配置SDA引脚为输入模式
void SDA_Input_Mode2(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = SDA2;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//配置SDA引脚为输出模式
void SDA_Output_Mode2(void)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = SDA2;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
}
//SDA引脚输出
void SDA_Output2(uint16_t val )
{
if(val)
GPIOB->BSRR |= SDA2;
else
GPIOB->BRR |= SDA2;
}
//SCL引脚输出
void SCL_Output2( uint16_t val )
{
if(val)
GPIOB->BSRR |= SCL2;
else
GPIOB->BRR |= SCL2;
}
//读取SDA引脚状态
uint8_t SDA_Input2(void)
{
return HAL_GPIO_ReadPin(GPIOB, SDA2);
}
//I2C总线启动信号
void I2CStart2(void)
{
SDA_Output2(1);
delay1(DELAY_TIME);
SCL_Output2(1);
delay1(DELAY_TIME);
SDA_Output2(0);
delay1(DELAY_TIME);
SCL_Output2(0);
delay1(DELAY_TIME);
}
//I2C总线停止信号
void I2CStop2(void)
{
SCL_Output2(0);
delay1(DELAY_TIME);
SDA_Output2(0);
delay1(DELAY_TIME);
SCL_Output2(1);
delay1(DELAY_TIME);
SDA_Output2(1);
delay1(DELAY_TIME);
}
//等待应答
unsigned char I2CWaitAck2(void)
{
unsigned short cErrTime = 5;
SDA_Input_Mode2();
delay1(DELAY_TIME);
SCL_Output2(1);
delay1(DELAY_TIME);
while(SDA_Input2())
{
cErrTime--;
delay1(DELAY_TIME);
if (0 == cErrTime)
{
SDA_Output_Mode2();
I2CStop2();
return ERROR;
}
}
SCL_Output2(0);
SDA_Output_Mode2();
delay1(DELAY_TIME);
return SUCCESS;
}
//发送应答
void I2CSendAck2(void)
{
SDA_Output2(0);
delay1(DELAY_TIME);
delay1(DELAY_TIME);
SCL_Output2(1);
delay1(DELAY_TIME);
SCL_Output2(0);
delay1(DELAY_TIME);
}
//发送非应答
void I2CSendNotAck2(void)
{
SDA_Output2(1);
delay1(DELAY_TIME);
delay1(DELAY_TIME);
SCL_Output2(1);
delay1(DELAY_TIME);
SCL_Output2(0);
delay1(DELAY_TIME);
}
//发送一个字节数据
void I2CSendByte2(unsigned char cSendByte)
{
unsigned char i = 8;
while (i--)
{
SCL_Output2(0);
delay1(DELAY_TIME);
SDA_Output2(cSendByte & 0x80);
delay1(DELAY_TIME);
cSendByte += cSendByte;
delay1(DELAY_TIME);
SCL_Output2(1);
delay1(DELAY_TIME);
}
SCL_Output2(0);
delay1(DELAY_TIME);
}
//接收一个字节数据
unsigned char I2CReceiveByte2(void)
{
unsigned char i = 8;
unsigned char cR_Byte = 0;
SDA_Input_Mode2();
while (i--)
{
cR_Byte += cR_Byte;
SCL_Output2(0);
delay1(DELAY_TIME);
delay1(DELAY_TIME);
SCL_Output2(1);
delay1(DELAY_TIME);
cR_Byte |= SDA_Input2();
}
SCL_Output2(0);
delay1(DELAY_TIME);
SDA_Output_Mode2();
return cR_Byte;
}
🟡️软件i2c.h
cpp
#ifndef __I2C_2_H
#define __I2C_2_H
#include "main.h"
//I2C总线引脚定义
#define SCL2 GPIO_PIN_6
#define SDA2 GPIO_PIN_7
//接口函数
void I2CStart2(void);
void I2CStop2(void);
unsigned char I2CWaitAck2(void);
void I2CSendAck2(void);
void I2CSendNotAck2(void);
void I2CSendByte2(unsigned char cSendByte);
unsigned char I2CReceiveByte2(void);
#endif
🟡️温度采集函数
cpp
/* 温度采集函数,中等可重复性+不使能时钟拉伸 */
float Temp_Read(void)
{
uint8_t H_VALUE, L_VALUE, CRC_VALUE;
float temp;
I2CStart2();
I2CSendByte2(0x94);
I2CWaitAck2();
I2CSendByte2(0x24);
I2CWaitAck2();
I2CSendByte2(0x0B);
I2CWaitAck2();
I2CStop2();
HAL_Delay(2);
I2CStart2();
I2CSendByte2(0x95);
I2CWaitAck2();
I2CStop2();
HAL_Delay(2);
I2CStart2();
I2CSendByte2(0x95);
I2CWaitAck2();
H_VALUE = I2CReceiveByte2();
I2CSendAck2();
L_VALUE = I2CReceiveByte2();
I2CSendAck2();
CRC_VALUE = I2CReceiveByte2();
I2CSendNotAck2();
I2CStop2();
if(CRC_VALUE)
{
;
}
temp = ((H_VALUE << 8) | L_VALUE) * 175.0 / 65535 - 45;
return temp;
}
🔴温度采集接口函数调用实例
cpp
/* 采集任务函数 */
void Task_Colt(void)
{
/* 200ms进入一次 */
if(cnt_colt < 200) return;
cnt_colt = 0;
/* 温度采集与转换 */
ui_dat_temp = (uint16_t)(Temp_Read() * 10);
}
三、踩坑日记
(1)引脚配置问题
- 推荐使用软件模拟I2C(硬件I2C,在4ti测试出来有问题);
- PB7(SDA)配置为开漏输出、速度快;PB6(SCL)配置为推挽输出,速度快;
(2)底层驱动延时问题
🔅注意编写底层驱动时存在两次HAL_Delay(2),释放时钟线作用,根据芯片手册说明,此处需要≥1ms;
(3)ALERT引脚
🔅报警引脚Alert一般用于连接MCU的中断引脚。引脚的输出取决于可编程限制的温度读数值。当满足报警条件时,引脚拉高;
🔅根据手册说明,不使用时必须保持浮空。