作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习
擅长领域:驱动开发,嵌入式软件开发,BSP开发
作者主页:一个平凡而乐于分享的小比特的个人主页
文章收录专栏:IMX8MP,本专栏记录imx8mp开发板,学习开发过程中的问题及解决方法记录
欢迎大家点赞 👍 收藏 ⭐ 加关注哦!💖💖
MS1112驱动开发(iio框架)
ms1112驱动代码及测试程序在ms1112驱动,如有需要自行下载
1.iio框架简介
IIO全称是IndustrialI/O,翻译过来就是工业I/O,大家不要看到“工业”两个字就觉得IIO是只用于工业领域的。大家一般在搜索IIO子系统的时候,会发现大多数讲的都是ADC,这是因为IIO就是为ADC类传感器准备的,当然了DAC也是可以的。大家常用的陀螺仪、加速度计、电压/电流测量芯片、光照传感器、压力传感器等内部都是有个ADC,内部ADC将原始的模拟数据转换为数字量,然后通过其他的通信接口,比如IIC、SPI等传输给SOC。
因此,当你使用的传感器本质是ADC或DAC器件的时候,可以优先考虑使用IIO驱动框架。
1.1 iio_dev结构体
IIO子系统使用结构体iio_dev来描述一个具体IIO设备,此设备结构体定义在include/linux/iio/iio.h文件中,结构体内容如下:
571 struct iio_dev { 572 int modes; 573 struct device dev; 574 575 struct iio_buffer *buffer; 576 int scan_bytes; 577 struct mutex mlock; 578 579 const unsigned long *available_scan_masks; 580 unsigned masklength; 581 const unsigned long *active_scan_mask; 582 bool scan_timestamp; 583 struct iio_trigger *trig; 584 struct iio_poll_func *pollfunc; 585 struct iio_poll_func *pollfunc_event; 586 587 struct iio_chan_spec const *channels; 588 int num_channels; 589 590 const char *name; 591 const char *label; 592 const struct iio_info *info; 593 const struct iio_buffer_setup_ops *setup_ops; 594 595 void *priv; 596 };
我们来看一下iio_dev结构体中几个比较重要的成员变量:
第571行,modes为设备支持的模式,可选择的模式如表所示:
模式 | 描述 |
---|---|
INDIO_DRECT_MODE | 提供sysfs接口 |
INDIO_BUFFER_TRIGGERED | 支持硬件缓冲触发 |
INDIO_BUFFER_SOFTWARE | 支持软件缓冲触发 |
INDIO_BUFFER_HARDWARE | 支持硬件缓冲区 |
第575行,buffer为缓冲区
第576行,scan_bytes为捕获到,并且提供给缓冲区的字节数。
第579行,available_scan_masks为可选的扫描位掩码,使用触发缓冲区的时候可以通过设置掩码来确定使能哪些通道,使能以后的通道会将捕获到的数据发送到IIO缓冲区。
第581行,active_scan_mask为缓冲区已经开启的通道掩码。只有这些使能了的通道数据才能被发送到缓冲区。
第582行,scan_timestamp为扫描时间戳,如果使能以后会将捕获时间戳放到缓冲区里面。
第583行,trig为IIO设备当前触发器,当使用缓冲模式的时候。
第584行,pollfunc为一个函数,在接收到的触发器上运行。
第587行,channels为IIO设备通道,为iio_chan_spec结构体类型,稍后会详细讲解IIO通道。
第588行,num_channels为IIO设备的通道数。
第590行,name为IIO设备名字。
第592行,info为iio_info结构体类型,这个结构体里面有很多函数,需要驱动开发人员编写,非常重要!我们从用户空间读取IIO设备内部数据,最终调用的就是iio_info里面的函数。稍后会详细讲解iio_info结构体。
第593行,setup_ops为 iio_buffer_setup_ops 结构体类型,内容如下:
530 struct iio_buffer_setup_ops { 531 int (*preenable)(struct iio_dev *); 532 int (*postenable)(struct iio_dev *); 533 int (*predisable)(struct iio_dev *); 534 int (*postdisable)(struct iio_dev *); 535 bool (*validate_scan_mask)(struct iio_dev *indio_dev, 536 const unsigned long *scan_mask); 537 };
可以看出iio_buffer_setup_ops 里面都是一些回调函数,在使能或禁用缓冲区的时候会调用这些函数。如果未指定的话就默认使用iio_triggered_buffer_setup_ops
1.2 iio_info结构体
iio_dev有个成员变量: info,为iio_info结构体指针变量,这个是我们在编写IIO驱动的时候需要着重去实现的,因为用户空间对设备的具体操作最终都会反映到iio_info里面。iio_info结构体定义在 include/linux/iio/liio.h中,结构体定义如下(有省略):
445 struct iio_info { 446 const struct attribute_group *event_attrs; 447 const struct attribute_group *attrs; 448 449 int (*read_raw)(struct iio_dev *indio_dev, 450 struct iio_chan_spec const *chan, 451 int *val, 452 int *val2, 453 long mask); 454 455 int (*read_raw_multi)(struct iio_dev *indio_dev, 456 struct iio_chan_spec const *chan, 457 int max_len, 458 int *vals, 459 int *val_len, 460 long mask); 461 462 int (*read_avail)(struct iio_dev *indio_dev, 463 struct iio_chan_spec const *chan, 464 const int **vals, 465 int *type, 466 int *length, 467 long mask); 468 469 int (*write_raw)(struct iio_dev *indio_dev, 470 struct iio_chan_spec const *chan, 471 int val, 472 int val2, 473 long mask); 474 ...... }
第447行,attrs是通用的设备属性。
第449和469行,分别为read_raw和 write_raw函数,这两个函数就是最终读写设备内部数据的操作函数,需要程序编写人员去实现的。比如应用读取一个ms1112的原始数据,那么最终完成工作的就是read_raw函数,我们需要在read_raw函数里面实现对ms1112芯片的读取操作。同理,write_raw是应用程序向ms1112芯片写数据,一般用于配置芯片,比如量程、数据速率等。这两个函数的参数都是一样的,我们依次来看一下:
indio_dev:需要读写的IIO设备。
chan:需要读取的通道。
val,val2:对于read_raw函数来说 val和 val2这两个就是应用程序从内核空间读取到数据,一般就是传感器指定通道值,或者传感器的量程、分辨率等。对于 write_raw来说就是应用程序向设备写入的数据。val和 val2共同组成具体值,val是整数部分,val2是小数部分。但是val2也是对具体的小数部分扩大N倍后的整数值,因为不能直接从内核向应用程序返回一个小数值。比如现在有个值为1.00236,那么val就是1,vla2理论上来讲是0.00236,但是我们需要对0.00236扩大N倍,使其变为整数,这里我们扩大 1000000倍,那么val2就是2360。因此val=1,val2=2360。扩大的倍数我们不能随便设置,而是要使用Linux定义的倍数,Linux 内核里面定义的数据扩大倍数,或者说数据组合形式如表所示:
组合宏 | 描述 |
---|---|
IIO_VAL_INT | 整数值,没有小数。比如5000,那么就是val=5000,不需要设置val2 |
IIO_VAL_INT_PLUS_MICRO | 小数部分扩大1000000倍,比如1.00236,此时val=1,val2=2360。 |
IIO_VAL_INT_PLUS_NANO | 小数部分扩大1000000000倍,同样是1.00236,此时val=1,val2=2360000。 |
IIO_VAL_INT_PLUS_MICRO_DB | dB数据,和 IIO_VAL_INT_PLUS_MICRO数据形式一样,只是在后面添加 db。 |
IIO_VAL_INT_MULITIPLE | 多个整数值,比如一次要传回6个整数值,那么val 和val2就不够用了.此宏主要用于iio_info的read_raw_multi函数。 |
IIO_VAL_FRACTIONAL | 分数值,也就是val/val2。比如val=1, val2=4,那么实际值就是1/4。 |
IIO_VAL_FRACTIONAL_LOG2 | 值为val>>val2,也就是val右移val2位。比如 val=25600,val2=4,那么真正的值就是 25600右移4位﹐25600>>4=1600. |
mask:掩码,用于指定我们读取的是什么数据,我们只有读出原始值以及对应的分辨率(量程),才能计算出真实的电压值。此时就有两种数据值:传感器原始值、分辨率。Linux 内核使用IIO_CHAN_INFO_RAW和IIO_CHAN_INFO_SCALE这两个宏来表示原始值以及分辨率,这两个宏就是掩码。至于每个通道可以采用哪几种掩码,这个在我们初始化通道的时候需要驱动编写人员设置好。掩码有很多种,稍后讲解IIO通道的时候详细讲解!
第462行的read_avail这种函数指针通常用于嵌入式系统中,特别是在驱动程序或数据采集模块中。它允许系统在运行时动态地选择或调用不同的读取可用数据的函数,具体操作可能涉及从传感器或其他数据源中获取数据,并将数据类型、长度等信息返回给调用者。
1.3 iio_chan_spec结构体
iio的核心就是通道,一个传感器可能有多路数据,比如一个ms1112ADC芯片支持4路数据采集,那么它就有4个通道。
Linux内核使用iio_chan_spec结构体来描述通道,定义在include/linux/iio/iio.h文件中,内容如下:
238 struct iio_chan_spec { 239 enum iio_chan_type type; 240 int channel; 241 int channel2; 242 unsigned long address; 243 int scan_index; 244 struct { 245 char sign; 246 u8 realbits; 247 u8 storagebits; 248 u8 shift; 249 u8 repeat; 250 enum iio_endian endianness; 251 } scan_type; 252 long info_mask_separate; 253 long info_mask_separate_available; 254 long info_mask_shared_by_type; 255 long info_mask_shared_by_type_available; 256 long info_mask_shared_by_dir; 257 long info_mask_shared_by_dir_available; 258 long info_mask_shared_by_all; 259 long info_mask_shared_by_all_available; 260 const struct iio_event_spec *event_spec; 261 unsigned int num_event_specs; 262 const struct iio_chan_spec_ext_info *ext_info; 263 const char *extend_name; 264 const char *datasheet_name; 265 unsigned modified:1; 266 unsigned indexed:1; 267 unsigned output:1; 268 unsigned differential:1; 269 };
来看一下iio_chan_spec结构体中一些比较重要的成员变量:
第239行,type为通道类型,iio_chan_type是一个枚举类型,列举出了可以选择的通道类型,定义在include/uapi/linux/iio/types.h文件里面,内容如下:
enum iio_chan_type { IIO_VOLTAGE, //电压 IIO_CURRENT, //电流 IIO_POWER, //功率 IIO_ACCEL, //加速度 IIO_ANGL_VEL, //角速度 IIO_MAGN, //磁场强度 IIO_LIGHT, //光强度 IIO_INTENSITY, //强度 IIO_PROXIMITY, //接近度 IIO_TEMP, //温度 IIO_INCLI, //倾斜度 IIO_ROT, //旋转 IIO_ANGL, //角度 IIO_TIMESTAMP, //时间戳 IIO_CAPACITANCE, //电容 IIO_ALTVOLTAGE, //备用电压 IIO_CCT, //色温 IIO_PRESSURE, //压力 IIO_HUMIDITYRELATIVE, //相对湿度 IIO_ACTIVITY, //活动状态 IIO_STEPS, //步数 IIO_ENERGY, //能量 IIO_DISTANCE, //距离 IIO_VELOCITY, //速度 IIO_CONCENTRATION, //浓度 IIO_RESISTANCE, //电阻 IIO_PH, //pH 值 IIO_UVINDEX, //紫外线指数 IIO_ELECTRICALCONDUCTIVITY, //电导率 IIO_COUNT, //计数 IIO_INDEX, //索引 IIO_GRAVITY, //重力 IIO_POSITIONRELATIVE, //相对位置 IIO_PHASE, //相位 IIO_MASSCONCENTRATION, //质量浓度 };
可以看出,目前Linux内核支持的传感器类型非常丰富,而且支持类型也会不断的增加。ms1112是ADC,那就是IIO_VOLTAGE类型。
继续来看iio_chan_spec结构体,第266行,当成员变量indexed 为1时候,channel为通道索引。
第265行,当成员变量modified为1的时候,channel2为通道修饰符。通道修饰符主要是影响sysfs下的通道文件名字,后面我们会讲解sysfs下通道文件名字组成形式。
第242行的address成员变量用户可以自定义,但是一般会设置为此通道对应的芯片数据寄存器地址。address 也可以用作其他功能,自行选择,也可以不使用address,一切以实际情况为准。
第243行,当使用触发缓冲区的时候,scan_index是扫描索引。
第244~251,scan_type是一个结构体,描述了扫描数据在缓冲区中的存储格式。我们依次来看一下scan_type各个成员变量的涵义:
scan_type.sign:如果为‘u’表示数据为无符号类型,为‘s’的话为有符号类型。
scan_type.realbits:数据真实的有效位数,比如很多传感器说的10位ADC,其真实有效数据就是10位。
scan_type.storagebits:存储位数,有效位数+填充位。比如有些传感器ADC是12位的,那么我们存储的话肯定要用到2个字节,也就是16位,这16位就是存储位数。
scan_type.shift:右移位数,也就是存储位数和有效位数不一致的时候,需要右移的位数,这个参数不总是需要,一切以实际芯片的数据手册位数。
scan_type.repeat:实际或存储位的重复数量。
scan_type.endianness:数据的大小端模式,可设置为IIO_CPU、IIO_BE(大端)或IIO_LE(小端)。
第252行,info_mask_separate标记某些属性专属于此通道,include/linux/iio/types.h文件中的iio chan_info_enum枚举类型描述了可选的属性值。
第254行,info_mask_shared_by_type标记导出的信息由相同类型的通道共享。也就是iio_chan_spec.type成员变量相同的通道。
第256行,info_mask_shared_by_dir标记某些导出的信息由相同方向的通道共享。
第258行,info_mask_shared_by_all表设计某些信息所有的通道共享,无论这些通道的类型、方向如何,全部共享。
第265行,modified为1的时候,channel2为通道修饰符。
第266行,indexed为1的时候,channel为通道索引。
第267行,output表示为输出通道。
第268行,differential表示为差分通道。
2. 程序编写
2.1 驱动程序编写
主要结构体:
static const struct iio_info ms1112_info = { .read_raw = ms1112_read_raw, .write_raw = ms1112_write_raw, .read_avail = ms1112_read_avail, };
struct ms1112_data { struct ms1112_channel_data channel_data[MS1112_CHANNELS]; struct ms1112_thresh_data thresh_data[MS1112_CHANNELS]; const struct ms1112_chip_data *chip; struct mutex lock; void *private_data; /* 私有数据 */ //int16_t value; /* adc数据 */ struct i2c_client *client; };
主要函数:
static int ms1112_get_adc_result(struct ms1112_data *data, int chan, int *val) { int ret, pga, dr , mode; unsigned int mask, cfg; ret = 0; if (chan < 0 || chan >= MS1112_CHANNELS) return -EINVAL; mode = data->channel_data[chan].mode; pga = data->channel_data[chan].pga; dr = data->channel_data[chan].data_rate; mask = MS1112_CFG_MUX_MASK | MS1112_CFG_PGA_MASK | MS1112_CFG_DR_MASK | MS1112_CFG_MOD_MASK | MS1112_SINGLESHOT << 7; cfg = chan << MS1112_CFG_MUX_SHIFT | pga << MS1112_CFG_PGA_SHIFT | dr << MS1112_CFG_DR_SHIFT | mode << MS1112_CFG_MOD_SHIFT | MS1112_SINGLESHOT << 7; printk("mask = %u\n", mask); printk("cfg = %u\n", cfg); cfg = (cfg & mask); #if 0 ms1112_write_reg(data, MS1112_CFG_REG, MS1112_DEFAULT_CONFIG); #else ms1112_write_reg(data, MS1112_CFG_REG, cfg); #endif ret = ms1112_readdata(data,val); if(ret<0) { printk("Failed to read data!\n"); } return ret; }
static int ms1112_read_avail(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, const int **vals, int *type, int *length, long mask) { struct ms1112_data *data = iio_priv(indio_dev); if (chan->type != IIO_VOLTAGE) return -EINVAL; switch (mask) { case IIO_CHAN_INFO_SCALE: *type = IIO_VAL_FRACTIONAL_LOG2; *vals = data->chip->scale; *length = data->chip->scale_len; return IIO_AVAIL_LIST; case IIO_CHAN_INFO_SAMP_FREQ: *type = IIO_VAL_INT; *vals = data->chip->data_rate; *length = data->chip->data_rate_len; return IIO_AVAIL_LIST; default: return -EINVAL; } }
static int ms1112_read_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int *val, int *val2, long mask) { int ret, idx; struct ms1112_data *data = iio_priv(indio_dev); mutex_lock(&data->lock); switch (mask) { case IIO_CHAN_INFO_RAW: ret = iio_device_claim_direct_mode(indio_dev); if (ret) break; ret = ms1112_get_adc_result(data, chan->address, val); if (ret < 0) { goto release_direct; } *val = sign_extend32(*val >> chan->scan_type.shift, chan->scan_type.realbits - 1); ret = IIO_VAL_INT; release_direct: iio_device_release_direct_mode(indio_dev); break; case IIO_CHAN_INFO_SCALE: idx = data->channel_data[chan->address].pga; *val = ms1112_fullscale_range[idx]; *val2 = chan->scan_type.realbits - 1; ret = IIO_VAL_FRACTIONAL_LOG2; break; case IIO_CHAN_INFO_SAMP_FREQ: idx = data->channel_data[chan->address].data_rate; *val = data->chip->data_rate[idx]; ret = IIO_VAL_INT; break; default: ret = -EINVAL; break; } mutex_unlock(&data->lock); return ret; }
static int ms1112_write_raw(struct iio_dev *indio_dev, struct iio_chan_spec const *chan, int val, int val2, long mask) { struct ms1112_data *data = iio_priv(indio_dev); int ret; mutex_lock(&data->lock); switch (mask) { case IIO_CHAN_INFO_SCALE: ret = ms1112_set_scale(data, chan, val, val2); break; case IIO_CHAN_INFO_SAMP_FREQ: ret = ms1112_set_data_rate(data, chan->address, val); break; default: ret = -EINVAL; break; } mutex_unlock(&data->lock); return ret; }
static int ms1112_probe(struct i2c_client *client,const struct i2c_device_id *id) { struct iio_dev *indio_dev; const struct ms1112_chip_data *chip; struct ms1112_data *data; int ret; int i; chip = device_get_match_data(&client->dev); if (!chip) chip = (const struct ms1112_chip_data *)id->driver_data; if (!chip) return dev_err_probe(&client->dev, -EINVAL, "Unknown chip\n"); indio_dev = devm_iio_device_alloc(&client->dev, sizeof(*indio_dev)); if (!indio_dev) return -ENOMEM; data = iio_priv(indio_dev); i2c_set_clientdata(client, indio_dev); mutex_init(&data->lock); indio_dev->name = MS1112_DRV_NAME; indio_dev->info = chip->info; indio_dev->modes = INDIO_DIRECT_MODE; indio_dev->channels = chip->channels; indio_dev->num_channels = chip->num_channels; data->chip = chip; data->client = client; for (i = 0; i < MS1112_CHANNELS; i++) { int realbits = indio_dev->channels[i].scan_type.realbits; data->thresh_data[i].low_thresh = -1 << (realbits - 1); data->thresh_data[i].high_thresh = (1 << (realbits - 1)) - 1; } /* we need to keep this ABI the same as used by hwmon ADS1015 driver */ ms1112_get_channels_config(client); ret = iio_device_register(indio_dev); printk("ret = %d\n",ret); if (ret) dev_err(&client->dev, "Failed to register IIO device\n"); return ret; }
2.2 测试程序
#include <sys/time.h> #include <signal.h> #include <fcntl.h> #include <errno.h> /* 字符串转数字,将浮点小数字符串转换为浮点数数值 */ #define SENSOR_FLOAT_DATA_GET(ret, index, str, member)\ ret = file_data_read(file_path[index], str);\ dev->member = atof(str);\ /* 字符串转数字,将整数字符串转换为整数数值 */ #define SENSOR_INT_DATA_GET(ret, index, str, member)\ ret = file_data_read(file_path[index], str);\ dev->member = atoi(str);\ /* adc iio框架对应的文件路径 */ static char *file_path[] = { "/sys/bus/iio/devices/iio:device0/in_voltage1_scale", "/sys/bus/iio/devices/iio:device0/in_voltage1_raw", }; /* 文件路径索引,要和file_path里面的文件顺序对应 */ enum path_index { IN_VOLTAGE_SCALE = 0, IN_VOLTAGE_RAW, }; /* * ADC数据设备结构体 */ struct adc_dev{ int raw; float scale; float act; }; struct adc_dev ms1112adc; /* * @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 : 获取ADC数据 * @param - dev : 设备结构体 * @return : 0 成功;其他 失败 */ static int adc_read(struct adc_dev *dev) { int ret = 0; char str[50]; SENSOR_FLOAT_DATA_GET(ret, IN_VOLTAGE_SCALE, str, scale); SENSOR_INT_DATA_GET(ret, IN_VOLTAGE_RAW, str, raw); /* 转换得到实际电压值mV */ dev->act = (dev->scale * dev->raw)/1000.f; return ret; } /* * @description : main主程序 * @param – argc : argv数组元素个数 * @param - argv : 具体参数 * @return : 0 成功;其他 失败 */ int main(int argc, char *argv[]) { int ret = 0; if (argc != 1) { printf("Error Usage!\r\n"); return -1; } while (1) { ret = adc_read(&ms1112adc); if(ret == 0) { /* 数据读取成功 */ \ printf("ADC原始值:%d,电压值:%.3fV\r\n", ms1112adc.raw, ms1112adc.act); } usleep(100000); /*100ms */ } return 0; }
3. 实验测试
加载驱动后可以看到在开发板的/sys/bus/iio/devices下有iio:device0设备,这个设备正式我们ms1112ADC芯片的sysfs文件信息,进入该目录,可以显示该芯片的目录信息
解释如下:
in_voltage*_raw:对应该通道采样的adc原始值
in_voltage*_sampling_frequency:对应通道的采样频率
in_voltage*_scale:用于存放对应通道输入电压比例因子
举个例子我们cat in_voltage0_scale
显示0.062500000,那这个值是如何算出的呢
其实我们不难发现,
我们的结果寄存器一共16位,最高位为符号位,因此结果寄存器的adc采样值范围为-215~215,即-32768-32767。
我们在IIO_CHAN_INFO_SCALE掩码情况下,设置的组合宏为IIO_VAL_FRACTIONAL_LOG2,值为val>>val2,也就是val右移val2位。假设满量程,则val值为2048,val2为15,2048/(2^15)=0.0625。刚好等于in_voltage0_scale的值。
这里我们测试voltage1通道,voltage1为AIN1,AIN1接MIKRO-BUS,我们只需接一个烟雾传感器,即可测试
烟雾传感器连接:
输入insmod ms1112.ko
加载驱动,./adcAPP
运行测试程序
我们可以点燃纸张,进行测试
点燃纸张前:
点燃纸张后:
我们发现点燃后电压值降低。