STM32MP157驱动开发——Linux IIO驱动(下)
- 0.前言
- 一、IIO 触发缓冲区
- 1.IIO 触发器
- 2.申请触发器
- 3.释放触发器
- 4.注册触发器
- 5.注销触发器
- 6. IIO 缓冲区
- 7.向驱动程序添加触发缓冲功能
- 8.驱动编写
- 9.触发缓冲测试
- 10.缓冲区读取
- 二、测试App
- 三、测试结果
0.前言
上一节完成了 IIO 子系统下的 icm20608 设备驱动开发,但是传感器的数据采集速度太快,这一节就介绍一下驱动中与 IIO 缓冲区有关的内容。
碍于篇幅,在上一节的驱动开发时,已经将缓冲区相关的代码添加进驱动,所以这一节仅对其进行了解。
一、IIO 触发缓冲区
触发缓冲区就是基于某种信号来触发数据采集,这些信号就是触发器,比如:
① 传感器数据就绪中断
② 周期性中断
③ 用户空间下读取 sysfs 下的指定文件
触发器肯定是触发数据的采集,数据采集到以后会填充到缓冲区里面,最终会以字符设备形式提供给用户空间,用户空间直接读取缓冲区文件即可
1.IIO 触发器
linux 内核使用 iio_trigger 结构体表示触发器,定义在 include/linux/iio/trigger.h 文件中:
iio_trigger_ops 为触发器的操作函数结构体:
①set_trigger_state 函数用于设置触发器状态,也就是打开/关闭触发器。如果用中断作为触发器,此函数一般设置传感器的中断使能状态。
②try_reenable 函数当用户计数为 0 的时候尝试重新使能触发器。
③ validate_device 函数用于当触发器改变时,使设备生效。
如果自行创建触发器,需要驱动开发人员编写 iio_trigger_ops。
2.申请触发器
使用 iio_trigger_alloc 函数创建触发器。
原型:
struct iio_trigger *iio_trigger_alloc(const char *fmt, ...)
参数:
iio_trigger_alloc 是个可变长度参数函数,主要目的就是拼凑触发器名字
返回值:
申请到的 iio_trigger。
用法与printf函数相似,也可以使用devm_iio_trigger_alloc 函数申请触发器,这样在卸载驱动时就不用手动释放触发器。
例:iio_trigger_alloc("%s-dev%d", indio_dev->name, indio_dev->id)
,假设 indio_dev->name 为“icm20608”,indio_dev->id 为 0,那么触发器的名字就是“icm20608-dev0”
3.释放触发器
原型:
void iio_trigger_free(struct iio_trigger *trig)
参数:
trig:要释放的触发器。
返回值:无
4.注册触发器
原型:
int iio_trigger_register(struct iio_trigger *trig_info)
参数:
trig_info:要注册的触发器。
返回值:
0:成功
其他值:失败
同理,也可以使用 devm_iio_trigger_register 函数。
5.注销触发器
原型:
void iio_trigger_unregister(struct iio_trigger *trig_info)
参数:
trig_info:要注销的触发器。
返回值:无
6. IIO 缓冲区
IIO 缓冲区就是保存采集到的数据,用户空间可以直接通过访问字符设备 /dev/iio:deviceX(X=0,1,2……)来读取缓冲区中的数据。
创建缓冲区:
原型:
int iio_triggered_buffer_setup( struct iio_dev *indio_dev,irqreturn_t (*h)(int irq, void *p),irqreturn_t (*thread)(int irq, void *p),const struct iio_buffer_setup_ops *setup_ops)
参数:
indio_dev:需要创建缓冲的 iio_dev
h:触发器中断上半部,上半部程序一定要简单,执行速度越快越好,一般就是提供捕获时间戳。可以直接使用 IIO 框架提供的 iio_pollfunc_store_time 函数。
thread:触发器中断下半部,重要的处理就在这里,此函数中需要将传感器中的数据和上半部获取到的时间戳一起推送到缓冲区中。
setup_ops:缓冲区操作函数集,iio_buffer_setup_ops 结构体内容如下:
474 struct iio_buffer_setup_ops {
475 int (*preenable)(struct iio_dev *);
476 int (*postenable)(struct iio_dev *);
477 int (*predisable)(struct iio_dev *);
478 int (*postdisable)(struct iio_dev *);
479 bool (*validate_scan_mask)(struct iio_dev *indio_dev,
480 const unsigned long *scan_mask);
481 };
如果设置为 NULL,那么就会使用默认的操作集 iio_triggered_buffer_setup_ops。
返回值:无
7.向驱动程序添加触发缓冲功能
修改设备树:
中断是最常用的触发方式,因为一般的传感器都有中断功能,当数据准备就绪或者指定事件发生以后就会产生中断通知 SOC,此时 SOC 就可以读取传感器内部数据。
修改设备树,向 ICM20608 驱动中添加中断引脚 PA14 的配置,在 stm32mp15-pinctrl.dtsi 文件中,添加 PA14 引脚配置:
icm20608_pins_b: icm20608-0 {pins {pinmux = <STM32_PINMUX('A', 14, ANALOG)>;bias-pull-up;};
};
另外,打开 stm32mp157d-atk.dts 文件,修改 icm20608 节点,添加中断信息:
spidev: icm20608@0 {compatible = "alientek,icm20608";reg = <0>; /* CS #0 */pinctrl-names = "default";pinctrl-0 = <&icm20608_pins_b>;interrupt-parent = <&gpioa>;interrupts = <14 IRQ_TYPE_LEVEL_HIGH>;spi-max-frequency = <80000000>;
};
设置中断父节点为 gpioa,设置 PA14 为高电平触发。
然后编译出新的设备树,启动开发板。
8.驱动编写
在上一节中,已经将缓冲区有关的代码段添加到驱动中,所以这里就不在赘述。这里建议再进行了解,iio框架使用情况确实比较多。
9.触发缓冲测试
缓冲区接口目录路径为:/sys/bus/iio/devices/iio:device0/buffer
:
进入buffer目录,会有一些属性文件:
data_available:指示数据是否有效,为 1 时有效,为 0 时无效。
enable:使能缓冲区,写入 1 使能缓冲区,写 0 关闭缓冲区
length:缓冲区大小,也就是可以存储数据的数量。
watermark:阻塞读取的时候只有数据量大于 watermark 的时候才能读取,非阻塞读取时不受 watermark 影响。
上一节提到的 /sys/bus/iio/devices/triggerX(X=0,1,2……)即为触发器目录。
10.缓冲区读取
读取数据之前需要配置缓冲区和触发器,首先使能各个扫描元素,也就是通道,使用以下命令:
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_x_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_y_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_z_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_anglvel_x_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_anglvel_y_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_anglvel_z_en
echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_temp_en
然后设置ICM20608 所使用的触发器:
echo icm20608-dev0 > /sys/bus/iio/devices/iio:device0/trigger/current_trigger
最后就是设置缓冲区长度并开启:
echo 14 > /sys/bus/iio/devices/iio:device0/buffer/length
echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable
配置完成以后就可以读取/dev/iio:device0 文件,得到缓冲区的数据。
二、测试App
#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 <poll.h>
#include <sys/select.h>
#include <sys/time.h>
#include <signal.h>
#include <fcntl.h>
#include <errno.h>/** icm20608数据设备结构体*/
struct icm20608_dev {unsigned char data[14];int accel_x_calibbias, accel_y_calibbias, accel_z_calibbias;int accel_x_raw, accel_y_raw, accel_z_raw;int gyro_x_calibbias, gyro_y_calibbias, gyro_z_calibbias;int gyro_x_raw, gyro_y_raw, gyro_z_raw;int temp_offset, temp_raw;float accel_scale, gyro_scale, temp_scale;float gyro_x_act, gyro_y_act, gyro_z_act;float accel_x_act, accel_y_act, accel_z_act;float temp_act;
};struct icm20608_dev icm20608;/* * @description : 对icm20608相关触发进行设置 * @param : 无* @return : 无*/
void icm20608_trigger_set(void)
{system("echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_x_en");system("echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_y_en"); system("echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_accel_z_en");system("echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_anglvel_x_en");system("echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_anglvel_y_en");system("echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_anglvel_z_en");system("echo 1 > /sys/bus/iio/devices/iio:device0/scan_elements/in_temp_en");system("echo icm20608-dev0 > /sys/bus/iio/devices/iio:device0/trigger/current_trigger");system("echo 14 > /sys/bus/iio/devices/iio:device0/buffer/length");system("echo 1 > /sys/bus/iio/devices/iio:device0/buffer/enable");
}/** @description : 读取指定文件内容* @param - filename : 要读取的文件路径* @param - str : 读取到的文件字符串* @return : 0 成功;其他 失败*/
static int file_data_read(char *filename, char *str)
{int ret = 0;FILE *data_stream;data_stream = fopen(filename, "r"); /* 只读打开 */if(data_stream == NULL) {printf("can't open file %s\r\n", filename);return -1;}ret = fscanf(data_stream, "%s", str);if(!ret) {printf("file read error!\r\n");} else if(ret == EOF) {/* 读到文件末尾的话将文件指针重新调整到文件头 */fseek(data_stream, 0, SEEK_SET); }fclose(data_stream); /* 关闭文件 */ return 0;
}/* * @description : 读取数据* @param - fd : 文件描述符* @return : 0 成功;其他 失败*/
int icm20608_read(int fd, struct icm20608_dev *dev)
{int ret = 0;char str[50];int i = 0;file_data_read("/sys/bus/iio/devices/iio:device0/in_accel_scale", str);dev->accel_scale = atof(str);file_data_read("/sys/bus/iio/devices/iio:device0/in_anglvel_scale", str);dev->gyro_scale = atof(str);file_data_read("/sys/bus/iio/devices/iio:device0/in_temp_scale", str);dev->temp_scale = atof(str);ret = read(fd, dev->data, 14);dev->accel_z_raw = (signed short)((dev->data[4] << 8) | dev->data[5]);dev->accel_x_raw = (signed short)((dev->data[0] << 8) | dev->data[1]); dev->accel_y_raw = (signed short)((dev->data[2] << 8) | dev->data[3]); dev->accel_z_raw = (signed short)((dev->data[4] << 8) | dev->data[5]); dev->temp_raw = (signed short)((dev->data[6] << 8) | dev->data[7]); dev->gyro_x_raw = (signed short)((dev->data[8] << 8) | dev->data[9]); dev->gyro_y_raw = (signed short)((dev->data[10] << 8) | dev->data[11]);dev->gyro_z_raw = (signed short)((dev->data[12] << 8) | dev->data[13]);/* 转换为实际数值 */dev->accel_x_act = dev->accel_x_raw * dev->accel_scale;dev->accel_y_act = dev->accel_y_raw * dev->accel_scale;dev->accel_z_act = dev->accel_z_raw * dev->accel_scale;dev->gyro_x_act = dev->gyro_x_raw * dev->gyro_scale;dev->gyro_y_act = dev->gyro_y_raw * dev->gyro_scale;dev->gyro_z_act = dev->gyro_z_raw * dev->gyro_scale;dev->temp_act = ((dev->temp_raw - dev->temp_offset) / dev->temp_scale) + 25;return ret;
}/** @description : main主程序* @param - argc : argv数组元素个数* @param - argv : 具体参数* @return : 0 成功;其他 失败*/
int main(int argc, char *argv[])
{int fd;int ret = 0;/* 判断传参个数是否正确 */if(2 != argc) {printf("Usage:\n""\t./icm20608_triggerApp /dev/iio:device0\n");return -1;}icm20608_trigger_set(); /* 配置好触发缓冲区相关设置 *//* 打开设备 */fd = open(argv[1], O_RDONLY);if(0 > fd) {printf("ERROR: %s file open failed!\n", argv[1]);return -1;}/* 循环轮训读取按键数据 */while(1) {icm20608_read(fd, &icm20608);if(ret == 0) { /* 数据读取成功 */printf("\r\n原始值:\r\n");printf("gx = %d, gy = %d, gz = %d\r\n", icm20608.gyro_x_raw, icm20608.gyro_y_raw, icm20608.gyro_z_raw);printf("ax = %d, ay = %d, az = %d\r\n", icm20608.accel_x_raw, icm20608.accel_y_raw, icm20608.accel_z_raw);printf("temp = %d\r\n", icm20608.temp_raw);printf("实际值:");printf("act gx = %.2f°/S, act gy = %.2f°/S, act gz = %.2f°/S\r\n", icm20608.gyro_x_act, icm20608.gyro_y_act, icm20608.gyro_z_act);printf("act ax = %.2fg, act ay = %.2fg, act az = %.2fg\r\n", icm20608.accel_x_act, icm20608.accel_y_act, icm20608.accel_z_act);printf("act temp = %.2f°C\r\n", icm20608.temp_act);}usleep(100000); /*100ms */}
}
①icm20608_trigger_set 函数用于配置触发器和缓冲区,直接使用 system 函数来写入 shell 命令
②file_data_read 函数用于读取文件流,主要用于读取 ICM20608 的加速度计、陀螺仪、温度的分辨率等参数
③icm20608_read 函数用于读取 ICM20608 缓冲区数据,其中使用 read 函数读取/dev/iio:device0 文件内容,然后对读取到的内容进行解析,提取出加速度计、陀螺仪、温度的原始值,经过计算得到具体数值