/********************************************************************** * Copyright: (C)2024 LingYun IoT System Studio * Author: GuoWenxue * * Description: SHT20 Temperature & humidity sensor driver on ISKBoard * * ChangeLog: * Version Date Author Description * V1.0.0 2024.08.29 GuoWenxue Release initial version * ***********************************************************************/ #include #include "sht20.h" #include "miscdev.h" /* udelay() / mdelay() */ /* SHT20 7位标准 I2C 从机地址 */ #define SHT20_I2C_ADDR 0x40 /* SHT20 控制指令 (No Hold Master) */ #define SHT20_CMD_TRIG_T 0xF3 /* 触发温度测量 (非阻塞) */ #define SHT20_CMD_TRIG_RH 0xF5 /* 触发湿度测量 (非阻塞) */ #define SHT20_CMD_SOFT_RESET 0xFE /* 软件复位 */ /* I2C 设备 SHT20 */ static i2c_dev_t sht20; /* 前置静态辅助函数声明 */ static uint8_t sht20_check_crc(uint8_t *data, uint8_t nbytes, uint8_t checksum); i2c_status_t sht20_read_value(i2c_dev_t *sht20, uint8_t cmd, float *value); /** * @brief SHT20 初始化并软复位芯片 * @param bus: 绑定的 I2C 物理总线指针 * @retval 0: 成功; -1: 失败 */ int sht20_init(i2c_bus_t *bus) { uint8_t cmd = SHT20_CMD_SOFT_RESET; int rv = 0; if (!bus) { return -1; } mdelay(20); /* 绑定设备及其设备号到 I2C 总线上 */ i2c_bind_dev(&sht20, bus, SHT20_I2C_ADDR); /* 将I2C总线加锁让设备独占总线 */ i2c_lock_dev(&sht20); /* 向 SHT20 发送软件复位指令,并等待复位完成 */ i2c_write_no_reg(sht20.bus, sht20.dev_addr, &cmd, 1); mdelay(20); /* 释放设备的 I2C 总线锁 */ i2c_free_dev(&sht20); return rv; } /** * @brief 应用层温湿度采样接口函数 (带总线锁) * @param temperature: 存放输出温度结果的指针 * @param humidity: 存放输出湿度结果的指针 * @retval 0: 采样成功且CRC通过; -1: 采样或校验失败 */ int sht20_sample_TrH(float *temperature, float *humidity) { int rv = -1; if( !temperature || !humidity ) { return -1; } /* 将I2C总线加锁让设备独占总线 */ i2c_lock_dev(&sht20); /* 2. 采集温度 */ if (sht20_read_value(&sht20, SHT20_CMD_TRIG_T, temperature) != I2C_OK) { goto cleanup; } /* 3. 采集湿度 */ if (sht20_read_value(&sht20, SHT20_CMD_TRIG_RH, humidity) != I2C_OK) { goto cleanup; } rv = 0; cleanup: /* 4. 释放总线并解锁 */ i2c_free_dev(&sht20); return rv; } /** * @brief SHT20 硬件标准 CRC-8 校验 * @note 多项式 P(x) = x^8 + x^5 + x^4 + 1 = 1 0011 0001 = 0x31 * @param data: 待校验的数据缓冲区指针 (含 High Byte 和 Low Byte) * @param nbytes: 校验的数据长度 (固定为 2 字节) * @param checksum: SHT20 芯片硬件返回的 CRC 结果字节 * @retval 0: 校验通过; 其余值: 校验失败 */ static uint8_t sht20_check_crc(uint8_t *data, uint8_t nbytes, uint8_t checksum) { uint8_t crc = 0x00; /* 初始值为 0 */ uint8_t byte_ctr; uint8_t bit; /* 逐个字节计算 */ for (byte_ctr = 0; byte_ctr < nbytes; ++byte_ctr) { crc ^= (data[byte_ctr]); /* 按位进行模二除法 */ for (bit = 8; bit > 0; --bit) { if (crc & 0x80) { crc = (uint8_t)((crc << 1) ^ 0x31); } else { crc = (uint8_t)(crc << 1); } } } if (crc != checksum) { return 1; /* 校验不匹配 */ } return 0; /* 成功 */ } /** * @brief 读取温湿度的底层通用核心函数 (加入完备的 CRC-8 硬件校验) */ i2c_status_t sht20_read_value(i2c_dev_t *dev, uint8_t cmd, float *value) { uint8_t raw_data[2]; uint8_t crc_byte; uint8_t rx_buf[3]; uint16_t raw_result; uint32_t poll_timeout = 2000; i2c_status_t status; if ( !value ) { return I2C_ERROR; } /* 1. 发送触发测量命令 (无寄存器写模式) */ status = i2c_write_no_reg(dev->bus, dev->dev_addr, &cmd, 1); if (status != I2C_OK) { return I2C_ERROR; } /* 根据测量指令进行预留的 ADC 硬件转换安全延时 */ if (cmd == SHT20_CMD_TRIG_T) { mdelay(65); } else { mdelay(25); } /* 2. 利用高级读 API 的内部寻址应答机制等待芯片转换完成 */ while (poll_timeout--) { /* SHT20 在进行 ADC 转换时,它会对读模式寻址返回 NACK。 * i2c_read_no_reg() 发现地址无应答,会自动发 Stop 并返回错误。 * 我们直接利用这一机制作为非阻塞就绪判断! */ status = i2c_read_no_reg(dev->bus, dev->dev_addr, rx_buf, 3); if (status == I2C_OK) { /* 读取成功,3 字节(高、低、CRC)*/ break; } udelay(100); } if (poll_timeout == 0) { return I2C_ERROR; } /* 3. 分离出读回的 High Byte, Low Byte 以及 CRC 校验码 */ raw_data[0] = rx_buf[0]; raw_data[1] = rx_buf[1]; crc_byte = rx_buf[2]; /* 4. 执行 CRC-8 校验 */ if (sht20_check_crc(raw_data, 2, crc_byte) != 0) { printf("SHT20 CRC Check Failed!\r\n"); return I2C_ERROR; } /* 5. 清除低 2 位状态位 */ raw_result = (uint16_t)((raw_data[0] << 8) | raw_data[1]); raw_result &= ~0x0003; /* 6. 代入物理公式转换 */ if (cmd == SHT20_CMD_TRIG_T) { *value = -46.85f + 175.72f * ((float)raw_result / 65536.0f); } else { *value = -6.0f + 125.0f * ((float)raw_result / 65536.0f); } return I2C_OK; }