ADC驱动在FL2440的开发

来源:互联网 发布:mysql 修改参数 编辑:程序博客网 时间:2024/05/16 04:25
一、开发环境
    主   机:fedora 14 (2.6.33.7)
    开发板:FL2440(nandflash:K9F1G08 128m)
    编译器:arm-linux-gcc 4.3.2
 
二、原理分析
    1. S3C2440内部ADC结构图。我们从下面的结构图和数据手册可以知道,该ADC模块总共有8个通道可以进行模拟信号的输入,分别是AIN0、AIN1、AIN2、AIN3、YM、YP、XM、XP。那么ADC是怎么实现模拟信号到数字信号的转换呢?首先模拟信号从任一通道输入,然后设定寄存器中预分频器的值来确定AD转换器频率,最后ADC将模拟信号转换为数字信号保存到ADC数据寄存器0中(ADCDAT0),然后ADCDAT0中的数据可以通过中断或查询的方式来访问。对于ADC的各寄存器的操作和注意事项请参阅数据手册。
    2. FL2440的ADC应用实例。下图是FL2440上的ADC应用实例,开发板通过一个10K的电位器(可变电阻)来产生电压模拟信号,然后通过第一个通道(即:AIN0)将模拟信号输入ADC。
    3. ADC 的使用分四个步骤:
    1) 设置ADCCON 寄存器,选择输入信号通道,设置A/D 转换器的时钟。使能A/D 转换器的预分频功能。计算A/D 时钟的公式:A/D 时钟频率=PLCK/(预分频值(PRSCVL)+1)。
    2) 设置ADCTSC 寄存器,使用设为普通转换模式,这里不能使用触摸屏功能。ADCTSC 寄存器多用于触摸屏,对于普通的ADC,使用默认值即可。
    3) 设置ADCCON 寄存器,启动A/D 转换。如果设置READ_START 位,则读取转换数据(读ADCDAT0 寄存器)时即启动下一次转换;否则,就通过设置ENABLE_START 位来启动A/D。
    4) 转换结束时,读取ADCDAT0 寄存器来获取数值。如果使用查询方式,则可以不读取ADCCON 寄存器的ECFLG 位来确定转换是否结束;否则可以使用INT_ADC 中断,发生INT_ADC 中断时表示转换结束。

三、实现步骤
    1. 编写背光驱动。文件名为fl2440_adc.c
  1. /*
  2.  *==============================================
  3.  *Name : fl2440_adc.c
  4.  *Author : y.q.yang
  5.  *Date : 22/2/2011
  6.  *Copyright : GPL
  7.  *Description : fl2440 adc driver
  8.  *==============================================
  9.  */
  10.  
  11. #include <linux/errno.h>
  12. #include <linux/kernel.h>
  13. #include <linux/module.h>
  14. #include <linux/init.h>
  15. #include <linux/input.h>
  16. #include <linux/serio.h>
  17. #include <linux/clk.h>
  18. #include <linux/miscdevice.h>
  19. #include <linux/sched.h>

  20. #include <plat/regs-adc.h>

  21. #include <asm/io.h>
  22. #include <asm/irq.h>
  23. #include <asm/uaccess.h>

  24. //设备名称
  25. #define DEVICE_NAME "my2440_adc"

  26. #define DEVICE_MINOR    6

  27. //定义了一个用来保存经过虚拟映射后的内存地址
  28. static void __iomem *adc_base;

  29. //保存从平台时钟队列中获取ADC的时钟
  30. static struct clk *adc_clk;

  31. //申明并初始化一个信号量ADC_LOCK,对ADC资源进行互斥访问
  32. DECLARE_MUTEX(ADC_LOCK);

  33. //定义并初始化一个等待队列adc_waitq,对ADC资源进行阻塞访问
  34. static DECLARE_WAIT_QUEUE_HEAD(adc_waitq);

  35. //用于标识AD转换后的数据是否可以读取,0表示不可读取
  36. static volatile int ev_adc = 0;

  37. //用于保存读取的AD转换后的值,该值在ADC中断中读取
  38. static int adc_data;

  39. //ADC中断服务程序,该服务程序主要是从ADC数据寄存器中读取AD转换后的值
  40. static irqreturn_t adc_irq(int irq, void *dev_id)
  41. {
  42.     //保证了应用程序读取一次这里就读取AD转换的值一次,
  43.     //避免应用程序读取一次后发生多次中断多次读取AD转换值
  44.     if(!ev_adc) 
  45.     {
  46.         /*读取AD转换后的值保存到全局变量adc_data中,S3C2410_ADCDAT0定义在regs-adc.h中,
  47.   这里为什么要与上一个0x3ff,很简单,因为AD转换后的数据是保存在ADCDAT0的第0-9位,
  48.           所以与上0x3ff(即:1111111111)后就得到第0-9位的数据,多余的位就都为0*/
  49.         adc_data = readl(adc_base + S3C2410_ADCDAT0) & 0x3ff;

  50.         //将可读标识为1,并唤醒等待队列
  51.         ev_adc = 1;
  52.         wake_up_interruptible(&adc_waitq);
  53.     }

  54.     return IRQ_HANDLED;
  55. }

  56. //ADC设备驱动的打开接口函数
  57. static int adc_open(struct inode *inode, struct file *file)
  58. {
  59.     int ret;

  60.     /*申请ADC中断服务,这里使用的是共享中断:IRQF_SHARED,为什么要使用共享中断,因为在触摸屏驱动中
  61.      也使用了这个中断号。中断服务程序为:adc_irq在下面实现,IRQ_ADC是ADC的中断号,这里注意:
  62.      申请中断函数的最后一个参数一定不能为NULL,否则中断申请会失败,如果中断服务程序中用不到这个
  63.      参数,就随便给个值就好了,我这里就给个1*/
  64.     ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME, (void *)1);
  65.     
  66.     if (ret) 
  67.     { 
  68.         printk(KERN_ERR "IRQ%d error %d\n", IRQ_ADC, ret);
  69.         return -EINVAL;
  70.     }

  71.     return 0;
  72. }

  73. //设置ADC控制寄存器,开启AD转换
  74. static void start_adc(void)
  75. {
  76.     unsigned int tmp;

  77.     tmp = (<< 14) | (255 << 6) | (<< 3);// 0 1 00000011 000 0 0 0 
  78.     writel(tmp, adc_base + S3C2410_ADCCON); //AD预分频器使能、模拟输入通道设为AIN0

  79.     tmp = readl(adc_base + S3C2410_ADCCON);
  80.     tmp = tmp | (<< 0);                   // 0 1 00000011 000 0 0 1 
  81.     writel(tmp, adc_base + S3C2410_ADCCON); //AD转换开始
  82. }

  83. //ADC设备驱动的读接口函数
  84. static ssize_t adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
  85. {

  86.     int ret;
  87.     
  88.     //试着获取信号量(即:加锁)
  89.     if (down_trylock(&ADC_LOCK)) 
  90.     {
  91.         return -EBUSY;
  92.     }

  93.     //表示还没有AD转换后的数据,不可读取
  94.     if(!ev_adc)
  95.     {
  96.         if(filp->f_flags & O_NONBLOCK)
  97.         {
  98.             //应用程序若采用非阻塞方式读取则返回错误
  99.             return -EAGAIN;
  100.         }
  101.         else//以阻塞方式进行读取
  102.         {
  103.             //设置ADC控制寄存器,开启AD转换
  104.             start_adc();

  105.             //使等待队列进入睡眠
  106.             wait_event_interruptible(adc_waitq, ev_adc);
  107.         }
  108.     }

  109.     //能到这里就表示已有AD转换后的数据,则标识清0,给下一次读做判断用
  110.     ev_adc = 0;

  111.     //将读取到的AD转换后的值发往到上层应用程序
  112.     ret = copy_to_user(buffer, (char *)&adc_data, sizeof(adc_data));

  113.     //释放获取的信号量(即:解锁)
  114.     up(&ADC_LOCK);

  115.     return sizeof(adc_data);
  116. }

  117. //ADC设备驱动的关闭接口函数
  118. static int adc_release(struct inode *inode, struct file *filp)
  119. {
  120.     return 0;
  121. }

  122. //字符设备的相关操作实现
  123. static struct file_operations adc_fops = 
  124. {
  125.     .owner = THIS_MODULE,
  126.     .open = adc_open,
  127.     .read = adc_read, 
  128.     .release = adc_release,
  129. };

  130. //misc设备结构体实现
  131. static struct miscdevice adc_miscdev = 
  132. {
  133.     .minor = DEVICE_MINOR, //次设备号,定义在miscdevice.h中,为255,表示在注册设备的时候动态获得次设备号
  134.     .name = DEVICE_NAME,   //设备名称
  135.     .fops = &adc_fops,     //对ADC设备文件操作
  136. };


  137. static int __init adc_init(void)
  138. {
  139.     int ret;

  140.     /*从平台时钟队列中获取ADC的时钟,这里为什么要取得这个时钟,因为ADC的转换频率跟时钟有关。
  141.     系统的一些时钟定义在arch/arm/plat-s3c24xx/s3c2410-clock.c中*/
  142.     adc_clk = clk_get(NULL, "adc");
  143.     if (!adc_clk) 
  144.     {
  145.         printk(KERN_ERR "failed to find adc clock source\n");
  146.         return -ENOENT;
  147.     }

  148.     //时钟获取后要使能后才可以使用,clk_enable定义在arch/arm/plat-s3c/clock.c中
  149.     clk_enable(adc_clk);

  150.     /*将ADC的IO端口占用的这段IO空间映射到内存的虚拟地址,ioremap定义在io.h中。
  151.      注意:IO空间要映射后才能使用,以后对虚拟地址的操作就是对IO空间的操作,
  152.      S3C2410_PA_ADC是ADC控制器的基地址,定义在mach-s3c2410/include/mach/map.h中,0x20是虚拟地址长度大小*/
  153.     adc_base = ioremap(S3C2410_PA_ADC, 0x20);
  154.     if (adc_base == NULL) 
  155.     {
  156.         printk(KERN_ERR "Failed to remap register block\n");
  157.         ret = -EINVAL;
  158.         goto err_noclk;
  159.     }

  160.     /*把看ADC注册成为misc设备,misc_register定义在miscdevice.h中
  161.      adc_miscdev结构体定义及内部接口函数在第②步中讲,MISC_DYNAMIC_MINOR是次设备号,定义在miscdevice.h中*/
  162.     ret = misc_register(&adc_miscdev);
  163.     if (ret) 
  164.     { 
  165.         printk(KERN_ERR "cannot register miscdev on minor=%d (%d)\n", MISC_DYNAMIC_MINOR, ret);
  166.         goto err_nomap;
  167.     }

  168.     printk(DEVICE_NAME " initialized!\n");

  169.     return 0;

  170. //以下是上面错误处理的跳转点
  171. err_noclk:
  172.     clk_disable(adc_clk);
  173.     clk_put(adc_clk);

  174. err_nomap:
  175.     iounmap(adc_base);

  176.     return ret;
  177. }

  178. static void __exit adc_exit(void)
  179. {
  180.     free_irq(IRQ_ADC, (void *)1); //释放中断
  181.     iounmap(adc_base); //释放虚拟地址映射空间

  182.     if (adc_clk) //屏蔽和销毁时钟
  183.     {
  184.         clk_disable(adc_clk); 
  185.         clk_put(adc_clk);
  186.         adc_clk = NULL;
  187.     }

  188.     misc_deregister(&adc_miscdev);/*注销misc设备*/
  189. }

  190. /*导出信号量ADC_LOCK在触摸屏驱动中使用,因为触摸屏驱动和ADC驱动公用
  191.   相关的寄存器,为了不产生资源竞态,就用信号量来保证资源的互斥访问*/
  192. EXPORT_SYMBOL(ADC_LOCK);

  193. module_init(adc_init);
  194. module_exit(adc_exit);

  195. MODULE_LICENSE("GPL");
  196. MODULE_AUTHOR("y.q.yang");
  197. MODULE_DESCRIPTION("FL2440 ADC Driver");
    2. 把ADC驱动代码部署到内核中去
  1. #cp -f fl2440_adc./linux-2.6.33.7/drivers/misc //把驱动源码复制到内核驱动的混杂设备下
  2. #vim /linux-2.6.33.7/drivers/misc/Kconfig  //添加ADC设备配置
  3. config FL2440_ADC
  4.         tristate "FL2440 Adc Conrols"
  5.         depends on ARCH_S3C2440
  6.         default y
  7.         help
  8.           FL2440 Adc Driver

  9. #vim /linux-2.6.33.7/drivers/misc/Makefile //添加ADC设备配置
  10. obj-$(CONFIG_FL2440_ADC) += fl2440_adc.o
    3. 配置内核,选择ADC设备选项
  1. #make menuconfig
  2. Device Drivers --->
  3.      [*] Misc devices  ---> 
  4.          <*>   FL2440 Adc Conrols  (NEW)
   4. 编译内核并下载到开发板上,查看已加载的设备:#cat /proc/devices,由于编译为混杂设备,所以无法看到fl2440_adc设备。
  1. [root@yyq2440 /]# cat /proc/devices
  2. Character devices:
  3.   1 mem
  4.   2 pty
  5.   3 ttyp
  6.   4 /dev/vc/0
  7.   4 tty
  8.   4 ttyS
  9.   5 /dev/tty
  10.   5 /dev/console
  11.   5 /dev/ptmx
  12.   6 lp
  13.   7 vcs
  14.  10 misc
  15.  13 input
  16.  14 sound
  17.  21 sg
  18.  29 fb
  19.  90 mtd
  20.  99 ppdev
  21. 116 alsa
  22. 128 ptm
  23. 136 pts
  24. 180 usb
  25. 188 ttyUSB
  26. 189 usb_device
  27. 204 s3c2410_serial
  28. 230 fl2440_backlight
  29. 231 fl2440_leds
  30. 232 fl2440_buttons
  31. 253 fl2440_pwm
  32. 254 rtc

四、测试驱动

    1. 编写应用程序测试LED驱动,文件名:adc_test.c

  1. /*
  2.  *==============================================
  3.  *Name : adc_test.c
  4.  *Author : y.q.yang
  5.  *Date : 22/2/2011
  6.  *Copyright : GPL
  7.  *Description : fl2440 ADC driver test
  8.  *==============================================
  9.  */
  10. #include <stdio.h>
  11. #include <stdlib.h>
  12. #include <errno.h>
  13. #include <linux/delay.h>

  14. int main(int argc, char **argv)
  15. {
  16.     int fd;

  17.     //以阻塞方式打开设备文件,非阻塞时flags=O_NONBLOCK
  18.     fd = open("/dev/fl2440_adc", 0);

  19.     if(fd < 0)
  20.     {
  21.         printf("Open ADC Device Faild!\n");
  22.         exit(1);
  23.     }

  24.     while(1)
  25.     {
  26.         int ret;
  27.         int data;
  28.         
  29. //延时,控制adc读取速度,使我们可以在终端上看清楚读出来的数据
  30.         sleep(1);

  31.         //读设备
  32.         ret = read(fd, &data, sizeof(data));

  33.         if(ret != sizeof(data))
  34.         {
  35.             if(errno != EAGAIN)
  36.             {
  37.                 printf("Read ADC Device Faild!\n");
  38.             }

  39.             continue;
  40.         }
  41.         else
  42.         {
  43.             printf("Read ADC value is: %d\n", data);
  44.         }

  45.     }

  46.     return 0;
  47. }

    2. 在开发主机上交叉编译测试应用程序,并复制到文件系统的/usr/sbin目录下,然后重新编译文件系统下载到开发板上

  1. #arm-linux-gcc -o adc_test adc_test.c

   3. 在开发板上的文件系统中创建一个adc设备的节点,然后运行测试程序,调节开发板上的电位器,可以观察到随着电阻的大小变化,adc转后后的数据也随着变化。

  1. [root@yyq2440 /]# mknod /dev/fl2440_adc c 10 6
  2. [root@yyq2440 /]# adc_test
  3. Read ADC value is: 572
  4. Read ADC value is: 574
  5. Read ADC value is: 573
  6. Read ADC value is: 570
  7. Read ADC value is: 578
  8. Read ADC value is: 569
  9. Read ADC value is: 570
  10. Read ADC value is: 570
  11. Read ADC value is: 571
  12. Read ADC value is: 573
  13. Read ADC value is: 583
  14. Read ADC value is: 579
  15. Read ADC value is: 599
  16. Read ADC value is: 607
  17. Read ADC value is: 603
  18. Read ADC value is: 598
  19. Read ADC value is: 601
  20. Read ADC value is: 598
  21. Read ADC value is: 601
  22. Read ADC value is: 599
  23. ^C
0 0
原创粉丝点击