1. 程式人生 > 實用技巧 >一文搞懂Cortex-A9 ADC裸機和基於Linux驅動編寫方法

一文搞懂Cortex-A9 ADC裸機和基於Linux驅動編寫方法

前言

在嵌入式開發中,ADC應用比較頻繁,本文主要講解ADC的基本原理以及如何編寫基於ARM的裸機程式和基於Linux的驅動程式。

ARM架構:Cortex-A9 Linux核心:3.14

在講述ADC之前,我們需要先了解什麼是模擬訊號和數字訊號。

模擬訊號

主要是與離散的數字訊號相對的連續的訊號。模擬訊號分佈於自然界的各個角落,如每天溫度的變化,而數字訊號是人為的抽象出來的在時間上不連續的訊號。電學上的模擬訊號是主要是指幅度和相位都連續的電訊號,此訊號可以被類比電路進行各種運算,如放大,相加,相乘等。

模擬訊號是指用連續變化的物理量表示的資訊,其訊號的幅度,或頻率,或相位隨時間作連續變化,如目前廣播的聲音訊號,或影象訊號等。

如下圖所示從上到下一次是正弦波、 調幅波、 阻尼震盪波、 指數衰減波 。

數字訊號

數字訊號指幅度的取值是離散的,幅值表示被限制在有限個數值之內。二進位制碼就是一種數字訊號。二進位制碼受噪聲的影響小,易於有數位電路進行處理,所以得到了廣泛的應用。

數字訊號:高清數字電視,MP3,JPG,PNG檔案等等。

優點:

1. 抗干擾能力強、無噪聲積累

在模擬通訊中,為了提高信噪比,需要在訊號傳輸過程中及時對衰減的傳輸訊號進行放大,訊號在傳輸過程中不可避免地疊加上的噪聲也被同時放大。

隨著傳輸距離的增加,噪聲累積越來越多,以致使傳輸質量嚴重惡化。

對於數字通訊,由於數字訊號的幅值為有限個離散值(通常取兩個幅值),在傳輸過程中雖然也受到噪聲的干擾,但當信噪比惡化到一定程度時,

即在適當的距離採用判決再生的方法,再生成沒有噪聲干擾的和原發送端一樣的數字訊號,所以可實現長距離高質量的傳輸。

2. 便於加密處理

資訊傳輸的安全性和保密性越來越重要,數字通訊的加密處理的比模擬通訊容易得多,以話音訊號為例,經過數字變換後的訊號可用簡單的數字邏輯運算進行加密、解密處理。

3. 便於儲存、處理和交換

數字通訊的訊號形式和計算機所用訊號一致,都是二進位制程式碼,因此便於與計算機聯網,也便於用計算機對數字訊號進行儲存、處理和交換,

可使通訊網的管理、維護實現自動化、智慧化。

4. 裝置便於整合化、微型

數字通訊採用時分多路複用,不需要體積較大的濾波器。裝置中大部分電路是數位電路,可用大規模和超大規模積體電路實現,因此體積小、功耗低。

5. 便於構成綜合數字網和綜合業務數字網

採用數字傳輸方式,可以通過程控數字交換裝置進行數字交換,以實現傳輸和交換的綜合。

另外,電話業務和各種非話業務都可以實現數字化,構成綜合業務數字網。

6. 佔用通道頻帶較寬

一路模擬電話的頻帶為4kHz頻寬,一路數字電話約佔64kHz,這是模擬通訊目前仍有生命力的主要原因。隨著寬頻帶通道(光纜、數字微波)的大量利用(一對光纜可開通幾千路電話)以及數字訊號處理技術的發展(可將一路數字電話的數位元速率由64kb/s壓縮到32kb/s甚至更低的數位元速率),數字電話的頻寬問題已不是主要問題了。

常用的數字訊號編碼有不歸零(NRZ)編碼、 曼徹斯特(Manchester)編碼和差分曼徹斯特(Differential Manchester)編碼。

數字訊號與模擬訊號的轉化

模擬訊號和數字訊號之間可以相互轉換:模擬訊號一般通過PCM脈碼調製(Pulse Code Modulation)方法量化為數字訊號,

即讓模擬訊號的不同幅度分別對應不同的二進位制值,例如採用8位編碼可將模擬訊號量化為2^8=256個量級,實用中常採取24位或30位編碼;

數字訊號一般通過對載波進行移相(Phase Shift)的方法轉換為模擬訊號。計算機、計算機區域網與都會網路中均使用二進位制數字訊號,

目前在計算機廣域網中實際傳送的則既有二進位制數字訊號,也有由數字訊號轉換而得的模擬訊號。但是更具應用發展前景的是數字訊號。

PCM脈衝編碼調製

脈衝編碼調製就是把一個時間連續,取值連續的模擬訊號變換成時間離散,取值離散的數字訊號後在通道中傳輸。

脈衝編碼調製就是對模擬訊號先抽樣,再對樣值幅度量化, 編碼的過程。

抽樣:

就是對模擬訊號進行週期性掃描,把時間上連續的訊號變成時間上離散的訊號。

該模擬訊號經過抽樣後還應當包含原訊號中所有資訊,也就是說能無失真的恢復原模擬訊號。

量化:

就是把經過抽樣得到的瞬時值將其幅度離散,即用一組規定的電平,把瞬時抽樣值用最接近的電平值來表示,通常是用二進位制表示。

編碼:

就是用一組二進位制碼組來表示每一個有固定電平的量化值。然而,實際上量化是在編碼過程中同時完成的,故編碼過程也稱為模/數變換,可記作A/D。

ADC

ADC,Analog-to-Digital Converter的縮寫,指模/數轉換器或者模數轉換器。是指將連續變化的模擬訊號轉換為離散的數字訊號的器件。真實世界的模擬訊號,例如溫度、壓力、聲音或者影象等,需要轉換成更容易儲存、處理和發射的數字形式。模/數轉換器可以實現這個功能,在各種不同的產品中都可以找到它的身影。

ADC最早用於對無線訊號向數字訊號轉換。如電視訊號,長短播電臺發接收等。

與之相對應的DAC,Digital-to-Analog Converter,它是ADC模數轉換的逆向過程。

現在市場上的電子產品都集成了感測器,感測器要採集資料,他的內部結構裡就一定要用到ADC,常見的感測器如下:

 溫溼度:溫度感測器,DHT11
聲音:音訊晶片進行錄音,WM8906
影象:索尼IMX386/IMX283感測器

Exynos4412 A/D轉換器

三星的Exynos4412模組結構圖如下所示:

Adc控制器整合在exynos4412 soc中,控制器內部有一根中斷線連線到中斷控制器combiner,然後路由到GIC(Generic Interrupt Controller),滑動變阻器連線到adc控制器的通道3。

ADC控制器

參考《Exynos 4412 SCP》 的datasheet。

ADC控制器是10位或12位CMOS再迴圈式模擬數字轉換器,它具有10個通道輸入,並可將模擬量轉換至10位或12位二進位制數。5Mhz A/D 轉換時鐘,最大1Msps的轉換速度。A/D轉換具備片上取樣保持功能,同時也支援待機工作模式。

ADC介面包括如下特性。

  1. 10bit/12bit輸出位可選。
  2. 微分誤差  1.0LSB。
  3. 積分誤差  2.0LSB。
  4. 最大轉換速率5Msps.
  5. 功耗少,電壓輸入1.8V。
  6. 電壓輸入範圍 0~1.8V。
  7. 支援偏上樣本保持功能。
  8. 通用轉換模式。

模組圖

4412 A/D轉換器的控制器介面框圖如下:

原理我們並不需要關注,知道即可。

通道選擇

由上圖可知,A/D控制器一共有4個通道,通用暫存器地址為0x126c0000。

A/D控制器暫存器

對ADC控制器的操作主要是通過配置暫存器來實現的,檢視datasheet,必須掌握暫存器的使用。以下是A/D控制器暫存器彙總。

1、A/D控制暫存器ADCCON

  • RES : 選擇A/D轉換精度,0:劃分成1024份 1:劃分成4096份

  • ECFLG :轉換是否結束 0:轉換中 1:轉換完畢;對於輪詢模式需要根據該位判斷資料是否轉換完畢。

  • PRSCEN:A/D轉換預分頻是否使能

  • PRSCVL:預分頻的值,轉換公式見下面

  • STANDBY:待機模式 0:正常工作模式 1:待機模式。處於待機模式時要將PRSCEN設定為0

  • READ_START: A/D轉換由讀操作觸發,設定為1後,每次讀取A/D值的操作都會觸發一次A/D轉換。

  • ENABLE_START: 單次開啟A/D轉換,轉換完畢後該位自動清零,當READ_START設定為1的時候,該位無效。

通常設定值為(1 << 16 | 1 << 14 | 99 <<6 | 1 << 1)

2、A/D轉換資料暫存器ADCDAT0

注意該暫存器的值只有低12位有效。

3、A/D清中斷暫存器CLRINTADC

黃色部分可知,中斷例程負責清中斷,中斷結束後寫入任意值就可以清中斷。

4、A/D通道選擇暫存器ADCMUX

每次操作都要先設定通道,因為 4個通道是共用同一套暫存器,如果有其他任務也在使用A/D,就會產生混亂。在此我們選擇通道3,置3即可。

5、ADC中斷ID

參見9.2.2GIC Interrupt Table

由此可知,ADC中斷號對應的SPI值是10,inturrupt ID 為42。對於終端查詢方式和編寫終端的驅動需要知道SPI id和inturrupt ID,後面講解基於Linux驅動還會再分析裝置樹節點如何填寫。

6、Combiner中斷控制器

combiner的配置暫存器:IMSRn、IECRn、ISERn、ISTRn,類似於GPIO 對中斷源分組。只有中斷模式才需要考慮combiner中斷控制器的操作。

7、Combiner分組

參考章節:10.2.1Interrupt Combiner Table 10-1Interrupt Groups of Interrupt Combiner

可見ADC在INTG10,即第10組。

8、Combiner IESR2

參考章節:10.4.2.9IESR2

如果要用中斷模式設定為1即可。

9、Combiner IECR2

參考章節:10.4.2.10IECR2

此處用於關閉中斷,採用預設值即可,注意,如果設定了1,那麼中斷功能就關閉了。

10、A/D轉換的轉換時間計算

例如:PCLK為100MHz,PRESCALER = 65 ;所有10位轉換時間為

100MHz/(99+1) = 1MHz

轉化時間為1/(1MHz/5 cycles) = 5us。

完成一次A/D轉換需要5個時鐘週期。A/D轉換器的最大工作時鐘為5MHz,所以最大采樣率可以達到1Mit/s.

電路連線圖

由該電路圖可知,外設是一個滑動變阻器,根據接觸點的不同,會導致輸入電壓的模擬值不同。連線的A/D控制器通道為3。該電路利用一個電位計輸出電壓到4412的AIN3管腳。輸入的電壓範圍為0~1.8V。

ADC裸機開發程式例項

ADC資料的讀取通常由2種方法:中斷模式、輪訓模式。

輪訓模式

輪詢模式讀取資料步驟如下:

  • 1.要讀取資料首先向ADC暫存器ADCCON的bit:1寫1,傳送轉換命令,採用讀-啟動模式來開啟轉換。
  • 2.當ADC控制器轉換完畢會將ADCCON的bit:15設定為1,
  • 3.輪詢檢測ADCCON的bit:15是否設定為1,如果設定為1,就讀走資料,否則繼續等待。

這種方式比較佔用CPU資源。

//注:這裡使用讀-啟動模式

 1 /***********************ADC ******************/
 2 #define   ADC_CFG  __REG(0x10010118)
 3 #define  ADCCON  __REG(0x126C0000)
 4 #define  ADCDLY  __REG(0x126C0008)
 5 #define  ADCDAT  __REG(0x126C000C)
 6 #define  CLRINTADC __REG(0x126C0018)
 7 #define  ADCMUX  __REG(0x126C001C)
 8 
 9 #include "exynos_4412.h"
10 #include "pwm.h"
11 #include "uart.h"
12 
13 unsigned char table[10] = {'0','1','2','3','4','5','6','7','8','9'};
14 
15 void mydelay_ms(int time)
16 {
17   int i, j;
18 
19   while(time--)
20   {
21     for (i = 0; i < 5; i++)
22     for (j = 0; j < 514; j++);
23   }
24 }
25 
26 adc_init(int temp)
27 {
28   ADCCON = (1 << 16 | 1 << 14 | 99 <<6 | 1 << 1);
29   ADCMUX = 3;
30   temp = ADCDAT & 0xfff;
31 }
32 
33 /*
34  *  裸機程式碼,不同於LINUX 應用層, 一定加迴圈控制
35  */
36 
37 int main (void)
38 {
39   unsigned char bit4,bit3,bit2,bit1;
40   unsigned int temp = 0;
41   
42   uart_init();
43   adc_init(temp);
44   puts("開始轉換\n");
45 
46   while(1)
47   {
48     while(!(ADCCON & 0x8000));
49     temp = ADCDAT & 0xfff;
50     printf("U = %d\n",temp);
51     temp = 1.8 * 1000 * temp/0xfff;
52     bit4 = temp /1000;
53     putc(table[bit4]);
54     bit3 = (temp % 1000)/100?;
55     putc(table[bit3]);
56     bit2 = ((temp % 1000)%100)/10;
57     putc(table[bit2]);
58     bit1 = ((temp % 1000)%100)%10;
59     putc(table[bit1]);
60     puts("mV");
61     putc('\n');
62     mydelay_ms(1000);
63   }
64   return 0;
65 }

中斷模式

中斷模式讀取資料步驟如下:

  • 1.要讀取資料首先向ADC暫存器ADCCON的bit:0寫1,傳送轉換命令;
  • 2.當ADC控制器轉換完畢會通過中斷線向CPU傳送中斷訊號;
  • 3.在中斷處理函式中,讀走資料,並清中斷.

注:中斷對應暫存器的設定,後續會更新對應的文件。

 1 void do_irq(void)
 2 {
 3        int irq_num;
 4 
 5        irq_num = CPU0.ICCIAR &0x3ff;
 6        switch(irq_num)
 7        {
 8          case 42:
 9               adc_num = ADCDAT&0xfff;
10               printf("adc = %d\n",adc_num);
11               CLRINTADC = 0;
12        //    IECR2 = IECR2 | (1 << 19);               開啟的話只能讀取一次,
13               //42/32
14               ICDICPR.ICDICPR1 = ICDICPR.ICDICPR1 | (1 << 10);【清GIC中斷標誌位類似於 ICDISER】
15               break;
16        }
17        CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3ff) | irq_num;
18 }
19 void adc_init(void)
20 {    //12bit   使能分頻       分頻值                 手動
21        ADCCON = (1 << 16) | (1 << 14) | (0xff << 6) | (1 << 0);
22        ADCMUX = 3;
23 }
24 void adcint_init(void)
25 {
26        IESR2 = IESR2 | (1 << 19);
27        ICDDCR = 1;    //使能分配器
28        //42/32
29        ICDISER.ICDISER1 = ICDISER.ICDISER1 | (1 << 10);//使能相應中斷到分配器
30        ICDIPTR.ICDIPTR10 = ICDIPTR.ICDIPTR10 &(~(0xff << 16)) | (0x1 << 16);//傳送到相應CPU介面
31        CPU0.ICCPMR = 255;//設定中斷遮蔽優先順序
32        CPU0.ICCICR = 1;  //全域性使能開關
33 }
34 int main (void)
35 { 
36   adc_init();
37        adcint_init();
38        while(1)
39        {
40               ADCCON = ADCCON | 1;
41               delay_ms(1000);
42        }
43    return 0;
44 }

基於Linux驅動編寫

裝置樹

編寫基於Linux的ADC外設驅動,首先需要編寫裝置樹節點資訊,在裸機程式中,我們只用到了暫存器地址,而編寫基於Linux的驅動,我們需要用到中斷功能。所以編寫裝置樹節點需要知道ADC要用到的硬體資源主要包括:暫存器資源和中斷資源。

關於中斷的使用我們在後續文章中會繼續分析,現在我們只需要知道中斷資訊如何填寫即可。

ADC暫存器資訊填寫

由上可知,暫存器基地址為0x126c0000,其他暫存器只需要根據基地址做偏移即可獲取,所以裝置樹的reg屬性資訊如下:

1 reg = <0x126C0000 0x20>;

ADC中斷資訊填寫

描述中斷連線需要四個屬性:

父節點提供以下資訊

interrupt-controller -一個空的屬性定義該節點作為一個接收中斷訊號的裝置。
interrupt-cells -這是一箇中斷控制器節點的屬性。它聲明瞭該中斷控制器的
中斷指示符中【interrupts】 cell 的個數(類似於#address-cells 和#size-cells)。

子節點描述資訊

interrupt-parent - 這是一個裝置節點的屬性,包含一個指向該裝置連線的中斷控制器的 
phandle。那些沒有 interrupt-parent 的節點則從它們的父節點中繼承該屬性。
iterrupts - 一個裝置節點屬性,包含一箇中斷指示符的列表,對應於該裝置上的
每個中斷輸出訊號。【裝置的中斷資訊放在該屬性中】

父節點

首先我們必須知道ADC控制器的中斷線的父節點:

由上圖可知ADC控制器位於soc內,4個ADC通道公用一根中斷線,該中斷線連線在combiner上,所以我們需要查詢到combiner這個父節點的說明:

進入裝置樹檔案所在目錄:arch\arm\boot\dts

grepcombiner*.*-n

經過篩選得到以下資訊:

因為我們使用的板子是exynos4412,而exynos系列通用的平臺裝置樹檔案是exynos4.dtsi,檢視該檔案:

上圖列舉了combiner控制器的詳細資訊:

interrupt-cells;
interrupt-cells=<2>;

所以ADC控制器中斷控制器的interrupts屬性應該有兩個cell。

interrupts屬性填寫

而裝置的中斷資訊填寫方式由核心的以下文件提供:

Documentation\devicetree\bindings\interrupt-controller\interrupts.txt
69.b)twocells
70.------------
71.The#interrupt-cellspropertyissetto2andthefirstcell72.definesthe
73.indexoftheinterruptwithinthecontroller,whilethesecondcellisused
74.tospecifyanyofthefollowingflags:
75.-bits[3:0]triggertypeandlevelflags
76.1=low-to-highedgetriggered
77.2=high-to-lowedgetriggered
78.4=activehighlevel-sensitive
79.8=activelowlevel-sensitive

由以上資訊可知,中斷的第一個cell是該中斷源所在中斷控制器的index,第二個cell表示中斷的觸發方式

  1. 上升沿觸發
  2. 下降沿觸發
  3. 高電平觸發
  4. 低電平觸發

那麼index應該是多少呢?

詳見datasheet的9.2.2 GIC Interrupt Table 節:

此處我們應該是填寫左側的SPI ID:10 還是填寫INTERRUPT ID:42呢?

此處我們可以參考LCD節點的interrupts填寫方法:

通過查詢父節點為combiner的裝置資訊。

繼續grep combiner . -n

由此可見lcd這個裝置的interrupts屬性index值是11,所以可知ADC控制器中斷線的index是10。中斷資訊如下:

interrupt-parent=<&combiner>;
interrupts=<103>;

ADC外設裝置樹資訊

fs4412-adc{
compatible="fs4412,adc";
reg=<0x126C00000x20>;
interrupt-parent=<&combiner>;
interrupts=<103>;
};

本文預設大家會使用裝置樹,不知道如何使用裝置樹的朋友,後續會開一篇單獨講解裝置樹。

【注意】在不支援裝置樹核心中,以Cortex-A8為例,中斷資訊填寫在以下檔案中

內部中斷,Irqs.h(arch\arm\mach-s5pc100\include\mach)
外部中斷在Irqs.h(arch\arm\plat-s5p\include\plat)

ADC屬於內部中斷,位於arch\arm\mach-s5pc100\include\mach\Irqs.h中。

暫存器資訊填寫在以下位置:

arch\arm\mach-s5pc100\Mach-smdkc100.c
staticstructplatform_device*smdkc100_devices[]__initdata={
&s3c_device_adc,
&s3c_device_cfcon,
&s3c_device_i2c0,
&s3c_device_i2c1,
&s3c_device_fb,
&s3c_device_hsmmc0,
&s3c_device_hsmmc1,
&s3c_device_hsmmc2,
&samsung_device_pwm,
&s3c_device_ts,
&s3c_device_wdt,
&smdkc100_lcd_powerdev,
&s5pc100_device_iis0,
&samsung_device_keypad,
&s5pc100_device_ac97,
&s3c_device_rtc,
&s5p_device_fimc0,
&s5p_device_fimc1,
&s5p_device_fimc2,
&s5pc100_device_spdif,
};

結構體s3c_device_adc定義在以下檔案:

\arch\arm\plat-samsung\Devs.c
#ifdefCONFIG_PLAT_S3C24XX
staticstructresources3c_adc_resource[]={
[0]=DEFINE_RES_MEM(S3C24XX_PA_ADC,S3C24XX_SZ_ADC),
[1]=DEFINE_RES_IRQ(IRQ_TC),
[2]=DEFINE_RES_IRQ(IRQ_ADC),
};

structplatform_devices3c_device_adc={
.name="s3c24xx-adc",
.id=-1,
.num_resources=ARRAY_SIZE(s3c_adc_resource),
.resource=s3c_adc_resource,
};
#endif/*CONFIG_PLAT_S3C24XX*/

#ifdefined(CONFIG_SAMSUNG_DEV_ADC)
staticstructresources3c_adc_resource[]={
[0]=DEFINE_RES_MEM(SAMSUNG_PA_ADC,SZ_256),
[1]=DEFINE_RES_IRQ(IRQ_TC),
[2]=DEFINE_RES_IRQ(IRQ_ADC),
};

structplatform_devices3c_device_adc={
.name="samsung-adc",
.id=-1,
.num_resources=ARRAY_SIZE(s3c_adc_resource),
.resource=s3c_adc_resource,
};
#endif/*CONFIG_SAMSUNG_DEV_ADC*/

由程式碼可知,平臺驅動對應的platform_device具體內容由巨集CONFIG_PLAT_S3C24XX、CONFIG_SAMSUNG_DEV_ADC來控制。

驅動編寫架構和流程如下

read()
{
1、向adc裝置傳送要讀取的命令
ADCCON1<<0|1<<14|0X1<<16|0XFF<<6
2、讀取不到資料就休眠
wait_event_interruptible();
3、等待被喚醒讀資料
havedata =0;
}
adc_handler()
{
1、清中斷ADC使用中斷來通知轉換資料完畢的
2、狀態位置位;
havedata=1;
3、喚醒阻塞程序
wake_up()
}
probe()
{
1、讀取中斷號,註冊中斷處理函式
2、讀取暫存器的地址,ioremap
3、字元裝置的操作
}

驅動需要首先捕獲中斷訊號後再去暫存器讀取相應的資料,在ADC控制器沒有準備好資料之前,應用層需要阻塞讀取資料,所以在讀取資料的函式中,需要藉助等待佇列來實現驅動對應用程序的阻塞。驅動程式

驅動程式對暫存器的操作參考裸機程式,只是基地址需要通過ioremap()做對映,對暫存器的讀寫操作需要用readl、writel。

driver.c

#include<linux/module.h>
#include<linux/device.h>
#include<linux/platform_device.h>
#include<linux/interrupt.h>
#include<linux/fs.h>
#include<linux/wait.h>
#include<linux/sched.h>
#include<asm/uaccess.h>
#include<asm/io.h>
staticintmajor=250;


staticwait_queue_head_twq;
staticinthave_data=0;
staticintadc;
staticstructresource*res1;
staticstructresource*res2;
staticvoid*adc_base;

#defineADCCON0x0000
#defineADCDLY0x0008
#defineADCDAT0x000C
#defineCLRINTADC0x0018
#defineADCMUX0x001C


staticirqreturn_tadc_handler(intirqno,void*dev)
{
have_data=1;

printk("11111\n");
/*清中斷*/
writel(0x12,adc_base+CLRINTADC);
wake_up_interruptible(&wq);
returnIRQ_HANDLED;
}
staticintadc_open(structinode*inod,structfile*filep)
{

return0;
}
staticssize_tadc_read(structfile*filep,char__user*buf,size_tlen,loff_t*pos)
{
writel(0x3,adc_base+ADCMUX);
writel(1<<0|1<<14|0X1<<16|0XFF<<6,adc_base+ADCCON);

wait_event_interruptible(wq,have_data==1);

/*readdata*/
adc=readl(adc_base+ADCDAT)&0xfff;

if(copy_to_user(buf,&adc,sizeof(int)))
{
return-EFAULT;
}
have_data=0;
returnlen;
}
staticintadc_release(structinode*inode,structfile*filep)
{
return0;
}
staticstructfile_operationsadc_ops=
{
.open=adc_open,
.release=adc_release,
.read=adc_read,
};


staticinthello_probe(structplatform_device*pdev)
{
intret;
printk("match0k\n");

res1=platform_get_resource(pdev,IORESOURCE_IRQ,0);
res2=platform_get_resource(pdev,IORESOURCE_MEM,0);

ret=request_irq(res1->start,adc_handler,IRQF_DISABLED,"adc1",NULL);
adc_base=ioremap(res2->start,res2->end-res2->start);

register_chrdev(major,"adc",&adc_ops);
init_waitqueue_head(&wq);

return0;
}
staticinthello_remove(structplatform_device*pdev)
{
free_irq(res1->start,NULL);
free_irq(res2->start,NULL);
unregister_chrdev(major,"adc");
return0;
}

staticstructof_device_idadc_id[]=
{
{.compatible="fs4412,adc"},
};

staticstructplatform_driverhello_driver=
{

.probe=hello_probe,
.remove=hello_remove,
.driver={
.name="bigbang",
.of_match_table=adc_id,
},
};

staticinthello_init(void)
{
printk("hello_init");
returnplatform_driver_register(&hello_driver);
}
staticvoidhello_exit(void)
{
platform_driver_unregister(&hello_driver);
printk("hello_exit\n");
return;
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);

測試程式

test.c

#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<stdio.h>

main()
{
intfd,len;
intadc;
fd=open("/dev/hello",O_RDWR);
if(fd<0)
{
perror("openfail\n");
return;
}

while(1)
{
read(fd,&adc,4);
printf("adc%0.2fV\n",(1.8*adc)/4096);
}

close(fd);
}



更多嵌入式資料,請關注公眾號: 一口Linux。