跳转至

i2c开发指南

本文档以 i2c_master_demo.ci2c_slave_demo.c 为例,带你在 HiDiTing 开发板上快速跑通第一个 I2C 通信应用,并了解如何基于它构建自己的应用。


I2C 驱动背景知识

I2C 工作原理

I2C(Inter-Integrated Circuit)模块是APB总线上的设备,是I2C总线上的主设备,支持多主设备时的总线仲裁。该模块内部包含8bit×8发送和接收FIFO各一个,用于主从设备之间的通信和数据传输:

功能 说明 应用场景
主设备发送 主设备向指定从设备发送数据 配置传感器、写入存储器、控制外设
主设备接收 主设备从指定从设备读取数据 读取传感器数据、读取存储器内容
从设备发送 从设备响应主设备请求发送数据 响应主设备读取请求
从设备接收 从设备接收主设备发送的数据 接收主设备配置命令

说明:

主从设备

  • 主设备(Master):发起通信、产生时钟信号、决定通信方向的设备。
  • 从设备(Slave):响应主设备请求,不能主动发起通信。
  • 本示例中,主设备和从设备分别运行在不同的开发板上,通过 I2C 总线连接通信。

I2C 支持手动模式和自动模式。相关功能宏在 /src/build/config/target_config/3322/menuconfig/acore 下按构建目标对应的 .config 文件中配置。

  • 手动模式:支持轮询模式读写、中断模式读写以及DMA模式读写。 CONFIG_I2C_SUPPORT_DMA:支持DMA。 CONFIG_I2C_SUPPORT_INT:支持中断。
  • 自动模式:支持DMA模式和轮询模式自动切换。I2C自动模式配置宏定义后,按照轮询的读写方式即可。 CONFIG_I2C_SUPPORT_DMA:支持DMA。 CONFIG_I2C_SUPPORT_POLL_AND_DMA_AUTO_SWITCH:支持自动识别传输模式。 CONFIG_I2C_POLL_AND_DMA_AUTO_SWITCH_THRESHOLD=0x8:自动识别传输阈值,大于阈值使用DMA模式,小于等于阈值使用轮询模式,阈值大小可调。

注意事项:

  • 需要主动确保数据发送buffer和数据接收buffer不可传入空指针。
  • 当发送的数据大于对接设备的可接受范围时,会发送失败;如果发送数据失败,再切换另一个I2C设备继续发送时,会造成总线挂死,所有I2C设备都无法正确发送数据。
  • 手动中断模式和自动切换模式互斥。

操作流程

本示例支持主设备和从设备两种角色,每种角色都遵循标准的初始化流程:

通用初始化步骤(所有操作共享):

  1. 引脚初始化:调用 uapi_pin_set_ie() 使能输入功能,调用 uapi_pin_set_mode() 将引脚配置为 I2C 功能。
  2. I2C 初始化:调用 uapi_i2c_master_init() 或 uapi_i2c_slave_init() 初始化I2C 设备。
  3. 数据配置:配置发送和接收缓冲区。

各角色特有流程:

角色 流程
主设备发送 初始化引脚 → 初始化主设备 → 配置数据 → 调用 uapi_i2c_master_write() 发送数据
主设备接收 初始化引脚 → 初始化主设备 → 配置数据 → 调用 uapi_i2c_master_read() 接收数据
从设备发送 初始化引脚 → 初始化从设备 → 配置数据 → 调用 uapi_i2c_slave_write() 发送数据
从设备接收 初始化引脚 → 初始化从设备 → 配置数据 → 调用 uapi_i2c_slave_read() 接收数据

接口说明:本示例基于AT命令框架实现,通过串口发送AT命令控制I2C通信,每个操作函数都封装了完整的初始化、配置、通信和去初始化流程。

关键参数说明

参数 说明 范围
slaveAddr 从设备地址 0-127
masterBusId / slaveBusId I2C 总线 ID 0-3
sclPin SCL 时钟引脚号 4-11
sdaPin SDA 数据引脚号 4-11
sclMode SCL 引脚工作模式 0-1
sdaMode SDA 引脚工作模式 0-1
type 收发模式(0:主发从收/1:从发主收) 0-1

宏定义说明

宏定义 说明
I2C_MASTER_ADDR 0x0 I2C 主设备地址
I2C_SET_BAUDRATE 400000 I2C 通信波特率
I2C_TASK_DURATION_MS 3000 任务循环间隔时间(毫秒)
CONFIG_I2C_TRANSFER_LEN 8 单次传输的数据长度
I2C_SLAVE_ADDR 0x8 I2C 从设备地址

API 接口列表

本文档示例代码中使用的接口:

接口函数 说明
uapi_i2c_master_init 初始化 I2C 主设备
uapi_i2c_master_write I2C 主设备发送数据
uapi_i2c_master_read I2C 主设备接收数据
uapi_i2c_slave_init 初始化 I2C 从设备
uapi_i2c_slave_write I2C 从设备发送数据
uapi_i2c_slave_read I2C 从设备接收数据
uapi_i2c_deinit 去初始化 I2C

说明: 示例中同时使用了 uapi_pin_set_ie()uapi_pin_set_mode() 进行引脚配置,以及 uapi_watchdog_kick() 进行喂狗操作。

完整 API 列表

更多 I2C 接口(如 DMA 模式、中断模式、波特率配置等)请参考:I2C API 参考


快速跑通 I2C Demo

功能说明

I2C Demo 通过 AT 命令方式提供交互式控制,支持以下命令:

AT 命令 功能 参数说明
AT+STARTI2CMASTER=slaveAddr,masterBusId,sclPin,sdaPin,sclMode,sdaMode,type 启动 I2C 主设备通信 参数详见关键参数说明
AT+STARTI2CSLAVE=slaveAddr,slaveBusId,sclPin,sdaPin,sclMode,sdaMode,type 启动 I2C 从设备通信 参数详见关键参数说明

编译

完成一站式 CLI 环境配置后执行:

# 编译固件,编译生成的固件从 output/3322/fwpkg 中获取 diting-community.fwpkg
fbb set-target pack_diting_community
fbb build

构建成功后,使用一站式 CLI 烧写固件并打开 UART2 串口监视器。以下为 Windows USB DFU 示例;将 COM3 替换为实际日志串口,其他平台和串口烧写参数参见一站式 CLI 开发环境使用指南

fbb flash -f "$env:FBB_SDK_DIR\output\3322\fwpkg\diting-community.fwpkg" --chip 3322 -d --timeout 180
fbb monitor --port COM3 --baud 750000

使用方式

  1. 烧录固件并启动设备。
  2. 需用两块板子,找到i2c通道引脚(SCL,SDA,GND),用杜邦线把两块板子的i2c通道引脚对应连接起来:本示例是以I2C3为例测试。
  3. 通过串口发送 AT 命令,格式如下:

    # I2C 主设备发送示例:从设备地址0x8,总线ID 3,SCL引脚10,SDA引脚11,模式1,发送数据
    AT+STARTI2CMASTER=8,3,10,11,1,1,0
    
    # I2C 主设备接收示例:从设备地址0x8,总线ID 3,SCL引脚10,SDA引脚11,模式1,接收数据
    AT+STARTI2CMASTER=8,3,10,11,1,1,1
    
    # I2C 从设备接收示例:从设备地址0x8,总线ID 3,SCL引脚10,SDA引脚11,模式1,接收数据
    AT+STARTI2CSLAVE=8,3,10,11,1,1,0
    
    # I2C 从设备发送示例:从设备地址0x8,总线ID 3,SCL引脚10,SDA引脚11,模式1,发送数据
    AT+STARTI2CSLAVE=8,3,10,11,1,1,1
    
    注意

先启动主设备,后启动从设备。

先启动主设备,后启动从设备

预期结果

I2C 主设备发送(AT+STARTI2CMASTER)

I2C 主设备发送(AT+STARTI2CMASTER)

I2C 主设备接收(AT+STARTI2CMASTER)

I2C 主设备接收(AT+STARTI2CMASTER)

I2C 从设备接收(AT+STARTI2CSLAVE)

I2C 从设备接收(AT+STARTI2CSLAVE)

I2C 从设备发送(AT+STARTI2CSLAVE)

I2C 从设备发送(AT+STARTI2CSLAVE)


文件结构与代码走读

文件结构

I2C Demo 的文件位于以下路径:

/samples/native_samples/i2c/
├── i2c_master_demo.c          # I2C 主设备示例实现文件
├── i2c_slave_demo.c           # I2C 从设备示例实现文件
├── i2c_demo.h                 # I2C 示例头文件,包含数据结构与命令表定义
└── CMakeLists.txt             # 编译配置

各文件职责

文件 职责 关键内容
i2c_master_demo.c I2C 主设备示例实现 at_i2c_master_start_sample(), i2c_master_task(), i2c_master_write(), i2c_master_read()
i2c_slave_demo.c I2C 从设备示例实现 at_i2c_slave_start_sample(), i2c_slave_task(), i2c_slave_write(), i2c_slave_read()
i2c_demo.h 头文件,定义数据结构、AT 命令表和函数声明 i2c_master_args_t, i2c_slave_args_t, g_at_i2c_cmd_parse_table[], 函数声明
CMakeLists.txt 编译配置,定义源文件、头文件路径和编译选项 set(SOURCES ...), build_component()

代码走读

CMakeLists.txt 解析

set(COMPONENT_NAME "i2c_sample")

set(SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/i2c_master_demo.c
    ${CMAKE_CURRENT_SOURCE_DIR}/i2c_slave_demo.c
)

set(PUBLIC_HEADER
    ${CMAKE_CURRENT_SOURCE_DIR}
)

set(PRIVATE_HEADER
    ${ROOT_DIR}/src/include/driver
    ${ROOT_DIR}/middleware/utils/at/at
    ${ROOT_DIR}/middleware/utils/at/at/include
    ${ROOT_DIR}/middleware/utils/at/at/src
)

set(PUBLIC_DEFINES
    AT_DITING_EXAMPLE_I2C
)

set(WHOLE_LINK
    true
)

build_component()

关键配置说明:

  • COMPONENT_NAME:组件名称为i2c_sample,需在config.py中的diting-community配置项下添加该组件名。
  • SOURCES:指定源文件为 i2c_master_demo.ci2c_slave_demo.c
  • PUBLIC_HEADER:公开头文件路径,使其他模块可以引用 i2c_demo.h
  • PRIVATE_HEADER:私有头文件路径,用于编译时查找依赖头文件。
  • WHOLE_LINK:设置为 true,确保整个组件被链接。
  • PUBLIC_DEFINES:定义宏 AT_DITING_EXAMPLE_I2C,用于在AT命令适配层文件at_adapter.c中条件性的添加AT命令注册。
  • 为使该i2c文件参与编译,还需在上层CMakeLists.txt文件中以宏CONFIG_ENABLE_I2C_SAMPLE为条件添加该i2c文件。
  • config.py
  • at_adapter.c

i2c_demo.h 解析

数据结构定义:

typedef struct {
    uint32_t para_map;
    uint8_t slaveAddr;          // 从设备自身地址
    uint8_t slaveBusId;         // 从设备使用的总线ID
    uint8_t sclPin;             // SCL时钟引脚号
    uint8_t sdaPin;             // SDA数据引脚号
    uint8_t sclMode;            // SCL引脚工作模式
    uint8_t sdaMode;            // SDA引脚工作模式
    uint8_t type;               // 收发模式(0:主发从收/1:从发主收)
} i2c_slave_args_t;

typedef struct {
    uint32_t para_map;
    uint8_t slaveAddr;          // 从设备地址
    uint8_t masterBusId;        // 主设备使用的总线ID
    uint8_t sclPin;             // SCL时钟引脚号
    uint8_t sdaPin;             // SDA数据引脚号
    uint8_t sclMode;            // SCL引脚工作模式
    uint8_t sdaMode;            // SDA引脚工作模式
    uint8_t type;               // 收发模式(0:主发从收/1:从发主收)
} i2c_master_args_t;

参数语法定义:

// I2C 从设备参数语法
const at_para_parse_syntax_t g_i2c_slave_syntax[] = {
    {
        .type = AT_SYNTAX_TYPE_INT,
        .attribute = AT_SYNTAX_ATTR_AT_MIN_VALUE | AT_SYNTAX_ATTR_AT_MAX_VALUE,
        .entry.int_range.min_val = 0,
        .entry.int_range.max_val = 127,
        .offset = offsetof(i2c_slave_args_t, slaveAddr)
    },
    {
        .type = AT_SYNTAX_TYPE_INT,
        .attribute = AT_SYNTAX_ATTR_AT_MIN_VALUE | AT_SYNTAX_ATTR_AT_MAX_VALUE,
        .entry.int_range.min_val = 0,
        .entry.int_range.max_val = 3,
        .offset = offsetof(i2c_slave_args_t, slaveBusId)
    },
    {
        .type = AT_SYNTAX_TYPE_INT,
        .attribute = AT_SYNTAX_ATTR_AT_MIN_VALUE | AT_SYNTAX_ATTR_AT_MAX_VALUE,
        .entry.int_range.min_val = 4,
        .entry.int_range.max_val = 11,
        .offset = offsetof(i2c_slave_args_t, sclPin)
    },
    {
        .type = AT_SYNTAX_TYPE_INT,
        .attribute = AT_SYNTAX_ATTR_AT_MIN_VALUE | AT_SYNTAX_ATTR_AT_MAX_VALUE,
        .entry.int_range.min_val = 4,
        .entry.int_range.max_val = 11,
        .offset = offsetof(i2c_slave_args_t, sdaPin)
    },
    {
        .type = AT_SYNTAX_TYPE_INT,
        .attribute = AT_SYNTAX_ATTR_AT_MIN_VALUE | AT_SYNTAX_ATTR_AT_MAX_VALUE,
        .entry.int_range.min_val = 0,
        .entry.int_range.max_val = 1,
        .offset = offsetof(i2c_slave_args_t, sclMode)
    },
    {
        .type = AT_SYNTAX_TYPE_INT,
        .attribute = AT_SYNTAX_ATTR_AT_MIN_VALUE | AT_SYNTAX_ATTR_AT_MAX_VALUE,
        .entry.int_range.min_val = 0,
        .entry.int_range.max_val = 1,
        .offset = offsetof(i2c_slave_args_t, sdaMode)
    },
    {
        .type = AT_SYNTAX_TYPE_INT,
        .last = true,
        .attribute = AT_SYNTAX_ATTR_AT_MIN_VALUE | AT_SYNTAX_ATTR_AT_MAX_VALUE,
        .entry.int_range.min_val = 0,
        .entry.int_range.max_val = 1,
        .offset = offsetof(i2c_slave_args_t, type)
    },
};

// I2C 主设备参数语法(结构类似,参数为 masterBusId)
const at_para_parse_syntax_t g_i2c_master_syntax[] = {
    // ... 类似定义,参数为 masterBusId
};

AT 命令表定义:

const at_cmd_entry_t g_at_i2c_cmd_parse_table[] = {
    // AT+STARTI2CMASTER
    {
        "STARTI2CMASTER",       // 命令名称
        0x2268,                 // 命令 ID
        0,                      // 保留
        g_i2c_master_syntax,    // 参数语法
        NULL,                   // 查询函数
        (at_set_func_t)at_i2c_master_start_sample,  // 设置函数
        NULL,                   // 执行函数
        NULL,                   // 保留
    },
    // AT+STARTI2CSLAVE
    {
        "STARTI2CSLAVE",        // 命令名称
        0x2269,                 // 命令 ID
        0,                      // 保留
        g_i2c_slave_syntax,     // 参数语法
        NULL,                   // 查询函数
        (at_set_func_t)at_i2c_slave_start_sample,   // 设置函数
        NULL,                   // 执行函数
        NULL,                   // 保留
    },
};

函数声明:

void at_diting_i2c_master_example_cmd_register(void);
void at_diting_i2c_slave_example_cmd_register(void);

at_ret_t at_i2c_master_start_sample(const i2c_master_args_t* args);
at_ret_t at_i2c_slave_start_sample(const i2c_slave_args_t* args);

命令注册函数

// 主设备命令注册
void at_diting_i2c_master_example_cmd_register(void)
{
    uapi_at_cmd_table_register(g_at_i2c_cmd_parse_table, AT_I2C_FUNC_NUM, EXT_AT_I2C_CMD_MAX_LEN);
}

// 从设备命令注册
void at_diting_i2c_slave_example_cmd_register(void)
{
    uapi_at_cmd_table_register(g_at_i2c_cmd_parse_table, AT_I2C_FUNC_NUM, EXT_AT_I2C_CMD_MAX_LEN);
}

代码逻辑解析:

  • 调用 uapi_at_cmd_table_register 注册 I2C 命令表。
  • g_at_i2c_cmd_parse_table:命令表数组,包含主设备和从设备两个命令。
  • AT_I2C_FUNC_NUM:命令数量(通过宏计算数组大小)。
  • EXT_AT_I2C_CMD_MAX_LEN:命令最大长度(128)。

引脚初始化

static errcode_t app_i2c_init_pin(uint8_t sclPin, uint8_t sdaPin, uint8_t sclMode, uint8_t sdaMode)
{
#if defined(CONFIG_PINCTRL_SUPPORT_IE)
    uapi_pin_set_ie(sclPin, 1);
    uapi_pin_set_ie(sdaPin, 1);
#endif
    uapi_pin_set_mode(sclPin, sclMode);
    uapi_pin_set_mode(sdaPin, sdaMode);

    return ERRCODE_SUCC;
}

数据配置

static errcode_t app_i2c_data_config(void)
{
    data.send_buf = tx_buff;
    data.send_len = CONFIG_I2C_TRANSFER_LEN;
    data.receive_buf = rx_buff;
    data.receive_len = CONFIG_I2C_TRANSFER_LEN;

    return ERRCODE_SUCC;
}

i2c_master_task —— I2C 主设备任务

static errcode_t i2c_master_task(uint8_t slaveAddr, uint8_t masterBusId, uint8_t sclPin, uint8_t sdaPin, uint8_t sclMode, uint8_t sdaMode, uint8_t type)
{
    uint32_t baudrate = I2C_SET_BAUDRATE;
    uint8_t hscode = I2C_MASTER_ADDR;
    uint16_t dev_addr = slaveAddr;
    const uint32_t MAX_LOOP = 10;

    // step1: 初始化I2C引脚
    errcode_t ret = app_i2c_init_pin(sclPin, sdaPin, sclMode, sdaMode);
    if (ret != ERRCODE_SUCC) {
        printf("i2c pin init failed!\n");
        return AT_RET_RUN_ERROR;
    } else {
        printf("i2c pin init succ!\n");
    }

    // step2: 初始化I2C主设备,设置总线ID、波特率和主机地址
    ret = uapi_i2c_master_init(masterBusId, baudrate, hscode);
    if (ret != ERRCODE_SUCC) {
        printf("i2c master init failed!\n");
        return AT_RET_RUN_ERROR;
    } else {
        printf("i2c master init succ!\n");
    }

    // step3: 配置发送和接收数据缓冲区
    ret = app_i2c_data_config();
    if (ret != ERRCODE_SUCC) {
        return AT_RET_RUN_ERROR;
    } else {
        printf("i2c data config succ!\n");
    }

    // step4: 循环执行10次I2C通信(发送或接收)
    while (loop_count < MAX_LOOP) {
        if (type == 0) {
            loop_count++;
            osal_msleep(I2C_TASK_DURATION_MS);
            // 从从设备接收数据
            i2c_master_read(masterBusId, dev_addr);
        } else if (type == 1) {
            loop_count++;
            osal_msleep(I2C_TASK_DURATION_MS);
            // 向从设备发送数据
            i2c_master_write(masterBusId, dev_addr);
        }
        (void)uapi_watchdog_kick();
    }
    loop_count = 0;
    osal_msleep(I2C_WAIT_MS);
    // step5: I2C主设备去初始化
    uapi_i2c_deinit(masterBusId);

    return ERRCODE_SUCC;
}

i2c_master_write —— I2C 主设备发送

static errcode_t i2c_master_write(uint8_t masterBusId, uint8_t slaveAddr)
{
    printf("i2c master send %d start!\n", loop_count);
    if (uapi_i2c_master_write(masterBusId, slaveAddr, &data) == ERRCODE_SUCC) {
        printf("i2c master send %d succ!\n", loop_count);
    } else {
        printf("i2c master send %d failed\n", loop_count);
    }

    return ERRCODE_SUCC;
}

i2c_master_read —— I2C 主设备接收

static errcode_t i2c_master_read(uint8_t masterBusId, uint8_t slaveAddr)
{
    printf("i2c master receive %d start!\n", loop_count);
    if (uapi_i2c_master_read(masterBusId, slaveAddr, &data) == ERRCODE_SUCC) {
        for (uint32_t i = 0; i < data.receive_len; i++) {
            printf("%c", data.receive_buf[i]);
        }
        printf("\n");
        printf("i2c master receive %d succ!\n", loop_count);
    } else {
        printf("i2c master receive %d failed\n", loop_count);
    }

    return ERRCODE_SUCC;
}
说明

主设备接收数据时,是向从设备读取数据,因此需要等待,需要在配置文件diting_community.config中配置一个等待时间宏CONFIG_I2C_WAIT_CONDITION_TIMEOUT,并赋值为0xFFFFFFFF。

diting_community.config(如果编译的是其他 target,则修改对应 target 的 config 文件,如 pack_diting_community_native_js 则修改 diting_community_native_js.config

at_master_sample —— 解析参数并启动I2C主设备任务

static errcode_t at_master_sample(void *arg)
{
    i2c_master_args_t *args = (i2c_master_args_t *)arg;
    printf("========== i2c master start ==========\n");
    uint8_t u8SaveAddr = args->slaveAddr;
    uint8_t u8MasterBusId = args->masterBusId;
    uint8_t u8SclPin = args->sclPin;
    uint8_t u8SdaPin = args->sdaPin;
    uint8_t u8SclMode = args->sclMode;
    uint8_t u8SdaMode = args->sdaMode;
    uint8_t u8Type = args->type;
    i2c_master_task(u8SaveAddr, u8MasterBusId, u8SclPin, u8SdaPin, u8SclMode, u8SdaMode, u8Type);
    printf("========== i2c master done ==========\n");
    return AT_RET_OK;
}

at_i2c_master_start_sample —— AT命令处理函数入口

at_ret_t at_i2c_master_start_sample(i2c_master_args_t *args)
{
    osal_task *task_handle = NULL;
    // step1 上锁,防止多线程资源抢占
    osal_kthread_lock();
    // step2 创建线程任务
    task_handle =
        osal_kthread_create((osal_kthread_handler)at_master_sample, (void *)args, "I2CMasterDemo", I2C_TASK_STACK_SIZE);
    // step3 设置线程任务优先级
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2C_TASK_PRIO);
    }
    // step4 解锁
    osal_kthread_unlock();

    return AT_RET_OK;
}

i2c_slave_task —— I2C 从设备任务

static errcode_t i2c_slave_task(uint8_t slaveAddr, uint8_t slaveBusId, uint8_t sclPin, uint8_t sdaPin, uint8_t sclMode, uint8_t sdaMode, uint8_t type)
{
    uint32_t baudrate = I2C_SET_BAUDRATE;
    uint16_t dev_addr = I2C_SLAVE_ADDR;
    const uint32_t MAX_LOOP = 10;

    // step1: 初始化I2C引脚
    errcode_t ret = app_i2c_init_pin(sclPin, sdaPin, sclMode, sdaMode);
    if (ret != ERRCODE_SUCC) {
        return AT_RET_RUN_ERROR;
    } else {
        printf("i2c pin init succ!\n");
    }

    // step2: 初始化I2C从设备,设置总线ID、波特率和从设备地址
    ret = uapi_i2c_slave_init(slaveBusId, baudrate, dev_addr);
    if (ret != ERRCODE_SUCC) {
        return AT_RET_RUN_ERROR;
    } else {
        printf("i2c slave init succ!\n");
    }

    // step3: 配置发送和接收数据缓冲区
    ret = app_i2c_data_config();
    if (ret != ERRCODE_SUCC) {
        return AT_RET_RUN_ERROR;
    } else {
        printf("i2c data config succ!\n");
    }

    // step4: 循环等待主设备通信,接收数据并发送响应
    while (loop_count < MAX_LOOP) {
        if (type == 0) {
            loop_count++;
            osal_msleep(I2C_TASK_DURATION_MS);
            // 等待并接收来自主设备的数据
            i2c_slave_read(slaveBusId);
        } else if (type == 1) {
            loop_count++;
            osal_msleep(I2C_TASK_DURATION_MS);
            // 向主设备发送数据
            i2c_slave_write(slaveBusId);
        }
        (void)uapi_watchdog_kick();
    }
    loop_count = 0;
    osal_msleep(I2C_WAIT_MS);
    // step5: I2C从设备去初始化
    uapi_i2c_deinit(slaveBusId);

    return ERRCODE_SUCC;
}

i2c_slave_write —— I2C 从设备发送

static errcode_t i2c_slave_write(uint8_t slaveBusId)
{
    printf("i2c slave send %d start!\n", loop_count);
    if (uapi_i2c_slave_write(slaveBusId, &data) == ERRCODE_SUCC) {
        printf("i2c slave send %d succ!\n", loop_count);
    } else {
        printf("i2c slave send %d failed\n", loop_count);
    }
    return ERRCODE_SUCC;
}

i2c_slave_read —— I2C 从设备接收

static errcode_t i2c_slave_read(uint8_t slaveBusId)
{
    printf("i2c slave receive %d start!\n", loop_count);
    if (uapi_i2c_slave_read(slaveBusId, &data) == ERRCODE_SUCC) {
        for (uint32_t i = 0; i < data.receive_len; i++) {
            printf("%c", data.receive_buf[i]);
        }
        printf("\n");
        printf("i2c slave receive %d succ!\n", loop_count);
    } else {
        printf("i2c slave receive %d failed\n", loop_count);
    }

    return ERRCODE_SUCC;
}

at_slave_sample —— 解析参数并启动I2C从设备任务

static errcode_t at_slave_sample(void *arg)
{
    i2c_slave_args_t *args = (i2c_slave_args_t *)arg;
    printf("========== i2c slave start ==========\n");
    uint8_t u8SlaveAddr = args->slaveAddr;
    uint8_t u8SlaveBusId = args->slaveBusId;
    uint8_t u8SclPin = args->sclPin;
    uint8_t u8SdaPin = args->sdaPin;
    uint8_t u8SclMode = args->sclMode;
    uint8_t u8SdaMode = args->sdaMode;
    uint8_t u8Type = args->type;
    i2c_slave_task(u8SlaveAddr, u8SlaveBusId, u8SclPin, u8SdaPin, u8SclMode, u8SdaMode, u8Type);
    printf("========== i2c slave done ==========\n");

    return AT_RET_OK;
}

at_i2c_slave_start_sample —— AT命令处理函数入口

at_ret_t at_i2c_slave_start_sample(i2c_slave_args_t *args)
{
    osal_task *task_handle = NULL;
    // step1 上锁,防止多线程资源抢占
    osal_kthread_lock();
    // step2 创建线程任务
    task_handle = osal_kthread_create((osal_kthread_handler)at_slave_sample, (void *)args, "I2CSlaveDemo", I2C_TASK_STACK_SIZE);
    // step3 设置线程任务优先级
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, I2C_TASK_PRIO);
    }
    // step4 解锁
    osal_kthread_unlock();

    return AT_RET_OK;
}

返回值说明

返回值 说明
AT_RET_OK 操作成功
AT_RET_RUN_ERROR 运行时错误

基于 I2C Demo 开发自己的应用

上面的demo是使用AT指令触发运行,HiDiTing还支持app_run方式触发应用在系统启动时自动运行,以下示例将以app_run的方式开发一个开发者自己的应用

  • app_run(func) 是 HiDiTing 中应用层注册应用函数的宏,基于 GCC 编译器属性和自定义段区(section)自动注册来实现集中调用应用函数,系统启动时会自动遍历所有用 app_run 注册过的函数并执行,无需在系统 main 函数里逐个调用函数。

代码清单

新建一个 I2C 控制应用(以 my_i2c_demo 为例)通常只需要以下改动:

  • 新建 my_i2c_demo.c 源文件
  • 新建 my_i2c_demo.h 头文件
  • 新建 CMakeLists.txt 源文件
  • my_i2c_demo.c 中实现I2C操作函数
  • my_i2c_demo.h 中定义宏常量和函数声明
  • CMakeLists.txt 源文件编译规则

my_i2c_demo文件结构:

/samples/native_samples/my_i2c_demo/
├── my_i2c_demo.c
├── my_i2c_demo.h
└── CMakeLists.txt

CMakeLists.txt 修改示例

# 在新建的 CMakeLists.txt 中添加应用的源文件
set(SOURCES
    ${CMAKE_CURRENT_SOURCE_DIR}/my_i2c_demo.c
)

set(PUBLIC_HEADER
    ${CMAKE_CURRENT_SOURCE_DIR}
)

set(PRIVATE_HEADER
    ${ROOT_DIR}/src/include/driver
)

set(PUBLIC_DEFINES
    MY_I2C_DEMO_ENABLE
)

build_component()

关键代码片段

my_i2c_demo.c —— I2C 控制实现

#include <stdio.h>
#include <string.h>
#include "errcode.h"
#include "i2c.h"
#include "my_i2c_demo.h"


#define I2C_TEST_SLAVE_ADDR     0x8     // 从设备地址
#define I2C_TEST_BUS_ID         3       // 总线ID
#define I2C_TEST_SCL_PIN        10      // SCL引脚号
#define I2C_TEST_SDA_PIN        11      // SDA引脚号
#define I2C_TEST_SCL_MODE       1       // SCL引脚模式
#define I2C_TEST_SDA_MODE       1       // SDA引脚模式
#define I2C_TEST_BAUDRATE       400000  // 波特率
#define I2C_TEST_MASTER_ADDR    0x0     // 主设备地址
#define I2C_TRANSFER_LEN        8       // 传输长度
#define TASK_PRIO               26
#define TASK_STACK_SIZE         0x1000

static i2c_data_t data = { 0 };
static uint8_t tx_buff[I2C_TRANSFER_LEN] = "hello";
static uint8_t rx_buff[I2C_TRANSFER_LEN] = { 0 };

static void *my_i2c_task(const char *arg)
{
    unused(arg);
    printf("my_i2c_demo start\n");

    // 初始化引脚
    uapi_pin_set_ie(I2C_TEST_SCL_PIN, 1);
    uapi_pin_set_ie(I2C_TEST_SDA_PIN, 1);
    uapi_pin_set_mode(I2C_TEST_SCL_PIN, I2C_TEST_SCL_MODE);
    uapi_pin_set_mode(I2C_TEST_SDA_PIN, I2C_TEST_SDA_MODE);
    printf("i2c pin init done\n");

    // 初始化I2C主设备
    uapi_i2c_master_init(I2C_TEST_BUS_ID, I2C_TEST_BAUDRATE, I2C_TEST_MASTER_ADDR);
    printf("i2c master init done\n");

    // 配置数据
    data.send_buf = tx_buff;
    data.send_len = I2C_TRANSFER_LEN;
    data.receive_buf = rx_buff;
    data.receive_len = I2C_TRANSFER_LEN;

    // 发送数据
    if (uapi_i2c_master_write(I2C_TEST_BUS_ID, I2C_TEST_SLAVE_ADDR, &data) == ERRCODE_SUCC) {
        printf("i2c master write succ\n");
    } else {
        printf("i2c master write failed\n");
    }

    // 接收数据
    if (uapi_i2c_master_read(I2C_TEST_BUS_ID, I2C_TEST_SLAVE_ADDR, &data) == ERRCODE_SUCC) {
        printf("i2c master read succ, data: ");
        for (uint32_t i = 0; i < data.receive_len; i++) {
            printf("%c", data.receive_buf[i]);
        }
        printf("\n");
    } else {
        printf("i2c master read failed\n");
    }

    // 去初始化
    uapi_i2c_deinit(I2C_TEST_BUS_ID);
    printf("my_i2c_demo done\n");
    return NULL;
}

static void my_i2c_entry(void)
{
    osal_task *task_handle = NULL;
    osal_kthread_lock();
    task_handle = osal_kthread_create((osal_kthread_handler)my_i2c_task, 0, "MyI2cTask", TASK_STACK_SIZE);
    if (task_handle != NULL) {
        osal_kthread_set_priority(task_handle, TASK_PRIO);
    }
    osal_kthread_unlock();
}

app_run(my_i2c_entry);

测试验证

完成一站式 CLI 环境配置后执行:

# 编译固件,编译生成的固件从 output/3322/fwpkg 中获取 diting-community.fwpkg
fbb set-target pack_diting_community
fbb build
  1. 烧录固件并启动设备
  2. 通过串口观察输出日志:

    my_i2c_demo start
    i2c pin init done
    i2c master init done
    i2c master write succ
    i2c master read succ, data: hello
    my_i2c_demo done
    

说明

  • 因为 app_run 是在系统启动时运行应用,所以应用的运行日志会被夹在开机启动日志中。
  • 为使该 I2C 应用参与编译,还需在上层 CMakeLists.txt 文件中以宏 MY_I2C_DEMO_ENABLE 为条件添加该 my_i2c_demo 文件。
  • MY_I2C_DEMO_ENABLE 默认关闭。如需启用新建 I2C 示例功能,需要在 config.py 中的 diting-community 配置项下将其打开。

app_run运行配置

app_run应用默认是关闭的,如需启用此应用,需用户手动在acore.prelds文件中添加 KEEP (*(SORT(.zinitcall.app_run*.init))) 具体参考示意图如下:

apprun运行配置

acore.prelds


注意事项

引脚配置注意事项:

  • I2C 通信需要两个引脚:SCL(时钟线)和 SDA(数据线)。
  • 使用前必须调用 uapi_pin_set_ie() 使能输入功能。
  • 必须调用 uapi_pin_set_mode() 将引脚配置为 I2C 功能模式。
  • 本示例使用的是I2C3_SCL_CHG和I2C3_SDA_CHG,对应的引脚号是10和11,用户实际使用需要根据芯片手册具体配置。

主设备注意事项:

  • 初始化主设备时需指定总线 ID、波特率和主机地址。
  • 主机地址固定为 I2C_MASTER_ADDR(0x0)。
  • 发送数据时需指定从设备地址。

从设备注意事项:

  • 初始化从设备时需指定总线 ID、波特率和从设备地址。
  • 从设备地址固定为 I2C_SLAVE_ADDR(0x8)。
  • 从设备不能主动发起通信,需等待主设备请求。

通信注意事项:

  • 单次传输数据长度固定为 CONFIG_I2C_TRANSFER_LEN(8字节)。
  • 主设备发送数据为 "hello",从设备发送数据为 "world"
  • 通信循环次数固定为 10 次,每次间隔 I2C_TASK_DURATION_MS(3000ms)。

AT 命令注意事项:

  • 命令名称为大写字母,如 STARTI2CMASTER
  • 参数使用逗号分隔,共7个参数。
  • 参数范围:slaveAddr 为 0-127,busId 为 0-3,pin 为 4-11,mode 为 0-1,type 为 0-1。

常见错误

错误现象 原因 解决方法
undefined reference to 'uapi_i2c_master_init' 未链接 I2C 驱动库 检查 CMakeLists.txt 中是否正确添加了头文件路径
undefined reference to 'uapi_at_cmd_table_register' 未链接 AT 命令库 检查 CMakeLists.txt 中是否正确添加了 AT 相关头文件路径
编译报 at_ret_t 未定义 未包含 at.h 头文件 添加 #include "at.h"
串口无输出 AT 命令未注册 确认调用了 at_diting_i2c_master_example_cmd_register()at_diting_i2c_slave_example_c_register()
引脚无响应 引脚未正确初始化 确认调用了 uapi_pin_set_ie()uapi_pin_set_mode()
I2C 通信失败 引脚模式配置错误 确认 sclMode 和 sdaMode 参数与硬件匹配
数据接收异常 从设备地址不匹配 确认主设备发送的从设备地址与从设备配置的地址一致