GXHTC3驱动同上一篇文章,我们再做一款基于北京中科银河芯单总线通讯的高精度温湿度传感器GXHT3W驱动;
做驱动先看规格书:
可以看到GXHT3W温湿度传感器仅需两根线即可进行通讯(VCC采用寄生供电,从通讯数据线上窃取电流,从而节省VCC端口),并且拥有全球唯一的ID可 用作加密保护,数字标签等功能;
官方推荐的传感器原理图也非常简单
注意:由于供电采用的“窃电”原理,上拉电阻的阻值不能太大,最好是4.7K以内的阻值
我们将数据线接在鲁班猫2开发板物理引脚第40脚GPIOB2上面,如下图所示
修改设备树代码,将GPIOB2设置为普通GPIO:
鲁班猫2的设备树文件位于/kernel/arch/arm64/boot/dts/rockchip/rk3568-lubancat-2-v2.dts ,编译后替换掉原设备树文件:
cp arch/arm64/boot/dts/rockchip/rk3568-lubancat-2-v2.dtb /boot/dtb/
开始着手GXHT3W的单总线驱动软件配置;
首先,GXHT3W的总线读写时序如下:
static void gxht3w_write_bit(uint8_t bit)
{
GXHT3W_IO_OUT(); /* 设置为输出 */
bit = bit > 1 ? 1 : bit;
GXHT3W_WRITE(0);
udelay(2);
GXHT3W_WRITE(bit);
/* 写0和写1的时间至少要大于60us */
udelay(60); /* us */
GXHT3W_WRITE(1); /* 释放总线 */
udelay(16);
}
/* 整个读周期最少需要60us,启动读开始信号后必须15us内读取IO电平,否则就会被上拉拉高 */
static uint8_t gxht3w_read_bit(void)
{
uint8_t bit;
GXHT3W_IO_OUT(); /* 设置为输出 */
/* 读时间的起始:必须由主机产生 >1us <15us 的低电平信号 */
GXHT3W_WRITE(0);
udelay(5);
GXHT3W_IO_IN(); /* 设置为输入 */
udelay(5);
bit = GXHT3W_READ(); /* 读取结果 */
/* 这个延时参数请参考时序图 */
udelay(45);
return bit;
}
复位时序:
static int gxht3w_reset(void)
{
int ret = 1;
GXHT3W_IO_OUT(); /* 设置为输出 */
GXHT3W_WRITE(0);
/* 主机至少产生480us的低电平复位信号 */
udelay(750);
/* 主机在产生复位信号后,需将总线拉高 */
GXHT3W_WRITE(1);
/*从机接收到主机的复位信号后,会在15~60us后给主机发一个存在脉冲*/
udelay(75);
GXHT3W_IO_IN(); /* 设置为输入 */
ret = GXHT3W_READ();
udelay(10);
GXHT3W_IO_OUT();
GXHT3W_WRITE(1); /* 释放总线 */
// udelay(10);
// GXHT3W_WRITE(0);
// udelay(60);
// GXHT3W_WRITE(1);
// udelay(16);
return ret;
}
数据读取流程ROM匹配模式:
数据读取流程跳过ROM匹配模式:
因为我们的总线上面只挂载一颗GXHT3W,我们采用比较简单的跳过ROM匹配的模式来采集数据,采集数据过程如下:
int ret = -1, i;
unsigned char tpmsb, tplsb, Th, Tl, con, res, humil, humim, crc, data[9];
unsigned short tem, hum;
short gxht3w_result[2];
if (gxht3w_reset() != 0)
{
printk("%d gxht3w reset failed\n", __LINE__);
return -EFAULT;
}
gxht3w_wrte_byte(CMD_SKIP_ROM_ID);
gxht3w_wrte_byte(CMD_CONVERT_TEMP);
msleep(60);
if (gxht3w_reset() != 0)
{
printk("%d gxht3w reset failed", __LINE__);
return -EFAULT;
}
gxht3w_wrte_byte(CMD_SKIP_ROM_ID);
gxht3w_wrte_byte(CMD_READ_DATA); /* 发送读数据命令 */
for (i = 0; i < 9; i++)
{
data[i] = gxht3w_read_byte();
}
crc = data[8];
if (CRC_Check(data, 8) == crc)
{
tplsb = data[0];
tpmsb = data[1];
Th = data[2];
Tl = data[3];
con = data[4];
res = data[5];
humil = data[6];
humim = data[7];
tpmsb = 0x0f & tpmsb;
tem = ((tpmsb << 8) | tplsb);//温度拼接
hum = ((humil << 8) | humim);//湿度拼接i
//内核不能进行浮点运算,把值放大100倍
gxht3w_result[0] = 17500 * tem / 4095 - 4500;
gxht3w_result[1] = (100 * hum / 655);
if (gxht3w_result[0] < 17501 && gxht3w_result[1] < 10001)
{
ret = copy_to_user(buf, gxht3w_result, size);
if (ret != 0)
{
printk("copy_to_user error!");
}
}
}
return ret;
测试app:
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
int main(int argc, char *argv[])
{
unsigned short data[2];
float T,RH;
int ret = -1 ;
/*打开文件*/
int fd = open("/dev/gxht3w", O_RDWR);
if(fd < 0)
{
printf("open file : %s failed !\n", argv[0]);
return -1;
}
/*读取数据*/
while(1)
{
ret = read(fd,data,sizeof(data));//linux内核不支持浮点运算,返回的数据是放大100倍的整型数需要/100
//ret = fread(data, sizeof(unsigned short), 2, (FILE*)fdopen(fd, "r"));
printf("reading gxht3W:%d\r\n",ret);
printf("sizeof data:%d\r\n",sizeof(data));
if(ret >= 0){
T = (float)data[0]/100 ;
RH = (float)data[1]/100;
printf("T:%.2f RH:%.2f \r\n",T,RH);
}
usleep(1000000);
}
int error = close(fd);
if(error<0)
{
printf("close file error!\n");
}
return 0;
}
运行结果:
以下是整体的测试源码方便码友们一次性复制
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <asm/io.h>
#include <linux/device.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/platform_device.h>
#include <linux/of_irq.h>
#include <linux/wait.h>
#include <linux/sched/signal.h>
#include <linux/atomic.h>
#define DEV_PIN_DTS_NAME "gxht3wdat" /* GPIO引脚的属性名 */
#define DEV_NAME "gxht3w" /* 设备名 /dev/gxht3w */
#define DEV_DTS_COMPATIBLE "gxht,gxht3w" /* 设备匹配属性 compatible */
struct semaphore sem; //互斥锁,只能有一个打开
struct gxht3w {
int gpio; /* gpio */
dev_t dev_no; /* 设备号 */
struct cdev chrdev;
struct class* class;
};
static struct gxht3w gxht3w_dev;
#define CMD_CONVERT_TEMP 0x44
#define CMD_READ_DATA 0xBE
#define CMD_SKIP_ROM_ID 0xCC
#define CMD_MATCH_ROM_ID 0x55
#define CMD_READ_ROM_ID 0x33
#define CMD_SEARCH_ROM_ID 0xF0
#define GXHT3W_IO_OUT() gpio_direction_output(gxht3w_dev.gpio, 1)
#define GXHT3W_IO_IN() gpio_direction_input(gxht3w_dev.gpio)
#define GXHT3W_WRITE(bit) gpio_set_value(gxht3w_dev.gpio, bit)
#define GXHT3W_READ() gpio_get_value(gxht3w_dev.gpio)
static int gxht3w_reset(void)
{
int ret = 1;
GXHT3W_IO_OUT(); /* 设置为输出 */
GXHT3W_WRITE(0);
/* 主机至少产生480us的低电平复位信号 */
udelay(750);
/* 主机在产生复位信号后,需将总线拉高 */
GXHT3W_WRITE(1);
/*从机接收到主机的复位信号后,会在15~60us后给主机发一个存在脉冲*/
udelay(75);
GXHT3W_IO_IN(); /* 设置为输入 */
ret = GXHT3W_READ();
udelay(10);
GXHT3W_IO_OUT();
GXHT3W_WRITE(1); /* 释放总线 */
udelay(10);
GXHT3W_WRITE(0);
udelay(60);
GXHT3W_WRITE(1);
udelay(16);
return ret;
}
static void gxht3w_write_bit(uint8_t bit)
{
GXHT3W_IO_OUT(); /* 设置为输出 */
bit = bit > 1 ? 1 : bit;
GXHT3W_WRITE(0);
udelay(2);
GXHT3W_WRITE(bit);
/* 写0和写1的时间至少要大于60us */
udelay(60); /* us */
GXHT3W_WRITE(1); /* 释放总线 */
udelay(16);
}
/* 整个读周期最少需要60us,启动读开始信号后必须15us内读取IO电平,否则就会被上拉拉高 */
static uint8_t gxht3w_read_bit(void)
{
uint8_t bit;
GXHT3W_IO_OUT(); /* 设置为输出 */
/* 读时间的起始:必须由主机产生 >1us <15us 的低电平信号 */
GXHT3W_WRITE(0);
udelay(5);
GXHT3W_IO_IN(); /* 设置为输入 */
udelay(5);
bit = GXHT3W_READ(); /* 读取结果 */
/* 这个延时参数请参考时序图 */
udelay(45);
return bit;
}
static void gxht3w_wrte_byte(uint8_t byte)
{
int i;
for (i = 0; i < 8; i++)
{
gxht3w_write_bit((byte >> i) & 0x01);
}
}
static uint8_t gxht3w_read_byte(void)
{
int i;
uint8_t bit;
uint8_t byte = 0;
for (i = 0; i < 8; i++)
{
bit = gxht3w_read_bit();
if (bit) byte |= (0x01 << i);
}
return byte;
}
/* 使设备只能被一个进程打开 */
static int _drv_open(struct inode* node, struct file* file)
{
/*上锁*/
// down(&sem);
gxht3w_reset();
printk("gxht3w open ,gpio=%d\n", gxht3w_dev.gpio);
return 0;
}
//CRC校验
static u8 CRC_Check(unsigned char *check_data, unsigned char num)
{
uint8_t ret = 0,pBuf,i;
while(num--)
{
pBuf = *check_data ++;
for ( i = 0; i < 8; i ++ )
{
if ((ret ^ (pBuf)) & 0x01)
{
ret ^= 0x18;
ret >>= 1;
ret |= 0x80;
}
else
{
ret >>= 1;
}
pBuf >>= 1;
}
}
return ret;
}
static ssize_t _drv_read(struct file* filp, char __user* buf, size_t size, loff_t* offset)
{
int ret=-1,i;
unsigned char tpmsb, tplsb, Th, Tl, con, res, humil, humim, crc,data[9];
unsigned short tem, hum;
short gxht3w_result[2];
if(gxht3w_reset() != 0)
{
printk("%d gxht3w reset failed\n",__LINE__);
return -EFAULT;
}
gxht3w_wrte_byte(CMD_SKIP_ROM_ID);
gxht3w_wrte_byte(CMD_CONVERT_TEMP);
msleep(60);
if (gxht3w_reset() != 0)
{
printk("%d gxht3w reset failed", __LINE__);
return -EFAULT;
}
gxht3w_wrte_byte(CMD_SKIP_ROM_ID);
gxht3w_wrte_byte(CMD_READ_DATA); /* 发送读数据命令 */
for(i=0;i<9;i++)
{
data[i] = gxht3w_read_byte();
}
crc = data[8];
if(CRC_Check(data,8) == crc)
{
tplsb = data[0];
tpmsb = data[1];
Th = data[2];
Tl = data[3];
con = data[4];
res = data[5];
humil = data[6];
humim = data[7];
tpmsb = 0x0f & tpmsb;
tem = ((tpmsb << 8) | tplsb);//温度拼接
hum = ((humil << 8) | humim);//湿度拼接i
//内核不能进行浮点运算,把值放大100倍
gxht3w_result[0] = 17500 * tem / 4095 - 4500;
gxht3w_result[1] = (100 * hum / 655);
if(gxht3w_result[0]<17501 && gxht3w_result[1]<10001)
{
ret = copy_to_user(buf, gxht3w_result, size);
if (ret != 0)
{
printk("copy_to_user error!");
}
}
}
return ret;
}
static int _drv_release(struct inode* node, struct file* file)
{
/*释放锁*/
// up(&sem);
printk("gxht3w release\n");
return 0;
}
static struct file_operations drv_file_ops = {
.owner = THIS_MODULE,
.open = _drv_open,
.read = _drv_read,
.release = _drv_release,
};
/* 设备树的匹配列表 */
static struct of_device_id dts_match_table[] = {
{.compatible = DEV_DTS_COMPATIBLE, }, /* 通过设备树来匹配 */
};
static int _driver_probe(struct platform_device* pdev)
{
int err;
struct device* ds_dev;
struct device_node *gxhtc3_device_node; //设备树节点
gxhtc3_device_node = of_find_node_by_path("/gxht3w");
gxht3w_dev.gpio = of_get_named_gpio(gxhtc3_device_node, "gpios", 0);
err = gpio_request(gxht3w_dev.gpio, DEV_PIN_DTS_NAME);
if (err)
{
printk("gpio_request gpio is failed!\n");
return -EINVAL;
}
printk("gxht3w gpio = %d \n",gxht3w_dev.gpio);
/* 内核自动分配设备号 */
err = alloc_chrdev_region(&gxht3w_dev.dev_no, 0, 1, DEV_NAME);
if (err < 0) {
pr_err("Error: failed to register mbochs_dev, err: %d\n", err);
return err;
}
cdev_init(&gxht3w_dev.chrdev, &drv_file_ops);
cdev_add(&gxht3w_dev.chrdev, gxht3w_dev.dev_no, 1);
gxht3w_dev.class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(gxht3w_dev.class)) {
err = PTR_ERR(gxht3w_dev.class);
goto failed1;
}
/* 创建设备节点 */
ds_dev = device_create(gxht3w_dev.class, NULL, gxht3w_dev.dev_no, NULL, DEV_NAME);
if (IS_ERR(ds_dev)) { /* 判断指针是否合法 */
err = PTR_ERR(ds_dev);
goto failed2;
}
printk("gxht3w probe success\r\n");
return 0;
failed2:
device_destroy(gxht3w_dev.class, gxht3w_dev.dev_no);
class_destroy(gxht3w_dev.class);
failed1:
unregister_chrdev_region(gxht3w_dev.dev_no, 1);
cdev_del(&gxht3w_dev.chrdev);
gpio_free(gxht3w_dev.gpio);
return err;
}
static int _driver_remove(struct platform_device* pdev)
{
device_destroy(gxht3w_dev.class, gxht3w_dev.dev_no);
class_destroy(gxht3w_dev.class);
unregister_chrdev_region(gxht3w_dev.dev_no, 1);
cdev_del(&gxht3w_dev.chrdev);
gpio_free(gxht3w_dev.gpio);
printk(KERN_INFO"GXHT3W remove success\n");
return 0;
}
static struct platform_driver _platform_driver = {
.probe = _driver_probe,
.remove = _driver_remove,
.driver = {
.name = DEV_DTS_COMPATIBLE,
.owner = THIS_MODULE,
.of_match_table = dts_match_table, /* 通过设备树匹配 */
},
};
/* 入口函数 */
static int __init _driver_init(void)
{
int ret;
printk("GXHT3W init %s\n", __FUNCTION__);
ret = platform_driver_register(&_platform_driver); //注册platform驱动
return ret;
}
/* 出口函数 */
static void __exit _driver_exit(void)
{
printk("GXHT3W exit %s\n", __FUNCTION__);
platform_driver_unregister(&_platform_driver);
}
module_init(_driver_init);
module_exit(_driver_exit);
MODULE_AUTHOR("GXHT");
MODULE_LICENSE("GPL");