[OK210開發(fā)板體驗]功能篇(7) Linux字符驅(qū)動之ADC模數(shù)轉(zhuǎn)換驅(qū)動

原創(chuàng) 2015-12-24 16:25:00 [OK210開發(fā)板體驗]功能篇(7) Linux字符驅(qū)動之ADC模數(shù)轉(zhuǎn)換驅(qū)動
前面進行了OK210試用體驗的入門篇介紹,算是初步入門,分別包含:
[OK210開發(fā)板體驗]入門篇(1):開箱驗板
[OK210開發(fā)板體驗]入門篇(2):板載資源
[OK210開發(fā)板體驗]入門篇(3):開發(fā)環(huán)境(軟件安裝,開發(fā)環(huán)境,燒寫系統(tǒng))
[OK210開發(fā)板體驗]入門篇(4):編程入門(NFS登錄,驅(qū)動入門)
[OK210開發(fā)板體驗]功能篇(1):Linux字符驅(qū)動之Led
[OK210開發(fā)板體驗]功能篇(2):Linux字符驅(qū)動之Key按鍵
[OK210開發(fā)板體驗]功能篇(3):Linux Input子系統(tǒng)之Key按鍵
[OK210開發(fā)板體驗]功能篇(4):Linux字符驅(qū)動之DS18B20
[OK210開發(fā)板體驗]功能篇(5):Linux字符驅(qū)動之PWM蜂鳴器
[OK210開發(fā)板體驗]功能篇(6):Linux字符驅(qū)動之紅外遙控    
今天是功能篇的第七篇:Linux字符驅(qū)動之ADC模數(shù)轉(zhuǎn)換,本節(jié)主要分3部分:硬件分析,軟件基礎(chǔ),驅(qū)動編程。
一、硬件分析
【OK210試用體驗】的第二篇:板載資源中,簡單分析了ADC的
功能和作用。其實對ADC的操作,可以看作是對一些模擬量的采集操作,如溫濕度,光度。
首先從OK210的底板原理圖中可知,通過一個可調(diào)電位器RP1,連接到S5PV210的模擬輸入0通道,通過對該可調(diào)電位器的調(diào)節(jié)可以改變模擬通道的輸入電壓值,電壓的調(diào)節(jié)范圍為0V-3V,可以利用這個可調(diào)電位器來熟悉S5PV210的ADC控制器的使用,如下圖所示

模擬輸入0通首家引腳為XADCAIN0,
由S5PV210用戶手冊,可知,該引腳是一個模塊引腳,如下圖所示。
所以,我們要對ADC進行操作,就是通過對配置ADC的各寄存器進行實現(xiàn)。
二、軟件基礎(chǔ)
1、S5PV210的ADC的主要特征:
(1)分辨率(輸出離散值的個數(shù))可以是10位或者12位(可以通過TSADCCON0/TSADCCON1的第16位RES進行設(shè)定,將RES設(shè)為0表示10位,設(shè)為1表示12位)。
(2)10通道的模擬輸入(AIN[9]---AIN[0])。
(3)輸入電壓為0--3.3V。
(4)最大轉(zhuǎn)換速率:1MSPS。
對于轉(zhuǎn)換速率的計算,S5PV210_UM手冊上是這樣介紹的:當(dāng)PCLK=66MHz時,預(yù)分頻比P=65時,12位分辨率的轉(zhuǎn)換時間如下:
A/D轉(zhuǎn)換頻率=66MHz/(65+1)=1MHz
A/D轉(zhuǎn)換時間=1/(1MHz/5cycles)=1/200kHz=5us
PS
1)A/D的轉(zhuǎn)換頻率最大可達5MHz,所以A/D轉(zhuǎn)換時間最大可達1MSPS
2)對于官方手冊上,所說的A/D轉(zhuǎn)換時間的計算,可能我們第一次看的時候會不太理解,會什么要1MHz/5cycles?原因是這樣的,因為A/D轉(zhuǎn)換時間包括A/D建立時間,1位1位轉(zhuǎn)換時間,保存數(shù)據(jù)時間等等,加起來一共是5個時鐘周期,所以會是1MHz/5cycles。其實對于A/D轉(zhuǎn)換時間=1/(1MHz/5cycles) 可以換種寫法:A/D轉(zhuǎn)換時間=1/1MHz *5=5us 也是一樣的,所以當(dāng)最大轉(zhuǎn)換頻率達到5MHz時,A/D轉(zhuǎn)換時間可達1MSPS(SPS為每秒的采樣率)。
(5)具有采樣保持功能。
(6)ADC有10通道的模擬輸入,但是只有AIN[0],AIN[1]沒有復(fù)用,而AIN[2]--AIN[9]是復(fù)用為觸摸屏的兩路控制信號(XM,XP)。
2、S5PV210的ADC的寄存器
操作AIN[0],在編寫ADC的驅(qū)動代碼的時候,一般情況下只需考慮三個寄存器,分別是
寄存器 映射地址 讀/寫 描述 復(fù)位值
TSADCCON0 0xE170_0000 R/W TS0/ADC控制寄存器 0x0000_3FC4
TSDLY0 0xE170_0008 R/W TS0/ADC延時寄存器 0x0000_00FF
TSDATX0 0xE170_000C R TS0/ADC轉(zhuǎn)換數(shù)據(jù)X寄存器 ---
上面表格即是操作AIN[0]時需要用到的三個寄存器,對于上面的映射地址,在編寫驅(qū)動代碼的時候可使用ioremap的形式進行映射,然后強制轉(zhuǎn)為(volatile unsigned long *)指針形式,接下來就可以直接操作寄存器。
3、S5PV210的ADC操作流程
主要分三步:
(1)使用clk_get獲取adc時鐘,接著使用clk_enable使能adc時鐘;
(2)設(shè)置ADCCON的工作方式,預(yù)分頻比之類的;
(3)當(dāng)開始讀取數(shù)據(jù)值時,開啟ADC轉(zhuǎn)換,判斷是否開始轉(zhuǎn)換,判斷是否轉(zhuǎn)換完成,最終讀取數(shù)據(jù),返回給用戶空間。值得注意的是,S5PV210的控制寄存器,是沒有通道選擇的,因為在使用之前,直接用ioremap進行地址的映射時,就相當(dāng)于選擇了不同的通道,所以無需通道選擇了。

三、驅(qū)動編程

有圖有真相,

1 驅(qū)動代碼
  1. #include <linux/kernel.h>
  2. #include <linux/module.h>
  3. #include <linux/slab.h>
  4. #include <linux/input.h>
  5. #include <linux/init.h>
  6. #include <linux/errno.h>
  7. #include <linux/serio.h>
  8. #include <linux/delay.h>
  9. #include <linux/clk.h>
  10. #include <linux/wait.h>
  11. #include <linux/sched.h>
  12. #include <linux/cdev.h>
  13. #include <linux/miscdevice.h>

  14. #include <asm/io.h>
  15. #include <asm/irq.h>
  16. #include <asm/uaccess.h>

  17. #include <mach/map.h>
  18. #include <mach/regs-clock.h>
  19. #include <mach/regs-adc.h>
  20. #include <mach/regs-gpio.h>
  21. #include <plat/regs-timer.h>

  22. #undef DEBUG
  23. //#define DEBUG
  24. #ifdef DEBUG
  25. #define DPRINTK(x...) {printk(__FUNCTION__"(%d): ",__LINE__);printk(##x);}
  26. #else
  27. #define DPRINTK(x...) (void)(0)
  28. #endif

  29. #define DEVICE_NAME        "adc"
  30. static void __iomem *base_addr;

  31. typedef struct {
  32.         wait_queue_head_t wait;
  33.         int channel;
  34.         int prescale;
  35. } ADC_DEV;

  36. static int __ADC_locked = 0;
  37. static ADC_DEV adcdev;
  38. static volatile int ev_adc = 0;
  39. static int adc_data;
  40. static struct clk        *adc_clock;

  41. #define __ADCREG(name)        (*(volatile unsigned long *)(base_addr + name))
  42. #define ADCCON                        __ADCREG(S3C_ADCCON)        // ADC control
  43. #define ADCTSC                        __ADCREG(S3C_ADCTSC)        // ADC touch screen control
  44. #define ADCDLY                        __ADCREG(S3C_ADCDLY)        // ADC start or Interval Delay
  45. #define ADCDAT0                        __ADCREG(S3C_ADCDAT0)        // ADC conversion data 0
  46. #define ADCDAT1                        __ADCREG(S3C_ADCDAT1)        // ADC conversion data 1
  47. #define ADCUPDN                        __ADCREG(S3C_ADCUPDN)        // Stylus Up/Down interrupt status

  48. #define PRESCALE_DIS                (0 << 14)
  49. #define PRESCALE_EN                        (1 << 14)
  50. #define PRSCVL(x)                        ((x) << 6)
  51. #define ADC_INPUT(x)                ((x) << 3)
  52. #define ADC_START                        (1 << 0)
  53. #define ADC_ENDCVT                        (1 << 15)

  54. #define START_ADC_AIN(ch, prescale) \
  55.         do { \
  56.                 ADCCON = PRESCALE_EN | PRSCVL(prescale) | ADC_INPUT((ch)) ; \
  57.                 ADCCON |= ADC_START; \
  58.         } while (0)


  59. static irqreturn_t adcdone_int_handler(int irq, void *dev_id)
  60. {
  61. #if        1
  62.         if (__ADC_locked) {
  63.                 adc_data = ADCDAT0 & 0x3ff;

  64.                 ev_adc = 1;
  65.                 wake_up_interruptible(&adcdev.wait);

  66.                 /* clear interrupt */
  67.                 __raw_writel(0x0, base_addr + S3C_ADCCLRINT);
  68.         }
  69. #endif

  70.         return IRQ_HANDLED;
  71. }

  72. static ssize_t ok210_adc_read(struct file *filp, char *buffer, size_t count, loff_t *ppos)
  73. {
  74.         char str[20];
  75.         int value;
  76.         size_t len;

  77.     __ADC_locked = 1;
  78.     START_ADC_AIN(adcdev.channel, adcdev.prescale);
  79.     wait_event_interruptible(adcdev.wait, ev_adc);
  80.     ev_adc = 0;
  81.     DPRINTK("AIN[%d] = 0x%04x, %d\n", adcdev.channel, adc_data, ADCCON & 0x80 ? 1:0);
  82.     value = adc_data;
  83.     __ADC_locked = 0;
  84.         len = sprintf(str, "%d\n", value);
  85.         if (count >= len) {
  86.                 int r = copy_to_user(buffer, str, len);
  87.                 return r ? r : len;
  88.         } else {
  89.                 return -EINVAL;
  90.         }
  91. }

  92. static int ok210_adc_open(struct inode *inode, struct file *filp)
  93. {
  94.         init_waitqueue_head(&(adcdev.wait));

  95.         adcdev.channel=0;
  96.         adcdev.prescale=0xff;

  97.         DPRINTK("adc opened\n");
  98.         return 0;
  99. }

  100. static int ok210_adc_release(struct inode *inode, struct file *filp)
  101. {
  102.         DPRINTK("adc closed\n");
  103.         return 0;
  104. }


  105. static struct file_operations dev_fops = {
  106.         owner:        THIS_MODULE,
  107.         open:        ok210_adc_open,
  108.         read:        ok210_adc_read,
  109.         release:        ok210_adc_release,
  110. };

  111. static struct miscdevice misc = {
  112.         .minor        = MISC_DYNAMIC_MINOR,
  113.         .name        = "adc",        //DEVICE_NAME,
  114.         .fops        = &dev_fops,
  115. };

  116. static int __init dev_init(void)
  117. {
  118.         int ret;

  119.         base_addr = ioremap(SAMSUNG_PA_ADC, 0x20);
  120.         if (base_addr == NULL) {
  121.                 printk(KERN_ERR "Failed to remap register block\n");
  122.                 return -ENOMEM;
  123.         }

  124.         adc_clock = clk_get(NULL, "adc");
  125.         if (!adc_clock) {
  126.                 printk(KERN_ERR "failed to get adc clock source\n");
  127.                 return -ENOENT;
  128.         }
  129.         clk_enable(adc_clock);

  130.         /* normal ADC */
  131.         ADCTSC = 0;

  132. #if        1
  133.         ret = request_irq(IRQ_ADC, adcdone_int_handler, IRQF_SHARED, DEVICE_NAME, &adcdev);
  134.         if (ret) {
  135.                 printk("request IRQ %d failed for adc, %d\n", IRQ_ADC, ret);
  136.                 iounmap(base_addr);
  137.                 return ret;
  138.         }
  139. #endif

  140.         ret = misc_register(&misc);

  141.         printk (DEVICE_NAME"\tinitialized\n");
  142.         return ret;
  143. }

  144. static void __exit dev_exit(void)
  145. {
  146.         free_irq(IRQ_ADC, &adcdev);
  147.         iounmap(base_addr);

  148.         if (adc_clock) {
  149.                 clk_disable(adc_clock);
  150.                 clk_put(adc_clock);
  151.                 adc_clock = NULL;
  152.         }

  153.         misc_deregister(&misc);
  154. }

  155. module_init(dev_init);
  156. module_exit(dev_exit);

  157. MODULE_LICENSE("GPL");
  158. MODULE_AUTHOR("gjianw217@163.com");
  159. MODULE_DESCRIPTION("ADC driver");
復(fù)制代碼
2 測試代碼
  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <stdlib.h>
  4. #include <sys/types.h>
  5. #include <sys/stat.h>
  6. #include <sys/ioctl.h>
  7. #include <fcntl.h>
  8. #include <linux/fs.h>
  9. #include <errno.h>
  10. #include <string.h>

  11. int main(void)
  12. {
  13.         fprintf(stderr, "press Ctrl-C to stop\n");
  14.         int fd = open("/dev/adc", "r");
  15.         if (fd < 0) {
  16.                 perror("open ADC device:");
  17.                 return 1;
  18.         }
  19.         ioctl(fd,'s',0);
  20.         int ADCValue=0;
  21.         double voltage=0;
  22.         for(;;) {
  23.                 ADCValue=0;
  24.                 voltage=0;
  25.                 read(fd, &ADCValue, sizeof(int));
  26.                 if(ADCValue==-1) continue;
  27.                 //channel 0 12bit ADC max voltage = 3.3v /10K+1k *10K =3v
  28.                 voltage=(float)3.3*ADCValue/4096;
  29.                 printf("adc = %d voltage=%f V \n",ADCValue,voltage);
  30.                 usleep(500* 1000);
  31.         }

  32.         close(fd);
  33. }
復(fù)制代碼
3 Makefile
  1. #adc Makefile
  2. ARCH=arm
  3. CROSS_COMPILE=/home/ok210/arm-2009q3/bin/arm-none-linux-gnueabi-
  4. APP_COMPILE=/home/ok210/arm-2009q3/bin/arm-none-linux-gnueabi-
  5. #obj-m := app-drv.o
  6. obj-m := pwm-drv.o
  7. #KDIR := /path/to/kernel/linux/
  8. KDIR := /home/ok210/android-kernel-samsung-dev/
  9. PWD := $(shell pwd)
  10. default:
  11.         make -C $(KDIR) M=$(PWD) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules
  12. app:pwm-app.c
  13.         $(APP_COMPILE)gcc -o app pwm-app.c
  14. clean:
  15.         $(MAKE) -C $(KDIR) M=$(PWD) clean
復(fù)制代碼

相關(guān)產(chǎn)品 >

  • OKMX6UL-C開發(fā)板

    飛凌嵌入式專注imx6系列imx6ul開發(fā)板、飛思卡爾imx6ul核心板等ARM嵌入式核心控制系統(tǒng)研發(fā)、設(shè)計和生產(chǎn),i.mx6UL系列產(chǎn)品現(xiàn)已暢銷全國,作為恩智浦imx6ul,imx6ul開發(fā)板,i.mx6提供者,飛凌嵌入式提供基于iMX6 iMX6UL解決方案定制。

    了解詳情
    OKMX6UL-C開發(fā)板
  • OKMX6ULL-C開發(fā)板

    40*29mm,雙網(wǎng)雙CAN,8路串口| i.MX6ULL開發(fā)板是基于NXP i.MX6ULL設(shè)計開發(fā)的的一款Linux開發(fā)板 ,主頻800MHz,體積小,其核心板僅40*29mm,采用板對板連接器,適應(yīng)場景豐富。 了解詳情
    OKMX6ULL-C開發(fā)板

推薦閱讀 換一批 換一批