---
# System prepended metadata

title: linux驅動16. I2c通訊協議 (+註冊char device 複習)

---

## 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)













