通过普通IO模拟I2C主机

 I2C协议是嵌入式开发中常用的一种总线协议,使用方便占用IO少,多个从设备可以挂在同一个主机总线上。在一些特殊的情况下,需要多个I2C接口,硬件模块不够用;或者硬件设计原因无法使用硬件I2C,这时候就需要使用普通IO模拟I2C,通常模拟为主机。使用GPIO模拟I2C方便灵活,不受硬件资源限制,移植方便。

1. 模拟I2C主机代码

 这里使用的是Stm32L4的开发板,基于HAL库实现了GPIO模拟I2C主机,可以支持模拟多个I2C接口,不同接口可以有不同的SCL频率。

 头文件为:

#ifndef __IIC_SIMU_H
#define __IIC_SIMU_H

#ifdef __cplusplus
extern "C" {
#endif /*_cplusplus*/

/* Includes */
#include "stm32l4xx_hal.h"
#include "common_def.h"
#include "user_config.h"

typedef struct
{
    uint32_t        I2C_Timing;
    GPIO_TypeDef*   SCL_GPIOx;
    uint32_t        SCL_Pin;
    GPIO_TypeDef*   SDA_GPIOx;
    uint32_t        SDA_Pin;
} I2C_PortDef;

#define I2C_TIMING_100K      4
#define I2C_TIMING_200K      2

#define I2C_WRITE            0u
#define I2C_READ             1u

#define I2C_BYTE_TIMEOUT      10u

#define I2C_PIN_MODE          GPIO_MODE_OUTPUT_OD

#define ENTER_CRITICAL()
#define EXIT_CRITICAL()

/* Exported functions */
void I2C_SimInit(I2C_PortDef* port);

HAL_StatusTypeDef I2C_SimWrite(I2C_PortDef* port, uint8_t addr, 
                               uint8_t *data, uint32_t len, uint32_t Timeout);

HAL_StatusTypeDef I2C_SimRead(I2C_PortDef* port, uint8_t addr, 
                              uint8_t *data, uint32_t len, uint32_t Timeout);

HAL_StatusTypeDef I2C_SimMemWrite(I2C_PortDef* port, uint8_t addr, 
                                  uint8_t *memAddr, uint32_t memAddrLen,
                                  uint8_t *data, uint32_t len, uint32_t Timeout);

HAL_StatusTypeDef I2C_SimMemRead(I2C_PortDef* port, uint8_t addr, 
                                 uint8_t *memAddr, uint32_t memAddrLen,
                                 uint8_t *data, uint32_t len, uint32_t Timeout);
#ifdef __cplusplus
}
#endif /*_cplusplus*/

#endif /* __IIC_SIMU_H */

 实现文件为:

#include "iic_sim.h"

void SysCtlDelay(uint32_t ulCount)
{
    __asm("    subs    r0, #1\n"
          "    bne.n   SysCtlDelay\n"
          "    bx      lr");
}

void _I2C_DelayUs(uint32_t us)
{
    SysCtlDelay(us*(SystemCoreClock/3000000));
}

void _I2C_RCC_ENABLE(GPIO_TypeDef* GPIOx)
{
    if (GPIOx == GPIOA)
    {
        __HAL_RCC_GPIOA_CLK_ENABLE();
    } else if (GPIOx == GPIOB) {
        __HAL_RCC_GPIOB_CLK_ENABLE();
    } else if (GPIOx == GPIOC) {
        __HAL_RCC_GPIOC_CLK_ENABLE();
    }
    #if defined(GPIOD)
    else if (GPIOx == GPIOD) {
        __HAL_RCC_GPIOD_CLK_ENABLE();
    }
    #endif
    #if defined(GPIOE)
    else if (GPIOx == GPIOE) {
        __HAL_RCC_GPIOE_CLK_ENABLE();
    }
    #endif
    #if defined(GPIOF)
    else if (GPIOx == GPIOF) {
        __HAL_RCC_GPIOF_CLK_ENABLE();
    }
    #endif
    #if defined(GPIOG)
    else if (GPIOx == GPIOG) {
        __HAL_RCC_GPIOG_CLK_ENABLE();
    }
    #endif
}

void _I2C_PinOutPut(GPIO_TypeDef *GPIOx, uint32_t Pin)
{
    GPIO_InitTypeDef  GPIO_InitStruct;

    GPIO_InitStruct.Mode  = I2C_PIN_MODE;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pin = Pin;
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}

void _I2C_PinInPut(GPIO_TypeDef *GPIOx, uint32_t Pin)
{
    GPIO_InitTypeDef  GPIO_InitStruct;

    GPIO_InitStruct.Mode  = GPIO_MODE_INPUT;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pin = Pin;
    HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}

void I2C_SimInit(I2C_PortDef* port)
{
    GPIO_InitTypeDef  GPIO_InitStruct;

    _I2C_RCC_ENABLE(port->SCL_GPIOx);
    _I2C_RCC_ENABLE(port->SDA_GPIOx);

    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);

    GPIO_InitStruct.Mode  = I2C_PIN_MODE;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pin = port->SDA_Pin;
    HAL_GPIO_Init(port->SDA_GPIOx, &GPIO_InitStruct);

    GPIO_InitStruct.Mode  = I2C_PIN_MODE;
    GPIO_InitStruct.Pull  = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
    GPIO_InitStruct.Pin = port->SCL_Pin;
    HAL_GPIO_Init(port->SCL_GPIOx, &GPIO_InitStruct);
}

/*
* Start: SDA High->Low, when SCL High
*/
void I2C_SimStart(I2C_PortDef* port)
{
    _I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);

    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
    _I2C_DelayUs(port->I2C_Timing);
    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_RESET);
    _I2C_DelayUs(port->I2C_Timing);

    /* Drive bus */
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
}

/*
* Stop: SDA Low->High, when SCL High
*/
void I2C_SimStop(I2C_PortDef* port)
{
    _I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);

    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
    _I2C_DelayUs(port->I2C_Timing);
    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
    _I2C_DelayUs(port->I2C_Timing);
}

HAL_StatusTypeDef I2C_SimWaitAck(I2C_PortDef* port, uint32_t WaitMs)
{
    uint32_t start;
  
    _I2C_PinInPut(port->SDA_GPIOx, port->SDA_Pin);
    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
    
    start = HAL_GetTick();
    _I2C_DelayUs(port->I2C_Timing>>1u);
    while(HAL_GPIO_ReadPin(port->SDA_GPIOx, port->SDA_Pin) != GPIO_PIN_RESET)
    {
        if((HAL_GetTick()-start) > WaitMs)
        {
            I2C_SimStop(port);
            return HAL_TIMEOUT;
        }
    }
    
    _I2C_DelayUs(port->I2C_Timing>>1u);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
    return HAL_OK;
}

/*
* ACK: SDA Low, when SCL Low->High->Low
*/
void I2C_SimSendAck(I2C_PortDef* port)
{
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
    _I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);
    
    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
    _I2C_DelayUs(port->I2C_Timing);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
}

/*
* NACK: SDA High, when SCL Low->High->Low
*/
void I2C_SimSendNack(I2C_PortDef* port)
{
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
    _I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);
    
    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
    _I2C_DelayUs(port->I2C_Timing);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
}

void I2C_SimSendByte(I2C_PortDef* port, uint8_t chr)
{
    uint8_t i;
    
    ENTER_CRITICAL();
    
    _I2C_PinOutPut(port->SDA_GPIOx, port->SDA_Pin);
    HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
    
    for (i=0;i<8u;i++)
    {
        if (chr & 0x80)
        {
            HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
        } else {
            HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_RESET);
        }

        _I2C_DelayUs(port->I2C_Timing>>1u);
        HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
        _I2C_DelayUs(port->I2C_Timing);
        HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
        _I2C_DelayUs(port->I2C_Timing>>1u);

        chr <<= 1u;
    }
    
    EXIT_CRITICAL();
}

uint8_t I2C_SimGetByte(I2C_PortDef* port)
{
    uint8_t i;
    uint8_t chr = 0u;
    
    ENTER_CRITICAL();

    _I2C_PinInPut(port->SDA_GPIOx, port->SDA_Pin);
    HAL_GPIO_WritePin(port->SDA_GPIOx, port->SDA_Pin, GPIO_PIN_SET);
    
    for (i=0;i<8u;i++)
    {
        HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
        _I2C_DelayUs(port->I2C_Timing>>1u);
        HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_SET);
        _I2C_DelayUs(port->I2C_Timing>>1u);

        chr <<= 1u;
        if (HAL_GPIO_ReadPin(port->SDA_GPIOx, port->SDA_Pin) == GPIO_PIN_SET)
        {
            chr |= 1u;
        }

        _I2C_DelayUs(port->I2C_Timing>>1u);
        HAL_GPIO_WritePin(port->SCL_GPIOx, port->SCL_Pin, GPIO_PIN_RESET);
        _I2C_DelayUs(port->I2C_Timing>>1u);
    } 
    EXIT_CRITICAL();

    return chr;
}

HAL_StatusTypeDef I2C_WaitTill(I2C_PortDef* port, uint32_t start, uint32_t Timeout)
{
    if ((HAL_GetTick()-start) > Timeout)
    {
        I2C_SimStop(port);
        return HAL_TIMEOUT;
    }
    
    return HAL_OK;
}

/*
* I2c write data
* addr: slave address
* Timeout: timeout(ms)
*/
HAL_StatusTypeDef I2C_SimWrite(I2C_PortDef* port, uint8_t addr, 
                               uint8_t *data, uint32_t len, uint32_t Timeout)
{
    uint32_t i;
    uint32_t start;
    
    start = HAL_GetTick();

    I2C_SimStart(port);
    I2C_SimSendByte(port, (addr<<1u)|I2C_WRITE);
    if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
    {
        return HAL_TIMEOUT;
    }

    for (i=0;i<len;i++)
    {
        I2C_SimSendByte(port, data[i]);
        if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }

        if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }
    I2C_SimStop(port);

    return HAL_OK;
}

HAL_StatusTypeDef I2C_SimRead(I2C_PortDef* port, uint8_t addr, 
                              uint8_t *data, uint32_t len, uint32_t Timeout)
{
    uint32_t i;
    uint32_t start;

    start = HAL_GetTick();

    I2C_SimStart(port);
    I2C_SimSendByte(port, (addr<<1u)|I2C_READ);
    if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
    {
        return HAL_TIMEOUT;
    }

    for (i=0;i<len;i++)
    {
        data[i] = I2C_SimGetByte(port);
        if (i == (len-1))
        {
            I2C_SimSendNack(port);
        } else {
            I2C_SimSendAck(port);
        }

        if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }
    I2C_SimStop(port);

    return HAL_OK;
}

HAL_StatusTypeDef I2C_SimMemWrite(I2C_PortDef* port, uint8_t addr, 
                                  uint8_t *memAddr, uint32_t memAddrLen,
                                  uint8_t *data, uint32_t len, uint32_t Timeout)
{
    uint32_t i;
    uint32_t start;

    start = HAL_GetTick();

    I2C_SimStart(port);
    I2C_SimSendByte(port, (addr<<1u)|I2C_WRITE);
    if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
    {
        return HAL_TIMEOUT;
    }
    
    /* Send register addrss */
    for (i=0;i<memAddrLen;i++)
    {
        I2C_SimSendByte(port, memAddr[i]);
        if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }

        if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }
    
    /* Send data */
    for (i=0;i<len;i++)
    {
        I2C_SimSendByte(port, data[i]);
        if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }

        if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }
    I2C_SimStop(port);

    return HAL_OK;
}

HAL_StatusTypeDef I2C_SimMemRead(I2C_PortDef* port, uint8_t addr, 
                                 uint8_t *memAddr, uint32_t memAddrLen,
                                 uint8_t *data, uint32_t len, uint32_t Timeout)
{
    uint32_t i;
    uint32_t start;

    start = HAL_GetTick();

    I2C_SimStart(port);
    I2C_SimSendByte(port, (addr<<1u)|I2C_WRITE);
    if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
    {
        return HAL_TIMEOUT;
    }
    
    /* Send register addrss */
    for (i=0;i<memAddrLen;i++)
    {
        I2C_SimSendByte(port, memAddr[i]);
        if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }

        if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }

    I2C_SimStart(port);
    I2C_SimSendByte(port, (addr<<1u)|I2C_READ);
    if (I2C_SimWaitAck(port, I2C_BYTE_TIMEOUT) != HAL_OK)
    {
        return HAL_TIMEOUT;
    }

    /* Get data */
    for (i=0;i<len;i++)
    {
        data[i] = I2C_SimGetByte(port);
        if (i == (len-1))
        {
            I2C_SimSendNack(port);
        } else {
            I2C_SimSendAck(port);
        }

        if (I2C_WaitTill(port, start, Timeout) != HAL_OK)
        {
            return HAL_TIMEOUT;
        }
    }
    I2C_SimStop(port);

    return HAL_OK;
}

2.测试代码

 这里使用24C08模块作为从设备,通过模拟I2C进行数据读写,通过逻辑分析仪分析数据正确性。测试代码为:

I2C_PortDef i2c_port;

i2c_port.I2C_Timing = I2C_TIMING_100K;
i2c_port.SCL_GPIOx = GPIOC;
i2c_port.SCL_Pin = GPIO_PIN_8;
i2c_port.SDA_GPIOx = GPIOC;
i2c_port.SDA_Pin = GPIO_PIN_9;
I2C_SimInit(&i2c_port);

uint8_t aa[4] = {0x55, 0xaa, 0x5a, 0xa5};
uint8_t memaddr = 0;
I2C_SimMemWrite(&i2c_port, 0x50, 
                   &memaddr, 1,
                   aa, 4, 30);
HAL_Delay(100);
    
uint8_t bb[4];
memaddr = 2;
I2C_SimMemRead(&i2c_port, 0x50, 
                   &memaddr, 1,
                   bb, 4, 30);

 通过逻辑分析仪抓到的数据可以看到I2C的时序是正确的:

 写EEPROM

 读EEPROM

3.总结

 通过GPIO模拟I2C主机灵活方便,可以同时模拟多个I2C端口,缺点是相较硬件实现容易受到其他代码的影响,无法使用DMA等高级功能。然而由于I2C使用的场景大部分情况下速度都比较慢,GPIO模拟I2C仍是一种比较实用的方式。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 5
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值