1 mini2440的ADC驱动实例
这节与输入子系统无关,出现在这里是因为后面的章节会讲到触摸屏输入子系统驱动,由于触摸屏也使用ADC,因此本节是为了说明ADC通过驱动代码是如何控制的。
本节重点:
- 如何通过原理图查找ADC硬件使用的资源
- 如何通过芯片手册查找ADC硬件的操作方法
- ADC设备驱动程序的初始化流程
- ADC设备驱动程序的中断处理流程
本节难点:
- ADC的控制寄存器的操作方法
- ADC驱动程序的控制逻辑
1.1 模数转换(ADC)简介
ADC是把模拟信号转化为计算机能够处理的数字信号的过程。
模拟信号一般为电压,或者是电流。有些时候也可以是非电信号,如温度、湿度、声音、位移等,它们通过传感器转换为电压信号传递给A/D转换器才可以进行A/D转换。
1.2 mini2440上的可调电阻
由mini2440的用户手册的1.3.8节A/D输入测试可知,S3C2440的AIN0引脚接到了开发板的可调电阻W1上,原理图如下图3所示:
图3 mini2440可调电阻原理图
上图中,1、2电路的状态是能够确定的,一个接3.3V电压,一个接地,中间接可变电阻W1(10K)。而引脚3接AIN0,它是什么?可以通过mini2440开发板原理图来查找:
图4 mini2440可调电阻与S3C2440接口电路
通过上图可知,开发板的AIN0引脚与S3C2440 CPU芯片上的AIN0引脚相连接。因此需要进一步查看S3C2440芯片手册获得AIN0引脚的作用。
下图5是S3C2440芯片手册的第16章对A/D转换器和触摸屏接口的介绍。S3C2440内部共有8个通道的模拟输入接口,其转换的模拟信号为10位的二进制数字编码。
A[3:0]分别代表AIN0、AIN1、AIN2、AIN3,触摸屏接口可以控制/选择触摸屏X、Y方向的引脚(XP,XM,YP,YM)的变换。
图5 A/D转换器和触摸屏的功能结构图
那么ADC如何实现模拟信号到数字信号的转换呢,由上图可知,模拟信号通过8个通道的任意一个输入,然后通过分频器决定A/D转换器的频率,最后通过ADC将模拟信号转换为数字信号保存在ADCDAT0中,ADCDAT0中的数据可以通过查询或者中断的方式来获得。
S3C2440模数转换器的控制逻辑可由以下寄存器来进行操作:
ADCCON ADC控制寄存器
ADCTSC ADC触摸屏控制寄存器器
ADCDLY ADC启动初始化延迟寄存器
ADCDAT0 ADC转换数据寄存器
ADCDAT1 ADC转换数据寄存器
ADCUPDN 笔尖抬起或落下中断状态寄存器
由以上内容,开发板可以通过W1可变电阻的阻值变化产生电压的变化,由AIN0引脚传递给ADC控制器转化为数字信号,我们通过驱动来获得可调电阻W1硬件的变化。
1.3 可调电阻的ADC驱动程序
既然需要写驱动,首先先确定可调电阻的ADC驱动属于什么设备。由于是顺序读取寄存器ADCDAT0的过程,所以把它看成一个字符设备,而且对于这个设备来说,更简单的实现方法是通过misc杂项设备来实现。
代码实现的非常简单,通过中断的方式获取ADCDAT0的前10位的值就可以了。代码如下:
-
-
-
-
-
-
- #include<linux/kernel.h> /* 提供prink等内核特有属性 */
- #include<linux/module.h> /* 提供如MODULE_LICENSE()、EXPORT_SYMBOL() */
- #include<linux/init.h> /* 设置段,如_init、_exit,设置初始化优先级,如__initcall */
- #include<linux/wait.h> /* 等待队列wait_queue */
- #include<linux/interrupt.h> /* 中断方式,如IRQF_SHARED */
- #include<linux/fs.h> /* file_operations操作接口等 */
- #include<linux/clk.h> /* 时钟控制接口,如struct clk */
- #include<linux/miscdevice.h> /* 杂项设备 */
- #include<asm/io.h> /* 提供readl、writel */
- #include<asm/irq.h> /* 提供中断号,中断类型等,如IRQ_ADC中断号 */
- #include<asm/arch/regs-adc.h> /* 提供控制器的寄存器操作,如S3C2410_ADCCON */
- #include<asm/uaccess.h> /* 提供copy_to_user等存储接口 */
-
-
- #defineDEVICE_NAME "adc"
-
-
- staticstruct clk *adc_clock;
-
-
- staticvoid __iomem *base_addr;
-
-
- staticwait_queue_head_t adc_waitqueue;
-
-
- DECLARE_MUTEX(adc_lock);
- EXPORT_SYMBOL(adc_lock);
-
-
- staticvolatileint is_read_ok = 0;
-
-
- staticvolatileint adc_data;
-
- staticint adc_open(struct inode *inode, struct file *file);
- staticssize_t adc_read(struct file *filp, char *buffer,size_t count, loff_t *ppos);
- staticint adc_close(struct inode *inode, struct file *filp);
-
-
- staticstruct file_operations adc_fops =
- {
- .owner = THIS_MODULE,
- .open = adc_open,
- .read = adc_read,
- .release = adc_close,
- };
-
-
- staticstruct miscdevice adc_miscdev =
- {
- .minor = MISC_DYNAMIC_MINOR,
- .name = DEVICE_NAME,
- .fops = &adc_fops,
- };
-
-
- staticirqreturn_t adc_irq(int irq, void *dev_id)
- {
-
- if(!is_read_ok)
- {
-
- adc_data = readl(base_addr +S3C2410_ADCDAT0) & 0x3ff;
-
-
- is_read_ok = 1;
- wake_up_interruptible(&adc_waitqueue);
- }
-
- return IRQ_RETVAL(IRQ_HANDLED);
- }
-
-
- staticint adc_open(struct inode *inode, struct file *file)
- {
- int ret;
-
-
- ret = request_irq(IRQ_ADC, adc_irq,IRQF_SHARED, DEVICE_NAME, (void *)1);
- if (ret)
- {
- printk(KERN_ERR"Could notallocate ts IRQ_ADC !\n");
- return -EBUSY;
- }
-
- return 0;
- }
-
-
- staticvoid adc_run(void)
- {
- volatile unsignedint adccon;
-
-
-
-
-
- adccon = (1 << 14) | (32 << 6);
- writel(adccon, base_addr + S3C2410_ADCCON);
-
-
- adccon = readl(base_addr + S3C2410_ADCCON)| (1 << 0);
- writel(adccon, base_addr + S3C2410_ADCCON);
- }
-
-
- staticssize_t adc_read(struct file *filp, char *buff,size_t count, loff_t *offp)
- {
- int err;
-
-
-
-
- down_interruptible(&adc_lock);
-
-
- adc_run();
-
-
- wait_event_interruptible(adc_waitqueue,is_read_ok);
-
-
- is_read_ok = 0;
-
-
- err = copy_to_user(buff, (char*)&adc_data, min(sizeof(adc_data),count));
-
-
- up(&adc_lock);
-
- return err ? -EFAULT :sizeof(adc_data);
- }
-
-
- staticint adc_close(struct inode *inode, struct file *filp)
- {
-
- free_irq(IRQ_ADC, (void *)1);
- return 0;
- }
-
- staticint __init adc_init(void)
- {
- int ret;
-
-
- adc_clock = clk_get(NULL,"adc");
- if (!adc_clock)
- {
- printk(KERN_ERR"failed to get adcclock source\n");
- return -ENOENT;
- }
-
-
- clk_enable(adc_clock);
-
-
-
-
- base_addr = ioremap(S3C2410_PA_ADC, 0x1c);
- if (base_addr == NULL)
- {
- printk(KERN_ERR"Failed to remapregister block\n");
- return -ENOMEM;
- goto fail1;
- }
-
-
- init_waitqueue_head(&adc_waitqueue);
-
-
- ret = misc_register(&adc_miscdev);
- if (ret)
- {
- printk(KERN_ERR"Failed toregister miscdev\n");
- goto fail2;
- }
-
- printk(DEVICE_NAME"initialized!\n");
-
- return 0;
-
- fail2:
- iounmap(base_addr);
- fail1:
- clk_disable(adc_clock);
- clk_put(adc_clock);
-
- return ret;
- }
-
- staticvoid __exit adc_exit(void)
- {
-
- iounmap(base_addr);
-
-
- if (adc_clock)
- {
- clk_disable(adc_clock);
- clk_put(adc_clock);
- adc_clock = NULL;
- }
-
-
- misc_deregister(&adc_miscdev);
- }
-
- module_init(adc_init);
- module_exit(adc_exit);
-
- MODULE_AUTHOR("KevinLee <www.ielife.cn>");
- MODULE_DESCRIPTION("Mini2440ADC Misc Device Driver");
- MODULE_VERSION("MINI2440ADC 1.0");
- MODULE_LICENSE("GPL");
由于驱动程序不同于应用程序main函数,因此读者观看以上程序的顺序应该如下所示:
首先执行的代码是__init adc_init函数,它会被insmod加载进内核,当然也可以在内核初始化的时候加载,加载成功,应用层访问接口“/dev/adc”被创建;
其次,由于应用层会首先打开“/dev/adc”设备,进而操作ADC设备,因此需要查看adc_open函数做了什么。由于打开设备意味着要使用设备,所以在adc_open中注册IRQ_ADC中断资源;
最后,用户会调用read函数读取ADC转换的值,会调用到adc_read。因此,在adc_read函数中需要设置好AIN0引脚的模拟输入,并启动ADC,把读取的任务交给adc_irq函数去完成,最后由adc_read函数把数据提交给应用层。
如果使用insmod的方式加载,需要编写Makefile函数,如下:
- MODULENAME:= adc.o
-
- ifneq($(KERNELRELEASE),)
- #call from kernel build system
- obj-m := $(MODULENAME)
-
- else
- #KERNELDIR?= /lib/modules/$(shell uname -r)/build
- KERNELDIR?= /work/system/linux-2.6.22.6
- PWD := $(shell pwd)
- default:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
- endif
-
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko*.mod.c .tmp_versions module* Module* $(APPNAME)
-
- depend.depend dep:
- $(CC) $(CFLAGS) -M *.c > .depend
-
- ifeq(.depend,$(wildcard .depend))
- include.depend
- endif
adc.c与Makefile文件放在同一目录下,执行make就可以了。Makefile中使用的编译器的名称为arm-linux-gcc,根据自己的情况修改即可。
编译成功,在当前目录下得到adc.ko驱动模块,使用命令modinfo adc.ko,获取信息如下:
stu@stu-desktop:adc$modinfo adc.ko
filename: adc.ko
license: GPL
version: MINI2440 ADC 1.0
description: Mini2440 ADC Misc Device Driver
author: Kevin Lee <www.ielife.cn>
srcversion: 901D02B007F9D53D9C54EA3
depends: built-in,built-in,built-in,built-in,built-in
vermagic: 2.6.22.6mod_unload ARMv4
以上信息也是我们在adc.c代码中添加的,还有的是在编译过程中得到的。
把adc.ko文件放到开发板中,执行insmod adc.ko,看到如下信息则说明启动正常:
并且可以查看/dev目录下,已经有adc设备文件
# ls -l /dev/adc
crw-rw---- 1 0 0 10, 61 Jul 27 23:17 /dev/adc
1.4 可调电阻的测试程序
编写测试程序adc_test.c文件,源代码如下:
- #include<stdio.h>
- #include<stdlib.h>
- #include<fcntl.h>
- #include<unistd.h>
- #include<sys/types.h>
- #include<errno.h>
-
- #defineDEVICE_NAME "/dev/adc"
-
- intmain()
- {
- int fd,ret,value;
-
- fd = open(DEVICE_NAME, O_RDONLY);
- if(fd < 0) {
- perror("open ADC : ");
- exit(EXIT_FAILURE);
- }
-
- ret = read(fd, &value,sizeof(value));
- if(ret < 0) {
- perror("read ADC:");
- close(fd);
- exit(EXIT_FAILURE);
- }
-
- printf("read from ADC : %d\n",value);
- close(fd);
-
- return 0;
- }
源代码简单不做说明,编译源代码的命令:
arm-linux-gcc -Wall -O2 adc_test.c -o adc_test
arm-linux-strip adc_test
拷贝adc_test文件到开发板,执行命令./adc_test,显示如下:
#./adc_test
readfrom ADC : 736
调节(旋转)电位器即转动变阻器,再次执行./adc_test,显示如下:
#./adc_test
readfrom ADC : 886
读到的数值随电阻值的变化而变化,由此说明驱动及硬件工作正常。