目录
原理图
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:
-
Write SARADC_CTRL[3] as 0 to power down adc converter.
-
Write SARADC_CTRL[2:0] as n to select adc channel(n).
-
Write SARADC_CTRL[5] as 1 to enable adc interrupt.
-
Write SARADC_CTRL[3] as 1 to power up adc converter.
-
Wait for adc interrupt or poll SARADC_STAS register to assert whether the conversionis completed
-
Read the conversion result from SARADC_DATA[9:0]
-
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的模数转换值