前言
在嵌入式開發中,ADC應用比較頻繁,本文主要講解ADC的基本原理以及如何編寫基于ARM的裸機程序和基于Linux的驅動程序。
ARM架構:Cortex-A9Linux內核:3.14
模擬信號
主要是與離散的數字信號相對的連續的信號。模擬信號分布于自然界的各個角落,如每天溫度的變化,而數字信號是人為的抽象出來的在時間上不連續的信號。電學上的模擬信號是主要是指幅度和相位都連續的電信號,此信號可以被模擬電路進行各種運算,如放大,相加,相乘等。
模擬信號是指用連續變化的物理量表示的信息,其信號的幅度,或頻率,或相位隨時間作連續變化,如目前廣播的聲音信號,或圖像信號等。
如下圖所示從上到下一次是正弦波、 調幅波、 阻尼震蕩波、 指數衰減波 。
數字信號
數字信號指幅度的取值是離散的,幅值表示被限制在有限個數值之內。二進制碼就是一種數字信號。二進制碼受噪聲的影響小,易于有數字電路進行處理,所以得到了廣泛的應用。
數字信號:高清數字電視,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 CombinerTable 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資源。
//注:這里使用讀-啟動模式
/***********************ADC?******************/ #define???ADC_CFG??__REG(0x10010118) #define??ADCCON??__REG(0x126C0000) #define??ADCDLY??__REG(0x126C0008) #define??ADCDAT??__REG(0x126C000C) #define??CLRINTADC?__REG(0x126C0018) #define??ADCMUX??__REG(0x126C001C) #include?"exynos_4412.h" #include?"pwm.h" #include?"uart.h" unsigned?char?table[10]?=?{'0','1','2','3','4','5','6','7','8','9'}; void?mydelay_ms(int?time) { ??int?i,?j; ??while(time--) ??{ ????for?(i?=?0;?i?5;?i++) ????for?(j?=?0;?j?514;?j++); ??} } adc_init(int?temp) { ??ADCCON?=?(1?<16?|?1?<14?|?99?<<6?|?1?<1); ??ADCMUX?=?3; ??temp?=?ADCDAT?&?0xfff; } /* ?*??裸機代碼,不同于LINUX?應用層,?一定加循環控制 ?*/ int?main?(void) { ??unsigned?char?bit4,bit3,bit2,bit1; ??unsigned?int?temp?=?0; ?? ??uart_init(); ??adc_init(temp); ??puts("開始轉換 "); ??while(1) ??{ ????while(!(ADCCON?&?0x8000)); ????temp?=?ADCDAT?&?0xfff; ????printf("U?=?%d ",temp); ????temp?=?1.8?*?1000?*?temp/0xfff; ????bit4?=?temp?/1000; ????putc(table[bit4]); ????bit3?=?(temp?%?1000)/100?; ????putc(table[bit3]); ????bit2?=?((temp?%?1000)%100)/10; ????putc(table[bit2]); ????bit1?=?((temp?%?1000)%100)%10; ????putc(table[bit1]); ????puts("mV"); ????putc(' '); ????mydelay_ms(1000); ??} ??return?0; }
中斷模式
中斷模式讀取數據步驟如下:
1.要讀取數據首先向ADC寄存器ADCCON的bit:0寫1,發送轉換命令;
2.當ADC控制器轉換完畢會通過中斷線向CPU發送中斷信號;
3.在中斷處理函數中,讀走數據,并清中斷.
注:中斷對應寄存器的設置,后續會更新對應的文檔。
void?do_irq(void)
{ ???????int?irq_num; ???????irq_num?=?CPU0.ICCIAR?&0x3ff; ???????switch(irq_num) ???????{ ?????????case?42: ??????????????adc_num?=?ADCDAT&0xfff; ??????????????printf("adc?=?%d ",adc_num); ??????????????CLRINTADC?=?0; ???????//????IECR2?=?IECR2?|?(1?<19);???????????????打開的話只能讀取一次, ??????????????//42/32 ??????????????ICDICPR.ICDICPR1?=?ICDICPR.ICDICPR1?|?(1?<10);【清GIC中斷標志位類似于?ICDISER】 ??????????????break; ???????} ???????CPU0.ICCEOIR?=?CPU0.ICCEOIR?&?(~0x3ff)?|?irq_num; } void?adc_init(void) {????//12bit???使能分頻???????分頻值?????????????????手動 ???????ADCCON?=?(1?<16)?|?(1?<14)?|?(0xff?<6)?|?(1?<0); ???????ADCMUX?=?3; } void?adcint_init(void) { ???????IESR2?=?IESR2?|?(1?<19); ???????ICDDCR?=?1;????//使能分配器 ???????//42/32 ???????ICDISER.ICDISER1?=?ICDISER.ICDISER1?|?(1?<10);//使能相應中斷到分配器 ???????ICDIPTR.ICDIPTR10?=?ICDIPTR.ICDIPTR10?&(~(0xff?<16))?|?(0x1?<16);//發送到相應CPU接口 ???????CPU0.ICCPMR?=?255;//設置中斷屏蔽優先級 ???????CPU0.ICCICR?=?1;??//全局使能開關 } int?main?(void) {? ??adc_init(); ???????adcint_init(); ???????while(1) ???????{ ??????????????ADCCON?=?ADCCON?|?1; ??????????????delay_ms(1000); ???????} ???return?0; }
基于Linux驅動編寫
設備樹
編寫基于Linux的ADC外設驅動,首先需要編寫設備樹節點信息,在裸機程序中,我們只用到了寄存器地址,而編寫基于Linux的驅動,我們需要用到中斷功能。所以編寫設備樹節點需要知道ADC要用到的硬件資源主要包括:寄存器資源和中斷資源。
關于中斷的使用我們在后續文章中會繼續分析,現在我們只需要知道中斷信息如何填寫即可。
ADC寄存器信息填寫
在這里插入圖片描述
由上可知,寄存器基地址為0x126c0000,其他寄存器只需要根據基地址做偏移即可獲取,所以設備樹的reg屬性信息如下:
reg?=?<0x126C0000?0x20>;
ADC中斷信息填寫
描述中斷連接需要四個屬性:父節點提供以下信息
interrupt-controller -?一個空的屬性定義該節點作為一個接收中斷信號的設備。 interrupt-cells ?????-?這是一個中斷控制器節點的屬性。它聲明了該中斷控制器的中斷指示符中【interrupts】 cell 的個數(類似于?#address-cells 和?#size-cells)。
子節點描述信息
interrupt-parent -?這是一個設備節點的屬性,包含一個指向該設備連接的中斷控制器的 phandle。那些沒有 interrupt-parent 的節點則從它們的父節點中繼承該屬性。 interrupts ??????-?一個設備節點屬性,包含一個中斷指示符的列表,對應于該設備上的每個中斷輸出信號。【設備的中斷信息放在該屬性中】
父節點
首先我們必須知道ADC控制器的中斷線的父節點:
由上圖可知ADC控制器位于soc內,4個ADC通道公用一根中斷線,該中斷線連接在combiner上,所以我們需要查找到combiner這個父節點的說明:
進入設備樹文件所在目錄:archarmootdts
grep?combiner?*.*?-n
經過篩選得到以下信息,
因為我們使用的板子是exynos4412,而exynos系列通用的平臺設備樹文件是exynos4.dtsi,查看該文件:
上圖列舉了combiner控制器的詳細信息:
interrupt-cells?;
interrupt-cells?=<2>;
所以ADC控制器中斷控制器的interrupts屬性應該有兩個cell。
interrupts屬性填寫
而設備的中斷信息填寫方式由內核的以下文檔提供:
Documentationdevicetreeindingsinterrupt-controllerinterrupts.txt
69.?b)?two?cells ?70.??------------ ?71.??The?#interrupt-cells?property?is?set?to?2?and?the?first?cell?72.?defines?the ?73.??index?of?the?interrupt?within?the?controller,?while?the?second?cell?is?used ?74.??to?specify?any?of?the?following?flags: ?75.????-?bits[3:0]?trigger?type?and?level?flags ?76.????????1?=?low-to-high?edge?triggered ?77.????????2?=?high-to-low?edge?triggered ?78.????????4?=?active?high?level-sensitive ?79.????????8?=?active?low?level-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?=?<10?3>;
ADC外設設備樹信息
fs4412-adc{ ????compatible?=?"fs4412,adc"; ????reg?=?<0x126C0000?0x20>; ????interrupt-parent?=?<&combiner>; ????interrupts?=?<10?3>; };
本文默認大家會使用設備樹,不知道如何使用設備樹的朋友,后續會開一篇單獨講解設備樹。【注意】在不支持設備樹內核中,以Cortex-A8為例,中斷信息填寫在以下文件中
內部中斷,Irqs.h?(archarmmach-s5pc100includemach) 外部中斷在Irqs.h?(archarmplat-s5pincludeplat)
ADC屬于內部中斷,位于archarmmach-s5pc100includemachIrqs.h中。
寄存器信息填寫在以下位置:
archarmmach-s5pc100Mach-smdkc100.c
static?struct?platform_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定義在以下文件:
archarmplat-samsungDevs.c
#ifdef?CONFIG_PLAT_S3C24XX static?struct?resource?s3c_adc_resource[]?=?{ ??[0]?=?DEFINE_RES_MEM(S3C24XX_PA_ADC,?S3C24XX_SZ_ADC), ??[1]?=?DEFINE_RES_IRQ(IRQ_TC), ??[2]?=?DEFINE_RES_IRQ(IRQ_ADC), }; struct?platform_device?s3c_device_adc?=?{ ??.name????=?"s3c24xx-adc", ??.id????=?-1, ??.num_resources??=?ARRAY_SIZE(s3c_adc_resource), ??.resource??=?s3c_adc_resource, }; #endif?/*?CONFIG_PLAT_S3C24XX?*/ #if?defined(CONFIG_SAMSUNG_DEV_ADC) static?struct?resource?s3c_adc_resource[]?=?{ ??[0]?=?DEFINE_RES_MEM(SAMSUNG_PA_ADC,?SZ_256), ??[1]?=?DEFINE_RES_IRQ(IRQ_TC), ??[2]?=?DEFINE_RES_IRQ(IRQ_ADC), }; struct?platform_device?s3c_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設備發送要讀取的命令 ??????????ADCCON????1<<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? #include? #include? #include? #include? #include? #include? #include? #include? static?int?major?=?250; ? ? static?wait_queue_head_t?wq; static?int?have_data?=?0; static?int?adc; static?struct?resource?*res1; static?struct?resource?*res2; static?void?*adc_base; ? #define?ADCCON?0x0000 #define?ADCDLY?0x0008 #define?ADCDAT?0x000C #define?CLRINTADC?0x0018 #define?ADCMUX?0x001C ? ? static??irqreturn_t?adc_handler(int?irqno,?void?*dev) { ??have_data?=?1; ? ??printk("11111 "); ??/*清中斷*/ ??writel(0x12,adc_base?+?CLRINTADC); ??wake_up_interruptible(&wq); ??return?IRQ_HANDLED; } static?int?adc_open?(struct?inode?*inod,?struct?file?*filep) { ? ??return?0; } static?ssize_t?adc_read(struct?file?*filep,?char?__user?*buf,?size_t?len,?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); ? ??/*read?data*/ ??adc?=?readl(adc_base+ADCDAT)&0xfff; ?? ??if(copy_to_user(buf,&adc,sizeof(int))) ??{ ????return?-EFAULT; ??} ??have_data?=?0; ??return?len; } static??int?adc_release(struct?inode?*inode,?struct?file?*filep) { ??return?0; } static?struct?file_operations??adc_ops?= { ??.open?=?adc_open, ??.release?=?adc_release, ??.read?=?adc_read, }; ? ? static?int?hello_probe(struct?platform_device?*pdev) { ??int?ret; ??printk("match?0k? "); ? ??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); ?? ??return?0; } static?int?hello_remove(struct?platform_device?*pdev) { ??free_irq(res1->start,NULL); ??free_irq(res2->start,NULL);?? ??unregister_chrdev(?major,?"adc"); ??return?0; } ? static?struct?of_device_id?adc_id[]= { ??{.compatible?=?"fs4412,adc"?}, }; ? static?struct?platform_driver?hello_driver= { ?? ??.probe?=?hello_probe, ??.remove?=?hello_remove, ??.driver?={ ????.name?=?"bigbang", ????.of_match_table?=?adc_id, ??}, }; ? static?int?hello_init(void) { ??printk("hello_init"); ??return?platform_driver_register(&hello_driver); } static?void?hello_exit(void) { ??platform_driver_unregister(&hello_driver); ??printk("hello_exit? "); ??return; } MODULE_LICENSE("GPL"); module_init(hello_init); module_exit(hello_exit);
測試程序
test.c
#include? #include? #include? #include? ? main() { ??int?fd,len; ??int?adc; ??fd?=?open("/dev/hello",O_RDWR); ??if(fd<0) ??{ ????perror("open?fail? "); ????return?; ??} ? ??while(1) ??{ ????read(fd,&adc,4); ????printf("adc%0.2f?V? ",(1.8*adc)/4096); ??} ? ??close(fd); }
編輯:黃飛
?
評論
查看更多