串口模拟I2C


摘要

通常情况下在硬件资源不够或者某些芯片的I2C调试不通的时候会选择串口模拟I2C,本文记录串口模拟I2C的软件实现方法。

1、函数宏定义

#define IIC_SDA_PIN_PORT     (GPIO0)
#define IIC_SDA_PIN          (17)
#define IIC_SCL_PIN_PORT     (GPIO0)
#define IIC_SCL_PIN          (16)

2、串口初始化

void UserI2cIoInit(void)                  //io复用配置
{
    //I2C1
    //复用为普通io口
    HAL_GPIO_AF_SET(IIC_SDA_PIN_PORT, IIC_SDA_PIN, GPIO_AF0);
    HAL_GPIO_AF_SET(IIC_SCL_PIN_PORT, IIC_SCL_PIN, GPIO_AF0);
    //输出使能
    HAL_GPIO_OUT_EN(IIC_SDA_PIN_PORT, 1U << IIC_SDA_PIN);
    HAL_GPIO_OUT_EN(IIC_SCL_PIN_PORT, 1U << IIC_SCL_PIN);
    //输出为高电平
    HAL_GPIO_BIT_SET(IIC_SDA_PIN_PORT, (1U << IIC_SDA_PIN));
    HAL_GPIO_BIT_SET(IIC_SCL_PIN_PORT, (1U << IIC_SCL_PIN));
    //SDA设置为开漏输出(软件模拟I2C)
    HAL_GPIO_OUT_OD(IIC_SDA_PIN_PORT, 1U << IIC_SDA_PIN);
}

3、延时函数处理

_Pragma("push")         \
_Pragma("O0")
__RAM_CODE void SysCycleUsDelay(volatile float_t delayInUs)
{
    // 按照周期进行延时,不依赖于定时器,偏差0.1%左右
    uint32_t cycCnt = DWT->CYCCNT;
    uint32_t usCnt = (uint32_t)((float_t)SystemHclk / 1000000.0f * delayInUs);
    usCnt -= 133;           // 修正偏差
    usCnt >>= 2;
    while(usCnt--){;}
    cycCnt = DWT->CYCCNT-cycCnt;
    return;
}
_Pragma ("pop")

4、SDA SCL高低电平配置

//SDA输入模式
static void I2C_SDA_IN(void)
{
    HAL_GPIO_IN_EN(IIC_SDA_PIN_PORT, (1U << IIC_SDA_PIN));
}
//SDA输出模式
static void I2C_SDA_OUT(void)
{
    HAL_GPIO_OUT_EN(IIC_SDA_PIN_PORT, (1U << IIC_SDA_PIN));
}
//SDA电平变换配置
static void I2C_SDA(uint8_t sdaState)    
{
    if (sdaState == 1)
    {
        //开漏模式下设置为输入NMOS截止,由外部上拉输出为高电平
        HAL_GPIO_IN_EN(IIC_SDA_PIN_PORT, 1U << IIC_SDA_PIN);
    }
    else
    {
        //开漏模式下设置为低NMOS导通接地输出为低电平
        HAL_GPIO_OUT_EN(IIC_SDA_PIN_PORT, 1U << IIC_SDA_PIN);
        HAL_GPIO_BIT_CLR(IIC_SDA_PIN_PORT, (1U << IIC_SDA_PIN));
    }
}
//SCL电平配置
static void I2C_SCL(uint8_t sclState)     
{
    if (sclState == 1)
    {
        HAL_GPIO_BIT_SET(IIC_SCL_PIN_PORT, (1U << IIC_SCL_PIN));
    }
    else
    {
        HAL_GPIO_BIT_CLR(IIC_SCL_PIN_PORT, (1U << IIC_SCL_PIN));
    }
}

5、I2C开始

//开始 sda high->low  scl high->low , sda 下降沿跳变早于scl
static void I2C_Start(void)
{
    I2C_SDA_OUT();
    I2C_SDA(1);
    I2C_SCL(1);
    SysCycleUsDelay(4);
    I2C_SDA(0);
    SysCycleUsDelay(4);
    I2C_SCL(0);c
}

6、I2C停止

//停止,SDA↓, SCL↑,SDA↑
static void I2C_Stop(void)
{
    I2C_SDA_OUT();
    I2C_SCL(0);
    I2C_SDA(0);
    SysCycleUsDelay(4);
    I2C_SCL(1);
    SysCycleUsDelay(4);
    I2C_SDA(1);
}

7、I2C响应

//发送Ack
static void IIC_Ack(void)
{
    I2C_SCL(0);
    I2C_SDA_OUT();
    I2C_SDA(0);//发送低电平,响应从机
    SysCycleUsDelay(2);
    I2C_SCL(1);
    SysCycleUsDelay(2);
    I2C_SCL(0);
}

8、I2C不响应

//发送Nack
static void IIC_NAck(void)
{
    I2C_SCL(0);
    I2C_SDA_OUT();
    I2C_SDA(1);//发送低电平,响应从机
    SysCycleUsDelay(2);
    I2C_SCL(1);
    SysCycleUsDelay(2);
    I2C_SCL(0);
}

9、I2C等待响应

//等待Ack
static UINT8 IIC_Wait_Ack(uint32_t timeout_ms)
{
    UINT32 t0;
    I2C_SDA_IN();                     //SDA设置为输入
    I2C_SDA(1);
    SysCycleUsDelay(1);
    I2C_SCL(1);                    //时钟置高
    SysCycleUsDelay(1);
    t0 = GetMs(0);
    while ((HAL_GPIO_IN_DATA_GET(IIC_SDA_PIN_PORT) & (1U << IIC_SDA_PIN)) != 0)       //判断SDA端口状态
    {
        if (GetMs(t0) > timeout_ms)
        {
            I2C_Stop();
            return 0;
        }
    }
    I2C_SCL(0);         //拉低,完成应答位
    SysCycleUsDelay(4);
    I2C_SDA_OUT();                    //SDA设置为输出
    return 1;
}

10、I2C写一个字节

//发送一个字节
static void I2C_WriteByte(uint8_t Data)
{
    UINT8 cnt;
    I2C_SDA_OUT();
    I2C_SCL(0);
    for (cnt = 0; cnt < 8; cnt++)
    {
        if (Data & 0x80)
        {
            I2C_SDA(1);            //SDA高
        }
        else
        {
            I2C_SDA(0);            //SDA低
        }
        Data <<= 1;
        SysCycleUsDelay(2);
        I2C_SCL(1);
        SysCycleUsDelay(2);
        I2C_SCL(0);
        SysCycleUsDelay(2);      
    }
}

11、I2C读一个字节

//读一个字节
static uint8_t I2C_ReadByte(uint8_t ack)
{
    uint8_t cnt;
    uint8_t Data = 0;
    I2C_SDA_IN();                     //SDA设置为输入
    for (cnt = 0; cnt < 8; cnt++)
    {
        I2C_SCL(0);
        SysCycleUsDelay(2);
        I2C_SCL(1);
        Data <<= 1;
        SysCycleUsDelay(1);
        if ((HAL_GPIO_IN_DATA_GET(IIC_SDA_PIN_PORT) & (1U << IIC_SDA_PIN)) != 0)         //SDA读数据
        {
            //Data++;
            Data |= 0x01;
        }
        SysCycleUsDelay(1);
    }
    if (!ack) /*when ack = 0*/
    {
        IIC_NAck();          //发送NAck
    }
    else
    {
        IIC_Ack();           //发送Ack
    }
    return Data;
}

12、I2C连续写

//连续写
uint8_t I2cDataWrite(uint8_t address, uint32_t length, uint8_t *pData, uint32_t timeout_ms)
{
    UINT8 Ack;
    if (length)
    {
        I2C_Start();
        I2C_WriteByte(address);
        Ack = IIC_Wait_Ack(timeout_ms);
        while (!Ack)
        {
            I2C_Stop();
            return IICERR_NACK;
        }
        while (length != 0)
        {
            I2C_WriteByte(*pData++);
            Ack = IIC_Wait_Ack(timeout_ms);
            while (!Ack)
            {
                I2C_Stop();
                return IICERR_NACK;
            }
            length--;
            SysCycleUsDelay(4 * 10);
        }
        I2C_Stop();
        return IIC_OK;
    }
    return IICERR_LENZERO;
}

13、I2C连续读

//连续读
uint8_t I2cDataRead(uint8_t address, uint32_t length, uint8_t *pData, uint32_t timeout_ms)
{
    uint8_t Ack;
    if (length)
    {
        I2C_Start();
        I2C_WriteByte(address);               //发送寄存器地址
        Ack = IIC_Wait_Ack(timeout_ms);       //检测有无应答以及是否超时
        while (!Ack)
        {
            return IICERR_NACK;
        }
        while (length > 1)
        {
            *pData++ = I2C_ReadByte(1);      //读一个字节
            length --;
        }
        *pData ++ = I2C_ReadByte(0);         //读最后一个字节
        I2C_Stop();
        return IIC_OK;
    }
    return IICERR_LENZERO;
}

小结

本文记录的串口模式拟2C的为一种实现模式,不同的代码有不同的实现方式,欢迎各位大佬提供宝贵的意见。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值