i2c开发指南
本文档以 i2c_master_demo.c 和 i2c_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设备都无法正确发送数据。
- 手动中断模式和自动切换模式互斥。
操作流程
本示例支持主设备和从设备两种角色,每种角色都遵循标准的初始化流程:
通用初始化步骤(所有操作共享):
- 引脚初始化:调用 uapi_pin_set_ie() 使能输入功能,调用 uapi_pin_set_mode() 将引脚配置为 I2C 功能。
- I2C 初始化:调用 uapi_i2c_master_init() 或 uapi_i2c_slave_init() 初始化I2C 设备。
- 数据配置:配置发送和接收缓冲区。
各角色特有流程:
| 角色 | 流程 |
|---|---|
| 主设备发送 | 初始化引脚 → 初始化主设备 → 配置数据 → 调用 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
使用方式
- 烧录固件并启动设备。
- 需用两块板子,找到i2c通道引脚(SCL,SDA,GND),用杜邦线把两块板子的i2c通道引脚对应连接起来:本示例是以I2C3为例测试。
-
通过串口发送 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+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.c和i2c_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.pyat_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文件结构:
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
- 烧录固件并启动设备
-
通过串口观察输出日志:
说明
- 因为
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))) 具体参考示意图如下:

注意事项
引脚配置注意事项:
- 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 参数与硬件匹配 |
| 数据接收异常 | 从设备地址不匹配 | 确认主设备发送的从设备地址与从设备配置的地址一致 |