1.????I2C總線的硬件特性:兩線式串行總線.用于連接CPU和外設之間的通信接口需要2根信號線,時鐘控制線SCL和數據傳輸信號線SDA.串行:CPU和外設之間傳輸是一個周期傳輸一個BIT位, 如果需要寫入0X55,需要兩個時鐘周期才能完成.CPU又稱master,外設又稱slave.
“一個時鐘周期傳輸一個bit”:CPU和外設之間傳輸一個bit位,必須要通過時鐘控制信號來實現雙方的數據收和發!比如CPU在時鐘高電平像數據線寫入數據,設備在同一個周期低電平從數據線上接收數據.
“總線”:兩根信號線上可以掛接很多外設,也可以掛接很多CPU.一般來說總線上只有一個CPU,如果有多個CPU,I2C總線具有仲裁機制來實現同步訪問.
SCL和SDA分別會接上一個上拉電阻,這兩根信號線的默認狀態為高電平狀態!一般如果CU或者外設配置GPIO為輸出口,就等于CPU或者外設控制了GPIO獲取控制權;如果配置成輸入口,就等于釋放控制權.
2.????問:CPU如何通過兩根線找到需要訪問的具體設備?CPU如果找到某個具有的外設,那么CPU和外設是如何通過兩根信號新完成數據交互? SDA和SCL如何搭配的?
以上答案在I2C總線協議中(芯片手冊)
3.????總線協議相關內容:
(1)?????START信號,起始信號:CPU如果要訪問總線,必須CU首先向總線上發送一個START起始信號;此信號由CPU發起;SCL為高電平。SDL由高電平向低電平跳變,產生START信號‘
(2)?????停止信號,結束信號:CPU結束訪問總線,需要向總線發送一個STOP信號;此信號由CPU發起;SCL為高電平,SDA由低電平向高電平跳變,產生STOP信號;
(3)?????設備地址:用于標識外設在I2C總線上的唯一性!同一個I2C總線上外設,每一個外設都有唯一的設備地址;如果CPU要訪問某個外設,CPU只需要在總線上發送某個外設的設備地址即可,發送完畢,如果外設存在于總線上,外設會給CPU一個反饋信號,就可以進行后續的數據訪問;
外設的設備地址的確定:一般有芯片廠家和原理圖共同決定,以三個外設為例,電可檫除存儲器AT24C02、溫度傳感器LM77,背光燈控制芯片ADP8860。
AT24C02,EEPROM的設備地址:1010A2A1A0R/W:1010:高4bit,芯片廠家定義;A2A1A0:原理圖上都接GNDèA2A1A01=000 R=1,表示CPU讀取外設,W=0,表示CPU寫外設。
WP:寫保護
通過以上信息,得到讀設備地址:10100001=>0Xa1 寫設備地址:10100000=>0Xa0 設備地址(7位,不算讀寫位,地址右移1位,高位補0)=》最終設備地址=01010000=》0X50
ADP8860背光燈控制芯片設備地址0101010X:讀設備地址:01010101=>0X55 寫設備地址:01010100=>0X54
設備地址:00101010=>0X2A
LM77設備地址100100A1AA1:如果A1A0都接地:A1A0=00 讀設備地址:10010001=>0X91 寫設備地址:10010000=>0X90 設備地址:01001000=>0X48。
(4)ACK信號:反饋應答信號。如果CPU發送完設備地址,,并且外設進行響應,此時給CPU發送一個ACK應答信號,告訴CPU,外設存在于總線上;如果在數據讀寫過程中,也可以通過ACK信號指示數據的讀寫過程是否正常!有效的ACK信號為低電平(數據線),無效的ACK信號為高電平!?????????????????????????????????????
4.CPU與外設的數據交互:I2C的數據傳輸,從高位開始發送,一次傳輸一個字節!
1.LM77溫度傳感器的數據傳輸
(1)CPU發送START信號;(2)COU發送設備地址 3.CPU發送讀寫位4.設備如果正常存在總線上,設備給CPU發送一個ACK信號 5.根據芯片手冊進行數據的讀寫操作,其中涉及到ACK信號,這個ACK與4步驟ACK信號意義不太一樣!6.CPU發送STOP信號,結束此次數據交互。
2.ADP8860背光燈芯片:芯片內部有一組寄存器,但是這些寄存器CPU不能像訪問GPIO一樣直接去訪問寄存器地址,原因ADP8860并沒有直接連接到CPU的4G地址空間中,需要間接的利用I2C總線訪問芯片內部的寄存器地址;例如把數據0XAA寫入芯片內部寄存器0X34這個地址。
(1)。CPU發送START信號 (2)CPU發送設備地址 3.CPU發送讀寫位,如果是寫,這個bit為0 4.ADP8860如果正常存在于總線上,設備返回一個ACK信號給CPU,低電平有效。 5.CPU發送訪問的寄存器以后,設備給CPU發送一個ACK信號,告訴CPU可以繼續訪問; 7.CPU發送要寫入的數據0XAA;8.設備將數據0XAA寫入到內部寄存器0X34,然后設備給CPU一個ACK信號,告訴CPU寫入成功; 9.CPU發送STOP信號,結束此次的寄存器寫入操作
3.電可檫除存儲器EEPROM(at24c02)訪問過程:
AT24C01的容量為256字節,地址編址:0—255?MSN:高位LSB:低位。
將數據0XAA 寫入到內部儲存地址0X55,CPU要訪問的內部地址的操作過程和ADP8860相似!
(1)CPU發送START信號 (2)CPU發送設備地址 (3)CPU發送寫:0 (4)設備存在總線上,那么設備給CPU發送一個ACK應答信號,告訴CPU,我在總線上! (4)CPU發送要操作的地址:0X55 (5)設備接收到這個要操作的地址,設備給CPU發送一個ACK信號,告訴CPU,可以訪問這個地址!(6)CPU發送要寫入的數據0XAA (7)設備接收這個要寫入的數據,并將數據寫入到對應的地址0X55中,設備給CPU發送一個ACK信號,告訴CPU寫入數據成功! (8)CPU發送一個STOP信號,停止數據的此次訪問。
隨機讀:就是讀任何一個地址里的數據即可,例如讀取0X55地址空間中的數據信息。
SDA和SCL如何搭配使用? 如果CPU或者設備向數據線上寫入數據,應該在SCL為低電平寫入數據;如果CPU或者設備從數據線上獲取數據,應該在同周期的高電平去讀數據!
I2C外設的具體如何操作,關鍵看芯片手冊即可!
4.????AT24C02的硬件特性:EEPROM,電可檫除存儲器; 容量:2K ,256字節 傳輸速度:100khz’,400KHZ
寫周期:5ms 分頁:1頁為8字節,如果按頁寫,最多一次只能寫8字節,如果寫9字節,第9字節數據會把第一字節的數據進行覆蓋; 地址編碼:0—255 設備地址:0X50
5.案例:存儲軟件和硬件版本號到EEPROM中:軟件版本號:SYYMMDDXY->S14101700 硬件版本號:HYYMMDDXY->H14101700EEROM存儲器地址規劃:軟件版本號的地址范圍:0X0—0X9硬件版本哈的地址范圍:0X10—0X19
驅動設計:1.采用GPIO模擬I2C時序來實現I2C總線硬件操作!2.采用操作I2C控制器來實現I2C總線硬件操作! 3.采用linux內核I2C驅動框架來實現I2C硬件操作
5.1 Linux內核的I2C驅動框架:
App:open,read,write,ioctl
Eeprom.ADDR = 0;設備地址 Eeprom.data = ‘s’;//數據
Ioctl(fd,I2C_WRITE_CMD,&eeprom)
I2C設備驅動:eeprom_ioctl, eeprom_read,………;
1.????只關注硬件外設; 2.只關注硬件外設操作的數據信息; 設備地址、設備片內地址 設備的數據
2.????I2C設備驅動利用內核提供的相關操作方法,將以上數據信息發送給I2C的總線驅動,由I2C總線驅動來實現硬件上面的數據傳輸!
內核提供的操作接口:1.i2c_transfer();//老式接口 SMBUS接口;//新式接口,兼容老式接口
作用:上一層的I2C設備驅動利用這些接口函數,將I2C設備驅動要訪問的數據信息(設備地址、片內地址、數據)丟給I2C總線驅動來實現硬件的總線傳輸!
I2C總線驅動:1.管理的設備對象僅僅I2C控制器 I2C硬件控制器集成在CPU的內容,訪問I2C控制器就通過寄存器來進行,類似串口控制器,nandflash控制器,由硬件幫你發時序!
3.????I2C總線驅動啟動硬件的時序,然后根據I2C設備驅動發來的數據信息(設備地址、片內地址、數據)最終完成I2C的硬件傳輸。
利用I2C實現I2C外設的驅動重點涉及兩個驅動:I2C總線驅動和I2C設備驅動。
1.????I2C總線驅動:管理的對象是I2C控制器,只負責硬件的傳輸數據,這個驅動一般都是芯片公司在提供的linux內核源碼中,只需配置內核添加I2C總線驅動即可:
DEVICE DRIVERàI2C SupportàI2C hardware bus support-><*>s3c2410 i2c drivers
I2C總線驅動:drivers/i2c/buses/i2c-s3c.c
2.????i2C設備驅動:實際開發只需關注I2C外設的驅動,就是I2C設備驅動,只需關注外設的操作的數據信息(設備地址、片內地址、數據)
5.2利用I2C驅動框架實現I2C設備驅動。
I2C設備驅動實現利用了內核分離的思想,采用虛擬總線的形式來管理I2C設備驅動,具體如下:
(1)首先linux內核已經幫你定義好了一個I2C的虛擬總線(i2c_bus_type),在這個總線上維護者兩個鏈表:dev鏈表和drv鏈表(2)dev鏈表存放硬件信息,每一個節點的對應的數據類型是struct i2c_client,用這個結構體來裝載硬件信息。每當向內核添加一個硬件節點時,內核幫你遍歷drv鏈表,取出drv鏈表每一個軟件節點,根據硬件節點的name和軟件節點的id_table中的name進行匹配,如果匹配成功,說明硬件找到了軟件,然后調用軟件節點的probe函數,然后將匹配成功的硬件節點的首地址傳遞給probe函數,供probe函數獲取硬件信息;(3)drv鏈表存放軟件信息,每一個節點的對應的數據類型是struct_i2c_driver,用這個結構體來裝載軟件信息(fops)。每當向內核添加一個軟件節點時,內核會幫你遍歷dev鏈表,取出dev鏈表每一個硬件節點,根據硬件節點的name和軟件節點的id_table中的name進行匹配,如果匹配成功,說明軟件找到了硬件,然后調用軟件節點的probe函數,然后將匹配成功的硬件節點首地址傳遞給probe函數,供probe函數獲取硬件信息;(4)i2c硬件的信息:關鍵是i2c設備地址,因為I2C設備地址有可能由原理圖決定。
總結;實現一個I2C設備驅動關鍵圍繞著i2c_client和i2c_driver。
[c]?view plain?copy
struct?i2c_client?{??
unsigned?short?flags;/*?div.,?see?below?讀寫標志?*/???
unsigned?short?addr;/*?chip?address?-?NOTE:?7bit?設備地址7bit*//*?addresses?are???
char?name[I2C_NAME_SIZE];//用于匹配??
struct?i2c_adapter?*adapter;?/*?the?adapter?we?sit?on適配器,4個總線*/???
struct?i2c_driver?*driver;?/*?and?our?access?routines軟件節點*/???
struct?device?dev;/*?the?device?structure?platform_data來存自己定義的硬件信息?*/???
int?irq;/*?irq?issued?by?device?中斷號?*/???
struct?list_head?detected;?//鏈表???
這個結構體不會像platform_device顯示的需要自己去分配,初始化和注冊,這個工作linux內核已經幫你實現,甚至注冊的時候進行匹配,都是linux內核來幫你實現!
問:如果linux內核幫你實現分配初始化i2c_client,內核如何知道我要的操作的設備地址,自己的私有硬件信息? Linux內核提供了另外一個結構體struct i2c_board_info,程序員根據這個結構體來進行對I2C外設的硬件分配初始化和注冊,內核會根據i2c_board_info的信息來實現對i2c_client的一系列操作。
問:驅動如何使用i2c_board_info呢?
Struct i2c_board_info{
Char type[I2C_NAME_SIZE];//用于匹配,最終會賦值給i2c_client的name
Unsigned short flags; //讀寫標志
Unsigned short addrd; //設備地址。最終賦值給i2c_client的addr
Void *platform_data; //裝載自己定義的硬件信息,最終賦值給i2c_client的dev.platform_data
Int irq; //中斷號,最終賦值給i2c_client的irq
}
明確:struct i2c_board_info的分配,初始化和注冊三個步驟必須在平臺代碼中完成,不能以模塊加載的形式來實現! 對struct i2c_board_info的操作本質就是間接的在操作i2c_client。
實現在平臺代碼中初始化分配i2c_board_info
1.Vimarch/arm/mach-s5pv210/mach-cw21.c 在頭文件的后面添加分配i2c_board_info的分配初始化。
Static structi2c_board_info eeprom[] = {I2C_BOARD_INFO(“at24c02”,0x50)};
說明:I2C_BOARD_INFO:用于初始化i2c_board_info的type和addrat24c02:用于匹配,跟i2c_driver的id_table的name匹配,最終會賦值給i2c_client的name。 0x50:設備地址,最終會賦值給i2c_client.addr。
2.同樣在平臺代碼的初始化函數中(.init_machine =smdkc110_machine_init),所在函數smdkc110_machine_init中調用I2c_register_board_info注冊i2c_board_info信息到內核中!i2c_regster_board_info(int busnum, struct i2c_board_info const*info, unsigned n)
函數功能:注冊分配初始化好的i2c_board_info對象到內核中,內核根據這個信息初始化注冊i2c_client;
參數:busnum:i2c外設所在的I2C總線編號,cw210開發板的at’24c02通過原理圖可知連接到CPU的第一個I2C總線上,所以這個參數指定為0,info:執行分配初始化的i2_board_info對象數組(=eeprom)
N:對象數組的個數RRAY_SIZE(eeprom)
注意:一旦向內核注冊I2c_board_info設備信息,內核在初始化時會根據此信息幫你分配初始化和注冊一個i2c_client.
2177i2c_register_board_info(0, i2c-devs0, ARRAY_SIZE(i2c_devs0));
2178i2c_register_board_info(1, i2c-devs1, ARRAY_SIZE(i2c_devs1));
2179i2c_register_board_info(2, i2c-devs2, ARRAY_SIZE(i2c_devs2));
2180i2c_register_board_info(5, i2c-devs3, ARRAY_SIZE(i2c_devs5));
2181 //注冊atc24c02的硬件對象i2c_board_info
2182i2c_register_board_info(0, eeprom, ARRAY_SIZE(eeprom));
Struct i2c_driver怎樣使用?
1.分配初始化struct i2c_driver
Struct i2c_driver eeprom_drv = {
.driver = {
.name= “tarena”//不重要
},
.probe = at24c02_probe, //匹配成功調用
.remove = at24c02, //卸載軟件節點調用
.id_table = 其中的name用于匹配
}
2.調用i2c_add_driver注冊 3.調用i2c_del_driver卸載。
案例:SMBUS接口作用:I2C設備驅動利用SMBUS相關的函數,能夠將I2C設備驅動涉及的數據信息丟給I2C總線驅動,然后I2C硬件傳輸!本質上就是I2C設備驅動和I2C總線驅動的一個數據交互的橋梁!
SMBUS接口說明文檔:內核源碼\Documentation\i2c\smbus-protocol找到對應的SMBUS接口函數。
SMBUS接口函數的使用。
1.找到打開smbus-protol說明文檔 2.打開芯片的時序圖 3.根據時序圖在smbus-protol文檔中找到對應的操作函數 4.想盡一切辦法看這個函數的說明或者參考別人的代碼。
//寫數據到EEPROM中
// ?????????????????????? addr,data
App:ioctl---àaddr,data
I2C設備驅動:at24c02_i2c_write---à設備地址,addr,data
內核:SMBUS--à設備地址,addr,data
總線驅動--àSTART> 設備地址寫ACK addr ACK data ACK STOP
1.使用SMBUS接口將數據(地址、數據、設備地址(G_CLIENT->addr)),丟給I2C總線驅動,啟動I2C總線驅動的硬件傳輸。
1.1 打開SMUBS文檔:內核源碼\Documentation\i2c\smbus-protocol找到對應的SMBUS接口函數。
1.2打開芯片操作時序圖
1.3根據時序圖找到對應的SMBUS操作函數
1.4將ADDR,data和匹配成功的i2c_client通過函數丟給I2C總線驅動然后啟動I2C總線的硬件傳輸
i2c_smbus_write_byte_data(g_client, addr, data);
從EEPROM讀取數據
//?????????????? addr
//?????????????? app:ioctlà data
I2C設備驅動:at24c02_i2c_readà data 設備地組織:addr
內核:SMBUE—》data設備地址,addr
總線驅動-àSTART設備地址寫ACK addr ACK START 設備地址 讀ACK返回數據data
NOACK STOP -àdata
?
評論
查看更多