[K230学习笔记 02] I2C

硬件背景

I2C

  如图1.1所示,简单来说I2C是一种同步、串行、多主从的通信总线,由时钟线(SCL)与数据线(SDA)组成。在总线上的设备由一个唯一的地址(这个地址分为了7bit与10bit两种)识别,并且可以作为发送器或接收器运行,具体取决于设备的功能。在通信过程中主机负责发起和终止数据传输,并生成时钟信号,而从设备响应主机的请求。

assets/-K230学习笔记 02- I2C/file-20251026135934929.jpg
图1.1 I2C总线结构图

  
  最初,I2C的通信速率被限制在100Kbit/s以内,之后又在I2C协议规范中增加了相关内容。目前,I2C主要有5种运行速度:

  1. 标准模式(standard mode):最高100Kbit/s

  2. 快速模式(fast mode):最高400Kbit/s

  3. 快速模式+(fast mode +):最高1Mbit/s

  4. 高速模式(high mode ):最高3.4Mbit/s

  5. 超快模式(ultra mode ):最高5Mbit/s

  值得一提的是,前四种速度是向下兼容的,但是超快模式是与前四种模式不兼容的,并且超快模式总线使用推挽驱动器,因此不再具有多主特性,再超快模式中主句控制器是唯一在总线上启动数据传输并生成时钟信号以允许传输的器件。

K230 I2C控制器

  K230的I2C控制器支持标准模式、快速模式、快速模式+以及高速模式,控制器拥有32*32bits的TX FIFO,64*8bits的RX FIFO,支持设置触发阈值,并且支持DMA操作。还支持SMBus与PMBus,这两者应该是I2C的一种变体,有更多的电气参数与协议细节,有兴趣的同学可以去具体了解。

配置流程

I2C 主机

初始化主机

  初始化I2C控制器为主机模式,首先失能i2c,然后配置i2c控制寄存器,如有需要还可以继续配置TX/RX FIFO的阈值,用于触发TX EMPTY中断以及RX FULL中断,使能i2c。当然,如果想要触发中断,还需继续配置中断掩码寄存器的相应位,然后注册相应中断服务函数,使能相应中断。

static void i2c_master_init(struct k230_i2c_dev *dev)
{
    volatile i2c_t *i2c = (i2c_t *)dev->base;

    /* Disable i2c */
    i2c_enable(dev, RT_FALSE);

    i2c->con.slave_disable = 1;
    i2c->con.restart_en = 1;
    i2c->con.master_mode = 1;
    i2c->tx_tl.tl = I2C_TX_TL;
    i2c->rx_tl.tl = I2C_RX_TL;
    i2c->intr_mask.m_stop_det = 1;

    /* Enable i2c */
    i2c_enable(dev, RT_TRUE);
}

读取数据

  读取数据时,首先判断是否需要执行完整发送初始化(开始条件+设备地址),还是发送重新开始,然后获取开始读取时的tick,向TX FIFO写入命令触发I2C执行操作,然后从RX FiFO中读取数据,以此同时会判断是否超时。

static int _i2c_read(struct k230_i2c_dev *dev)
{
    volatile i2c_t *i2c = (i2c_t *)dev->base;
    rt_size_t start_time_rx = 0;
    rt_uint32_t recv_len = dev->msg->len;
    rt_uint32_t tran_len = dev->msg->len;
    rt_uint8_t *buffer = dev->msg->buf;
    rt_uint32_t cmd = 0;

    /* If no start condition is sent before reading, then send a repeated start. */
    if(dev->msg->flags & RT_I2C_NO_START)
    {
        cmd |= I2C_DATA_CMD_RESTART;
    }
    else
    {
        if(i2c_xfer_init(dev) != RT_EOK)
        {
            return -RT_EBUSY;
        }
    }

    start_time_rx = i2c_get_timer(0);
    while(recv_len || tran_len)
    {
        if (tran_len)
        {
            while(i2c->status.tfnf == 0);
            /* Write stop when the last byte */
            cmd = tran_len == 1 ? cmd | I2C_DATA_CMD_STOP : cmd;
            /* Write to data cmd register to trigger i2c */
            writel(cmd | I2C_DATA_CMD_READ, &i2c->data_cmd);
            cmd = 0;
            tran_len--;
        }

        if(i2c->status.rfne)
        {
            *buffer++ = i2c->data_cmd.dat;
            recv_len--;
            start_time_rx = i2c_get_timer(0);
        }
        else if(i2c_get_timer(start_time_rx) > dev->dev.timeout)
        {
            return -RT_ETIMEOUT;
        }
    }
    return i2c_xfer_finish(dev);
}

  

写入数据

  写入数据时,首先需要执行完整发送初始化(开始条件+设备地址),然后TX FIFO还有空间则继续写入命令,当处理最后一个字节时需要特殊处理,如果后续没有紧接着读操作(也就是本次写数据是包括了写入地址与写数据),则信息标志中应该不包含RT_I2C_NO_STOP,此时需要置位I2C_DATA_CMD_STOP(在发送完最后一个字节后产生停止位),最后,若信息标志中应该不包含RT_I2C_NO_STOP,则应当等待TX FIFO空后在退出写操作。

static int _i2c_write(struct k230_i2c_dev *dev)
{
    volatile i2c_t *i2c = (i2c_t *)dev->base;
    rt_size_t start_time_tx = 0;
    rt_uint32_t tran_len = dev->msg->len;
    rt_uint8_t *buffer = dev->msg->buf;
    rt_uint32_t cmd = 0;
    rt_uint32_t cut = 0;

    if (i2c_xfer_init(dev) != RT_EOK)
    {
        return -RT_EBUSY;
    }

    start_time_tx = i2c_get_timer(0);
    while(tran_len)
    {
        if(i2c->status.tfnf)
        {
            /* If there is no stop flag, the stop condition will not be sent at the last byte. */
            if(tran_len == 1 && !(dev->msg->flags & RT_I2C_NO_STOP))
            {
                cmd |= I2C_DATA_CMD_STOP;
            }
            else
            {
                cmd &= ~I2C_DATA_CMD_STOP;
            }
            cmd |= *buffer++;
            writel(cmd, &i2c->data_cmd);
            cmd = 0;
            tran_len--;
            start_time_tx = i2c_get_timer(0);
        }
        else if(i2c_get_timer(start_time_tx) > dev->dev.timeout)
        {
            return -RT_ETIMEOUT;
        }
    }

    if (dev->msg->flags & RT_I2C_NO_STOP)
    {
        return RT_EOK;
    }

    if (i2c_wait_for_bus_busy(dev) != RT_EOK)
    {
        return -RT_EBUSY;
    }

    return RT_EOK;
}

I2C 从机

初始化从机

  初始化I2C控制器为从机模式,首先失能i2c,失能从机状态,检查从机是否配置了回调函数,然后复位从机状态,配置i2c控制寄存器,TX/RX FIFO的阈值,配置中断掩码寄存器的相应位,然后注册相应中断服务函数,使能i2c,使能相应中断。

static void i2c_slave_init(struct k230_i2c_dev *dev) {
    volatile i2c_t *i2c = (i2c_t *)dev->base;
    rt_uint8_t slave_address = dev->slave.address;

    i2c_enable(dev, RT_FALSE);
    dev->slave.status &= ~I2C_SLAVE_STATUS_ACTIVE;

    if(dev->slave.callback == RT_NULL)
    {
        LOG_E("i2c device %s slave callback is NULL\n", dev->name);
        return;
    }

    dev->slave.status = 0;
    writel(0, &i2c->con);
    i2c->con.rx_fifo_full_hld_ctrl = 1;
    i2c->con.restart_en = 1;
    i2c->con.stop_det_ifaddressed = 1;
    i2c->tx_tl.tl = I2C_TX_TL;
    i2c->rx_tl.tl = I2C_RX_TL;
    writel(0, &i2c->intr_mask);
    i2c->intr_mask.m_rx_full = 1;
    i2c->intr_mask.m_tx_abrt = 1;
    i2c->intr_mask.m_stop_det = 1;
    i2c->intr_mask.m_rx_under = 1;
    i2c->intr_mask.m_rd_req = 1;


    i2c->sar.sar = slave_address;

    rt_hw_interrupt_install(dev->vector, i2c_slave_isr, dev, dev->name);

    dev->slave.status |= I2C_SLAVE_STATUS_ACTIVE;

    i2c_enable(dev, RT_TRUE);

    rt_hw_interrupt_umask(dev->vector);
}

从机读写

  从机的数据数据维护在一个指定长度的缓冲区内,读取与写入数据本质上都是对这段缓冲区的操作,并且这段缓冲区还注册了dfs_file设备,可以通过文件系统进行读写,或者通过msh进行读写操作,以辅助i2c从机的使用。

  从机的读写区别在于接收到的从机的指令的区别,在主机写入从机的操作过程中,主要分为两步,首先是接收到写地址后,将地址定位到缓冲区,然后置位标志,将后续数据写入到缓冲区(若有要写的数据)。在主机读取时,会先写入要读取的地址(可选),然后再执行读操作,i2c在接收到读指令后,将缓冲区的数据读出。

static void i2c_slave_eeprom_callback(void *ctx, enum _i2c_slave_event event, rt_uint8_t *val)
{
    struct i2c_slave_eeprom *eeprom = ctx;

    switch (event)
    {
        case I2C_SLAVE_WRITE_RECEIVED:
            if (eeprom->flag_recv_ptr)
            {
                // write data
                _i2c_slave_write(eeprom, val);
            }
            else
            {
                // recv addr byte
                _i2c_slave_receive_addr(eeprom, val);
            }
            break;

        case I2C_SLAVE_READ_PROCESSED:
            /* The previous byte made it to the bus, get next one */
            _i2c_slave_read_add_increment(eeprom, val);
            /* fall through */
            __attribute__((fallthrough));
        case I2C_SLAVE_READ_REQUESTED:
            _i2c_slave_read(eeprom, val);
            /*
            * Do not increment buffer_idx here, because we don't know if
            * this byte will be actually used. Read Linux I2C slave docs
            * for details.
            */
            break;

        case I2C_SLAVE_STOP:
            if(eeprom->poll_event & POLLIN)
            {
                rt_wqueue_wakeup(&eeprom->device.wait_queue, (void*)POLLIN);
            }
            /* fall through */
            __attribute__((fallthrough));
        case I2C_SLAVE_WRITE_REQUESTED:
            eeprom->ptr = 0;
            eeprom->flag_recv_ptr = RT_FALSE;
            break;

        default:
            break;
    }
}

中断服务函数

  在中断函数中需要做的就是设置相应标志,调用回调函数,然后读写data_cmd寄存器以完成i2c操作。

static void i2c_slave_isr(int vector, void *param) {
    struct k230_i2c_dev *dev = (struct k230_i2c_dev *)param;
    volatile i2c_t *i2c = (i2c_t *)dev->base;
    rt_uint32_t stat, tmp;
    rt_uint8_t val = 0;

    if(!i2c->enable_status.en || !i2c->raw_intr_stat.activity)
    {
        return;
    }

    stat = i2c_slave_read_clear_intr(dev);
    i2c_ic_intr_stat_t intr_stat;
    memcpy(&intr_stat, &stat, sizeof(stat));

    if(intr_stat.r_rx_full)
    {
        if(!(dev->slave.status & I2C_SLAVE_STATUS_WRITE_IN_PROGRESS))
        {
            dev->slave.status |= I2C_SLAVE_STATUS_WRITE_IN_PROGRESS;
            dev->slave.status &= ~I2C_SLAVE_STATUS_READ_IN_PROGRESS;
            dev->slave.callback(dev->slave.callback_ctx, I2C_SLAVE_WRITE_REQUESTED, &val);
        }

        do
        {
            tmp = readl(&i2c->data_cmd);
            if(tmp & I2C_DATA_CMD_FIRST_DATA_BYTE)
            {
                dev->slave.callback(dev->slave.callback_ctx, I2C_SLAVE_WRITE_REQUESTED, &val);
            }
            val = tmp;
            dev->slave.callback(dev->slave.callback_ctx, I2C_SLAVE_WRITE_RECEIVED, &val);
        } while(i2c->status.rfne);
    }

    if(intr_stat.r_rd_req)
    {
        if(i2c->status.activity)
        {
            if(!(dev->slave.status & I2C_SLAVE_STATUS_READ_IN_PROGRESS))
            {
                dev->slave.status |= I2C_SLAVE_STATUS_READ_IN_PROGRESS;
                dev->slave.status &= ~I2C_SLAVE_STATUS_WRITE_IN_PROGRESS;
                dev->slave.callback(dev->slave.callback_ctx, I2C_SLAVE_READ_REQUESTED, &val);
            }
            else
            {
                dev->slave.callback(dev->slave.callback_ctx, I2C_SLAVE_READ_PROCESSED, &val);
            }
            writel(val, &i2c->data_cmd);
        }
    }

    if(intr_stat.r_stop_det)
    {
        dev->slave.callback(dev->slave.callback_ctx, I2C_SLAVE_STOP, &val);
    }
}

DMA支持

概述

  k230 拥有一个 8 条通道的 PDMA (Peripheral Direct Memory Access,外设直接内存访问),每条通道拥有64字节的数据缓冲区,通过一条 64 位的 AXI4 总线来传输不同通道的事务,并且要求外设数据地址只能以 4 字节对齐方式访问。PDMA 框图如图 2.1 所示。

assets/-K230学习笔记 02- I2C/file-20251127191132795.png
图2.1 PDMA 框图

  

DMA 配置

  配置 DMA 采用 k230 平台的 PDMA 驱动框架,步骤可以分为以下几步:

  1. 请求 PDMA 通道
  2. 注册 PDMA 回调
  3. 处理内存地址对齐
  4. 清除 PDMA 事件
  5. 配置 PDMA 参数 (外设、源地址、目标地址、传输大小、源类型 (传输方向)、外设大小、数据模式 (数据长度以及是否进行大小端交换)、突发长度、优先级、超时周期)
  6. 使能传输
  7. 等待传输完成/超时事件
  8. 释放资源

  在配置之前需要先初始化 I2C 自己的 DMA 相关寄存器,主要有使能位以及触发阈值设置位。

I2C DMA 写

  按照上述步骤,特别的点在于,配置写时外设大小配置为 32 位,每次写入整个寄存器,并且由于 I2C 拥有深度为 32 的 TX FIFO,所有可以配合触发阈值进行突发传输,这里配置的突发长度为4字节,所以阈值可以设置为 32 - 4 以下的值,具体代码如下。

static rt_err_t k230_i2c_pdma_write(struct k230_i2c_dev *dev)
{
    volatile i2c_t *i2c = (i2c_t *)dev->base;
    rt_uint8_t *buffer = dev->msg->buf;
    rt_uint32_t *buf;
    void *buf_pa;
    rt_err_t err;
    rt_uint8_t ch;
    rt_uint32_t recv_event, tran_len, flags;
    rt_size_t start_time_tx = 0;

    err = k230_pdma_request_channel(&ch);
    if (err != RT_EOK)
    {
        LOG_E("i2c pdma request channel failed");
        return err;
    }
    dev->pdma_cfg.ch = ch;

    err = k230_pdma_set_callback(ch, k230_i2c_pdma_call_back);
    if (err != RT_EOK)
    {
        LOG_E("i2c pdma set callback failed");
        k230_i2c_pdma_cleanup(dev);
        return err;
    }

    tran_len = dev->msg->len;

    buf = (rt_uint32_t *)rt_malloc_align(tran_len * sizeof(rt_uint32_t), K230_I2C_PDMA_CACHE_LINE_SIZE);
    if (buf == RT_NULL)
    {
        LOG_E("i2c pdma malloc buffer failed");
        k230_i2c_pdma_cleanup(dev);
        return -RT_ENOMEM;
    }
    for (rt_uint32_t i = 0; i < tran_len; i++)
    {
        flags = 0;

        if (i == (tran_len - 1) && !(dev->msg->flags & RT_I2C_NO_STOP))
        {
            flags |= I2C_DATA_CMD_STOP;
        }

        buf[i] = k230_i2c_make_data_cmd(buffer[i], flags);
    }

    buf_pa = rt_kmem_v2p(buf);
    if (buf_pa == RT_NULL)
    {
        LOG_E("i2c pdma get phy addr failed");
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return -RT_ERROR;
    }

    rt_event_control(dev->pdma_cfg.event, RT_IPC_CMD_RESET, NULL);

    dev->pdma_cfg.cfg.device = dev->pdma_cfg.tx_dev;
    dev->pdma_cfg.cfg.src_addr = (rt_uint8_t *)buf_pa;
    dev->pdma_cfg.cfg.dst_addr = (rt_uint8_t *)(dev->base_pa + 0x10);
    dev->pdma_cfg.cfg.line_size = tran_len * sizeof(rt_uint32_t);
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_src_type = CONTINUE;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_dev_hsize = PSBYTE4;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_dat_endian = PDEFAULT;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_dev_blen = PBURST_LEN_4;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_priority = 7;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_dev_tout = 0xFFF;

    err = k230_pdma_config(ch, &dev->pdma_cfg.cfg);
    if (err != RT_EOK)
    {
        LOG_E("i2c pdma config failed");
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return err;
    }

    if (k230_i2c_xfer_init(dev) != RT_EOK)
    {
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return -RT_EBUSY;
    }

    start_time_tx = k230_i2c_get_timer(0);
    err = k230_pdma_start(ch);
    if (err != RT_EOK)
    {
        LOG_E("i2c pdma start failed");
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return err;
    }

    err = rt_event_recv(dev->pdma_cfg.event,
                        K230_I2C_PDMA_EVENT_COMPLETE | K230_I2C_PDMA_EVENT_TIMEOUT,
                        RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
                        RT_WAITING_FOREVER,
                        &recv_event);

    if (err != RT_EOK || (recv_event & K230_I2C_PDMA_EVENT_TIMEOUT))
    {
        LOG_E("i2c pdma write timeout");
        k230_pdma_stop(ch);
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return -RT_ETIMEOUT;
    }

    k230_pdma_stop(ch);
    k230_i2c_pdma_cleanup(dev);
    rt_free_align(buf);

    if (k230_i2c_get_timer(start_time_tx) > dev->dev.timeout)
    {
        return -RT_ETIMEOUT;
    }

    return RT_EOK;
}

I2C DMA 读

  同样按照上述步骤进行配置。需要注意的点是突发传输的设置以及触发阈值的设定的问题,设置不好会导致剩余阈值下的数据不会触发 DMA 传输,比如设置 16 的触发阈值,如果读的数据长度是 4 的倍数 (PDMA 要求的),但是不是 16 的倍数,则会最后一次触发后 RX FIFO 中还会剩余下 4/8/12字节数据,所以这里设置突发长度以及触发阈值都为4。

  此外由于 I2C 的传输需要写 TX FIFO 触发,所以区别于 I2C DMA 写,在配置好并触发I2C DMA 读后需要向TX FIFO 写读命令以出发 I2C 操作。具体代码如下。

static rt_err_t k230_i2c_pdma_read(struct k230_i2c_dev *dev)
{
    volatile i2c_t *i2c = (i2c_t *)dev->base;
    rt_uint8_t *buffer = dev->msg->buf;
    rt_uint8_t *buf;
    void *buf_pa;
    rt_err_t err;
    rt_uint8_t ch;
    rt_uint32_t recv_event, read_len;
    rt_size_t start_time_tx = 0;
    rt_uint32_t cmd = 0;

    err = k230_pdma_request_channel(&ch);
    if (err != RT_EOK)
    {
        LOG_E("i2c pdma request channel failed");
        return err;
    }
    dev->pdma_cfg.ch = ch;

    err = k230_pdma_set_callback(ch, k230_i2c_pdma_call_back);
    if (err != RT_EOK)
    {
        LOG_E("i2c pdma set callback failed");
        k230_i2c_pdma_cleanup(dev);
        return err;
    }

    read_len = dev->msg->len;

    buf = (rt_uint8_t *)rt_malloc_align(read_len, K230_I2C_PDMA_CACHE_LINE_SIZE);
    if (buf == RT_NULL)
    {
        LOG_E("i2c pdma malloc buffer failed");
        k230_i2c_pdma_cleanup(dev);
        return -RT_ENOMEM;
    }
    buf_pa = rt_kmem_v2p(buf);
    if (buf_pa == RT_NULL)
    {
        LOG_E("i2c pdma get phy addr failed");
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return -RT_ERROR;
    }

    rt_event_control(dev->pdma_cfg.event, RT_IPC_CMD_RESET, NULL);

    dev->pdma_cfg.cfg.device = dev->pdma_cfg.rx_dev;
    dev->pdma_cfg.cfg.src_addr = (rt_uint8_t *)(dev->base_pa + 0x10);
    dev->pdma_cfg.cfg.dst_addr = (rt_uint8_t *)buf_pa;
    dev->pdma_cfg.cfg.line_size = read_len;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_src_type = FIXED;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_dev_hsize = PSBYTE1;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_dat_endian = PDEFAULT;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_dev_blen = PBURST_LEN_4;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_priority = 7;
    dev->pdma_cfg.cfg.pdma_ch_cfg.ch_dev_tout = 0xFFF;

    err = k230_pdma_config(ch, &dev->pdma_cfg.cfg);
    if (err != RT_EOK)
    {
        LOG_E("i2c pdma config failed");
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return err;
    }

    /* If no start condition is sent before reading, then send a repeated start. */
    if (dev->msg->flags & RT_I2C_NO_START)
    {
        cmd |= I2C_DATA_CMD_RESTART;
    }
    else
    {
        if (k230_i2c_xfer_init(dev) != RT_EOK)
        {
            LOG_E("i2c pdma xfer init failed");
            k230_i2c_pdma_cleanup(dev);
            rt_free_align(buf);
            return -RT_EBUSY;
        }
    }

    start_time_tx = k230_i2c_get_timer(0);
    err = k230_pdma_start(ch);
    if (err != RT_EOK)
    {
        LOG_E("i2c pdma start failed");
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return err;
    }

    for (rt_uint32_t i = 0; i < read_len; i++)
    {
        while (i2c->status.tfnf == 0);
        /* Write stop when the last byte */
        cmd = i == (read_len - 1) ? I2C_DATA_CMD_STOP : 0;
        /* Write to data cmd register to trigger i2c */
        writel(cmd | I2C_DATA_CMD_READ, &i2c->data_cmd);
        cmd = 0;
    }

    err = rt_event_recv(dev->pdma_cfg.event,
                        K230_I2C_PDMA_EVENT_COMPLETE | K230_I2C_PDMA_EVENT_TIMEOUT,
                        RT_EVENT_FLAG_OR | RT_EVENT_FLAG_CLEAR,
                        RT_WAITING_FOREVER,
                        &recv_event);

    if (err != RT_EOK || (recv_event & K230_I2C_PDMA_EVENT_TIMEOUT))
    {
        LOG_E("i2c pdma read timeout");
        k230_pdma_stop(ch);
        k230_i2c_pdma_cleanup(dev);
        rt_free_align(buf);
        return -RT_ETIMEOUT;
    }

    rt_memcpy(buffer, buf, read_len);

    k230_pdma_stop(ch);
    k230_i2c_pdma_cleanup(dev);
    rt_free_align(buf);

    if (k230_i2c_get_timer(start_time_tx) > dev->dev.timeout)
    {
        return -RT_ETIMEOUT;
    }

    return k230_i2c_xfer_finish(dev);
}

测试

I2C主机

  主机的测试对象是EEPROM,型号为AT24C08,测试代码如下。

static void _test_i2c0_master(rt_uint8_t *buffer_w, rt_uint8_t *buffer_r, rt_uint32_t size, rt_uint32_t speed)
{
    rt_err_t ret = RT_EOK;
    struct rt_i2c_bus_device *dev;
    struct rt_i2c_msg msgs[2];

    dev = rt_i2c_bus_device_find(I2C_NAME);
    uassert_not_null(dev);
    rt_i2c_control(dev, RT_I2C_DEV_CTRL_CLK, (void *)&speed);

    msgs[0].addr  = TARGET_ADDR;
    msgs[0].flags = RT_I2C_WR;
    msgs[0].buf   = buffer_w;
    msgs[0].len   = size + 1;

    if(rt_i2c_transfer(dev, msgs, 1) != 1)
    {
        LOG_E("i2c transfer failed");
        uassert_true(0);
    }

    rt_thread_mdelay(10);

    msgs[0].addr  = TARGET_ADDR;
    msgs[0].flags = RT_I2C_WR | RT_I2C_NO_STOP;
    msgs[0].buf   = &buffer_r[0];
    msgs[0].len   = 1;

    msgs[1].addr  = TARGET_ADDR;
    msgs[1].flags = RT_I2C_RD | RT_I2C_NO_START;
    msgs[1].buf   = &buffer_r[1];
    msgs[1].len   = size;

    if(rt_i2c_transfer(dev, msgs, 2) != 2)
    {
        LOG_E("i2c transfer failed");
        uassert_true(0);
    }

    LOG_I("Read data:\n");
    for(rt_uint8_t i = 1; i < size + 1; i++)
    {
        LOG_I("0x%02X ", buffer_r[i]);
    }
    uassert_buf_equal(buffer_w + 1, buffer_r + 1, size);
}

  值得注意的是,在开始测试前,可以先检测i2c总线的状态,避免出现i2c死锁的情况。

static int test_i2c_check_pin(void)
{
    test_i2c0_deinit_pin();

    if(kd_pin_read(I2C_SCL_PIN) != 1 || kd_pin_read(I2C_SDA_PIN) != 1)
    {
        LOG_W("i2c bus is not idle, try to recover it.");
        k230_pinctrl_set_oe(I2C_SCL_PIN, 1);
        kd_pin_mode(I2C_SCL_PIN, GPIO_DM_OUTPUT);
        for(rt_uint8_t i = 0; i < 9; i++)
        {
            kd_pin_write(I2C_SCL_PIN, 0);
            rt_hw_us_delay(2);
            kd_pin_write(I2C_SCL_PIN, 1);
            rt_hw_us_delay(2);
        }
        k230_pinctrl_set_oe(I2C_SCL_PIN, 0);
        kd_pin_mode(I2C_SCL_PIN, GPIO_DM_INPUT);
    }

    if(kd_pin_read(I2C_SCL_PIN) != 1 || kd_pin_read(I2C_SDA_PIN) != 1)
    {
        LOG_E("i2c bus recover failed");
        return -RT_ERROR;
    }

    LOG_I("i2c bus(pin: %u, %u) is idle, init i2c bus pin", I2C_SCL_PIN, I2C_SDA_PIN);
    test_i2c0_init_pin();

    return RT_EOK;
}

  测试时首先按照400KHz的速度进行写读测试,第一次向EEPROM的0地址连续写入16字节数据(0xAA),然后再读出这16字节数据做校验,写数据的时序如图3.1所示,读数据的时序如图3.2所示。

assets/-K230学习笔记 02- I2C/file-20251025160837258.jpg
图3.1 400KHz写时序图
assets/-K230学习笔记 02- I2C/file-20251025160916833.jpg
图3.2 400KHz读时序图

  第二次测试时向EEPROM的0地址连续写入16字节数据(0x55),然后再读出这16字节数据做校验,写数据的时序如图3.3所示,读数据的时序如图3.4所示。

assets/-K230学习笔记 02- I2C/file-20251025160942274.jpg
图3.1 1MHz写时序图
assets/-K230学习笔记 02- I2C/file-20251025161014243.jpg
图3.2 1MHz读时序图

  可以从上述读时序中看出在写完读地址后并没有停止时序,这就是置位了相应标志后的作用,这种情况就可以先避免发送停止信号后,再发送完整发送初始化(开始条件+设备地址)来读数据。

  测试时msh的记录如下所示:

OpenSBI v1.2.2

 \ | /
- RT -     Thread Smart Operating System
 / | \     5.2.2 build Oct 25 2025 15:58:34
 2006 - 2024 Copyright by RT-Thread team
lwIP-2.1.2 initialized!
[I/drv_i2c] i2c0 clock=100000000Hz

[I/drv_i2c] i2c0 master mode

[I/sal.skt] Socket Abstraction Layer initialize success.
[I/utest] utest is initialize success.
[I/utest] total utest testcase num: (4)
[I/drivers.serial] Using /dev/ttyS0 as default console
tmpfs mounted on /dev/shm success!
file system initialization done!
Press any key to stop init process startup ... 3
Press any key to stop init process startup ... 2
Press any key to stop init process startup ... 1
Starting init ...
Hello RISC-V
msh />utest_run bsp.k230.drivers.i2c
[I/utest] [==========] [ utest    ] loop 1/1
[I/utest] [==========] [ utest    ] started
[I/utest] [----------] [ testcase ] (bsp.k230.drivers.i2c) started
[I/utest] i2c bus(pin: 48, 49) is idle, init i2c bus pin
[I/utest] This is a i2c test case.

[I/utest] [==========] utest unit name: (test_i2c0_master_slave)
[I/utest] Read data:

[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] 0xAA 
[I/utest] Read data:

[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] 0x55 
[I/utest] [  PASSED  ] [ result   ] testcase (bsp.k230.drivers.i2c)
[I/utest] i2c bus pin deinit.

[I/utest] [----------] [ testcase ] (bsp.k230.drivers.i2c) finished
[I/utest] [==========] [ utest    ] 1 tests from 4 testcase ran.
[I/utest] [  PASSED  ] [ result   ] 1 tests.
[I/utest] [==========] [ utest    ] finished
msh />

I2C从机

  从机的测试也与主机类似,通过CH341T模块充当i2c主机,而k230 i2c控制器配置为从机,通过CH341T读写k230的指定缓冲区。读数据时的CH341T模块上位机如图3.5所示,读时序如图3.6所示,写数据时的CH341T模块上位机如图3.7所示,读时序如图3.8所示。

assets/-K230学习笔记 02- I2C/file-20251025014418585.jpg
图3.5 读上位机页面
assets/-K230学习笔记 02- I2C/file-20251025014744367.jpg
图3.6 读时序
assets/-K230学习笔记 02- I2C/file-20251025015120576.jpg
图3.7 写上位机页面
assets/-K230学习笔记 02- I2C/file-20251025015108726.jpg
图3.8 写时序

  测试时msh的记录如下所示:

OpenSBI v1.2.2

 \ | /
- RT -     Thread Smart Operating System
 / | \     5.2.2 build Oct 25 2025 01:21:25
 2006 - 2024 Copyright by RT-Thread team
lwIP-2.1.2 initialized!
[I/drv_i2c] i2c0 clock=100000000Hz

[I/drv_i2c] i2c0 slave address=0x50

[I/drv_i2c] slave-eeprom size: 256
[I/sal.skt] Socket Abstraction Layer initialize success.
[I/utest] utest is initialize success.
[I/utest] total utest testcase num: (4)
[I/drivers.serial] Using /dev/ttyS0 as default console
tmpfs mounted on /dev/shm success!
file system initialization done!
Press any key to stop init process startup ... 3
Press any key to stop init process startup ... 2
Press any key to stop init process startup ... 1
Starting init ...
Hello RISC-V
msh />cat /dev/i2c-slv-eeprom

msh />echo "0123" /dev/i2c-slv-eeprom
msh />cat /dev/i2c-slv-eeprom
0123
msh />utest_run bsp.k230.drivers.i2c
[I/utest] [==========] [ utest    ] loop 1/1
[I/utest] [==========] [ utest    ] started
[I/utest] [----------] [ testcase ] (bsp.k230.drivers.i2c) started
[I/utest] i2c bus(pin: 48, 49) is idle, init i2c bus pin
[I/utest] This is a i2c test case.

[I/utest] [==========] utest unit name: (test_i2c0_master_slave)
[I/utest] slave buffer data:

[I/utest] 0x34 
[I/utest] 0x35 
[I/utest] 0x36 
[I/utest] 0x37 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] 0x00 
[I/utest] [  PASSED  ] [ result   ] testcase (bsp.k230.drivers.i2c)
[I/utest] i2c bus pin deinit.

[I/utest] [----------] [ testcase ] (bsp.k230.drivers.i2c) finished
[I/utest] [==========] [ utest    ] 1 tests from 4 testcase ran.
[I/utest] [  PASSED  ] [ result   ] 1 tests.
[I/utest] [==========] [ utest    ] finished
msh />cat /dev/i2c-slv-eeprom
4567
msh />

测试图

  测试图如图3.9,3.10所示,分别是测试k230 i2c主机与从机的测试场景图。

assets/-K230学习笔记 02- I2C/IMG_20251025_014403.jpg
图3.9 主机测试场景图
assets/-K230学习笔记 02- I2C/IMG_20251025_170326.jpg
图3.9 从机测试场景图
posted @ 2025-10-26 14:31  Ze-Hou  阅读(21)  评论(0)    收藏  举报