1. 程式人生 > 其它 >s3c2440裸機-I2c程式設計-3.i2c程式框架

s3c2440裸機-I2c程式設計-3.i2c程式框架

1.iiC裝置的功能

很顯然,IIC控制器提供了傳輸資料的能力,至於資料有什麼含義,IIC控制器並不知道,資料的含義有外部i2c從裝置,我們需要閱讀晶片手冊,才知道IIC控制器應該發出怎樣的資料。

下圖是AT24cxx的操作方法:

2.I2c程式框架

顯然我們的程式應該分為兩層(IIC裝置層,IIC控制器層),框架如下圖所示:

最上層是i2c_test層,用來對i2c的功能進行測試和驗證。

第2層是i2c裝置層,用來對具體某一型號的從裝置進行i2c讀寫

第3層是通用i2c控制器層,用來提供對具體某一型號的i2c主控進行管理操作

最底層是i2c控制器具體的型號層

在通用i2c控制層,我們提供一個統一的介面i2c_transfer,不關使用哪個晶片,他最終都會呼叫i2c_transfer,來選擇某一款I2C控制器,把資料傳送出去,或者從I2c裝置讀到資料。

對於每一次傳輸的資料都可以用一個i2c_msg結構體來表示。但是,讀某個地址的資料時,就要用兩個i2c_msg結構體來描述它,因為一個i2c_msg結構體只能描述一個傳輸方向(讀/寫),我們讀取ac24ccxx某個地址上的資料時,要先寫出要讀取的地址,然後來讀取裝置地址上的資料。

i2c_test.c檔案

該檔案的內容如下:

void i2c_test(void)
{
        /* 初始化: 選擇I2C控制器 */
        /* 提供選單供測試 */
}

這個選單最終會呼叫到at24cxx.c裡面的函式。

at24cxx.c檔案

在裡面會使用標準的介面i2c_transfer來啟動I2C傳輸。該檔案的內容如下:

#define AT24CXX_ADDR 0x50
int at24cxx_write(unsigned int addr, unsigned char *data, int len)
{
        i2c_msg msg;
        int i;
        int err;
        unsigned char buf[2];

        for (i = 0; i < len; i++)
        {
                buf[0] = addr++;
                buf[1] = data[i];
                
/* 構造i2c_msg */ msg.addr = AT24CXX_ADDR; msg.lags = 0; /* write */ msg.len = 2; msg.buf = buf; msg.err = 0; msg.cnt_transferred = -1; /* 呼叫i2c_transfer */ err = i2c_transfer(&msg, 1); if (err) return err; } return 0; } int at24cxx_read(unsigned int addr, unsigned char *data, int len) { i2c_msg msg[2]; int err; /* 構造i2c_msg */ msg[0].addr = AT24CXX_ADDR; msg[0].lags = 0; /* write */ msg[0].len = 1; msg[0].buf = &addr; msg[0].err = 0; msg[0].cnt_transferred = -1; msg[1].addr = AT24CXX_ADDR; msg[1].lags = 1; /* read */ msg[1].len = len; msg[1].buf = data; msg[1].err = 0; msg[1].cnt_transferred = -1; /* 呼叫i2c_transfer */ err = i2c_transfer(&msg, 2); if (err) return err; return 0; }
View Code

i2c_controller.h檔案

typedef struct i2c_msg {
        unsigned int addr;  /* 7bits */
        int flags;  /* 0 - write, 1 - read */
        int len;
        int cnt_transferred;
        unsigned char *buf;
}i2c_msg, *p_i2c_msg;
typedef struct i2c_controller {
        int (*int)(void);
        int (*master_xfer)(i2c_msg msgs, int num);
        char *name;
}i2c_controller, *p_i2c_controller;

i2c_controller.c檔案

該檔案的內容如下:

#define I2C_CONTROLLER_NUM 10
/* 有一個i2c_controller陣列用來存放各種不同晶片的操作結構體 */
static p_i2c_controller p_i2c_controllers[I2C_CONTROLLER_NUM];
static p_i2c_controller p_i2c_con_selected;

void register_i2c_controller(p_i2c_controller *p)
{
        int i;
        for (i = 0; i < I2C_CONTROLLER_NUM; i++)
        {
                if (!p_i2c_controllers[i])
                {
                        p_i2c_controllers[i] = p;
                        return;
                }
        }
}

/* 根據名字來選擇某款I2C控制器 */
int select_i2c_controller(char *name)
{
        int i;
        for (i = 0; i < I2C_CONTROLLER_NUM; i++)
        {
                if (p_i2c_controllers[i] && !strcmp(name, p_i2c_controllers[i]->name))
                {
                        p_i2c_con_selected = p_i2c_controllers[i];
                        return 0;
                }
        }
        return -1;
}

/* 實現 i2c_transfer 介面函式 */
int i2c_transfer(i2c_msg msgs, int num)
{
        return p_i2c_con_selected->master_xfer(msgs, num);
}

void i2c_init(void)
{
/* 註冊下面的I2C控制器 */
s3c2440_i2c_con_add();
/* 選擇某款I2C控制器 */
select_i2c_controller("s3c2440");
/* 呼叫它的init函式 */
p_i2c_con_selected->init();
}
View Code

有陣列一定有註冊函式register_i2c_controller會把下面實現的I2C控制器結構體i2c_controller放到i2c_controller數組裡面。select_i2c_controller函式根據名字來選擇某款I2C控制器後,以後就會使用被選擇的I2C控制器來啟動傳輸。

s3c2440_i2c_controller.c檔案

中斷服務函式,當發生中斷時,就會呼叫中斷服務函式,程式碼如下(詳細內容見下一節):

void i2c_interrupt_func(int irq)
{
        /* 每傳輸完一個數據將產生一箇中斷 */
        /* 對於每次傳輸, 第1箇中斷是"已經發出了裝置地址" */
}

s3c2440_i2c_con_init函式,用來初始化I2C,控制器程式碼如下:

void s3c2440_i2c_con_init(void)
{
        /* 配置引腳用於I2C*/
        GPECON &= ~((3<<28) | (3<<30));
        GPECON |= ((2<<28) | (2<<30));

        /* 設定時鐘 */
        /* [7] : IIC-bus acknowledge enable bit, 1-enable in rx mode
         * [6] : 時鐘源, 0: IICCLK = fPCLK /16; 1: IICCLK = fPCLK /512
         * [5] : 1-enable interrupt
         * [4] : 讀出為1時表示中斷髮生了, 寫入0來清除並恢復I2C操作
         * [3:0] : Tx clock = IICCLK/(IICCON[3:0]+1).
         * Tx Clock = 100khz = 50Mhz/16/(IICCON[3:0]+1)
         */
        IICCON = (1<<7) | (0<<6) | (1<<5) | (30<<0);

        /* 註冊中斷處理函式 */
        register_irq(27, i2c_interrupt_func);
}
View Code

1).IICCON = (0<<6) | (1<<5) | (30<<0); 設定IICCON控制暫存器。選擇傳送時鐘,使能中斷。設定ACK應答使能,bit[7]。

2).register_irq(27, i2c_interrupt_func):註冊中斷處理函式,當發生I2C中斷的時候就會呼叫i2c_interrupt_func中斷處理函式。

初始化完成後,就可以呼叫do_master_tx寫I2C從機了,這個函式僅僅啟動I2C傳輸,然後等待,直到資料在中斷服務程式中傳輸完畢後再返回。函式程式碼如下:

int do_master_tx(p_i2c_msg msg)
{
        p_cur_msg = msg;
        msg->cnt_transferred = -1;
        msg->err = 0;

        /* 設定暫存器啟動傳輸 */
        /* 1. 配置為 master tx mode */
        IICCON |= (1<<7); /* TX mode, 在ACK週期釋放SDA */
        IICSTAT = (1<<4); /*IIC-bus data output enable/disable(1: Enable Rx/Tx)*/
                
        /* 2. 把從裝置地址寫入IICDS */
        IICDS = msg->addr<<1;//[slave addr [7:1], addr[0] is trans dir]

        /* 3. IICSTAT = 0xf0 (啟動傳輸), slave addr資料即被髮送出去,當到達第9個clk,無論是否有ack, 將導致中斷產生 */
        IICSTAT = 0xf0;

        /* 後續的傳輸由中斷驅動 */
        /* 迴圈等待中斷處理完畢 */
        while (!msg->err && msg->cnt_transferred != msg->len);
        if (msg->err)
                return -1;
        else
                return 0;
}
View Code

1).IICDS = msg->addr<<1: 把從機地址(高7位,所以需要向右移一位)寫入到IICDS暫存器中。

2).IICSTAT = 0xf0:設定IICSTAT暫存器,將s3c2440設為主機發送器,併發出S訊號後,緊接著就發出從機地址。後續的傳輸工作將在中斷服務程式中完成。

do_master_rx函式的實現和do_master_tx函式類似,程式碼如下:

int do_master_rx(p_i2c_msg msg)
{
        p_cur_msg = msg;
        msg->cnt_transferred = -1;
        msg->err = 0;

        /* 設定暫存器啟動傳輸 */
        /* 1. 配置為 Master Rx mode */
        IICCON |= (1<<7); /* RX mode, 在ACK週期迴應ACK */
        IICSTAT = (1<<4);  /*IIC-bus data output enable/disable*/

        /* 2. 把從裝置地址寫入IICDS */
        IICDS = (msg->addr<<1)|(1<<0);

        /* 3. IICSTAT = 0xb0 , 從裝置地址即被髮送出去, 將導致中斷產生 */
        IICSTAT = 0xb0;
        /* 後續的傳輸由中斷驅動 */
        /* 迴圈等待中斷處理完畢 */
        while (!msg->err && msg->cnt_transferred != msg->len);
        if (msg->err)
                return -1;
        else
                return 0;
}
View Code

1).IICDS = (msg->addr<<1)|(1<<0):把從裝置地址寫入IICDS,前7位是從機地址,第8位表示傳輸方向(0表示寫操作,1表示讀操作)。

s3c2440傳輸函式,根據標誌位flags,來指明是讀/寫(1:讀 0:寫)。程式碼如下:

int s3c2440_master_xfer(p_i2c_msg msgs, int num)
{
        int i;
        int err;
        for (i = 0; i < num; i++)        
        {
                if (msgs[i].flags == 0)/* write */
                        err = do_master_tx(&msgs[i]);
                else
                        err = do_master_rx(&msgs[i]);
                if (err)
                        return err;
        }
        return 0;
}

s3c2440_i2c_con_add函式把上面定義的s3c2440_i2c_con結構體註冊到上層的i2c_controller陣列中.

void s3c2440_i2c_con_add(void)
{
        register_i2c_controller(&s3c2440_i2c_con);
}

我們定義一個i2c_controller結構體s3c2440_i2c_con。下面的程式碼對他進行初始化。

static i2c_controller s3c2440_i2c_con = {
        .name = "s3c2440",
        .init = s3c2440_i2c_con_init,
        .master_xfer = s3c2440_master_xfer,
};

框架總結如下: