若该文为原创文章,转载请注明原文出处。
一、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
测试正常,可以旋转板子上的滑动变阻器。
如有侵权,或需要完整代码,请及时联系博主。