返回文章列表

USB裝置驅動與I2C匯流排整合應用

本文探討如何整合USB裝置驅動程式與I2C匯流排,以LTC3206與PIC32MX470為例,詳細說明驅動程式的初始化、設定、核心程式碼解析、I2C傳輸方法實作,以及在Raspberry Pi上的示範與驗證,包含程式碼範例和逐步操作說明,讓讀者瞭解如何透過USB介面控制I2C裝置。

嵌入式系統 驅動程式開發

Linux 核心中的 USB 裝置驅動程式開發涉及 USB 子系統和核心模組的理解。本文以 LTC3206 Demo Board 為例,講解如何開發 USB 到 I2C 的橋接驅動程式,實作對外部 I2C 裝置的控制。驅動程式核心功能包含 I2C 訊息傳輸處理與 USB 裝置初始化。I2C 訊息傳輸透過 ltc3206_usb_i2c_xfer 函式實作,該函式負責將多個 I2C 訊息透過 USB 介面傳送至 LTC3206。USB 裝置初始化則在 ltc3206_probe 函式中完成,包含組態記憶體、設定 USB 端點、初始化 LTC3206,以及註冊 I2C 介面卡。文章也提供程式碼片段說明 I2C 寫入操作、USB 中斷輸出 URB 的完成回呼函式等關鍵部分,並以 Raspberry Pi 與 PIC32MX470 Curiosity 開發板的實際操作案例,展示如何載入驅動模組、檢查 I2C 介面卡,並透過 I2C 指令控制 LED 亮度,驗證驅動程式的功能。

USB裝置驅動程式與I2C匯流排的整合應用

本章節將探討USB裝置驅動程式的開發,特別是如何將USB與I2C匯流排結合,以實作對外部裝置的有效控制。文中將以LTC3206 DC749A Demo Board為例,詳細說明驅動程式的實作細節。

驅動程式初始化與設定

首先,我們需要開啟I2C驅動程式並取得其控制程式碼,以進行後續的資料傳輸。

appData.drvI2CHandle_Master = DRV_I2C_Open(DRV_I2C_INDEX_0, DRV_IO_INTENT_WRITE);
if (appData.drvI2CHandle_Master == (DRV_HANDLE)NULL)
{
    appInitialized = false;
}

內容解密:

  1. DRV_I2C_Open函式用於開啟I2C驅動程式。
  2. DRV_I2C_INDEX_0指定了要開啟的I2C驅動程式索引。
  3. DRV_IO_INTENT_WRITE表示開啟I2C驅動程式的目的是進行寫入操作。
  4. 若傳回的控制程式碼為NULL,則表示開啟失敗,將appInitialized設為false

接著,我們需要開啟USB裝置層,以實作USB裝置的功能。

if (appData.handleUsbDevice == USB_DEVICE_HANDLE_INVALID)
{
    appData.handleUsbDevice = USB_DEVICE_Open(USB_DEVICE_INDEX_0, DRV_IO_INTENT_READWRITE);
    if(appData.handleUsbDevice != USB_DEVICE_HANDLE_INVALID)
    {
        appInitialized = true;
    }
    else
    {
        appInitialized = false;
    }
}

內容解密:

  1. USB_DEVICE_Open函式用於開啟USB裝置。
  2. USB_DEVICE_INDEX_0指定了要開啟的USB裝置索引。
  3. DRV_IO_INTENT_READWRITE表示開啟USB裝置的目的是進行讀寫操作。
  4. 若傳回的控制程式碼非USB_DEVICE_HANDLE_INVALID,則表示開啟成功,將appInitialized設為true

驅動程式核心程式碼解析

  1. 標頭檔引入與ID表格建立

首先,我們需要引入必要的標頭檔,並建立ID表格以支援熱插拔功能。

#include <linux/module.h>
#include <linux/slab.h>
#include <linux/usb.h>
#include <linux/i2c.h>

#define USBLED_VENDOR_ID 0x04D8
#define USBLED_PRODUCT_ID 0x003F

static const struct usb_device_id id_table[] = {
    { USB_DEVICE(USBLED_VENDOR_ID, USBLED_PRODUCT_ID) },
    { }
};
MODULE_DEVICE_TABLE(usb, id_table);

內容解密:

  1. 引入必要的Linux核心模組開發標頭檔。

  2. 定義USB裝置的Vendor ID和Product ID。

  3. 建立usb_device_id表格,以匹配特定的USB裝置。

  4. 使用MODULE_DEVICE_TABLE巨集註冊該ID表格。

  5. 私有資料結構定義

接下來,我們定義一個私有資料結構來儲存驅動程式的相關資料。

struct i2c_ltc3206 {
    u8 obuffer[LTC3206_OUTBUF_LEN]; 
    u8 user_data_buffer[LTC3206_I2C_DATA_LEN]; 
    int ep_out; 
    struct usb_device *usb_dev; 
    struct usb_interface *interface; 
    struct i2c_adapter adapter; 
    wait_queue_head_t usb_urb_completion_wait;
    bool ongoing_usb_ll_op; 
    struct urb *interrupt_out_urb; 
};

內容解密:

  1. 該結構儲存了與USB和I2C通訊相關的資料。

  2. obufferuser_data_buffer分別用於儲存USB寫入緩衝區和I2C資料緩衝區。

  3. ep_out儲存了輸出端點的位址。

  4. usb_devinterface分別指向對應的USB裝置和介面。

  5. adapter用於I2C介面卡的操作。

  6. probe()函式實作

當USB裝置被插入時,probe()函式將被呼叫,以初始化相關的資料結構和組態。

static int ltc3206_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    struct usb_host_interface *hostif = interface->cur_altsetting;
    struct i2c_ltc3206 *dev; 

    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    
    dev->ep_out = hostif->endpoint[1].desc.bEndpointAddress;
    dev->usb_dev = usb_get_dev(interface_to_usbdev(interface));
    dev->interface = interface;
    
    init_waitqueue_head(&dev->usb_urb_completion_wait);
    
    usb_set_intfdata(interface, dev);
    
    dev->adapter.owner = THIS_MODULE;
    dev->adapter.class = I2C_CLASS_HWMON;
    dev->adapter.algo = &ltc3206_usb_algorithm;
    i2c_set_adapdata(&dev->adapter, dev);
    
    dev->adapter.dev.parent = &dev->interface->dev;
    
    ltc3206_init(dev);
    
    i2c_add_adapter(&dev->adapter);
    return 0;
}

內容解密:

  1. 組態並初始化私有資料結構。
  2. 設定輸出端點位址、取得USB裝置和介面的參考。
  3. 初始化等待佇列以處理URB完成的等待事件。
  4. 設定I2C介面卡的相關屬性,並將其註冊到I2C子系統中。

I2C傳輸方法的實作

我們需要實作I2C傳輸方法,以支援從使用者空間到I2C裝置的資料傳輸。

static const struct i2c_algorithm ltc3206_usb_algorithm = {
    .master_xfer = ltc3206_usb_i2c_xfer,
    .functionality = ltc3206_usb_func,
};

內容解密:

  1. 定義了一個i2c_algorithm結構體,用於描述I2C傳輸的方法。
  2. .master_xfer成員指向了實際執行I2C傳輸的函式ltc3206_usb_i2c_xfer

USB 裝置驅動程式開發:以 LTC3206 為例

在 Linux 核心中開發 USB 裝置驅動程式是一項複雜的任務,需要深入瞭解 USB 子系統、核心模組以及裝置特定的通訊協定。本文將以 LTC3206 為例,介紹如何開發一個 USB 到 I2C 的橋接驅動程式。

驅動程式初始化

首先,我們需要定義驅動程式的結構和初始化函式。在這個例子中,我們定義了一個 struct i2c_ltc3206 結構來儲存裝置的相關資訊,包括 USB 裝置、介面、I2C 配接器等。

內容解密:

struct i2c_ltc3206 {
    u8 obuffer[LTC3206_OUTBUF_LEN]; /* USB 寫入緩衝區 */
    u8 user_data_buffer[LTC3206_I2C_DATA_LEN]; /* I2C/SMBus 資料緩衝區 */
    int ep_out; /* 輸出端點 */
    struct usb_device *usb_dev; /* USB 裝置 */
    struct usb_interface *interface; /* USB 介面 */
    struct i2c_adapter adapter; /* I2C 配接器 */
    wait_queue_head_t usb_urb_completion_wait; /* 等待佇列 */
    bool ongoing_usb_ll_op; /* 是否正在進行 USB 低階操作 */
    struct urb *interrupt_out_urb; /* 中斷輸出 URB */
};

ltc3206_init 函式中,我們初始化了 LTC3206 裝置,包括分配和初始化中斷輸出 URB。

程式碼解析

static int ltc3206_init(struct i2c_ltc3206 *dev)
{
    int ret;
    
    /* 初始化 LTC3206 */
    dev_info(&dev->interface->dev,
             "LTC3206 at USB bus %03d address %03d -- ltc3206_init()\n",
             dev->usb_dev->bus->busnum, dev->usb_dev->devnum);
    
    /* 分配中斷輸出 URB */
    dev->interrupt_out_urb = usb_alloc_urb(0, GFP_KERNEL);
    if (!dev->interrupt_out_urb) {
        ret = -ENODEV;
        goto init_error;
    }
    
    /* 初始化中斷輸出 URB */
    usb_fill_int_urb(dev->interrupt_out_urb, dev->usb_dev,
                     usb_sndintpipe(dev->usb_dev, dev->ep_out),
                     (void *)&dev->obuffer, LTC3206_OUTBUF_LEN,
                     ltc3206_usb_cmpl_cbk, dev,
                     1);
    
    ret = 0;
    goto init_no_error;
    
init_error:
    dev_err(&dev->interface->dev, "ltc3206_init: Error = %d\n", ret);
    return ret;
    
init_no_error:
    dev_info(&dev->interface->dev, "ltc3206_init: Success\n");
    return ret;
}

I2C 寫入操作

ltc3206_i2c_write 函式中,我們實作了 I2C 寫入操作,將資料從 I2C 訊息複製到 USB 寫入緩衝區。

程式碼解析

static int ltc3206_i2c_write(struct i2c_ltc3206 *dev, struct i2c_msg *pmsg)
{
    u8 ucXferLen;
    int rv;
    u8 *pSrc, *pDst;
    
    if (pmsg->len > LTC3206_I2C_DATA_LEN) {
        pr_info("problem with the length\n");
        return -EINVAL;
    }
    
    /* I2C 寫入長度 */
    ucXferLen = (u8)pmsg->len;
    pSrc = &pmsg->buf[0];
    pDst = &dev->obuffer[0];
    
    // ...
}

USB 中斷輸出 URB 的完成回呼函式

ltc3206_usb_cmpl_cbk 函式中,我們處理了中斷輸出 URB 的完成事件,包括檢查 URB 狀態、重新提交 URB 等。

程式碼解析

static void ltc3206_usb_cmpl_cbk(struct urb *urb)
{
    struct i2c_ltc3206 *dev = urb->context;
    int status = urb->status;
    int retval;
    
    switch (status) {
    case 0: /* 成功 */
        break;
    case -ECONNRESET: /* 連線重設 */
    case -ENOENT:
    case -ESHUTDOWN:
        return;
        /* -EPIPE: 應該清除 halt */
    default: /* 錯誤 */
        goto resubmit;
    }
    
    /*
     * 喚醒等待函式。
     * 修改標誌表示低階狀態
     */
    dev->ongoing_usb_ll_op = 0; /* 通訊正常 */
    wake_up_interruptible(&dev->usb_urb_completion_wait);
    return;
    
resubmit:
    retval = usb_submit_urb(urb, GFP_ATOMIC);
    if (retval) {
        dev_err(&dev->interface->dev,
                "ltc3206(irq): can't resubmit interrupt urb, retval %d\n",
                retval);
    }
}

USB裝置驅動程式章節13 - LTC3206 USB-I2C橋接器驅動實作與示範

本章節探討LTC3206 USB-I2C橋接器驅動程式的開發與應用,涵蓋從驅動程式設計到實際硬體整合的完整流程。文中詳細介紹了驅動程式的核心功能實作、USB裝置的識別與初始化、I2C匯流排操作,以及如何透過Raspberry Pi與PIC32MX470 Curiosity開發板進行實測驗證。

驅動程式核心功能實作

I2C訊息傳輸處理

驅動程式中的ltc3206_usb_i2c_xfer函式負責處理I2C訊息的傳輸,其主要任務是將多個I2C訊息透過USB介面傳送至LTC3206裝置。此函式的實作邏輯如下:

static int ltc3206_usb_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
{
    struct i2c_ltc3206 *dev = i2c_get_adapdata(adap);
    struct i2c_msg *pmsg;
    int ret, count;

    pr_info("number of i2c msgs is = %d\n", num);
    for (count = 0; count < num; count++) {
        pmsg = &msgs[count];
        ret = ltc3206_i2c_write(dev, pmsg);
        if (ret < 0)
            goto abort;
    }
    ret = num;
abort:
    return ret;
}

內容解密:

  1. 此函式接收I2C介面卡(i2c_adapter)、I2C訊息陣列(i2c_msg)和訊息數量(num)作為輸入引數。
  2. 透過迴圈遍歷每個I2C訊息,並呼叫ltc3206_i2c_write函式將訊息寫入LTC3206裝置。
  3. 若寫入過程中發生錯誤,則立即跳至abort標籤傳回錯誤碼。
  4. 若所有訊息均成功傳輸,則傳回傳輸的訊息數量。

USB裝置初始化與設定

ltc3206_probe函式中,驅動程式會對USB裝置進行初始化和設定:

static int ltc3206_probe(struct usb_interface *interface, const struct usb_device_id *id)
{
    struct usb_host_interface *hostif = interface->cur_altsetting;
    struct i2c_ltc3206 *dev;
    int ret;

    // 組態USB裝置並註冊I2C介面卡
    dev = kzalloc(sizeof(*dev), GFP_KERNEL);
    if (!dev) {
        ret = -ENOMEM;
        goto error;
    }

    // 初始化LTC3206裝置
    ret = ltc3206_init(dev);
    if (ret < 0) {
        dev_err(&interface->dev, "failed to initialize adapter\n");
        goto error_init;
    }

    // 新增I2C介面卡
    ret = i2c_add_adapter(&dev->adapter);
    if (ret < 0) {
        dev_info(&interface->dev, "failed to add I2C adapter\n");
        goto error_i2c;
    }

    return 0;

error_init:
    usb_free_urb(dev->interrupt_out_urb);
error_i2c:
    usb_set_intfdata(interface, NULL);
    ltc3206_free(dev);
error:
    return ret;
}

內容解密:

  1. 此函式在USB裝置被識別時呼叫,負責組態和初始化裝置。
  2. 組態過程中包括分配記憶體、設定USB端點、初始化LTC3206裝置,以及註冊I2C介面卡。
  3. 若初始化或註冊過程中發生錯誤,則進行相應的錯誤處理和資源釋放。

示範與驗證

本文展示如何在Raspberry Pi上載入LTC3206驅動模組,並透過I2C指令控制連線的PIC32MX470 Curiosity開發板上的LED亮度。

  1. 載入驅動模組:使用insmod指令載入編譯好的usb_ltc3206.ko模組。

    root@raspberrypi:/home# insmod usb_ltc3206.ko
    
  2. 檢查I2C介面卡:使用i2cdetect -l指令檢視系統中的I2C介面卡。載入模組後,應能看到新增的I2C介面卡。

    root@raspberrypi:/home# i2cdetect -l
    i2c-1   i2c         bcm2835 (i2c@7e804000)              I2C adapter
    i2c-11  i2c         usb-ltc3206 at bus 001 device 004   I2C adapter
    
  3. 控制LED亮度:透過i2cset指令向LTC3206裝置傳送指令,以控制PIC32MX470板上的LED亮度。

    root@raspberrypi:/sys/bus/usb/devices/1-1.3:1.0# i2cset -y 11 0x1b 0x00 0xf0 0x00 i
    

內容解密:

  1. 此指令透過I2C介面卡11(對應LTC3206裝置)向位址0x1b的裝置傳送控制指令。
  2. 指令引數(0x00、0xf0、0x00)用於設定LTC3206裝置的輸出,從而控制連線的LED亮度。