开发环境为Ubuntu16.04LTS+vscode,使用了正点原子IMX6ULL开发板,板子上运行的是正点原子的出厂系统
参考了这篇文章(温湿度传感器SHTC3驱动开发(一)小白也能轻松理解_启希的博客-CSDN博客_shtc3
SHTC3简介
SHTC3是一款I2C温湿度传感器,测量的精度为湿度:±2%,温度±0.2℃
一些详细的参数可以在数据手册里找到,这里列几个重要的
设备地址为0x70
一个测量周期包概括五个步骤:
1、唤醒SHTC3:先发送写入指令(0xE0),再发送唤醒指令高位(0x35),再发送唤醒指令低位(0x17)。
2、等待唤醒:数据手册上写的最大唤醒时间是240us,等待的时间大于这个就行了。
3、发送采集指令:先发送写入指令(0xE0),再发送采集指令的高位和低位。采集指令有多个,根据需要自行选择,见上文。
4、接收数据:发送读取指令(0xE1),连续接收6个字节数据。如果采集的指令是先存温度,那么这6个字节的第1-2个字节就是温度数值,第3个字节是温度校验。第4-5个字节是湿度数值,第6个字节是湿度校验。如果采集的指令是先存湿度,则前3个字节和后3个字节相反。
5、进入睡眠:发送写入指令,再发送睡眠指令进入睡眠。
不同的采集指令对应不同的采集模式:
温湿度计算公式:
驱动程序、测试程序编写
修改设备树
芯片与SHTC3通过I2C交流,首先需要在设备树里添加设备节点
在i2c1节点下添加设备:
编译设备树,并拷贝到nfs目录或tftp目录:
make dtbs
cp imx6ull-14x14-nand-4.3-800x480-c.dtb imx6ull-14x14-nand-4.3-480x272-c.dtb imx6ull-14x14-nand-7-800x480-c.dtb imx6ull-14x14-nand-7-1024x600-c.dtb imx6ull-14x14-nand-hdmi.dtb imx6ull-14x14-nand-vga.dtb /home/zjz/linux/nfs/ -f
在uboot通过网络测试dtb文件:
tftp 83000000 imx6ull-alientek-nand.dtb
或nfs 83000000 192.168.1.123:/home/zjz/linux/nfs/imx6ull-alientek-nand.dtb (实际使用时出现卡死,下载不动,但是指令应该是没问题的)
更新设备树查看i2c设备:
驱动程序
linux I2C驱动框架分为两部分,I2C总线驱动和I2C设备驱动。总线部分驱动由厂商提供,设备驱动部分由用户自行编写。
驱动程序框架搭建可以参考文章开头提到的博客,驱动源码放下面:
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/err.h>
#include <linux/errno.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include "shtc3reg.h"
#include <linux/semaphore.h>
#define SHTC3_CNT 1
#define SHTC3_NAME "shtc3"
struct shtc3_dev{
dev_t devid;
struct cdev cdev;
int major;
int minor;
struct class* class;
struct device* device;
void * private_data;
unsigned short T,RH;/*温湿度数据*/
//设置互斥量,同一时间只能有一个应用在读取温湿度
struct semaphore sem;
};
struct shtc3_dev shtc3dev;
int shtc3_open(struct inode *inode,struct file *filp)
{
filp->private_data = &shtc3dev;
/*上锁*/
down(&shtc3dev.sem);
return 0;
}
static ssize_t shtc3_read(struct file *filp, char __user *buf,size_t cnt, loff_t *offt)
{
unsigned char data[SHTC1_RESPONSE_LENGTH];
short real_data[2];
int ret = 0;
int val;
//获取私有数据
struct shtc3_dev* dev = (struct shtc3_dev *)filp->private_data;
/*向用户返回SHTC3的原始数据*/
//发送唤醒指令,等待唤醒成功
ret = i2c_master_send(dev->private_data, shtc1_cmd_wakeup, SHTC1_CMD_LENGTH);
if (ret != SHTC1_CMD_LENGTH) {
printk( "failed to send wakeup command: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
mdelay(1);//延时1ms等待唤醒,大于240us即可
//发送读取数据指令,选择读取模式,这里的是0x7ca2,长度是2字节
ret = i2c_master_send(dev->private_data, shtc1_cmd_normalmode_rec_clockstr_enable_Tfirst,
SHTC1_CMD_LENGTH);//模式为normal,clock stretching enable,先读取温度数据,后读取湿度数据
if (ret != SHTC1_CMD_LENGTH) {
printk("failed to send receive command: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
mdelay(1);//延时1ms,等待数据获取
//接收数据,保存在data中
ret = i2c_master_recv(dev->private_data, data, SHTC1_RESPONSE_LENGTH);
if (ret != SHTC1_RESPONSE_LENGTH) {
printk("failed to read values: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
/*
* From datasheet:
* T = -45 + 175 * ST / 2^16
* RH = 100 * SRH / 2^16
*
* Adapted for integer fixed point (3 digit) arithmetic.
*/
//这里温度的数据处理要特别注意,因为Linux内核不支持浮点运算,
//所以想要多保留小数点后几位需要特别处理
//我们保留小数点后三位,所以放大1000倍,正常来说,根据公式
//会这样写((175000 * val) >> 16) - 45000;
//但是会有问题,val是一个5位数得值,所以175000*val超过了int数据类型范围了
//所以这里直接把175000 >> 3后,再乘以val,然后再 >> 13,减去45000
//就得到这个了((21875 * val) >> 13) - 45000
//湿度保留小数点后两位
//先把10000 >> 3后,再做处理
val = be16_to_cpup((__be16 *)data);
dev->T = ((21875 * val) >> 13) - 45000;
val = be16_to_cpup((__be16 *)(data + 3));
dev->RH = ((1250 * val) >> 13);
real_data[0] = dev->T;
real_data[1] = dev->RH;
ret = -1;
ret = copy_to_user(buf,real_data,sizeof(real_data));
if(ret < 0){
printk("copy to user fail\r\n");
}
//printk("kernel message T=%d RH=%d\r\n",dev->T,dev->RH);
//发送睡眠指令
ret = i2c_master_send(dev->private_data, shtc1_cmd_sleep, SHTC1_CMD_LENGTH);
if (ret != SHTC1_CMD_LENGTH) {
printk("failed to send sleep command: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
return ret;
}
int shtc3_release (struct inode *inode, struct file *filp)
{
int ret = 0;
//获取私有数据
struct shtc3_dev* dev = (struct shtc3_dev *)filp->private_data;
//发送睡眠指令
ret = i2c_master_send(dev->private_data, shtc1_cmd_sleep, SHTC1_CMD_LENGTH);
if (ret != SHTC1_CMD_LENGTH) {
printk("failed to send sleep command: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
/*释放锁*/
up(&shtc3dev.sem);
return ret;
}
struct file_operations shtc3_fops={
.owner = THIS_MODULE,
.open = shtc3_open,
.read = shtc3_read,
.release = shtc3_release,
};
static int shtc3_probe(struct i2c_client *client,
const struct i2c_device_id *id)
{
int ret;
struct device *dev = &client->dev;
char id_reg[2];
sema_init(&shtc3dev.sem,1);
/*搭建字符设备驱动框架*/
shtc3dev.major = 0;
if(shtc3dev.major){//给定设备号
shtc3dev.devid = MKDEV(shtc3dev.major,0);
ret = register_chrdev_region(shtc3dev.devid,SHTC3_CNT,SHTC3_NAME);
}else{//未给定设备号
ret = alloc_chrdev_region(&shtc3dev.devid,0,SHTC3_CNT,SHTC3_NAME);
shtc3dev.major = MAJOR(shtc3dev.devid);
shtc3dev.minor = MINOR(shtc3dev.devid);
}
if(ret < 0){
printk("shtc3dev module fail devid\r\n");
goto fail_devid;
}else{
printk("major:%d minor:%d\r\n",shtc3dev.major,shtc3dev.minor);
}
/*注册字符设备*/
shtc3dev.cdev.owner = THIS_MODULE;
cdev_init(&shtc3dev.cdev,&shtc3_fops);
ret = cdev_add(&shtc3dev.cdev,shtc3dev.devid,SHTC3_CNT);
if(ret < 0){
printk("ap3216c module fail cdev\r\n");
goto fail_cdev;
}
/*自动添加设备节点*/
//初始化类
shtc3dev.class = class_create(THIS_MODULE,SHTC3_NAME);
if(IS_ERR(shtc3dev.class)){
ret = PTR_ERR(shtc3dev.class);
printk("key module failed class\r\n");
goto fail_class;
}
//初始化设备
shtc3dev.device = device_create(shtc3dev.class,NULL,shtc3dev.devid,NULL,SHTC3_NAME);
if(IS_ERR(shtc3dev.device)){
ret = PTR_ERR(shtc3dev.device);
printk("key module failed device\r\n");
goto fail_device;
}
//将从机地址传入shtc3dev
shtc3dev.private_data = client;
//shtc3自检
//唤醒
ret = i2c_master_send(client, shtc1_cmd_wakeup, SHTC1_CMD_LENGTH);
if (ret != SHTC1_CMD_LENGTH) {
dev_err(dev, "could not send wakeup command: %d\n", ret);
return ret < 0 ? ret : -ENODEV;
}
mdelay(1);//延时1ms,等待延时完成
/* 由数据手册可知,有一个efc8寄存器用于验证传感器是否能正常沟通,所以我们这里读取该寄存器 */
ret = i2c_master_send(client, shtc1_cmd_read_id_reg, SHTC1_CMD_LENGTH);
if (ret != SHTC1_CMD_LENGTH) {
dev_err(dev, "could not send read_id_reg command: %d\n", ret);
return ret < 0 ? ret : -ENODEV;
}
/* 读取完,再接收数据,进行验证 */
ret = i2c_master_recv(client, id_reg, sizeof(id_reg));
if (ret != sizeof(id_reg)) {
dev_err(dev, "could not read ID register: %d\n", ret);
return -ENODEV;
}
if ((id_reg[1] & SHTC1_ID_REG_MASK) != SHTC1_ID) {
dev_err(dev, "ID register doesn't match\n");
return -ENODEV;
}
printk("shtc3 check ID success\r\n");
printk("shtc3_probe\r\n");
return 0;
fail_device:
device_destroy(shtc3dev.class,shtc3dev.devid);
fail_class:
class_destroy(shtc3dev.class);
fail_cdev:
cdev_del(&shtc3dev.cdev);
fail_devid:
unregister_chrdev_region(shtc3dev.devid,SHTC3_CNT);
return ret;
}
static int shtc3_remove(struct i2c_client* client)
{
//发送睡眠指令
int ret = i2c_master_send(shtc3dev.private_data, shtc1_cmd_sleep, SHTC1_CMD_LENGTH);
if (ret != SHTC1_CMD_LENGTH) {
printk("failed to send sleep command: %d\n", ret);
return ret < 0 ? ret : -EIO;
}
//注销cdev
cdev_del(&shtc3dev.cdev);
//注销设备号
unregister_chrdev_region(shtc3dev.devid,SHTC3_CNT);
//摧毁设备
device_destroy(shtc3dev.class,shtc3dev.devid);
//摧毁类
class_destroy(shtc3dev.class);
printk("shtc3 remove\r\n");
return 0;
}
//传统匹配方法
static const struct i2c_device_id shtc3_id[]={
{"shtc3",0},
{ }
};
//设备数匹配方法
static struct of_device_id shtc3_dt_id[]={
{.compatible = "fzu,shtc3"},
{ }
};
static struct i2c_driver shtc3_i2c_driver={
.probe = shtc3_probe,
.remove = shtc3_remove,
.id_table = shtc3_id,
.driver = {
.name = "shtc3",
.owner = THIS_MODULE,
.of_match_table = of_match_ptr(shtc3_dt_id),
},
};
static int __init shtc3_init(void)
{
printk("i2c module init\r\n");
return i2c_add_driver(&shtc3_i2c_driver);
}
static void __exit shtc3_exit(void)
{
i2c_del_driver(&shtc3_i2c_driver);
printk("i2c module exit\r\n");
}
module_init(shtc3_init);
module_exit(shtc3_exit);
测试程序
编写一个读取温湿度的测试程序
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/time.h>
/*
*argc:应用程序参数个数
*argv[]:参数内容字符串
*./shtc3 /dev/shtc3 读取数据
*/
int main(int argc,char *argv[])
{
int fd;
unsigned short data[2];
float T,RH;
int ret = -1;
if(argc!=2){
printf("usage error\r\n");
return -1;
}
char *filename;
filename = argv[1];
fd = open(filename,O_RDONLY);
if(fd < 0){
printf("open file %s failed\r\n",filename);
return -1;
}
while(1){
ret = read(fd,data,sizeof(data));
//ret = fread(data, sizeof(unsigned short), 2, (FILE*)fdopen(fd, "r"));
printf("reading shtc3:%d\r\n",ret);
printf("sizeof data:%d\r\n",sizeof(data));
if(ret >= 0){
T = (float)data[0]/1000 ;
RH = (float)data[1]/1000;
printf("T:%.2f RH:%.2f \r\n",T,RH);
}
usleep(500000);
}
close(fd);
return ret;
}