萬(wàn)字長(zhǎng)文 | 你確定還不懂Linux的ADC驅(qū)動(dòng)?
前言
在嵌入式開發(fā)中,ADC應(yīng)用比較頻繁,本文主要講解ADC的基本原理以及如何編寫基于ARM的裸機(jī)程序和基于Linux的驅(qū)動(dòng)程序。
ARM架構(gòu):Cortex-A9 Linux內(nèi)核:3.14
在講述ADC之前,我們需要先了解什么是模擬信號(hào)和數(shù)字信號(hào)。
模擬信號(hào)
主要是與離散的數(shù)字信號(hào)相對(duì)的連續(xù)的信號(hào)。模擬信號(hào)分布于自然界的各個(gè)角落,如每天溫度的變化,而數(shù)字信號(hào)是人為的抽象出來(lái)的在時(shí)間上不連續(xù)的信號(hào)。電學(xué)上的模擬信號(hào)是主要是指幅度和相位都連續(xù)的電信號(hào),此信號(hào)可以被模擬電路進(jìn)行各種運(yùn)算,如放大,相加,相乘等。
模擬信號(hào)是指用連續(xù)變化的物理量表示的信息,其信號(hào)的幅度,或頻率,或相位隨時(shí)間作連續(xù)變化,如目前廣播的聲音信號(hào),或圖像信號(hào)等。
如下圖所示從上到下一次是正弦波、 調(diào)幅波、 阻尼震蕩波、 指數(shù)衰減波 。
數(shù)字信號(hào)
數(shù)字信號(hào)指幅度的取值是離散的,幅值表示被限制在有限個(gè)數(shù)值之內(nèi)。二進(jìn)制碼就是一種數(shù)字信號(hào)。二進(jìn)制碼受噪聲的影響小,易于有數(shù)字電路進(jìn)行處理,所以得到了廣泛的應(yīng)用。
數(shù)字信號(hào):高清數(shù)字電視,MP3,JPG,PNG文件等等。
優(yōu)點(diǎn):
1. 抗干擾能力強(qiáng)、無(wú)噪聲積累
在模擬通信中,為了提高信噪比,需要在信號(hào)傳輸過程中及時(shí)對(duì)衰減的傳輸信號(hào)進(jìn)行放大,信號(hào)在傳輸過程中不可避免地疊加上的噪聲也被同時(shí)放大。
隨著傳輸距離的增加,噪聲累積越來(lái)越多,以致使傳輸質(zhì)量嚴(yán)重惡化。
對(duì)于數(shù)字通信,由于數(shù)字信號(hào)的幅值為有限個(gè)離散值(通常取兩個(gè)幅值),在傳輸過程中雖然也受到噪聲的干擾,但當(dāng)信噪比惡化到一定程度時(shí),
即在適當(dāng)?shù)木嚯x采用判決再生的方法,再生成沒有噪聲干擾的和原發(fā)送端一樣的數(shù)字信號(hào),所以可實(shí)現(xiàn)長(zhǎng)距離高質(zhì)量的傳輸。
2. 便于加密處理
信息傳輸?shù)陌踩院捅C苄栽絹?lái)越重要,數(shù)字通信的加密處理的比模擬通信容易得多,以話音信號(hào)為例,經(jīng)過數(shù)字變換后的信號(hào)可用簡(jiǎn)單的數(shù)字邏輯運(yùn)算進(jìn)行加密、解密處理。
3. 便于存儲(chǔ)、處理和交換
數(shù)字通信的信號(hào)形式和計(jì)算機(jī)所用信號(hào)一致,都是二進(jìn)制代碼,因此便于與計(jì)算機(jī)聯(lián)網(wǎng),也便于用計(jì)算機(jī)對(duì)數(shù)字信號(hào)進(jìn)行存儲(chǔ)、處理和交換,
可使通信網(wǎng)的管理、維護(hù)實(shí)現(xiàn)自動(dòng)化、智能化。
4. 設(shè)備便于集成化、微型
數(shù)字通信采用時(shí)分多路復(fù)用,不需要體積較大的濾波器。設(shè)備中大部分電路是數(shù)字電路,可用大規(guī)模和超大規(guī)模集成電路實(shí)現(xiàn),因此體積小、功耗低。
5. 便于構(gòu)成綜合數(shù)字網(wǎng)和綜合業(yè)務(wù)數(shù)字網(wǎng)
采用數(shù)字傳輸方式,可以通過程控?cái)?shù)字交換設(shè)備進(jìn)行數(shù)字交換,以實(shí)現(xiàn)傳輸和交換的綜合。
另外,電話業(yè)務(wù)和各種非話業(yè)務(wù)都可以實(shí)現(xiàn)數(shù)字化,構(gòu)成綜合業(yè)務(wù)數(shù)字網(wǎng)。
6. 占用信道頻帶較寬
一路模擬電話的頻帶為4kHz帶寬,一路數(shù)字電話約占64kHz,這是模擬通信目前仍有生命力的主要原因。隨著寬頻帶信道(光纜、數(shù)字微波)的大量利用(一對(duì)光纜可開通幾千路電話)以及數(shù)字信號(hào)處理技術(shù)的發(fā)展(可將一路數(shù)字電話的數(shù)碼率由64kb/s壓縮到32kb/s甚至更低的數(shù)碼率),數(shù)字電話的帶寬問題已不是主要問題了。
常用的數(shù)字信號(hào)編碼有不歸零(NRZ)編碼、 曼徹斯特(Manchester)編碼和差分曼徹斯特(Differential Manchester)編碼。
數(shù)字信號(hào)與模擬信號(hào)的轉(zhuǎn)化
模擬信號(hào)和數(shù)字信號(hào)之間可以相互轉(zhuǎn)換:模擬信號(hào)一般通過PCM脈碼調(diào)制(Pulse Code Modulation)方法量化為數(shù)字信號(hào),
即讓模擬信號(hào)的不同幅度分別對(duì)應(yīng)不同的二進(jìn)制值,例如采用8位編碼可將模擬信號(hào)量化為2^8=256個(gè)量級(jí),實(shí)用中常采取24位或30位編碼;
數(shù)字信號(hào)一般通過對(duì)載波進(jìn)行移相(Phase Shift)的方法轉(zhuǎn)換為模擬信號(hào)。計(jì)算機(jī)、計(jì)算機(jī)局域網(wǎng)與城域網(wǎng)中均使用二進(jìn)制數(shù)字信號(hào),
目前在計(jì)算機(jī)廣域網(wǎng)中實(shí)際傳送的則既有二進(jìn)制數(shù)字信號(hào),也有由數(shù)字信號(hào)轉(zhuǎn)換而得的模擬信號(hào)。但是更具應(yīng)用發(fā)展前景的是數(shù)字信號(hào)。
PCM脈沖編碼調(diào)制
脈沖編碼調(diào)制就是把一個(gè)時(shí)間連續(xù),取值連續(xù)的模擬信號(hào)變換成時(shí)間離散,取值離散的數(shù)字信號(hào)后在信道中傳輸。
脈沖編碼調(diào)制就是對(duì)模擬信號(hào)先抽樣,再對(duì)樣值幅度量化, 編碼的過程。
抽樣:
就是對(duì)模擬信號(hào)進(jìn)行周期性掃描,把時(shí)間上連續(xù)的信號(hào)變成時(shí)間上離散的信號(hào)。
該模擬信號(hào)經(jīng)過抽樣后還應(yīng)當(dāng)包含原信號(hào)中所有信息,也就是說(shuō)能無(wú)失真的恢復(fù)原模擬信號(hào)。
量化:
就是把經(jīng)過抽樣得到的瞬時(shí)值將其幅度離散,即用一組規(guī)定的電平,把瞬時(shí)抽樣值用最接近的電平值來(lái)表示,通常是用二進(jìn)制表示。
編碼:
就是用一組二進(jìn)制碼組來(lái)表示每一個(gè)有固定電平的量化值。然而,實(shí)際上量化是在編碼過程中同時(shí)完成的,故編碼過程也稱為模/數(shù)變換,可記作A/D。
ADC
ADC,Analog-to-Digital Converter的縮寫,指模/數(shù)轉(zhuǎn)換器或者模數(shù)轉(zhuǎn)換器。是指將連續(xù)變化的模擬信號(hào)轉(zhuǎn)換為離散的數(shù)字信號(hào)的器件。真實(shí)世界的模擬信號(hào),例如溫度、壓力、聲音或者圖像等,需要轉(zhuǎn)換成更容易儲(chǔ)存、處理和發(fā)射的數(shù)字形式。模/數(shù)轉(zhuǎn)換器可以實(shí)現(xiàn)這個(gè)功能,在各種不同的產(chǎn)品中都可以找到它的身影。
ADC最早用于對(duì)無(wú)線信號(hào)向數(shù)字信號(hào)轉(zhuǎn)換。如電視信號(hào),長(zhǎng)短播電臺(tái)發(fā)接收等。
與之相對(duì)應(yīng)的DAC,Digital-to-Analog Converter,它是ADC模數(shù)轉(zhuǎn)換的逆向過程。
現(xiàn)在市場(chǎng)上的電子產(chǎn)品都集成了傳感器,傳感器要采集數(shù)據(jù),他的內(nèi)部結(jié)構(gòu)里就一定要用到ADC,常見的傳感器如下:
溫濕度:溫度傳感器,DHT11
聲音:音頻芯片進(jìn)行錄音,WM8906
圖像:索尼IMX386/IMX283傳感器
Exynos4412 A/D轉(zhuǎn)換器
三星的Exynos4412模塊結(jié)構(gòu)圖如下所示:
Adc控制器集成在exynos4412 soc中,控制器內(nèi)部有一根中斷線連接到中斷控制器combiner,然后路由到GIC(Generic Interrupt Controller),滑動(dòng)變阻器連接到adc控制器的通道3。
ADC控制器
參考《Exynos 4412 SCP》 的datasheet。
ADC控制器是10位或12位CMOS再循環(huán)式模擬數(shù)字轉(zhuǎn)換器,它具有10個(gè)通道輸入,并可將模擬量轉(zhuǎn)換至10位或12位二進(jìn)制數(shù)。5Mhz A/D 轉(zhuǎn)換時(shí)鐘,最大1Msps的轉(zhuǎn)換速度。A/D轉(zhuǎn)換具備片上采樣保持功能,同時(shí)也支持待機(jī)工作模式。
ADC接口包括如下特性。
-
10bit/12bit輸出位可選。 -
微分誤差 1.0LSB。
-
積分誤差 2.0LSB。
-
最大轉(zhuǎn)換速率5Msps.
-
功耗少,電壓輸入1.8V。
-
電壓輸入范圍 0~1.8V。
-
支持偏上樣本保持功能。
-
通用轉(zhuǎn)換模式。
模塊圖
4412 A/D轉(zhuǎn)換器的控制器接口框圖如下:
原理我們并不需要關(guān)注,知道即可。
通道選擇
由上圖可知,A/D控制器一共有4個(gè)通道,通用寄存器地址為0x126c0000。
A/D控制器寄存器
對(duì)ADC控制器的操作主要是通過配置寄存器來(lái)實(shí)現(xiàn)的,查看datasheet,必須掌握寄存器的使用。以下是A/D控制器寄存器匯總。
1、A/D控制寄存器ADCCON
RES : 選擇A/D轉(zhuǎn)換精度,0:劃分成1024份 1:劃分成4096份
ECFLG :轉(zhuǎn)換是否結(jié)束 0:轉(zhuǎn)換中 1:轉(zhuǎn)換完畢;對(duì)于輪詢模式需要根據(jù)該位判斷數(shù)據(jù)是否轉(zhuǎn)換完畢。
PRSCEN:A/D轉(zhuǎn)換預(yù)分頻是否使能
PRSCVL:預(yù)分頻的值,轉(zhuǎn)換公式見下面
STANDBY:待機(jī)模式 0:正常工作模式 1:待機(jī)模式。處于待機(jī)模式時(shí)要將PRSCEN設(shè)置為0
READ_START: A/D轉(zhuǎn)換由讀操作觸發(fā),設(shè)置為1后,每次讀取A/D值的操作都會(huì)觸發(fā)一次A/D轉(zhuǎn)換。
ENABLE_START: 單次開啟A/D轉(zhuǎn)換,轉(zhuǎn)換完畢后該位自動(dòng)清零,當(dāng)READ_START設(shè)置為1的時(shí)候,該位無(wú)效。
通常設(shè)置值為(1 << 16 | 1 << 14 | 99 <<6 | 1 << 1)。
2、A/D轉(zhuǎn)換數(shù)據(jù)寄存器ADCDAT0
注意該寄存器的值只有低12位有效。
3、A/D清中斷寄存器CLRINTADC
黃色部分可知,中斷例程負(fù)責(zé)清中斷,中斷結(jié)束后寫入任意值就可以清中斷。
4、A/D通道選擇寄存器ADCMUX
每次操作都要先設(shè)置通道,因?yàn)?4個(gè)通道是共用同一套寄存器,如果有其他任務(wù)也在使用A/D,就會(huì)產(chǎn)生混亂。在此我們選擇通道3
,置3即可。
5、ADC中斷ID
參見9.2.2GIC Interrupt Table
由此可知,ADC中斷號(hào)對(duì)應(yīng)的SPI值是10,inturrupt ID 為42。對(duì)于終端查詢方式和編寫終端的驅(qū)動(dòng)需要知道SPI id和inturrupt ID,后面講解基于Linux驅(qū)動(dòng)還會(huì)再分析設(shè)備樹節(jié)點(diǎn)如何填寫。
6、Combiner中斷控制器
combiner的配置寄存器:IMSRn、IECRn、ISERn、ISTRn,類似于GPIO 對(duì)中斷源分組。只有中斷模式才需要考慮combiner中斷控制器的操作。
7、Combiner分組
參考章節(jié):10.2.1Interrupt Combiner Table 10-1Interrupt Groups of Interrupt Combiner
可見ADC在INTG10,即第10組。
8、Combiner IESR2
參考章節(jié):10.4.2.9IESR2
如果要用中斷模式設(shè)置為1即可。
9、Combiner IECR2
參考章節(jié):10.4.2.10IECR2
此處用于關(guān)閉中斷,采用默認(rèn)值即可,注意,如果設(shè)置了1,那么中斷功能就關(guān)閉了。
10、A/D轉(zhuǎn)換的轉(zhuǎn)換時(shí)間計(jì)算
例如:PCLK為100MHz,PRESCALER = 65 ;所有10位轉(zhuǎn)換時(shí)間為
100MHz/(99+1) = 1MHz
轉(zhuǎn)化時(shí)間為1/(1MHz/5 cycles) = 5us。
完成一次A/D轉(zhuǎn)換需要5個(gè)時(shí)鐘周期。A/D轉(zhuǎn)換器的最大工作時(shí)鐘為5MHz,所以最大采樣率可以達(dá)到1Mit/s.
電路連接圖
由該電路圖可知,外設(shè)是一個(gè)滑動(dòng)變阻器,根據(jù)接觸點(diǎn)的不同,會(huì)導(dǎo)致輸入電壓的模擬值不同。連接的A/D控制器通道為3。該電路利用一個(gè)電位計(jì)輸出電壓到4412的AIN3管腳。輸入的電壓范圍為0~1.8V。
ADC裸機(jī)開發(fā)程序?qū)嵗?/h1>
ADC數(shù)據(jù)的讀取通常由2種方法:中斷模式、輪訓(xùn)模式。
輪訓(xùn)模式
輪詢模式讀取數(shù)據(jù)步驟如下:
-
1.要讀取數(shù)據(jù)首先向ADC寄存器ADCCON的bit:1寫1,發(fā)送轉(zhuǎn)換命令,采用讀-啟動(dòng)模式來(lái)開啟轉(zhuǎn)換。 -
2.當(dāng)ADC控制器轉(zhuǎn)換完畢會(huì)將ADCCON的bit:15設(shè)置為1, -
3.輪詢檢測(cè)ADCCON的bit:15是否設(shè)置為1,如果設(shè)置為1,就讀走數(shù)據(jù),否則繼續(xù)等待。
這種方式比較占用CPU資源。
//注:這里使用讀-啟動(dòng)模式
/***********************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;
}
/*
* 裸機(jī)代碼,不同于LINUX 應(yīng)用層, 一定加循環(huán)控制
*/
int main (void)
{
unsigned char bit4,bit3,bit2,bit1;
unsigned int temp = 0;
uart_init();
adc_init(temp);
puts("開始轉(zhuǎn)換\n");
while(1)
{
while(!(ADCCON & 0x8000));
temp = ADCDAT & 0xfff;
printf("U = %d\n",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('\n');
mydelay_ms(1000);
}
return 0;
}
中斷模式
中斷模式讀取數(shù)據(jù)步驟如下:
-
1.要讀取數(shù)據(jù)首先向ADC寄存器ADCCON的bit:0寫1,發(fā)送轉(zhuǎn)換命令; -
2.當(dāng)ADC控制器轉(zhuǎn)換完畢會(huì)通過中斷線向CPU發(fā)送中斷信號(hào); -
3.在中斷處理函數(shù)中,讀走數(shù)據(jù),并清中斷.
注:中斷對(duì)應(yīng)寄存器的設(shè)置,后續(xù)會(huì)更新對(duì)應(yīng)的文檔。
void do_irq(void)
{
int irq_num;
irq_num = CPU0.ICCIAR &0x3ff;
switch(irq_num)
{
case 42:
adc_num = ADCDAT&0xfff;
printf("adc = %d\n",adc_num);
CLRINTADC = 0;
// IECR2 = IECR2 | (1 << 19); 打開的話只能讀取一次,
//42/32
ICDICPR.ICDICPR1 = ICDICPR.ICDICPR1 | (1 << 10);【清GIC中斷標(biāo)志位類似于 ICDISER】
break;
}
CPU0.ICCEOIR = CPU0.ICCEOIR & (~0x3ff) | irq_num;
}
void adc_init(void)
{ //12bit 使能分頻 分頻值 手動(dòng)
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);//使能相應(yīng)中斷到分配器
ICDIPTR.ICDIPTR10 = ICDIPTR.ICDIPTR10 &(~(0xff << 16)) | (0x1 << 16);//發(fā)送到相應(yīng)CPU接口
CPU0.ICCPMR = 255;//設(shè)置中斷屏蔽優(yōu)先級(jí)
CPU0.ICCICR = 1; //全局使能開關(guān)
}
int main (void)
{
adc_init();
adcint_init();
while(1)
{
ADCCON = ADCCON | 1;
delay_ms(1000);
}
return 0;
}
基于Linux驅(qū)動(dòng)編寫
設(shè)備樹
編寫基于Linux的ADC外設(shè)驅(qū)動(dòng),首先需要編寫設(shè)備樹節(jié)點(diǎn)信息,在裸機(jī)程序中,我們只用到了寄存器地址,而編寫基于Linux的驅(qū)動(dòng),我們需要用到中斷功能。所以編寫設(shè)備樹節(jié)點(diǎn)需要知道ADC要用到的硬件資源主要包括:寄存器資源和中斷資源。
關(guān)于中斷的使用我們?cè)诤罄m(xù)文章中會(huì)繼續(xù)分析,現(xiàn)在我們只需要知道中斷信息如何填寫即可。
ADC寄存器信息填寫
由上可知,寄存器基地址為0x126c0000,其他寄存器只需要根據(jù)基地址做偏移即可獲取,所以設(shè)備樹的reg屬性信息如下:
reg = <0x126C0000 0x20>;
ADC中斷信息填寫
描述中斷連接需要四個(gè)屬性:
父節(jié)點(diǎn)提供以下信息
interrupt-controller - 一個(gè)空的屬性定義該節(jié)點(diǎn)作為一個(gè)接收中斷信號(hào)的設(shè)備。
interrupt-cells - 這是一個(gè)中斷控制器節(jié)點(diǎn)的屬性。它聲明了該中斷控制器的
中斷指示符中【interrupts】 cell 的個(gè)數(shù)(類似于 #address-cells 和 #size-cells)。
子節(jié)點(diǎn)描述信息
interrupt-parent - 這是一個(gè)設(shè)備節(jié)點(diǎn)的屬性,包含一個(gè)指向該設(shè)備連接的中斷控制器的
phandle。那些沒有 interrupt-parent 的節(jié)點(diǎn)則從它們的父節(jié)點(diǎn)中繼承該屬性。
iterrupts - 一個(gè)設(shè)備節(jié)點(diǎn)屬性,包含一個(gè)中斷指示符的列表,對(duì)應(yīng)于該設(shè)備上的
每個(gè)中斷輸出信號(hào)?!驹O(shè)備的中斷信息放在該屬性中】
父節(jié)點(diǎn)
首先我們必須知道ADC控制器的中斷線的父節(jié)點(diǎn):
由上圖可知ADC控制器位于soc內(nèi),4個(gè)ADC通道公用一根中斷線,該中斷線連接在combiner上,所以我們需要查找到combiner這個(gè)父節(jié)點(diǎn)的說(shuō)明:
進(jìn)入設(shè)備樹文件所在目錄:arch\arm\boot\dts
grep combiner *.* -n
經(jīng)過篩選得到以下信息:
因?yàn)槲覀兪褂玫陌遄邮莈xynos4412,而exynos系列通用的平臺(tái)設(shè)備樹文件是exynos4.dtsi,查看該文件:
上圖列舉了combiner控制器的詳細(xì)信息:
interrupt-cells ;
interrupt-cells =<2>;
所以ADC控制器中斷控制器的interrupts屬性應(yīng)該有兩個(gè)cell。
interrupts屬性填寫
而設(shè)備的中斷信息填寫方式由內(nèi)核的以下文檔提供:
Documentation\devicetree\bindings\interrupt-controller\interrupts.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
由以上信息可知,中斷的第一個(gè)cell是該中斷源所在中斷控制器的index,第二個(gè)cell表示中斷的觸發(fā)方式
-
上升沿觸發(fā) -
下降沿觸發(fā) -
高電平觸發(fā) -
低電平觸發(fā)
那么index應(yīng)該是多少呢?
詳見datasheet的9.2.2 GIC Interrupt Table 節(jié):
此處我們應(yīng)該是填寫左側(cè)的SPI ID:10 還是填寫INTERRUPT ID:42呢?
此處我們可以參考LCD節(jié)點(diǎn)的interrupts填寫方法:
通過查找父節(jié)點(diǎn)為combiner的設(shè)備信息。
繼續(xù)grep combiner . -n
由此可見lcd這個(gè)設(shè)備的interrupts屬性index值是11,所以可知ADC控制器中斷線的index是10。中斷信息如下:
interrupt-parent = <&combiner>;
interrupts = <10 3>;
ADC外設(shè)設(shè)備樹信息
fs4412-adc{
compatible = "fs4412,adc";
reg = <0x126C0000 0x20>;
interrupt-parent = <&combiner>;
interrupts = <10 3>;
};
本文默認(rèn)大家會(huì)使用設(shè)備樹,不知道如何使用設(shè)備樹的朋友,后續(xù)會(huì)開一篇單獨(dú)講解設(shè)備樹。
【注意】在不支持設(shè)備樹內(nèi)核中,以Cortex-A8為例,中斷信息填寫在以下文件中
內(nèi)部中斷,Irqs.h (arch\arm\mach-s5pc100\include\mach)
外部中斷在Irqs.h (arch\arm\plat-s5p\include\plat)
ADC屬于內(nèi)部中斷,位于arch\arm\mach-s5pc100\include\mach\Irqs.h中。
寄存器信息填寫在以下位置:
arch\arm\mach-s5pc100\Mach-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,
};
結(jié)構(gòu)體s3c_device_adc定義在以下文件:
\arch\arm\plat-samsung\Devs.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 */
由代碼可知,平臺(tái)驅(qū)動(dòng)對(duì)應(yīng)的platform_device具體內(nèi)容由宏CONFIG_PLAT_S3C24XX、CONFIG_SAMSUNG_DEV_ADC來(lái)控制。
驅(qū)動(dòng)編寫架構(gòu)和流程如下
read()
{
1、向adc設(shè)備發(fā)送要讀取的命令
ADCCON 1<<0 | 1<<14 | 0X1<<16 | 0XFF<<6
2、讀取不到數(shù)據(jù)就休眠
wait_event_interruptible();
3、等待被喚醒讀數(shù)據(jù)
havedata = 0;
}
adc_handler()
{
1、清中斷 ADC使用中斷來(lái)通知轉(zhuǎn)換數(shù)據(jù)完畢的
2、狀態(tài)位置位;
havedata=1;
3、喚醒阻塞進(jìn)程
wake_up()
}
probe()
{
1、讀取中斷號(hào),注冊(cè)中斷處理函數(shù)
2、讀取寄存器的地址,ioremap
3、字符設(shè)備的操作
}
驅(qū)動(dòng)需要首先捕獲中斷信號(hào)后再去寄存器讀取相應(yīng)的數(shù)據(jù),在ADC控制器沒有準(zhǔn)備好數(shù)據(jù)之前,應(yīng)用層需要阻塞讀取數(shù)據(jù),所以在讀取數(shù)據(jù)的函數(shù)中,需要借助等待隊(duì)列來(lái)實(shí)現(xiàn)驅(qū)動(dòng)對(duì)應(yīng)用進(jìn)程的阻塞。驅(qū)動(dòng)程序
驅(qū)動(dòng)程序?qū)拇嫫鞯牟僮鲄⒖悸銠C(jī)程序,只是基地址需要通過ioremap()做映射,對(duì)寄存器的讀寫操作需要用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>
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\n");
/*清中斷*/
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 \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);
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 \n");
return;
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
測(cè)試程序
test.c
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
main()
{
int fd,len;
int adc;
fd = open("/dev/hello",O_RDWR);
if(fd<0)
{
perror("open fail \n");
return ;
}
while(1)
{
read(fd,&adc,4);
printf("adc%0.2f V \n",(1.8*adc)/4096);
}
close(fd);
}
推薦閱讀
【2】手把手教Linux驅(qū)動(dòng)2-模塊參數(shù)param和符號(hào)導(dǎo)出export用法
【3】手把手教Linux驅(qū)動(dòng)3-之字符設(shè)備架構(gòu)詳解,有這篇就夠了
【4】手把手教Linux驅(qū)動(dòng)4-進(jìn)程、文件描述符、file、inode關(guān)系詳解
【5】玩轉(zhuǎn)內(nèi)核鏈表list_head,如何管理不同類型節(jié)點(diǎn)的實(shí)現(xiàn)(萬(wàn)字文)可以收藏
【6】I2C干貨-基于Cortex-A9(重新整理)
【8】一文講懂什么是vlan、三層交換機(jī)、網(wǎng)關(guān)、DNS、子網(wǎng)掩碼、MAC地址
【9】詳解實(shí)時(shí)查看網(wǎng)卡流量的幾款工具
【10】如何高效閱讀代碼?Linux大神拍了拍你并教給你這三個(gè)步驟
5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹莓派,等等。在公眾號(hào)內(nèi)回復(fù)「1024」,即可免費(fèi)獲取!!
免責(zé)聲明:本文內(nèi)容由21ic獲得授權(quán)后發(fā)布,版權(quán)歸原作者所有,本平臺(tái)僅提供信息存儲(chǔ)服務(wù)。文章僅代表作者個(gè)人觀點(diǎn),不代表本平臺(tái)立場(chǎng),如有問題,請(qǐng)聯(lián)系我們,謝謝!