Linux应用层两种方法读写sht20:read/write和ioctl


前言

本项目将在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
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值