CSDN仅用于增加百度收录权重,排版未优化,日常不维护。请访问:www.hceng.cn 查看、评论。
本博文对应地址: https://hceng.cn/2019/01/18/AM335X——hwmon和input子系统/#more
记录两个SPI设备分别采用hwmon子系统和input子系统。
刚开始学Linux驱动的时候,就看了input子系统,现在都忘得差不多了,不过回忆起来也还快,这里再记录一下。
为什么要用各种子系统框架,就目前的理解,一是为了向应用层提供统一的接口,二是简化了编写驱动的流程。
各种子系统它们是通过一层一层的函数传递与封装,实现了设备驱动的注册,定义了file_operations
结构体里面的各种函数操作,不需要在单独的设备驱动代码中进行注册、定义,直接调用相应的的子系统即可。
1. hwmon子系统简介
hwmon
即硬件监控(Hardware monitor
),它是用于检测设备状态的一类传感器设备接口,比如CPU温度、风扇转速、模数转换等。
Hwmon
子系统的核心代码是drivers/hwmon/hwmon.c
。
通过同路径下的Kconfig
文件,可以得知它在make menuconfig
中的配置名字为Hardware Monitoring support
。
通过hwmon.c
中的EXPORT_SYMBOL_GPL()
符号,可知对外提供如下几组接口函数:
hwmon_device_register_with_groups() / hwmon_device_register()
hwmon_device_unregister()
devm_hwmon_device_register_with_groups()
devm_hwmon_device_unregister()
1.1 AD7705简介
AD7705是十六位分辨率的A/D转换器,2个通道全差分模拟输入。
内部有8个寄存器,常用的就Communication Register
、Setup register
、Clock register
、Data register
。
在每次做任何操作前,都要写Communication Register
来设置即将操作的是哪一个寄存器、是读还是写操作、操作哪一个通道。
其操作流程如下:
- 拉低复位引脚,硬件复位;
- 在至少32个时钟周期里连续发送高脉冲,以同步时钟;
- 配置AD7705时钟(时钟源、分频系数等);
- 自校准,并等待就绪引脚拉低;
- 从数据寄存器里读取数据;
1.2 完整代码及效果
-
设备树:
{% codeblock lang:dts [am335x-evm.dts] %}
spidev@0 {
compatible = “titan,ad7705”;
spi-max-frequency = <2500000>;
reg = <2>;
};
{% endcodeblock %} -
驱动代码:
{% codeblock lang:c [ad7705_drv.c] %}
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/spi/spi.h>
#include <linux/hwmon.h>
#include <linux/hwmon-sysfs.h>
#define CHANNEL_NUM (2) //AD7705 channel number
#define DRDY_PIN (12) //AD7705 DRDY Pin(GPIO0_12)
#define RESET_PIN (13) //AD7705 RESET Pin(GPIO0_13)
//Communication Register
enum
{
REG_COMM = (0 << 4), //Communication Register RS2:RS1:RS0 = [0:0:0]
REG_SETUP = (1 << 4), //Setup register RS2:RS1:RS0 = [0:0:1]
REG_CLOCK = (2 << 4), //Clock register RS2:RS1:RS0 = [0:1:0]
REG_DATA = (3 << 4), //Data register RS2:RS1:RS0 = [0:1:1]
REG_TEST = (4 << 4), //Test register RS2:RS1:RS0 = [1:0:0]
REG_RESE = (5 << 4), //No operation RS2:RS1:RS0 = [1:0:1]
REG_OFFSET = (6 << 4), //Offset register RS2:RS1:RS0 = [1:1:0]
REG_GAIN = (7 << 4), //Gain register RS2:RS1:RS0 = [1:1:1]
CMD_WRITE = (0 << 3), //write operation
CMD_READ = (1 << 3), //read operation
CH_1 = 0, //Register Pair 0 (AIN1+ AIN1-)
CH_2 = 1, //Register Pair 1 (AIN2+ AIN2-)
CH_3 = 2, //Register Pair 0 (AIN1- AIN1-)
CH_4 = 3 //Register Pair 2 (AIN1- AIN2-)
};
//Setup register
enum
{
MD_NORMAL = (0 << 6), //Normal Mode.
MD_CAL_SELF = (1 << 6), //Self-Calibration
MD_CAL_ZERO = (2 << 6), //Zero-Scale System Calibration
MD_CAL_FULL = (3 << 6), //Full-Scale System Calibration
GAIN_1 = (0 << 3), //Gain 1
GAIN_2 = (1 << 3), //Gain 2
GAIN_4 = (2 << 3), //Gain 4
GAIN_8 = (3 << 3), //Gain 8
GAIN_16 = (4 << 3), //Gain 16
GAIN_32 = (5 << 3), //Gain 32
GAIN_64 = (6 << 3), //Gain 64
GAIN_128 = (7 << 3), //Gain 128
BIPOLAR = (0 << 2), //Bipolar Operation
UNIPOLAR = (1 << 2), //Unipolar Operation
BUF_NO = (0 << 1), //Buffer Control Off
BUF_EN = (1 << 1), //Buffer Control On
FSYNC_0 = (0 << 0), //Filter Synchronization Normal
FSYNC_1 = (1 << 0) //Filter Synchronization Disable
};
//Clock register
enum
{
CLKDIS_0 = (0 << 4), //Master Clock Enable (Use crystal clock source)
CLKDIS_1 = (1 << 4), //Master Clock Disable(Use an external clock source)
CLKDIV_0 = (0 << 3), //Clock Divider 0
CLKDIV_1 = (1 << 3), //Clock Divider 2 (4.9152Mhz/2=2.4576Mhz)
CLK_0 = (0 << 2), //Clock Bit(If master clock 1MHz(CLKDIV = 0) or 2MHz(CLKDIV = 1))
CLK_1 = (1 << 2), //Clock Bit(If master clock 2.4576MHz(CLKDIV = 0) or 4.9152MHz(CLKDIV = 1))
//If Clock Bit = 0
UPDATE_20 = (0 << 0), //Filter Selection Bits
UPDATE_25 = (1 << 0),
UPDATE_100 = (2 << 0),
UPDATE_200 = (3 << 0),
//If Clock Bit = 1
UPDATE_50 = (0 << 0), //Filter Selection Bits
UPDATE_60 = (1 << 0),
UPDATE_250 = (2 << 0),
UPDATE_500 = (3 << 0)
};
struct ad7705 {
struct device *hwmon_dev;
struct mutex lock;
};
//Reset
static void ad7705_reset(void)
{
gpio_direction_output(RESET_PIN, 1);
msleep(1);
gpio_direction_output(RESET_PIN, 0);
msleep(2);
gpio_direction_output(RESET_PIN, 1);
msleep(1);
}
//Synchronous SPI timing
static void ad7705_sync_spi(struct spi_device *spi)
{
u8 tx_buf[6]; //Write logic “1” to DIN for at least 32 clocks
memset(tx_buf, 0xFF, sizeof(tx_buf));
spi_write(spi, tx_buf, sizeof(tx_buf));
msleep(1);
}
//Waiting for DRDY pin signal
static int ad7705_wait_DRDY(void)
{
int i = 0;
int time_cnt = 500*1000;
for (i=0; i<time_cnt; i++)
{
if (0 == gpio_get_value(DRDY_PIN))
break;
udelay(1);
}
if (i >= time_cnt)
return -1;
else
return 0;
}
//Self-Calibration
static void ad7705_calib_self(struct spi_device *spi, u8 channel)
{
u8 tx_buf[2] = {0};
tx_buf[0] = REG_SETUP | CMD_WRITE | channel;
tx_buf[1] = MD_CAL_SELF | GAIN_1 | UNIPOLAR | BUF_EN | FSYNC_0;
spi_write(spi, tx_buf, sizeof(tx_buf));
ad7705_wait_DRDY(); //Waiting for the internal operation to complete, the time is long, about 180ms
msleep(50);
}
//Initialize the specified channel
static void ad7705_config_channel(struct spi_device *spi, u8 channel)
{
u8 tx_buf[2] = {0};
tx_buf[0] = REG_CLOCK | CMD_WRITE | channel;
tx_buf[1] = CLKDIS_0 | CLKDIV_1 | CLK_1 | UPDATE_50;
spi_write(spi, tx_buf, sizeof(tx_buf));
ad7705_calib_self(spi, channel);
}
//Reset and initialize the specified channel
static void ad7705_reset_and_reconfig(struct spi_device *spi)
{
ad7705_reset();
msleep(5);
ad7705_sync_spi(spi);
msleep(5);
ad7705_config_channel(spi, CH_1);
ad7705_config_channel(spi, CH_2);
}
//Read the specified channel value
static int ad7705_read_channel(struct device *dev, u8 channel, u16 *data)
{
struct spi_device *spi = to_spi_device(dev);
struct ad7705 *adc = spi_get_drvdata(spi);
int ret = -1;
u16 value = 0;
u8 tx_buf[1] = {0};
u8 rx_buf[2] = {0};
if (mutex_lock_interruptible(&adc->lock))
return -ERESTARTSYS;
if (ad7705_wait_DRDY() < 0)
{
printk(KERN_ERR "[%s] ad7705_wait_DRDY() time out.\n", __FUNCTION__);
goto fail;
}
tx_buf[0] = REG_DATA | CMD_READ | channel;
ret = spi_write_then_read(spi, tx_buf, sizeof(tx_buf), rx_buf, sizeof(rx_buf));
//printk("channel:%d rx_buf[0]=%d rx_buf[1]=%d \n", channel, rx_buf[0], rx_buf[1]);
if (0 > ret)
{
printk(KERN_ERR "[%s] ad7705_read_channel() fail. ret=%d\n", __FUNCTION__, ret);
goto fail;
}
value = (rx_buf[0] << 8) + rx_buf[1];
#if 0
if (0xFFFF == value)
{
ret = -1;
goto fail;
}
#endif
*data = value;
fail:
mutex_unlock(&adc->lock);
return ret;
}
//sysfs hook function
static ssize_t ad7705_get_sensor_value(struct device *dev, struct device_attribute *devattr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
u16 ad = 0;
int i, ret = -1;
int vol1, vol2;
//After switching channels, the value of the other channel is read for the first time.
for (i=0; i<2; i++)
{
ret = ad7705_read_channel(dev, attr->index, &ad);
continue;
if (0 > ret)
{
ad7705_reset_and_reconfig(spi);
return ret;
}
}
#if 0
ret = sprintf(buf, “%u\n”, ad);
#else
vol1 = ad5/65535; //Voltage integral part
vol2 = (ad51000/65535) - (vol11000);//Voltage fraction part
ret = sprintf(buf, “vol = %d.%dV\n”,vol1, vol2);
#endif
return ret;
}
static struct sensor_device_attribute ad_input[] = {
SENSOR_ATTR(ad7705_ch1, S_IRUGO, ad7705_get_sensor_value, NULL, CH_1),
SENSOR_ATTR(ad7705_ch2, S_IRUGO, ad7705_get_sensor_value, NULL, CH_2),
};
static int ad7705_probe(struct spi_device *spi)
{
struct ad7705 *adc = NULL;
int i, status;
adc = kzalloc(sizeof *adc, GFP_KERNEL);
if (!adc)
return -ENOMEM;
mutex_init(&adc->lock);
mutex_lock(&adc->lock);
spi_set_drvdata(spi, adc);
for (i=0; i<CHANNEL_NUM; i++)
{
status = device_create_file(&spi->dev, &ad_input[i].dev_attr);
if (status)
{
dev_err(&spi->dev, "device_create_file() failed.\n");
goto fail_crete_file;
}
}
adc->hwmon_dev = hwmon_device_register(&spi->dev);
if (IS_ERR(adc->hwmon_dev))
{
dev_err(&spi->dev, "hwmon_device_register() fail.\n");
status = PTR_ERR(adc->hwmon_dev);
goto fail_crete_file;
}
status = gpio_request(DRDY_PIN, "ad7705_drdy"); //ad7705 DRDY Pin
if (status)
{
dev_err(&spi->dev, "gpio_request(AD705_DRDY_PIN) fail.\n");
goto fail_device_register;
}
gpio_direction_input(DRDY_PIN);
status = gpio_request(RESET_PIN, "ad7705_reset"); //ad7705 RESET Pin
if (status)
{
dev_err(&spi->dev, "gpio_request(RESET_PIN) fail.\n");
goto fail_request_drdy_pin;
}
gpio_direction_output(RESET_PIN, 1);
ad7705_reset_and_reconfig(spi);
mutex_unlock(&adc->lock);
return 0;
fail_request_drdy_pin:
gpio_free(DRDY_PIN);
fail_device_register:
hwmon_device_unregister(adc->hwmon_dev);
fail_crete_file:
for (i–; i>=0; i–)
device_remove_file(&spi->dev, &ad_input[i].dev_attr);
spi_set_drvdata(spi, NULL);
mutex_unlock(&adc->lock);
kfree(adc);
return status;
}
static int ad7705_remove(struct spi_device *spi)
{
int i;
struct ad7705 *adc = spi_get_drvdata(spi);
mutex_lock(&adc->lock);
gpio_free(DRDY_PIN);
gpio_free(RESET_PIN);
hwmon_device_unregister(adc->hwmon_dev);
for (i=0; i<CHANNEL_NUM; i++)
device_remove_file(&spi->dev, &ad_input[i].dev_attr);
spi_set_drvdata(spi, NULL);
mutex_unlock(&adc->lock);
kfree(adc);
return 0;
}
static const struct of_device_id of_match_spi[] = {
{ .compatible = “titan,ad7705”, .data = NULL },
{ /* sentinel */ }
};
static struct spi_driver ad7705_driver = {
.driver = {
.name = “ad7705”,
.owner = THIS_MODULE,
.of_match_table = of_match_spi,
},
.probe = ad7705_probe,
.remove = ad7705_remove,
};
module_spi_driver(ad7705_driver);
MODULE_LICENSE(“GPL”);
MODULE_AUTHOR(“hceng huangcheng.job@foxmail.com”);
MODULE_DESCRIPTION(“TI am335x board spi device: ad7705 driver.”);
MODULE_VERSION(“v1.0”);
{% endcodeblock %}
- 测试代码:
因为是子系统的关系,可以在应用层使用cat /sys/class/hwmon/hwmon0/device/ad7705_ch1
直接得到结果。
这里再使用该节点,写了个应用程序,以便进行连续访问:
{% codeblock lang:c [ad7705_app.c] %}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
void read_channel(char *dev_file_path)
{
int fd = 0;
int ret = 0;
unsigned char buff[128] = {0};
fd = open(dev_file_path, O_RDONLY);
if (-1 == fd)
{
printf("Can't open device file fail\n");
return ;
}
memset(buff, 0, 128);
ret = read(fd, buff, 128);
if (0 > ret)
{
printf("Can't read data\n");
}
printf("%s", buff);
close(fd);
}
int main(void)
{
char ch1_path[] = {"/sys/class/hwmon/hwmon0/device/ad7705_ch1"};
char ch2_path[] = {"/sys/class/hwmon/hwmon0/device/ad7705_ch2"};
//char ch1_path[] = {"/sys/bus/spi/drivers/ad7705/spi1.2/ad7705_ch1"};
//char ch2_path[] = {"/sys/bus/spi/drivers/ad7705/spi1.2/ad7705_ch2"};
while (1)
{
read_channel(ch1_path);
usleep(1000*1000);
read_channel(ch2_path);
usleep(1000*1000);
}
}
{% endcodeblock %}
值得注意的是,通过read()
返回的数据不再是整型数据,而是字符串,且返回的字符串样式,在驱动里定义。
- 效果:
cat /sys/class/hwmon/hwmon0/device/ad7705_ch1
vol = 2.1V
cat /sys/class/hwmon/hwmon0/device/ad7705_ch2
vol = 0.0V
./ad7705_app
vol = 2.1V
vol = 0.0V
1.3 详细分析
1.3.1 驱动与设备树匹配
ad7705_drv.c
和am335x-evm.dts
各自中的compatible
属性名字相同时,即调用probe()
函数,进入一切的开端。
1.3.2 设置结构体
这里定义了一个ad7705
的结构体,包含设备指针和互斥锁。
{% codeblock lang:c %}
struct ad7705 {
struct device *hwmon_dev;
struct mutex lock;
};
{% endcodeblock %}
在probe()
里定义和分配该结构体:
{% codeblock lang:c %}
struct ad7705 *adc = NULL;
adc = kzalloc(sizeof *adc, GFP_KERNEL);
if (!adc)
return -ENOMEM;
{% endcodeblock %}
补充下内核中内存申请的相关知识:
内核中常用的内存申请函数有:kmalloc()
、kzalloc()
、vmalloc()
:
void *kmalloc(size_t size, gfp_t flags);
void kfree(const void *objp);
kmalloc()
申请的内存位于物理内存映射区域,而且在物理上也是连续的,它们与真实的物理地址只有一个固定的偏移,因为存在较简单的转换关系,所以对申请的内存大小有限制,不能超过128KB。- 常用的flags:
GFP_ATOMIC
—— 分配内存的过程是一个原子过程,分配内存的过程不会被(高优先级进程或中断)打断;
GFP_KERNEL
—— 正常分配内存;
GFP_DMA
—— 给 DMA 控制器分配内存,需要使用该标志(DMA要求分配虚拟地址和物理地址连续;
void *kzalloc(size_t size, gfp_t flags);
void kfree(const void *objp);
kzalloc()
就是调用的kmalloc()
,多加了个__GFP_ZERO
标志,即分配内存的时候还会将其清零;- 常用的flags:
同kmalloc()
。
void *vmalloc(unsigned long size);
void vfree(const void *addr);
vmalloc()
会在虚拟内存空间给出一块连续的内存区,但这片连续的虚拟内存在物理内存中并不一定连续。由于vmalloc()
没有保证申请到的是连续的物理内存,因此对申请的内存大小没有限制,如果需要申请较大的内存空间就可以用此函数了。vmalloc()
和vfree()
可以睡眠,因此不能从中断上下文调用。
1.3.3 设置互斥锁
为了并发控制,加入互斥锁独占资源。
我好像写驱动都没加锁的习惯,以后改正。
互斥锁的使用比较简单,先初始化,再加锁,执行要做的内容,最后解锁。
{% codeblock lang:c %}
mutex_init(&adc->lock);
mutex_lock(&adc->lock);
……
mutex_unlock(&adc->lock);
{% endcodeblock %}
1.3.4 设置私有变量
将定义的ad7705
结构体adc
,保存到spi_device
:
{% codeblock lang:c %}
spi_set_drvdata(spi, adc);
{% endcodeblock %}
后面其它函数就可以通过device
得到spi_device
,再提取到私有数据:
{% codeblock lang:c %}
struct spi_device *spi = to_spi_device(dev);
struct ad7705 *adc = spi_get_drvdata(spi);
{% endcodeblock %}
1.3.5 创建sysfs属性文件
首先解释一下sysfs
:
sysfs
是Linux所提供的一种虚拟档案系统;
在设备模型中,sysfs
文件系统用来表示设备的结构,将设备的层次结构形象的反应到用户空间中,从而可以通过修改sysfs
中的文件属性来修改设备的属性值;
sysfs
被挂载到根目录下的/sys
文件夹下。
使用函数device_create_file()
将会调用到sysfs_create_file()
,将在/sys/class/hwmon/
下创建文件夹,并按照hwmon0
、hwmon1
、hwmon2
等顺序编号
第二个参数是struct device_attribute
结构体,通过定义的sensor_device_attribute
结构体成员传给它。
{% codeblock lang:c %}
static struct sensor_device_attribute ad_input[] = {
SENSOR_ATTR(ad7705_ch1, S_IRUGO, ad7705_get_sensor_value, NULL, CH_1),
SENSOR_ATTR(ad7705_ch2, S_IRUGO, ad7705_get_sensor_value, NULL, CH_2),
};
{% endcodeblock %}
参数含义分别是节点名字、节点访问权限(S_IRUGO
:用户、组、其它成员都可读)、读函数(cat
命令时将调用)、写函数(echo
命令时将调用)、索引。
1.3.6 实现device_attribute
的函数
前面的sensor_device_attribute
只提供了读函数ad7705_get_sensor_value
,并未提供写函数NULL
,这里就只实现读函数即可。
{% codeblock lang:c %}
static ssize_t ad7705_get_sensor_value(struct device *dev, struct device_attribute *devattr, char *buf)
{
struct spi_device *spi = to_spi_device(dev);
struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr);
u16 ad = 0;
int i, ret = -1;
int vol1, vol2;
//After switching channels, the value of the other channel is read for the first time.
for (i=0; i<2; i++)
{
ret = ad7705_read_channel(dev, attr->index, &ad);
continue;
if (0 > ret)
{
ad7705_reset_and_reconfig(spi);
return ret;
}
}
#if 0
ret = sprintf(buf, “%u\n”, ad);
#else
vol1 = ad5/65535; //Voltage integral part
vol2 = (ad51000/65535) - (vol11000);//Voltage fraction part
ret = sprintf(buf, “vol = %d.%dV\n”,vol1, vol2);
#endif
return ret;
}
{% endcodeblock %}
AD7705有两个ADC通道,它们都调用ad7705_get_sensor_value()
,但通过sensor_device_attribute
结构体中的index
成员,将会读指定通道值。
sprintf()
将指定格式的数据传给用户层。
1.3.7 注册hwmon子系统
使用hwmon_device_register
注册hwmon
设备。
1.3.8 引脚GPIO初始化
AD7705除了SPI引脚,还用到了一个就绪引脚和复位引脚。
这两个脚的信息可以从设备树中指定,再获取,这里直接写死在驱动里面了。
{% codeblock lang:c %}
status = gpio_request(DRDY_PIN, “ad7705_drdy”); //ad7705 DRDY Pin
if (status)
{
dev_err(&spi->dev, “gpio_request(AD705_DRDY_PIN) fail.\n”);
goto fail_device_register;
}
gpio_direction_input(DRDY_PIN);
status = gpio_request(RESET_PIN, "ad7705_reset"); //ad7705 RESET Pin
if (status)
{
dev_err(&spi->dev, "gpio_request(RESET_PIN) fail.\n");
goto fail_request_drdy_pin;
}
gpio_direction_output(RESET_PIN, 1);
{% endcodeblock %}
GPIO常见的操作:
1.判断引脚是否合法
int gpio_is_valid(int number);
2.申请该引脚
int gpio_request(unsigned gpio, const char *label);
3.设置为输入还是输出
int gpio_direction_input(unsigned gpio);
int gpio_direction_output(unsigned gpio, int value);
4.获取/设置引脚电平
int gpio_get_value(unsigned gpio);
void gpio_set_value(unsigned gpio, int value);
5.申请作为中断引脚/释放中断
int gpio_to_irq(unsigned gpio);
void free_irq(unsigned int irq, void *dev_id);
6.导出到用户态(/sys/class/gpio/gpioN)
int gpio_export(unsigned gpio, bool direction_may_change);
void gpio_unexport(unsigned gpio);
1.3.9 复位和配置AD7705
调用ad7705_reset()
硬件复位,调用ad7705_sync_spi()
同步下时钟,调用ad7705_config_channel()
配置时钟和自校准。
2. input子系统简介
输入子系统是对不同类型的输入设备进行统一处理的驱动程序。
一个输入事件,如按键,是通过设备驱动层->系统核心层->事件处理层->用户空间的顺序到达用户空间并传给应用程序使用。
![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/190118/1.jpg)
Input子系统由设备驱动层、系统核心层、事件处理层三部分组成。
事件处理层的驱动程序叫事件驱动程序,它负责和应用程序的接口,其内容是标准的,对所有输入设备都可用,就不需要我们编写了。
设备驱动层的驱动程序叫设备驱动程序,它负责和底层输入设备的通信,需要我们自己编写。
2.1 ADXL345简介
ADXL345是一款小而薄的超低功耗3轴加速度计,分辨率为13位,测量范围达±16g。
数字输出数据为16位二进制补码格式,可通过SPI(3线或4线)或I2C数字接口访问。
芯片默认是SPI四线模式,且CPHA=1 CPOL=1
,也就是一般SPI的模式3。
但我使用的ADXL345模块默认是I2C接口,即把12脚SDO
接地了,如果使用SPI接口,需要去掉R4这个0欧电阻。
![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/190118/2.png)
关于ADXL345的使用目的,目前能想到这几个:
1、读取加速度(加速度是矢量,既有大小又有方向),估计某些场合需要这个参数;
2、让ADXL345处于睡眠状态,当被摇动时候,触发最大阈值,从睡眠模式唤醒并从中断引脚输出高电平唤醒主控;
3、根据加速度计算得到角度值,进行屏幕翻转判定等;
Linux内核中自带ADXL345驱动,路径为:
drivers/input/misc/adxl34x-i2c.c
drivers/input/misc/adxl34x-spi.c
drivers/input/misc/adxl34x.c
ADI的官网有对该驱动使用、测试方法有详细介绍。
2.2 代码框架介绍
![](https://blog-image-1257972744.cos.ap-chongqing.myqcloud.com/hceng/blog_image/190118/3.jpg)
如果是设备树,就得修改下adxl34x-spi.c
和am335x-evm.dts
,使各自中的compatible
属性匹配,从而调用adxl34x-spi.c
中的probe()
函数。
如果是C文件,就需要自己创建个board_info.c
文件,填充spi_board_info
内容,并注册。这里注意目前的内核已经没有将注册函数spi_register_board_info()
导出,不能使用,可以通过spi_busnum_to_master()
、spi_new_device()
的方式注册。最后spi_board_info
中的.modalias
和spi_driver
中的.name
名字匹配,则调用adxl34x-spi.c
中的probe()
函数。
{% codeblock lang:c %}
static int spi_info_init(void)
{
struct spi_master *master = NULL;
struct spi_device *device = NULL;
master = spi_busnum_to_master(am335x_spi1_slave_info->bus_num);
if (!master)
return -EINVAL;
device = spi_new_device(master, am335x_spi1_slave_info);
if (!device)
return -EINVAL;
return 0;
//return spi_register_board_info(am335x_spi1_slave_info, ARRAY_SIZE(am335x_spi1_slave_info));
}
{% endcodeblock %}
然后中间的是设备驱动模型中的驱动,它可以看成三部分:
1、在probe()
中调用adxl34x.c
中的adxl34x_probe()
;
2、提供read()
、write()
函数;
3、调用adxl34x.c
的电源管理相关函数,支持电源管理模型;
如果使用I2C接口,其目的还是一样的。
最右边的就是ADXL345的具体操作内容,也是后面详细分析的重点。
2.3 完整代码及效果
- 驱动部分:
adxl34x-spi.c
和adxl34x.c
的完整代码就没必要贴了。
这里使用的设备树提供设备信息,只稍微的修改了点adxl34x-spi.c
的内容:
{% codeblock lang:c %}
static const struct of_device_id of_match_spi[] = {
{ .compatible = “adi,adxl34x”, .data = NULL },
{ /* sentinel */ }
};
static struct spi_driver adxl34x_driver = {
.driver = {
.name = “adxl34x”,
.owner = THIS_MODULE,
.pm = &adxl34x_spi_pm,
.of_match_table = of_match_spi,
},
.probe = adxl34x_spi_probe,
.remove = adxl34x_spi_remove,
};
{% endcodeblock %}
-
设备树部分:
{% codeblock lang:dts %}
spidev@0 {
compatible = “adi,adxl34x”;
spi-max-frequency = <2500000>;
reg = <0>;interrupt-parent = <&gpio0>; interrupts = <12 IRQ_TYPE_LEVEL_HIGH>; spi-cpha = <1>; spi-cpol = <1>;
};
{% endcodeblock %}
AM335X设备树中断的写法有两种,可以参考Documentation/devicetree/bindings/interrupt-controller/interrupts.txt
。
这里interrupt-parent
指定使用那个GPIO中断控制器,interrupts
属性第一个值为该组引脚中的引脚号,第二个为中断触发电平。 -
测试程序:
有两个开源的程序可用来读取input event
事件的值,evtest
与getevent
(这个是从android中提取的)。
如下是evtest
源码,前面的ADI官网链接也使用了该测试程序。
{% codeblock lang:c %}
/*
- $Id: evtest.c,v 1.23 2005/02/06 13:51:42 vojtech Exp $
- Copyright © 1999-2000 Vojtech Pavlik
- Event device test program
*/
/*
- This program is free software; you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation; either version 2 of the License, or
- (at your option) any later version.
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with this program; if not, write to the Free Software
- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
- Should you need to contact me, the author, you can do so either by
- e-mail - mail your message to vojtech@ucw.cz, or by paper mail:
- Vojtech Pavlik, Simunkova 1594, Prague 8, 182 00 Czech Republic
*/
#include <stdint.h>
#include <linux/input.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#ifndef EV_SYN
#define EV_SYN 0
#endif
char *events[EV_MAX + 1] =
{
[0 … EV_MAX] = NULL,
[EV_SYN] = “Sync”, [EV_KEY] = “Key”,
[EV_REL] = “Relative”, [EV_ABS] = “Absolute”,
[EV_MSC] = “Misc”, [EV_LED] = “LED”,
[EV_SND] = “Sound”, [EV_REP] = “Repeat”,
[EV_FF] = “ForceFeedback”, [EV_PWR] = “Power”,
[EV_FF_STATUS] = “ForceFeedbackStatus”,
};
char *keys[KEY_MAX + 1] =
{
[0 … KEY_MAX] = NULL,
[KEY_RESERVED] = “Reserved”, [KEY_ESC] = “Esc”,
[KEY_1] = “1”, [KEY_2] = “2”,
[KEY_3] = “3”, [KEY_4] = “4”,
[KEY_5] = “5”, [KEY_6] = “6”,
[KEY_7] = “7”, [KEY_8] = “8”,
[KEY_9] = “9”, [KEY_0] = “0”,
[KEY_MINUS] = “Minus”, [KEY_EQUAL] = “Equal”,
[KEY_BACKSPACE] = “Backspace”, [KEY_TAB] = “Tab”,
[KEY_Q] = “Q”, [KEY_W] = “W”,
[KEY_E] = “E”, [KEY_R] = “R”,
[KEY_T] = “T”, [KEY_Y] = “Y”,
[KEY_U] = “U”, [KEY_I] = “I”,
[KEY_O] = “O”, [KEY_P] = “P”,
[KEY_LEFTBRACE] = “LeftBrace”, [KEY_RIGHTBRACE] = “RightBrace”,
[KEY_ENTER] = “Enter”, [KEY_LEFTCTRL] = “LeftControl”,
[KEY_A] = “A”, [KEY_S] = “S”,
[KEY_D] = “D”, [KEY_F] = “F”,
[KEY_G] = “G”, [KEY_H] = “H”,
[KEY_J] = “J”, [KEY_K] = “K”,
[KEY_L] = “L”, [KEY_SEMICOLON] = “Semicolon”,
[KEY_APOSTROPHE] = “Apostrophe”, [KEY_GRAVE] = “Grave”,
[KEY_LEFTSHIFT] = “LeftShift”, [KEY_BACKSLASH] = “BackSlash”,
[KEY_Z] = “Z”, [KEY_X] = “X”,
[KEY_C] = “C”, [KEY_V] = “V”,
[KEY_B] = “B”, [KEY_N] = “N”,
[KEY_M] = “M”, [KEY_COMMA] = “Comma”,
[KEY_DOT] = “Dot”, [KEY_SLASH] = “Slash”,
[KEY_RIGHTSHIFT] = “RightShift”, [KEY_KPASTERISK] = “KPAsterisk”,
[KEY_LEFTALT] = “LeftAlt”, [KEY_SPACE] = “Space”,
[KEY_CAPSLOCK] = “CapsLock”, [KEY_F1] = “F1”,
[KEY_F2] = “F2”, [KEY_F3] = “F3”,
[KEY_F4] = “F4”, [KEY_F5] = “F5”,
[KEY_F6] = “F6”, [KEY_F7] = “F7”,
[KEY_F8] = “F8”, [KEY_F9] = “F9”,
[KEY_F10] = “F10”, [KEY_NUMLOCK] = “NumLock”,
[KEY_SCROLLLOCK] = “ScrollLock”, [KEY_KP7] = “KP7”,
[KEY_KP8] = “KP8”, [KEY_KP9] = “KP9”,
[KEY_KPMINUS] = “KPMinus”, [KEY_KP4] = “KP4”,
[KEY_KP5] = “KP5”, [KEY_KP6] = “KP6”,
[KEY_KPPLUS] = “KPPlus”, [KEY_KP1] = “KP1”,
[KEY_KP2] = “KP2”, [KEY_KP3] = “KP3”,
[KEY_KP0] = “KP0”, [KEY_KPDOT] = “KPDot”,
[KEY_ZENKAKUHANKAKU] = “Zenkaku/Hankaku”, [KEY_102ND] = “102nd”,
[KEY_F11] = “F11”, [KEY_F12] = “F12”,
[KEY_RO] = “RO”, [KEY_KATAKANA] = “Katakana”,
[KEY_HIRAGANA] = “HIRAGANA”, [KEY_HENKAN] = “Henkan”,
[KEY_KATAKANAHIRAGANA] = “Katakana/Hiragana”, [KEY_MUHENKAN] = “Muhenkan”,
[KEY_KPJPCOMMA] = “KPJpComma”, [KEY_KPENTER] = “KPEnter”,
[KEY_RIGHTCTRL] = “RightCtrl”, [KEY_KPSLASH] = “KPSlash”,
[KEY_SYSRQ] = “SysRq”, [KEY_RIGHTALT] = “RightAlt”,
[KEY_LINEFEED] = “LineFeed”, [KEY_HOME] = “Home”,
[KEY_UP] = “Up”, [KEY_PAGEUP] = “PageUp”,
[KEY_LEFT] = “Left”, [KEY_RIGHT] = “Right”,
[KEY_END] = “End”, [KEY_DOWN] = “Down”,
[KEY_PAGEDOWN] = “PageDown”, [KEY_INSERT] = “Insert”,
[KEY_DELETE] = “Delete”, [KEY_MACRO] = “Macro”,
[KEY_MUTE] = “Mute”, [KEY_VOLUMEDOWN] = “VolumeDown”,
[KEY_VOLUMEUP] = “VolumeUp”, [KEY_POWER] = “Power”,
[KEY_KPEQUAL] = “KPEqual”, [KEY_KPPLUSMINUS] = “KPPlusMinus”,
[KEY_PAUSE] = “Pause”, [KEY_KPCOMMA] = “KPComma”,
[KEY_HANGUEL] = “Hanguel”, [KEY_HANJA] = “Hanja”,
[KEY_YEN] = “Yen”, [KEY_LEFTMETA] = “LeftMeta”,
[KEY_RIGHTMETA] = “RightMeta”, [KEY_COMPOSE] = “Compose”,
[KEY_STOP] = “Stop”, [KEY_AGAIN] = “Again”,
[KEY_PROPS] = “Props”, [KEY_UNDO] = “Undo”,
[KEY_FRONT] = “Front”, [KEY_COPY] = “Copy”,
[KEY_OPEN] = “Open”, [KEY_PASTE] = “Paste”,
[KEY_FIND] = “Find”, [KEY_CUT] = “Cut”,
[KEY_HELP] = “Help”, [KEY_MENU] = “Menu”,
[KEY_CALC] = “Calc”, [KEY_SETUP] = “Setup”,
[KEY_SLEEP] = “Sleep”, [KEY_WAKEUP] = “WakeUp”,
[KEY_FILE] = “File”, [KEY_SENDFILE] = “SendFile”,
[KEY_DELETEFILE] = “DeleteFile”, [KEY_XFER] = “X-fer”,
[KEY_PROG1] = “Prog1”, [KEY_PROG2] = “Prog2”,
[KEY_WWW] = “WWW”, [KEY_MSDOS] = “MSDOS”,
[KEY_COFFEE] = “Coffee”, [KEY_DIRECTION] = “Direction”,
[KEY_CYCLEWINDOWS] = “CycleWindows”, [KEY_MAIL] = “Mail”,
[KEY_BOOKMARKS] = “Bookmarks”, [KEY_COMPUTER] = “Computer”,
[KEY_BACK] = “Back”, [KEY_FORWARD] = “Forward”,
[KEY_CLOSECD] = “CloseCD”, [KEY_EJECTCD] = “EjectCD”,
[KEY_EJECTCLOSECD] = “EjectCloseCD”, [KEY_NEXTSONG] = “NextSong”,
[KEY_PLAYPAUSE] = “PlayPause”, [KEY_PREVIOUSSONG] = “PreviousSong”,
[KEY_STOPCD] = “StopCD”, [KEY_RECORD] = “Record”,
[KEY_REWIND] = “Rewind”, [KEY_PHONE] = “Phone”,
[KEY_ISO] = “ISOKey”, [KEY_CONFIG] = “Config”,
[KEY_HOMEPAGE] = “HomePage”, [KEY_REFRESH] = “Refresh”,
[KEY_EXIT] = “Exit”, [KEY_MOVE] = “Move”,
[KEY_EDIT] = “Edit”, [KEY_SCROLLUP] = “ScrollUp”,
[KEY_SCROLLDOWN] = “ScrollDown”, [KEY_KPLEFTPAREN] = “KPLeftParenthesis”,
[KEY_KPRIGHTPAREN] = “KPRightParenthesis”, [KEY_F13] = “F13”,
[KEY_F14] = “F14”, [KEY_F15] = “F15”,
[KEY_F16] = “F16”, [KEY_F17] = “F17”,
[KEY_F18] = “F18”, [KEY_F19] = “F19”,
[KEY_F20] = “F20”, [KEY_F21] = “F21”,
[KEY_F22] = “F22”, [KEY_F23] = “F23”,
[KEY_F24] = “F24”, [KEY_PLAYCD] = “PlayCD”,
[KEY_PAUSECD] = “PauseCD”, [KEY_PROG3] = “Prog3”,
[KEY_PROG4] = “Prog4”, [KEY_SUSPEND] = “Suspend”,
[KEY_CLOSE] = “Close”, [KEY_PLAY] = “Play”,
[KEY_FASTFORWARD] = “Fast Forward”, [KEY_BASSBOOST] = “Bass Boost”,
[KEY_PRINT] = “Print”, [KEY_HP] = “HP”,
[KEY_CAMERA] = “Camera”, [KEY_SOUND] = “Sound”,
[KEY_QUESTION] = “Question”, [KEY_EMAIL] = “Email”,
[KEY_CHAT] = “Chat”, [KEY_SEARCH] = “Search”,
[KEY_CONNECT] = “Connect”, [KEY_FINANCE] = “Finance”,
[KEY_SPORT] = “Sport”, [KEY_SHOP] = “Shop”,
[KEY_ALTERASE] = “Alternate Erase”, [KEY_CANCEL] = “Cancel”,
[KEY_BRIGHTNESSDOWN] = “Brightness down”, [KEY_BRIGHTNESSUP] = “Brightness up”,
[KEY_MEDIA] = “Media”, [KEY_UNKNOWN] = “Unknown”,
[BTN_0] = “Btn0”, [BTN_1] = “Btn1”,
[BTN_2] = “Btn2”, [BTN_3] = “Btn3”,
[BTN_4] = “Btn4”, [BTN_5] = “Btn5”,
[BTN_6] = “Btn6”, [BTN_7] = “Btn7”,
[BTN_8] = “Btn8”, [BTN_9] = “Btn9”,
[BTN_LEFT] = “LeftBtn”, [BTN_RIGHT] = “RightBtn”,
[BTN_MIDDLE] = “MiddleBtn”, [BTN_SIDE] = “SideBtn”,
[BTN_EXTRA] = “ExtraBtn”, [BTN_FORWARD] = “ForwardBtn”,
[BTN_BACK] = “BackBtn”, [BTN_TASK] = “TaskBtn”,
[BTN_TRIGGER] = “Trigger”, [BTN_THUMB] = “ThumbBtn”,
[BTN_THUMB2] = “ThumbBtn2”, [BTN_TOP] = “TopBtn”,
[BTN_TOP2] = “TopBtn2”, [BTN_PINKIE] = “PinkieBtn”,
[BTN_BASE] = “BaseBtn”, [BTN_BASE2] = “BaseBtn2”,
[BTN_BASE3] = “BaseBtn3”, [BTN_BASE4] = “BaseBtn4”,
[BTN_BASE5] = “BaseBtn5”, [BTN_BASE6] = “BaseBtn6”,
[BTN_DEAD] = “BtnDead”, [BTN_A] = “BtnA”,
[BTN_B] = “BtnB”, [BTN_C] = “BtnC”,
[BTN_X] = “BtnX”, [BTN_Y] = “BtnY”,
[BTN_Z] = “BtnZ”, [BTN_TL] = “BtnTL”,
[BTN_TR] = “BtnTR”, [BTN_TL2] = “BtnTL2”,
[BTN_TR2] = “BtnTR2”, [BTN_SELECT] = “BtnSelect”,
[BTN_START] = “BtnStart”, [BTN_MODE] = “BtnMode”,
[BTN_THUMBL] = “BtnThumbL”, [BTN_THUMBR] = “BtnThumbR”,
[BTN_TOOL_PEN] = “ToolPen”, [BTN_TOOL_RUBBER] = “ToolRubber”,
[BTN_TOOL_BRUSH] = “ToolBrush”, [BTN_TOOL_PENCIL] = “ToolPencil”,
[BTN_TOOL_AIRBRUSH] = “ToolAirbrush”, [BTN_TOOL_FINGER] = “ToolFinger”,
[BTN_TOOL_MOUSE] = “ToolMouse”, [BTN_TOOL_LENS] = “ToolLens”,
[BTN_TOUCH] = “Touch”, [BTN_STYLUS] = “Stylus”,
[BTN_STYLUS2] = “Stylus2”, [BTN_TOOL_DOUBLETAP] = “Tool Doubletap”,
[BTN_TOOL_TRIPLETAP] = “Tool Tripletap”, [BTN_GEAR_DOWN] = “WheelBtn”,
[BTN_GEAR_UP] = “Gear up”, [KEY_OK] = “Ok”,
[KEY_SELECT] = “Select”, [KEY_GOTO] = “Goto”,
[KEY_CLEAR] = “Clear”, [KEY_POWER2] = “Power2”,
[KEY_OPTION] = “Option”, [KEY_INFO] = “Info”,
[KEY_TIME] = “Time”, [KEY_VENDOR] = “Vendor”,
[KEY_ARCHIVE] = “Archive”, [KEY_PROGRAM] = “Program”,
[KEY_CHANNEL] = “Channel”, [KEY_FAVORITES] = “Favorites”,
[KEY_EPG] = “EPG”, [KEY_PVR] = “PVR”,
[KEY_MHP] = “MHP”, [KEY_LANGUAGE] = “Language”,
[KEY_TITLE] = “Title”, [KEY_SUBTITLE] = “Subtitle”,
[KEY_ANGLE] = “Angle”, [KEY_ZOOM] = “Zoom”,
[KEY_MODE] = “Mode”, [KEY_KEYBOARD] = “Keyboard”,
[KEY_SCREEN] = “Screen”, [KEY_PC] = “PC”,
[KEY_TV] = “TV”, [KEY_TV2] = “TV2”,
[KEY_VCR] = “VCR”, [KEY_VCR2] = “VCR2”,
[KEY_SAT] = “Sat”, [KEY_SAT2] = “Sat2”,
[KEY_CD] = “CD”, [KEY_TAPE] = “Tape”,
[KEY_RADIO] = “Radio”, [KEY_TUNER] = “Tuner”,
[KEY_PLAYER] = “Player”, [KEY_TEXT] = “Text”,
[KEY_DVD] = “DVD”, [KEY_AUX] = “Aux”,
[KEY_MP3] = “MP3”, [KEY_AUDIO] = “Audio”,
[KEY_VIDEO] = “Video”, [KEY_DIRECTORY] = “Directory”,
[KEY_LIST] = “List”, [KEY_MEMO] = “Memo”,
[KEY_CALENDAR] = “Calendar”, [KEY_RED] = “Red”,
[KEY_GREEN] = “Green”, [KEY_YELLOW] = “Yellow”,
[KEY_BLUE] = “Blue”, [KEY_CHANNELUP] = “ChannelUp”,
[KEY_CHANNELDOWN] = “ChannelDown”, [KEY_FIRST] = “First”,
[KEY_LAST] = “Last”, [KEY_AB] = “AB”,
[KEY_NEXT] = “Next”, [KEY_RESTART] = “Restart”,
[KEY_SLOW] = “Slow”, [KEY_SHUFFLE] = “Shuffle”,
[KEY_BREAK] = “Break”, [KEY_PREVIOUS] = “Previous”,
[KEY_DIGITS] = “Digits”, [KEY_TEEN] = “TEEN”,
[KEY_TWEN] = “TWEN”, [KEY_DEL_EOL] = “Delete EOL”,
[KEY_DEL_EOS] = “Delete EOS”, [KEY_INS_LINE] = “Insert line”,
[KEY_DEL_LINE] = “Delete line”,
};
char *absval[5] = { “Value”, "Min ", "Max ", "Fuzz ", "Flat " };
char *relatives[REL_MAX + 1] =
{
[0 … REL_MAX] = NULL,
[REL_X] = “X”, [REL_Y] = “Y”,
[REL_Z] = “Z”, [REL_HWHEEL] = “HWheel”,
[REL_DIAL] = “Dial”, [REL_WHEEL] = “Wheel”,
[REL_MISC] = “Misc”,
};
char *absolutes[ABS_MAX + 1] =
{
[0 … ABS_MAX] = NULL,
[ABS_X] = “X”, [ABS_Y] = “Y”,
[ABS_Z] = “Z”, [ABS_RX] = “Rx”,
[ABS_RY] = “Ry”, [ABS_RZ] = “Rz”,
[ABS_THROTTLE] = “Throttle”, [ABS_RUDDER] = “Rudder”,
[ABS_WHEEL] = “Wheel”, [ABS_GAS] = “Gas”,
[ABS_BRAKE] = “Brake”, [ABS_HAT0X] = “Hat0X”,
[ABS_HAT0Y] = “Hat0Y”, [ABS_HAT1X] = “Hat1X”,
[ABS_HAT1Y] = “Hat1Y”, [ABS_HAT2X] = “Hat2X”,
[ABS_HAT2Y] = “Hat2Y”, [ABS_HAT3X] = “Hat3X”,
[ABS_HAT3Y] = “Hat 3Y”, [ABS_PRESSURE] = “Pressure”,
[ABS_DISTANCE] = “Distance”, [ABS_TILT_X] = “XTilt”,
[ABS_TILT_Y] = “YTilt”, [ABS_TOOL_WIDTH] = “Tool Width”,
[ABS_VOLUME] = “Volume”, [ABS_MISC] = “Misc”,
};
char *misc[MSC_MAX + 1] =
{
[ 0 … MSC_MAX] = NULL,
[MSC_SERIAL] = “Serial”, [MSC_PULSELED] = “Pulseled”,
[MSC_GESTURE] = “Gesture”, [MSC_RAW] = “RawData”,
[MSC_SCAN] = “ScanCode”,
};
char *leds[LED_MAX + 1] =
{
[0 … LED_MAX] = NULL,
[LED_NUML] = “NumLock”, [LED_CAPSL] = “CapsLock”,
[LED_SCROLLL] = “ScrollLock”, [LED_COMPOSE] = “Compose”,
[LED_KANA] = “Kana”, [LED_SLEEP] = “Sleep”,
[LED_SUSPEND] = “Suspend”, [LED_MUTE] = “Mute”,
[LED_MISC] = “Misc”,
};
char *repeats[REP_MAX + 1] =
{
[0 … REP_MAX] = NULL,
[REP_DELAY] = “Delay”, [REP_PERIOD] = “Period”
};
char *sounds[SND_MAX + 1] =
{
[0 … SND_MAX] = NULL,
[SND_CLICK] = “Click”, [SND_BELL] = “Bell”,
[SND_TONE] = “Tone”
};
char **names[EV_MAX + 1] =
{
[0 … EV_MAX] = NULL,
[EV_SYN] = events, [EV_KEY] = keys,
[EV_REL] = relatives, [EV_ABS] = absolutes,
[EV_MSC] = misc, [EV_LED] = leds,
[EV_SND] = sounds, [EV_REP] = repeats,
};
#define BITS_PER_LONG (sizeof(long) * 8)
#define NBITS(x) ((((x)-1)/BITS_PER_LONG)+1)
#define OFF(x) ((x)%BITS_PER_LONG)
#define BIT(x) (1UL<<OFF(x))
#define LONG(x) ((x)/BITS_PER_LONG)
#define test_bit(bit, array) ((array[LONG(bit)] >> OFF(bit)) & 1)
int main (int argc, char **argv)
{
int fd, rd, i, j, k;
struct input_event ev[64];
int version;
unsigned short id[4];
unsigned long bit[EV_MAX][NBITS(KEY_MAX)];
char name[256] = “Unknown”;
int abs[5];
if (argc < 2)
{
printf("Usage: evtest /dev/input/eventX\n");
printf("Where X = input device number\n");
return 1;
}
if ((fd = open(argv[argc - 1], O_RDONLY)) < 0)
{
perror("evtest");
return 1;
}
if (ioctl(fd, EVIOCGVERSION, &version))
{
perror("evtest: can't get version");
return 1;
}
printf("Input driver version is %d.%d.%d\n",
version >> 16, (version >> 8) & 0xff, version & 0xff);
ioctl(fd, EVIOCGID, id);
printf("Input device ID: bus 0x%x vendor 0x%x product 0x%x version 0x%x\n",
id[ID_BUS], id[ID_VENDOR], id[ID_PRODUCT], id[ID_VERSION]);
ioctl(fd, EVIOCGNAME(sizeof(name)), name);
printf("Input device name: \"%s\"\n", name);
memset(bit, 0, sizeof(bit));
ioctl(fd, EVIOCGBIT(0, EV_MAX), bit[0]);
printf("Supported events:\n");
for (i = 0; i < EV_MAX; i++)
if (test_bit(i, bit[0]))
{
printf(" Event type %d (%s)\n", i, events[i] ? events[i] : "?");
if (!i) continue;
ioctl(fd, EVIOCGBIT(i, KEY_MAX), bit[i]);
for (j = 0; j < KEY_MAX; j++)
if (test_bit(j, bit[i]))
{
printf(" Event code %d (%s)\n", j, names[i] ? (names[i][j] ? names[i][j] : "?") : "?");
if (i == EV_ABS)
{
ioctl(fd, EVIOCGABS(j), abs);
for (k = 0; k < 5; k++)
if ((k < 3) || abs[k])
printf(" %s %6d\n", absval[k], abs[k]);
}
}
}
printf("Testing ... (interrupt to exit)\n");
while (1)
{
rd = read(fd, ev, sizeof(struct input_event) * 64);
if (rd < (int) sizeof(struct input_event))
{
printf("yyy\n");
perror("\nevtest: error reading");
return 1;
}
for (i = 0; i < rd / sizeof(struct input_event); i++)
if (ev[i].type == EV_SYN)
{
printf("Event: time %ld.%06ld, -------------- %s ------------\n",
ev[i].time.tv_sec, ev[i].time.tv_usec, ev[i].code ? "Config Sync" : "Report Sync" );
}
else if (ev[i].type == EV_MSC && (ev[i].code == MSC_RAW || ev[i].code == MSC_SCAN))
{
printf("Event: time %ld.%06ld, type %d (%s), code %d (%s), value %02x\n",
ev[i].time.tv_sec, ev[i].time.tv_usec, ev[i].type,
events[ev[i].type] ? events[ev[i].type] : "?",
ev[i].code,
names[ev[i].type] ? (names[ev[i].type][ev[i].code] ? names[ev[i].type][ev[i].code] : "?") : "?",
ev[i].value);
}
else
{
printf("Event: time %ld.%06ld, type %d (%s), code %d (%s), value %d\n",
ev[i].time.tv_sec, ev[i].time.tv_usec, ev[i].type,
events[ev[i].type] ? events[ev[i].type] : "?",
ev[i].code,
names[ev[i].type] ? (names[ev[i].type][ev[i].code] ? names[ev[i].type][ev[i].code] : "?") : "?",
ev[i].value);
}
}
}
{% endcodeblock %}
- 测试效果:
# ./evtest /dev/input/event2
Input driver version is 1.0.1
Input device ID: bus 0x1c vendor 0x0 product 0x159 version 0x0
Input device name: "ADXL34x accelerometer"
Supported events:
Event type 0 (Sync)
Event type 1 (Key)
Event code 330 (Touch)
Event type 3 (Absolute)
Event code 0 (X)
Value 0
Min -4096
Max 4096
Fuzz 3
Flat 3
Event code 1 (Y)
Value 0
Min -4096
Max 4096
Fuzz 3
Flat 3
Event code 2 (Z)
Value 0
Min -4096
Max 4096
Fuzz 3
Flat 3
Testing ... (interrupt to exit)
Event: time 3949236.075868, type 3 (Absolute), code 0 (X), value -223
Event: time 3949236.075868, type 3 (Absolute), code 1 (Y), value -10
Event: time 3949236.075868, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949236.075868, -------------- Report Sync ------------
Event: time 3949236.115188, type 3 (Absolute), code 0 (X), value -235
Event: time 3949236.115188, -------------- Report Sync ------------
Event: time 3949236.154590, type 3 (Absolute), code 0 (X), value -234
Event: time 3949236.154590, -------------- Report Sync ------------
Event: time 3949236.824446, type 3 (Absolute), code 2 (Z), value -14
Event: time 3949236.824446, -------------- Report Sync ------------
Event: time 3949236.863862, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949236.863862, -------------- Report Sync ------------
Event: time 3949236.903264, type 3 (Absolute), code 2 (Z), value -12
Event: time 3949236.903264, -------------- Report Sync ------------
Event: time 3949236.942659, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949236.942659, -------------- Report Sync ------------
Event: time 3949237.021471, type 3 (Absolute), code 2 (Z), value -12
Event: time 3949237.021471, -------------- Report Sync ------------
Event: time 3949237.257883, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949237.257883, -------------- Report Sync ------------
Event: time 3949238.006540, type 3 (Absolute), code 0 (X), value -233
Event: time 3949238.006540, type 3 (Absolute), code 1 (Y), value -11
Event: time 3949238.006540, -------------- Report Sync ------------
Event: time 3949238.045949, type 3 (Absolute), code 2 (Z), value -14
Event: time 3949238.045949, -------------- Report Sync ------------
Event: time 3949238.085346, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949238.085346, -------------- Report Sync ------------
Event: time 3949238.164144, type 3 (Absolute), code 1 (Y), value -10
Event: time 3949238.164144, -------------- Report Sync ------------
Event: time 3949238.361159, type 3 (Absolute), code 2 (Z), value -12
Event: time 3949238.361159, -------------- Report Sync ------------
Event: time 3949238.400569, type 3 (Absolute), code 0 (X), value -234
Event: time 3949238.400569, -------------- Report Sync ------------
Event: time 3949238.676384, type 3 (Absolute), code 2 (Z), value -13
Event: time 3949238.676384, -------------- Report Sync ------------
Event: time 3949238.755194, type 3 (Absolute), code 0 (X), value -241
Event: time 3949238.755194, type 3 (Absolute), code 1 (Y), value -1
Event: time 3949238.755194, type 3 (Absolute), code 2 (Z), value 19
Event: time 3949238.755194, -------------- Report Sync ------------
Event: time 3949238.794592, type 3 (Absolute), code 0 (X), value -298
Event: time 3949238.794592, type 3 (Absolute), code 1 (Y), value 42
Event: time 3949238.794592, type 3 (Absolute), code 2 (Z), value -33
2.4 详细分析
2.4.1 几个重要结构体
adxl34x.c
的probe()
一进来就定义了三个结构体:
struct adxl34x *ac;
struct input_dev *input_dev;
const struct adxl34x_platform_data *pdata;
adxl34x
就是针对设备量身定制的,包含所有信息,在对其进行一系列的设置后,在adxl34x-spi.c
的probe()
里,使用spi_set_drvdata()
将其保存到spi_device
的私有数据里,以后其它函数可以通过spi_get_drvdata()
得到该结构体,从而获取设备的所有信息。
input_dev
表示一个输入设备,里面有一个输入设备的属性定义。
adxl34x_platform_data
包含adxl34x设备的属性定义。
定义完后,进行初始化:
{% codeblock lang:c %}
ac = kzalloc(sizeof(*ac), GFP_KERNEL);
input_dev = input_allocate_device();
if (!ac || !input_dev) {
err = -ENOMEM;
goto err_free_mem;
}
ac->fifo_delay = fifo_delay_default;
pdata = dev_get_platdata(dev);
if (!pdata) {
dev_dbg(dev,
"No platform data: Using default initialization\n");
pdata = &adxl34x_default_init;
}
{% endcodeblock %}
adxl34x
使用kzalloc()
分配空间并清零。
input_dev
使用输入子系统提供的input_allocate_device()
分配空间。
adxl34x_platform_data
先尝试从device
获取,没有的话使用默认的配置,也就是adxl34x_default_init
的内容,里面有ADXL34X支持何种输入事件、电源模式等。
2.4.2 判断中断
ADXL34X工作时,通过中断引脚通知主机发生了某中断事件,因此中断是必不可少的,这里检测如果没有中断则返回错误。
{% codeblock lang:c %}
if (!irq) {
dev_err(dev, “no IRQ?\n”);
err = -ENODEV;
goto err_out;
}
{% endcodeblock %}
2.4.3 设置adxl34x
{% codeblock lang:c %}
ac->fifo_delay = fifo_delay_default;
ac->pdata = *pdata;
pdata = &ac->pdata;
ac->input = input_dev;
ac->dev = dev;
ac->irq = irq;
ac->bops = bops;
mutex_init(&ac->mutex);
input_dev->name = "ADXL34x accelerometer";
revid = AC_READ(ac, DEVID);
switch (revid) {
case ID_ADXL345:
ac->model = 345;
break;
case ID_ADXL346:
ac->model = 346;
break;
default:
dev_err(dev, "Failed to probe %s\n", input_dev->name);
err = -ENODEV;
goto err_free_mem;
}
snprintf(ac->phys, sizeof(ac->phys), "%s/input0", dev_name(dev));
{% endcodeblock %}
将input_dev
、adxl34x_platform_data
指向adxl34x
;
将传入的参数赋值给adxl34x
,初始化部分adxl34x
参数;
还读取设备ID判断是ADXL345,还是ADXL346。
2.4.4 设置input_dev
先使用__set_bit(ac->pdata.ev_type, input_dev->evbit);
设置支持哪一类事件,由前面的adxl34x_default_init
可知为EV_ABS
。
如果为EV_ABS
类型,再使用_set_bit()
和input_set_abs_params()
设置能产生该类的哪些事件。其中input_set_abs_params()
也是调用的__set_bit()
,参数依次为input设备指针、坐标轴、最小范围、最大范围、过滤值、丢弃范围值。
__set_bit(ac->pdata.ev_type, input_dev->evbit);
……
__set_bit(ABS_X, input_dev->absbit);
__set_bit(ABS_Y, input_dev->absbit);
__set_bit(ABS_Z, input_dev->absbit);
……
input_set_abs_params(input_dev, ABS_X, -range, range, 3, 3);
input_set_abs_params(input_dev, ABS_Y, -range, range, 3, 3);
input_set_abs_params(input_dev, ABS_Z, -range, range, 3, 3);
接下来设置支持EV_KEY
类事件,再具体设置支持类中的哪些事件:
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit);
可以看出,设置input_dev
的思路就是先设置支持哪一类事件,再设置类中的具体事件。
input
子系统中支持的类型含义:
EV_SYN 同步事件
EV_KEY 键盘事件
EV_REL 相对坐标事件,用于鼠标
EV_ABS 绝对坐标事件,用于摇杆
EV_MSC 其他事件
EV_LED LED灯事件
EV_SND 声音事件
EV_REP 重复按键事件
EV_FF 受力事件
EV_PWR 电源事件
EV_FF_STATUS 受力状态事件
{% codeblock lang:c %}
input_dev->phys = ac->phys;
input_dev->dev.parent = dev;
input_dev->id.product = ac->model;
input_dev->id.bustype = bops->bustype;
input_dev->open = adxl34x_input_open;
input_dev->close = adxl34x_input_close;
input_set_drvdata(input_dev, ac);
__set_bit(ac->pdata.ev_type, input_dev->evbit);
if (ac->pdata.ev_type == EV_REL) {
__set_bit(REL_X, input_dev->relbit);
__set_bit(REL_Y, input_dev->relbit);
__set_bit(REL_Z, input_dev->relbit);
} else {
/* EV_ABS */
__set_bit(ABS_X, input_dev->absbit);
__set_bit(ABS_Y, input_dev->absbit);
__set_bit(ABS_Z, input_dev->absbit);
if (pdata->data_range & FULL_RES)
range = ADXL_FULLRES_MAX_VAL; /* Signed 13-bit */
else
range = ADXL_FIXEDRES_MAX_VAL; /* Signed 10-bit */
input_set_abs_params(input_dev, ABS_X, -range, range, 3, 3);
input_set_abs_params(input_dev, ABS_Y, -range, range, 3, 3);
input_set_abs_params(input_dev, ABS_Z, -range, range, 3, 3);
}
__set_bit(EV_KEY, input_dev->evbit);
__set_bit(pdata->ev_code_tap[ADXL_X_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Y_AXIS], input_dev->keybit);
__set_bit(pdata->ev_code_tap[ADXL_Z_AXIS], input_dev->keybit);
if (pdata->ev_code_ff) {
ac->int_mask = FREE_FALL;
__set_bit(pdata->ev_code_ff, input_dev->keybit);
}
if (pdata->ev_code_act_inactivity)
__set_bit(pdata->ev_code_act_inactivity, input_dev->keybit);
{% endcodeblock %}
2.4.5 设置中断
ADXL345的中断引脚默认为高电平有效,也可自行修改寄存器改为低电平有效。
所有功能都可以同时使用,但是,一些功能可能需要共享中断引脚。
中断功能的详细介绍参考手册,内容有点多,有产生新数据就产生中断,加速度超过某个值产生中断,两次加速度超过某个值产生中断等。
这里根据adxl34x_platform_data
的内容,设置adxl34x
的int_mask
。
再通过request_threaded_irq()
申请线程中断,绑定中断处理函数,并把adxl34x
传给中断处理函数。
{% codeblock lang:c %}
ac->int_mask |= ACTIVITY | INACTIVITY;
if (pdata->watermark) {
ac->int_mask |= WATERMARK;
if (!FIFO_MODE(pdata->fifo_mode))
ac->pdata.fifo_mode |= FIFO_STREAM;
} else {
ac->int_mask |= DATA_READY;
}
if (pdata->tap_axis_control & (TAP_X_EN | TAP_Y_EN | TAP_Z_EN))
ac->int_mask |= SINGLE_TAP | DOUBLE_TAP;
if (FIFO_MODE(pdata->fifo_mode) == FIFO_BYPASS)
ac->fifo_delay = false;
AC_WRITE(ac, POWER_CTL, 0);
err = request_threaded_irq(ac->irq, NULL, adxl34x_irq,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
dev_name(dev), ac);
if (err) {
dev_err(dev, "irq %d busy?\n", ac->irq);
goto err_free_mem;
}
{% endcodeblock %}
在产生中断时,进入中断处理函数adxl34x_irq()
,读取中断源寄存器,判断产生的何种中断,调用相应的处理函数。
调用input_event()
设置要上报的数据值,再调用input_sync()
上报。
2.4.6 创建sysfs属性文件
同前面的AD7705一样,这里也在/sys/class/input/
下创建节点,直接可以访问。
{% codeblock lang:c %}
err = sysfs_create_group(&dev->kobj, &adxl34x_attr_group);
if (err)
goto err_free_irq;
{% endcodeblock %}
根据传入的attribute_group
结构体可以找到具体实现了哪些功能:
{% codeblock lang:c %}
static struct attribute *adxl34x_attributes[] = {
&dev_attr_disable.attr,
&dev_attr_calibrate.attr,
&dev_attr_rate.attr,
&dev_attr_autosleep.attr,
&dev_attr_position.attr,
#ifdef ADXL_DEBUG
&dev_attr_write.attr,
#endif
NULL
};
static const struct attribute_group adxl34x_attr_group = {
.attrs = adxl34x_attributes,
};
static DEVICE_ATTR(position, S_IRUGO, adxl34x_position_show, NULL);
static DEVICE_ATTR(autosleep, 0664,
adxl34x_autosleep_show, adxl34x_autosleep_store);
static DEVICE_ATTR(rate, 0664, adxl34x_rate_show, adxl34x_rate_store);
static DEVICE_ATTR(calibrate, 0664,
adxl34x_calibrate_show, adxl34x_calibrate_store);
static DEVICE_ATTR(disable, 0664, adxl34x_disable_show, adxl34x_disable_store);
{% endcodeblock %}
可以看出提供了position
,获取当前三轴状态;autosleep
,设置自动休眠;rate
,设置输出数据速率;calibrate
,设备校准;disable
,启用/禁用设备。
与实测内容吻合。
# ls /sys/class/input/input2
capabilities id name properties uniq
device modalias phys subsystem
event2 mouse1 power uevent
2.4.7 注册输入子系统
{% codeblock lang:c %}
err = input_register_device(input_dev);
if (err)
goto err_remove_attr;
{% endcodeblock %}
2.4.8 硬件操作
最后,使用adxl34x-spi.c
提供的传输函数,根据adxl34x
的内容操作ADXL345。
{% codeblock lang:c %}
AC_WRITE(ac, OFSX, pdata->x_axis_offset);
ac->hwcal.x = pdata->x_axis_offset;
AC_WRITE(ac, OFSY, pdata->y_axis_offset);
ac->hwcal.y = pdata->y_axis_offset;
AC_WRITE(ac, OFSZ, pdata->z_axis_offset);
ac->hwcal.z = pdata->z_axis_offset;
AC_WRITE(ac, THRESH_TAP, pdata->tap_threshold);
AC_WRITE(ac, DUR, pdata->tap_duration);
AC_WRITE(ac, LATENT, pdata->tap_latency);
AC_WRITE(ac, WINDOW, pdata->tap_window);
AC_WRITE(ac, THRESH_ACT, pdata->activity_threshold);
AC_WRITE(ac, THRESH_INACT, pdata->inactivity_threshold);
AC_WRITE(ac, TIME_INACT, pdata->inactivity_time);
AC_WRITE(ac, THRESH_FF, pdata->free_fall_threshold);
AC_WRITE(ac, TIME_FF, pdata->free_fall_time);
AC_WRITE(ac, TAP_AXES, pdata->tap_axis_control);
AC_WRITE(ac, ACT_INACT_CTL, pdata->act_axis_control);
AC_WRITE(ac, BW_RATE, RATE(ac->pdata.data_rate) |
(pdata->low_power_mode ? LOW_POWER : 0));
AC_WRITE(ac, DATA_FORMAT, pdata->data_range);
AC_WRITE(ac, FIFO_CTL, FIFO_MODE(pdata->fifo_mode) |
SAMPLES(pdata->watermark));
if (pdata->use_int2) {
/* Map all INTs to INT2 */
AC_WRITE(ac, INT_MAP, ac->int_mask | OVERRUN);
} else {
/* Map all INTs to INT1 */
AC_WRITE(ac, INT_MAP, 0);
}
if (ac->model == 346 && ac->pdata.orientation_enable) {
AC_WRITE(ac, ORIENT_CONF,
ORIENT_DEADZONE(ac->pdata.deadzone_angle) |
ORIENT_DIVISOR(ac->pdata.divisor_length));
ac->orient2d_saved = 1234;
ac->orient3d_saved = 1234;
if (pdata->orientation_enable & ADXL_EN_ORIENTATION_3D)
for (i = 0; i < ARRAY_SIZE(pdata->ev_codes_orient_3d); i++)
__set_bit(pdata->ev_codes_orient_3d[i],
input_dev->keybit);
if (pdata->orientation_enable & ADXL_EN_ORIENTATION_2D)
for (i = 0; i < ARRAY_SIZE(pdata->ev_codes_orient_2d); i++)
__set_bit(pdata->ev_codes_orient_2d[i],
input_dev->keybit);
} else {
ac->pdata.orientation_enable = 0;
}
AC_WRITE(ac, INT_ENABLE, ac->int_mask | OVERRUN);
ac->pdata.power_mode &= (PCTL_AUTO_SLEEP | PCTL_LINK);
{% endcodeblock %}
3. 总结
通过这两个子系统的驱动代码,总结几个之前写驱动没有意识。
1、自定义一个结构体,包含device
和设备私有属性,其它函数可通过它获取到所有数据;
2、使用互斥锁的意识;
3、创建sysfs内容,方便应用层直接访问;