RK3568笔记四十八:ADC驱动开发测试

avatar
作者
猴君
阅读量:0

若该文为原创文章,转载请注明原文出处。

一、ADC介绍

RK3568集成了一个逐次逼近模数转换器(Successive Approximation ADC),通常简称为SAR ADC。

这种转换器能够将连续的模拟信号转换为离散的数字信号,其特点在于具有较高的分辨率和转换速度。

在RK3568中,这个SAR ADC支持六通道单端10位的SAR-ADC,时钟频率必须小于13MHZ。

从硬件角度来看,RK3568的ADC接口包括温度传感器(Temperature Sensor)和逐次逼近ADC(Successive Approximation Register)两种类型。

TSADC 具有高达 50KS/s 的采样率,支持两通道,温度范围在-20℃~120℃和 5℃ 温度分辨率。

SARADC 具有高达 1MS/s 的采样率,支持八通道单端 10 位,时针频率要小于13MHZ。

其中,SAR-ADC支持六通道单端10位的转换,这六个通道分别对应处理器上的六个引脚。

每个通道都可以独立配置并执行AD转换,这使得RK3568能够灵活地满足不同应用场景的需求。

在软件层面,RK3568的ADC模块通过设备树(Device Tree)进行描述和配置。

二、硬件原理

ATK-DLRK        3568 开发板 ADC 硬件原理图

根据手册 SARADC_VIN3是ADC3 , 通过ADC3接口来采集 VR1 可调电位器的电压.注意: ADC 的采集电压绝对值最大是 1.8V,请不要超过 1.8V,否则可能对芯片造成损坏

三、设备树修改

修改/home/alientek/rk3568_linux_sdk/kernel/arch/arm64/boot/dts/rockchip/rk3568-atk-evb1-ddr4- v10.dtsi 文件,添加adc节点。

 adc_vol: adc_vol { 	 status = "okay"; 	 compatible = "yifeng,rk356x-adc"; 	 io-channels = <&saradc 3>; 	 vref-supply = <&vcca_1v8>;  };  

修改后重新编译内核,并重新烧写boot.img文件。

三、驱动程序

1、adc_drv.c

/*  adc_vol: adc_vol { 	 status = "okay"; 	 compatible = "yifeng,rk356x-adc"; 	 io-channels = <&saradc 3>; 	 vref-supply = <&vcca_1v8>;  };  */  #include <linux/moduleparam.h> #include <linux/iio/iio.h> #include <linux/iio/machine.h> #include <linux/iio/driver.h> #include <linux/iio/consumer.h> #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/ide.h> #include <linux/init.h> #include <linux/module.h> #include <linux/errno.h> #include <linux/gpio.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/of_gpio.h> #include <linux/semaphore.h> #include <linux/timer.h> #include <linux/irq.h> #include <linux/wait.h> #include <linux/poll.h> #include <linux/fs.h> #include <linux/fcntl.h> #include <linux/platform_device.h> #include <asm/uaccess.h> #include <asm/io.h>   #define VREF 1800 #define ADCDEV_CNT           1              /* 设备号长度 */ #define ADCDEV_NAME          "adc_drv"   /* 设备名字 */   struct adcdev_dev{   dev_t devid;                  /* 设备号 */   struct cdev cdev;             /* cdev */   struct class *class;          /* 类 */   struct device *device;        /* 设备 */    struct device_node *node;     /* adcdev 设备节点 */   struct iio_channel *chan;     /* 通道 */ };   struct adcdev_dev adcdev; /* adcd ev 设备 */  /* * @description : 打开设备 * @param – inode : 传递给驱动的 inode * @param - filp : 设备文件,file 结构体有个叫做 private_data 的成员变量 * 一般在 open 的时候将 private_data 指向设备结构体。 * @return : 0 成功;其他 失败 */ static int adc_open(struct inode *inode, struct file *filp) {   printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);      return 0; }     /*     linux driver 驱动接口:      实现对应的open/read/write等函数,填入file_operations结构体 */ static ssize_t adc_drv_read ( struct file *file, char __user *buf,                                  size_t size, loff_t *offset) {   int ret = 0, raw = 0;   int result = -1;      printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);     ret = iio_read_channel_raw(adcdev.chan, &raw);   if (ret < 0) {     printk("read hook adc channel() error: %d\n", ret);     return -1;   }      result = (VREF*raw)/1023;   printk(" out! raw= %d Voltage=%dmV\n",raw, result);    ret = copy_to_user(buf, &result, sizeof(result)); 	   return ret;   }       /* * @description : 向设备写数据 * @param - filp : 设备文件,表示打开的文件描述符 * @param - buf : 要写给设备写入的数据 * @param - cnt : 要写入的数据长度 * @param - offt : 相对于文件首地址的偏移 * @return : 写入的字节数,如果为负值,表示写入失败 */ static ssize_t adc_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt) {   printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);      return 0; }   static int adc_drv_close(struct inode *node, struct file *file) {   printk(" %s line %d \r\n",  __FUNCTION__, __LINE__);    return 0; }    /* 设备操作函数 */ static struct file_operations adc_fops = {   .owner   = THIS_MODULE,   .open    = adc_open,   .read    = adc_drv_read,   .write   = adc_write,   .release = adc_drv_close, };      static int atk_adc_probe(struct platform_device *pdev) { 	int ret = -1; 	     printk("atk_adc_probe!\n");       adcdev.chan = iio_channel_get(&(pdev->dev), NULL);     if (IS_ERR(adcdev.chan))     {         adcdev.chan = NULL;         printk("%s() have not set adc chan\n", __FUNCTION__);         return -1;     } 	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 	/* 1、设置设备号 */     ret = alloc_chrdev_region(&adcdev.devid, 0, ADCDEV_CNT, ADCDEV_NAME);     if(ret < 0) { 		pr_err("%s Couldn't alloc_chrdev_region, ret=%d\r\n", ADCDEV_NAME, ret);     }     printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 	/* 2、初始化 cdev */ 	adcdev.cdev.owner = THIS_MODULE; 	cdev_init(&adcdev.cdev, &adc_fops);     printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 	/* 3、添加一个 cdev */ 	ret = cdev_add(&adcdev.cdev, adcdev.devid, ADCDEV_CNT); 	if(ret < 0) 		goto del_unregister; 	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);   	/* 4、创建类 */ 	adcdev.class = class_create(THIS_MODULE, ADCDEV_NAME); 	if (IS_ERR(adcdev.class)) { 		goto del_cdev; 	} 	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__);   	/* 5、创建设备 */ 	adcdev.device = device_create(adcdev.class, NULL, adcdev.devid, NULL, ADCDEV_NAME); 	if (IS_ERR(adcdev.device)) { 		goto destroy_class; 	} 	 	printk("%s %s line %d\n", __FILE__, __FUNCTION__, __LINE__); 	 	return 0;  destroy_class:     class_destroy(adcdev.class); del_cdev:     cdev_del(&adcdev.cdev); del_unregister:     unregister_chrdev_region(adcdev.devid, ADCDEV_CNT);  	return -EIO; }   static int atk_adc_remove(struct platform_device *pdev) {     printk("atk_adc_remove!\n");     iio_channel_release(adcdev.chan);      cdev_del(&adcdev.cdev); /* 删除 cdev */     unregister_chrdev_region(adcdev.devid, ADCDEV_CNT);     device_destroy(adcdev.class, adcdev.devid); /* 注销设备 */     class_destroy(adcdev.class); /* 注销类 */     return 0; }   static const struct of_device_id atk_adc_match[] = {     { .compatible = "yifeng,rk356x-adc" },     {}, };   static struct platform_driver atk_adc_driver = {     .probe      = atk_adc_probe,     .remove     = atk_adc_remove,     .driver     = {         .name   = "yifeng_adc",         .owner  = THIS_MODULE,         .of_match_table = atk_adc_match,     }, };   static int atk_adc_init(void) {     return platform_driver_register(&atk_adc_driver); }    static void atk_adc_exit(void) {     platform_driver_unregister(&atk_adc_driver); }  module_init(atk_adc_init); module_exit(atk_adc_exit);   MODULE_AUTHOR("yifeng"); MODULE_DESCRIPTION(" adc demo driver"); MODULE_ALIAS("platform:yifeng-adc"); MODULE_LICENSE("GPL");  

驱动程序是直接在内核打印电压值,如果想要使用APP来读取,可以增加字符设备read等函数,在使用APP来读取。

2、makefile

KERNELDIR := /home/alientek/rk3568_linux_sdk/kernel ARCH=arm64 CROSS_COMPILE=/opt/atk-dlrk356x-toolchain/usr/bin/aarch64-buildroot-linux-gnu-   export  ARCH  CROSS_COMPILE   CURRENT_PATH := $(shell pwd) obj-m := adc_drv.o   build: kernel_modules   kernel_modules: 	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules clean: 	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

四、编写测试 APP

注意:编写APP测试使用的不是上面的驱动,原子的出厂系统默认已经适配好了,进入/sys/bus/iio/devices 目录下,此目录下就有 ADC 对应的 IIO 设备:iio:deviceX。

ls /sys/bus/iio/devices/iio:deviceX

标准的 IIO 设备文件目录,我们只关心两个文件:

in_voltage3_raw:ADC3 原始值文件。

in_voltage_scale:ADC 比例文件(分辨率),单位为 mV。

实际电压值(mV)= in_voltage3_raw  * in_voltage_scale。

1、编写测试 APP

adcApp.c

编译

/opt/atk-dlrk356x-toolchain/bin/aarch64-buildroot-linux-gnu-gcc adcApp.c -o adcApp

测试正常,可以旋转板子上的滑动变阻器。

如有侵权,或需要完整代码,请及时联系博主。

广告一刻

为您即时展示最新活动产品广告消息,让您随时掌握产品活动新动态!