(四)编译设备树:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/linux-imx-imx_4.1.15_2.0.0_ga$ make dtbs
编译生成的设备树文件为imx6ull-elf1-emmc.dtb,参考《01-0 ELF1、ELF1S开发板_快速启动手册_V1》4.4节单独更新设备树。
编写elf-aht20.c驱动
(一)在驱动中要操作很多芯片相关的寄存器,所以需要先新建一个i2c_aht20.h的头文件,用来定义相关寄存器值。
#ifndef I2C_AHT20_H
#define I2C_AHT20_H
#define AHT20_STATUS_CALI_SHIFT 3 // bit[3] CAL Enable
#define AHT20_STATUS_CALI_MASK (0x1<> AHT20_STATUS_CALI_SHIFT)
// bit[2:0] Reserved
#define AHT20_STATUS_BUSY_SHIFT 7 // bit[7] Busy indication
#define AHT20_STATUS_BUSY_MASK (0x1<> AHT20_STATUS_BUSY_SHIFT)
#define AHT20_CMD_STATUS 0x71
#define AHT20_CMD_RESET 0xBA
#define AHT20_CMD_TRIGGER 0xAC
#define AHT20_CMD_TRIGGER_ARG0 0x33
#define AHT20_CMD_TRIGGER_ARG1 0x00
#define AHT20_CMD_CALIBRATION 0xBE
#define AHT20_CMD_CALIBRATION_ARG0 0x08
#define AHT20_CMD_CALIBRATION_ARG1 0x00
#define AHT20_STARTUP_TIME 20 //ms
#define AHT20_CALIBRATION_TIME 40 //ms
#define AHT20_MEASURE_TIME 75 //ms
#define AHT20_MAX_RETRY 5
#define AHT20_RESOLUTION (1<<20)
#endif
(二)elf-aht20.c文件编写
(1)头文件引用
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include “i2c_aht20.h”
(2)创建相关宏定义和变量
#define DEV_NAME "aht20" /*设备名称*/
#define DEV_CNT (1)
/* Private typedef -----------------------------------------------------------*/
/* aht20设备结构体 */
typedef struct {
dev_t devid; /* 设备号 */
struct cdev cdev; /* cdev */
struct class *class; /* 类 */
struct device *device; /* 设备 */
struct device_node *nd; /*设备节点 */
int major; /*主设备号 */
void *private_data; /* 私有数据 */
unsigned short ir, als, ps; /* 光传感数据 */
}aht20_dev_t;
/* Private variables ---------------------------------------------------------*/
static aht20_dev_t aht20dev;
uint8_t clibrate_arg[] = {AHT20_CMD_CALIBRATION_ARG0, AHT20_CMD_CALIBRATION_ARG1};
uint8_t trigger_arg[] = {AHT20_CMD_TRIGGER_ARG0,AHT20_CMD_TRIGGER_ARG1};
(3)驱动模块的入口和出口
module_init(aht20_driver_init);
module_exit(aht20_driver_exit);
(4)ath20_driver_init和aht20_driver_exit实现
static int __init aht20_driver_init(void)
{
pr_info("aht20 driver init\n");
return i2c_add_driver(&aht20_driver);
}
static void __exit aht20_driver_exit(void)
{
pr_info("aht20 driver exit\n");
i2c_del_driver(&aht20_driver);
}
在入口函数中调用了i2c_add_driver函数,来注册I2C总线驱动程序。在出口函数中调用了i2c_del_driver函数,来注销I2C驱动程序。
i2c_add_driver函数原型如下:
int i2c_add_driver(struct i2c_driver *driver);
该函数接受一个指向struct i2c_driver结构的指针作为参数,该结构包含了驱动程序的相关信息,例如驱动程序的名称、ID表、探测函数等。函数返回一个整数值,表示注册是否成功。如果成功,返回0;如果失败,返回一个负数错误代码。
以下是struct i2c_driver结构体的常见成员:
driver:这是一个指向struct device_driver结构的指针,用于描述I2C驱动程序所属的设备驱动程序。
probe:这是一个函数指针,指向设备探测函数。当一个设备与I2C总线匹配时,该函数会被调用。设备探测函数负责初始化设备并进行必要的配置。
remove:这是一个函数指针,指向设备移除函数。当一个设备从I2C总线上移除时,该函数会被调用。设备移除函数负责释放设备所占用的资源。
id_table:这是一个指向struct i2c_device_id数组的指针,用于描述I2C设备的标识信息。驱动程序可以使用这些标识信息来识别与之匹配的设备。
address_list:这是一个指向unsigned short数组的指针,用于描述驱动程序支持的I2C设备地址列表。驱动程序会使用这些地址来匹配和识别设备。
driver.name:这是一个字符串,表示驱动程序的名称。它在设备和驱动程序之间建立关联。
通过调用i2c_add_driver函数并传入正确配置的struct i2c_driver结构体,可以将I2C总线驱动程序注册到Linux内核,使其能够接收和处理I2C设备的相关操作。
(5)i2c_driver类型结构体定义
struct i2c_driver aht20_driver = {
.probe = aht20_probe,
.remove = aht20_remove,
.id_table = aht20_device_id,
.driver = {
.name = "elf,aht20",
.owner = THIS_MODULE,
.of_match_table = aht20_match_table,
},
};
(6)aht20_match_table实现,用来与设备树中的compatible匹配
static const struct of_device_id aht20_match_table[] = {
{.compatible = "elf,aht20", },
{ },
};
(7)remove函数实现,执行aht20设备的清理操作
static int aht20_remove(struct i2c_client *client)
{
// 销毁设备节点
device_destroy(aht20dev.class, aht20dev.devid);
// 销毁设备类
class_destroy(aht20dev.class);
// 删除字符设备
cdev_del(&aht20dev.cdev);
// 注销字符设备驱动程序
unregister_chrdev_region(aht20dev.devid, DEV_CNT);
return 0;
}
(8)probe函数实现,此处简略描述regmap注册的过程。
static int aht20_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = -1;
// 注册字符设备驱动程序
ret = alloc_chrdev_region(&aht20dev.devid, 0, DEV_CNT, DEV_NAME);
if (ret < 0)
{
printk("fail to alloc aht_dev\n");
goto alloc_err;
}
//初始化字符设备结构体
cdev_init(&aht20dev.cdev, &aht20_chr_dev_fops);
//将字符设备添加到内核中
ret = cdev_add(&aht20dev.cdev, aht20dev.devid, DEV_CNT);
if (ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
// 创建设备类
aht20dev.class = class_create(THIS_MODULE, DEV_NAME);
// 创建设备节点并关联到设备类
aht20dev.device = device_create(aht20dev.class, NULL, aht20dev.devid, NULL, DEV_NAME);
aht20dev.private_data = client;
aht20_init();
return 0;
add_err:
unregister_chrdev_region(aht20dev.devid, DEV_CNT);
printk("\n add_err error! \n");
alloc_err:
return -1;
}
probe函数中实现的就是前面讲到的字符设备的注册流程,注册完成后调用aht20_init()函数,对芯片进行初始化。
(9)定义file_operations类型结构体:
static struct file_operations aht20_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = aht20_open,
.read = aht20_read,
.release = aht20_release,
};
static int aht20_open(struct inode *inode, struct file *filp)
{
filp->private_data = &aht20dev;
return 0;
}
static ssize_t aht20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
float temp,humi;
// uint8_t data[7] = {0};
uint32_t data[2] = {0};
long err = 0;
filp->private_data = &aht20dev;
aht20_get_measure(&data[0]);
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int aht20_release(struct inode *inode, struct file *filp)
{
return 0;
}
(10)操作函数的实现:
aht20_read函数中调用aht20_get_measuer()函数读取温湿度传感器中的数据,然后通过copy_to_user()函数将数据拷贝到用户空间。
(11)aht20_get_measuer()函数定义:
static int aht20_get_measure(uint32_t* RAW)
{
int retval = 0, i = 0;
uint8_t data[7] = {0};
retval = aht20_write_regs(&aht20dev,AHT20_CMD_TRIGGER,trigger_arg,2);
msleep(AHT20_MEASURE_TIME);
aht20_read_data(&aht20dev,data,7);
for (i = 0; AHT20_STATUS_BUSY(data[0]) && i < AHT20_MAX_RETRY; i++) {
printk("AHT20 device busy, retry %d/%d!\r\n", i, AHT20_MAX_RETRY);
msleep(AHT20_MEASURE_TIME);
aht20_read_data(&aht20dev,data,7);
}
if (i >= AHT20_MAX_RETRY) {
printk("AHT20 device always busy!\r\n");
}
uint32_t humiRaw = data[1];
humiRaw = (humiRaw << 8) | data[2];
humiRaw = (humiRaw << 4) | ((data[3] & 0xF0) >> 4);
uint32_t tempRaw = data[3] & 0x0F;
tempRaw = (tempRaw << 8) | data[4];
tempRaw = (tempRaw << 8) | data[5];
RAW[0] = humiRaw;
RAW[1] = tempRaw;
// printk("aht20 humiRAW = %05X, tempRAW = %05X\r\n", humiRaw, tempRaw);
return 0;
}
aht20_get_measuer()函数中又调用了aht20_write_regs()函数写寄存器,aht20_read_data()函数来读取数据。
(12)aht20_write_regs()函数和aht20_read_data()函数的定义:
static s32 aht20_write_regs(aht20_dev_t *dev, u8 reg, u8 *buf, u8 len)
{
u8 byte[256] = {0};
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client*)dev->private_data;
byte[0] = reg; /*!< 寄存器首地址 */
memcpy(&byte[1], buf, len); /*!< 拷贝数据 */
msg.addr = client->addr; /*!< aht20地址 */
msg.flags = 0; /*!< 标记为写数据 */
msg.buf = byte;
msg.len = len + 1; /*!< 要写入数据的长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
static int aht20_read_data(aht20_dev_t *dev, void *val, int len)
{
int ret = 0;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client*)dev->private_data;
/* msg[1]为要读取数据 */
msg[0].addr = client->addr; /*!< aht20地址 */
msg[0].flags = I2C_M_RD; /*!< 标记为读取数据 */
msg[0].buf = val; /*!< 读取数据的缓冲区 */
msg[0].len = len; /*!< 读取数据的长度 */
return i2c_transfer(client->adapter, msg, 1);
}
函数中都调用了i2c_transfer()函数,这是一个用来进行I2C数据传输的函数,原型如下:
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
该函数接受三个参数:
adap:一个指向struct i2c_adapter结构的指针,表示要使用的I2C适配器。
msgs:一个指向struct i2c_msg结构数组的指针,每个结构表示一个I2C传输消息(包括读取和写入操作)。
num:传输消息的数量,即msgs数组中的元素个数。
函数返回一个整数值,表示传输是否成功。如果成功,返回传输的消息数量;如果失败,返回一个负数错误代码。
struct i2c_msg结构体用于描述一个I2C传输消息,定义如下:
struct i2c_msg {
__u16 addr; // 设备地址
__u16 flags; // 消息标志位
__u16 len; // 数据长度
__u8 *buf; // 数据缓冲区
};
该结构包含以下成员:
addr:表示I2C设备的地址。
flags:用于指定消息的标志位,例如读取或写入操作。
len:指定数据缓冲区的长度。
buf:指向数据缓冲区的指针,用于存储要传输的数据。
通过调用i2c_transfer函数,可以将一系列的I2C传输消息发送到指定的I2C设备上,以实现数据的读取和写入操作。
完整的驱动elf-aht20.c源码
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "i2c_aht20.h"
#define DEV_NAME "aht20"
#define DEV_CNT (1)
/* Private typedef -----------------------------------------------------------*/
/* aht20设备结构体 */
typedef struct {
dev_t devid; /*!< 设备号 */
struct cdev cdev; /*!< cdev */
struct class *class; /*!< 类 */
struct device *device; /*!< 设备 */
struct device_node *nd; /*!< 设备节点 */
int major; /*!< 主设备号 */
void *private_data; /*!< 私有数据 */
unsigned short ir, als, ps; /*!< 光传感数据 */
}aht20_dev_t;
/* Private variables ---------------------------------------------------------*/
static aht20_dev_t aht20dev;
uint8_t clibrate_arg[] = {AHT20_CMD_CALIBRATION_ARG0, AHT20_CMD_CALIBRATION_ARG1};
uint8_t trigger_arg[] = {AHT20_CMD_TRIGGER_ARG0,AHT20_CMD_TRIGGER_ARG1};
static s32 aht20_write_regs(aht20_dev_t *dev, u8 reg, u8 *buf, u8 len)
{
u8 byte[256] = {0};
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client*)dev->private_data;
byte[0] = reg; /*!< 寄存器首地址 */
memcpy(&byte[1], buf, len); /*!< 拷贝数据 */
msg.addr = client->addr; /*!< aht20地址 */
msg.flags = 0; /*!< 标记为写数据 */
msg.buf = byte;
msg.len = len + 1; /*!< 要写入数据的长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
static int aht20_read_regs(aht20_dev_t *dev, u8 reg, void *val, int len)
{
int ret = 0;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client*)dev->private_data;
/* msg[0]为发送要读取的首地址 */
msg[0].addr = client->addr; /*!< aht20地址 */
msg[0].flags = 0; /*!< 标记为发送数据 */
msg[0].buf = ® /*!< 读取的首地址 */
msg[0].len = 1; /*!< reg长度 */
/* msg[1]为要读取数据 */
msg[1].addr = client->addr; /*!< aht20地址 */
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 ret = -EREMOTEIO;
return ret;
}
static void aht20_write_reg(aht20_dev_t *dev, u8 reg, u8 data)
{
u8 buf = 0;
buf = data;
aht20_write_regs(dev, reg, &buf, 1);
}
static unsigned char aht20_read_reg(aht20_dev_t *dev, u8 reg)
{
u8 data = 0;
aht20_read_regs(dev, reg, &data, 1);
return data;
#if 0
struct i2c_client *client = (struct i2c_client *)dev->private_data;
return i2c_smbus_read_byte_data(client, reg);
#endif
}
static int aht20_read_data(aht20_dev_t *dev, void *val, int len)
{
int ret = 0;
struct i2c_msg msg[2];
struct i2c_client *client = (struct i2c_client*)dev->private_data;
/* msg[1]为要读取数据 */
msg[0].addr = client->addr; /*!< aht20地址 */
msg[0].flags = I2C_M_RD; /*!< 标记为读取数据 */
msg[0].buf = val; /*!< 读取数据的缓冲区 */
msg[0].len = len; /*!< 读取数据的长度 */
return i2c_transfer(client->adapter, msg, 1);
}
static int aht20_get_measure(uint32_t* RAW)
{
int retval = 0, i = 0;
uint8_t data[7] = {0};
retval = aht20_write_regs(&aht20dev,AHT20_CMD_TRIGGER,trigger_arg,2);
msleep(AHT20_MEASURE_TIME);
aht20_read_data(&aht20dev,data,7);
for (i = 0; AHT20_STATUS_BUSY(data[0]) && i < AHT20_MAX_RETRY; i++) {
printk("AHT20 device busy, retry %d/%d!\r\n", i, AHT20_MAX_RETRY);
msleep(AHT20_MEASURE_TIME);
aht20_read_data(&aht20dev,data,7);
}
if (i >= AHT20_MAX_RETRY) {
printk("AHT20 device always busy!\r\n");
}
uint32_t humiRaw = data[1];
humiRaw = (humiRaw << 8) | data[2];
humiRaw = (humiRaw << 4) | ((data[3] & 0xF0) >> 4);
uint32_t tempRaw = data[3] & 0x0F;
tempRaw = (tempRaw << 8) | data[4];
tempRaw = (tempRaw << 8) | data[5];
RAW[0] = humiRaw;
RAW[1] = tempRaw;
// printk("aht20 humiRAW = %05X, tempRAW = %05X\r\n", humiRaw, tempRaw);
return 0;
}
/* send reset cmd */
static int aht20_write_reset(aht20_dev_t *dev)
{
u8 byte[256] = {0};
struct i2c_msg msg;
struct i2c_client *client = (struct i2c_client*)dev->private_data;
byte[0] = AHT20_CMD_RESET; /*!< 寄存器首地址 */
msg.addr = client->addr; /*!< aht20地址 */
msg.flags = 0; /*!< 标记为写数据 */
msg.buf = byte; /*!< 要写入的数据缓冲区 */
msg.len = 1; /*!< 要写入数据的长度 */
return i2c_transfer(client->adapter, &msg, 1);
}
static int aht20_init(void)
{
unsigned char status;
int retval;
float temp, humi;
uint8_t RAW[7];
status = aht20_read_reg(&aht20dev, AHT20_CMD_STATUS);
if (AHT20_STATUS_BUSY(status) || !AHT20_STATUS_CALI(status)) {
retval = aht20_write_reset(&aht20dev);
msleep(AHT20_STARTUP_TIME);
retval = aht20_write_regs(&aht20dev,AHT20_CMD_CALIBRATION,clibrate_arg,2);
msleep(AHT20_CALIBRATION_TIME);
return retval;
}
aht20_get_measure(&RAW[0]);
}
static int aht20_open(struct inode *inode, struct file *filp)
{
filp->private_data = &aht20dev;
return 0;
}
static ssize_t aht20_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
float temp,humi;
// uint8_t data[7] = {0};
uint32_t data[2] = {0};
long err = 0;
filp->private_data = &aht20dev;
aht20_get_measure(&data[0]);
err = copy_to_user(buf, data, sizeof(data));
return 0;
}
static int aht20_release(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations aht20_chr_dev_fops =
{
.owner = THIS_MODULE,
.open = aht20_open,
.read = aht20_read,
.release = aht20_release,
};
static int aht20_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
int ret = -1;
ret = alloc_chrdev_region(&aht20dev.devid, 0, DEV_CNT, DEV_NAME);
if (ret < 0)
{
printk("fail to alloc aht_dev\n");
goto alloc_err;
}
// aht20_chr_dev.owner = THIS_MODULE;
cdev_init(&aht20dev.cdev, &aht20_chr_dev_fops);
ret = cdev_add(&aht20dev.cdev, aht20dev.devid, DEV_CNT);
if (ret < 0)
{
printk("fail to add cdev\n");
goto add_err;
}
aht20dev.class = class_create(THIS_MODULE, DEV_NAME);
aht20dev.device = device_create(aht20dev.class, NULL, aht20dev.devid, NULL, DEV_NAME);
aht20dev.private_data = client;
aht20_init();
return 0;
add_err:
unregister_chrdev_region(aht20dev.devid, DEV_CNT);
printk("\n add_err error! \n");
alloc_err:
return -1;
}
static int aht20_remove(struct i2c_client *client)
{
device_destroy(aht20dev.class, aht20dev.devid);
class_destroy(aht20dev.class);
cdev_del(&aht20dev.cdev);
unregister_chrdev_region(aht20dev.devid, DEV_CNT);
return 0;
}
static const struct i2c_device_id aht20_device_id[] = {
{"elf,aht20", 0},
{ }
};
/*定义设备树匹配表*/
static const struct of_device_id aht20_match_table[] = {
{.compatible = "elf,aht20", },
{ },
};
/*定义i2c设备结构体*/
struct i2c_driver aht20_driver = {
.probe = aht20_probe,
.remove = aht20_remove,
.id_table = aht20_device_id,
.driver = {
.name = "elf,aht20",
.owner = THIS_MODULE,
.of_match_table = aht20_match_table,
},
};
static int __init aht20_driver_init(void)
{
pr_info("aht20 driver init\n");
return i2c_add_driver(&aht20_driver);
}
static void __exit aht20_driver_exit(void)
{
pr_info("aht20 driver exit\n");
i2c_del_driver(&aht20_driver);
}
module_init(aht20_driver_init);
module_exit(aht20_driver_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("bkxr@outlook.com");
MODULE_DESCRIPTION("aht20 sensor driver");
编译
复制7.7.3驱动中的Makefile文件,将其中的platform_led.o修改为elf-aht20.o,效果如下:
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/07_I2C驱动-aht20/aht20$ make
将编译生成的elf-aht20.ko模块拷贝到开发板。
编写测试应用源码aht20_app.c
测试源码中循环读取驱动传到用户空间的数据:
#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "sys/ioctl.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include
#include
#include
#include
#include
#define AHT20_DEV "/dev/aht20"
int main(int argc, char *argv[])
{
int fd;
unsigned int databuf[2];
int c1,t1;
float hum,temp;
int ret = 0;
fd = open(AHT20_DEV, O_RDWR);
if(fd < 0) {
printf("can't open file %s\r\n", AHT20_DEV);
return -1;
}
while (1) {
ret = read(fd, databuf, sizeof(databuf));
if(ret == 0) { /* ?????? */
c1 = databuf[0]*1000/1024/1024; //
t1 = databuf[1] *200*10/1024/1024-500;
hum = (float)c1/10.0;
temp = (float)t1/10.0;
printf("hum = %0.2f temp = %0.2f \r\n",hum,temp);
usleep(500000);
}
}
close(fd);
return 0;
}
编译应用
. /opt/fsl-imx-x11/4.1.15-2.0.0/environment-setup-cortexa7hf-neon-poky-linux-gnueabi
elf@ubuntu:~/work/test/07_I2C驱动-aht20/aht20_app$ $CC aht20_app.c -o aht20_\app
将编译好的测试应用拷贝到开发板中。
测试
root@ELF1:~# insmod elf-aht20.ko
aht20 driver init
root@ELF1:~# ./aht20_app
hum = 45.60 temp = 30.60
hum = 45.60 temp = 30.60
hum = 45.70 temp = 30.60
hum = 45.70 temp = 30.60
hum = 45.60 temp = 30.60
root@ELF1:~# rmmod elf-aht20.ko
aht20 driver exit