第1部分 SHT31传感器介绍
1.1芯片简介
SHT3X系列是由瑞士Sensirion生产的高精度温湿度传感器,也是Sensirion公司目前主打的温湿度传感器系列。现在网上常见的相关资料调试的基本上以SHT30为主,SHT31则较少。本次开发用的是更高级的SHT35系列。
这次采用的SHT35传感器允许宽电压输入,支持2.15V~5.5V。采用IIC总线通信,最高可达1MHz的通信速度。并根据ADDR引脚的接法,提供两个可选的地址:0x44,0x45。传感器的精度为1.5%RH和0.1℃。传感器最大工作范围-40-125℃,0-100%RH。原装芯片有8个引脚。
还有一点要注意的是SHT3x推荐的的最佳工作环境是5℃-60℃,20%RH-80%RH。当传感器暴露在>80%RH的工作环境下超过60小时后,会出现+3%RH的偏差。
SHT3x系列提供数字接口和模拟接口两种规格。
相关资料在文末给出。
1.2 引脚介绍
芯片总共有8个引脚
(1)SDA :IIC数据线引脚
(2)ADDR :地址引脚,可连接VSS或VDD,分别会有不同的地址。不能浮空。
(3)ALERT :报警引脚,如果使用,建议接到单片机的外部中断。不用的话建议浮空。
(4)SCL :IIC时钟线引脚
(5)VDD :电压输入引脚
(6)nRESET :复位引脚,低电平有效。如果不用,建议接到VDD。
(7)R :没有电气作用的“没卵用“”引脚,连接到VSS
(8) VSS :接地
(附上官方推荐的典型应用电路)
1.3命令和模式介绍
首先看一下地址
很明显,当传感器ADDR引脚接VSS时,采用地址A;当传感器ADDR引脚接VDD时,采用地址B。本次开发的传感器 Address=0x44.
传感器支持单次数据采集模式和周期性数据采集模式。其实单次数据采集模式下,可选时钟延伸,而周期性数据采集默认开始数据延伸。这里我们默认采用周期性数据采集模式。
SHT3X支持12种工作模式,分别有高,中,低三档可选刷新率。mps=0.5,1,2…时,分别代表每两秒采集一次数据,每秒采集一次数据,每秒采集两次数据…
如希望设定高刷新率,每秒采集一次。那么向传感器写命令0x2130即可。注意当采用mps=10时,会导致传感器自发热,影响测量。
工作顺序为:先发送IIC通信开始标志Start后,写入左移一位的地址,并将空出来的位写0表示写数据。当收到传感器应答后,即可发送命令的高八位,再次等待应答,再发送余下的低八位。然后等待ACK应答即可。
其他命令同理,大部分都是同样的写入模式。
1.4 重要命令及其工作流程
那么我们看几条重要的命令及其工作流程。
设置好工作模式后写入此命令,可以准备好接受数据。先发送IIC通信开始标志Start后,写入左移一位的地址,并将空出来的位写1表示读数据。然后等待ACK应答即可接受数据。注意数据传输顺序是先温度后湿度。并且都是十六位数据。并且每个数据后都附8位的CRC校验。在完成湿度的CRC校验后,即可回复NACK,传感器将停止发送数据,释放SDA线,以便于MCU发送Stop标志,结束通信。
第2部分 SHT3x官方参考代码简介
2.1.官方给的参考code是基于STM32平台的。文末有资料获取方式。
由于Nordic蓝牙芯片的I2C接口与STM32有稍微不同。本文将基于该参考code,通过修改,移植到nRF52832蓝牙芯片上。
2.2 参考I2C 代码详解
本文档包含C语言的示例代码,用于通过与SHT3x湿度和温度传感器通信
I2C接口。代码的目的是在实现SHT3x传感器时简化用户的软件编程。除了
简单的测量湿度和温度,代码包含计算CRC校验和和计算物理湿度和温度值。这个示例代码是为STM32-Discovery板编写和优化的,但它可以很容易地应用于其他微控制器而做稍微改变。
至于更详细的官方参考code和说明,不是本文的重点,系列资料在文末有获取方式。
第3部分 Nordic蓝牙芯片 I2C(TWI)软件设计介绍:
关于nRF52832蓝牙芯片的I2C(TWI)串行总线的原理,之前的文章已有介绍。以下主要介绍软件设计部分。
nRF52832 片内集成的 TWI(两线串行总线)兼容 I2C 总线,带有 EasyDMA,可与连接到同一总线的多个从机设备通讯,主要特点如下:
(1)兼容 I2C。
(2)速率:100 kbps、250 kbps 或 400 kbps。
(3)支持时钟延伸。
(4)带 EasyDMA。
(5)TWI 的 SCL 和 SDA 信号可以通过配置寄存器连接到任何一个GPIO,这样可以灵活地实现器件引脚排列,并有效利用电路板空间和信号路由。
nRF52832 的 TWI 的原理框图如下图所示,TWI 主机通过触发STARTTX 或STARTRX 任务启动TWI 传输,通过触发 STOP 任务停止 TWI 传输。TWI 主机在挂起时无法停止,因此必须在 TWI 主机恢复后触发 STOP 任务停止 TWI。启动 TWI 主机后,在TWI 主机停止之前,即在 LASTRX,LASTTX 或 STOPPED 事件之后,不应再次触发 STARTTX 任务或STARTRX 任务。如果从机产生 NACK 输入,那么 TWI 主机将产生ERROR 事件。
3.1.nRF的I2C 函数库应用
TWI 的应用步骤如下图所示,首先要定义 TWI 驱动程序实例,驱动程序实例对应具体的硬件 TWI 外设(TWI0 和 TWI1),驱动程序实例决定了我们使用的是 TWI0 还是 TWI1。接着初始化配置TWI 连接的引脚和速率等参数,注册事件句柄(非堵塞模式下),初始化后使能TWI,之后就可以使用 TWI 进行传输数据。
3.2定义TWI 驱动程序实例
TWI 驱动程序实例使用 nrf_drv_twi_t 结构体定义,该结构体描述了具体的 TWI 外设, 当我们定义了nrf_drv_twi_t 类型的变量并对其赋值后,该变量就对应了一个具体的硬件 TWI 外设。
定义驱动程序实例代码如下,初始化宏 NRF_DRV_TWI_INSTANCE 的输入参数对应TWI 外设的编号,即如果我们定义 TWI0 的驱动程序实例,TWI_INSTANCE_ID 的值设置为 0,定义 TWI1 的驱动程序实例,TWI_INSTANCE_ID 的值设置为 1。驱动程序实例定义后,我们即可通过该驱动程序实例访问对应的 TWI。
3.3 初始化TWI(I2C)
TWI 初始化的库函数是 nrf_drv_twi_init ()函数,该函数同时也配置了 TWI 是否使用阻塞模式。
(1)应用程序提供事件句柄:TWI 工作于非阻塞模式。
(2)应用程序不提供事件句柄(event_handler 设置为 NULL):TWI 工作于阻塞模式。建议使用阻塞模式。
/** TWI初始化
* @brief TWI initialization.
*/
//I2C引脚
#define TWI_SCL_M 26 //I2C SCL
#define TWI_SDA_M 25 //I2C SDA
/* TWI instance. */
static const nrf_drv_twi_t m_twi = NRF_DRV_TWI_INSTANCE(TWI_INSTANCE_ID);
void twi_init (void)
{
ret_code_t err_code;
const nrf_drv_twi_config_t twi_config = {
.scl = TWI_SCL_M,
.sda = TWI_SDA_M,
.frequency = NRF_DRV_TWI_FREQ_100K,
.interrupt_priority = APP_IRQ_PRIORITY_HIGH,
.clear_bus_init = false
};
err_code = nrf_drv_twi_init(&m_twi, &twi_config, NULL, NULL);
APP_ERROR_CHECK(err_code);
nrf_drv_twi_enable(&m_twi);
NRF_LOG_INFO("twi_master_init ok.");
NRF_LOG_FLUSH();
nrf_gpio_pin_clear(20); //ledµÆÁÁ
}
3.5 TWI数据传输函数
TWI 驱动程序提供了两个单独的函数nrf_drv_twi_tx() 函数 和 nrf_drv_twi_ rx() 函数 , 分别用于完成数据的发送和接收,函原型如下表所示。使tx 和 rx 函数时,尤其要注意从机地址 address,函数接收的是 7位地址,函数内部会自己加上读写位。因此如果某个 I2C 接口设备提供的是 8位地址,我们需要提取出 7位地址赋值给address,也就是去掉 8位地址的最低(R/W位)。
nRF52832蓝牙芯片的所有I2C 通信都是通过这两个函数接口。不需要自己去重写I2C驱动函数。
3.5.1 TWI发送函数模型:nrf_drv_twi_tx()
函数原型 | STATIC_INLINE ret_code_t nrf_drv_twi_tx (nrf_drv_twi_t const * p_instance, uint8_t address, uint8_t const * p_data, uint8_t length, bool no_stop) |
函数功能 | 向 TWI 从机发送数据,发生错误时将停止传输。 如果传输正在进行,则该函数返回错误代码 NRF_ERROR_BUSY。 |
参 数 | [in] p_instance:指向 TWI 驱动程序实例结构体。 [in] address:指定的从机地址(7 位 LSB)。 [in] p_data:指向传输数据缓存。 [in] length:发送的字节数。 [in] no_stop:如果置位,成功传输后总线上不会生成停止条件(允许在 下一次传输中重复启动)。 |
返回值 | NRF_SUCCESS:发送成功。 NRF_ERROR_BUSY:驱动程序尚未准备好进行新的传输。NRF_ERROR_INTERNAL:硬件检测到错误。NRF_ERROR_INVALID_ADDR:使用了 EasyDMA,但是缓存地址没有位于RAM 空间。 NRF_ERROR_DRV_TWI_ERR_ANACK:在轮询模式下发送地址后收到NAC。 NRF_ERROR_DRV_TWI_ERR_DNACK:在轮询模式下发送数据后收到 NACK。 |
3.5.2 TWI接收函数模型:nrf_drv_twi_rx()
|
返回值 | [in] address:指定的从机地址(7 位 LSB)。 [in] p_data:指向接收数据缓存。 [in] length:读取的字节数。 |
返回值 | NRF_SUCCESS:发送成功。 NRF_ERROR_BUSY:驱动程序尚未准备好进行新的传输。NRF_ERROR_INTERNAL:硬件检测到错误。NRF_ERROR_DRV_TWI_ERR_OVERRUN:接收数据未及时读取,被新接收的数据覆盖。 NRF_ERROR_DRV_TWI_ERR_ANACK:在轮询模式下发送地址后收到NAC。 NRF_ERROR_DRV_TWI_ERR_DNACK:在轮询模式下发送数据后收到 NACK。 |
3.5 I2C 设备扫描
为什么要扫描设备?通过扫描I2C 设备,可以确认设备是否正常工作,以及设备接入是否正常等,还可以通过打印信息,确认设备的 I2C 地址。
/* Number of possible TWI addresses. */
#define TWI_ADDRESSES 127
void iic_scan_address(void)
{
ret_code_t err_code;
uint8_t address;
uint8_t sample_data;
bool detected_device = false;
NRF_LOG_INFO("TWI scanner started.");
NRF_LOG_FLUSH();
twi_init();//twi init
for (address = 1; address <= TWI_ADDRESSES; address++)
{
err_code = nrf_drv_twi_rx(&m_twi, address, &sample_data, sizeof(sample_data));
if (err_code == NRF_SUCCESS)
{
detected_device = true;
NRF_LOG_INFO("TWI-i2c device detected at address 0x%x.", address);
}
NRF_LOG_FLUSH();
}
if (!detected_device)
{
NRF_LOG_INFO("No device was found.");
//NRF_LOG_FLUSH();
}
NRF_LOG_INFO("TWI device scan ended.");
NRF_LOG_FLUSH();
}
I2C设备扫描的Log信息:
由于我的I2C 总线上挂了两个设备:
0x3C为 0.96寸oled屏幕的I2C地址,
0x44为,SHT35温湿度传感器的I2C地址。
第4部分 SHT3x代码移植到Nordic蓝牙芯片:
由于Nordic蓝牙芯片的I2C接口与STM32有稍微不同。本文将基于官方给的STM32代码,通过修改,移植到nRF52832蓝牙芯片上。
通过扫描,我知道我的SHT35温湿度传感器的I2C地址为0x44。当然也可以通过手册得知。扫描只是为了确认没错。
4.1 写SHT35寄存器函数
这个函数非常重要,由于SHT35的命令都是16位,而nrf_drv_twi_tx()函数中,数据定义的是8位。所以关键点就在code中。
static uint8_t tx_buf[2];
tx_buf[0] = (uint8_t)(cmd>>8); //低8位放在buf0
tx_buf[1] = (uint8_t)(cmd & 0xFF); //高8位放在buf1
static etError SHT3x_WriteCommand(uint16_t cmd) { ret_code_t err_code; static uint8_t tx_buf[2]; tx_buf[0] = (uint8_t)(cmd>>8); //低8位 tx_buf[1] = (uint8_t)(cmd & 0xFF); //高8位 //TWI传输完成标志设置为false //m_xfer_done = false; uint8_t retry_num = 20; do{ err_code = nrf_drv_twi_tx(&m_twi, SHT35_ADDRESS, tx_buf, 2, false); APP_ERROR_CHECK(err_code); retry_num--; //等待TWI总线应答 //UNUSED_VARIABLE(err_code); } while((NRF_SUCCESS != err_code) && (0 < retry_num));
//返回写入成功 return NO_ERROR; } |
4.2 周期测量模式
//发送命令,周期测量模式,测量频率1Hz void SHT3X_SetPeriodicMeasurement(void) { SHT3x_WriteCommand(CMD_MEAS_PERI_2_H); NRF_LOG_INFO("cmd_meas_peri_2_h"); NRF_LOG_FLUSH(); } |
4.3 读取温湿度数据
//读取温湿度数据,函数中会校验数据 etError SHX3X_ReadMeasurementBuffer(float* temperature, float* humidity) { etError error; ret_code_t err_code; static uint8_t bytes[6]; //写入命令 SHT3x_WriteCommand(CMD_FETCH_DATA); //0xE000,readout measurements for periodic mode //读出温湿度数据 //m_xfer_done = false; //尝试20次 uint8_t retry_num = 20; do{ err_code = nrf_drv_twi_rx(&m_twi, SHT35_ADDRESS, bytes, 6); APP_ERROR_CHECK(err_code); retry_num--; //等待TWI总线应答 //UNUSED_VARIABLE(err_code); } while((NRF_SUCCESS != err_code) && (0 < retry_num)); //while (m_xfer_done == false){}; //校验温湿度数据 error = SHT3X_CheckCrc(bytes, 2, bytes[2]); if(error == NO_ERROR) { *temperature = SHT3X_CalcTemperature((bytes[0] << 8) | bytes[1]); *humidity = SHT3X_CalcHumidity((bytes[3] << 8) | bytes[4]); } return NO_ERROR; } |
4.4 CRC校验
//------------------------------CRC校验和验证------------------------ // Generator polynomial for CRC #define POLYNOMIAL 0x131 // P(x) = x^8 + x^5 + x^4 + 1 = 100110001
//CRC校验 static uint8_t SHT3X_CalcCrc(uint8_t data[], uint8_t nbrOfBytes) { uint8_t bit; // bit mask uint8_t crc = 0xFF; // calculated checksum uint8_t byteCtr; // byte counter //用给定的多项式计算8位校验和 for(byteCtr = 0; byteCtr < nbrOfBytes; byteCtr++) { crc ^= (data[byteCtr]); for(bit = 8; bit > 0; --bit) { if(crc & 0x80) crc = (crc << 1) ^ POLYNOMIAL; else crc = (crc << 1); } }
return crc; }
//检验校验是否正确
static etError SHT3X_CheckCrc(uint8_t data[], uint8_t nbrOfBytes, uint8_t checksum) { uint8_t crc; // calculated checksum //计算8位校验和 crc = SHT3X_CalcCrc(data, nbrOfBytes); //验证校验 if(crc != checksum) return CHECKSUM_ERROR; else return NO_ERROR; } |
4.5 温湿度计算
//------------------------计算温湿度--------------------------------- /** * 描述 : 计算温度 * 入参: rawValue:读取的温度信息 * 返回值 : 计算出的温度 **/ static float SHT3X_CalcTemperature(uint16_t rawValue) { //T = -45 + 175 * rawValue / (2^16-1) float temperature = 175.0f * (float)rawValue / 65535.0f - 45.0f; return temperature; } /** * 描述 : 计算湿度[%RH] * 入参: dat :读取的湿度信息 * 返回值 : 计算出的湿度值 ***/ static float SHT3X_CalcHumidity(uint16_t rawValue) { // 计算相对湿度[%RH] // RH = rawValue / (2^16-1) * 100 float huminity = 100.0f * (float)rawValue / 65535.0f; return huminity; } |
4.6 测试温湿度
先设置测量模式,再读取数据。
//-----------------------温湿度测试程序--------------------------------------- void sht35_c_test(void){
NRF_LOG_INFO("sht35_c_test start..,measure the temperature and humidity"); NRF_LOG_FLUSH();
float temperature; // ?? float humidity; // ?? [%RH]
//周期测量模式1HZ SHT3X_SetPeriodicMeasurement(); nrf_delay_ms(50); //while(true) // { SHX3X_ReadMeasurementBuffer(&temperature, &humidity); NRF_LOG_INFO("temperature:%d `C",temperature); NRF_LOG_INFO("humidity :%d RH",humidity); NRF_LOG_FLUSH(); //测量频率设置1Hz,因此读取间隔不能小于1s nrf_delay_ms(1200); // } } |
测试结果:
用4pin的I2C OLED屏幕显示温湿度:
5.结语
下一章,介绍nRF52832上点亮4pin 0.96寸I2C的OLED屏幕。
白浪介绍:
(1)关于射频、微波、天线、无线通信、智能硬件、软件编程、渗透安全、人工智能、区块链,Java、Android、C/C++、python等综合能力的培养提升。
(2)各种学习资料、学习软件分享。
1.扫码关注公众号(Geekxiaobai)
2. 如在后台发送“Python高级编程”“Python Graphics”或者“2003”,即可免费获得电子书籍。仅供学习之用。
3. 扫码关注后,查看往期内容,会有更多资料惊喜等着你来拿哦
想要更多相关学习资料,可以在公众号留言哦。
========******=========******========******=========******==========