## I2C使用情形: 1.如果不需要同步接收或是發送之情形(只需一條數據線SDA),需同時發送或接收資料可使用SPI。 2.傳送資料需要回應,接收資料需要回答,確保數據不丟失,就像TCP協議。 3.可同時掛載多個設備,且信號線不會搶佔(由主機決定與誰進行通訊)。 4.有時鐘信號進行同步讀寫(CPU可進中斷,中斷完後可繼續傳輸指令),需要一條clk(SCL)。 因此I2C只需兩條線通信(SCL and SDA): ![image](https://hackmd.io/_uploads/HJrRRM2PC.png) **I2C內部電路** 使用開漏輸出,高電平由外部電壓提供。 ![image](https://hackmd.io/_uploads/HyX_VQhPC.png) 使用開漏輸出原因: ![image](https://hackmd.io/_uploads/Sy4Or72wC.png) 為了使用多個設備,如果用推挽如果同時出現高和低電平,將會導致MOS管燒壞, 因此掛載之設備只提供低電平,高電平由外部提供,並通過上拉電阻進行限流,將狀態輸入到主機中,電路如下: ![image](https://hackmd.io/_uploads/rJgsP7hPC.png) **I2C通訊協議** 啟動和結束: ![image](https://hackmd.io/_uploads/Bk4gO7hDR.png) 發送資料給從機 跟always @ (posedge clk or negedge rst_n)差不多 (資料發送是高位先行) ![image](https://hackmd.io/_uploads/H1VDKEhwR.png) 主機接收從機資料 跟發送差不多 ![image](https://hackmd.io/_uploads/r1yuis6P0.png) 接收和發送後 主機或從機須回答接收或發送是否成功 0為應答,跟開漏輸出有關,應為0是強接地,1有可能是從機未回應。 ![image](https://hackmd.io/_uploads/BkZJeZOOA.png) **I2C相關細節** I2C每個設備都有不同的地址,同一個設備會分配相同地址,當需掛載相同設備時,可根據引腳修改後面幾位的地址。 可通過A0 A1(不一定有) A2(不一定有) 引腳,調整後三位地址。 I2C操作步驟通常是主機發送一個字節,其中前7位是地址,以及最後一位讀寫位 0為寫 1為讀 ,並接收從機應答,應答之後,則根據i2c設備進行後續指令,比如說ADC或是MPU6050,有可能是發送操控指令,或是讀取的記憶體位置。 讀取資料比較麻煩,因為在傳送讀取指令(第一個字節)後,會直接進入接收模式,接收從機資料,無法指令讀取底幾個記憶體位置,所以在讀取之前,會先進入寫模式,透過寫模式傳遞記憶體位置,這時候的從機記憶體指針會有紀錄,這時候在通過讀取指令,就能讀取對應位置。 **I2C device tree 配置** 使用的I2C設備為 AP3216C ![image](https://hackmd.io/_uploads/rkxiRBSR0.png) ![image](https://hackmd.io/_uploads/r1LlArrCC.png) 是一種感測光的sensor,可量測 ALS(光強度),PS(距離)IR(光照sensor) 開發板使用引腳: ![image](https://hackmd.io/_uploads/BJaQ1ISCC.png) 修改設備樹: 根據上面引腳使用,設置I2C引腳和電氣屬性(原來就有) ![image](https://hackmd.io/_uploads/rylIe8S00.png) 前面是地址,後面則為電器屬性,用於**pinctrl 子系統**。 配置I2C1使用的設備,屬性和地址,名稱不重要,只是用於查找。 ![image](https://hackmd.io/_uploads/SyF1mLHR0.png) 如果再i2c1總線上遇到子節點設備地址一樣,需修改地址,不能一樣。 **driver撰寫**: 跟platform概念很像,分成driver 和 device。 首先是 driver 使用: 使用i2c_driver 結構體配置 i2c device tree 訊息,和probe成功時執行的函數。 那 module init 自然是register i2c_driver。 ![image](https://hackmd.io/_uploads/B11cAJEJJl.png) i2c_driver結構體: ![image](https://hackmd.io/_uploads/r1Tklx4Jye.png) id_table 不是用 device tree 進行匹配,而是i2c的設備id,適合沒有device tree 的情況。 ![image](https://hackmd.io/_uploads/r1IweeN1yg.png) 接著是 of_match_table 用來匹配 device tree 的 compatiable ![image](https://hackmd.io/_uploads/Hy8jxl4yyx.png) 接著是在probe函數中註冊一個char device,方便使用read 和 write等 system call,配合應用進行i2c data 的讀取。 ## register char device 複習(之前的寫的太爛) char device 可以分為三個部分 ![image](https://hackmd.io/_uploads/Syg3y0j8kye.png) 1.首先是註冊設備號,要自己選設備號可以用**register_chrdev_region()**,由系統分配可以用**alloc_chrdev_region()**,MKDEV 和 MAJOR和 MINOR就是宏定義,可以處理這些地址得知設備號。 2.接著是創建設備,用CDEV結構體 ![image](https://hackmd.io/_uploads/HJYWknIykx.png) 讓KERNEL知道,當前設備進行WRITE等system call 時,要使用那些function(file_operations 結構體),用**cdev_init()** 進行連接,cnt 就是幾個設備,最後用**cdev_add()**和設備號相連。 file_operations 結構體需要註冊對應 function: ![image](https://hackmd.io/_uploads/HkNPS2Uykg.png) ![image](https://hackmd.io/_uploads/rJ5uBnI1yx.png) 3.最後是使用class和device結構體,把設備掛載到/dev下,class則是設備的類,像是GPIO、I2C等。 分別使用class_create()和device_create()即可。 ## I2c Client 使用 **i2c driver 函數撰寫:** 使用**i2c_transfer()** 函數即可,第一個函數傳入i2c_client 中的adapter(裡面包括i2c匹配的演算法等),第二個傳入msg結構體矩陣,根據需求修改,第三個參數則是決定msg矩陣大小,read通常是兩個,因為read 是 先write 決定讀第幾個reg才read,write通常是一個。 read: msg 結構體: addr 是設備地址 flags是讀寫位 len是buf要寫或讀幾個byte buf 是要寫或讀的buffer ![image](https://hackmd.io/_uploads/Bkgi4AUkyx.png) **i2c_read func:** msg[0]是寫操作,addr傳設備地址,flags=0為寫,buf傳reg,代表要讀的register,len為1 msg[1]是寫完的讀操作,flags=0x0001為讀操作,val為讀出來的資料,len是要讀幾位。 最後把adapter 和 msg[2] 以及msg矩陣大小傳進 i2c_transfer即可。 ![image](https://hackmd.io/_uploads/rk_dVRU1kx.png) **i2c_write func:** buf 要進行修改,創一個新矩陣b,第一位一樣填要寫的reg位置,接著是要寫的資料,長度變成len+1,其他跟讀一樣。 ![image](https://hackmd.io/_uploads/BJgAHRIk1g.png) 使用方式如下: ![image](https://hackmd.io/_uploads/rkLS2-2J1x.png) 42(rxd是sda)和43(txd是scl) PIN 是 I2C , mini沒有集成,需要另外接。 ![image](https://hackmd.io/_uploads/SJEIpWhyye.png) 引腳配置如下: ![image](https://hackmd.io/_uploads/H1Ik5PTJ1e.png) 用之前stm32的mpu6050做實驗。 在open中加入mpu6050_i2cinit()函數,利用write_reg去配置mpu6050相關電器屬性(抄以前stm32的)。 ![image](https://hackmd.io/_uploads/r1iE5w6J1e.png) 在來用read 去讀取三軸的信息: ![image](https://hackmd.io/_uploads/HJ2c9vpkye.png) 最後是應用的撰寫: ![image](https://hackmd.io/_uploads/SyDpcDaJJl.png) 成果: ![image](https://hackmd.io/_uploads/rkltFv6kke.png) 代碼地址: [i2c代碼(不小心把mpu6050的部分刪除了,只剩ap3216c的)](https://github.com/amazingfraf177/linuxdriver/tree/main/14.i2c)