本帖最后由 gjianw217 于 2015-8-22 02:26 编辑
前面进行了OK210试用体验的入门篇介绍,算是初步入门,分别包含:
【OK210试用体验】入门篇(1):开箱验板
【OK210试用体验】入门篇(2):板载资源
【OK210试用体验】入门篇(3):开发环境(软件安装,开发环境,烧写系统)
【OK210试用体验】入门篇(4):编程入门(NFS登录,驱动入门)
【OK210试用体验】功能篇(1):Linux字符驱动之Led
【OK210试用体验】功能篇(2):Linux字符驱动之Key按键
【OK210试用体验】功能篇(3):Linux Input子系统之Key按键
【OK210试用体验】功能篇(4):Linux字符驱动之DS18B20
【OK210试用体验】功能篇(5):Linux字符驱动之PWM蜂鸣器
【OK210试用体验】功能篇(6):Linux字符驱动之红外遥控
今天是功能篇的第七篇:Linux字符驱动之ADC模数转换,本节主要分3部分:硬件分析,软件基础,驱动编程。
一、硬件分析
在【OK210试用体验】的第二篇:板载资源中,简单分析了ADC的功能和作用。其实对ADC的操作,可以看作是对一些模拟量的采集操作,如温湿度,光度。
首先从OK210的底板原理图中可知,通过一个可调电位器RP1,连接到S5PV210的模拟输入0通道,通过对该可调电位器的调节可以改变模拟通道的输入电压值,电压的调节范围为0V-3V,可以利用这个可调电位器来熟悉S5PV210的ADC控制器的使用,如下图所示
模拟输入0通首家引脚为XADCAIN0,由S5PV210用户手册,可知,该引脚是一个模块引脚,如下图所示。
所以,我们要对ADC进行操作,就是通过对配置ADC的各寄存器进行实现。
二、软件基础
1、S5PV210的ADC的主要特征:
(1)分辨率(输出离散值的个数)可以是10位或者12位(可以通过TSADCCON0/TSADCCON1的第16位RES进行设定,将RES设为0表示10位,设为1表示12位)。
(2)10通道的模拟输入(AIN[9]---AIN[0])。
(3)输入电压为0--3.3V。
(4)最大转换速率:1MSPS。
对于转换速率的计算,S5PV210_UM手册上是这样介绍的:当PCLK=66MHz时,预分频比P=65时,12位分辨率的转换时间如下:
A/D转换频率=66MHz/(65+1)=1MHz
A/D转换时间=1/(1MHz/5cycles)=1/200kHz=5us
PS
1)A/D的转换频率最大可达5MHz,所以A/D转换时间最大可达1MSPS
2)对于官方手册上,所说的A/D转换时间的计算,可能我们第一次看的时候会不太理解,会什么要1MHz/5cycles?原因是这样的,因为A/D转换时间包括A/D建立时间,1位1位转换时间,保存数据时间等等,加起来一共是5个时钟周期,所以会是1MHz/5cycles。其实对于A/D转换时间=1/(1MHz/5cycles) 可以换种写法:A/D转换时间=1/1MHz *5=5us 也是一样的,所以当最大转换频率达到5MHz时,A/D转换时间可达1MSPS(SPS为每秒的采样率)。
(5)具有采样保持功能。
(6)ADC有10通道的模拟输入,但是只有AIN[0],AIN[1]没有复用,而AIN[2]--AIN[9]是复用为触摸屏的两路控制信号(XM,XP)。
2、S5PV210的ADC的寄存器
操作AIN[0],在编写ADC的驱动代码的时候,一般情况下只需考虑三个寄存器,分别是
寄存器 | 映射地址 | 读/写 | 描述 | 复位值 |
TSADCCON0 | 0xE170_0000 | R/W | TS0/ADC控制寄存器 | 0x0000_3FC4 |
TSDLY0 | 0xE170_0008 | R/W | TS0/ADC延时寄存器 | 0x0000_00FF |
TSDATX0 | 0xE170_000C | R | TS0/ADC转换数据X寄存器 | --- |
上面表格即是操作AIN[0]时需要用到的三个寄存器,对于上面的映射地址,在编写驱动代码的时候可使用ioremap的形式进行映射,然后强制转为(volatile unsigned long *)指针形式,接下来就可以直接操作寄存器。
3、S5PV210的ADC操作流程
主要分三步:
(1)使用clk_get获取adc时钟,接着使用clk_enable使能adc时钟;
(2)设置ADCCON的工作方式,预分频比之类的;
(3)当开始读取数据值时,开启ADC转换,判断是否开始转换,判断是否转换完成,最终读取数据,返回给用户空间。值得注意的是,S5PV210的控制寄存器,是没有通道选择的,因为在使用之前,直接用ioremap进行地址的映射时,就相当于选择了不同的通道,所以无需通道选择了。
三、驱动编程
有图有真相,
1 驱动代码
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #undef DEBUG
- //#define DEBUG
- #ifdef DEBUG
- #define DPRINTK(x...) {printk(__FUNCTION__"(%d): ",__LINE__);printk(##x);}
- #else
- #define DPRINTK(x...) (void)(0)
- #endif
- #define DEVICE_NAME "adc"
- static void __iomem *base_addr;
- typedef struct {
- wait_queue_head_t wait;
- int channel;
- int prescale;
- } ADC_DEV;
- static int __ADC_locked = 0;
- static ADC_DEV adcdev;
- static volatile int ev_adc = 0;
- static int adc_data;
- static struct clk *adc_clock;
- #define __ADCREG(name) (*(volatile unsigned long *)(base_addr + name))
- #define ADCCON __ADCREG(S3C_ADCCON) // ADC control
- #define ADCTSC __ADCREG(S3C_ADCTSC) // ADC touch screen control
- #define ADCDLY __ADCREG(S3C_ADCDLY) // ADC start or Interval Delay
- #define ADCDAT0 __ADCREG(S3C_ADCDAT0) // ADC conversion data 0
- #define ADCDAT1 __ADCREG(S3C_ADCDAT1) // ADC conversion data 1
- #define ADCUPDN __ADCREG(S3C_ADCUPDN) // Stylus Up/Down interrupt status
- #define PRESCALE_DIS (0 << 14)
- #define PRESCALE_EN (1 << 14)
- #define PRSCVL(x) ((x) << 6)
- #define ADC_INPUT(x) ((x) << 3)
- #define ADC_START (1 << 0)
- #define ADC_ENDCVT (1 << 15)
- #define START_ADC_AIN(ch, prescale)
- do {
- ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ;
- ADCCON |= ADC_START;
- } while (0)
- static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
- {
- #if 1
- if (__ADC_locked) {
- adc_data = ADCDAT0 & 0x3ff;
- ev_adc = 1;
- wake_up_interruptible(&adcdev.wait);
- /* clear interrupt */
- __raw_writel(0x0, base_addr + S3C_ADCCLRINT);
- }
- #endif
- return IRQ_HANDLED;
- }
- static ssize_t ok210_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
- {
- char str[20];
- int value;
- size_t len;
- __ADC_locked = 1;
- START_ADC_AIN(adcdev.channel, adcdev.prescale);
- wait_event_interruptible(adcdev.wait, ev_adc);
- ev_adc = 0;
- DPRINTK("AIN[%d] = 0x%04x, %dn", adcdev.channel, adc_data, ADCCON & 0x80 ? 1:0);
- value = adc_data;
- __ADC_locked = 0;
- len = sprintf(str, "%dn", value);
- if (count >= len) {
- int r = copy_to_user(buffer, str, len);
- return r ? r : len;
- } else {
- return -EINVAL;
- }
- }
- static int ok210_adc_open(struct inode *inode, struct file *filp)
- {
- init_waitqueue_head(&(adcdev.wait));
- adcdev.channel=0;
- adcdev.prescale=0xff;
- DPRINTK("adc openedn");
- return 0;
- }
- static int ok210_adc_release(struct inode *inode, struct file *filp)
- {
- DPRINTK("adc closedn");
- return 0;
- }
- static struct file_operations dev_fops = {
- owner: THIS_MODULE,
- open: ok210_adc_open,
- read: ok210_adc_read,
- release: ok210_adc_release,
- };
- static struct miscdevice misc = {
- .minor = MISC_DYNAMIC_MINOR,
- .name = "adc", //DEVICE_NAME,
- .fops = &dev_fops,
- };
- static int __init dev_init(void)
- {
- int ret;
- base_addr = ioremap(SAMSUNG_PA_ADC, 0x20);
- if (base_addr == NULL) {
- printk(KERN_ERR "Failed to remap register blockn");
- return -ENOMEM;
- }
- adc_clock = clk_get(NULL, "adc");
- if (!adc_clock) {
- printk(KERN_ERR "failed to get adc clock sourcen");
- return -ENOENT;
- }
- clk_enable(adc_clock);
- /* normal ADC */
- ADCTSC = 0;
- #if 1
- ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev);
- if (ret) {
- printk("request IRQ %d failed for adc, %dn", IRQ_ADC, ret);
- iounmap(base_addr);
- return ret;
- }
- #endif
- ret = misc_register(&misc);
- printk (DEVICE_NAME"tinitializedn");
- return ret;
- }
- static void __exit dev_exit(void)
- {
- free_irq(IRQ_ADC, &adcdev);
- iounmap(base_addr);
- if (adc_clock) {
- clk_disable(adc_clock);
- clk_put(adc_clock);
- adc_clock = NULL;
- }
- misc_deregister(&misc);
- }
- module_init(dev_init);
- module_exit(dev_exit);
- MODULE_LICENSE("GPL");
- MODULE_AUTHOR("gjianw217@163.com");
- MODULE_DESCRIPTION("ADC driver");
2 测试代码
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- #include
- int main(void)
- {
- fprintf(stderr, "press Ctrl-C to stopn");
- int fd = open("/dev/adc", "r");
- if (fd < 0) {
- perror("open ADC device:");
- return 1;
- }
- ioctl(fd,'s',0);
- int ADCValue=0;
- double voltage=0;
- for(;;) {
- ADCValue=0;
- voltage=0;
- read(fd, &ADCValue, sizeof(int));
- if(ADCValue==-1) continue;
- //channel 0 12bit ADC max voltage = 3.3v /10K+1k *10K =3v
- voltage=(float)3.3*ADCValue/4096;
- printf("adc = %d voltage=%f V n",ADCValue,voltage);
- usleep(500* 1000);
- }
- close(fd);
- }
3 Makefile
- #adc Makefile
- ARCH=arm
- CROSS_COMPILE=/home/ok210/arm-2009q3/bin/arm-none-linux-gnueabi-
- APP_COMPILE=/home/ok210/arm-2009q3/bin/arm-none-linux-gnueabi-
- #obj-m := app-drv.o
- obj-m := pwm-drv.o
- #KDIR := /path/to/kernel/linux/
- KDIR := /home/ok210/android-kernel-samsung-dev/
- PWD := $(shell pwd)
- default:
- make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
- app:pwm-app.c
- $(APP_COMPILE)gcc -o app pwm-app.c
- clean:
- $(MAKE) -C $(KDIR) M=$(PWD) clean
欢迎大家关注本人的微信公众号【口袋物联】,微信号为koudaiwulian。