ADC驱动程序编写

一. adc框架架构

adc框架 = dts框架 + paltform框架 + iio框架

dts框架,前文设备树子设备的框架。

platform框架,简化设备与驱动的联系,api接口更隐蔽了。

iio框架,专门用于管理adc或dac驱动程序。

二. adc驱动开发流程

有一个新的编写程序框架概念:从结尾往源头编写驱动程序。下面是编写的一个思路流程:

驱动程序

├─ 驱动入口函数
│  ├─ 注册平台设备驱动
│     └─ 平台设备驱动结构体--adc_driver
├─ 驱动出口函数
│  ├─ 注销平台设备驱动
│     └─ 平台设备驱动结构体--adc_driver
├─ 平台设备驱动结构体--adc_driver
│  ├─ probe
│  ├─ remove
│  └─ driver
│     └─ of_match_table
├─ probe
│  ├─ 注册字符设备驱动
│  └─ 获取iio通道
├─ remove
│  └─ 注销字符设备驱动
├─ of_match_table
├─ file_operations--操作函数集
│     ├─ .owner
│     ├─ .open
│     ├─ .write
│     ├─ .read
│     └─ .adc_dev_ioctl
└─ 编写操作函数

总体驱动开发流程如上所示,其中主要以dts框架下注册一个字符驱动设备为核心,在其中通过platform平台设备开发简化设备树注册流程,再通过iio驱动的api接口–ioctl函数,去读取adc数据。

其中有个新的知识点,iio驱动的api接口,通过接受命令符,使用api接口使用adc通道,最终读取数据发送至用户空间。

/*********************************************************************
 * @fn      adc_dev_ioctl
 *
 * @brief   iio子系统的api函数,完成指定命令,主要为读取数据

 * @param   file:读取设备参数    cmd:ioctl操作的命令     arg:用户空间的地址

 * @return  none
 */
long adc_dev_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
    int error;      //错误判断参数

    int scale;


    switch (cmd)
    {   
    //情况1
    case CMD_READ_SCALE:
        error = iio_read_channel_raw(adcdevice.adc_channel, &scale);          //iio子系统api函数:获取adc通道数据
        //错误判断
        if(error != 0)
        {
            printk("iio read channel error!!! \n");
            return -1;
        }

        error = copy_to_user((int *)arg, &scale, sizeof(scale));
        if(error != 0)
        {
            printk("copy to user error!!! \n");
            return -2;
        }


        break;

    //默认情况
    default:
        break;

    }

    return 0;
}

App应用程序编写

一. Linux文件流读取

在ADC驱动编写中,需要连续不断的读取传感器数据,该情况下,不能使用cat命令一次次读取数据,需要编写对应的APP软件。同时对比ioctl,文件流读取方式专门对项目中需要读取数据的部分,ioctliio通道的api函数,面对少量数据的读取,且有一定的泄露风险(tip:因为通过命令的形式去使得读取数据)。

接下来介绍几个关键的API函数。

1. 打开文件流

打开文件流使用fopen函数,函数原型如下:
FILE *fopen(const char *pathname, const char *mode)

函数参数和返回值含义如下:
pathname:需要打开的文件流路径。
mode:打开方式,可选的打开方式如下图:
![[mode打开方式.png]]

返回值:NULL,打开错误;其他值,打开成功的文件流指针,为 FILE 类型。

2. 关闭文件流

关闭文件流使用函数 fclose,函数原型如下:
int fclose(FILE *stream)

函数参数和返回值含义如下:
stream:要关闭的文件流指针。

返回值:0,关闭成功;EOF,关闭错误。

3. 读取文件流

要读取文件流使用 fread 函数,函数原型如下:
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream)

fread 函数用于从给定的输入流中读取最多 nmemb 个对象到数组 ptr 中,函数参数和返回值含义如下:
ptr:要读取的数组中首个对象的指针。
size:每个对象的大小。
nmemb:要读取的对象个数。
stream:要读取的文件流。

返回值:返回读取成功的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值(或者 0)。

4. 写文件流

要向文件流写入数据,使用 fwrite 函数,函数原型如下:
size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);

fwrite 函数用于向给定的文件流中写入最多 nmemb 个对象,函数参数和返回值含义如下:
ptr:要写入的数组中首个对象的指针。
size:每个对象的大小。
nmemb:要写入的对象个数。
stream:要写入的文件流。

返回值:返回成功写入的对象个数,如果出现错误或到文件末尾,那么返回一个短计数值(或者 0)。

5. 格式化输入文件流

fscanf 函数用于从一个文件流中格式化读取数据,fscanf 函数在遇到空格和换行符的时候就会结束。前面我们说了 IIO 框架下的 sysfs 文件内容都是字符串,因此我们在读取的时候就需要使用字符串读取格式。在这里就可以使用 fscanf 函数来格式化读取文件内容,函数原型如下:
int fscanf(FILE *stream, const char *format, ,[argument...])

fscanf用法和scanf类似,函数参数和返回值含义如下:
stream:要操作的文件流。
format:格式。
argument:保存读取到的数据。

返回值:成功读取到的数据个数,如果读到文件末尾或者读取错误就返回 EOF。

6. 程序源码

该部分程序目的是连续不断读取传感器数据,在程序中,传感器数据保存在xx文件中,通过file_data_read函数,去获取传感器数据。

 /*
 * @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"); /* r:只读打开 */
    if(data_stream == NULL) {
		printf("can't open file %s\r\n", filename);
		return -1;
	}
    //fscanf:格式化读取文件内容---IIO框架下的sysfs文件内容都是字符串的形式,因此读取时需用字符串的读取方式
	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;
}

程序主要流程如下,fopen函数将文件以只读打开返回文件流指针到data_streamdata_stream为该文件的的唯一句柄,后续操作都需通过该句柄访问文件;fscanf函数格式化读取文件内容到str;最后通过fclose关闭文件。

二. 读取adc数据

读取adc数据部分主要由调用其他函数完成,程序如下:

 /*
 * @description	: 获取adc数据
 * @param - dev : 设备结构体
 * @return 		: 0 成功;其他 失败
 */
static int sensor_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_VOLTAGE3_RAW, str, raw);

	/* 转换得到实际的电压值 */
	dev->act = (dev->scale * dev->raw)/1000.f;
	return ret;
}

其中,SENSOR_FLOAT_DATA_GETSENSOR_INT_DATA_GET函数实际为调用上一章节读取文件流数据函数,且其中文件路径file_path[index]需与sensor_read函数中文件索引对应。

/* 字符串转数字,将浮点小数字符串转换为浮点数数值 */
#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_voltage_scale",
	"/sys/bus/iio/devices/iio:device0/in_voltage3_raw",
};

/* 文件路径索引,要和file_path里面的文件顺序对应 */
enum path_index {
	IN_VOLTAGE_SCALE = 0,
	IN_VOLTAGE3_RAW,
};

三. 应用主程序

理解完上述两个函数的程序原理,接下来的main函数编写就十分简单了。在main函数中,在循环内一直调用sensor_read函数,并将获得的数据储存在结构体adc内,然后不断打印出结构体内的值。

/*
 * adc数据设备结构体
 */
struct adc_dev{
	int raw;
	float scale;
	float act;
};

struct adc_dev adc;

/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int ret = 0;
	int fd;

	if (argc != 1) {
		printf("Error Usage!\r\n");
		return -1;
	}

    open("/dev/adc", O_RDWR);   //O_RDWR:打开驱动程序读和写
    if(fd < 0)
    {
        printf("open error\n");
        return -1;
    }

	while (1) {
		ret = sensor_read(&adc);
		if(ret == 0) { 			/* 数据读取成功 */
			printf("\r\n原始值:\r\n");
			printf("ADC原始值:%d,电压值:%.3fV\r\n", adc.raw, adc.act);       
            /*  %.3f:格式化输出字符串,以规范的格式输出float型,占宽3列右对齐
                不足三位在前面有空格补齐,多于三位以实际宽度输出。小数点也占1位。
            */
		}
		usleep(100000); /*100ms */
	}
	return 0;
}

至此,通过文件流方式读取数据App应用程序编写完成。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐