i2c抽象/模擬i2c
1.寫在前面
i2c匯流排是由PHILIPS公司開發的一種簡單、雙向二線制同步序列匯流排。關於i2c的使用,並不陌生,C51、ARM、MSP430等,都基本整合硬體i2c,或者不整合i2c的,可以根據匯流排時序圖使用普通IO口翻轉模擬一根i2c匯流排。對於當下流行的stm32飽受詬病的硬體i2c,相信很多人都是使用模擬i2c。模擬i2c的原始碼比較多,大多都是大同小異,對於各類例程,提供的模擬i2c似乎都不是太規範(個人見解),特別是一根i2c匯流排掛多個外設、模擬多根i2c匯流排、以及更換一個i2c外設時,都需要大幅度修改原始碼、複製原始碼、重新除錯時序等重複的工作。在看過RT-Thread的驅動框架(包括其提供的模擬i2c)、Linux裝置驅動框架,覺得分層就特別好,完美解決了上述提及的問題。參考RT-Thread的模擬i2c,整理修改在裸機上使用。下面“從上到下”講述基本實現。
2.Linux、RT-Thread裝置驅動模型
1) 匯流排驅動與外設驅動分離,方便一根匯流排掛多個外設,方便移植;
2) 底層(與硬體相關)與上層分離,方便新增匯流排及移植到不同處理器,移植到其他處理器,只需重新實現硬體相關的“暫存器”層即可;
3.MCU下裸機形式i2c匯流排抽象
此部分實現原始碼為:i2c_core.c i2c_core.h
1)i2c匯流排抽象對外介面(API)
“i2c_bus_xfer”為i2c封裝對外的API,函式原型如下,提供一個函式模型,具體需要例項化函式指標。
int i2c_bus_xfer(struct i2c_dev_device *dev,struct i2c_dev_message msgs[],unsigned int num) { int size; size = dev->xfer(dev,msgs,num); return size; }
a)此函式即作為驅動外設的對外介面,所有操作通過此函式介面,與底層匯流排實現分離,如EEPROM、RTC、溫度感測器等;
b)一個對外函式已經實現90%的情況使用,對應一些特殊情況,後期再完善或增加API。
a)struct i2c_dev_device *i2c_dev
2)i2c匯流排抽象API引數
a)i2c_dev:i2c裝置指標,型別為“struct i2c_dev_device”,驅動一個i2c外設時,首先要對此指標裝置初始化;
b)msgs:i2c一幀資料,傳送資料及存放返回資料的快取;
c)num:資料幀數量。
3)struct i2c_dev_device
該結構體為關鍵,呼叫API驅動外設時,首先對此初始化(類似於Linux/RT-Thread註冊裝置)。完整的裝置包括兩部分,資料操作函式和i2c相關資訊(如硬體i2c或者模擬i2c)。因此“struct i2c_dev_device”的原型為:
struct i2c_dev_device
{
int (*xfer)(struct i2c_dev_device *dev,struct i2c_dev_message msgs[],unsigned int num);
void *i2c_phy;
};
a)第一個引數是函式指標,資料收發通過此函式指標呼叫實體函式實現;
b)第二個引數是一個void指標,初始化時指向我們使用的物理i2c(硬體/模擬),使用時可強制轉換為對應的型別。
4)xfer
該函式與i2c匯流排裝置對外介面函式“i2c_bus_xfer”具有相同的引數,形參引數參考此項的第2點,初始化時例項化指向實體函式。
5)struct i2c_dev_message
“struct i2c_dev_message”為i2c匯流排訪問外設的一幀資料資訊,包括髮送資料、外設從地址、訪問標識等。原型如下:
struct i2c_dev_message
{
unsigned short addr;
unsigned short flags;
unsigned short size;
unsigned char *buff;
unsigned char retries;
};
a)addr:i2c外設從機地址,常用為7位,10位較少用;
b)flags:標識,傳送、接收、應答、地址位選擇等標識;幾種標識如下:
#define I2C_BUS_WR 0x0000
#define I2C_BUS_RD (1u << 0)
#define I2C_BUS_ADDR_10BIT (1u << 2)
#define I2C_BUS_NO_START (1u << 4)
#define I2C_BUS_IGNORE_NACK (1u << 5)
#define I2C_BUS_NO_READ_ACK (1u << 6)
c)size:傳送的資料大小,或者接收的快取大小;
d)buff:快取區;
e)retries:i2c啟動失敗時,重啟的次數。
4.模擬i2c抽象
對於模擬i2c,在以往的實現方式中,基本是時序圖和外設程式碼混合在一起,增加外設或者使用新的i2c外設時,需要對模擬2ic程式碼進行較大工作量的修改,或者以“複製”的方式實現一套新的i2c匯流排。但同理,可以把模擬i2c時序部分程式碼抽象出來,以“複用”程式碼的形式實現。此部分實現原始碼為:i2c_bitops.c i2c_bitops.h
1)模擬i2c抽象對外介面
根據上述封裝的對外API,使用時,首先需要實現入口引數“i2c_dev”例項化,用模擬i2c即是呼叫模擬i2c相關介面。
int i2c_bitops_bus_xfer(struct ops_i2c_dev *i2c_bus,struct i2c_dev_message msgs[],unsigned long num)
{
struct i2c_dev_message *msg;
unsigned long i;
unsigned short ignore_nack;
int ret;
ignore_nack = msg->flags & I2C_BUS_IGNORE_NACK;
i2c_bitops_start(i2c_bus);
for (i = 0; i < num; i++)
{
msg = &msgs[i];
if (!(msg->flags & I2C_BUS_NO_START))
{
if (i)
{
i2c_bitops_restart(i2c_bus);
}
ret = i2c_bitops_send_address(i2c_bus,msg);
if ((ret != 0) && !ignore_nack)
goto out;
}
if (msg->flags & I2C_BUS_RD)
{//read
ret = i2c_bitops_bus_read(i2c_bus,msg);
if(ret < msg->size)
{
ret = -1;
goto out;
}
}
else
{//write
ret = i2c_bitops_bus_write(i2c_bus,msg);
if(ret < msg->size)
{
ret = -1;
goto out;
}
}
}
ret = i;
out:
i2c_bitops_stop(i2c_bus);
return ret;
}
int ops_i2c_bus_xfer(struct i2c_dev_device *i2c_dev,struct i2c_dev_message msgs[],unsigned int num)
{
return(i2c_bitops_bus_xfer((struct ops_i2c_dev*)(i2c_dev->i2c_phy),msgs,num));
}
a)模擬一根i2c匯流排時,對外的操作函式都通過上訴函式;i2c資訊幀相關引數由上層呼叫傳遞進入,此處主要增加“struct ops_i2c_dev”的封裝;
b)該函式使用到的函,其中入口引數為“struct ops_i2c_dev”型別的都是模擬i2c相關;
d)模擬i2c封裝實現主要針對“struct ops_i2c_dev”原型的例項化。
2)struct ops_i2c_dev
“struct ops_i2c_dev”原型如下:
struct ops_i2c_dev
{
void (*set_sda)(int8_t state);
void (*set_scl)(int8_t state);
int8_t (*get_sda)(void);
int8_t (*get_scl)(void);
void (*delayus)(uint32_t us);
};
a)set_sda:資料線輸出;
b)set_scl:時鐘線輸出;
c)get_sda:資料線輸入(捕獲);
d)get_scl:時鐘線輸入(捕獲);
e)delayus:延時函式;
要實現一個模擬i2c,只需將上訴函式指標的實體實現即可,具體看後面描述。
3)模擬i2c時序
以產生i2c起始訊號函式為例子,簡要分析:
static voidi2c_bitops_start(struct ops_i2c_dev *i2c_bus)
{
i2c_bus->set_sda(0);
i2c_bus->delayus(3);
i2c_bus->set_scl(0);
}
入口引數為struct ops_i2c_dev *i2c_bus,其實就是i2c_bitops_bus_xfer應用層函式傳入的引數,最終是在此呼叫,底層需要實現的就是io模擬的輸入/輸出狀態函式。
其他函式,如
static void i2c_bitops_restart(struct ops_i2c_dev *i2c_bus)
static chari2c_bitops_wait_ack(struct ops_i2c_dev *i2c_bus)
static int i2c_bitops_send_byte(struct ops_i2c_dev*i2c_bus,unsigned char data)
等等,入口引數都是i2c_bus,時序實現與常規裸機程式設計是一致的,不同的是函式指標的分離呼叫,具體看附件原始碼。
4)標識位
在以往的模擬i2c或者硬體i2c中,操作外設時都有各類情況,如讀和寫方向的切換、連續操作(不需啟動i2c匯流排,如寫EEPROM,先寫地址再寫資料)等。對於這類情況,我們處理辦法是選擇相關的巨集標識即可,具體實現由“中間層”實現,讓i2c外設驅動起來更簡單!以上述對外函式為例:
a)通過標識位判斷是讀還是寫狀態
if (msg->flags & I2C_BUS_RD)
{//read
ret = i2c_bitops_bus_read(i2c_bus,msg);
if(ret < msg->size)
{
ret = -1;
goto out;
}
}
b)應答狀態標識
ignore_nack = msg->flags & I2C_BUS_IGNORE_NACK;
5)讀寫函式
讀寫函式最終是通過io口1bit的翻轉模擬出時序,從而獲得資料,這部分與常規模擬i2c一致,通過函式指標方式操作。主要實現介面函式:
static unsigned long i2c_bitops_bus_write(struct ops_i2c_dev *i2c_bus,struct i2c_dev_message *msg);
static unsigned long i2c_bitops_bus_read(struct ops_i2c_dev *i2c_bus,struct i2c_dev_message *msg);
5.模擬i2c匯流排實現
此部分實現原始碼為:i2c_hw.c i2c_hw.h
以stm32f1為硬體平臺,採用上述模擬i2c封裝,實現一根模擬i2c匯流排。
1)實現struct ops_i2c_dev函式實體
除了“delayus”函式外,其餘為io翻轉,以“set_sda”和“delayus”為例,實現如下:
static void gpio_set_sda(int8_t state)
{
if (state)
I2C1_SDA_PORT->BSRR = I2C1_SDA_PIN;
else
I2C1_SDA_PORT->BRR = I2C1_SDA_PIN;
}
static void gpio_delayus(uint32_t us)
{
#if 0
volatile int32_t i;
for (; us > 0; us--)
{
i = 30; //mini 17
while(i--);
}
#else
Delayus(us);
#endif
}
a)為例提高速率,上訴程式碼採用暫存器方式操作,可以用庫函式操作io口;
b)延時可以用硬體定時器延時,或者軟體延時,具體根據cpu時鐘計算;
c)其他原始碼看附件中“i2c_hw.c”
2)初始化一根模擬i2c匯流排
void stm32f1xx_i2c_init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStructure.GPIO_Pin = I2C1_SDA_PIN | I2C1_SCL_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(I2C1_SDA_PORT, &GPIO_InitStructure);
I2C1_SDA_PORT->BSRR = I2C1_SDA_PIN;
I2C1_SCL_PORT->BSRR = I2C1_SCL_PIN;
//device init
ops_i2c1_dev.set_sda = gpio_set_sda;
ops_i2c1_dev.get_sda = gpio_get_sda;
ops_i2c1_dev.set_scl = gpio_set_scl;
ops_i2c1_dev.get_scl = gpio_get_scl;
ops_i2c1_dev.delayus = gpio_delayus;
i2c1_dev.i2c_phy = &ops_i2c1_dev;
i2c1_dev.xfer = ops_i2c_bus_xfer;
}
a)i2c io初始化;
b)i2c裝置例項化,其中“ops_i2c1_dev”和“i2c1_dev”即是我們定義的匯流排裝置,後面使用該匯流排時主要通過“i2c1_dev”實現對底層的呼叫。
6.驅動EEPROM(AT24C16)
此部分實現原始碼為:24clxx.c 24clxx.h
上面匯流排完成後,驅動一個i2c外設可以說就是信手拈來的事情了,而且模擬i2c匯流排抽象出來後,不需在做重複除錯時序的工作。
假設初始化的i2c裝置為i2c1_dev。
1) 寫EEPROM。寫一個位元組,頁寫演算法詳細見原始碼附件(24clxx.c):
char ee_24clxx_writebyte(u16 addr,u8 data)
{
struct i2c_dev_message ee24_msg[1];
u8 buf[3];
u8 slave_addr;
if(EEPROM_MODEL > 16)
{
slave_addr =EE24CLXX_SLAVE_ADDR;
buf[0] = (addr >>8)& 0xff;
buf[1] = addr & 0xff;
buf[2] = data;
ee24_msg[0].size = 3;
}
else
{
slave_addr = EE24CLXX_SLAVE_ADDR | (addr>>8);
buf[0] = addr & 0xff;
buf[1] = data;
ee24_msg[0].size = 2;
}
ee24_msg[0].addr = slave_addr;
ee24_msg[0].flags = I2C_BUS_WR;
ee24_msg[0].buff = buf;
i2c_bus_xfer(&i2c1_dev,ee24_msg,1);
return 0;
}
2)讀EEPROM
voidee_24clxx_readbytes(u16 read_ddr, char* pbuffer, u16 read_size)
{
struct i2c_dev_message ee24_msg[2];
u8 buf[2];
u8 slave_addr;
if(EEPROM_MODEL > 16)
{
slave_addr =EE24CLXX_SLAVE_ADDR;
buf[0] = (read_ddr>>8)& 0xff;
buf[1] = read_ddr& 0xff;
ee24_msg[0].size = 2;
}
else
{
slave_addr =EE24CLXX_SLAVE_ADDR | (read_ddr>>8);
buf[0] = read_ddr & 0xff;
ee24_msg[0].size = 1;
}
ee24_msg[0].buff = buf;
ee24_msg[0].addr = slave_addr;
ee24_msg[0].flags = I2C_BUS_WR;
ee24_msg[1].addr = slave_addr;
ee24_msg[1].flags = I2C_BUS_RD;
ee24_msg[1].buff = (u8*)pbuffer;
ee24_msg[1].size = read_size;
i2c_bus_xfer(&i2c1_dev,ee24_msg,2);
}
3)注意事項
驅動一個外設相對容易了,注意的事項就是標識位部分。
a)此處外設地址(addr),是實際地址,不含讀寫位(7bit),比如AT24C16外設地址為0x50,可能大家平常用的是0xA0,因為包括讀寫位;
b)寫資料時,如果以2幀i2c_dev_message訊息傳送,需要注意“I2C_BUS_NO_START”巨集,此巨集標識意思是不需要再次啟動i2c了,一般看i2c外設手冊時序圖可知道。如寫EEPROM是先寫地址,然後寫資料這個過程是連續的,此時就需用到“I2C_BUS_NO_START”標識。程式可改成這樣:
char ee_24clxx_writebyte(u16 addr,u8 data)
{
struct i2c_dev_message ee24_msg[2];
u8 buf[2];
u8 slave_addr;
if(EEPROM_MODEL > 16)
{
slave_addr =EE24CLXX_SLAVE_ADDR;
buf[0] = (addr>>8)& 0xff;
buf[1] = addr &0xff;
ee24_msg[0].size = 2;
}
else
{
slave_addr =EE24CLXX_SLAVE_ADDR | (addr>>8);
buf[0] = addr &0xff;
ee24_msg[0].size = 1;
}
ee24_msg[0].addr = slave_addr;
ee24_msg[0].flags = I2C_BUS_WR;
ee24_msg[0].buff = buf;
ee24_msg[1].addr = slave_addr;
ee24_msg[1].flags = I2C_BUS_WR |I2C_BUS_NO_START;
ee24_msg[1].buff = &data;
ee24_msg[1].size = 1;
i2c_bus_xfer(&i2c1_dev,ee24_msg,2);
return 0;
}
4)其他
理解之後,或者使用過Linux、RT-Thread的驅動框架的,再驅動其他i2c外設,就是很容易的事情了,剩下的就是配置暫存器、應用演算法的問題了。
7.總結
1)整體思路比較易理解,本質就是函式指標,將與硬體底層無關的部分抽象出來,相關聯的地方分層明確,通過函式指標的方式進行呼叫。
2)事務分離,通用、重複的事情交給匯流排處理,特殊任務留給外設驅動。
8.相關例子
1)LM75A溫度感測器使用:
2)LP55231 LED驅動使用:
9.原始碼
10.參考
相關推薦
i2c抽象/模擬i2c
1.寫在前面 i2c匯流排是由PHILIPS公司開發的一種簡單、雙向二線制同步序列匯流排。關於i2c的使用,並不陌生,C51、ARM、MSP430等,都基本整合硬體i2c,或者不整合i2c的,可以根據匯流排時序圖使用普通IO口翻轉模擬一根i2c匯流排。對於當下流行的stm3
STM32F103利用模擬I2C驅動ADS1115
ADS1115通過模擬I2C驅動:(部分程式碼借鑑了網路上的幾個,並且根據引腳進行了配置,都沒有執行成功,今天調了一天,終於在晚上調了出來) 注意:本部分程式碼需要只是ADS1115的部分程式(一些用到的陣列在此沒有寫),模擬II2C的各程式並未給出,大家根據需要進行裁剪,程式碼完全開源,希
微控制器GPIO軟體模擬I2C通訊程式
文章原始地址:http://feotech.com/?p=98 本程式主要用於使用可程式設計晶片自身的IO管腳,模擬I2C通訊的協議,實現I2C匯流排資料的傳輸. /** ****************************************************
GPIO實現I2C協議模擬(1)
最近需要用GPIO模擬I2C協議,如果是在Linux下面比較簡單,但在Windows下面,是否有沒Linux那麼簡單了。 索性自己對I2C協議還有一些瞭解,翻了SPEC並結合示波器量出的實際訊號分析,總算有些成就,在這裡以做記錄 I2C是什麼,這裡不做介紹,網上資料太多. 要用GPIO模擬I2C協議,就是控制
GPIO模擬I2C通訊協議(一)
概要: 從本節開始,我將用3-5篇部落格的篇幅對我為期3個月的本科實習做總結。本節將首先介紹I2C協議的基本時序,然後給出用GPIO模擬實現I2C功能的C程式碼。最後介紹驅動開發的一些思路。 關鍵字
linux核心GPIO模擬I2C例項(轉)
前言: 在許多情況下,我們並沒有足夠的I2C匯流排,本文主在介紹如何利用Linux核心中的i2c-gpio模組,利用2條GPIO線模擬i2c匯流排,並掛載裝置。 思路: 先通過對i2c-gpio所定義的結構體初始化(包括初始化i2c的2條線,頻率
開啟linux核心自帶的模擬i2c-gpio模組過程
首先我們要知道核心的makefile是根據配置檔案,也就是kconfig來決定是否編譯一個檔案的。 如果沒有配置核心編譯它,就不會生成.o檔案。 自然就不會開啟這個模組了。 如下是Kconfig 檔
STM32F1 模擬I2C驅動DAC(LTC2605)程式
由於在電路設計上將SCL、SDA線拉太長導致硬體I2C時常出問題,所以考慮用模擬I2C解決,這裡參考別人的程式碼然後自己應用到24位DAC(LTC2605)上,已移植成功。 (1).simulate_i2c.c #include "stm32f10x.h" #include "simulat
STM8 模擬I2C驅動0.91寸OLED屏
#define OLED_SCLK_Clr() GPIO_ResetBits(I2C_PORT, I2C_SDA_PIN )//SDA IIC介面的時鐘訊號 #define OLED_SCLK_Set() GPIO_SetBits(I2C_PORT, I2C_SDA_PIN
STM32 IO口模擬I2C+驅動MPU6050
之後運用MPU6050做了一個平衡小車,可以通過藍芽控制,下載連結: diy平衡小車 一、MPU6050 1. MPU6050介紹 MPU6050 是 InvenSense 公司推出的全球首款整合性 6 軸運動處理元件,相較於多元件 方案,免除
STM32模擬I2C程式
修改自cleanflight /******************************************************************************* 測試平臺:STM32F103ZET6最小系統 *******
GPIO模擬I2C操作除錯注意事項
I2C作為板級序列資料匯流排,其規格相對簡單,但除錯過程中的一些細節問題容易被忽視,產生意想不到的時序錯誤。寫這邊文章為了記錄我在除錯I2C過程中遇到的問題,以便今後查閱並作為經驗與大家分享。 關於I2C匯流排的規格可以參考I2C的規格書,描述準確詳細,此不贅述。 使用M
Linux使用模擬I2C
由於專案除錯過程需要使用I2C介面,但是在使用硬體I2C過程中,總是發現主機與從機通訊不穩定,想使用模擬I2C來除錯。因為沒做過Linux下的模擬I2C,心裡沒底,但是還是強迫自己去實踐。實踐證明:Linux的驅動框架真的很完善了,使能模擬I2C只需要往平臺匯流排新增一個平
模擬i2c實現流程
首先,在理論上,先分析一下I2C具體都包含了那些內容,後面再結合例項分析一下模擬I2C實現的具體流程 第一,I2C的具體包括以下幾部分內容 1、I2C協議 2條雙向序列線,一條資料線SDA,一條時鐘線SCL。 SDA傳輸資料是大端傳輸,每次
STM32 模擬I2C (STM32F051)
/** ****************************************************************************** * @file i2c simu.c * @brief simulation
計算模擬I2C的傳輸速率
在編寫I2C器件的驅動時,經常會看到手冊中提及該器件的最高傳輸速率,如: 而在配置暫存器實現I2C中也是需要配置I2C的傳輸速率,如: 但模擬I2C的速率該如何計算?首先一般I2C速率的單位是kbit/s,I2C傳輸速率的定義:每秒傳輸的位元位數。先把程式碼貼出來(這裡
linux gpio模擬i2c的使用/用GPIO模擬I2C匯流排-3
這個結構專門用於資料傳輸相關的addr為I2C裝置地址,flags為一些標誌位,len為資料的長度,buf為資料。這裡巨集定義的一些標誌還是需要了解一下。 I2C_M_TEN表示10位裝置地址 I2C_M_RD讀標誌 I2C_M_NOSTART無起始訊號標誌 I2
Android 平臺上 使用 i2c-tools除錯i2c
下載原始碼 編譯 在i2c-tools-3.1.1下新建Android.mk檔案,內容如下 LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional LO
linux I2C 驅動之----i2c驅動的註冊過程(i2c_register_driver->driver_register(&driver->driver)->driver_find)
Linux下i2c驅動的載入過程,分為i2c裝置層、i2c adapter層與i2c核心層 i2c裝置驅動層也就是我們為特定i2c裝置編寫的驅動,下面是我自己理解的i2c驅動的註冊過程 在我們寫的i2c裝置驅動中,我們會呼叫i2c_add_driver()開始i2c裝
amlogic平臺android 系統linux核心中新增i2c裝置實現i2c的讀寫
上一篇,我介紹瞭如何在uboot中新增i2c裝置,以及移植i2c的讀寫介面。簡單來說uboot階段使用i2c裝置和平臺關聯性比較大,但不同平臺套路是差不多的。你可以將uboot階段看作是引導androi