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

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

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

相关推荐
Nu11PointerException2 小时前
JAVA笔记 | ResponseBodyEmitter等异步流式接口快速学习
笔记·学习
亦枫Leonlew3 小时前
三维测量与建模笔记 - 3.3 张正友标定法
笔记·相机标定·三维重建·张正友标定法
考试宝3 小时前
国家宠物美容师职业技能等级评价(高级)理论考试题
经验分享·笔记·职场和发展·学习方法·业界资讯·宠物
黑叶白树5 小时前
简单的签到程序 python笔记
笔记·python
幸运超级加倍~6 小时前
软件设计师-上午题-15 计算机网络(5分)
笔记·计算机网络
芊寻(嵌入式)7 小时前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
准橙考典8 小时前
怎么能更好的通过驾考呢?
人工智能·笔记·自动驾驶·汽车·学习方法
密码小丑9 小时前
11月4日(内网横向移动(一))
笔记
鸭鸭梨吖10 小时前
产品经理笔记
笔记·产品经理
齐 飞10 小时前
MongoDB笔记01-概念与安装
前端·数据库·笔记·后端·mongodb