驅動分析
I2C設備驅動框架圖:
我們先RT-Thread的I2C框架圖(這是我自己理解的框架圖,如果不對的地方,請指出):- 上圖是我分析的RTT的I2C框架圖。主要分為三層,驅動層-核心層-設備層。如果你分析過Linux的I2C框架,它的層次也是這樣子。所以你了解了RTT的I2C之后再去看Linux的I2C框架,其實問題不大。
- 驅動層:分為硬件I2C驅動和軟件I2C驅動。
- 核心層: ①其中bit_ops是RTT為軟件I2C提供的中間層,它的作用:為底層模擬I2C驅動提供回調接口,為核心層提供統(tǒng)一I2C通信接口。②而硬件I2C則直接對接核心層,提供統(tǒng)一I2C通信接口。③RTT在核心層上,也像pin驅動那樣,封裝了一套API(虛線箭頭),供用戶直接使用。④dev是提供RTT設備驅動框架的統(tǒng)一的API(實現(xiàn)箭頭)。⑤注意的是:模擬I2C驅動到核心層,增加了一層中間層。
- 設備層:設備就是雜七雜八的使用I2C的總線的設備。而這些設備可以選擇使用RTT驅動框架的API,也可以選擇RTT封裝好的API。
通過上述的描述,可能還沒了解的很清晰。下面我根據(jù)兩種不同方式的驅動,兩種不同的API,逐一分析,并且會結合試驗來驗證。driver 層:
- RT-Thread的I2C驅動,分為兩種類型:硬件I2C和軟件I2C。在stm32的BSP中提供了軟件I2C的驅動,不過為了全面介紹,硬件I2C的對接,作者也進行簡單的對接和實現(xiàn)。
軟件I2C:
軟件I2C的層次圖:drv_soft_i2c層: 主要進行軟件I2C所用到scl引腳,sda引腳初始化。scl引腳和sda引腳的獲取電平和設置電平接口和延時函數(shù)(udelay)。并對接bit_opt層提供的操作結構體:struct rt_i2c_bit_ops。并通過rt_i2c_bit_add_bus注冊,提供給bit_opt層回調。- struct rt_i2c_bit_ops結構體:
struct rt_i2c_bit_ops{ void *data; void (*set_sda)(void *data, rt_int32_t state); void (*set_scl)(void *data, rt_int32_t state); rt_int32_t (*get_sda)(void *data); rt_int32_t (*get_scl)(void *data); void (*udelay)(rt_uint32_t us); rt_uint32_t delay_us; rt_uint32_t timeout;};
函數(shù)指針 功能 void (set_sda)(void data, rt_int32_t state) 設置SDA電平 void (set_scl)(void data, rt_int32_t state); 設置SCL電平 rt_int32_t (get_sda)(void data); 獲取SDA電平 rt_int32_t (get_scl)(void data); 獲取SCL電平 void (*udelay)(rt_uint32_t us); 軟件I2C時序所需要的的延時函數(shù)
- rt_i2c_bit_add_bus接口,主要注冊軟件I2C的引腳操作的回調函數(shù)。
bit_opt層:可以歸納為驅動層。其主要實現(xiàn)軟件I2C的時序等邏輯,并提供對應的I2C的收發(fā)處理函數(shù),為drv_soft_i2c層提供提供了(struct rt_i2c_bit_ops)注冊接口和(rt_i2c_bit_add_bus)接口,為i2c_core層提供主機模式的數(shù)據(jù)處理函數(shù)。bit_opt層主要對接到i2c_core層提供操作結構體:struct rt_i2c_bus_device_ops以及i2c總線的注冊函數(shù)rt_i2c_bus_device_register:- struct rt_i2c_bus_device_ops結構體:
struct rt_i2c_bus_device_ops{ rt_size_t (*master_xfer)(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num); rt_size_t (*slave_xfer)(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num); rt_err_t (*i2c_bus_control)(struct rt_i2c_bus_device *bus, rt_uint32_t, rt_uint32_t);};
函數(shù)指針 功能 master_xfer 主機模式的數(shù)據(jù)收發(fā) slave_xfer 從機模式的數(shù)據(jù)收發(fā) i2c_bus_control i2c總線的的操作參數(shù)等設置
- rt_i2c_bus_device_register接口:主要注冊I2C總線和收發(fā)數(shù)據(jù)的回調函數(shù)。
軟件I2C驅動總結:rt-thread的軟件I2C,如果要對接其他平臺,只需要對接好結構體:struct rt_i2c_bit_ops。而軟件I2C的邏輯完全不用理會,全部由bit_opt層管理。硬件I2C
硬件I2C的層次圖:drv_hw_i2c層:沒有軟件I2C的bit_opt層,而是直接對接i2c_core層提供的結構體:struct rt_i2c_bus_device_ops。作者為了簡單說明,寫了個例子(簡單粗暴的例子):struct rt_i2c_bus_device i2c1_bus;I2C_HandleTypeDef hi2c1;static rt_err_t i2c_hw_init(void){ hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } return RT_EOK;}static rt_size_t i2c_xfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num){ struct rt_i2c_msg *msg; rt_int32_t i, ret; for(i = 0; i< num; i++) { msg = &msgs[i]; if(msg->flags & RT_I2C_RD) { HAL_I2C_Master_Receive(&hi2c1, msg->addr, msg->buf, msg->len, 100); } else { HAL_I2C_Master_Transmit(&hi2c1, msg->addr, msg->buf, msg->len, 100); } } ret = i; return ret;}static const struct rt_i2c_bus_device_ops i2c_bus_ops ={ i2c_xfer, RT_NULL, RT_NULL};int rt_i2c_hw_init(void){ i2c_hw_init(); i2c1_bus.ops = &i2c_bus_ops; rt_i2c_bus_device_register(&i2c1_bus, "hw_i2c"); return RT_EOK;}INIT_DEVICE_EXPORT(rt_i2c_hw_init);
以上硬件I2C對接,我只是對接了master_xfer。硬件I2C驅動總結:如果你是采用硬件I2C,那么就不需要去關乎bit_opt層,其實通過上面的描述,你不難理解bit_opt層和drv_hw_i2c層其實對接都是i2c_core層的結構。這也是我為什么把bit_opt層歸納為驅動層來講解了。core 層:
i2c_core層為驅動層提供結構體:struct rt_i2c_bus_device_ops。為設備層提供I2C的數(shù)據(jù)收發(fā)處理函數(shù)。其實i2c_core層主要是封裝了一層API直接提供給用戶層設備調用。函數(shù) 說明 rt_i2c_bus_device_find 查找i2c總線 rt_i2c_transfer 主機模式的i2c數(shù)據(jù)傳輸 rt_i2c_master_send 主機模式的i2c數(shù)據(jù)發(fā)送 rt_i2c_master_recv 主機模式的i2c數(shù)據(jù)接受
其中rt_i2c_master_send函數(shù)和rt_i2c_master_recv函數(shù)是調用rt_i2c_transfer函數(shù),而rt_i2c_transfer函數(shù)是調用master_xfer回調函數(shù)。如果你是使用這些接口,那么device層可以不用理會。device 層:
i2c_dev層,對接rt-thread設備驅動框架。提供read,write,control函數(shù)。并通過函數(shù)rt_device_register注冊到設備驅動框架。下面我也會使用這些接口來實現(xiàn)控制OLED。函數(shù) 說明 i2c_bus_device_read I2C讀操作 i2c_bus_device_write I2C寫操作 i2c_bus_device_control I2C總線的控制
軟件I2C設計:
struct rt_i2c_bus_device *i2c_bus;#define OLED_I2C_BUS_NAME "i2c1"#define OLED_ADDRESS 0x3cstatic rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t data){ rt_uint8_t buf[2]; struct rt_i2c_msg msgs; buf[0] = reg; buf[1] = data; msgs.addr = OLED_ADDRESS; msgs.flags = RT_I2C_WR; msgs.buf = buf; msgs.len = 2; /@@* 調用I2C設備接口傳輸數(shù)據(jù) */ rt_i2c_transfer(bus, &msgs, 1); return RT_EOK;}static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf){ struct rt_i2c_msg msgs; msgs.addr = OLED_ADDRESS; msgs.flags = RT_I2C_RD; msgs.buf = buf; msgs.len = len; /@@* 調用I2C設備接口傳輸數(shù)據(jù) */ rt_i2c_transfer(bus, &msgs, 1); return RT_EOK;}int oled_init(void){ i2c_bus = rt_i2c_bus_device_find(OLED_I2C_BUS_NAME); if(i2c_bus == RT_NULL) { rt_kprintf("find i2c bus fail!"); return RT_ERROR; } rt_kprintf("find i2c bus success!"); ......}
硬件I2C設計:
struct rt_i2c_bus_device *i2c_bus;#define OLED_I2C_BUS_NAME "hw_i2c"#define OLED_ADDRESS 0x78static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t data){ rt_uint8_t buf[2]; struct rt_i2c_msg msgs; buf[0] = reg; buf[1] = data; msgs.addr = OLED_ADDRESS; msgs.flags = RT_I2C_WR; msgs.buf = buf; msgs.len = 2; /@@* 調用I2C設備接口傳輸數(shù)據(jù) */ rt_i2c_transfer(bus, &msgs, 1); return RT_EOK;}static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf){ struct rt_i2c_msg msgs; msgs.addr = OLED_ADDRESS; msgs.flags = RT_I2C_RD; msgs.buf = buf; msgs.len = len; /@@* 調用I2C設備接口傳輸數(shù)據(jù) */ rt_i2c_transfer(bus, &msgs, 1); return RT_EOK;}int oled_init(void){ i2c_bus = rt_i2c_bus_device_find(OLED_I2C_BUS_NAME); if(i2c_bus == RT_NULL) { rt_kprintf("find i2c bus fail!"); return RT_ERROR; } rt_kprintf("find i2c bus success!"); ......}
使用驅動框架API實現(xiàn):
#define OLED_I2C_BUS_NAME "hw_i2c"struct rt_device *dev_i2c;#define OLED_ADDRESS 0x78static rt_err_t write_reg(struct rt_device *dev, rt_uint8_t reg, rt_uint8_t data){ rt_uint8_t buf[2]; rt_off_t pos; rt_uint16_t addr = OLED_ADDRESS; rt_uint16_t flags = RT_I2C_WR; buf[0] = reg; buf[1] = data; pos = (flags << 16) | addr; rt_device_write(dev, pos, buf, 2); return RT_EOK;}static rt_err_t read_regs(struct rt_device *dev, rt_uint8_t len, rt_uint8_t *buf){ rt_off_t pos; rt_uint16_t addr = OLED_ADDRESS; rt_uint16_t flags = RT_I2C_WR; pos = (flags << 16) | addr; rt_device_write(dev, pos, buf, 2); return RT_EOK;}int oled_init(void){ dev_i2c = rt_device_find(OLED_I2C_BUS_NAME); if(dev_i2c == RT_NULL) { rt_kprintf("find i2c bus fail!"); return RT_ERROR; } rt_kprintf("find i2c bus success!"); rt_device_open(dev_i2c, RT_DEVICE_OFLAG_RDWR); .... return RT_EOK;}
總結
- 有了I2C驅動框架,對于上層來說,不管硬件I2C還是軟件I2C調用接口都是一樣的。
- rt-thread為了方便,直接在核心層提供了一套API,這樣用戶層調用就更加方便。
《新程序員》:云原生和全面數(shù)字化實踐50位技術專家共同創(chuàng)作,文字、視頻、音頻交互閱讀
總結
以上是生活随笔為你收集整理的获取另一个驱动的设备结构体_《rt-thread驱动框架分析》-i2c驱动的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。