流水源 发表于 2026-8-9 15:08:15

【瑞萨RA-eco-RA6M4评测】实现I2C点亮oled屏显示

本次学习I2C外设应用,以点亮I2C接口OLED12864屏为例子。开发板关于I2C接口引脚如下:
!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/145538qkjn3717jwahkqun.png)

这里使用SCI3外设的I2C接口,引脚为P408和P409。

接着打开RASC之前工程,配置I2C初始化。先配置引脚:
!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/145717sdy800ca8khsn0qd.png)

接着添加I2C外设驱动SDK,配置I2C接口参数。
!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/145831xxv953apxa3gsasv.png)

勾选I2C组件库。
!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/150002msksexexzxetihtt.png)

最后生成KEIL工程。
!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/150129qwi3d4d3wuj42uwu.png)

在keil工程内添加I2C驱动代码:

```
/*====================================================================*
*多路 I2C 总线上下文结构体
*====================================================================*/
typedef struct ra6m4_i2c_para_t
{
    sci_i2c_instance_ctrl_t         *_i2c;            /**< I2C 控制器实例指针 */
    volatile uint8_t                i2c_channel;       /**< SCI 通道号 (与 FSP callback p_args->channel 对应) */
    volatile uint8_t                i2c_transfer_done;/**< 传输完成标志 (回调中置 1) */
    volatile i2c_master_event_t   i2c_event;          /**< 当前传输事件 */
    int                           timeout_us;         /**< 超时倒计时 (us) */

} ra6m4_i2c_para_t;

/*====================================================================*
*全局变量
*====================================================================*/

/** 总线上下文数组 — 每条总线一个条目 */
ra6m4_i2c_para_t user_i2c_para;


/*====================================================================*
*中断回调函数 (所有总线共用)
*====================================================================*/
void sci_i2c_master_callback(i2c_master_callback_args_t *p_args)
{
    /*-----------------------------------------------------------------*
   * 派发策略 1 (优先): 通过 p_context 直接定位 — O(1)
   * drv_i2c_init() 中 CallbackSet 将 &user_i2c_para 写入 p_context
   *-----------------------------------------------------------------*/
    ra6m4_i2c_para_t *para = (ra6m4_i2c_para_t *)p_args->p_context;

    if (NULL != para)
    {
      para->i2c_event         = p_args->event;
      para->i2c_transfer_done= 1;
      return;
    }
//    /*-----------------------------------------------------------------*
//   * 派发策略 2 (回退): 通过 channel 匹配 — O(N)
//   * 当 p_context 为 NULL (未调用 CallbackSet) 时使用
//   *-----------------------------------------------------------------*/
//    for (uint8_t i = 0; i < I2C_BUS_NUM; i++)
//    {
//      if (user_i2c_para.i2c_channel == p_args->channel)
//      {
//            user_i2c_para.i2c_event         = p_args->event;
//            user_i2c_para.i2c_transfer_done= 1;
//            break;
//      }
//    }
}

/*====================================================================*
*初始化
*====================================================================*/
void drv_i2c_init(void)
{
    /*--- Bus 0: SCI0 ---*/
    user_i2c_para._i2c            = &g_i2c0_ctrl;
    user_i2c_para.i2c_channel       = 2;                            /* SCI0 → FSP channel 0 */
    user_i2c_para.i2c_transfer_done = 0;
    user_i2c_para.i2c_event         = I2C_MASTER_EVENT_ABORTED;
    user_i2c_para.timeout_us      = I2C_TIMEOUT_US;

    R_SCI_I2C_Open(&g_i2c0_ctrl, &g_i2c0_cfg);
    /* 设置回调上下文指针: 回调中可直接通过 p_args->p_context 定位总线, O(1) */
    R_SCI_I2C_CallbackSet(&g_i2c0_ctrl, sci_i2c_master_callback,
                           &user_i2c_para, NULL);

    /*--- Bus 1: SCI1 (需在 FSP 配置器中添加) ---*/
    user_i2c_para._i2c            = &g_i2c1_ctrl;
    user_i2c_para.i2c_channel       = 3;                            /* SCI1 → FSP channel 1 */
    user_i2c_para.i2c_transfer_done = 0;
    user_i2c_para.i2c_event         = I2C_MASTER_EVENT_ABORTED;
    user_i2c_para.timeout_us      = I2C_TIMEOUT_US;

    R_SCI_I2C_Open(&g_i2c1_ctrl, &g_i2c1_cfg);
    R_SCI_I2C_CallbackSet(&g_i2c1_ctrl, sci_i2c_master_callback,
                           &user_i2c_para, NULL);
}

/*====================================================================*
*内部辅助函数
*====================================================================*/

/**
* @brief等待指定总线的 I2C 传输完成
*
* 轮询 i2c_transfer_done 标志:
*   - false 且超时未到 → 继续等待
*   - true→ 回调已触发, 检查事件是否匹配
*   - 超时 → 返回失败 (从机无响应)
*
* @param   bus            总线索引
* @param   expected_event    期望事件 (TX_COMPLETE / RX_COMPLETE)
* @retval0成功
* @retval1失败 (超时或收到异常事件)
*/
static uint8_t I2C_WaitDone(uint8_t bus, i2c_master_event_t expected_event)
{
    ra6m4_i2c_para_t *para = &user_i2c_para;

    while (!para->i2c_transfer_done && (para->timeout_us > 0))
    {
      R_BSP_SoftwareDelay(1U, BSP_DELAY_UNITS_MICROSECONDS);
      para->timeout_us--;
    }

    /* 清除标志, 为下次传输做准备 */
    para->i2c_transfer_done = 0;

    if (para->timeout_us <= 0)
      return 1;                     /* 超时: 从机无响应 */

    if (para->i2c_event != expected_event)
      return 1;                     /* 收到异常事件 (如 NACK → ABORTED) */

    return 0;                           /* 成功 */
}

/**
* @brief将寄存器地址填入缓冲区
*
* 自动判断 8 位 / 16 位地址:
*   - addr <= 0xFF   → 1 字节
*   - addr >0xFF   → 2 字节 (大端序: 高字节在前)
*
* @param   buff目标缓冲区 (至少 2 字节)
* @param   addr寄存器地址
* @retval      地址字节数 (1 或 2)
*/
static uint8_t I2C_FillAddr(uint8_t *buff, uint32_t addr)
{
    if (addr > 0xFF)
    {
      buff = (uint8_t)((addr >> 8) & 0xFF);
      buff = (uint8_t)(addr & 0xFF);
      return 2;
    }
    else
    {
      buff = (uint8_t)(addr & 0xFF);
      return 1;
    }
}

/**
* @brief启动一次写传输并等待完成 (指定总线)
*/
static uint8_t I2C_DoWrite(uint8_t bus, uint8_t *data, uint32_t len, bool restart)
{
    ra6m4_i2c_para_t *para = &user_i2c_para;

    /* 重置状态, 为新传输做准备 */
    para->i2c_event          = I2C_MASTER_EVENT_ABORTED;
    para->i2c_transfer_done= 0;
    para->timeout_us         = I2C_TIMEOUT_US;

    if (FSP_SUCCESS != R_SCI_I2C_Write(para->_i2c, data, len, restart))
      return 1;

    return I2C_WaitDone(bus, I2C_MASTER_EVENT_TX_COMPLETE);
}

/**
* @brief启动一次读传输并等待完成 (指定总线)
*/
static uint8_t I2C_DoRead(uint8_t bus, uint8_t *data, uint32_t len, bool restart)
{
    ra6m4_i2c_para_t *para = &user_i2c_para;

    /* 重置状态, 为新传输做准备 */
    para->i2c_event          = I2C_MASTER_EVENT_ABORTED;
    para->i2c_transfer_done= 0;
    para->timeout_us         = I2C_TIMEOUT_US;

    if (FSP_SUCCESS != R_SCI_I2C_Read(para->_i2c, data, len, restart))
      return 1;

    return I2C_WaitDone(bus, I2C_MASTER_EVENT_RX_COMPLETE);
}

/*====================================================================*
*公共 API 实现
*====================================================================*/

uint8_t I2C_WriteByte(uint8_t bus, uint32_t dev_addr, uint32_t addr, uint8_t Byte)
{
    uint8_t buff;
    uint8_t addr_len;

    if (bus >= I2C_BUS_NUM)
      return 1;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr,
    (dev_addr > 0xff) ? I2C_MASTER_ADDR_MODE_10BIT : I2C_MASTER_ADDR_MODE_7BIT))
      return 1;

    addr_len = I2C_FillAddr(buff, addr);
    buff = Byte;

    /* START + slaveW + addr[..] + Byte + STOP */
    return I2C_DoWrite(bus, buff, addr_len + 1, false);
}

uint8_t I2C_ReadByte(uint8_t bus, uint32_t dev_addr, uint32_t addr)
{
    uint8_t buff;
    uint8_t addr_len;

    if (bus >= I2C_BUS_NUM)
      return 0;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr,
    (dev_addr > 0xff) ? I2C_MASTER_ADDR_MODE_10BIT : I2C_MASTER_ADDR_MODE_7BIT))
      return 0;

    addr_len = I2C_FillAddr(buff, addr);

    /* 写寄存器地址, 发 RESTART (不发 STOP, 保持总线占用) */
    if (I2C_DoWrite(bus, buff, addr_len, true))
      return 0;

    /* 读 1 字节数据 */
    if (I2C_DoRead(bus, buff, 1, false))
      return 0;

    return buff;
}

uint8_t I2C_PageWrite(uint8_t bus, uint32_t dev_addr, uint32_t addr, uint8_t *str, uint32_t num)
{
    uint8_t buff;
    uint8_t addr_len;

    if ((bus >= I2C_BUS_NUM) || (NULL == str) || (0 == num) || (num > (I2C_BUF_MAX_SIZE-4)))
      return 1;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr, I2C_MASTER_ADDR_MODE_7BIT))
      return 1;

    /* 将寄存器地址 + 数据合并到同一缓冲区, 单次 I2C 事务发送:
   *   START + slaveW + addr[..] + data + STOP
   */
    addr_len = I2C_FillAddr(buff, addr);
    memcpy(&buff, str, num);

    return I2C_DoWrite(bus, buff, (uint32_t)(addr_len + num), false);
}

uint8_t I2C_PageRead(uint8_t bus, uint32_t dev_addr, uint32_t addr, uint8_t *str, uint32_t num)
{
    uint8_t buff;
    uint8_t addr_len;

    if ((bus >= I2C_BUS_NUM) || (NULL == str) || (0 == num))
      return 1;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr,
    (dev_addr > 0xff) ? I2C_MASTER_ADDR_MODE_10BIT : I2C_MASTER_ADDR_MODE_7BIT))
      return 1;

    addr_len = I2C_FillAddr(buff, addr);

    /* 写寄存器地址, 发 RESTART */
    if (I2C_DoWrite(bus, buff, addr_len, true))      return 1;

    /* 读 num 字节数据 (直接读入用户缓冲区) */
    return I2C_DoRead(bus, str, num, false);
}

uint8_t I2C_Write(uint8_t bus, uint32_t dev_addr, uint8_t *cmd, uint32_t len, uint8_t *str, uint32_t num)
{
    uint8_t buff;
    uint32_t total;

    if (bus >= I2C_BUS_NUM)
      return 1;

    total = len + num;
    if ((0 == total) || (total > I2C_BUF_MAX_SIZE))
      return 1;
    if ((len > 0) && (NULL == cmd))
      return 1;
    if ((num > 0) && (NULL == str))
      return 1;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr, I2C_MASTER_ADDR_MODE_7BIT))
      return 1;

    /* 将 cmd + str 合并到同一缓冲区, 单次 I2C 事务发送:
   *   START + slaveW + cmd + str + STOP
   */
    if (len > 0)      memcpy(buff, cmd, len);
    if (num > 0)      memcpy(&buff, str, num);

    return I2C_DoWrite(bus, buff, total, false);
}

uint8_t I2C_Read(uint8_t bus, uint32_t dev_addr, uint8_t *cmd, uint32_t len, uint8_t *str, uint32_t num)
{
    if ((bus >= I2C_BUS_NUM) || (NULL == cmd) || (NULL == str) || (0 == len) || (0 == num))
      return 1;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr,
    (dev_addr > 0xff) ? I2C_MASTER_ADDR_MODE_10BIT : I2C_MASTER_ADDR_MODE_7BIT))
      return 1;

    /* 发送命令字节, 发 RESTART (不发 STOP) */
    if (I2C_DoWrite(bus, cmd, len, true))      return 1;

    /* 读 num 字节数据 */
    return I2C_DoRead(bus, str, num, false);
}

uint8_t I2C_WriteOnly(uint8_t bus, uint32_t dev_addr, uint8_t *str, uint32_t num)
{
    if ((bus >= I2C_BUS_NUM) || (NULL == str) || (0 == num))
      return 1;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr,
    (dev_addr > 0xff) ? I2C_MASTER_ADDR_MODE_10BIT : I2C_MASTER_ADDR_MODE_7BIT))
      return 1;

    return I2C_DoWrite(bus, str, num, false);
}

uint8_t I2C_ReadOnly(uint8_t bus, uint32_t dev_addr, uint8_t *str, uint32_t num)
{
    if ((bus >= I2C_BUS_NUM) || (NULL == str) || (0 == num))
      return 1;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr,
    (dev_addr > 0xff) ? I2C_MASTER_ADDR_MODE_10BIT : I2C_MASTER_ADDR_MODE_7BIT))
      return 1;

    return I2C_DoRead(bus, str, num, false);
}

uint8_t I2C_IsDeviceReady(uint8_t bus, uint32_t dev_addr)
{
    if (bus >= I2C_BUS_NUM)
      return 1;

    if (FSP_SUCCESS != R_SCI_I2C_SlaveAddressSet(user_i2c_para._i2c,
                                                   dev_addr,
    (dev_addr > 0xff) ? I2C_MASTER_ADDR_MODE_10BIT : I2C_MASTER_ADDR_MODE_7BIT))
      return 1;

    /* 发送 0 字节写: START + slaveW + STOP
   * 从机 ACK→ TX_COMPLETE → 返回 0 (就绪)
   * 从机 NACK → ABORTED   → 返回 1 (无响应)
   */
    return I2C_DoWrite(bus, NULL, 0, false);
}
```

接着添加OLED12864的驱动代码,以及字库。
!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/150340s2pa88282j6l10l2.png)
在hal_entry.c中调用初始化i2c,然后测试oled显示代码:
!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/150513f7f7sdrois87ox9g.png)

最终显示效果如下:
!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/150537zxs9yczvczyvoz7d.png)

!(https://www.eefocus.com/forum/data/attachment/forum/202608/09/150551uo5uhaza6znt3jea.png)





页: [1]
查看完整版本: 【瑞萨RA-eco-RA6M4评测】实现I2C点亮oled屏显示