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

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

一、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

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

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

相关推荐
不会调制解调的猫13 小时前
笔记 | 内网服务器通过wifi穿透,设置流量走向
运维·服务器·笔记
程序员大雄学编程14 小时前
「机器学习笔记7」决策树学习:从理论到实践的全面解析(上)
笔记·决策树·机器学习
聪明的笨猪猪15 小时前
Java Spring “Bean” 面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
bnsarocket16 小时前
Verilog和FPGA的自学笔记3——仿真文件Testbench的编写
笔记·fpga开发·verilog·自学
丰锋ff16 小时前
2025 年真题配套词汇单词笔记(考研真相)
笔记·考研
小熊猫程序猿18 小时前
Datawhale 算法笔记 AI硬件与机器人大模型 (五) Isaac Sim 入门
人工智能·笔记·机器人
不太可爱的叶某人1 天前
【学习笔记】kafka权威指南——第10章 监控kafka (7-10章只做了解)
笔记·学习·kafka
张人玉1 天前
C# TCP 客户端开发笔记(TcpClient)
笔记·tcp/ip·c#
不太可爱的叶某人1 天前
【学习笔记】kafka权威指南——第6章 可靠的数据传递
笔记·学习·kafka
研猛男1 天前
0、FreeRTOS编码和命名规则
笔记·stm32·freertos