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