基于RK3399Pro的SARADC数据采集-内存映射


目录

原理图

ADC的基础地址

PMUCRU基础地址

SARADC( 逐次逼近型ADC)

RK3399Pro寄存器

SARADC_DATA

SARADC_STAS

SARADC_CTRL

SARADC_DLY_PU_SOC

CRU_CLKSEL_CON26

操作流程

编写驱动代码

函数入口函数

杂项设备

文件操作集

ADC通道设备

ADC值获取

测试代码

编写Makefile文件

测试步骤

编译源码

加载驱动

执行测试程序

实验现象


原理图

ADC的基础地址

adc的基地址是从FF10_0000开始,大小0x10000。

PMUCRU基础地址

PMUCRU基础地址FF75_0000,大小0x10000。

SARADC( 逐次逼近型ADC)

逐次逼近寄存器型(SAR)模数转换器(ADC)占据着大部分的中等至高分辨率 ADC市场。

逐次逼近寄存器型(SAR)模拟数字转换器(ADC)是采样速率低于 5Msps (每秒百万次采样)的中等至高分辨率应用的常见结构。 SAR ADC 的分辨率一般为 8 位至 16 位,具有低功耗、小尺寸等特点。这些特点使该类型 ADC 具有很宽的应用范围,例如便携/电池供电仪表、笔输入量化器、工业控制和数据/信号采集等。

顾名思义, SAR ADC 实质上是实现一种二进制搜索算法。所以,当内部电路运行在数兆赫兹(MHz)时,由于逐次逼近算法的缘故, ADC 采样速率仅是该数值的几分之一。

SAR ADC 的主要优点是低功耗、高分辨率、高精度、以及小尺寸。由于这些优势, SAR ADC 常常与其它更大的功能集成在一起。 SAR 结构的主要局限是采样速率较低,并且其中的各个单元(如 DAC 和比较器),需要达到与整体系统相当的精度 。一般 dsp 和 mcu 中集成的 8 位、 12 位、 16 位 ADC 多数是 SAR 型的,如ADI(Blackfin),STC, silabs 等。

RK3399Pro寄存器

Name Offset Size ResetValue Description
SARADC_DATA 0x0000 W 0x00000000 This register contains the dataafter A/D Conversion.
SARADC_STAS 0x0004 W 0x00000000 The status register of A/DConverter.
SARADC_CTRL 0x0008 W 0x00000000 The control register of A/DConverter
SARADC_DLY_PU_SOC 0x000c W 0x00000000 delay between power up and startcommand

SARADC_DATA

最后ADC转换的数字值,通过读取这个寄存器获得。

Address: Operational Base + offset (0x0000)This register contains the data after A/D Conversion.

Bit Attr Reset Value Description
31:10 RO 0x0 reserved
9:0 RO 0x000 adc_dataA/D value of the last conversion (DOUT[9:0]).

SARADC_STAS

通过这个寄存器判断是否在进行模数转换。

Address: Operational Base + offset (0x0004)The status register of A/D Converter.

Bit Attr Reset Value Description
31:1 RO 0x0 reserved
0 RO 0x0 adc_statusADC status (EOC) 0: ADC stop 1: Conversion in progress

SARADC_CTRL

通过控制寄存器决定是否开始进行模数转换。

Address: Operational Base + offset (0x0008)The control register of A/D Converter.

Bit Attr Reset Value Description
31:7 RO 0x0 reserved
6 RW 0x0 int_statusInterrupt status. This bit will be set to 1 when end-of-conversion. Set 0 to clear the interrupt.
5 RW 0x0

int_enInterrupt enable.

0: Disable

1: Enable

4 RO 0x0 reserved
3 RW 0x0

adc_power_ctrlADC power down control bit

0: ADC power down;

1: ADC power up and reset.

start signal will be asserted (DLY_PU_SOC + 2) sclk clock periodlater after power up

2:0 RW 0x0

adc_input_src_selADC input source selection(CH_SEL[2:0]).

000 : Input source 0 (SARADC_AIN[0])

001 : Input source 1 (SARADC_AIN[1])

010 : Input source 2 (SARADC_AIN[2])

011 : Input source 3 (SARADC_AIN[3])

100 : Input source 4 (SARADC_AIN[4])

101 : Input source 5 (SARADC_AIN[5])

Others : Reserved

SARADC_DLY_PU_SOC

Address: Operational Base + offset (0x000c)delay between power up and start command

Bit Attr Reset Value Description
31:6 RO 0x0 reserved
5:0 RW 0x00 DLY_PU_SOC delay between power up and start commandThe start signal will be asserted (DLY_PU_SOC + 2) sclk clockperiod later after power up

CRU_CLKSEL_CON26

时钟配置

Address: Operational Base + offset (0x0168)Internal clock select and divide register26

Bit Attr Reset Value Description
31:16 WO 0x0000

write_maskwrite mask bits

When every bit HIGH, enable the writing corresponding bit

When every bit LOW, don't care the writing corresponding bit

15:8 RW 0x01

clk_saradc_div_con

clk_saradc divider control register

     clk=clk_src/(div_con+1)

7:6 RW 0x0

clk_crypto1_pll_sel

clk_crypto1 clock source select control register

2'b00:CPLL

2'b01:GPLL

2'b10:PPLL

5 RO 0x0 reserved
4:0 RW 0x03

clk_crypto1_div_con

clk_crypto1 divider control register

       clk=clk_src/(div_con+1)

操作流程

Steps of adc conversion:

  1. Write SARADC_CTRL[3] as 0 to power down adc converter.

  2. Write SARADC_CTRL[2:0] as n to select adc channel(n).

  3. Write SARADC_CTRL[5] as 1 to enable adc interrupt.

  4. Write SARADC_CTRL[3] as 1 to power up adc converter.

  5. Wait for adc interrupt or poll SARADC_STAS register to assert whether the conversionis completed

  6. Read the conversion result from SARADC_DATA[9:0]

  7. Note: The A/D converter was designed to operate at maximum 1MHZ.

编写驱动代码

函数入口函数

配置ADC的CTRL寄存器。

static int __init gec3399_adc_init(void)
{
    int ret;
    printk(KERN_INFO "gec3399_adc_init\n");
    //第一步:注册杂项设备
    ret = misc_register(&miscdev);
    if(ret < 0){
​
        printk("misc driver register error\n");
        goto misc_register_err;
    }
    //第二步:内存映射
    SARADC_BASE = ioremap(0xFF100000,0x1000);
    if(SARADC_BASE == NULL){
        printk("SARADC_BASE ioremap error\n");
        ret = -EFAULT;
        goto SARADC_ioremap_err;
    }
  
    //第三步:CLK地址内存映射
    CRU_CLKSEL_BASE = ioremap(0xFF760000,0x1000);
    if(SARADC_BASE == NULL){
        printk(" CRU_CLKSEL_BASE ioremap error\n");
        ret = -EFAULT;
        goto CRU_ioremap_err;
    }
    
    //第四步:内存地址初始化
    SARADC_DATA = SARADC_BASE + 0x0000; 
    SARADC_STAS = SARADC_BASE + 0x0004;
    SARADC_CTRL = SARADC_BASE + 0x0008;
    
    CRU_CLKSEL_CON26 = CRU_CLKSEL_BASE + 0x0168;
        
    writel(readl(SARADC_CTRL)&~(1<<3),SARADC_CTRL);     //关闭ADC                     
​
    writel(readl(CRU_CLKSEL_CON26)&~(0xFF<<8),CRU_CLKSEL_CON26); 
    writel(readl(CRU_CLKSEL_CON26)|(0xFF<<8),CRU_CLKSEL_CON26);   //设置分频因子  
    
    return 0;
    
CRU_ioremap_err:
    iounmap(SARADC_BASE);
SARADC_ioremap_err:
    misc_deregister(&miscdev);
misc_register_err:  
    return ret; //返回错误的原因
}

杂项设备

static struct miscdevice miscdev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "adc_drv",
    .fops = &gec3399_adc_fops,
};

文件操作集

static const struct file_operations gec3399_adc_fops = {
    .owner = THIS_MODULE,
    .unlocked_ioctl = gec3399_adc_ioctl,
    .read = gec3399_adc_read,
};

ADC通道设备

static long gec3399_adc_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    switch(cmd)
    {
        case GEC3399_ADC_SET_CHANNEL:       
            writel(readl(SARADC_CTRL)&~(7<<0),SARADC_CTRL);         //清零操作
            writel(readl(SARADC_CTRL)|(arg<<0),SARADC_CTRL);          //选择通道            
​
    }
    return 0;
}

ADC值获取

static ssize_t gec3399_adc_read(struct file *file, char __user *buf,size_t nbytes, loff_t *ppos)
{
    int ret;
    int timr_out = 10000;
    unsigned int value = 100;
    
    
    writel(readl(SARADC_CTRL)|(1<<3),SARADC_CTRL);   //启动ADC    
​
    mdelay(20);                                     //等待启动完毕
    
    writel(readl(SARADC_STAS)|1,SARADC_STAS);
        
        
    while((((readl(SARADC_STAS)&1)==1)&&(timr_out--)));
    if(timr_out == 0)
    {
        printk(KERN_INFO "timr out errot\n");
    }
  
    writel(readl(SARADC_CTRL)&~(1<<3),SARADC_CTRL);   //关闭ADC
    value = readl(SARADC_DATA)&0x3ff;                  //读取数据
    
    ret = copy_to_user((void*)buf,&value,4);
    if(ret < 0)
    {
        printk(KERN_INFO "copy_to_user errot\n");
        return -1;
    }
    
    return 0;
}   

测试代码

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
​
#define GEC3399_ADC_SET_CHANNEL       _IO('A',1)
​
int main(void)
{
    int fd;
    int ret;
    float tmp;
    unsigned int voltage = 0;
    //第一步:打开ADC设备节点
    fd = open("/dev/adc_drv", O_RDWR);
    if(fd < 0)
    {
        perror("open adc driver");
        return -1;      
    }
    
    while(1)
    {   
        //第二步:打开ADC通道0
        ret = ioctl(fd,GEC3399_ADC_SET_CHANNEL,0);
        if(ret<0)
        {
            perror("ioctl adc driver ");    
        }
        //第三步:读取ADC通道0的值
        ret = read(fd,&voltage,4);
        if(ret<0)
        {
            perror("read adc driver ");
            
        }
      
        if(voltage==0)
            continue;
        //printf("voltage = %d\n",voltage);
        tmp=(float)voltage;
        printf("ADC 0 voltage = %0.2f\n",(tmp/1024)*1.8);
        usleep(500*1000);//0.5s
        
        //第四步:打开ADC通道1
        ret = ioctl(fd,GEC3399_ADC_SET_CHANNEL,1);
        if(ret<0)
        {
            perror("ioctl adc driver ");    
        }
        
        //第五步:读取ADC通道1的值
        ret = read(fd,&voltage,4);
        if(ret<0)
        {
            perror("read adc driver ");
            
        }
        if(voltage==0)
            continue;
        //printf("voltage = %d\n",voltage);
        tmp=(float)voltage;
        printf("ADC 1 voltage = %0.2f\n",(tmp/1024)*1.8);
        usleep(500*1000);//0.5s
    }   
  
    close(fd);
    
    return 0;
}

编写Makefile文件

obj-m += adc_drv.o
KERNELDIR:=/file/RK3399Pro/rk3399pro_git_repo/kernel
PWD:=$(shell pwd)
​
default:
    $(MAKE)  -C $(KERNELDIR) M=$(PWD) modules
    
test:
    aarch64-linux-gnu-gcc adc_test.c -o adc_test    
​
clean:
    rm -rf *.o *.order .*.cmd *.ko *.mod.c *.symvers *.tmp_versions 

测试步骤

编译源码

在ubuntu中输入:

make

得到驱动目标文件adc_drv.ko

输入:

make test

得到测试目标文件:adc_test

加载驱动

在开发板命令终端输入:

insmod adc_drv.ko

执行测试程序

在开发板命令终端输入:

chmod 777 adc_test
./adc_test

实验现象

读取ADC的模数转换值

猜你喜欢

转载自blog.csdn.net/ZOROE123/article/details/121384348