Linux下如何使用I2C匯流排和ADC
I2C匯流排
一主多從的通訊協議
通訊都是由主裝置發起的
SCL:時鐘線,由主端控制
SDA:資料線,主端和從端都可以配置
SCL和SDA的預設電平狀態是高(上拉電阻)
i2c通訊協議:
START訊號:起始訊號,SCL保持高,SDA從高到低跳變
STOP訊號:結束訊號,SCL保持高,SDA從低到高跳變
從裝置地址:用來標識從裝置的唯一性,從裝置地址分為固定和可變的兩部分
at24c02
adp8860
ACK訊號:應答訊號,表示是否處於資料互動的狀態
I2C的通訊過程:
1.主裝置傳送起始訊號
2.主裝置傳送從裝置地址(確定要訪問的從裝置)
3.主裝置傳送讀寫訊號
4.從裝置在位,就必須傳送一個ACK訊號給主裝置
5.根據從裝置的說明手冊進行通訊
6.如果資料互動完畢,主裝置傳送STOP訊號
S5PV210內部集成了I2C控制器,I2C總線上資料互動時,所需的時序要求,都通過控制起來完成操作
控制器發起的時序,有些是固定的,有些是可變的,
固定:START/STOP/ACK/ R/W
可變:裝置地址,讀寫裝置的地址,資料
我們只需要把這些可變的資訊高速I2C控制器,開始控制器即可完成相應的操作
基於以上內容,核心實現了I2C的驅動
I2C匯流排驅動
管理的硬體是I2C控制器,關心如何進行資料傳輸,不關心資料的具體含 義,匯流排驅動一般由晶片廠家提供,核心中已經存在
make menuconfig
Device Drivers --->
<*> I2C support --->
I2C Hardware Bus support --->
<*> S3C2410 I2C Driver
i2c裝置驅動
管理的硬體是I2C從裝置,關心的是從裝置地址,訪問從裝置的方法,關 心資料的具體含義,不關心資料的傳輸,I2C裝置驅動只需要將要傳輸的資料交給I2C匯流排驅動即可
linux核心i2c驅動框架
app:open read write
------------------------------------
i2c裝置驅動
關心資料的具體含義,不關心資料的傳輸
eeprom_read................
-------------------------------------
核心提供了統一的方法實現裝置驅動和匯流排驅動的資料互動
i2c_transfer();//老式介面
SMBUS介面(提供了一組函式);//新式介面
新式介面相容老式介面
----------------------------------
I2C匯流排驅動
-----------------------
I2C控制器《=====》I2C從裝置
I2C裝置驅動如何實現?
使用裝置---匯流排---驅動模型
i2c_bus_type匯流排
dev連結串列:i2c_client
drv連結串列:i2c_driver
i2c_client和i2c_driver如何使用
i2c_driver如何使用?
1.分配i2c_driver
2.初始化i2c_driver
struct i2c_driver eeprom_drv =
{
.id_table = 名字,一定要初始化
.probe = 匹配成功呼叫的函式
.remove
};
3.呼叫i2c_add_driver進行註冊
i2c_client如何使用?
i2c_client的分配初始化註冊都是由核心幫你實現
我們只需要對i2c_board_info進行分配初始化即可
核心會根據i2c_board_info去填充i2c_client
at24c02
101000 = 0x50
修改開發板檔案
vi arch/arm/mach-s5pv210/mach-gec210.c
//定義i2c裝置名和裝置地址
1217 static struct i2c_board_info at24cxx[] = {
1218 {
1219 I2C_BOARD_INFO("at24c02",0x50);//第一個引數是名字,必須要和 上面的struct i2c_driver裡面的id_table匹配
1220 },
1221 };
//CPU有4條I2C匯流排,eeprom是掛載i2c0上的
1546 i2c_register_board_info(0, at24cxx, ARRAY_SIZE(eeprom));//括號裡應該是 at24cxx
make zImage
編寫i2c_driver的驅動
地址:
1010000 = 0x50
LCD相關
linux核心如何管理視訊記憶體
1.操作LCD就是操作對應的視訊記憶體
framebuffer驅動框架:
app:open,read,write,ioctl,mmap............
---------------------------------------------
framebuffer核心層:drivers/video/fbmem.c
1.對上提供統一的訪問介面
2.對下提供統一的註冊硬體驅動的方法和對應的資料結構
----------------------------------------------
裝置驅動(視訊記憶體和LCD控制器)
訪問介面和註冊硬體的方法,資料結構是怎樣的?
fbmem.c
fbmem_init:
register_chrdev(FB_MAJOR,"fb",&fb_fops)
fb_class = class_create(THIS_MODULE, "graphics");
訪問介面的實現
open->sys_open->fb_open:
//以次裝置號為索引,在陣列registered_fb中取出一項fb_info
int fbidx = iminor(inode);
struct fb_info *info;
info = registered_fb[fbidx];
//呼叫
info->fbops->fb_open
read->sys_read->fb_read:
//以次裝置號為索引,在陣列registered_fb中取出一項fb_info
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
//檢視fb_info中是否有fb_read的方法,有則呼叫
if (info->fbops->fb_read)
return info->fbops->fb_read(info, buf, count, ppos);
//沒有的話自己實現
total_size = info->screen_size;
total_size = info->fix.smem_len;
//分配核心緩衝區
count = total_size;
buffer = kmalloc((count > PAGE_SIZE) ? PAGE_SIZE : count,
GFP_KERNEL);
// 取得視訊記憶體的核心虛擬起始地址
src = (u32 __iomem *) (info->screen_base + p);
while (count)
{
dst = buffer;
//把視訊記憶體資料讀取到核心緩衝區
*dst++ = fb_readl(src++);
//把核心緩衝區的資料拷貝到使用者空間
copy_to_user(buf, buffer, c)
}
fb_read的實現和fb_info關係很大
mmap->sys-mmap->fb_mmap:
struct fb_info *info = registered_fb[fbidx];
//從fb_info中獲得視訊記憶體的物理起始地址
start = info->fix.smem_start;
off += start;
//核心幫你實現從視訊記憶體的實體地址對映到使用者的虛擬地址空間,使用者訪問mmap對映的虛擬地址就是訪問視訊記憶體
if (io_remap_pfn_range(vma, vma->vm_start, off >> PAGE_SHIFT,
vma->vm_end - vma->vm_start, vma->vm_page_prot))
ioctl->sys_ioctl->fb_ioctl:
int fbidx = iminor(inode);
struct fb_info *info = registered_fb[fbidx];
//螢幕的可變引數資訊
struct fb_var_screeninfo var;
//螢幕的固定引數資訊
struct fb_fix_screeninfo fix;
switch (cmd) {
//獲取螢幕的可變引數資訊
case FBIOGET_VSCREENINFO:
var = info->var;
ret = copy_to_user(argp, &var, sizeof(var)) ? -EFAULT : 0;
break;
//獲取螢幕的固定引數資訊
case FBIOGET_FSCREENINFO:
fix = info->fix;
ret = copy_to_user(argp, &fix, sizeof(fix)) ? -EFAULT : 0;
break;
....
//所有的操作介面嚴重依賴於fb_info,fb_info來自於registered_fb陣列
registered_fb陣列在哪裡被填充
register_framebuffer:
//找空項
for (i = 0 ; i < FB_MAX; i++)
if (!registered_fb[i])
break;
//建立裝置檔案
fb_info->dev = device_create(fb_class, fb_info->device,
MKDEV(FB_MAJOR, i), NULL, "fb%d", i);
//將fb_info填充到陣列中
registered_fb[i] = fb_info;
如何實現一個LCD的驅動?
1.分配fb_info
2.初始化fb_info
指定視訊記憶體的物理起始地址
指定視訊記憶體的核心虛擬起始地址
指定視訊記憶體的大小
指定視訊記憶體的可變引數資訊
執行視訊記憶體的固定引數資訊
..........
3.呼叫register_framebuffer註冊fb_info到核心層,供使用者和核心層使用
4.LCD控制器相關的初始化
VD0-VD23:24根資料線,用來傳輸RGB訊號
VDEN:資料使能訊號
VSYNC:垂直同步訊號,控制寫一幀的訊號
HSYNC:水平同步訊號,控制寫一行的訊號
VCLK:畫素時鐘,一個時鐘週期,寫一個畫素點 33.3M
相關時序:
VSPW+1:VSYNC的脈衝寬度
VBPD+1:表示傳送完VSYNC後,經過多久使能資料
VFPD+1:表示寫一幀的時間間隔
HSPW+1:HSYNC的脈衝寬度
HBPD+1:表示傳送完HSYNC後,經過多久使能資料
HFPD+1:表示寫一行的時間間隔
混雜裝置驅動:
1.定義:非標準的字元裝置就是混雜裝置,其實混雜裝置還是字元裝置,只是主裝置已經由核心定義好為10,通過此裝置號來分割槽各個混雜裝置個體
2.資料型別
struct miscdevice {
int minor; //次裝置號,如果指定巨集MISC_DYNAMIC_MINOR,表明讓核心幫你分配一個次裝置號
char *name; //裝置節點名,裝置節點由核心建立
struct file_operations *fops; //給應用程式提供的訪問操作介面
};
3.實現一個混雜裝置驅動:
1.分配,初始化miscdevice
struct miscdevice led_misc = {
.minor = MISC_DYNAMIC_MINOR,
.name = "myled", //dev/myled
.fops = &led_fops
};
2.呼叫misc_register向核心註冊即可
解除安裝misc_deregister即可
案例:利用混雜裝置驅動框架實現LED驅動
----------------------------------------------------------
AD/DA:
模擬訊號:連續的訊號,聲音,電壓,溫度等
數字訊號:離散訊號,0,1表示的訊號資訊
A/D:模擬訊號轉成數字訊號的過程
D/A:數字訊號轉成模擬訊號的過程
手機錄音,放音:
錄音:模擬訊號轉成數字訊號的過程
放音:數字訊號轉成模擬訊號的過程
ADC/DAC:
ADC:模擬訊號轉換為數字訊號的硬體單元
DAC:數字訊號轉換為模擬訊號的硬體單元
手機中使用audio codec 音訊處理晶片:集成了ADC和DAC
衡量ADC的工作引數指標:
1.解析度
描述ADC轉換的最小量度。一般ADC的解析度有8,10,12,20,24位等。
例如:對於一個電壓模擬訊號,測量電壓為3.3V,電壓變化範圍為0~3.3.如果採用ADC對其進行轉換,並且ADC使用的轉換的解析度為10位(表示的是轉換以後的數字量是一個10位的有效值)。也就是說3.3V模擬量對應的數字量就是1111111111(二進位制),從而得到最小量度:3.3/1024 = 0.0032,如果採用的解析度為12位,最小量度:3.3/4096 = 0.0008.3.3V電壓對應的數字量就是111111111111.
如果已知一個數字量是0010110111(10位),請問對應的模擬量是多少?3.3/1024 * (0010110111).
2.轉換時間:一般解析度越高,轉換的時間就會越長,但精度比較高!
----------------------------------------------------------
ADC硬體電路設計問題:
1.現在很多CPU都自帶ADC,在硬體電路設計時,如果採用自帶ADC,最後涉及的驅動程式,就是簡單的字元裝置驅動,通過操作ADC相關的暫存器達到對ADC的控制和訪問
2.如果不適用CPU自帶的ADC,使用外接的ADC晶片,切記要考慮外接ADC晶片和CPU之間的通訊介面,因為這種介面直接影響驅動的涉及,比如如果採用I2C介面的,外接ADC晶片驅動就是I2C裝置驅動,如果GPIO型別的介面,外接的ADC晶片驅動就是普通的操作GPIO來驅動ADC。
對於S5PV210來說,使用的是自己的ADC:
硬體特點:
1.解析度:10位和12位
2.ADC轉換器只有一個,但是模擬訊號輸入通道有10路
3.ADC最大的工作時鐘頻率5MHz,ADC的時鐘源是PCLK=66MHz,注意 要降頻
4.ADC只能轉換電壓模擬訊號,電壓訊號的範圍是0~3.3V
5.在同一時刻,ADC只能處理一路模擬輸入訊號,可用通過操作內部的多路選擇器來指定轉換哪路模擬輸入通道
6.AIN0,AIN1兩路只能接入普通的模擬輸入訊號,不能外界觸控式螢幕
AIN2~AIN9這八路即可用於普通的ADC轉換,也可以用於觸控式螢幕
7.由於ADC轉換需要時間,速度相對CPU來說,會慢點,所以一旦ADC轉換結束,會產生一個內部中斷,告訴CPU,轉換結束。
8.10路模擬輸入通道,只能用於輸入,不能複用!
9.使用涉及的暫存器:
控制器暫存器
配置解析度
配置時鐘頻率
啟動ADC的轉換
延時暫存器
資料暫存器
儲存的轉換以後的數字量
data = 暫存器 & 0xfff //12
data = 暫存器 & 0x3ff //10
中斷清除暫存器:採用中斷方式
模擬輸入通道選擇暫存器
10.判斷ADC轉換結束的方法:
1.輪訓讀取控制暫存器的第15bit
2.通過中斷,如果中斷觸發,表明ADC轉換結束
驅動程式設計:
1.註冊混雜驅動
read:獲取轉換以後的電壓,如果沒有轉換結束,應該進入休眠
ioctl:配置解析度,配置模擬輸入通道
2.註冊中斷處理函式
喚醒休眠的程序
清中斷
3.地址對映
4.初始化設定ADC處於一個正常的工作模式
設定預設的解析度
設定預設的工作頻率
設定預設的模擬輸入通道
readb/readw/readl
地址中的內容 = readx(核心虛擬地址);
writeb/writew/writel
writex(要寫入的值,核心虛擬地址);
測試步驟:
./adc_test 12 1
專案提升:裝置操作維護軟體:每隔5s上位機QT介面顯示ADC的取樣電壓