实验背景
由于官方内核i2c的BSP有问题(怀疑是设备树这块),本次实验将不通过设备树来驱动aht20(i2c)模块,大致的操作过程如下:
- 模块连接,查看aht20设备地址
- 编写device驱动,通过i2c_get_adapter注册i2c_client设备
- 编写i2c_driver驱动,需要匹device部分的i2c_device_id
- 编写测试用例,读取两个寄存器地址的温湿度数值
- 运行测试用例,检查传感器数值是否正常
模块连接
连接aht20温湿度传感器
使用i2c-tools查看i2c0总线上的从设备地址,可以看到为0x38
驱动代码
device驱动:大致的流程就是不通过设备树来注册一个i2c_client,i2c_get_adapter(0)表示i2c0,要定义一个DEV_ID_NAME
作为id
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/i2c.h>
static struct i2c_board_info aht20;
static struct i2c_client * client;
#define DEV_ID_NAME "loongson,aht20"
static const unsigned short addrs[] = {0x38, I2C_CLIENT_END};
static int dev_init(void)
{
struct i2c_adapter *adapter = NULL;
memset(&aht20, 0, sizeof(struct i2c_board_info));
strlcpy(aht20.type, DEV_ID_NAME, I2C_NAME_SIZE);
adapter = i2c_get_adapter(0);
client = i2c_new_probed_device(adapter, &aht20, addrs, NULL);
i2c_put_adapter(adapter);
if (client)
{
return 0;
}
else
{
return -ENODEV;
}
}
static void dev_exit(void)
{
i2c_unregister_device(client);
}
module_init(dev_init);
module_exit(dev_exit);
MODULE_LICENSE("GPL");
driver驱动:跟一般的设备驱动没有很大差别,这里match的i2c_device_id
要和上面的device驱动保持一致
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/i2c.h>
#include <linux/input.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/gpio.h>
#include <linux/irq.h>
#define DEVICE_CNT 1
#define DEVICE_NAME "aht20"
#define DEV_ID_NAME "loongson,aht20"
struct aht20_dev {
struct i2c_client *client;
dev_t dev_id;
struct cdev cdev;
struct class *class;
struct device *device;
};
static struct i2c_client *my_client;
static int aht20_read_regs(struct aht20_dev *dev, u8 reg, void *val, int len)
{
int ret;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client *)dev->client;
msg[0].addr = client->addr;
msg[0].flags = 0;
msg[0].buf = ®
msg[0].len = 1;
msg[1].addr = client->addr;
msg[1].flags = I2C_M_RD;
msg[1].buf = val;
msg[1].len = len;
ret = i2c_transfer(client->adapter, msg, 2);
if(ret == 2) {
ret = 0;
} else {
printk("i2c rd failed=%d reg=%06x len=%d\n",ret, reg, len);
ret = -EREMOTEIO;
}
return ret;
}
static s32 aht20_write_regs(struct aht20_dev *dev, u8 reg, u8 *buf, u8 len)
{
u8 b[256];
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client *)dev->client;
b[0] = reg;
memcpy(&b[1],buf,len);
msg.addr = client->addr;
msg.flags = 0;
msg.buf = b;
msg.len = len + 1;
return i2c_transfer(client->adapter, &msg, 1);
}
static unsigned char aht20_read_reg(struct aht20_dev *dev, u8 reg)
{
u8 data = 0;
aht20_read_regs(dev, reg, &data, 1);
return data;
}
void ATH20_Read_CTdata(struct aht20_dev *dev, uint32_t *ct)
{
uint32_t RetuData = 0;
uint16_t cnt = 0;
uint8_t Data[10];
uint8_t tmp[10];
uint8_t val = 0;
tmp[0] = 0x33;
tmp[1] = 0x00;
aht20_write_regs(dev, 0xAC, tmp, 2);
mdelay(75);//等待75ms
while((((val = aht20_read_reg(dev, 0x00))&0x80) == 0x80))
{
mdelay(1);
if(cnt++ >= 100)
{
break;
}
}
aht20_read_regs(dev, 0x00, Data, 7);
RetuData = 0;
RetuData = (RetuData|Data[1]) << 8;
RetuData = (RetuData|Data[2]) << 8;
RetuData = (RetuData|Data[3]);
RetuData = RetuData >> 4;
ct[0] = RetuData;
RetuData = 0;
RetuData = (RetuData|Data[3]) << 8;
RetuData = (RetuData|Data[4]) << 8;
RetuData = (RetuData|Data[5]);
RetuData = RetuData&0xfffff;
ct[1] = RetuData;
}
void aht20_readdata(struct aht20_dev *dev, uint32_t *CT_data)
{
ATH20_Read_CTdata(dev, CT_data);
}
uint8_t ATH20_Read_Cal_Enable(struct aht20_dev *dev)
{
uint8_t val = aht20_read_reg(dev, 0x00);
if((val & 0x68) == 0x08)
return 1;
else
return 0;
}
static int aht20_open(struct inode *inode, struct file *filp)
{
uint8_t count;
struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
struct aht20_dev *aht20 = container_of(cdev, struct aht20_dev, cdev);
uint8_t tmp[10];
mdelay(40);
tmp[0] = 0x08;
tmp[1] = 0x00;
aht20_write_regs(aht20, 0xBE, tmp, 2);
mdelay(500);
count = 0;
while(ATH20_Read_Cal_Enable(aht20) == 0)
{
aht20_write_regs(aht20, 0xBA, tmp, 0);
mdelay(200);
aht20_write_regs(aht20, 0xBE, tmp, 2);
count++;
if(count >= 10)
return 0;
mdelay(500);
}
return 0;
}
static ssize_t aht20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
uint32_t data[2];
long err = 0;
struct cdev *cdev = filp->f_path.dentry->d_inode->i_cdev;
struct aht20_dev *dev = container_of(cdev, struct aht20_dev, cdev);
aht20_readdata(dev, data);
err = copy_to_user(buf, data, sizeof(data));
return err;
}
static int aht20_release(struct inode *inode, struct file *filp)
{
return 0;
}
static const struct file_operations aht20_ops = {
.owner = THIS_MODULE,
.open = aht20_open,
.read = aht20_read,
.release = aht20_release,
};
static const struct i2c_device_id aht20_dev_id[] = {
{ DEV_ID_NAME, 0 },
{ }
};
static int i2c_drv_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret;
struct aht20_dev *aht20;
my_client = client;
aht20 = devm_kzalloc(&client->dev, sizeof(*aht20), GFP_KERNEL);
if(!aht20)
return -ENOMEM;
ret = alloc_chrdev_region(&aht20->dev_id, 0, DEVICE_CNT, DEVICE_NAME);
if(ret < 0) {
pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", DEVICE_NAME, ret);
return -ENOMEM;
}
aht20->cdev.owner = THIS_MODULE;
cdev_init(&aht20->cdev, &aht20_ops);
ret = cdev_add(&aht20->cdev, aht20->dev_id, DEVICE_CNT);
if(ret < 0) {
goto del_unregister;
}
aht20->class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(aht20->class)) {
goto del_cdev;
}
aht20->device = device_create(aht20->class, NULL, aht20->dev_id, NULL, DEVICE_NAME);
if (IS_ERR(aht20->device)) {
goto destroy_class;
}
aht20->client = client;
i2c_set_clientdata(client,aht20);
return 0;
destroy_class:
device_destroy(aht20->class, aht20->dev_id);
del_cdev:
cdev_del(&aht20->cdev);
del_unregister:
unregister_chrdev_region(aht20->dev_id, DEVICE_CNT);
return -EIO;
}
static int i2c_drv_remove(struct i2c_client *c)
{
struct aht20_dev *aht20 = i2c_get_clientdata(c);
cdev_del(&aht20->cdev);
unregister_chrdev_region(aht20->dev_id, DEVICE_CNT);
device_destroy(aht20->class, aht20->dev_id);
class_destroy(aht20->class);
return 0;
}
static struct i2c_driver aht20_drv = {
.driver = {
.name = "aht20_drv",
.owner = THIS_MODULE,
},
.probe = i2c_drv_probe,
.remove = i2c_drv_remove,
.id_table = aht20_dev_id,
};
static int __init i2c_drv_init(void)
{
i2c_add_driver(&aht20_drv);
return 0;
}
static void __exit i2c_drv_exit(void)
{
i2c_del_driver(&aht20_drv);
}
module_init(i2c_drv_init);
module_exit(i2c_drv_exit);
MODULE_LICENSE("GPL");
Makefile文件
obj-m += aht20_dev.o aht20_drv.o
KDIR:=/home/asensing/loongson/linux-4.19
ARCH=loongarch
CROSS_COMPILE=loongarch64-linux-gnu-
PWD?=$(shell pwd)
all:
make -C $(KDIR) M=$(PWD) modules
测试用例
#include "stdio.h"
#include "unistd.h"
#include "fcntl.h"
#define DEV_NAME "/dev/aht20"
int main()
{
int fd, temp, humi;
unsigned int data[2];
fd = open(DEV_NAME, 0);
if(fd < 0)
{
printf("Open %s failed\n", DEV_NAME);
return 1;
}
else
{
printf("Open %s success!\n", DEV_NAME);
}
while(1)
{
read(fd, &data, sizeof(data));
humi = data[0] * 1000.0 / 1024 / 1024;
temp = data[1] * 2000.0 / 1024 / 1024 - 500;
printf("temp : %d.%d℃, humi : %d.%d%%\n", (temp/10), (temp%10), (humi/10),(humi%10));
sleep(1);
}
close(fd);
return 0;
}
构建脚本
export PATH=$PATH:/home/asensing/loongson/loongson-gnu-toolchain-8.3-x86_64-loongarch64-linux-gnu-rc1.3-1/bin
make -j8
loongarch64-linux-gnu-gcc test.c -o test
FILE=$PWD/$(basename $PWD).ko
scp aht20_dev.ko aht20_drv.ko test root@192.168.137.148:/home/root
实验结果
insmod相关驱动、运行测试用例即可查看环境中的温湿度数值
参考
介绍:AHT20集成式温湿度传感器-温湿度传感器-温湿度传感器 温湿度芯片 温湿度变送器模块 气体传感器 流量传感器 广州奥松电子股份有限公司 (aosong.com)
例程:http://www.aosong.com/userfiles/files/file/20240119/20240119105503_8338.zip