在线观看www成人影院-在线观看www日本免费网站-在线观看www视频-在线观看操-欧美18在线-欧美1级

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

STM32指針抽象出I2C的數據實例

STM32嵌入式開發 ? 來源:STM32嵌入式開發 ? 2023-10-26 15:53 ? 次閱讀

1.寫在前面

I2C總線是由PHILIPS公司開發的一種簡單、「雙向二線制同步串行總線」。

關于i2c的使用,并不陌生,STM32、C51、ARMMSP430等,都基本集成硬件i2c,或者不集成i2c的,可以根據總線時序圖使用普通IO口翻轉模擬一根i2c總線。

wKgaomU6G5yAH6zwAABnIAjUCo0855.png

對于流行的STM32飽受詬病的硬件I2C,相信很多人都是使用模擬I2C。

模擬i2c的源碼比較多,大多都是大同小異,對于各類例程,提供的模擬i2c似乎都不是太規范(個人見解),特別是一根i2c總線掛多個外設、模擬多根i2c總線、以及更換一個i2c外設時,都需要大幅度修改源碼、復制源碼、重新調試時序等重復的工作。

在閱讀過Linux設備驅動框架和RT-Thread的驅動框架,發現在總線分層上處理就特別好,完美解決了上述提及的問題。參考RT-Thread和Linux下的模擬i2c,整理修改在裸機上使用。

2.Linux、RT-Thread設備驅動模型

1)模型分為總線驅動和設備驅動;

2) 總線驅動與外設驅動分離,方便一根總線掛多個外設,方便移植;

3) 底層(與硬件相關)與上層分離,方便添加總線及移植到不同處理器,移植到其他處理器,只需重新實現硬件相關的“寄存器”層即可;

wKgaomU6G5yAcInpAAAqxaqySFY086.jpg

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。

c)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外設時,需要對模擬i2c代碼進行較大工作量的修改,或者以“復制”的方式實現一套新的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 void i2c_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 char i2c_bitops_wait_ack(struct ops_i2c_dev *i2c_bus)
staticinti2c_bitops_send_byte(structops_i2c_dev*i2c_bus,unsignedchardata)

等等,入口參數都是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);
staticunsignedlongi2c_bitops_bus_read(structops_i2c_dev*i2c_bus,structi2c_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)事務分離,通用、重復的事情交給總線處理,特殊任務留給外設驅動。

審核編輯:湯梓紅

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • mcu
    mcu
    +關注

    關注

    146

    文章

    17357

    瀏覽量

    352790
  • Linux
    +關注

    關注

    87

    文章

    11351

    瀏覽量

    210499
  • STM32
    +關注

    關注

    2273

    文章

    10926

    瀏覽量

    357770
  • I2C
    I2C
    +關注

    關注

    28

    文章

    1495

    瀏覽量

    124705
  • 串行總線
    +關注

    關注

    1

    文章

    183

    瀏覽量

    30717

原文標題:STM32指針抽象出I2C的數據實例,附代碼

文章出處:【微信號:c-stm32,微信公眾號:STM32嵌入式開發】歡迎添加關注!文章轉載請注明出處。

收藏 人收藏

    評論

    相關推薦

    STM32硬件I2C例程

    STM32硬件I2C例程,感興趣的小伙伴們可以看看。
    發表于 07-26 10:26 ?93次下載

    《振南電子STM32視頻教程》第八講:I2C總線

    [第8講] I2C總線(96分鐘),由何強主講.本課主要講解:(1).I2C總線中的概念講解;(2).I2C總線應用實例;(3).
    發表于 10-09 15:57 ?1125次閱讀

    STM32F2—通信接口I2C

    STM32F2—通信接口I2C
    發表于 09-03 11:24 ?31次下載
    <b class='flag-5'>STM32F2</b>—通信接口<b class='flag-5'>I2C</b>

    I2C總線原理及應用實例

    I2C總線原理及應用實例
    發表于 09-22 11:41 ?17次下載
    <b class='flag-5'>I2C</b>總線原理及應用<b class='flag-5'>實例</b>

    STM32 I2C硬件的結構

    我們可以看見STM32的硬件I2C有兩個和數據有關的寄存器“數據寄存器(Data register)”(DR)和“數據移位寄存器(Data
    的頭像 發表于 04-30 15:00 ?7467次閱讀
    <b class='flag-5'>STM32</b> <b class='flag-5'>I2C</b>硬件的結構

    一文知道32位指針內部區域尋址

    32位指針用于I、Q、M、L數據塊等存儲器中的位、字節、字、雙字的尋址,32指針尋址,指針需要使用一個雙字的存儲器空間進行存儲。
    的頭像 發表于 11-20 17:33 ?3885次閱讀

    i2c總線用來做什么_i2c總線數據傳輸過程

    I2C總線控制器為微控制器或微處理器提供控制I2C總線的接口,它控制所有I2C總線的特殊序列、協議、仲裁、時序,這里MPC8250提供的I2C
    發表于 11-24 14:16 ?7328次閱讀

    STM32學習之I2C協議(讀寫EEPROM)

    關于STM32學習分享第七章 I2C協議(讀寫EEPROM)文章目錄關于STM32學習分享前言二、代碼1.i2c.c2.i2c.h3.main.c總結前言開始!開始!單片機的
    發表于 11-30 15:21 ?32次下載
    <b class='flag-5'>STM32</b>學習之<b class='flag-5'>I2C</b>協議(讀寫EEPROM)

    STM32的硬件I2C有BUG嗎?

    坊間一直流傳著一個傳說~STM32的硬件I2C設計有BUG,最好不要用,用軟件I2C比較靠譜。長久以來,為了不必要的麻煩,我也一直沒有用過硬件I2C,主要是軟件
    發表于 12-04 14:51 ?26次下載
    <b class='flag-5'>STM32</b>的硬件<b class='flag-5'>I2C</b>有BUG嗎?

    I2C接口配置ES7243錄音芯片,MCU(STM32)收不到I2C ACK的問題

    I2C接口配置ES7243錄音芯片,MCU(STM32)收不到I2C ACK的問題
    發表于 12-08 16:36 ?10次下載
    <b class='flag-5'>I2C</b>接口配置ES7243錄音芯片,MCU(<b class='flag-5'>STM32</b>)收不到<b class='flag-5'>I2C</b> ACK的問題

    STM32F103硬件I2C Slave

    MCU:STM32F103IDE:STM32CubeIDEHAL庫硬件I2C當作Slave,模擬EEPROM行為測試工具:樹莓派為I2C主機,使用I
    發表于 12-20 19:38 ?52次下載
    <b class='flag-5'>STM32</b>F103硬件<b class='flag-5'>I2C</b> Slave

    STM32 I2C存取實驗資料

    STM32 I2C存取實驗資料
    發表于 12-30 10:36 ?17次下載

    硬件I2C與模擬I2C

    硬件I2C對應芯片上的I2C外設,有相應I2C驅動電路,其所使用的I2C管腳也是專用的,因而效率要遠高于軟件模擬的I2C;一般也較為穩定,但
    發表于 12-28 19:14 ?81次下載
    硬件<b class='flag-5'>I2C</b>與模擬<b class='flag-5'>I2C</b>

    I2C子系統SW Architecture

    適配器(控制器)驅動,這里用到兩個重要的數據結構:i2c_adapter 和 i2c_algorithm。其中,Linux 內核將 SOC 的 I2C 適配器(控制器)
    的頭像 發表于 07-22 16:01 ?1009次閱讀
    <b class='flag-5'>I2C</b>子系統SW Architecture

    I2C總線應用實例分析

    I2C總線使用兩條線進行通信:數據線(SDA)和時鐘線(SCL)。這種雙線制設計使得I2C總線能夠在多個設備之間共享,而不需要為每個設備單獨布線。 應用實例:溫濕度傳感器 假設我們正
    的頭像 發表于 01-17 15:09 ?208次閱讀
    主站蜘蛛池模板: 国产女同在线观看 | 国内精品视频免费观看 | 色成年激情久久综合 | 在线观看成人网 | 在线电影亚洲 | 中文字幕在线一区二区三区 | 免费视频观看 | 中文字幕一区二区三区在线不卡 | 双性强迫开宫灌满h | 亚洲五月激情 | 天天射美女| 韩国三级hd中文字幕好大 | 国产精品视频永久免费播放 | 99久免费精品视频在线观看2 | 欧美精品二区 | 亚洲线精品一区二区三区 | 久久精品国产亚洲aa | 天堂网2021天堂手机版丶 | 老师我好爽再深一点好大 | 天天色播 | 久久综合视频网 | 久久国产乱子伦精品免费看 | 天天做天天爱夜夜大爽完整 | 国内精品手机在线观看视频 | 色停停 | 亚洲国产视频一区 | 日本免费一区二区视频 | 欧美一二三区在线 | 午夜国产精品久久影院 | 日本69xxxxxxxx69| 欧美色图亚洲激情 | 免费拍拍视频 | 激情综合六月 | 美女一区二区三区 | 精品国产第一国产综合精品gif | 俄罗斯久久| 韩国三级hd | 狠狠色丁香九九婷婷综合五月 | 亚洲一区二区中文字幕 | 爱爱456高清国语在线456 | 黄色毛片基地 |