实时时钟(RTC)/日历芯片PCF8563的I2C读写驱动(5):基于软件I2C实现读写接口

0 参考资料

PCF8563数据手册(第 11 版——2015 年 10 月 26 日).pdf

1 基于软件I2C实现读写接口

1.1 初始化软件I2C引脚

软件I2C引脚配置为开漏输出:

/**
 * @brief 软件I2C初始化
 *
 * @param sw_i2c_cfg 软件I2C配置指针
 */
int sw_i2c_init(sw_i2c_cfg_t *sw_i2c_cfg)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    /* 使能SCL/SDA GPIO时钟 */
    gpio_clk_enable(sw_i2c_cfg->scl_port);
    gpio_clk_enable(sw_i2c_cfg->sda_port);

    /* SCL配置为开漏输出 */
    GPIO_InitStruct.Pin = sw_i2c_cfg->scl_pin;
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_PULLUP;
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
    HAL_GPIO_Init(sw_i2c_cfg->scl_port, &GPIO_InitStruct);

    /* SDA配置为开漏输出 */
    GPIO_InitStruct.Pin = sw_i2c_cfg->sda_pin;
    HAL_GPIO_Init(sw_i2c_cfg->sda_port, &GPIO_InitStruct);

    /* SCL/SDA默认拉高 */
    HAL_GPIO_WritePin(sw_i2c_cfg->scl_port, sw_i2c_cfg->scl_pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(sw_i2c_cfg->sda_port, sw_i2c_cfg->sda_pin, GPIO_PIN_SET);

    g_sw_i2c_cfg = *sw_i2c_cfg;

    sw_i2c_delay();

    if ((SCL_IN != 1) || (SDA_IN != 1))
    {
        return -1;
    }

    return 0;
}

1.2 软件I2C延时

这里使用stm32MP135的64位时间戳作为时间基准,时钟来源为HSE(24MHz)

/**
 * @brief 软件I2C延时
 *
 */
void sw_i2c_delay(void)
{
    switch (g_sw_i2c_cfg.speed)
    {
    case SW_I2C_SPEED_1MHz:
        hw_delay_hse(7); // 延时6个HSE为250ns正好为1MHz,这里多加1个HSE防止取在跳变边沿
        break;
    case SW_I2C_SPEED_400KHz:
        hw_delay_hse(16); // 延时15个HSE为625ns正好为400KHz,这里多加1个HSE防止取在跳变边沿
        break;
    case SW_I2C_SPEED_100KHz:
        hw_delay_hse(61); // 延时15个HSE为2500ns正好为100KHz,这里多加1个HSE防止取在跳变边沿
        break;
    default:
        break;
    }
}

1.3 软件I2C产生起始信号

/**
 * @brief 软件I2C产生起始信号
 *
 * @return int 0:成功 -1:失败(总线存在竞争)
 */
int sw_i2c_START(void)
{
    SCL_L;
    sw_i2c_delay();
    SDA_H;
    sw_i2c_delay();
    SCL_H;
    sw_i2c_delay();
    SDA_L;
    sw_i2c_delay();
    if (SDA_IN != 0)
    {
        return -1;
    }
    return 0;
}

1.4 软件I2C产生停止信号

/**
 * @brief 软件I2C产生停止信号
 *
 * @return int 0:成功 -1:失败(总线存在竞争)
 */
int sw_i2c_STOP(void)
{
    SCL_L;
    sw_i2c_delay();
    SDA_L;
    sw_i2c_delay();
    SCL_H;
    sw_i2c_delay();
    SDA_H;
    sw_i2c_delay();
    if (SDA_IN != 1)
    {
        return -1;
    }
    return 0;
}

1.5 软件I2C发送NOACK

/**
 * @brief 软件I2C发送NOACK
 *
 * @return int 0:成功 -1:失败(总线存在竞争)
 */
int sw_i2c_NOACK(void)
{
    SCL_L;
    sw_i2c_delay();
    SDA_H;
    sw_i2c_delay();
    if (SDA_IN != 1)
    {
        return -1;
    }
    SCL_H;
    sw_i2c_delay();
    sw_i2c_delay();
    return 0;
}

1.6 软件I2C发送ACK

/**
 * @brief 软件I2C发送ACK
 *
 * @return int 0:成功 -1:失败(总线存在竞争)
 */
int sw_i2c_ACK(void)
{
    SCL_L;
    sw_i2c_delay();
    SDA_L;
    sw_i2c_delay();
    if (SDA_IN != 0)
    {
        return -1;
    }
    SCL_H;
    sw_i2c_delay();
    sw_i2c_delay();
    return 0;
}

1.7 软件I2C等待ACK信号

/**
 * @brief 软件I2C等待ACK信号
 *
 * @return int 0-成功 -1-失败
 */
int sw_i2c_wait_ACK(void)
{
    SCL_L;
    sw_i2c_delay();
    sw_i2c_delay();
    SCL_H;
    sw_i2c_delay();
    sw_i2c_delay();
    if (SDA_IN != 0)
    {
        return -1;
    }
    return 0;
}

1.8 软件I2C发送1字节数据

/**
 * @brief 软件I2C发送1字节数据
 *
 * @param data 数据
 * @return int 0:成功 -1:失败(总线存在竞争)
 */
int sw_i2c_send_byte(u8 data)
{
    int i;

    for (i = 0; i < 8; i++)
    {
        SCL_L;
        sw_i2c_delay(); // 等待SCL电平变为设置电平
        if ((data << i) & 0x80)
        {
            SDA_H;
        }
        else
        {
            SDA_L;
        }
        sw_i2c_delay(); // 等待SDA电平变为设置电平

        /* 检查SDA是否和目标电平一致 */
        if ((data << i) & 0x80)
        {
            if (SDA_IN != 1)
            {
                return -1;
            }
        }
        else
        {
            if (SDA_IN != 0)
            {
                return -1;
            }
        }
        SCL_H;
        // 这里使用2个延时主要是为了和前面的2个延时时间一致,便于调速
        sw_i2c_delay(); // 等待SCL电平变为设置电平
        sw_i2c_delay(); // 等待SCL电平变为设置电平
    }

    return 0;
}

1.9 软件I2C接收1字节数据

/**
 * @brief 软件I2C接收1字节数据
 *
 * @param data 数据指针
 */
void sw_i2c_recv_byte(u8 *data)
{
    int i;
    *data = 0x0;

    for (i = 0; i < 8; i++)
    {
        SCL_L;
        sw_i2c_delay();
        SDA_H; // 释放SDA,否则SDA读取数据全是0
        sw_i2c_delay();
        SCL_H;
        sw_i2c_delay();
        if (SDA_IN == 1)
        {
            *data = ((*data << 1) | 0x1);
        }
        else
        {
            *data = (*data << 1);
        }
        sw_i2c_delay();
    }
}

1.10 软件I2C写入n字节数据

参考数据手册写入n字节数据时序图:
在这里插入图片描述

/**
 * @brief 软件I2C写入数据
 * 标准操作,数据地址为单字节
 * @param dev_addr 设备地址
 * @param data_addr 数据起始地址
 * @param data 数据指针
 * @param len 数据长度
 * @return int 0:成功 <0:失败
 */
int write_sw_i2c_data(u8 dev_addr, u8 data_addr, u8 *data, int len)
{
    int i;
    int ret;

    /* 发送设备起始信号 */
    ret = sw_i2c_START();
    if (ret != 0)
    {
        return -1;
    }

    /* 发送设备地址,命令:写 */
    ret = sw_i2c_send_byte(dev_addr);
    if (ret != 0)
    {
        return -2;
    }
    ret = sw_i2c_wait_ACK();
    if (ret != 0)
    {
        return -3;
    }

    /* 发送数据地址高8位 */
    ret = sw_i2c_send_byte(data_addr);
    if (ret != 0)
    {
        return -4;
    }
    ret = sw_i2c_wait_ACK();
    if (ret != 0)
    {
        return -5;
    }

    /* 写入数据 */
    for (i = 0; i < len; i++)
    {

        sw_i2c_send_byte(data[i]);
        ret = sw_i2c_wait_ACK();
        if (ret != 0)
        {
            return -6;
        }
    }
    ret = sw_i2c_STOP();
    if (ret != 0)
    {
        return -7;
    }

    return 0;
}

1.11 软件I2C读取n字节数据

参考数据手册读取n字节数据时序图:
在这里插入图片描述

/**
 * @brief 软件I2C读取数据
 * 标准操作,数据地址为单字节
 * @param dev_addr 设备地址
 * @param data_addr 数据起始地址
 * @param data 数据指针
 * @param len 数据长度
 * @return int 0:成功 <0:失败
 */
int read_sw_i2c_data(u8 dev_addr, u8 data_addr, u8 *data, int len)
{
    int i;
    int ret;

    /* 发送设备起始信号 */
    ret = sw_i2c_START();
    if (ret != 0)
    {
        return -1;
    }

    /* 发送设备地址,命令:写 */
    ret = sw_i2c_send_byte(dev_addr);
    if (ret != 0)
    {
        return -2;
    }
    ret = sw_i2c_wait_ACK();
    if (ret != 0)
    {
        return -3;
    }

    /* 发送数据地址高8位 */
    ret = sw_i2c_send_byte(data_addr);
    if (ret != 0)
    {
        return -4;
    }
    ret = sw_i2c_wait_ACK();
    if (ret != 0)
    {
        return -5;
    }

    /* 发送设备起始信号 */
    ret = sw_i2c_START();
    if (ret != 0)
    {
        return -6;
    }

    /* 发送设备地址,命令:读 */
    ret = sw_i2c_send_byte(dev_addr + 1);
    if (ret != 0)
    {
        return -7;
    }
    ret = sw_i2c_wait_ACK();
    if (ret != 0)
    {
        return -8;
    }

    /* 读取数据 */
    for (i = 0; i < len; i++)
    {

        sw_i2c_recv_byte(&data[i]);
        if (i != (len - 1))
        {
            ret = sw_i2c_ACK();
            if (ret != 0)
            {
                return -9;
            }
        }
        else
        {
            ret = sw_i2c_NOACK();
            if (ret != 0)
            {
                return -10;
            }
        }
    }
    ret = sw_i2c_STOP();
    if (ret != 0)
    {
        return -11;
    }

    return 0;
}
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

NW嵌入式开发

感谢您的支持,让我们一起进步!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值