一文搞懂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介面包括如下特性。
- 10bit/12bit輸出位可選。
-
微分誤差 1.0LSB。
-
積分誤差 2.0LSB。
-
最大轉換速率5Msps.
-
功耗少,電壓輸入1.8V。
-
電壓輸入範圍 0~1.8V。
-
支援偏上樣本保持功能。
-
通用轉換模式。
模組圖
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表示中斷的觸發方式
- 上升沿觸發
- 下降沿觸發
- 高電平觸發
- 低電平觸發
那麼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。