MS1112驱动开发(iio框架)

作者简介: 一个平凡而乐于分享的小比特,中南民族大学通信工程专业研究生在读,研究方向无线联邦学习
擅长领域:驱动开发,嵌入式软件开发,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_DBdB数据,和 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运行测试程序

我们可以点燃纸张,进行测试

点燃纸张前:
在这里插入图片描述

点燃纸张后:

在这里插入图片描述

我们发现点燃后电压值降低。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/716343.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

期货交易中的仓位管理

期货市场就像是一个复杂的游戏场所。由于期货高杠杆双向交易机制的影响&#xff0c;期货交易中错误的容忍度很低&#xff0c;所以期货交易系统中最重要的是风险控制。而风险控制体系最核心的是仓位管理&#xff0c;因为仓位的多少直接影响到潜在损失的大小。 仓位管理指的是账户…

做好程序前设计

不要小看任何一道编程题目&#xff01;一定一定一定要想好之后再动手&#xff01;&#xff01;&#xff01; 带上你的草稿本&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xff01;&#xf…

LLoCO技术:突破大型语言模型处理长文本的局限

在自然语言处理领域&#xff0c;大型语言模型&#xff08;LLMs&#xff09;虽然在众多任务上展现出了卓越的能力&#xff0c;但在处理长文本上下文时却遭遇了瓶颈。由于自注意力机制导致的计算和内存开销随序列长度呈二次方增长&#xff0c;使得这些模型在面对长文本时力不从心…

Vue3中的常见组件通信之`provide`、`inject`

Vue3中的常见组件通信之provide、inject 概述 ​ 在vue3中常见的组件通信有props、mitt、v-model、 r e f s 、 refs、 refs、parent、provide、inject、pinia、slot等。不同的组件关系用不同的传递方式。常见的撘配形式如下表所示。 组件关系传递方式父传子1. props2. v-mo…

大数据的力量:推动战略决策和业务转型

在当前全球化的时代背景下&#xff0c;国际间的联系日益紧密&#xff0c;世界变得更加互联互通。面对各种危机&#xff0c;数据驱动决策和分析显得愈发重要。从医学研究到市场趋势分析&#xff0c;大数据技术在各个领域发挥着关键作用&#xff0c;推动着一场深刻的变革浪潮。 大…

关于伪标头那些事

前言 看到伪标头&#xff0c;不少同学可能会比较陌生&#xff0c;因为谁让它默默无闻呢&#xff1f; 当然博主把它比喻为一个来自传输层的“共享盒子”。提到共享&#xff0c;我想大家有所体会了。这里给大家贴一张直观的图例&#xff0c;可以静静观摩之。 Q&#xff1a;什么是…

高端品牌网站建设

随着互联网的快速发展&#xff0c;越来越多的企业开始意识到高端品牌网站建设对于企业发展的重要性。高端品牌网站建设不仅是企业形象展示的窗口&#xff0c;更是与消费者进行有效沟通和互动的桥梁。下面从设计、内容和用户体验三个方面&#xff0c;探讨高端品牌网站建设的重要…

使用代理IP常见问题有哪些?

代理IP在互联网数据收集和业务开展中发挥着重要作用&#xff0c;它充当用户客户端和网站服务器之间的“屏障”&#xff0c;可以保护用户的真实IP地址&#xff0c;并允许用户通过不同的IP地址进行操作。然而&#xff0c;在使用代理IP的过程中&#xff0c;用户经常会遇到一些问题…

php遇到的问题

1、 underfined at line 3 in xxx.php , 错误提示&#xff0c;注释这行代码 // error_reporting(DEBUG ? E_ALL : 0); 目录&#xff1a;config/config.php

爆火的“植物大战僵尸杂交版” ‍♂️【附带安装包】

一、 杂交版植物引言 什么&#xff01;你不会还没听过最近爆火的 《植物大战僵尸杂交版》吧&#xff1f;&#xff01;&#xff1f;&#xff01; 下面这些植物你是否未曾见过&#xff0c;或者已经非常熟悉呢&#xff1f; 二、&#x1f331;&#x1f9df;‍♂️ **杂交版介绍** &…

探索C嘎嘎的奇妙世界:第三关---缺省参数与函数重载

在c语言中,我们常常在对有参函数进行传参,这样的繁琐过程,C祖师爷对此进行了相关改进,多说无益,上干货: 1 缺省参数: 缺省参数是指在声明或定义函数时为函数的形参指定一个默认值&#xff08;默认参数&#xff09;。在调用该函数时&#xff0c;如果没有指定实参&#xff0c;则…

2376.统计特殊整数

链接&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; 题解&#xff1a;. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int countSpecialNumbers(int n) {if (n < 0) {return -1;}std::string str to_string(n);std::vector<std::vector&l…

东南亚短视频:四川鑫悦里文化传媒有限公司

东南亚短视频&#xff1a;探寻异域的魅力与风情 随着短视频平台的兴起&#xff0c;世界各地的风土人情得以通过屏幕展现在我们眼前。而东南亚&#xff0c;这片充满异域魅力的土地&#xff0c;更是成为了短视频创作者们争相探索的热门目的地。今天&#xff0c;就让四川鑫悦里文…

【源码】2024运营版多商户客服系统/在线客服系统/手机客服/PC软件客服端

带客服工作台pc软件源代码&#xff0c;系统支持第三方系统携带参数打开客服链接&#xff0c;例如用户名、uid、头像等 支持多商家&#xff08;多站点&#xff09;支持多商家&#xff08;多站点&#xff09;&#xff0c;每个注册用户为一个商家&#xff0c;每个商家可以添加多个…

element-ui tabs+table 实现点击表格切换标签页

客户需求&#xff1a;点击主任务标签页中的表格 跳转到子任务所在的标签页 代码&#xff1a; 表格部分&#xff1a; <el-tabs type"border-card" :active-name"currentTab" tab-click"handleTabClick"><el-tab-pane class"table…

Fastjson漏洞之CVE-2022-25845

前言&#xff1a; 针对Fastjson之前已经介绍了&#xff0c;这里就不再重复了&#xff0c;漏洞CVE-2017-18349只能用来攻击>1.2.24版本的&#xff0c;CVE-2022-25845属于CVE-2017-18349的升级版&#xff0c;但是目前仅影响到1.2.83以下版本。CVE-2022-25845本质上是绕过了名…

理解并应用:JavaScript响应式编程与事件驱动编程的差异

背景介绍 在现代JavaScript开发中&#xff0c;响应式编程&#xff08;Reactive Programming&#xff09;和事件驱动编程&#xff08;Event-Driven Programming&#xff09;是两种非常重要且常用的编程范式。虽然它们都用于处理异步操作&#xff0c;但在理念和实现方式上存在显…

2 程序的灵魂—算法-2.4 怎样表示一个算法-2.4.2 用流程图表示算法-【例 2.9】

将例 2.4 求 1-1/21/3-1/41/99-1/100 的算用流程图表示。 一个流程图包括&#xff1a; 1. 表示相应操作的框&#xff1b; 2. 带箭头的流程线&#xff1b; 3. 框内外必要的文字说明。

Canvas倒计时

Canvas倒计时 前言 用Canvas绘制一个倒计时组件&#xff0c;显示距离新年还有多长时间&#xff0c;精确到秒&#xff0c;该倒计时需要实时更新 基础知识点 JS Date() 创建一个新Date对象的唯一方法是通过new 操作符&#xff0c;例如&#xff1a;let now new Date(); 若将…

Unity API学习之资源的动态加载

资源的动态加载 在实际游戏开发的更新换代中&#xff0c;随着开发的软件不断更新&#xff0c;我们在脚本中需要拖拽赋值的变量会变空&#xff0c;而要想重新拖拽又太花费时间&#xff0c;因此我们就需要用到Resources.Load<文件类型>("文件名")函数来在一开始…