文章目錄
- 前言
- 一、IIC驅動框架
- 二、總線驅動
- 2.1 iic總線的運行機制
- 2.2 重要數據結構
- 2.2.1 i2c_driver結構體
- 2.2.2 i2c總線結構體
- 2.3 匹配規則
- 三、設備樹的修改
- 四、設備驅動的編寫
- 4.1 相關API函數
- 4.1.1 i2c_add_adapter( )
- 4.1.2 i2c_register_driver( )
- 4.1.3 i2c_transfer( )
- 4.1.4 i2c_master_send( )
- 4.1.5 i2c_master_recv( )
- 4.1.6 i2c_transfer_buffer_flags( )
- 4.1.7 i2c_del_driver( )
- 4.1.8 i2c_set_clientdata
- 4.2 MPU6050
- 4.2.1 基本介紹
- 4.2.2 主要特點
- 4.2.3 引腳對應表
- 4.3 驅動編寫
- 4.3.1 IIC驅動的設計框架
- 4.3.2 .probe函數
- 4.3.3 .remove函數
- 4.3.4 mpu6050初始化函數
- 4.3.5 write/read函數
- 4.3.6 report函數
前言
??IIC我們已經學習過很多次了,在應用部分我們已經介紹過其應用層的開發,這章我們將繼續驅動部分的開發。本次實驗采用MPU6050,使用了input子系統及IIC子系統構成。
一、IIC驅動框架
??i2c總線包括i2c設備(i2c_client)和i2c驅動(i2c_driver),當我們向linux中注冊設備或驅動的時候,按照i2c總線匹配規則進行配對,這也意味著我們不再需要手動創建,而是使用設備樹機制引入,設備樹節點是與paltform總線相配合使用,在匹配成功之后自動進入.probe函數。
?I2C core框架
??提供了核心數據結構的定義和相關接口函數,用來實現I2C適配器驅動和設備驅動的注冊、注銷管理,以及I2C通信方法上層的、與具體適配器無關的代碼,為系統中每個I2C總線增加相應的讀寫方法。
?I2C總線驅動
??定義描述具體I2C總線適配器的i2c_adapter數據結構、實現在具體I2C適配器上的I2C總線通信方法,并由i2c_algorithm數據結構進行描述。 經過I2C總線驅動的的代碼,可以為我們控制I2C產生開始位、停止位、讀寫周期以及從設備的讀寫、產生ACK等。
?I2C 設備驅動
??I2C 設備驅動通過I2C適配器與CPU通信,其中主要包含i2c_driver和i2c_client數據結構,i2c_driver結構對應一套具體的驅動方法。i2c_client數據結構由內核根據具體的設備注冊信息自動生成,設備驅動根據硬件具體情況填充。
二、總線驅動
??一個完整的iic驅動函數包括兩部分,即iic總線驅動和設備驅動,而總線部分的驅動通常情況下在外設出廠時就由廠商提供,這里我們便簡單了解即可。
2.1 iic總線的運行機制
- 注冊i2C總線
- 將i2C驅動添加到i2C總線的驅動鏈表中
- 遍歷i2C總線上的設備鏈表,根據i2c_device_match函數進行匹配,如果匹配調用i2c_device_probe函數
- i2c_device_probe函數會調用I2C驅動的probe函數
2.2 重要數據結構
??在應用層的學習中我們已經介紹過i2c_algorithm,i2c_client和i2c_adapter結構體,感興趣可以回顧下。
2.2.1 i2c_driver結構體
struct i2c_driver {unsigned int class;int (*probe)(struct i2c_client *, const struct i2c_device_id *);int (*remove)(struct i2c_client *);struct device_driver driver;const struct i2c_device_id *id_table;int (*detect)(struct i2c_client *, struct i2c_board_info *);const unsigned short *address_list;struct list_head clients;...};
- probe: i2c設備和i2c驅動匹配后,回調該函數指針。
- id_table: struct i2c_device_id 要匹配的從設備信息。
- address_list: 設備地址
- clients: 設備鏈表
- detect: 設備探測函數
2.2.2 i2c總線結構體
//定義總線,維護著兩個鏈表(I2C驅動、I2C設備),管理I2C設備和I2C驅動的匹配和刪除等
struct bus_type i2c_bus_type = {.name = "i2c",.match = i2c_device_match,.probe = i2c_device_probe,.remove = i2c_device_remove,.shutdown = i2c_device_shutdown,};
2.3 匹配規則
??一般來說,i2c的匹配方式有三種,包括設備樹,ACPI和字符匹配,這部分的對比前章已經介紹過了。現在我們習慣性采用設備樹的匹配方式:
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{struct i2c_client *client = i2c_verify_client(dev);struct i2c_driver *driver;//設備樹匹配方式,比較 I2C 設備節點的 compatible 屬性和 of_device_id 中的 compatible 屬性if (i2c_of_match_device(drv->of_match_table, client))return 1;//ACPI 匹配方式if (acpi_driver_match_device(dev, drv))return 1;driver = to_i2c_driver(drv);//i2c總線傳統匹配方式,比較 I2C設備名字和 i2c驅動的id_table->name 字段是否相等if (i2c_match_id(driver->id_table, client))return 1;return 0;
}
三、設備樹的修改
??下面是瑞芯微官方給出的ic3控制器的設備樹代碼:
//存放于“rk3568.dtsi”
i2c2: i2c@fe5b0000 {//驅動名稱compatible = "rockchip,rk3399-i2c";//寄存器reg = <0x0 0xfe5b0000 0x0 0x1000>;//時鐘源clocks = <&cru CLK_I2C2>, <&cru PCLK_I2C2>;clock-names = "i2c", "pclk";//中斷源interrupts = <GIC_SPI 48 IRQ_TYPE_LEVEL_HIGH>;pinctrl-names = "default";pinctrl-0 = <&i2c2m0_xfer>;#address-cells = <1>;#size-cells = <0>;status = "disabled";//存放于“rk3568-pinctrl.dtsi”
i2c2 {/omit-if-no-ref/i2c2m0_xfer: i2c2m0-xfer {rockchip,pins =/* i2c2_sclm0 */<0 RK_PB5 1 &pcfg_pull_none_smt>,/* i2c2_sdam0 */<0 RK_PB6 1 &pcfg_pull_none_smt>;};/omit-if-no-ref/i2c2m1_xfer: i2c2m1-xfer {rockchip,pins =/* i2c2_sclm1 */<4 RK_PB5 1 &pcfg_pull_none_smt>,/* i2c2_sdam1 */<4 RK_PB4 1 &pcfg_pull_none_smt>;};};
??接下來就是我們要編寫的內容,這部分編寫方式和以前一樣即可。
&i2c2 {status = "okay";pinctrl-names = "default";pinctrl-0 = <&i2c2m0_xfer>; #address-cells = <1>;#size-cells = <0>;/*添加你的I2C設備參考*/myi2c: myi2c@68 {compatible = "company,myi2c";reg = <0x68>;status = "okay";};
四、設備驅動的編寫
4.1 相關API函數
4.1.1 i2c_add_adapter( )
??使用這個函數時,不需要提前指定適配器編號,內核會負責管理和分配編號,適合于大多數情況下的使用。
//自動分配 I2C 適配器編號
int i2c_add_adapter(struct i2c_adapter *adapter);
??適配器編號(adapter->nr)在注冊過程中由系統自動設置,具體的步驟如下:
- 適配器初始化:調用這個函數之前,需要先初始化 i2c_adapter 結構體,填充相關字段。
- 自動分配編號:內核會自動選擇一個可用的編號,并將其分配給 adapter->nr。
- 注冊適配器:將適配器注冊到 I2C子系統中,使其可以被使用。
注:相似地還還存在int i2c_add_numbered_adapter(struct i2c_adapter *adapter)函數,這個函數用于手動設置 I2C 適配器編號。調用這個函數之前,需要先初始化 i2c_adapter 結構體,并填充 adapter->nr 字段和其他相關字段。
i2c_register_driver 函數用于在 Linux 內核中注冊一個 I2C 驅動。這個函數是 I2C 子系統的一部分,用于將一個 I2C 驅動程序注冊到 I2C 驅動程序模型中,以便內核能夠識別并管理該驅動程序。
4.1.2 i2c_register_driver( )
??這個函數是 I2C 子系統的一部分,用于將一個 I2C 驅動程序注冊到 I2C 驅動程序模型中,以便內核能夠識別并管理該驅動程序。
//在 Linux 內核中注冊一個 I2C 驅動
int i2c_register_driver(struct module *owner, struct i2c_driver *driver);
- 參數:
- owner:通常使用 THIS_MODULE 宏來指定當前模塊
- driver:指向一個 i2c_driver 結構體,該結構體包含了驅動程序的相關信息和操作函數
- 返回值:
- 0:成功注冊
- 負數:注冊失敗
注:#define i2c_add_driver(driver)宏定義是對i2c_register_driver函數的調用,也可以直接使用這個宏定義進行注冊
4.1.3 i2c_transfer( )
??i2c_transfer 是一個底層函數,它可以執行多條消息的讀寫操作。
//在 I2C 總線上進行數據傳輸
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
- 參數
- adap:指向 i2c_adapter 結構體的指針,表示 I2C 適配器。
- msgs:指向 i2c_msg 結構體數組的指針,每個 i2c_msg 結構體表示一條 I2C 消息。
- num:消息數量。
- 返回值
- 正值:表示成功傳輸的消息數量。
- 負值:表示傳輸失敗,返回一個負的錯誤代碼。
注:i2c_msg結構體之前在應用開發實驗時已經介紹過了, 這里簡單回憶一下:
//描述一個iic消息
struct i2c_msg {__u16 addr; //iic設備地址__u16 flags; //消息傳輸方向和特性。I2C_M_RD:表示讀取消息;0:表示發送消息__u16 len; //消息數據的長度__u8 *buf; //字符數組存放消息,作為消息的緩沖區...
};
4.1.4 i2c_master_send( )
??i2c_master_send 是一個便捷函數,用于向 I2C 設備發送數據。
復制代碼
int i2c_master_send(const struct i2c_client *client, const char *buf, int count);
- 參數
- client:指向 i2c_client 結構體的指針,表示目標 I2C 設備。
- buf:指向數據緩沖區的指針。
- count:要發送的數據長度。
- 返回值
- 正值:表示成功發送的字節數。
- 負值:表示發送失敗,返回一個負的錯誤代碼。
4.1.5 i2c_master_recv( )
??i2c_master_recv 是一個便捷函數,用于從 I2C 設備接收數據。
int i2c_master_recv(const struct i2c_client *client, char *buf, int count);
- 參數
- client:指向 i2c_client 結構體的指針,表示目標 I2C 設備。
- buf:指向接收緩沖區的指針。
- count:要接收的數據長度。
- 返回值
- 正值:表示成功接收的字節數。
- 負值:表示接收失敗,返回一個負的錯誤代碼。
4.1.6 i2c_transfer_buffer_flags( )
//用于在 I2C 總線上進行帶有特定標志的數據傳輸
int i2c_transfer_buffer_flags(const struct i2c_client *client, char *buf, int count, u16 flags);
- 參數
- client:指向 i2c_client 結構體的指針,表示目標 I2C 設備。
- buf:指向數據緩沖區的指針。
- count:要傳輸的數據長度。
- flags:傳輸標志。
- 返回值
- 正值:表示成功傳輸的字節數。
- 負值:表示傳輸失敗,返回一個負的錯誤代碼。
4.1.7 i2c_del_driver( )
??這個函數與 i2c_register_driver 相對應,i2c_register_driver 用于注冊一個 I2C 驅動程序,而 i2c_del_driver 用于注銷它。
//從I2C 子系統中注銷一個 I2C 驅動程序
void i2c_del_driver(struct i2c_driver *driver);
- 參數
- driver:指向一個 i2c_driver 結構體,該結構體表示要注銷的 I2C 驅動程序
4.1.8 i2c_set_clientdata
??這個函數用于在I2C驅動的probe函數中設置私有數據指針。這個私有數據通常是指向設備結構體或者其他相關數據結構的指針,它允許驅動在后續的操作中能夠方便地訪問這些數據。
void i2c_set_clientdata(struct i2c_client *client, void *data);
- 參數
- client:指向I2C設備客戶端結構體的指針。
- data:要設置的私有數據指針,通常指向一個自定義的設備結構體或其他相關數據。
4.2 MPU6050
4.2.1 基本介紹
??MPU6050是全球首例整合性6軸(3軸陀螺儀+3軸加速度計)運動處理組件,也可以通過擴展實現9軸運動處理(在連接三軸磁傳感器后)。它集成了三軸MEMS陀螺儀和三軸MEMS加速度計,以及一個可擴展的數字運動處理器DMP(Digital Motion Processor)。MPU6050通過I2C接口與微控制器通信,廣泛應用于需要精確姿態測量的場合,如無人機、機器人和智能穿戴設備等。
4.2.2 主要特點
- 整合性:MPU6050免除了組合陀螺儀與加速器時間軸之差的問題,減少了大量的封裝空間。
- 靈活性:MPU6050的角速度全格感測范圍為±250、±500、±1000與±2000°/sec(dps),可準確追蹤快速與慢速動作;用戶可程式控制的加速器全格感測范圍為±2g、±4g、±8g與±16g,滿足各種應用需求。
- 接口:MPU6050支持最高至400kHz的I2C接口,對于需要高速傳輸的應用,也可使用SPI接口(但請注意,SPI接口僅在MPU-6000上可用)。
- 穩定性:MPU6050具有內建的溫度感測器和在工作環境下僅有±1%變動的振蕩器,保證了測量數據的穩定性。
- 尺寸與封裝:MPU6050采用QFN封裝(無引線方形封裝),尺寸為4x4x0.9mm,可承受最大10000g的沖擊。
- 電源:VDD供電電壓為2.5V±5%、3.0V±5%、3.3V±5%;VDDIO為1.8V±5%或VDD。
- 功耗:陀螺儀運作電流5mA,待命電流5μA;加速器運作電流350μA,省電模式電流20μA@10Hz。
- 性能:陀螺儀敏感度131 LSBs/°/sec,加速度計范圍±2g至±16g。
4.2.3 引腳對應表
MPU6050引腳 | 說明 | 泰山派引腳 |
---|---|---|
SCL | SCL引腳 | GPIO0_B5 |
SDA | SDA引腳 | GPIO0_B6 |
XDA | 沒有使用 | |
XCL | 沒有使用 | |
AD0 | 接地 | GND |
INT(Interrupt) | 懸空或者接地 | |
GND | GND | GND |
VCC | 電源 | 3.3V |
4.3 驅動編寫
4.3.1 IIC驅動的設計框架
??本次實驗大致采用input子系統和IIC子系統,這里著重講一下這部分的設計思路:
- 本實驗采用設備樹匹配的方式進行匹配,故而需要設置i2c_driver結構體。
//定義ID匹配表
static const struct i2c_device_id gtp_device_id[] = {{"company,myi2c", 0},{}
};
//定義設備樹匹配表
static const struct of_device_id mpu6050_of_match_table[] = {{.compatible = "company,myi2c"},{}
};
//定義i2c設備結構體
struct i2c_driver mpu6050_driver = {.probe = mpu6050_probe,.remove = mpu6050_remove,.id_table = gtp_device_id,.driver = {.owner = THIS_MODULE,.name = "company,myi2c",.of_match_table = mpu6050_of_match_table,},
};
- 出入口函數的編寫,通常情況下這里需要編寫module_init和module_exti,并且在其中分別使用i2c_register_driver( )函數和i2c_del_driver( )函數進行 i2c 驅動的注冊和注銷。但是這里引入一個module_i2c_driver(driver) 宏定義,這個宏定義可以自動進行iic總線的注冊和注銷,故而不需要前兩個函數的編寫。
- probe函數的編寫,這部分內容我們需要的進行內存申請,之后便可以進行選擇利用字符設備的方式還是input子系統,筆者這里選擇趁熱打鐵使用input設備,這里流程不太熟悉的可以回顧上章內容。之后利用i2c_set_clientdata將數據和設備鏈接起來。
- remove函數的編寫,這部分與probe函數對應即可,如果選擇字符設備的方式,則依次進行device_destroy(設備刪除)、class_destroy(清除類)、cdev_del(清除設備號)、unregister_chrdev_region(注銷字符設備);若采用input的子系統,則僅需要使用input_unregister_device(注銷input設備即可)。
- 具體外設的初始化、讀、寫函數的編寫,這部分內容根據廠商提供的寄存器、時序圖操作即可。
- 若使用字符設備的話,這里還需要編寫operations結構體相關函數,包括open、write、read、release,并利用cdev_init()函數與設備進行綁定。
4.3.2 .probe函數
static int mpu6050_probe(struct i2c_client *client, const struct i2c_device_id *id){struct mpu6050_data *data;int ret = 0;printk(KERN_EMERG "mpu6050_probe enter!\n");data = devm_kzalloc(&client->dev, sizeof(struct mpu6050_data), GFP_KERNEL);if (!data){printk(KERN_EMERG "devm_kzalloc err!\n");return -ENOMEM;}data->client = client;ret = mpu6050_init_device(client);if(ret < 0){printk(KERN_EMERG "mpu6050_init_device err!\n");return ret;}data->input_dev = devm_input_allocate_device(&client->dev);if (!data->input_dev){printk(KERN_EMERG "devm_input_allocate_device err!\n");return -ENOMEM;}data->input_dev->name = "mpu6050";data->input_dev->id.bustype = BUS_I2C;input_set_abs_params(data->input_dev, ABS_X, -32768, 32767, 0, 0);input_set_abs_params(data->input_dev, ABS_Y, -32768, 32767, 0, 0);input_set_abs_params(data->input_dev, ABS_Z, -32768, 32767, 0, 0);input_set_abs_params(data->input_dev, ABS_RX, -32768, 32767, 0, 0);input_set_abs_params(data->input_dev, ABS_RY, -32768, 32767, 0, 0);input_set_abs_params(data->input_dev, ABS_RZ, -32768, 32767, 0, 0);ret = input_register_device(data->input_dev);if (ret < 0){printk("input_register_device err!\n");return ret;}i2c_set_clientdata(client, data);INIT_DELAYED_WORK(&data->work, mpu6050_report_data);schedule_delayed_work(&data->work, msecs_to_jiffies(100));return 0;
}
4.3.3 .remove函數
static int mpu6050_remove(struct i2c_client *client){struct mpu6050_data *data = i2c_get_clientdata(client);printk(KERN_EMERG "mpu6050_remove enter!\n");cancel_delayed_work_sync(&data->work);input_unregister_device(data->input_dev);return 0;
}
4.3.4 mpu6050初始化函數
static int mpu6050_init_device(struct i2c_client *client)
{int error = 0;error += i2c_write_mpu6050(client, PWR_MGMT_1, 0x00);error += i2c_write_mpu6050(client, SMPLRT_DIV, 0x07);error += i2c_write_mpu6050(client, CONFIG, 0x06);error += i2c_write_mpu6050(client, ACCEL_CONFIG, 0x01);if (error < 0) {printk(KERN_DEBUG "mpu6050_init_device error\n");return error;}return 0;
}
4.3.5 write/read函數
static int i2c_write_mpu6050(struct i2c_client *mpu6050_client, u8 address, u8 data){int error = 0;u8 write_data[2];struct i2c_msg send_msg;write_data[0] = address;write_data[1] = data;send_msg.addr = mpu6050_client->addr;send_msg.flags = 0;send_msg.buf = write_data;send_msg.len = 2;error = i2c_transfer(mpu6050_client->adapter, &send_msg, 1);if (error != 1) {printk(KERN_DEBUG "i2c_write_mpu6050 error\n");return -EIO;}return 0;
}static int i2c_read_mpu6050(struct i2c_client *mpu6050_client, u8 address, void *data, u32 length){int error = 0;u8 address_data = address;struct i2c_msg mpu6050_msg[2];mpu6050_msg[0].addr = mpu6050_client->addr;mpu6050_msg[0].flags = 0;mpu6050_msg[0].buf = &address_data;mpu6050_msg[0].len = 1;mpu6050_msg[1].addr = mpu6050_client->addr;mpu6050_msg[1].flags = I2C_M_RD;mpu6050_msg[1].buf = data;mpu6050_msg[1].len = length;error = i2c_transfer(mpu6050_client->adapter, mpu6050_msg, 2);if (error != 2) {printk(KERN_DEBUG "i2c_read_mpu6050 error\n");return -EIO;}return 0;
}
4.3.6 report函數
static void mpu6050_report_data(struct work_struct *work)
{struct mpu6050_data *data = container_of(work, struct mpu6050_data, work.work);struct i2c_client *client = data->client;s16 accel_data[3];s16 gyro_data[3];u8 buffer[14];int ret;ret = i2c_read_mpu6050(client, ACCEL_XOUT_H, buffer, 14);if (ret < 0) {dev_err(&client->dev, "Failed to read data: %d\n", ret);return;}accel_data[0] = (buffer[0] << 8) | buffer[1];accel_data[1] = (buffer[2] << 8) | buffer[3];accel_data[2] = (buffer[4] << 8) | buffer[5];gyro_data[0] = (buffer[8] << 8) | buffer[9];gyro_data[1] = (buffer[10] << 8) | buffer[11];gyro_data[2] = (buffer[12] << 8) | buffer[13];//用于報告絕對坐標事件input_report_abs(data->input_dev, ABS_X, accel_data[0]);input_report_abs(data->input_dev, ABS_Y, accel_data[1]);input_report_abs(data->input_dev, ABS_Z, accel_data[2]);input_report_abs(data->input_dev, ABS_RX, gyro_data[0]);input_report_abs(data->input_dev, ABS_RY, gyro_data[1]);input_report_abs(data->input_dev, ABS_RZ, gyro_data[2]);input_sync(data->input_dev);schedule_delayed_work(&data->work, msecs_to_jiffies(100));
}
免責聲明:本內容部分參考野火科技及其他相關公開資料,若有侵權或者勘誤請聯系作者。