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位的值就可以了。代码如下:

[cpp] view plaincopy
  1. /*
  2. * mini2440 ADC驱动程序
  3. *
  4. * Kevin Lee <www.ielife.cn>
  5. */
  6. #include<linux/kernel.h> /* 提供prink等内核特有属性 */
  7. #include<linux/module.h> /* 提供如MODULE_LICENSE()、EXPORT_SYMBOL() */
  8. #include<linux/init.h> /* 设置段,如_init、_exit,设置初始化优先级,如__initcall */
  9. #include<linux/wait.h> /* 等待队列wait_queue */
  10. #include<linux/interrupt.h> /* 中断方式,如IRQF_SHARED */
  11. #include<linux/fs.h> /* file_operations操作接口等 */
  12. #include<linux/clk.h> /* 时钟控制接口,如struct clk */
  13. #include<linux/miscdevice.h> /* 杂项设备 */
  14. #include<asm/io.h> /* 提供readl、writel */
  15. #include<asm/irq.h> /* 提供中断号,中断类型等,如IRQ_ADC中断号 */
  16. #include<asm/arch/regs-adc.h> /* 提供控制器的寄存器操作,如S3C2410_ADCCON */
  17. #include<asm/uaccess.h> /* 提供copy_to_user等存储接口 */
  18. /* 定义设备名称,用户访问接口/dev/adc */
  19. #defineDEVICE_NAME "adc"
  20. /* 定义adc时钟,通过adc_clock接口获得adc输入时钟,adc转换器需要 */
  21. staticstruct clk *adc_clock;
  22. /* 定义虚拟地址访问硬件寄存器,__iomem只是用于表示指针将指向I/O内存 */
  23. staticvoid __iomem *base_addr;
  24. /* 定义并初始化一个等待队列adc_waitqueue,对ADC资源进行阻塞访问 */
  25. staticwait_queue_head_t adc_waitqueue;
  26. /* 定义并初始化信号量adc_lock,用于控制共享中断IRQ_ADC资源的使用 */
  27. DECLARE_MUTEX(adc_lock);
  28. EXPORT_SYMBOL(adc_lock);
  29. /* 定义等待队列的条件,当is_read_ok=1时,ADC转换完毕,数据可读 */
  30. staticvolatile int is_read_ok = 0;
  31. /* 定义ADC转换的数据内容 */
  32. staticvolatile int adc_data;
  33. staticint adc_open(struct inode *inode, struct file *file);
  34. staticssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos);
  35. staticint adc_close(struct inode *inode, struct file *filp);
  36. /* 实现字符设备操作接口 */
  37. staticstruct file_operations adc_fops =
  38. {
  39. .owner   = THIS_MODULE,
  40. .open    = adc_open,
  41. .read    = adc_read,
  42. .release = adc_close,
  43. };
  44. /* 实现misc杂项设备操作接口 */
  45. staticstruct miscdevice adc_miscdev =
  46. {
  47. .minor  = MISC_DYNAMIC_MINOR, /* 动态获取杂项设备的次设备号 */
  48. .name   = DEVICE_NAME,        /* 杂项设备的设备名称,这里为adc */
  49. .fops   = &adc_fops,          /* 杂项设备子系统接口,指向adc_fops操作接口 */
  50. };
  51. /*ADC中断服务程序,获取ADC转换后的数据 */
  52. staticirqreturn_t adc_irq(int irq, void *dev_id)
  53. {
  54. /* 仅当is_read_ok=0时才进行转换,防止多次中断 */
  55. if(!is_read_ok)
  56. {
  57. /* 读取ADCCON[9:0]的值,0x3ff为只获取[9:0]位,ADCCON为转换后的数据 */
  58. adc_data = readl(base_addr +S3C2410_ADCDAT0) & 0x3ff;
  59. /* 设置标识为1,唤醒读等待进程可以拷贝数据给用户空间了 */
  60. is_read_ok = 1;
  61. wake_up_interruptible(&adc_waitqueue);
  62. }
  63. return IRQ_RETVAL(IRQ_HANDLED);
  64. }
  65. /*ADC设备打开,并注册IRQ_ADC中断处理函数 */
  66. staticint adc_open(struct inode *inode, struct file *file)
  67. {
  68. int ret;
  69. /* 由于IRQ_ADC为共享中断,因此中断类型选择IRQF_SHARED,最后一个参数需要设置NULL以外的值 */
  70. ret = request_irq(IRQ_ADC, adc_irq,IRQF_SHARED, DEVICE_NAME, (void *)1);
  71. if (ret)
  72. {
  73. printk(KERN_ERR "Could notallocate ts IRQ_ADC !\n");
  74. return -EBUSY;
  75. }
  76. return 0;
  77. }
  78. /*设置ADC控制寄存器,开启AD转换*/
  79. staticvoid adc_run(void)
  80. {
  81. volatile unsigned int adccon;
  82. /* ADCCON的位[14]=1为使能A/D预分频器,位[13:6]=32表示设置的分频值,ADC的转换频率需要在2.5MHZ以下
  83. * 我们使用的ADC输入时钟为PCLK=50MHZ,50MHZ/32<2.5MHZ,满足条件
  84. * 位[5:3]=000,表示模拟输入通道选择AIN0
  85. */
  86. adccon = (1 << 14) | (32 << 6);
  87. writel(adccon, base_addr + S3C2410_ADCCON);
  88. /* 位[0]=1表示使能ADC转换,当转换完毕后此位被ADC控制器自动清0 */
  89. adccon = readl(base_addr + S3C2410_ADCCON)| (1 << 0);
  90. writel(adccon, base_addr + S3C2410_ADCCON);
  91. }
  92. /*ADC设备驱动读函数 */
  93. staticssize_t adc_read(struct file *filp, char *buff, size_t count, loff_t *offp)
  94. {
  95. int err;
  96. /* 获取信号量,如果被占用,睡眠等待持有者调用up唤醒
  97. * 这样做的原因是,有可能其他进程抢占执行或是触摸屏驱动抢占执行
  98. */
  99. down_interruptible(&adc_lock);
  100. /* 启动adc转换,调用中断处理函数adc_irq*/
  101. adc_run();
  102. /* 如果is_read_ok为假,则睡眠等待条件为真,由中断处理函数唤醒 */
  103. wait_event_interruptible(adc_waitqueue,is_read_ok);
  104. /* 执行到此说明中断处理程序获得了ADC转换后的值,清除为0等待下一次的读 */
  105. is_read_ok = 0;
  106. /* 将转换后的数据adc_data提交给用户 */
  107. err = copy_to_user(buff, (char*)&adc_data, min(sizeof(adc_data),count));
  108. /* 释放信号量,并唤醒因adc_lock而睡眠的进程 */
  109. up(&adc_lock);
  110. return err ? -EFAULT : sizeof(adc_data);
  111. }
  112. /*ADC设备关闭函数 */
  113. staticint adc_close(struct inode *inode, struct file *filp)
  114. {
  115. /*释放中断*/
  116. free_irq(IRQ_ADC, (void *)1);
  117. return 0;
  118. }
  119. staticint __init adc_init(void)
  120. {
  121. int ret;
  122. /* 获得adc的时钟源,通过arch/arm/mach-s3c2410/clock.c获得提供的时钟源为PCLK */
  123. adc_clock = clk_get(NULL, "adc");
  124. if (!adc_clock)
  125. {
  126. printk(KERN_ERR "failed to get adcclock source\n");
  127. return -ENOENT;
  128. }
  129. /* 在时钟控制器中给adc提供输入时钟,ADC转换需要输入时钟 */
  130. clk_enable(adc_clock);
  131. /* 使用ioremap获得操作ADC控制器的虚拟地址
  132. * S3C2410_PA_ADC=ADCCON,是ADC控制器的基地址,寄存器组的长度=0x1c
  133. */
  134. base_addr = ioremap(S3C2410_PA_ADC, 0x1c);
  135. if (base_addr == NULL)
  136. {
  137. printk(KERN_ERR "Failed to remapregister block\n");
  138. return -ENOMEM;
  139. goto fail1;
  140. }
  141. /* 初始化等待队列 */
  142. init_waitqueue_head(&adc_waitqueue);
  143. /* 注册杂项设备 */
  144. ret = misc_register(&adc_miscdev);
  145. if (ret)
  146. {
  147. printk(KERN_ERR "Failed toregister miscdev\n");
  148. goto fail2;
  149. }
  150. printk(DEVICE_NAME "initialized!\n");
  151. return 0;
  152. fail2:
  153. iounmap(base_addr);
  154. fail1:
  155. clk_disable(adc_clock);
  156. clk_put(adc_clock);
  157. return ret;
  158. }
  159. staticvoid __exit adc_exit(void)
  160. {
  161. /* 释放虚拟地址 */
  162. iounmap(base_addr);
  163. /* 禁止ADC的时钟源 */
  164. if (adc_clock)
  165. {
  166. clk_disable(adc_clock);
  167. clk_put(adc_clock);
  168. adc_clock = NULL;
  169. }
  170. /*注销misc设备*/
  171. misc_deregister(&adc_miscdev);
  172. }
  173. module_init(adc_init);
  174. module_exit(adc_exit);
  175. MODULE_AUTHOR("KevinLee <www.ielife.cn>");
  176. MODULE_DESCRIPTION("Mini2440ADC Misc Device Driver");
  177. MODULE_VERSION("MINI2440ADC 1.0");
  178. 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函数,如下:

[cpp] view plaincopy
  1. MODULENAME:= adc.o
  2. ifneq($(KERNELRELEASE),)
  3. #call from kernel build system
  4. obj-m      := $(MODULENAME)
  5. else
  6. #KERNELDIR?= /lib/modules/$(shell uname -r)/build
  7. KERNELDIR?= /work/system/linux-2.6.22.6
  8. PWD       := $(shell pwd)
  9. default:
  10. $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
  11. endif
  12. clean:
  13. rm -rf *.o *~ core .depend .*.cmd *.ko*.mod.c .tmp_versions module* Module* $(APPNAME)
  14. depend.depend dep:
  15. $(CC) $(CFLAGS) -M *.c > .depend
  16. ifeq(.depend,$(wildcard .depend))
  17. include.depend
  18. 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,看到如下信息则说明启动正常:

adc initialized!

并且可以查看/dev目录下,已经有adc设备文件

# ls  -l  /dev/adc

crw-rw----    1 0       0         10,  61 Jul 27 23:17 /dev/adc

1.4    可调电阻的测试程序

编写测试程序adc_test.c文件,源代码如下:

[cpp] view plaincopy
  1. #include<stdio.h>
  2. #include<stdlib.h>
  3. #include<fcntl.h>
  4. #include<unistd.h>
  5. #include<sys/types.h>
  6. #include<errno.h>
  7. #defineDEVICE_NAME       "/dev/adc"
  8. intmain()
  9. {
  10. int fd,ret,value;
  11. fd = open(DEVICE_NAME, O_RDONLY);
  12. if(fd < 0) {
  13. perror("open ADC : ");
  14. exit(EXIT_FAILURE);
  15. }
  16. ret = read(fd, &value, sizeof(value));
  17. if(ret < 0) {
  18. perror("read ADC:");
  19. close(fd);
  20. exit(EXIT_FAILURE);
  21. }
  22. printf("read from ADC : %d\n",value);
  23. close(fd);
  24. return 0;
  25. }

源代码简单不做说明,编译源代码的命令:

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

读到的数值随电阻值的变化而变化,由此说明驱动及硬件工作正常。

linux input输入子系统分析《二》:s3c2440的ADC简单驱动实例分析相关推荐

  1. linux input输入子系统分析《三》:S3C2440的触摸屏驱动实例

    1.1    本节阅读前提 本节的说明建立在前两节的基础之上,需要先阅读如下两篇章: linux input输入子系统分析<一>:初识input输入子系统 linux input输入子系统 ...

  2. linux input输入子系统分析《四》:input子系统整体流程全面分析

    1      input输入子系统整体流程 本节分析input子系统在内核中的实现,包括输入子系统(Input Core),事件处理层(Event Handler)和设备驱动层.由于上节代码讲解了设备 ...

  3. linux input输入子系统分析《一》:初识input输入子系统

    主要讲述本人在学习Linux内核input子系统的全部过程,如有分析不当,多谢指正.以下交流方式,文章欢迎转载,保留联系信息,以便交流. 邮箱:eabi010@gmail.com 主页:www.iel ...

  4. Linux输入事件类型EV_SW,Linux的input输入子系统:总体框架

    一.input输入子系统总体框架 Linux输入子系统将输入驱动抽象为三层:设备驱动层.核心层.事件处理层. 设备驱动层:将底层的硬件输入事件转化为统一事件形式,向输入核心(Input Core)汇报 ...

  5. 12.Linux之输入子系统分析(详解)

    在此节之前,我们学的都是简单的字符驱动,涉及的内容有字符驱动的框架.自动创建设备节点.linux中断.poll机制.异步通知.同步互斥/非阻塞.定时器去抖动. 其中驱动框架如下: 1)写file_op ...

  6. ARM Linux内核Input输入子系统浅解

    --以触摸屏驱动为例 第一章.了解linux input子系统       Linux输入设备总类繁杂,常见的包括有按键.键盘.触摸屏.鼠标.摇杆等等,他们本身就是字符设备,而linux内核将这些设备 ...

  7. Linux中断子系统(二)中断控制器GIC驱动分析

    Linux中断子系统(二)中断控制器GIC驱动分析 备注:   1. Kernel版本:5.4   2. 使用工具:Source Insight 4.0   3. 参考博客: Linux中断子系统(一 ...

  8. Linux驱动:input输入子系统

    input输入子系统 1.input输入子系统 1.1 简介 1.2 相关API函数 1.3 使用流程(驱动框架) 2.驱动示例 3.测试程序 4.测试结果 5.内核自带的input按键驱动 附:对应 ...

  9. Linux下输入子系统上报触摸屏坐标

    Linux下输入子系统上报触摸屏坐标 1.输入子系统简介   在 Linux 中,输入子系统是由输入子系统设备驱动层.输入子系统核心层(Input Core)和输入子系统事件处理层(Event Han ...

最新文章

  1. python代码根据时间获取周数(week of the year)
  2. 怎么把页面内容填满一页_Excel表格太宽,一张纸打印不下,两张纸又空太多!怎么办?...
  3. react技术栈实践(1)
  4. cmd对应linux sleep命令,linux sleep命令参数及用法详解(linux休眠延迟执行命令)
  5. MBSA有关安全漏洞的检查说明一
  6. 高级编程语言学习概论
  7. 2022-2028全球及中国计算流体动力学软件和服务行业研究及十四五规划分析报告
  8. c语言运行全屏,怎么用代码控制C源程序运行时窗口是全屏的?
  9. 在linux下如何显示隐藏文件
  10. 触摸屏与usb鼠标同时支持
  11. HTML标签关系——双标签和单标签,标签的嵌套与并列
  12. ICME2021:基于机器学习的VVC帧内编码码率控制
  13. MATLAB仿真节点个数和节点通信半径与网络连通率的关系,WSN实验
  14. 【文章阅读】【超解像】--Residual Dense Network for Image Super-Resolution
  15. 十进制转换成二进制——C语言
  16. python开发项目管理平台_基于Python的软件项目管理系统.doc
  17. 7:第三章:电商工程分析:2:电商工程业务解读与微服务拆分;
  18. 深入理解Linux进程描述符task_struct结构体
  19. 等待事件:Streams AQ: qmn coordinator waiting for slave to start
  20. PAT日志 1042

热门文章

  1. python网络爬虫 抓取金融分析师名单
  2. 解决Mac无法编辑 .bash_profile文件与使用sudo时permission denied报错
  3. 华为p20支持手机云闪付吗_余承东:明年华为智能手机全面支持鸿蒙系统
  4. python做前端mongodb_Python爬虫之mongodb和python交互
  5. c语言编程命令,C语言编程命令
  6. 【Teradata SQL】禁用和启用数据库用户登录
  7. python交互模式下tab键自动补全
  8. Struts2中 radio标签的详细使用方法
  9. php返回json数据函数实例
  10. OAuth 授权timestamp refused问题