前言
本项目将在linux应用层中通过 read/write 和 ioctl 两种方法来读写sht20传感器,主控板选择的是树莓派4B,系统为 ubuntu22。
一、了解I2C
I2C总线是由时钟线(SCL)和数据线(SDA)构成,是一种主从结构总线。总线上可以有多个设备,但只能一个设备作为主机,其他均为从机。
I2C时序图:
启动信号:
在时钟线SCL保持高电平期间,数据线SDA上的电平被拉低(即负跳变),定义为I2C总线总线的启动信号,它标志着一次数据传输的开始。启动信号是一种电平跳变时序信号,而不是一个电平信号。启动信号是由主控器主动建立的,在建立该信号之前I2C总线必须处于空闲状态。
数据位传送:
在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。进行数据传送时,在SCL呈现高电平期间,SDA上的电平必须保持稳定,低电平为数据0,高电平为数据1。只有在SCL为低电平期间,才允许SDA上的电平改变状态。逻辑0的电平为低电压,而逻辑1的电平取决于器件本身的正电源电压VDD(当使用独立电源时)。数据位的传输是边沿触发。
应答信号:
I2C总线上的所有数据都是以8位字节传送的,发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。 应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。 对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号P。
停止信号:
在时钟线SCL保持高电平期间,数据线SDA被释放,使得SDA返回高电平(即正跳变),称为I2C总线的停止信号,它标志着一次数据传输的终止。停止信号也是一种电平跳变时序信号,而不是一个电平信号,停止信号也是由主控器主动建立的,建立该信号之后,I2C总线将返回空闲状态。
二、提取sht20数据手册有效数据
1.command:
2.sht20进行读写的格式:
由此可以看出 sht20 设备的地址为 0x40, 当读取采集的数据时, 返回三个字节,前两个字节是温湿度数据,后一个字节是CRC检验码。
三、第一种方法读写sht20:read/write
在Linux应用层中我们只需对特殊文件进行读写即可和外部设备进行通信,sht20是用I2C协议通信的,我们需要读写/dev/i2c-x的字符流文件。
步骤1:接线
步骤2:使能I2C驱动
在命令行输入下面的命令
选择3
选择I5
选择 yes ,后选择 finish
来到 /dev/ 路径下查看i2c-x文件,如下图i2c-1就是 sht20 的设备文件
步骤3:安装I2C-Tools
分别执行下列命令, 安装并重启
sudo apt-get install i2c-tools
sudo apt-get install python-smbus
sudo adduser pi i2c
sudo reboot
步骤4:查看 I2C 总线上的设备地址
步骤5:编写代码
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/types.h>
#include <sys/stat.h>
#include <linux/i2c.h>
#include <linux/i2c-dev.h>
#include <stdlib.h>
#include <sys/types.h>
#include <string.h>
#include <stdint.h>
#include <time.h>
#include <errno.h>
#include <string.h>
#define SOFTRESET 0xFE
#define TRIGGER_TEMPERATURE_NO_HOLD 0xF3
#define TRIGGER_HUMIDITY_NO_HOLD 0xF5
static inline void msleep(unsigned long ms);
static inline void dump_buf(const char *prompt, uint8_t *buf, int size);
int sht2x_init(void);
int sht2x_softreset(int fd);
int sht2x_get_temp_humidity(int fd, float *temp, float *rh);
int main (int argc, char **argv)
{
int fd;
float temp;
float rh;
uint8_t serialnumber[8];
fd = sht2x_init();
if (fd < 0)
{
printf("SHT2x initialsize failure\n");
return 1;
}
if (sht2x_softreset(fd) < 0)
{
printf("sht2x_softreset failure\n");
return 2;
}
if (sht2x_get_temp_humidity(fd, &temp, &rh) < 0)
{
printf("SHT2x get get Temperature and Relative humidity, failure\n");
return 3;
}
printf("Temperature=%lf 度, humidity= %lf%\n", temp, rh);
close(fd);
return 0;
}
int sht2x_init(void)
{
int fd;
if ((fd=open("/dev/i2c-1", O_RDWR)) < 0)
{
printf("i2c device open failure: %s\n", strerror(errno));
return -1;
}
ioctl(fd, I2C_TENBIT, 0); //设置地址模式,0为7bit
ioctl(fd, I2C_SLAVE, 0x40); //设置从机地址,刚刚我们查看到的地址
return fd;
}
int sht2x_softreset(int fd)
{
uint8_t buf[4];
if (fd < 0)
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__);
return -1;
}
memset(buf, 0, sizeof(buf));
buf[0] = SOFTRESET;
write(fd, buf, 1);
msleep(50);
return 0;
}
static inline void msleep(unsigned long ms)
{
struct timespec cSleep;
unsigned long ulTmp;
cSleep.tv_sec = ms / 1000;
if (cSleep.tv_sec == 0)
{
ulTmp = ms * 10000;
cSleep.tv_nsec = ulTmp * 100;
}
else
{
cSleep.tv_nsec = 0;
}
nanosleep(&cSleep, 0);
}
int sht2x_get_temp_humidity(int fd, float *temp, float *rh)
{
uint8_t buf[4];
if (fd<0 || !temp || !rh)
{
printf("%s line [%d] %s() get invalid input arguments\n", __FILE__, __LINE__, __func__);
return -1;
}
memset(buf, 0, sizeof(buf));
buf[0] = TRIGGER_TEMPERATURE_NO_HOLD;
write(fd, buf, 1);
msleep(85);
memset(buf, 0, sizeof(buf));
read(fd, buf, 3);
dump_buf("Temperature sample data: ", buf, 3);
*temp = 175.72 * (((((int)buf[0]) << 8) + buf[1])/65536.0) -46.85;
memset(buf, 0, sizeof(buf));
buf[0] = TRIGGER_HUMIDITY_NO_HOLD;
write(fd, buf, 1);
msleep(29);
memset(buf, 0, sizeof(buf));
read(fd, buf, 3);
dump_buf("Relative humidity sample data: ", buf, 3);
*rh = 125 * (((((int)buf[0]) << 8) + buf[1]) / 65536.0) - 6;
return 0;
}
static inline void dump_buf(const char *prompt, uint8_t *buf, int size)
{
int i;
if (!buf)
{
return ;
}
if (prompt)
{
printf("%s", prompt);
}
for (i=0; i<size; i++)
{
printf("%02x ", buf[i]);
}
printf("\n");
return ;
}
编写完后,进行编译,运行需要root权限
四、第二方法读写sht20:ioctl
我们都知道在应用层与外部设备进行通信的话我们只需读写相关的设备文件,ioctl便是可以通过设备文件来进行 IO 的控制。在进行ioctl读写之前了解一下相关的结构体:
struct i2c_rdwr_ioctl_data
{
struct i2c_msg __user *msgs; /* pointers to i2c_msgs */
__u32 nmsgs; /* number of i2c_msgs */
};
struct i2c_msg
{
_ _u16 addr; /* slave address */
_ _u16 flags; /* 标志(读、写) */
_ _u16 len; /* msg length */
_ _u8 *buf; /* pointer to msg data */
};
ioctl读写sht20的代码如下:
sht20.h
#include <stdint.h>
#ifndef _SHT20_H
#define _SHT20_H
#define I2C_FILENAME "/dev/i2c-1"
#define DEVICE_ADDR 0x40
#define SHT20_SOFTRESET 0xFE
#define SHT20_TEMPERATURE_NO_HOLD_CMD 0xF3
#define SHT20_HUMIDITY_NO_HOLD_CMD 0xF5
#define SHT20_TEMPERATURE_HOLD_CMD 0xE3
#define SHT20_HUMIDITY_HOLD_CMD 0xE5
static uint8_t sht20_crc8(const uint8_t *data, int len);
int i2c_init(char *i2c_filename);
int sht20_softreset(int fd, uint8_t command, uint8_t i2c_addr);
int get_temperature(int fd, uint8_t command, uint8_t i2c_addr, float *temperature);
int get_humidity(int fd, uint8_t command, uint8_t i2c_addr, float *humidity);
static inline void msleep(unsigned long ms);
void close_fd(int fd);
#endif
sht20.c
#include <stdio.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <time.h>
#include <stdint.h>
#include <fcntl.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/i2c-dev.h>
#include <linux/i2c.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include "sht20.h"
static inline void msleep(unsigned long ms)
{
struct timespec cSleep;
unsigned long ulTmp;
cSleep.tv_sec = ms/1000;
if (cSleep.tv_sec == 0)
{
ulTmp = ms * 10000;
cSleep.tv_nsec = ulTmp * 100;
}
else
{
cSleep.tv_nsec = 0;
}
nanosleep(&cSleep, 0);
}
static uint8_t sht20_crc8(const uint8_t *data, int len)
{
const uint8_t POLYNOMIAL = 0x31;
uint8_t crc = 0;
int i, j;
for (i=0; i<len; ++i)
{
crc ^= *data++;
for (j=0; j<8; ++j)
{
crc = (crc & 0x80) ? (crc << 1) ^ POLYNOMIAL : (crc << 1);
}
}
return crc;
}
int i2c_init(char *i2c_filename)
{
int fd;
//查看 i2c 目标文件是否存在
if (access(i2c_filename, F_OK) < 0)
{
printf("i2c file is not exists!\n");
return -1;
}
//打开 i2c 目标文件
fd = open(i2c_filename, O_RDWR);
if (fd < 0)
{
printf("open i2c file failure: %s\n", strerror(errno));
return -2;
}
ioctl(fd, I2C_TIMEOUT, 2);
ioctl(fd, I2C_RETRIES, 2);
return fd;
}
int sht20_softreset(int fd, uint8_t command, uint8_t i2c_addr)
{
int rv = -1;
int send_size = 1;
uint8_t send_data;
struct i2c_msg i2c_msg;
struct i2c_rdwr_ioctl_data i2c_data;
send_data = command;
i2c_data.msgs = &i2c_msg;
i2c_data.nmsgs = 1;
i2c_msg.len = send_size;
i2c_msg.addr = i2c_addr;
i2c_msg.flags = 0;
i2c_msg.buf = &send_data;
rv = ioctl(fd, I2C_RDWR, &i2c_data);
if (rv < 0)
{
printf("sht20 softreset failure: %s\n", strerror(errno));
return -1;
}
msleep(50);
return 0;
}
int get_temperature(int fd, uint8_t command, uint8_t i2c_addr, float *temperature)
{
int rv = -1;
int send_size = 1;
uint8_t send_data;
uint8_t buf[4];
uint8_t crc;
int read_size = 3;
struct i2c_msg i2c_msg, i2c_msgs;
struct i2c_rdwr_ioctl_data i2c_data, i2c_datas;
send_data = command;
i2c_data.msgs = &i2c_msg;
i2c_data.nmsgs = 1;
i2c_msg.len = send_size;
i2c_msg.addr = i2c_addr;
i2c_msg.flags = 0;
i2c_msg.buf = &send_data;
rv = ioctl(fd, I2C_RDWR, &i2c_data);
if (rv < 0)
{
printf("write command failure: %s\n", strerror(errno));
return -1;
}
msleep(85);
memset(buf, 0, sizeof(buf));
i2c_datas.msgs = &i2c_msgs;
i2c_datas.nmsgs = 1;
i2c_msgs.len = read_size;
i2c_msgs.addr = i2c_addr;
i2c_msgs.flags = 1;
i2c_msgs.buf = buf;
rv = ioctl(fd, I2C_RDWR, &i2c_datas);
if (rv < 0)
{
printf("sht20 measurement temperature failure: %s\n", strerror(errno));
return -2;
}
#if 0
int i;
for (i=0; buf[i]!=NULL; ++i)
{
printf("%lx ", buf[i]);
}
printf("\n");
#endif
#if 1
//crc
crc = sht20_crc8(buf, 2);
if (crc != buf[2])
{
printf("sht20 measurement temperature got CRC error\n");
return -3;
}
#endif
*temperature = 175.72 * (((((int)buf[0]) << 8) + buf[1]) / 65536.0) - 46.85;
return 0;
}
int get_humidity(int fd, uint8_t command, uint8_t i2c_addr, float *humidity)
{
int rv = -1;
int send_size = 1;
uint8_t send_data;
uint8_t buf[4];
uint8_t crc;
int read_size = 3;
struct i2c_msg i2c_msg, i2c_msgs;
struct i2c_rdwr_ioctl_data i2c_data, i2c_datas;
send_data = command;
i2c_data.msgs = &i2c_msg;
i2c_data.nmsgs = 1; //消息的数目
i2c_msg.len = send_size;//写入的字节数
i2c_msg.addr = i2c_addr; //查看到的从机地址
i2c_msg.flags = 0; //0:写、1:读
i2c_msg.buf = &send_data; //写入的数据(命令)
rv = ioctl(fd, I2C_RDWR, &i2c_data);
if (rv < 0)
{
printf("sht20 write command failure: %s\n", strerror(errno));
return -1;
}
msleep(29);
memset(buf, 0, sizeof(buf));
i2c_datas.msgs = &i2c_msgs;
i2c_datas.nmsgs = 1;
i2c_msgs.len = read_size;
i2c_msgs.addr = i2c_addr;
i2c_msgs.flags = 1;
i2c_msgs.buf = buf;
rv = ioctl(fd, I2C_RDWR, &i2c_datas);
if (rv < 0)
{
printf("sht20 measurement humidity failure: %s\n", strerror(errno));
return -2;
}
#if 1
//crc
crc = sht20_crc8(buf, 2);
if (crc != buf[2])
{
printf("sht20 measurement humidity got CRC error\n");
return -3;
}
#endif
*humidity = 125 * (((((int)buf[0]) << 8) + buf[1]) / 65536.0) - 6;
return 0;
}
void close_fd(int fd)
{
close(fd);
return ;
}
main.c
#include <stdio.h>
#include "sht20.h"
int main (int argc, char **argv)
{
int fd;
int rv;
float temperature;
float humidity;
fd = i2c_init(I2C_FILENAME);
if (fd < 0)
{
rv = -1;
goto cleanup;
}
if (sht20_softreset(fd, SHT20_SOFTRESET, DEVICE_ADDR) < 0)
{
rv = -2;
goto cleanup;
}
if (get_temperature(fd, SHT20_TEMPERATURE_NO_HOLD_CMD, DEVICE_ADDR, &temperature) < 0)
{
rv = -3;
goto cleanup;
}
if (get_humidity(fd, SHT20_HUMIDITY_NO_HOLD_CMD, DEVICE_ADDR, &humidity) < 0)
{
rv = -4;
goto cleanup;
}
printf("temperature = %lf度\n", temperature);
printf("humidity = %lf%\n", humidity);
cleanup:
close_fd(fd);
return 0;
}
运行时也是需要 root