s3c2440 IIC控制器裸板程式
I2C匯流排只有兩根雙向訊號線。一根是資料線SDA,另一根是時鐘線SCL。
1、匯流排空閒:I2C匯流排空閒的時候,兩條線SDA和SCL都是高電平。
2、開始訊號 S 訊號:SCL 為高電平時,SDA由高電平向低電平跳變,開始傳送資料。
3、結束訊號 P 訊號:SCL 為高電平時,SDA由低電平向高電平跳變,結束傳送資料。
4、響應訊號 ACK:接收器在接收到8位資料後,在第9個時鐘週期,拉低 SDA 電平。
(注意:在第9個時鐘週期,傳送器保持SDA為高,如果有ACK,那麼第9個時鐘週期SDA為 低電平,如果沒有為高電平,傳送器根據電平高低分辨是否有ACK訊號。)
5、正常資料傳輸時:SDA 在 SCL 為低電平時改變,在 SCL 為高電平時保持穩定。
如果使能了IIC中斷,傳送完8bit資料後,主機自動進入中斷處理函式,此時SCL被髮送器拉低,讓接收器被迫等待。恢復傳輸只需要清除中斷掛起。
接收器件收到一個完整的資料位元組後,有可能需要完成一些其它工作,如處理內部中斷服務等,可能無法立刻接收下一個位元組,這時接收器件可以將SCL線拉成低電平,從而使主機處於等待狀態。直到接收器件準備好接收下一個位元組時,再釋放SCL線使之為高電平,從而使資料傳送可以繼續進行。
傳送模式中當傳送了資料時,在 IIC 匯流排資料移位(IICDS)暫存器收到新資料之前 IIC 匯流排介面將會一直等待。在新資料寫入到暫存器之前,SCL 線將會保持為低,然後在其寫入後釋放。S3C2440A 應該等待中斷來確定當前資料傳送的完成。在 CPU 收到中斷請求後,需要再次寫一個新資料到 IICDS 暫存器中。
接收模式中當收到了資料時,在讀取 IICDS 暫存器前 IIC 介面將會一直等待。在新資料讀出前,SCL 線將會保持為低,然後在其讀取後釋放。S3C2440A 應該等待中斷來確定當前資料接收的完成。在 CPU 收到中斷請求後,需要從 IICDS 暫存器中讀取資料。
特別注意:
IIC 匯流排中斷髮生在:
1、當完成了 1 位元組傳送或接收操作;
2、當廣播呼叫或從地址匹配發生時;
3、如果匯流排仲裁失敗。
s3c2440的IIC匯流排控制器:
s3c2440提供4個暫存器來完成所有IIC的操作:
IICDS:
IICADD:
IICCON:ACK訊號使能,傳送模式時鐘源選擇,傳送、接收中斷使能,中斷標記,傳送模式時鐘分頻係數。
IICSTAT:工作模式,忙狀態,發出S訊號,P訊號等,序列輸出使能(IICDS輸出使能),最後一位的狀態(是否接收到ACK)。當發出S訊號後,IICDS暫存器中的資料被自動傳送。
S3C2440A 的 IIC 匯流排介面有 4 種工作模式:
– 主機發送模式
– 主機接收模式
– 從機發送模式
– 從機接收模式
這裡只要介紹主機發送模式和主機接收模式流程:
主機發送模式流程圖:
主機接收模式流程圖:
結合at24cxx的讀寫來學習吧:
at24cxx.c:
實現兩個函式at24cxx_read()和at24cxx_write(),這兩個函式直接提供給使用者操作at24cxx。
#include <string.h> #include "i2c.h" unsigned char at24cxx_read(unsigned char address) { unsigned char val; printf("at24cxx_read address = %d\r\n", address); i2c_write(0xA0, &address, 1); printf("at24cxx_read send address ok\r\n"); i2c_read(0xA0, (unsigned char *)&val, 1); printf("at24cxx_read get data ok\r\n"); return val; } void at24cxx_write(unsigned char address, unsigned char data) { unsigned char val[2]; val[0] = address; val[1] = data; i2c_write(0xA0, val, 2); }
i2c.c:
注意:控制器讀/寫一位元組後,會自動更新下一個讀/寫的地址,所以傳送一次讀/寫地址,可連續讀寫多個數據。
同時注意,EEPROM 介面, Rx 模式中為了產生停止條件在讀取最後資料之前會禁止產生應答。
在次強調一下:IIC 匯流排中斷髮生的時機:1、當完成了 1 位元組傳送或接收操作;2、當廣播呼叫或從地址匹配發生時;3、如果匯流排仲裁失敗。
/*
* FILE: i2c.c
* 用於主機發送/接收
*/
#include <stdio.h>
#include "s3c24xx.h"
#include "i2c.h"
void Delay(int time);
#define WRDATA (1)
#define RDDATA (2)
typedef struct tI2C {
unsigned char *pData; /* 資料緩衝區 */
volatile int DataCount; /* 等待傳輸的資料長度 */
volatile int Status; /* 狀態 */
volatile int Mode; /* 模式:讀/寫 */
volatile int Pt; /* pData中待傳輸資料的位置 */
}tS3C24xx_I2C, *ptS3C24xx_I2C;
static tS3C24xx_I2C g_tS3C24xx_I2C;
/*
* I2C初始化
*/
void i2c_init(void)
{
GPEUP |= 0xc000; // 禁止內部上拉
GPECON |= 0xa0000000; // 選擇引腳功能:GPE15:IICSDA, GPE14:IICSCL
INTMSK &= ~(BIT_IIC);
/* bit[7] = 1, 使能ACK
* bit[6] = 0, IICCLK = PCLK/16
* bit[5] = 1, 使能中斷
* bit[3:0] = 0xf, Tx clock = IICCLK/16
* PCLK = 50MHz, IICCLK = 3.125MHz, Tx Clock = 0.195MHz
*/
IICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); // 0xaf
IICADD = 0x10; // S3C24xx slave address = [7:1]
IICSTAT = 0x10; // I2C序列輸出使能(Rx/Tx)
}
/*
* 主機發送
* slvAddr : 從機地址,buf : 資料存放的緩衝區,len : 資料長度
*/
void i2c_write(unsigned int slvAddr, unsigned char *buf, int len)
{
g_tS3C24xx_I2C.Mode = WRDATA; // 寫操作
g_tS3C24xx_I2C.Pt = 0; // 索引值初始為0
g_tS3C24xx_I2C.pData = buf; // 儲存緩衝區地址
g_tS3C24xx_I2C.DataCount = len; // 傳輸長度
IICDS = slvAddr;
IICSTAT = 0xf0; // 主機發送模式,傳送從機地址,使能序列傳輸,發出S訊號啟動
/* 等待直至資料傳輸完畢 */
while (g_tS3C24xx_I2C.DataCount != -1);
}
/*
* 主機接收
* slvAddr : 從機地址,buf : 資料存放的緩衝區,len : 資料長度
*/
void i2c_read(unsigned int slvAddr, unsigned char *buf, int len)
{
g_tS3C24xx_I2C.Mode = RDDATA; // 讀操作
g_tS3C24xx_I2C.Pt = -1; // 索引值初始化為-1,表示第1箇中斷時不接收資料(地址中斷)
g_tS3C24xx_I2C.pData = buf; // 儲存緩衝區地址
g_tS3C24xx_I2C.DataCount = len; // 傳輸長度
IICDS = slvAddr;
IICSTAT = 0xb0; // 主機接收,啟動
/* 等待直至資料傳輸完畢 */
while (g_tS3C24xx_I2C.DataCount != 0);
}
/*
* I2C中斷服務程式
* 根據剩餘的資料長度選擇繼續傳輸或者結束
*/
void I2CIntHandle(void)
{
unsigned int iicSt,i;
// 清中斷
SRCPND = BIT_IIC;
INTPND = BIT_IIC;
iicSt = IICSTAT;
if(iicSt & 0x8){ printf("Bus arbitration failed\n\r"); }
switch (g_tS3C24xx_I2C.Mode)
{
case WRDATA:
{
if((g_tS3C24xx_I2C.DataCount--) == 0)
{
// 下面兩行用來恢復I2C操作,發出P訊號
IICSTAT = 0xd0;
IICCON = 0xaf;
Delay(10000); // 等待一段時間以便P訊號已經發出
break;
}
//資料的格式是,地址 資料1 資料2 資料3...,對於各個資料的寫地址,控制器會自動+1更新
//想要改為讀模式,需要在發出P訊號之後,發出讀模式地址。
IICDS = g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++];
// 將資料寫入IICDS後,需要一段時間才能出現在SDA線上
for (i = 0; i < 10; i++);
IICCON = 0xaf; // 恢復I2C傳輸
break;
}
case RDDATA:
{
if (g_tS3C24xx_I2C.Pt == -1)
{
// 這次中斷是傳送I2C裝置地址後發生的,沒有資料
// 只接收一個數據時,不要發出ACK訊號
g_tS3C24xx_I2C.Pt = 0;
if(g_tS3C24xx_I2C.DataCount == 1)
IICCON = 0x2f; // 恢復I2C傳輸,開始接收資料,接收到資料時不發出ACK
else
IICCON = 0xaf; // 恢復I2C傳輸,開始接收資料
break;
}
g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++] = IICDS;
g_tS3C24xx_I2C.DataCount--;
if (g_tS3C24xx_I2C.DataCount == 0)
{
// 下面兩行恢復I2C操作,發出P訊號
IICSTAT = 0x90;
IICCON = 0xaf;
Delay(10000); // 等待一段時間以便P訊號已經發出
break;
}
else
{
// 接收最後一個數據時,不要發出ACK訊號
if(g_tS3C24xx_I2C.DataCount == 1)
IICCON = 0x2f; // 恢復I2C傳輸,接收到下一資料時無ACK
else
IICCON = 0xaf; // 恢復I2C傳輸,接收到下一資料時發出ACK
}
break;
}
default:
break;
}
}
/*
* 延時函式
*/
void Delay(int time)
{
for (; time > 0; time--);
}
本文到此為止,謝謝閱讀。