轉(zhuǎn)自: http://blog.csdn.net/ielife/article/details/7798999 主要講述本人在學(xué)習(xí)Linux內(nèi)核input子系統(tǒng)的全部過程,如有分析不當(dāng),多謝指正。以下交流方式,文章歡迎轉(zhuǎn)載,保留聯(lián)系信息,以便交流。
郵箱:eabi010@gmail.com
主頁:www.(愛嵌論壇——嵌入式技術(shù)學(xué)習(xí)交流)
博客:blog.csdn.net/ielife
1 mini2440的ADC驅(qū)動(dòng)實(shí)例
這節(jié)與輸入子系統(tǒng)無關(guān),出現(xiàn)在這里是因?yàn)楹竺娴恼鹿?jié)會(huì)講到觸摸屏輸入子系統(tǒng)驅(qū)動(dòng),由于觸摸屏也使用ADC,因此本節(jié)是為了說明ADC通過驅(qū)動(dòng)代碼是如何控制的。
本節(jié)重點(diǎn):
- 如何通過原理圖查找ADC硬件使用的資源
- 如何通過芯片手冊(cè)查找ADC硬件的操作方法
- ADC設(shè)備驅(qū)動(dòng)程序的初始化流程
- ADC設(shè)備驅(qū)動(dòng)程序的中斷處理流程
本節(jié)難點(diǎn):
- ADC的控制寄存器的操作方法
- ADC驅(qū)動(dòng)程序的控制邏輯
1.1 模數(shù)轉(zhuǎn)換(ADC)簡(jiǎn)介
ADC是把模擬信號(hào)轉(zhuǎn)化為計(jì)算機(jī)能夠處理的數(shù)字信號(hào)的過程。
模擬信號(hào)一般為電壓,或者是電流。有些時(shí)候也可以是非電信號(hào),如溫度、濕度、聲音、位移等,它們通過傳感器轉(zhuǎn)換為電壓信號(hào)傳遞給A/D轉(zhuǎn)換器才可以進(jìn)行A/D轉(zhuǎn)換。
1.2 mini2440上的可調(diào)電阻
由mini2440的用戶手冊(cè)的1.3.8節(jié)A/D輸入測(cè)試可知,S3C2440的AIN0引腳接到了開發(fā)板的可調(diào)電阻W1上,原理圖如下圖3所示:
圖3 mini2440可調(diào)電阻原理圖
上圖中,1、2電路的狀態(tài)是能夠確定的,一個(gè)接3.3V電壓,一個(gè)接地,中間接可變電阻W1(10K)。而引腳3接AIN0,它是什么?可以通過mini2440開發(fā)板原理圖來查找:
圖4 mini2440可調(diào)電阻與S3C2440接口電路
通過上圖可知,開發(fā)板的AIN0引腳與S3C2440 CPU芯片上的AIN0引腳相連接。因此需要進(jìn)一步查看S3C2440芯片手冊(cè)獲得AIN0引腳的作用。
下圖5是S3C2440芯片手冊(cè)的第16章對(duì)A/D轉(zhuǎn)換器和觸摸屏接口的介紹。S3C2440內(nèi)部共有8個(gè)通道的模擬輸入接口,其轉(zhuǎn)換的模擬信號(hào)為10位的二進(jìn)制數(shù)字編碼。
A[3:0]分別代表AIN0、AIN1、AIN2、AIN3,觸摸屏接口可以控制/選擇觸摸屏X、Y方向的引腳(XP,XM,YP,YM)的變換。
圖5 A/D轉(zhuǎn)換器和觸摸屏的功能結(jié)構(gòu)圖
那么ADC如何實(shí)現(xiàn)模擬信號(hào)到數(shù)字信號(hào)的轉(zhuǎn)換呢,由上圖可知,模擬信號(hào)通過8個(gè)通道的任意一個(gè)輸入,然后通過分頻器決定A/D轉(zhuǎn)換器的頻率,最后通過ADC將模擬信號(hào)轉(zhuǎn)換為數(shù)字信號(hào)保存在ADCDAT0中,ADCDAT0中的數(shù)據(jù)可以通過查詢或者中斷的方式來獲得。
S3C2440模數(shù)轉(zhuǎn)換器的控制邏輯可由以下寄存器來進(jìn)行操作:
ADCCON ADC控制寄存器
ADCTSC ADC觸摸屏控制寄存器器
ADCDLY ADC啟動(dòng)初始化延遲寄存器
ADCDAT0 ADC轉(zhuǎn)換數(shù)據(jù)寄存器
ADCDAT1 ADC轉(zhuǎn)換數(shù)據(jù)寄存器
ADCUPDN 筆尖抬起或落下中斷狀態(tài)寄存器
由以上內(nèi)容,開發(fā)板可以通過W1可變電阻的阻值變化產(chǎn)生電壓的變化,由AIN0引腳傳遞給ADC控制器轉(zhuǎn)化為數(shù)字信號(hào),我們通過驅(qū)動(dòng)來獲得可調(diào)電阻W1硬件的變化。
1.3 可調(diào)電阻的ADC驅(qū)動(dòng)程序
既然需要寫驅(qū)動(dòng),首先先確定可調(diào)電阻的ADC驅(qū)動(dòng)屬于什么設(shè)備。由于是順序讀取寄存器ADCDAT0的過程,所以把它看成一個(gè)字符設(shè)備,而且對(duì)于這個(gè)設(shè)備來說,更簡(jiǎn)單的實(shí)現(xiàn)方法是通過misc雜項(xiàng)設(shè)備來實(shí)現(xiàn)。
代碼實(shí)現(xiàn)的非常簡(jiǎn)單,通過中斷的方式獲取ADCDAT0的前10位的值就可以了。代碼如下:
- /*
- * mini2440 ADC驅(qū)動(dòng)程序
- *
- * Kevin Lee <www.>
- */
-
- #include<linux/kernel.h> /* 提供prink等內(nèi)核特有屬性 */
- #include<linux/module.h> /* 提供如MODULE_LICENSE()、EXPORT_SYMBOL() */
- #include<linux/init.h> /* 設(shè)置段,如_init、_exit,設(shè)置初始化優(yōu)先級(jí),如__initcall */
- #include<linux/wait.h> /* 等待隊(duì)列wait_queue */
- #include<linux/interrupt.h> /* 中斷方式,如IRQF_SHARED */
- #include<linux/fs.h> /* file_operations操作接口等 */
- #include<linux/clk.h> /* 時(shí)鐘控制接口,如struct clk */
- #include<linux/miscdevice.h> /* 雜項(xiàng)設(shè)備 */
- #include<asm/io.h> /* 提供readl、writel */
- #include<asm/irq.h> /* 提供中斷號(hào),中斷類型等,如IRQ_ADC中斷號(hào) */
- #include<asm/arch/regs-adc.h> /* 提供控制器的寄存器操作,如S3C2410_ADCCON */
- #include<asm/uaccess.h> /* 提供copy_to_user等存儲(chǔ)接口 */
-
- /* 定義設(shè)備名稱,用戶訪問接口/dev/adc */
- #defineDEVICE_NAME "adc"
-
- /* 定義adc時(shí)鐘,通過adc_clock接口獲得adc輸入時(shí)鐘,adc轉(zhuǎn)換器需要 */
- staticstruct clk *adc_clock;
-
- /* 定義虛擬地址訪問硬件寄存器,__iomem只是用于表示指針將指向I/O內(nèi)存 */
- staticvoid __iomem *base_addr;
-
- /* 定義并初始化一個(gè)等待隊(duì)列adc_waitqueue,對(duì)ADC資源進(jìn)行阻塞訪問 */
- staticwait_queue_head_t adc_waitqueue;
-
- /* 定義并初始化信號(hào)量adc_lock,用于控制共享中斷IRQ_ADC資源的使用 */
- DECLARE_MUTEX(adc_lock);
- EXPORT_SYMBOL(adc_lock);
-
- /* 定義等待隊(duì)列的條件,當(dāng)is_read_ok=1時(shí),ADC轉(zhuǎn)換完畢,數(shù)據(jù)可讀 */
- staticvolatile int is_read_ok = 0;
-
- /* 定義ADC轉(zhuǎn)換的數(shù)據(jù)內(nèi)容 */
- staticvolatile int 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);
-
- /* 實(shí)現(xiàn)字符設(shè)備操作接口 */
- staticstruct file_operations adc_fops =
- {
- .owner = THIS_MODULE,
- .open = adc_open,
- .read = adc_read,
- .release = adc_close,
- };
-
- /* 實(shí)現(xiàn)misc雜項(xiàng)設(shè)備操作接口 */
- staticstruct miscdevice adc_miscdev =
- {
- .minor = MISC_DYNAMIC_MINOR, /* 動(dòng)態(tài)獲取雜項(xiàng)設(shè)備的次設(shè)備號(hào) */
- .name = DEVICE_NAME, /* 雜項(xiàng)設(shè)備的設(shè)備名稱,這里為adc */
- .fops = &adc_fops, /* 雜項(xiàng)設(shè)備子系統(tǒng)接口,指向adc_fops操作接口 */
- };
-
- /*ADC中斷服務(wù)程序,獲取ADC轉(zhuǎn)換后的數(shù)據(jù) */
- staticirqreturn_t adc_irq(int irq, void *dev_id)
- {
- /* 僅當(dāng)is_read_ok=0時(shí)才進(jìn)行轉(zhuǎn)換,防止多次中斷 */
- if(!is_read_ok)
- {
- /* 讀取ADCCON[9:0]的值,0x3ff為只獲取[9:0]位,ADCCON為轉(zhuǎn)換后的數(shù)據(jù) */
- adc_data = readl(base_addr +S3C2410_ADCDAT0) & 0x3ff;
-
- /* 設(shè)置標(biāo)識(shí)為1,喚醒讀等待進(jìn)程可以拷貝數(shù)據(jù)給用戶空間了 */
- is_read_ok = 1;
- wake_up_interruptible(&adc_waitqueue);
- }
-
- return IRQ_RETVAL(IRQ_HANDLED);
- }
-
- /*ADC設(shè)備打開,并注冊(cè)IRQ_ADC中斷處理函數(shù) */
- staticint adc_open(struct inode *inode, struct file *file)
- {
- int ret;
-
- /* 由于IRQ_ADC為共享中斷,因此中斷類型選擇IRQF_SHARED,最后一個(gè)參數(shù)需要設(shè)置NULL以外的值 */
- 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;
- }
-
- /*設(shè)置ADC控制寄存器,開啟AD轉(zhuǎn)換*/
- staticvoid adc_run(void)
- {
- volatile unsigned int adccon;
-
- /* ADCCON的位[14]=1為使能A/D預(yù)分頻器,位[13:6]=32表示設(shè)置的分頻值,ADC的轉(zhuǎn)換頻率需要在2.5MHZ以下
- * 我們使用的ADC輸入時(shí)鐘為PCLK=50MHZ,50MHZ/32<2.5MHZ,滿足條件
- * 位[5:3]=000,表示模擬輸入通道選擇AIN0
- */
- adccon = (1 << 14) | (32 << 6);
- writel(adccon, base_addr + S3C2410_ADCCON);
-
- /* 位[0]=1表示使能ADC轉(zhuǎn)換,當(dāng)轉(zhuǎn)換完畢后此位被ADC控制器自動(dòng)清0 */
- adccon = readl(base_addr + S3C2410_ADCCON)| (1 << 0);
- writel(adccon, base_addr + S3C2410_ADCCON);
- }
-
- /*ADC設(shè)備驅(qū)動(dòng)讀函數(shù) */
- staticssize_t adc_read(struct file *filp, char *buff, size_t count, loff_t *offp)
- {
- int err;
-
- /* 獲取信號(hào)量,如果被占用,睡眠等待持有者調(diào)用up喚醒
- * 這樣做的原因是,有可能其他進(jìn)程搶占執(zhí)行或是觸摸屏驅(qū)動(dòng)搶占執(zhí)行
- */
- down_interruptible(&adc_lock);
-
- /* 啟動(dòng)adc轉(zhuǎn)換,調(diào)用中斷處理函數(shù)adc_irq*/
- adc_run();
-
- /* 如果is_read_ok為假,則睡眠等待條件為真,由中斷處理函數(shù)喚醒 */
- wait_event_interruptible(adc_waitqueue,is_read_ok);
-
- /* 執(zhí)行到此說明中斷處理程序獲得了ADC轉(zhuǎn)換后的值,清除為0等待下一次的讀 */
- is_read_ok = 0;
-
- /* 將轉(zhuǎn)換后的數(shù)據(jù)adc_data提交給用戶 */
- err = copy_to_user(buff, (char*)&adc_data, min(sizeof(adc_data),count));
-
- /* 釋放信號(hào)量,并喚醒因adc_lock而睡眠的進(jìn)程 */
- up(&adc_lock);
-
- return err ? -EFAULT : sizeof(adc_data);
- }
-
- /*ADC設(shè)備關(guān)閉函數(shù) */
- 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的時(shí)鐘源,通過arch/arm/mach-s3c2410/clock.c獲得提供的時(shí)鐘源為PCLK */
- adc_clock = clk_get(NULL, "adc");
- if (!adc_clock)
- {
- printk(KERN_ERR "failed to get adcclock source\n");
- return -ENOENT;
- }
-
- /* 在時(shí)鐘控制器中給adc提供輸入時(shí)鐘,ADC轉(zhuǎn)換需要輸入時(shí)鐘 */
- clk_enable(adc_clock);
-
- /* 使用ioremap獲得操作ADC控制器的虛擬地址
- * S3C2410_PA_ADC=ADCCON,是ADC控制器的基地址,寄存器組的長(zhǎng)度=0x1c
- */
- base_addr = ioremap(S3C2410_PA_ADC, 0x1c);
- if (base_addr == NULL)
- {
- printk(KERN_ERR "Failed to remapregister block\n");
- return -ENOMEM;
- goto fail1;
- }
-
- /* 初始化等待隊(duì)列 */
- init_waitqueue_head(&adc_waitqueue);
-
- /* 注冊(cè)雜項(xiàng)設(shè)備 */
- 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);
-
- /* 禁止ADC的時(shí)鐘源 */
- if (adc_clock)
- {
- clk_disable(adc_clock);
- clk_put(adc_clock);
- adc_clock = NULL;
- }
-
- /*注銷misc設(shè)備*/
- misc_deregister(&adc_miscdev);
- }
-
- module_init(adc_init);
- module_exit(adc_exit);
-
- MODULE_AUTHOR("KevinLee <www.>");
- MODULE_DESCRIPTION("Mini2440ADC Misc Device Driver");
- MODULE_VERSION("MINI2440ADC 1.0");
- MODULE_LICENSE("GPL");
由于驅(qū)動(dòng)程序不同于應(yīng)用程序main函數(shù),因此讀者觀看以上程序的順序應(yīng)該如下所示:
首先執(zhí)行的代碼是__init adc_init函數(shù),它會(huì)被insmod加載進(jìn)內(nèi)核,當(dāng)然也可以在內(nèi)核初始化的時(shí)候加載,加載成功,應(yīng)用層訪問接口“/dev/adc”被創(chuàng)建;
其次,由于應(yīng)用層會(huì)首先打開“/dev/adc”設(shè)備,進(jìn)而操作ADC設(shè)備,因此需要查看adc_open函數(shù)做了什么。由于打開設(shè)備意味著要使用設(shè)備,所以在adc_open中注冊(cè)IRQ_ADC中斷資源;
最后,用戶會(huì)調(diào)用read函數(shù)讀取ADC轉(zhuǎn)換的值,會(huì)調(diào)用到adc_read。因此,在adc_read函數(shù)中需要設(shè)置好AIN0引腳的模擬輸入,并啟動(dòng)ADC,把讀取的任務(wù)交給adc_irq函數(shù)去完成,最后由adc_read函數(shù)把數(shù)據(jù)提交給應(yīng)用層。
如果使用insmod的方式加載,需要編寫Makefile函數(shù),如下:
- 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文件放在同一目錄下,執(zhí)行make就可以了。Makefile中使用的編譯器的名稱為arm-linux-gcc,根據(jù)自己的情況修改即可。
編譯成功,在當(dāng)前目錄下得到adc.ko驅(qū)動(dòng)模塊,使用命令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.>
srcversion: 901D02B007F9D53D9C54EA3
depends: built-in,built-in,built-in,built-in,built-in
vermagic: 2.6.22.6mod_unload ARMv4
以上信息也是我們?cè)赼dc.c代碼中添加的,還有的是在編譯過程中得到的。
把a(bǔ)dc.ko文件放到開發(fā)板中,執(zhí)行insmod adc.ko,看到如下信息則說明啟動(dòng)正常:
并且可以查看/dev目錄下,已經(jīng)有adc設(shè)備文件
# ls -l /dev/adc
crw-rw---- 1 0 0 10, 61 Jul 27 23:17 /dev/adc
1.4 可調(diào)電阻的測(cè)試程序
編寫測(cè)試程序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;
- }
源代碼簡(jiǎn)單不做說明,編譯源代碼的命令:
arm-linux-gcc -Wall -O2 adc_test.c -o adc_test
arm-linux-strip adc_test
拷貝adc_test文件到開發(fā)板,執(zhí)行命令./adc_test,顯示如下:
#./adc_test
readfrom ADC : 736
調(diào)節(jié)(旋轉(zhuǎn))電位器即轉(zhuǎn)動(dòng)變阻器,再次執(zhí)行./adc_test,顯示如下:
#./adc_test
readfrom ADC : 886
讀到的數(shù)值隨電阻值的變化而變化,由此說明驅(qū)動(dòng)及硬件工作正常。
|