mini2440驅動奇譚——ADC驅動與測試(動態掛載驅動)

weixin_34119545發表於2016-01-02

部落格:http://blog.csdn.net/muyang_ren

實現功能:開發板動態載入adc驅動模組並能通過測試程式

系統:Ubuntu 14.04     驅動交叉編譯核心:linux-2.6.32.2     開發板:mini2440  

建立交叉編譯請點選。燒寫linux到開發板請點選。Linux RootFs 選擇rootfs_rtm_2440.img  (光碟資料夾:image/linux/rtm )

開發所需工具:NFS網路檔案  minicom  vim

linux檔案資料夾:/opt/FriendlyARM/mini2440/linux-2.6.32.2

自己驅動資料夾 : /home/lianghuiyong/my2440drivers

注意:我的開發板核心被我裁剪了adc驅動部分,包含之後要寫的一些驅動,裁剪核心驅動非常easy,make menuconfig選單後相應的驅動空格為空即可了


ADC原理


ADC模組共同擁有8個模擬訊號通道(XP、XM、YP、YM。A[ 3 : 0 ]),我們使用最多的是觸控式螢幕,這時設定XP、XM、YP、YM選擇為觸控式螢幕的引腳,XM、YM則分別為X、Y方向接地線,為低電平。XP、YP使能後,筆尖等讓觸控式螢幕接觸點受壓產生電壓變化來計算產生的X 、Y軸座標。並將座標值分別儲存於ADCDAT0。ADCDAT1中。觸控式螢幕就先這樣簡介下,當觸控式螢幕引腳為禁止時,這四個port(XP、XM、YP、YM)可被用於ADC的模擬輸入port(AIN4、AIN5、AIN6和AIN7);


原理:這裡模擬訊號源選擇開發板上的可調電位器。從電位器電路圖中可知模擬訊號輸出端為AIN0。可調電位器阻值的改變產生電壓的變化,設定MUX多路模擬訊號選擇器為AIN0,進行模數轉換,並將產生的資料儲存於ADCDAT0中。順便說一下。MUX(8選1)可選擇XP、XM、YP、YM作為模擬訊號源,但此時不是觸控式螢幕訊號。而是AIN4、AIN5、AIN6或者AIN7的模擬訊號


驅動部分

adc.c

/*************************************************************************
	> File Name: adc.c
	> Author: 樑惠湧
	> Mail: 
	> Created Time: 2014年10月08日 星期三 21時01分48秒
 ************************************************************************/
//在 linux-2.6.32.2/arch/arm/mach-s3c2410/include/mach 資料夾下
#include<mach/regs-gpio.h>         // 和GPIO相關的巨集定義 
#include<mach/hardware.h>          //S3C2410_gpio_cfgpin等gpio函式定義
//在 linux-2.6.32.2/include/linux 資料夾下
#include<linux/miscdevice.h>        //註冊 miscdevide 結構體成員變數
#include<linux/delay.h>             //這個應該是時延函式了
#include<linux/kernel.h>            //核心。不用說
#include<linux/module.h>            //驅動載入解除安裝函式定義
#include<linux/init.h>              //初始化標頭檔案
#include<linux/mm.h>
#include<linux/fs.h>                //註冊file_operations結構體變數
#include<linux/types.h>
#include<linux/moduleparam.h>
#include<linux/slab.h>
#include<linux/errno.h>              
#include<linux/ioctl.h>
#include<linux/cdev.h>
#include<linux/string.h>
#include<linux/list.h>
#include<linux/pci.h>
#include<linux/gpio.h>              //gpio口定義檔案(虛擬地址)
#include<linux/clk.h>               //系統時脈頻率初始化檔案
#include<linux/sched.h>
#include<linux/interrupt.h>         //巨集定義 IRQ_HANDLED 等
//在 linux-2.6.32.2/arch/arm/include/asm 資料夾下
#include<asm/io.h>           
#include<asm/irq.h>
#include<asm/uaccess.h>
#include<asm/atomic.h>
#include<asm/unistd.h>
//在 linux-2.6.32.2/arch/arm/plat-s3c/include/plat 資料夾下
#include<plat/regs-adc.h>            //定義ADCCON ADCDAT0等暫存器

#define DEVICE_NAME "adc"

static DECLARE_WAIT_QUEUE_HEAD(adc_waitq);
static void __iomem *adc_base;
static struct clk *adc_clk;
static int adc_data;                 
static volatile int ev_adc =0;  
/* (1) 定義等待變數adc_waitq
 * (2) 定義虛擬地址指標 adc_base, __iomem是2.6.9之後增加的特性,用來表示
 * 指向一個 I/O 的虛擬記憶體空間,void能夠使編譯器忽略對變數的檢查。

* (3) 定義一個clk型別的指標變數adc_clk,用於儲存標頭檔案裡adc時鐘的設定。 * (4) clk結構體是定義在 s3c2410-clock.c檔案裡。 * (5) 中volatile確保讀取ev_adc的值不會被編譯器的優化忽略 * */ static ssize_t adc_read(struct file *fp, char *buf, size_t count, loff_t *ppos) { unsigned int tmp; tmp = (1<<14)|(255<<6)|(0<<3)|(1<<0); writel(tmp, adc_base + S3C2410_ADCCON); //AD轉換轉換開始 wait_event_interruptible(adc_waitq, ev_adc); ev_adc =0; copy_to_user(buf, (char *)&adc_data, sizeof(adc_data)); return sizeof(adc_data); } /* (1) adc_read函式中,ssize_t是表示讀寫資料塊的大小,型別是無符號整形 * (2) (1<<14) 預分頻使能,(255<<6)預分頻值設為255,(0<<3)模擬輸入通道AIN0 * (1<<0)使能AD轉換,依據開發板啟動資訊(S3C244X:core 405 MHZ 等)能夠知道開 * 發板 PCLK為50.625MHZ,A/D轉換頻率=50.625MHZ /(255+1)=0.197MHZ,A/D轉換時間 * =1/(0.197/5)=26uS。

* (3) writel函式是將tmp的值寫入adc虛擬控制暫存器 * (4) wait_event_interruptible使等待佇列進入睡眠,應該是為了copy_to_user * 傳遞的不是一個正在改變的值的值,adc_data值讀取在adc_irq函式實現 * (5) copy_to_user。將讀取到的值傳送到測試程式(使用者層)返回adc_data的位元組數 * */ static irqreturn_t adc_irq(int irq, void *dev_id){ if(!ev_adc){ adc_data=readl(adc_base + S3C2410_ADCDAT0) & 0x3ff; ev_adc= 1; wake_up_interruptible( &adc_waitq); } return IRQ_HANDLED; } /* (1)adc_irq在以下函式被引用的,irqreturn_t在irqreturn.h中定義列舉型別 * (2)adc_data儲存ADCDAT0虛擬資料暫存器後十位值 * (3)wake_up_interruptible喚醒adc_waitq佇列 * (4)IRQ_HANDLED巨集定義為1,表示接收到了準確中斷訊號,並作了對應正確的處理 * */ static int adc_open(struct inode *inode, struct file *fp){ int ret; ret=request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME, &adc_data); if(ret){ printk(KERN_ERR"IRQ %d error %d \n", IRQ_ADC , ret); } return 0; } /* (1) request_irq中斷申請函式 * 引數1:中斷源, 引數2:中斷處理函式, * 引數3:中斷方式。 引數4:裝置名字, 引數5:裝置ID * (2) 當中裝置ID不能使用NULL,否則ret會為1值,報錯,裝置ID一般為驅動結構體 * 地址。但我沒有使用結構體,直接使用&adc_data; * (3) 看過一些文章說寫1等。可是會有warning。原因是裝置ID引數是一個指標,需 * 要寫一個地址才行。

* */ static struct file_operations dev_fops={ .owner = THIS_MODULE, .open = adc_open, .read = adc_read, }; static struct miscdevice misc = { .minor = MISC_DYNAMIC_MINOR, .name = DEVICE_NAME, .fops = &dev_fops, }; static int __init adc_init(void){ int ret; adc_base = ioremap(S3C2410_PA_ADC, 4); adc_clk=clk_get(NULL, "adc"); if(!adc_clk){ printk(KERN_ERR "failed to find adc clock source! \n"); return -ENOENT; } printk(KERN_ALERT "Hello! mini2440 adc module is installed\n"); clk_enable(adc_clk); ret = misc_register(&misc); return ret; } /* (1) 函式__init是一個屬性標誌。當驅動模組被載入時,__init函式就被執行了 * (2) ioremap函式是將實體地址對映成虛擬地址並傳遞於adc_base * (3) clk_get是從標頭檔案裡獲取adc時鐘設定,定義在s3c2410-clock.c,因為核心 * 並沒有adc驅動。所以adc的時鐘並沒初始化,clk_enable(adc_clk)初始化 adc * 時鐘misc_register函式是註冊混雜裝置驅動 * */ static void __exit adc_exit(void){ printk(KERN_ALERT "Good-bye ,mini2440 adc module is removed! \n"); misc_deregister(&misc); } /* (1) __exit和__init一樣是module屬性標誌 * (2) misc_deregister函式是登出混雜裝置驅動 * */ module_init(adc_init); module_exit(adc_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Lianghuiyong Inc.");



Makefile

ifeq ($(KERNELRELEASE),)
	KDIR ?= /opt/FriendlyARM/mini2440/linux-2.6.32.2/
	PWD  := $(shell pwd)
modules:
	$(MAKE) -C $(KDIR) M=$(PWD) modules
modules_install:
	$(MAKE) -C $(KDIR) M=$(PWD) modules_install
clean:
	rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
else
	obj-m:= adc.o
endif

/* 第1行:推斷 KERNELRELEASE 變數是否為空,僅僅有make命令該變數才不為空。

* 第2、3行:KDIR是核心路徑,PWD是當前模組路徑。

* 第4行:是makefile的功能選項。冒號結尾。 * 第5行:是執行模組的編譯,語法是“Make -C 核心路徑 M=模組路徑 modules”。 * 第6、7行和第4、5行一樣。 * 第8行:刪除多餘檔案功能標識。 * 第9行:是刪除編譯過程的中間檔案的命令。 * 第11行:假設make命令沒有帶功能標識(modules、modules_install、clean)使用, * 就將adc.o編譯成adc.ko模組 * */



測試模組

adcceshi.c

/*************************************************************************
	> File Name: adcceshi.c
	> Author: 樑惠湧
	> Mail: 
	> Created Time: 2014年10月10日 星期五 23時41分26秒
 ************************************************************************/

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>

void delay(int j){
    int i;
    for(;j>0;j--)
    for(i=10000;i>0;i--);
}

int main (int argc, char **argv){
    int fd;
    fd = open("/dev/adc",0);
    
    if( fd<0 ){
        printf("open adc device faild! \n");
        exit(1);
    }

    while(1){
        int ret;
        int data;

        ret =read(fd, &data, sizeof(data));
        if(ret !=sizeof(data)){
            if(errno!=EAGAIN){
                printf(" Read ADC Device Faild! \n");
            } 
            continue;
        }

        else {
            printf("    Read ADC : %d \n",data);
        }
        delay(5000);
    }
    close(fd);
    return 0;
}
/* (1) read(fd,&data,sizeof(data)) 函式將跳轉到驅動file_openration
 * 中的.read =adc_read。進入adc_read函式,通過copy_to_user將驅動程
 * 序中adc_data 傳遞給使用者層測試程式data。return返回data位元組數
 * (2) 當中驅動中 adc_data的值是在驅動程式 adc_irq函式中通過readl從
 * ADCDAT0虛擬地址獲取.
 * (3) delay函式通過多次空迴圈實現時間的延遲
 * */

測試程式使用命令:arm-linux-gcc -g adcceshi.c -o adcceshi  來生成測試程式adcceshi


開發板執行截圖

當中rmmod: module 'adc' not found提示是由於是沒把驅動程式放在/lib/modules/2.6.32.2-FriendlyARM下,我直接在掛載的目錄下使用,

這個提示能夠忽略


錯誤筆記

1 、error :‘TASK_INTERRUPTIBLE' undeclared (first use in this function)

包括標頭檔案: #include<linux/sched.h>


2、 error: 'IRQF_SHARED' undeclared

包括標頭檔案:#include<linux/interrupt.h>


3、掛載驅動後執行測試程式出現:

Unable to handle kernel NULL pointer dereference at virtual address 00000000    
pgd = c3934000                                                                  
[00000000] *pgd=33969031, *pte=00000000, *ppte=00000000                         
Internal error: Oops: 817 [#2]                                                  
last sysfs file: /sys/devices/virtual/misc/adc/dev                              
Modules linked in: adc [last unloaded: adc]                                     
CPU: 0    Tainted: G      D     (2.6.32.2-FriendlyARM #2)                       
PC is at adc_read+0x20/0x10c [adc]                                              
LR is at vfs_read+0xac/0xe0                                                     
pc : [<bf00c0e0>]    lr : [<c008ec10>]    psr: a0000013                         
sp : c393bf20  ip : c393bf58  fp : c393bf54                                     
r10: 00000000  r9 : c393a000  r8 : c0023088                                     
r7 : bec9fd34  r6 : c393bf78  r5 : bec9fd34  r4 : bf00c470                      
r3 : 00000000  r2 : 00007fc1  r1 : bec9fd34  r0 : c390e380                      
Flags: NzCv  IRQs on  FIQs on  Mode SVC_32  ISA ARM  Segment user               
Control: c000717f  Table: 33934000  DAC: 00000015                               
Process adcceshi (pid: 664, stack limit = 0xc393a270)                           
Stack: (0xc393bf20 to 0xc393c000)                                               
bf20: 00000000 c390e380 c399b904 00000000 c393bf64 c393bf40 c390e380 bec9fd34   
bf40: c393bf78 00000003 c393bf74 c393bf58 c008ec10 bf00c0d0 c390e380 00000000   
bf60: 00000000 00000003 c393bfa4 c393bf78 c008ed1c c008eb74 00000000 00000000   
bf80: 00000005 00000000 c393bfa4 00000000 00000000 00000000 00000000 c393bfa8   
bfa0: c0022ee0 c008ece0 00000000 00000000 00000003 bec9fd34 00000004 bec9fd34   
bfc0: 00000000 00000000 00000000 00000003 00000000 00000000 40024000 bec9fd44   
bfe0: 00000000 bec9fd28 00008550 400daebc 60000010 00000003 00000000 00000000   
Backtrace:                                                                      
[<bf00c0c0>] (adc_read+0x0/0x10c [adc]) from [<c008ec10>] (vfs_read+0xac/0xe0)  
 r7:00000003 r6:c393bf78 r5:bec9fd34 r4:c390e380                                
[<c008eb64>] (vfs_read+0x0/0xe0) from [<c008ed1c>] (sys_read+0x4c/0x84)         
 r7:00000003 r6:00000000 r5:00000000 r4:c390e380                                
[<c008ecd0>] (sys_read+0x0/0x84) from [<c0022ee0>] (ret_fast_syscall+0x0/0x28)  
 r6:00000000 r5:00000000 r4:00000000                                            
Code: e59f40e0 e59f20e0 e5943004 e1a07001 (e5832000)                            
---[ end trace 929b7bc9d9ae4dda ]---                                            
Segmentation fault 

僅僅需注意上面中:PC is at adc_read+0x20/0x10c [adc]   出錯的是 adc_read函式。從 Unable to handle kernel NULL pointer dereference at virtual address 00000000  

可看出是指標的問題。看整個驅動發現是由於adc_base沒有賦予虛擬地址值,在驅動載入函式中新增:adc_base= ioremap(S3C2410_PA_ADC, 0x20);adc實體地址轉換成虛擬地址並儲存於adc_base 中


4、IRQ 80 error -22  錯誤

當中irq中斷函式 ret = request_irq(IRQ_ADC, adc_irq, IRQF_SHARED, DEVICE_NAME,NULL); 最後一個引數不能為NULL,我改動後為 &adc_data

相關文章