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 文件内容,然后对读取到的内容进行解析,提取出加速度计、陀螺仪、温度的原始值,经过计算得到具体数值

三、测试结果

STM32MP157驱动开发——Linux IIO驱动(下)相关推荐

  1. STM32MP157驱动开发——Linux IIO驱动(上)

    STM32MP157驱动开发--Linux IIO驱动(上 ) 0.前言 一.IIO 子系统简介 1.iio_dev 结构体 2.iio_dev 申请与释放 3.iio_dev 注册与注销 4.iio ...

  2. STM32MP157驱动开发——Linux 音频驱动

    STM32MP157驱动开发--Linux 音频驱动 一.简介 1.CS42L51 简介 2.I2S总线 3.STM32MP1 SAI 总线接口 二.驱动开发 1.音频驱动 1)修改设备树 i2c 接 ...

  3. STM32MP157驱动开发——Linux 网络设备驱动

    STM32MP157驱动开发--Linux 网络设备驱动 一.简介 STM32MP1 GMAC 接口简介 YT8511C 详解 二.驱动开发 1.网络外设的设备树 2.设备驱动 三.测试 网速测试 参 ...

  4. STM32MP157驱动开发——Linux I2C驱动

    相关文章:正点原子教程第四十章--Linux I2C驱动实验 0.前言   为了简化笔记的编写以及降低工作量,本节开始相关的基础知识部分通过引入原子哥的教材链接来完成,有兴趣的可以进入学习.   上一 ...

  5. STM32MP157驱动开发——USB设备驱动

    STM32MP157驱动开发--USB设备驱动 一.简介 1.电气属性 2.USB OTG 3.STM32MP1 USB 接口简介 4.Type-C 电气属性 二.USB HOST 驱动开发 1.US ...

  6. STM32MP157驱动开发——Linux自带的LED灯驱动

    STM32MP157驱动开发--Linux自带的LED灯驱动 0.前言 一.Linux 内核自带 LED 驱动使能 二.驱动简介 1.LED灯驱动框架分析 2.module_platform_driv ...

  7. STM32MP157驱动开发——Linux RS232/485/GPS 驱动

    STM32MP157驱动开发--Linux RS232/485/GPS 驱动 一.简介 二.STM32MP1 UART 驱动分析 1.UART 的 platform 驱动框架 2.uart_drive ...

  8. STM32MP157驱动开发——Linux块设备驱动

    STM32MP157驱动开发--Linux块设备驱动 一.简介 二.驱动开发 1.使用请求队列的方式 2.测试① 3.不使用请求队列的方式 4.测试② 参考文章:[正点原子]I.MX6U嵌入式Linu ...

  9. STM32MP157驱动开发——Linux并发与竞争

    STM32MP157驱动开发--Linux并发与竞争 一.相关知识 二.实现原子操作的一些方式 1.原子操作API ①整型数据原子操作 ②原子位操作API 2.自旋锁 3.其他类型的锁 ①读写自旋锁 ...

最新文章

  1. 人工智能训练云燧T10
  2. Spring学习笔记:3(面向切面AOP)
  3. lnmp环境如何添加域名和虚拟主机
  4. 炉石android更新日志,炉石传说每逢更新必卡门,安卓用户你们想哭了吗?
  5. Hyperledger Fabric1.0架构概览
  6. 打开AzureRay园子的大门,欢迎大家串门哟~
  7. 华为交换机VLAN Trunk模式设置
  8. 154. Find Minimum in Rotated Sorted Array II
  9. github上成员贡献量_真祖传代码!你的GitHub代码已打包运往北极,传给1000年后人类...
  10. Web后端学习笔记 Flask(9)cookie and session
  11. java 深拷贝 序列化_java 之 利用序列化实现深拷贝
  12. linux免费私人云盘软件,Appnode+kodexplorer免费搭建私有云盘
  13. 问卷数据分析(SPSSSPSS Modeler)
  14. php nginx 伪静态规则,常见PHP程序的Nginx 伪静态规则
  15. Arduino_OLED-0.96游戏机
  16. 【FPGA】SPI协议
  17. 数据的增量采集与全量采集
  18. 基于python获取少量图片的简单代码
  19. m2e(maven integration for eclipse)理解
  20. 脑皮质算法篇(1)-- 导读

热门文章

  1. 【FPGA】Verilog 实践:半加器与全加器 | 半减器与全减器 | Code Converter
  2. 前有红海,后有竞争,怎么做好产品规划?
  3. 京东商品接口加解密算法解析
  4. Go语言教程【二、 Go 语言环境安装】
  5. 代码评审|阿里巴巴DevOps实践指南
  6. Jsp:param标签的使用
  7. 自动化测试之ios测试脚本
  8. UI设计师都会分享的五个好用设计软件
  9. word 中添加图片目录、内部链接
  10. 程序员的小工具,用python操作excel表格 --- 可用于多张表的数据对比筛选等。