杰理 SDK 文档中心
首页
首页
  • 概述与入门

    • 项目概述与芯片平台
    • 环境搭建与工具链安装
    • 编译与烧录指南
    • 工程结构总览
  • 应用层与公共模块

    • GP MCU 主应用入口
    • AT 指令与调试模块
    • 电池检测与电源管理
    • EEPROM 与参数存储
    • 按键与 USB 设备驱动
    • 音频解码与 APA 语音播报
  • 外设驱动与示例

    • 高精度 ADC(HADC)
    • 通用 ADC 与定时器
    • UART / SPI / IIC 通信外设
    • MCPWM 与电机控制
    • RTC 与低功耗唤醒
    • 段码 LCD 驱动
    • NOR Flash 与红外编解码组件
  • 显示与 UI 系统

    • LCD 驱动与字库引擎
    • UI 平台与控件绘制
    • UI 工程与资源生成工具
  • 系统底层与芯片平台

    • cd09 芯片平台与预编译库
    • GPIO 与 IIC 底层驱动
    • 系统文件系统与设备模型
  • 启动引导与固件升级

    • UBOOT 引导工程
    • 固件升级机制
  • 开发工具与资源

    • 编译脚本与命令行工具
    • 音频文件转换工具
    • 硬件资料与文档资源

GPIO 与 IIC 底层驱动

AC82N SDK 中 GPIO 引脚控制与 I2C(IIC)总线通信的底层驱动层。本文档解析 GPIO 模式配置、电平读写、中断与功能复用机制,以及软件 IIC 与硬件 IIC 两套主从设备通信实现的完整调用链。

Purpose and Scope

本文档覆盖 AC82N SDK 中位于 sdk/cpu/ 与 sdk/include_lib/driver/cpu/periph/ 的 GPIO 与 IIC 底层驱动:

  • GPIO 驱动:gpio.c 提供的模式设置、初始化/去初始化、电平读写、输出翻转、中断注册(gpio_irq_*)与引脚功能复用(gpio_set_function 等)。
  • IIC 驱动:iic_api.c + iic_api.h 提供的统一 I2C 抽象层,内部通过 _IIC_USE_HW 宏在软件 IIC(iic_soft.c,GPIO 位操作模拟)与硬件 IIC(iic_hw_v2.h)之间切换,并提供主模式读写、从模式轮询收发 API。

不在本文档范围内的相关话题:IIC EEPROM 应用层封装(sdk/apps/common/eeprom/iic_eeprom.c,属于应用层存储管理,仅作扩展点引用);芯片级寄存器位操作 gpio_hw.h 为内部硬件抽象,仅在说明实现机制时引用;其他外设驱动(UART、SPI 等)由各自的 wiki 页面覆盖。

Overview

GPIO(通用输入输出)是嵌入式系统与外部世界交互的最基本通道。AC82N 的 GPIO 驱动通过 enum gpio_port + u32 pin(位掩码,可一次操作同一端口多个引脚)或 u32 gpio(形如 IO_PORTA_00 的全局编号)两种寻址方式访问引脚,支持输出低/高、浮空输入、多种上下拉强度组合、高阻态等 10 余种模式,以及 2.4mA 到 64mA 的驱动强度分级。这些能力是 IIC、UART、SPI 等复用功能的硬件基础。

IIC 驱动则解决"如何与片外器件交换数据"的问题。SDK 提供两条路径:

  1. 软件 IIC(soft):直接占用两个普通 GPIO(SCL/SDA),用 gpio_write/gpio_read 位操作模拟 I2C 时序。优点是不依赖专用外设、引脚任意、实现简单;缺点是时序受 CPU 主频与中断影响,且传输期间必须 local_irq_disable() 关闭全部中断。
  2. 硬件 IIC(hw):使用芯片内置 I2C 控制器与寄存器,支持中断、滤波、总线锁定(lock busy)等特性,吞吐与可靠性更高;SDK 通过 iic_hw_v2.h 提供寄存器级封装。

两个层次通过 iic_api.h 中的条件宏对外暴露同一套 API 名称(iic_init、i2c_master_read_nbytes_from_device_reg 等),使上层应用(如 iic_eeprom.c)无需关心底层实现是软是硬——这是本驱动层最重要的设计意图:API 稳定性与实现可替换性。

Architecture

下图展示了 GPIO 与 IIC 驱动在 SDK 中的分层结构、依赖关系与数据流向:

flowchart TD
    subgraph sg_App["应用层 (apps)"]
        App["iic_eeprom.c / 业务代码"]
    end

    subgraph sg_Api["统一 IIC 抽象层 (iic_api.h)"]
        API["iic_init / iic_start / iic_stop<br/>i2c_master_read/write_nbytes_...<br/>(宏路由)"]
    end

    subgraph sg_Impl["IIC 实现层"]
        Soft["soft_iic 软件实现<br/>(iic_soft.c)"]
        Hw["hw_iic 硬件实现<br/>(iic_hw_v2.h)"]
    end

    subgraph sg_Gpio["GPIO 驱动层 (gpio.c)"]
        GpioApi["gpio_init / gpio_set_mode<br/>gpio_read / gpio_write / gpio_toggle_port"]
        GpioIrq["gpio_irq_config / gpio_set_function<br/>(gpio_irq.c / gpio_func.c)"]
    end

    subgraph sg_HwReg["寄存器层 (asm/gpio_hw.h)"]
        Reg["gpio_hw_* 位操作"]
        Ctrl["I2C 控制器寄存器"]
    end

    App -->|"统一 API 名"| API
    API -->|"_IIC_USE_HW 宏路由"| Soft
    API -->|"否则"| Hw
    Soft -->|"SCL/SDA 位操作"| GpioApi
    Hw --> Ctrl
    GpioApi --> Reg
    GpioIrq --> Reg
    Ctrl --> Reg

架构要点:

  • 统一抽象层是核心。iic_api.h 顶部定义 enum iic_state_enum、struct iic_master_config 等公共类型,随后根据 _IIC_USE_HW 是否定义,把 iic_init/iic_deinit/iic_start/iic_stop/iic_tx_byte/iic_read_buf 等 12 个操作和 2 个"读/写 n 字节到设备寄存器"高层函数分别映射到 soft_* 或 hw_* 实现。上层业务只面对一套 API,切换实现只需改编译宏。
  • GPIO 是软件 IIC 的物理基础。iic_soft.c 用 GPIO 输出翻转模拟 SCL、用 GPIO 读写采样 SDA,因此软件 IIC 实例(struct soft_iic_config)中直接保存 scl_io/sda_io 两个 GPIO 编号。
  • 硬件 IIC 不经过 GPIO 模拟层,直接操作 I2C 控制器寄存器,但引脚复用仍需要先通过 gpio_set_function 把对应引脚切到 I2C 功能(见 GPIO 功能复用章节)。
  • 寄存器层为唯一底层。gpio_hw_*(端口判断、方向、上下拉、驱动强度、die/dieh 等)封装在 asm/gpio_hw.h 中,gpio.c 的所有模式设置最终都落到这些位操作上。

GPIO 驱动详解

模式设置的核心算法:gpio_set_mode

gpio_set_mode() 是 GPIO 配置的入口,输入端口 + 引脚掩码 + 目标模式,内部按模式枚举分派到不同的硬件寄存器组合。它首先调用 gpio_hw_port_pin_judge() 校验端口/引脚合法性,非法直接返回 -1;随后按 enum gpio_mode 分派:

int gpio_set_mode(enum gpio_port port, u32 pin, enum gpio_mode mode)
{
    if (gpio_hw_port_pin_judge(port, pin) < 0) {
        return -1;
    }
    switch (mode) {
    case PORT_OUTPUT_LOW:
        gpio_hw_write_port(port, pin, 0);//out 0
        gpio_hw_set_direction(port, pin, 0);//0:out, 1:in
        break;
    case PORT_OUTPUT_HIGH:
        gpio_hw_write_port(port, pin, 1);//out 1
        gpio_hw_set_direction(port, pin, 0);//0:out, 1:in
        break;
    case PORT_INPUT_FLOATING:
        gpio_hw_set_direction(port, pin, 1);//0:out, 1:in
        gpio_hw_set_die(port, pin, 1);
        gpio_hw_set_dieh(port, pin, 1);
        gpio_hw_set_pull_up(port, pin, GPIO_PULLUP_DISABLE);
        gpio_hw_set_pull_down(port, pin, GPIO_PULLDOWN_DISABLE);
        break;
    case PORT_HIGHZ:
        gpio_hw_set_direction(port, pin, 1);//0:out, 1:in
        gpio_hw_set_die(port, pin, 0);
        gpio_hw_set_dieh(port, pin, 0);
        gpio_hw_set_pull_up(port, pin, GPIO_PULLUP_DISABLE);
        gpio_hw_set_pull_down(port, pin, GPIO_PULLDOWN_DISABLE);
        break;
    case PORT_INPUT_PULLUP_10K:
        gpio_hw_set_pull_up(port, pin, GPIO_PULLUP_10K);
        gpio_hw_set_pull_down(port, pin, GPIO_PULLDOWN_DISABLE);
        gpio_hw_set_direction(port, pin, 1);//0:out, 1:in
        gpio_hw_set_die(port, pin, 1);
        gpio_hw_set_dieh(port, pin, 1);
        break;
    // ... 其余 PULLUP_100K / PULLUP_1M / PULLDOWN_10K / PULLDOWN_100K / PULLDOWN_1M
    default:
        log_error("%s(), param:mode error!", __func__);
        return -1;//error
    }
    return 0;
}

来源:gpio.c

设计意图分析:

  • 先写电平、再设方向:PORT_OUTPUT_LOW/HIGH 先 gpio_hw_write_port 再 gpio_hw_set_direction(…, 0),保证引脚从输入/高阻切换为输出时电平已经确定,避免输出毛刺(glitch)——这是工业级 GPIO 驱动常见的"写数据寄存器在先、切方向在后"顺序。
  • 上下拉独立可控:上拉与下拉分别用 GPIO_PULLUP_x/GPIO_PULLDOWN_x 两组枚举独立设置,从而组合出浮空、10K/100K/1M 上拉、10K/100K/1M 下拉共 7 种输入态,适配不同外部负载与总线(I2C 上拉通常 10K)。
  • die/dieh 是输入缓冲使能:输入类模式把 die 与 dieh 置 1 使能读取,PORT_HIGHZ 则同时关闭二者并禁用上下拉,实现真正的高阻隔离。
  • 函数带有 __attribute__((always_inline_when_const_args)) 与 AT(.gpio.text.cache.L2) 段属性:当参数为编译期常量时强制内联、并把代码放入 L2 缓存文本段,说明该函数位于高频调用热路径,SDK 借此减少调用开销与取指延迟。

初始化与去初始化

gpio_init() 一次性完成模式 + 驱动强度配置,gpio_deinit() 将引脚恢复为高阻态:

int gpio_init(enum gpio_port port, const struct gpio_config *config)
{
    gpio_set_mode(port, config->pin, config->mode);
    gpio_hw_set_drive_strength(port, config->pin, config->hd);
    return 0;
}

int gpio_deinit(enum gpio_port port, u32 pin)
{
    if (gpio_hw_port_pin_judge(port, pin) < 0) {
        return -1;
    }
    gpio_hw_set_direction(port, pin, 1);//0:out, 1:in
    gpio_hw_set_die(port, pin, 0);
    gpio_hw_set_dieh(port, pin, 0);
    gpio_hw_set_pull_up(port, pin, GPIO_PULLUP_DISABLE);
    gpio_hw_set_pull_down(port, pin, GPIO_PULLDOWN_DISABLE);
    gpio_hw_set_drive_strength(port, pin, PORT_DRIVE_STRENGT_2p4mA);
    return 0;
}

来源:gpio.c

struct gpio_config 定义于 gpio.h:pin(位掩码,支持 PORT_PIN_0 | PORT_PIN_2 多引脚)、mode(enum gpio_mode)、hd(enum gpio_drive_strength)。deinit 是 init 的逆操作,把方向置为输入、关闭输入缓冲、禁用上下拉并把驱动强度复位到最小 2.4mA——即"安全默认态",防止引脚残留驱动电平影响总线或外设。

读写与翻转操作

GPIO 提供单引脚与多引脚(端口掩码)两套读写 API,全部是薄封装,直接透传 gpio_hw_*:

  • gpio_read(u32 gpio) / gpio_write(u32 gpio, u32 value):面向 IO_PORTA_00 形式的全局 GPIO 编号。
  • gpio_read_port(port, pin) / gpio_write_port(port, pin, out_state):面向端口 + 引脚掩码,可一次读写同组多个 IO。
  • gpio_toggle_port(port, pin):翻转输出电平,内部按 #ifdef GPIOx 编译开关分派到 _toggle_port(PA/PB/…/PR, pin),仅支持已配置为输出的引脚。
  • gpio_get_out_level(port, pin):回读输出数据寄存器(区别于 gpio_read 的输入采样)。

翻转函数的分派体现了 SDK 的编译期裁剪策略:每个端口(PORTA…PORTH、PORTP、PORTUSB、PORTR)是否可用由对应 GPIOx 宏控制,未定义的端口在编译时即被排除,减小代码体积。

GPIO 中断与内部信号中断

gpio.h 的第二大部分是中断子系统,由 gpio_irq.c 实现(本目录项引用其接口)。公共类型与约束:

  • enum gpio_irq_edge:PORT_IRQ_DISABLE=0(注销)、PORT_IRQ_EDGE_RISE=1(上升沿)、PORT_IRQ_EDGE_FALL=2(下降沿)、PORT_IRQ_ANYEDGE=3(双边沿)。
  • 回调签名:typedef void (*gpio_irq_callback_p)(enum gpio_port port, u32 pin, enum gpio_irq_edge edge);
  • 配置结构 struct gpio_irq_config_st:pin、irq_edge、callback、irq_priority。

核心 API 语义(见 gpio.h):

  • gpio_irq_config(port, config):配置并直接使能中断;禁止同一引脚同一边沿重复注册;单边沿与双边沿切换必须"先注销(PORT_IRQ_DISABLE)再注册"。
  • gpio_irq_set_callback:仅修改回调函数。
  • gpio_irq_enable / gpio_irq_disable:快速开关同组多个引脚的响应(不改变边沿配置)。
  • gpio_irq_set_edge / gpio_irq_get_edge:仅在已注册单边沿的前提下切换边沿。
  • 内部信号中断(inside_signal_irq_*):为芯片内部信号(enum inside_signal_sel)提供与 PORT 中断相同的注册/使能/边沿管理接口。

设计意图:把"边沿配置"与"中断使能"分离,使得业务可以在不丢配置的前提下快速暂停/恢复中断(例如临界区前后),避免反复走完整注册流程。

引脚功能复用(Crossbar)

GPIO 引脚需要切换为 UART/I2C/SPI 等特殊功能时,使用:

  • gpio_set_function(port, pin, fn):配置单个引脚为指定功能;pin 只能带 1 个 IO。
  • gpio_disable_function(port, pin, fn):注销特殊功能。
  • gpio_request_function(port, pin, fn, timeout) / gpio_release_function(port, pin, fn):带超时的资源申请/释放,用于多外设竞争同一引脚时的互斥管理(timeout 表示等待引脚可用的最长时间,超时返回错误)。

头文件示例注释展示了典型用法:gpio_set_function(PORTA, PORT_PIN_0, PORT_FUNC_UART0_TX);。硬件 IIC 使用前同样需要把 SCL/SDA 引脚通过该接口切到 PORT_FUNC_IICx_SCL/SDA 功能。

IIC 驱动详解

统一抽象层:_IIC_USE_HW 宏路由

iic_api.h 是整个 I2C 能力的门面。它先定义公共类型与错误码,然后通过条件编译把一套统一的 API 名称映射到软件或硬件实现:

#ifdef _IIC_USE_HW
#define get_iic_config(iic)                 get_hw_iic_config(iic)
#define iic_init(iic, config)               hw_iic_init(iic, config)
#define iic_deinit(iic)                     hw_iic_deinit(iic)
#define iic_start(iic)                      hw_iic_start(iic)
#define iic_stop(iic)                       hw_iic_stop(iic)
#define iic_reset(iic)                      hw_iic_reset(iic)
#define iic_tx_byte(iic, byte)              hw_iic_tx_byte(iic, byte)
#define iic_rx_byte(iic, ack)               hw_iic_rx_byte(iic, ack)
#define iic_read_buf(iic, buf, len)         hw_iic_read_buf(iic, buf, len)
#define iic_write_buf(iic, buf, len)        hw_iic_write_buf(iic, buf, len)
#define iic_suspend(iic)                    hw_iic_suspend(iic)
#define iic_resume(iic)                     hw_iic_resume(iic)

#define i2c_master_read_nbytes_from_device_reg(iic, dev_addr, reg_addr, reg_len, read_buf, read_len) \
        hw_i2c_master_read_nbytes_from_device_reg(iic, dev_addr, reg_addr, reg_len, read_buf, read_len)
#define i2c_master_write_nbytes_to_device_reg(iic, dev_addr, reg_addr, reg_len, write_buf, write_len) \
        hw_i2c_master_write_nbytes_to_device_reg(iic, dev_addr, reg_addr, reg_len, write_buf, write_len)
#else
#define get_iic_config(iic)                 get_soft_iic_config(iic)
#define iic_init(iic, config)               soft_iic_init(iic, config)
// ... 对应映射到 soft_iic_* / soft_i2c_master_*
#endif

来源:iic_api.h

设计意图:这是典型的"适配层 + 门面(Facade)"模式。12 个底层原语(init/deinit/start/stop/reset/tx_byte/rx_byte/read_buf/write_buf/suspend/resume)加上 2 个高层业务函数(按设备寄存器地址读写 n 字节),构成完整的最小 I2C 操作集。上层驱动(EEPROM、传感器、RTC 等)只需依赖这 14 个名字;换芯片或换实现时只改编译宏,业务代码零改动。

软件 IIC 实现

iic_soft.c 用两个 GPIO 模拟 I2C 时序。由于 I2C 是半双工同步协议,SCL 时钟与 SDA 数据都由主机控制,软件实现的核心就是"翻转 SCL 采样 SDA"。其关键约束在 iic_api.c 的高层函数中体现得最清楚:

int soft_i2c_master_write_nbytes_to_device_reg(soft_iic_dev iic,
        unsigned char dev_addr,
        unsigned char *reg_addr, unsigned char reg_len,
        unsigned char *write_buf, int write_len)
{
    int res;
    u8 ack;
    local_irq_disable();//软件iic不可被中断
    if (soft_iic_check_busy(iic) != IIC_OK) { //busy
        res = IIC_ERROR_BUSY; //busy
        goto _write_exit2;
    }

    soft_iic_start(iic);
    ack = soft_iic_tx_byte(iic, dev_addr);
    if (ack == 0) {
        log_error("dev_addr no ack!");
        res = IIC_ERROR_DEV_ADDR_ACK_ERROR; //无应答
        goto _write_exit1;
    }

    if ((reg_addr != NULL) && (reg_len != 0)) {
        for (u8 i = 0; i < reg_len; i++) {
            ack = soft_iic_tx_byte(iic, reg_addr[i]);
            if (ack == 0) {
                log_error("reg_addr no ack!");
                res = IIC_ERROR_REG_ADDR_ACK_ERROR; //无应答
                goto _write_exit1;
            }
        }
    }

    for (res = 0; res < write_len; res++) {
        if (0 == soft_iic_tx_byte(iic, write_buf[res])) {
            log_error("write data no ack!");
            goto _write_exit1;
        }
    }
_write_exit1:
    soft_iic_stop(iic);
_write_exit2:
    local_irq_enable();
    return res;
}

来源:iic_api.c

关键机制逐条解读:

  • local_irq_disable() / local_irq_enable() 包住整个事务:软件 IIC 的时序由 CPU 逐位翻转 GPIO 产生,任何中断插入都会拉长 SCL 高/低电平时间,破坏与从设备之间约定的时序(尤其高频从设备可能因超时误判)。因此整个读/写事务必须原子执行——这是软件 IIC 最大的性能与实时性代价,也是其可靠性保证。
  • 忙检查前置:soft_iic_check_busy(iic) 在开事务前检查总线/实例是否被占用,占用直接返回 IIC_ERROR_BUSY,避免两个任务交错操作同一组引脚导致时序错乱。
  • ACK 逐字节校验:每发送一个字节(设备地址、寄存器地址、数据)都读取 SDA 上的应答位;ACK=0 视为从设备无应答,立即 log_error 并跳转到停止序列。读方向同理,写寄存器地址后以**重复起始条件(repeated START)**再发 dev_addr | BIT(0)(读位)。
  • 统一的错误出口:_write_exit1 执行 soft_iic_stop() 释放总线,_write_exit2 恢复中断——保证任何出错路径都不会把总线挂在半途,也不会遗留"关中断未恢复"的致命状态。
  • 返回值语义:写成功返回实际写入字节数(=write_len 为 OK),读成功返回 =read_len;其余返回值为负错误码。

硬件 IIC 实现

hw_i2c_master_* 与软件版结构几乎一致,差异在于底层原语换成硬件控制器操作,并多了一层总线锁保护:

int hw_i2c_master_read_nbytes_from_device_reg(hw_iic_dev iic,
        unsigned char dev_addr,
        unsigned char *reg_addr, unsigned char reg_len,
        unsigned char *read_buf, int read_len)
{
    u8 ack;
    int ret = 0;
    local_irq_disable();
    if (hw_iic_check_busy(iic) != IIC_OK) { //busy
        ret = IIC_ERROR_BUSY; //busy
        goto _read_exit2;
    }

    if ((reg_addr != NULL) && (reg_len != 0)) {
        ret = hw_iic_start(iic);
        if (ret < 0) {
            log_error("iic lock busy!%d", ret);
            goto _read_exit2;
        }
        ack = hw_iic_tx_byte(iic, dev_addr);
        if (ack == 0) {
            log_error("dev_addr no ack!");
            ret = IIC_ERROR_DEV_ADDR_ACK_ERROR; //无应答
            goto _read_exit1;
        }
        // ... 寄存器地址逐字节发送并校验 ACK
    }

    hw_iic_start(iic);
    ack = hw_iic_tx_byte(iic, dev_addr | BIT(0));
    if (ack == 0) {
        log_error("dev_addr no ack!");
        ret = IIC_ERROR_DEV_ADDR_ACK_ERROR; //无应答
        goto _read_exit1;
    }

    ret = hw_iic_read_buf(iic, read_buf, read_len);
_read_exit1:
    hw_iic_stop(iic);
_read_exit2:
    local_irq_enable();
    return ret;
}

来源:iic_api.c

与软件版的关键差异:

  • hw_iic_start() 可能返回负值(iic lock busy!,对应错误码 IIC_ERROR_RESLOCK_BUSY = -11):硬件 IIC 控制器自带总线资源锁,多核或多任务竞争时 start 会失败,代码据此提前退出,而不是像软件版那样直接操作引脚。
  • 批量收发:写数据用 hw_iic_write_buf(iic, write_buf, write_len)(代码中 #if 0 保留了逐字节发送的备选路径),读用 hw_iic_read_buf——硬件控制器支持 FIFO/状态机批量搬移,减少逐字节轮询开销,这是硬件 IIC 吞吐优势的来源。
  • 仍保留 local_irq_disable():虽然硬件控制器可独立工作,但高层 API 为保证事务原子性(避免任务切换期间总线被其他事务占用)仍关闭中断。

IIC 从模式(Slave)

iic_api.h 声明了从设备轮询收发接口,实现在 iic_api.c 末尾:

int hw_iic_slave_polling_rx(hw_iic_dev iic, u8 *rx_buf)
{
    int rx_cnt = 0;
    int rx_state = 0;

    log_info("--iic slave polling rx --");
    local_irq_disable();//关闭所有中断
    rx_state = hw_iic_slave_rx_prepare(iic, 0, 600000);//1s
    if (rx_state == IIC_SLAVE_RX_PREPARE_OK) { //rx
        // ... 轮询接收数据字节
    }
    // ...
}

来源:iic_api.c

从模式协议约定为 start, addr write, data0, data1, …, stop。hw_iic_slave_rx_prepare(iic, 0, 600000) 中的 600000 为超时(约 1 秒,单位依实现而定),防止主设备异常时从设备无限等待;同样以关闭中断保证接收期间不被抢占。

错误码体系

enum iic_state_enum 定义了完整的返回值语义(iic_api.h):

错误码值含义
IIC_OK0成功
IIC_ERROR_INIT_FAIL-1初始化失败
IIC_ERROR_NO_INIT-2未初始化
IIC_ERROR_SUSPEND_FAIL-3挂起(suspend)失败
IIC_ERROR_RESUME_FAIL-4恢复(resume)失败
IIC_ERROR_BUSY-5总线/实例忙
IIC_ERROR_PARAM_ERROR-6参数错误
IIC_ERROR_DEV_ADDR_ACK_ERROR-7设备地址无应答
IIC_ERROR_REG_ADDR_ACK_ERROR-8寄存器地址无应答
IIC_ERROR_INDEX_ERROR-9索引错误
IIC_ERROR_FREQUENCY_ERROR-10频率参数错误
IIC_ERROR_RESLOCK_BUSY-11硬件资源锁被占用

约定:读操作返回 <0 为错误、=read_len 为成功;写操作返回 =write_len 为成功,其余为错误。上层代码据此判断事务成败,无需解析具体错误码即可做重试/报错决策。

Core Flow

主模式读取设备寄存器(Read from Device Register)时序

以 i2c_master_read_nbytes_from_device_reg() 为例,完整事务序列如下:

sequenceDiagram
    participant App as 上层应用 (eeprom/业务)
    participant Api as iic_api 统一层
    participant Impl as soft/hw_iic 实现
    participant Bus as I2C 总线 (SCL/SDA)

    App->>Api: i2c_master_read_nbytes_from_device_reg(iic, dev_addr, reg_addr, reg_len, buf, len)
    Api->>Impl: local_irq_disable() + check_busy()
    Impl-->>Api: IIC_ERROR_BUSY (若忙则立即返回)
    Impl->>Bus: START 条件
    Impl->>Bus: 发送 dev_addr (写位)
    Bus-->>Impl: ACK 校验
    alt 无应答
        Impl-->>Api: IIC_ERROR_DEV_ADDR_ACK_ERROR
    else 有应答
        loop reg_len 次
            Impl->>Bus: 发送 reg_addr[i]
            Bus-->>Impl: ACK 校验 (失败则 IIC_ERROR_REG_ADDR_ACK_ERROR)
        end
        Impl->>Bus: 重复 START
        Impl->>Bus: 发送 dev_addr|BIT(0) (读位)
        Bus-->>Impl: ACK 校验
        Impl->>Bus: 读取 read_len 个字节 (读方发 ACK/NACK)
        Impl->>Bus: STOP 条件
    end
    Impl-->>Api: 返回 read_len 或负错误码
    Api-->>App: 结果

错误处理与总线释放流程

所有读写函数共享"单出口收尾"结构,确保任何失败路径都释放总线并恢复中断:

flowchart TD
    Start([进入事务]) --> Disable["local_irq_disable()"]
    Disable --> Busy{"check_busy == IIC_OK?"}
    Busy -->|"否"| ErrBusy["返回 IIC_ERROR_BUSY"]
    Busy -->|"是"| Start1["iic_start (START 条件)"]
    Start1 --> Ack1{"dev_addr 有 ACK?"}
    Ack1 -->|"否"| ErrDev["IIC_ERROR_DEV_ADDR_ACK_ERROR"]
    Ack1 -->|"是"| RegLoop{"存在 reg_addr?"}
    RegLoop -->|"是"| Ack2{"逐字节 reg_addr 均有 ACK?"}
    Ack2 -->|"否"| ErrReg["IIC_ERROR_REG_ADDR_ACK_ERROR"]
    Ack2 -->|"是"| Restart["重复 START + dev_addr|BIT(0) (读)"]
    RegLoop -->|"否"| Restart
    Restart --> Ack3{"读方向 dev_addr 有 ACK?"}
    Ack3 -->|"否"| ErrDev
    Ack3 -->|"是"| Data["read_buf/write_buf 批量传输"]
    Data --> Stop["iic_stop (STOP 条件)"]
    ErrDev --> Stop
    ErrReg --> Stop
    ErrBusy --> Enable["local_irq_enable()"]
    Stop --> Enable
    Enable --> Ret(["返回 read_len/write_len 或错误码"])

Configuration Options

GPIO 配置

选项类型默认值说明
struct gpio_config.pinu32无(必填)引脚位掩码,支持 PORT_PIN_0 或 PORT_PIN_0 | PORT_PIN_2 同组多引脚
struct gpio_config.modeenum gpio_mode无(必填)引脚模式:PORT_OUTPUT_LOW/HIGH、PORT_HIGHZ、PORT_INPUT_FLOATING、PORT_INPUT_PULLUP/DOWN_10K/100K/1M、PORT_KEEP_STATE
struct gpio_config.hdenum gpio_drive_strength无(必填)驱动强度:PORT_DRIVE_STRENGT_2p4mA/8p0mA/24p0mA/64p0mA(对应最大驱动电流 2.4/8.0/24.0/64.0 mA)

IIC 配置(struct iic_master_config,见 iic_api.h)

选项类型默认值说明
roleenum iic_role无(必填)IIC_MASTER 或 IIC_SLAVE;注释明确"软件只有 IIC_MASTER"
scl_ioint无(必填)SCL 引脚(软件 IIC 为 GPIO 编号,硬件 IIC 为控制器对应引脚)
sda_ioint无(必填)SDA 引脚
io_modeenum gpio_mode无(必填)SCL/SDA 引脚 GPIO 模式,I2C 总线通常配置为 PORT_INPUT_PULLUP_10K(开漏外加上拉的替代)
hdriveenum gpio_drive_strength无(必填)驱动强度:0=2.4mA,1=8mA,2=26.4mA,3=40mA
master_frequencyu32无(必填)主机频率;注释提示"软件 iic 频率 (hz 不准)",即软件实现只能近似
ie_enu8无(必填)中断使能开关
irq_priorityu8无(必填)中断优先级
io_filteru8无(必填)输入滤波:BR27/28/36 芯片 0=关滤波、1=开滤波;BR50 芯片 0=关、1=<1*Tiic_baud_clk、2=<2*Tiic_baud_clk、3=<3*Tiic_baud_clk

编译期开关

宏作用
_IIC_USE_HW定义后 IIC 统一 API 映射到硬件实现(hw_iic_*),否则映射到软件实现(soft_iic_*)
GPIOA…GPIOH、GPIOP、GPIOUSB、GPIOR各端口是否编译进 gpio_toggle_port 分派,用于裁剪代码体积

API Reference

GPIO 核心 API(gpio.h)

int gpio_init(enum gpio_port port, const struct gpio_config *config)

配置同组多个 IO 的模式及驱动强度(pin 可为 PORT_PIN_0 | PORT_PIN_2 掩码)。

参数: port — 端口枚举(PORTA…PORTR);config — 配置结构(pin/mode/hd) 返回: 0 成功;<0 错误(参数非法)

int gpio_set_mode(enum gpio_port port, u32 pin, enum gpio_mode mode)

配置同组多个 IO 的模式。内部按模式分派到 gpio_hw_write_port/gpio_hw_set_direction/gpio_hw_set_die/gpio_hw_set_dieh/gpio_hw_set_pull_up/gpio_hw_set_pull_down。

参数: port、pin(位掩码)、mode(enum gpio_mode) 返回: 0 成功;-1 端口/引脚非法或模式错误(并打印 log_error)

int gpio_deinit(enum gpio_port port, u32 pin)

恢复同组多个 IO 为高阻态(方向置输入、关缓冲、禁上下拉、驱动强度复位 2.4mA)。 返回: 0 成功;-1 参数非法

int gpio_read(u32 gpio) / int gpio_read_port(enum gpio_port port, u32 pin)

读取输入电平。gpio 形如 IO_PORTA_00;pin 为位掩码(多引脚读取)。 返回: 引脚输入电平(1/0)

int gpio_write(u32 gpio, u32 value) / int gpio_write_port(enum gpio_port port, u32 pin, int out_state)

设置输出电平(需先配置为输出)。value/out_state:0 输出低,1 输出高。 返回: 0

int gpio_toggle_port(enum gpio_port port, u32 pin)

翻转同组多个 IO 的输出电平(需先配置为输出)。端口有效性由 GPIOx 宏裁剪。 返回: 0 成功;-1 端口不支持或非法

int gpio_get_out_level(enum gpio_port port, u32 pin)

回读输出数据寄存器电平。 返回: 当前输出电平(1/0)

GPIO 中断 API(gpio.h)

int gpio_irq_config(enum gpio_port port, const struct gpio_irq_config_st *config)

配置并立即使能中断。config 含 pin、irq_edge(PORT_IRQ_DISABLE/RISE/FALL/ANYEDGE)、callback(void (*)(port, pin, edge))、irq_priority。

约束: 禁止同一 IO 同一边沿重复注册;单边沿↔双边沿切换必须先注销再注册。 返回: 0 成功;<0 错误

int gpio_irq_set_callback(enum gpio_port port, u32 pin, gpio_irq_callback_p callback)

仅修改中断回调函数,不改变边沿与使能状态。

int gpio_irq_enable(enum gpio_port port, u32 pin) / int gpio_irq_disable(enum gpio_port port, u32 pin)

快速使能/暂停同组多个 IO 的中断响应,保留边沿配置。

int gpio_irq_set_edge(enum gpio_port port, u32 pin, enum gpio_irq_edge irq_edge) / enum gpio_irq_edge gpio_irq_get_edge(enum gpio_port port, u32 pin)

切换/查询触发边沿。set_edge 仅适用于已注册单边沿的引脚。

IIC 统一 API(iic_api.h)

int i2c_master_read_nbytes_from_device_reg(iic, unsigned char dev_addr, unsigned char *reg_addr, unsigned char reg_len, unsigned char *read_buf, int read_len)

从设备寄存器地址读取 read_len 字节。宏路由到 soft_i2c_... 或 hw_i2c_...。

参数: iic — 设备索引(soft_iic_dev 或 hw_iic_dev);dev_addr — 7 位设备地址;reg_addr/reg_len — 寄存器地址及长度(无寄存器地址传 NULL, 0);read_buf — 接收缓冲;read_len — 读取长度 返回: =read_len 成功;<0 错误(IIC_ERROR_BUSY、IIC_ERROR_DEV_ADDR_ACK_ERROR、IIC_ERROR_REG_ADDR_ACK_ERROR 等) 内部行为: 关中断 → 检查忙 → START → 发设备地址(写) → 发寄存器地址 → 重复 START → 发设备地址|BIT(0)(读) → 批量读 → STOP → 开中断

int i2c_master_write_nbytes_to_device_reg(iic, unsigned char dev_addr, unsigned char *reg_addr, unsigned char reg_len, unsigned char *write_buf, int write_len)

向设备寄存器地址写入 write_len 字节。宏路由到 soft_i2c_... 或 hw_i2c_...。

返回: =write_len 成功;其他为错误 内部行为: 关中断 → 检查忙 → START → 发设备地址(写) → 发寄存器地址 → 批量写(软件版逐字节校验 ACK,硬件版 hw_iic_write_buf)→ STOP → 开中断

int hw_iic_slave_polling_rx(hw_iic_dev iic, u8 *rx_buf) / int hw_iic_slave_polling_tx(hw_iic_dev iic, u8 *tx_buf)

硬件 IIC 从模式轮询接收/发送。接收协议:start, addr write, data0, data1, …, stop;内部 hw_iic_slave_rx_prepare(iic, 0, 600000) 以约 1 秒超时等待主设备发起传输,全程关中断。 返回: 实际收发字节数或错误码

Failure Modes, Edge Cases & Concurrency

失败模式

失败场景检测方式行为
从设备不存在/掉线设备地址发送后 ACK=0返回 IIC_ERROR_DEV_ADDR_ACK_ERROR(-7),打印 dev_addr no ack!,释放总线
寄存器地址越界或设备不支持寄存器地址 ACK=0返回 IIC_ERROR_REG_ADDR_ACK_ERROR(-8),打印 reg_addr no ack!
写数据阶段从设备 NACK数据字节 ACK=0软件版打印 write data no ack! 后停止;硬件版批量写失败返回负值
总线/实例被占用check_busy() 非 OK立即返回 IIC_ERROR_BUSY(-5),不做任何总线操作
硬件资源锁被占用hw_iic_start() 返回负值打印 iic lock busy!%d,返回 IIC_ERROR_RESLOCK_BUSY(-11)语义
GPIO 参数非法gpio_hw_port_pin_judge()返回 -1,不触碰寄存器
非法 GPIO 模式switch 的 default 分支log_error 并返回 -1

并发与原子性

  • 关中断保证事务原子:软/硬件 IIC 高层读写全程 local_irq_disable()。软件 IIC 的位时序完全依赖 CPU 连续执行,中断会直接破坏时序;硬件 IIC 关中断则是为了防止事务中途被任务切换抢占、导致两个线程交错使用同一控制器。这是"正确性优先于实时性"的明确取舍。
  • 忙检查是软互斥:check_busy() 是进入事务前的前置校验,配合关中断形成"检查-使用"原子窗口,防止并发任务交错。多任务环境建议在上层(如 iic_eeprom 或信号量)再做一层互斥,因为忙检查返回 OK 与后续操作之间依赖关中断窗口的完整性。
  • 中断回调上下文:GPIO 中断回调在中断上下文执行,回调内不应调用会阻塞/睡眠的 IIC API——IIC API 本身关中断,在中断里再调用可能叠加关中断时间;高频边沿中断下应只做置标志位等轻量处理。

边界情况

  • reg_addr == NULL && reg_len == 0:跳过寄存器地址阶段,直接进行数据读写——支持"无寄存器地址"的简单器件(如某些 EEPROM 按地址流读写)。
  • gpio_irq_config 重复注册:同一 IO 同一边沿重复注册被禁止,需先以 PORT_IRQ_DISABLE 注销。
  • gpio_toggle_port 的引脚掩码:内部 pin &= PORT_PIN_MASK 后再按端口 IO_PORT_Px_MASK 收敛,多引脚掩码一次翻转。
  • gpio_get_mode 参数断言:ASSERT(!(pin & (pin - 1)), ...) 要求 pin 必须为单个引脚(2 的幂),多引脚掩码会触发断言。

Usage Examples

GPIO 初始化与电平控制

以 gpio_init + gpio_write_port 为例,配置 PORTA 的 PIN_0 为推挽输出高电平,PIN_1 为浮空输入:

struct gpio_config cfg = {
    .pin  = PORT_PIN_0 | PORT_PIN_1,
    .mode = PORT_OUTPUT_HIGH,        /* PIN_0 输出高 */
    .hd   = PORT_DRIVE_STRENGT_8p0mA,
};
gpio_init(PORTA, &cfg);               /* 同组多 IO 一次配置 */

gpio_write_port(PORTA, PORT_PIN_0, 0);   /* 输出低 */
gpio_toggle_port(PORTA, PORT_PIN_0);     /* 翻转为高 */
u32 level = gpio_read_port(PORTA, PORT_PIN_1);  /* 读输入 */
gpio_deinit(PORTA, PORT_PIN_0 | PORT_PIN_1);    /* 恢复高阻 */

来源:gpio.c 与 gpio.h

IIC 读写设备寄存器(统一 API)

上层业务不区分软/硬实现,直接调用 i2c_master_* 宏展开后的函数。以下是从设备 0xA0 的寄存器 0x00 读取 2 字节的典型用法(以软件 IIC 实例为例):

/* 配置软件 IIC:SCL/SDA 复用普通 GPIO */
struct iic_master_config cfg = {
    .role            = IIC_MASTER,
    .scl_io          = IO_PORTA_02,          /* SCL 引脚 */
    .sda_io          = IO_PORTA_03,          /* SDA 引脚 */
    .io_mode         = PORT_INPUT_PULLUP_10K,/* I2C 总线上拉 */
    .hdrive          = PORT_DRIVE_STRENGT_8p0mA,
    .master_frequency = 100000,              /* 目标 100kHz(软 IIC 近似) */
    .ie_en           = 0,
    .irq_priority    = 0,
    .io_filter       = 0,
};
iic_init(iic, &cfg);   /* iic 为 soft_iic_dev 实例 */

u8 reg  = 0x00;
u8 buf[2] = {0};
int ret = i2c_master_read_nbytes_from_device_reg(iic, 0xA0, &reg, 1, buf, 2);
if (ret == 2) {
    /* 读取成功 */
} else if (ret == IIC_ERROR_DEV_ADDR_ACK_ERROR) {
    /* 从设备无应答 */
}

来源:iic_api.h 与 iic_api.c

无寄存器地址的连续写

当器件没有寄存器地址概念时,reg_addr=NULL, reg_len=0,直接连续写 write_len 字节:

u8 data[4] = {0x11, 0x22, 0x33, 0x44};
int ret = i2c_master_write_nbytes_to_device_reg(iic, 0xA0, NULL, 0, data, 4);
if (ret == 4) {
    /* 写入成功 */
}

来源:iic_api.h(注释约定"如果无 reg_addr: reg_addr=NULL, reg_len=0")

Performance & Operational Notes

  • 软件 IIC 的时序不确定性:struct iic_master_config.master_frequency 注释明确"软件 iic 频率(hz 不准)"——位翻转由 CPU 指令序列产生,实际 SCL 频率受主频、缓存命中、中断延迟影响,只能作为近似目标。对时序敏感的高速从设备应改用硬件 IIC。
  • 关中断时长即最坏延迟:一次 IIC 事务(尤其多字节读写)期间所有中断被关闭,这会直接增大系统中断响应延迟与调度抖动。长事务(大 read_len/write_len)应拆分或评估对实时任务的影响。
  • 热路径优化:GPIO 高频 API(gpio_set_mode、gpio_write 等)带 always_inline_when_const_args + AT(.gpio.text.cache.L2) 属性,把代码固定进 L2 缓存文本段,减少取指开销;编译期常量参数可被完全内联。用户代码在循环中调用时尽量传常量引脚编号以受益于内联。
  • 批量收发优于逐字节:硬件 IIC 写数据走 hw_iic_write_buf 批量路径(逐字节路径被 #if 0 保留),软件 IIC 则只能逐字节发送;因此大数据量传输优先选硬件 IIC。
  • 睡眠行为:gpio_keep_mode_at_sleep(port, pin) 使引脚在睡眠期间保持模式(内部置 dieh=1),用于需要维持外部状态(如保持上拉、维持使能信号)的低功耗场景;iic_suspend/iic_resume 对应 IIC 外设的电源管理挂起/恢复。

Extension Points

  • 上层驱动封装:sdk/apps/common/eeprom/iic_eeprom.c/.h 是 IIC 统一 API 的典型消费者,将 I2C 读写封装为 EEPROM 存储接口。新器件(传感器、RTC、触摸 IC)驱动可直接复用 i2c_master_read/write_nbytes_from/to_device_reg,只需提供器件地址与寄存器映射——这是本驱动层最自然的扩展方式。
  • 软/硬实现切换:定义 _IIC_USE_HW 编译宏即可把整条 IIC 调用链从软件实现切换到硬件控制器,业务代码不变;反之亦然。这为"量产用硬件 IIC 提性能、调试用软件 IIC 任意引脚"提供了零成本切换能力。
  • GPIO 中断回调:gpio_irq_config 注册的回调是引脚事件驱动的扩展点,可在回调中唤醒任务、置事件标志或直接处理(注意中断上下文约束)。
  • 功能复用接口:新外设功能若占用 GPIO,应通过 gpio_request_function/gpio_release_function 申请/释放引脚资源,避免与其他模块冲突(timeout 参数可等待占用方释放)。

Related Links

  • GPIO 驱动实现 gpio.c — 模式设置、读写、翻转的完整实现
  • GPIO API 头文件 gpio.h — 模式/强度枚举、中断与功能复用接口定义
  • IIC 统一 API 头文件 iic_api.h — 错误码、配置结构、软硬宏路由
  • IIC API 实现 iic_api.c — 软/硬 IIC 主模式读写与从模式轮询实现
  • 软件 IIC 实现 iic_soft.c — GPIO 位操作模拟 I2C 时序
  • 软件 IIC 头文件 iic_soft.h — soft_iic_dev/struct soft_iic_config 定义
  • 硬件 IIC 寄存器封装 iic_hw_v2.h — 硬件 I2C 控制器寄存器级操作
  • GPIO 硬件寄存器层 gpio_hw.h — 端口判断、方向、上下拉、驱动强度等位操作
  • GPIO 中断实现 gpio_irq.c — PORT 中断与内部信号中断实现
  • GPIO 功能复用实现 gpio_func.c — crossbar 功能切换实现
  • EEPROM 上层封装 iic_eeprom.c — IIC 统一 API 的应用层使用示例
  • IIC 演示程序 iic_demo.c — 驱动调用示例
Prev
cd09 芯片平台与预编译库
Next
系统文件系统与设备模型