杰理 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 引导工程
    • 固件升级机制
  • 开发工具与资源

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

通用 ADC 与定时器

本文档深入解析 AC82N SDK 中 GPADC(通用模数转换器)与 GPTimer(通用定时器)两大外设驱动的完整实现,覆盖 API 契约、工作模式、底层寄存器操作、配置项与典型使用流程。

Purpose and Scope

本页面面向需要直接操作 AC82N 芯片片上外设的驱动开发与系统集成工程师,完整覆盖:

  • GPADC 驱动:SARADC 初始化、CPU/DMA/阻塞/DMA 单通道连续四种采集模式、通道注册与注销、ADC 原始值到电压的换算。
  • GPTimer 驱动:定时、PWM、捕获(上升沿/下降沿/双边沿)、时间测量四种工作模式,以及 gptimer_* 应用层接口与 timer_hw_* 底层寄存器接口的完整 API 参考。

以下内容属于兄弟页面,不在本页展开:GPIO 管脚配置与复用(gpio.h)、系统时钟树(asm/clock.h)、中断控制器与 request_irq 使用、看门狗(wdt_clear)。需要时请参考对应的外设驱动页面。

Overview

GPADC 是什么

GPADC(General Purpose ADC)是芯片内部的 SARADC(逐次逼近型模数转换器),负责把模拟电压转换为数字量。SDK 通过 gpadc_api.h 暴露一组精简 API,屏蔽了采样调度细节:

  • 支持 IO 通道(如 AD_CH_PA5)、ANA 通道(如 AD_ANA_PMU_CH_VPWR_DIV_4,VPWR 四分压)与 DIFF 差分通道(如 AD_DIFF_CH_SP_PA2_SN_PA3)。
  • 提供 CPU 轮询采集队列与 DMA 采集队列两种后台采样机制;调用方先注册通道,硬件持续采样并缓存结果,之后可随时读取最新值或换算电压。
  • 提供阻塞式采集与 DMA 单通道连续采集(可用于连续波形监控),前者会打断现有转换队列,后者会独占 ADC(期间 CPU 模式采样暂停)。

GPTimer 是什么

GPTimer 是芯片内置的通用定时器外设,SDK 的 gptimer.h 将其封装为四个可复用场景:

模式用途
GPTIMER_MODE_TIMER周期定时,产生周期中断并回调用户函数
GPTIMER_MODE_PWMPWM 波形输出,可动态调频、调占空比、翻转、切换输出 IO
GPTIMER_MODE_CAPTURE_EDGE_*输入信号边沿捕获,支持滤波与最大周期限制,可测量脉冲间隔
时间测量辅助接口gptimer_measure_time_* 在无需示波器的场景下粗略测量代码段耗时(比实际时间少约 40us)

驱动层通过 lib_gptimer_* 全局变量控制各功能是否编译进库,通过 gptimer_init() 动态分配 timer id(lib_gptimer_auto_tid_en 使能时自动分配),并暴露一组 timer_hw_* 底层接口供高级用户直接操作寄存器(如 custom_timer_demo 展示的直接配置 JL_TIMER0 的 PRD/PWM/CNT 寄存器)。

Architecture

flowchart TD
    subgraph sg_App["应用层"]
        App["应用代码"]
    end

    subgraph sg_GPADC["GPADC 模块 (gpadc_api.h)"]
        G_Api["adc_init / adc_get_value / adc_get_voltage"]
        G_Cpu["CPU 模式采样队列<br/>adc_add/remove_sample_ch"]
        G_Dma["DMA 模式采样队列<br/>adc_dma_add/remove_sample_ch"]
        G_Block["阻塞采集<br/>adc_get_value_by_blocking"]
        G_Single["DMA 单通道连续采集<br/>adc_dma_enter/exit_single_ch_sample"]
    end

    subgraph sg_GPTimer["GPTimer 模块 (gptimer.h)"]
        T_Api["gptimer_init / gptimer_start / ..."]
        T_Timer["定时模式 GPTIMER_MODE_TIMER"]
        T_Pwm["PWM 模式 GPTIMER_MODE_PWM"]
        T_Cap["捕获模式 GPTIMER_MODE_CAPTURE_*"]
        T_Meas["时间测量 gptimer_measure_time_*"]
        T_Hw["底层驱动 timer_hw_* 寄存器接口"]
    end

    subgraph sg_HW["硬件层"]
        SARADC["SARADC 转换器"]
        TIMER["GPTIMER 外设"]
        IO["GPIO 引脚"]
    end

    App --> G_Api
    App --> T_Api
    G_Api --> G_Cpu
    G_Api --> G_Dma
    G_Api --> G_Block
    G_Api --> G_Single
    G_Cpu --> SARADC
    G_Dma --> SARADC
    G_Block --> SARADC
    G_Single --> SARADC
    T_Api --> T_Timer
    T_Api --> T_Pwm
    T_Api --> T_Cap
    T_Api --> T_Meas
    T_Timer --> T_Hw
    T_Pwm --> T_Hw
    T_Cap --> T_Hw
    T_Meas --> T_Hw
    T_Hw --> TIMER
    TIMER --> IO
    SARADC --> IO

架构说明:

  • 应用层只面向两组公开头文件 API(gpadc_api.h 与 gptimer.h),不直接触碰硬件寄存器(除非走 timer_hw_* 底层接口)。
  • GPADC 的四种采集模式共用同一个 SARADC 硬件,区别在于调度方式:CPU 模式由固件按队列轮询,DMA 模式由 DMA 搬运结果,阻塞模式临时抢占转换队列,单通道连续模式独占 ADC。
  • GPTimer 的四种业务模式都收敛到 timer_hw_* 驱动层,该层负责配置 GPTIMER 的 CON/CNT/PRD/PWM 寄存器、时钟源(lsb_clk/std_x_clk/clk_out2)与预分频、中断优先级和 IO 复用。

GPADC 模块深入分析

通道类型与枚举

GPADC 通道由 enum AD_CH(定义于 sdk/include_lib/driver/cpu/cd09/asm/gpadc.h)枚举,分为三类:

  • IO 通道:直接采样 GPIO 引脚上的电压,例如 AD_CH_PA5、AD_CH_PA8,适合电位器、分压电阻、电池电压检测等场景。
  • ANA 通道:采样芯片内部模拟节点,例如 AD_ANA_PMU_CH_VPWR_DIV_4(VPWR 经 4:1 分压后的电压),读取时需乘以分压倍数还原真实电压。
  • DIFF 差分通道:采样两引脚间压差,例如 AD_DIFF_CH_SP_PA2_SN_PA3(PA2 为正端 SP、PA3 为负端 SN),适合传感器差分信号。

注意区分:CPU 模式的 adc_add_sample_ch() 支持 ANA、IO、DIFF 全部三类通道;DMA 模式的 adc_dma_add_sample_ch() 仅支持 IO 与 DIFF 通道,ANA 通道只能走 CPU 模式。

四种采集模式

1. CPU 模式(轮询队列)

调用 adc_add_sample_ch() 把通道挂入 CPU 采样队列,固件在后台周期性地依次转换队列中的通道,并把最新结果缓存。之后通过 adc_get_value() / adc_get_voltage() 读取。adc_remove_sample_ch() 从队列中移除通道,停止该通道的采样。

printf("@_0 cpu add ch PA5\n");
adc_add_sample_ch(AD_CH_PA5);
printf("@_0 cpu add ch VPWR\n");
adc_add_sample_ch(AD_ANA_PMU_CH_VPWR_DIV_4);

mdelay(10);

printf("@_0 PA5 adc res: %d\n", adc_get_value(AD_CH_PA5));
printf("@_0 PA5 voltage: %dmV\n", adc_get_voltage(AD_CH_PA5));

printf("@_0 VPWR adc res: %d\n", adc_get_value(AD_ANA_PMU_CH_VPWR_DIV_4));
printf("@_0 VPWR voltage: %dmV\n", adc_get_voltage(AD_ANA_PMU_CH_VPWR_DIV_4) * 4);

printf("@_0 cpu remove ch PA5\n");
adc_remove_sample_ch(AD_CH_PA5);
printf("@_0 cpu remove ch VPWR\n");
adc_remove_sample_ch(AD_ANA_PMU_CH_VPWR_DIV_4);

Source: gpadc_demo.c

设计意图:CPU 模式是"注册-后台采样-按需读取"的异步模型。mdelay(10) 用于等待至少一个完整采样周期,保证后续读取到的是有效数据;adc_get_voltage() 内部已使用 MVBG 基准通道做校准换算(详见 adc_value_to_voltage())。

2. DMA 模式(多通道队列)

与 CPU 模式用法几乎一致,但采样结果由 DMA 搬运,减轻 CPU 负担。仅支持 IO 与 DIFF 通道:

printf("@_1 dma add ch PA5\n");
adc_dma_add_sample_ch(AD_CH_PA5);
printf("@_1 dma add ch DIFF SP_PA2_SN_PA3\n");
adc_dma_add_sample_ch(AD_DIFF_CH_SP_PA2_SN_PA3);

mdelay(10);

printf("@_1 PA5 adc res: %d\n", adc_get_value(AD_CH_PA5));
printf("@_1 PA5 voltage: %dmV\n", adc_get_voltage(AD_CH_PA5));

printf("@_1 DIFF SP_PA2_SN_PA3 adc res: %d\n", adc_get_value(AD_DIFF_CH_SP_PA2_SN_PA3));
printf("@_1 DIFF SP_PA2_SN_PA3 voltage: %dmV\n", adc_get_voltage(AD_DIFF_CH_SP_PA2_SN_PA3));

printf("@_1 dma remove ch PA5\n");
adc_dma_remove_sample_ch(AD_CH_PA5);
printf("@_1 dma remove ch DIFF SP_PA2_SN_PA3\n");
adc_dma_remove_sample_ch(AD_DIFF_CH_SP_PA2_SN_PA3);

Source: gpadc_demo.c

注意:读取 DMA 模式的结果同样使用 adc_get_value() / adc_get_voltage(),无需区分模式——API 会从对应缓存中取数。前提是通道必须已经存在于 CPU 或 DMA 采集队列中(头文件注释明确要求"需确保通道有存在于CPU或DMA的采集队列")。

3. 阻塞模式(打断队列)

adc_get_value_by_blocking() / adc_get_voltage_by_blocking() 会打断正在进行的 ADC 转换队列,立刻对指定通道连续采样 times 次,内部做滤波取平均后返回。适合低频、偶发的关键测量:

printf("@_2 PA8 adc res: %d\n", adc_get_value_by_blocking(AD_CH_PA8, 8));
printf("@_2 PA8 voltage: %dmV\n", adc_get_voltage_by_blocking(AD_CH_PA8, 8));

Source: gpadc_demo.c

设计意图:阻塞模式以牺牲队列采样的连续性为代价换取即时性。头文件明确标注"不支持中断调用"——它依赖忙等与队列打断,若在中断上下文调用会导致采样调度错乱甚至死锁。

4. DMA 单通道连续采集模式

adc_dma_enter_single_ch_sample() 让 ADC 持续对单个通道采样,通过 DMA 把数据连续送入用户回调,适合连续波形采集。进入该模式后 CPU 模式采样暂停,退出后恢复:

void gpadc_dma_conti_mode_callback(u16 *buf, u32 len)
{
    /* printf("len: %d\n", len); */
}

void gpadc_dma_conti_mode_demo(void)
{
    printf("\n@_3 gpadc blocking mode demo test start\n");
    printf("@_3 enter sample DIFF SP_PA2_SN_PA3\n");
    adc_dma_enter_single_ch_sample(AD_DIFF_CH_SP_PA2_SN_PA3, gpadc_dma_conti_mode_callback);
    mdelay(1);
    printf("@_3 exit sample DIFF SP_PA2_SN_PA3\n");
    adc_dma_exit_single_ch_sample(AD_DIFF_CH_SP_PA2_SN_PA3);
}

Source: gpadc_demo.c

数据格式说明(头文件注释):回调收到的 buf 为 16bit 数据,有效数据在高位(bit15~4),低 4 位无效,读取时需 >> 4。enter 与 exit 必须成对调用,否则 ADC 会一直处于独占状态,CPU 模式采样无法恢复。

GPADC 核心流程(CPU 模式)

sequenceDiagram
    participant App as 应用代码
    participant Api as adc_xxx API
    participant Queue as 采样队列
    participant ADC as SARADC 硬件
    participant Cache as 结果缓存

    App->>Api: adc_init()
    App->>Api: adc_add_sample_ch(AD_CH_PA5)
    Api->>Queue: 注册通道 PA5
    App->>Api: adc_add_sample_ch(AD_ANA_PMU_CH_VPWR_DIV_4)
    Api->>Queue: 注册通道 VPWR
    Note over ADC,Queue: 硬件按队列周期轮询采样 (mdelay 10)
    ADC-->>Cache: 更新各通道原始 ADC 值
    App->>Api: adc_get_value(AD_CH_PA5)
    Api-->>App: 原始 ADC 值
    App->>Api: adc_get_voltage(AD_CH_PA5)
    Api-->>App: 电压值 (mV)
    App->>Api: adc_remove_sample_ch(AD_CH_PA5)
    Api->>Queue: 注销通道 PA5

电压换算原理:adc_value_to_voltage(u16 adc_vbg, s16 adc_raw_value) 以 MVBG(内部基准电压通道)的 ADC 值为参考,把目标通道原始值线性映射为毫伏电压。adc_get_voltage() 内部自动完成"读 MVBG 通道 + 读目标通道 + 换算"三步,因此返回的是已校准电压。

GPTimer 模块深入分析

配置结构体与模式枚举

gptimer.h 使用联合体 + 模式枚举的设计:struct gptimer_config 内嵌一个 union,根据 mode 字段选择 timer/pwm/capture 三种子结构之一,避免无关字段互相干扰:

enum gptimer_mode : u8 {
    GPTIMER_MODE_TIMER = 0, //定时模式
    GPTIMER_MODE_PWM, //pwm模式
    GPTIMER_MODE_CAPTURE_EDGE_RISE, //上升沿捕获
    GPTIMER_MODE_CAPTURE_EDGE_FALL, //下降沿捕获
    GPTIMER_MODE_CAPTURE_EDGE_ANYEDGE, //双边沿捕获
};

struct gptimer_timer { //计时(计数)器配置
    u32 period_us;
};
struct gptimer_pwm { //pwm配置
    u32 freq;
    enum gpio_port port;
    u16 pin;
    u16 duty; //x10000
};
struct gptimer_capture { //捕获配置
    u32 filter; //需要滤除的频率,单位 Hz
    u32 max_period; //最大捕获周期, 单位:us, 需要用到才配置,默认配置0
    enum gpio_port port;
    u16 pin;
};

struct gptimer_config {
    union {
        struct gptimer_timer timer;
        struct gptimer_pwm pwm;
        struct gptimer_capture capture;
    };
    timer_irq_callback irq_cb;
    void *private_data; //用户参数
    u8 irq_priority;
    enum gptimer_mode mode; //工作模式
};

Source: gptimer.h

设计意图:duty 以万分比表示(注释 x10000),例如 3456 表示 34.56%——用整数避免浮点运算,在无 FPU 的 MCU 上性能更好。irq_cb 回调签名统一为 void (*)(u32 tid, void *private_data),private_data 透传用户上下文,与 Linux 内核 request_irq 的 dev_id 思路一致。

定时模式(GPTIMER_MODE_TIMER)

配置周期 period_us(微秒),注册中断回调,gptimer_start() 后硬件按周期产生中断:

static void timer_callback_func_0(u32 tid, void *private_data)
{
    putchar('t');
    //可以翻转IO口测量时间
    gpio_set_mode(PORTA, BIT(1), PORT_OUTPUT_HIGH);
    gpio_set_mode(PORTA, BIT(1), PORT_OUTPUT_LOW);
}

static void timer_demo()
{
    printf("timer_demo()\n");
    const struct gptimer_config timer_config = {
        .timer.period_us = 1 * 1000 * 1000, //定时周期, 1000000us
        .irq_cb = timer_callback_func_0, //设置中断回调函数
        .irq_priority = 1, //中断优先级
        .mode = GPTIMER_MODE_TIMER, //设置工作模式
        .private_data = NULL,
    };
    u32 id = gptimer_init(TIMERx, &timer_config); //初始化timer配置,成功会返回分配的timer_id, 失败返回-1
    printf("timer_demo id:%d\n", id);

    gptimer_start(id); //启动timer
    /* gptimer_set_timer_period(id, 500 * 1000); //设置定时周期为500ms */
    /* u32 timer_us = gptimer_get_timer_period(id); //获取定时周期 */
    /* printf("timer period_us = %dus\n", timer_us); */
    /* gptimer_set_irq_callback(id, timer_callback_func_1); //设置回调函数 */
}

Source: gptimer_demo.c

设计意图:gptimer_init() 返回的 id 是句柄而非硬件单元——lib_gptimer_auto_tid_en 使能时驱动内部自动完成 tid → GPTIMER* 映射,后续所有 gptimer_* 接口都只认 tid,上层无需关心 TIMER0/1/2 具体是哪个。失败时返回 GPTIMER_ERR_INIT_FAIL (0xFF),因此 demo 中先判断 id 再使用。

PWM 模式(GPTIMER_MODE_PWM)

配置频率 freq(Hz)、占空比 duty(万分比)与输出 IO(port + pin)。初始化后必须先把 IO 设为输出,再 gptimer_start();运行中可动态调频、调占空比、翻转相位、切换输出引脚:

static void pwm_demo()
{
    printf("pwm_demo()\n");
    const struct gptimer_config pwm_config = {
        .pwm.freq = 1 * 1000, //设置输出频率
        .pwm.duty = 3456, //设置占空比为34.56%
        .pwm.port = PORTA, //设置pwm输出IO_ PORTA_02
        .pwm.pin = BIT(2), //设置pwm输出IO_PORTA_02
        .mode = GPTIMER_MODE_PWM, //设置工作模式
    };
    u32 id = gptimer_init(TIMERx, &pwm_config);
    printf("pwm_demo id:%d\n", id);
    gpio_set_mode(PORTA, BIT(2), PORT_OUTPUT_LOW); //IO口设为输出

    gptimer_start(id); //启动timer
    gptimer_set_pwm_freq(id, 2000); //设置pwm频率
    u32 freq = gptimer_get_pwm_freq(id);
    printf("pwm freq = %dHz\n", freq);
    gptimer_set_pwm_duty(id, 7500); //设置pwm占空比
    u32 duty = gptimer_get_pwm_duty(id);
    printf("pwm duty = %d/10000\n", duty);
    gptimer_pwm_flip(id); //pwm翻转
    udelay(1 * 1000 * 1000);
    gpio_set_mode(PORTA, BIT(3), PORT_OUTPUT_LOW); //IO口设为输出
    gptimer_set_pwm_port(id, PORTA, BIT(3)); //切换输出IO
    udelay(1 * 1000 * 1000);
    gpio_set_mode(PORTD, BIT(8), PORT_OUTPUT_LOW); //IO口设为输出
    gptimer_set_pwm_port(id, PORTD, BIT(8)); //切换输出IO, PORTD_08 为timer0_pwm的固定IO
}

Source: gptimer_demo.c

设计意图:切换输出 IO 前需先对新 IO 执行 gpio_set_mode(..., PORT_OUTPUT_LOW),因为 PWM 信号要能驱动引脚必须先配置为输出;gptimer_set_pwm_port() 负责把 PWM 复用功能从旧 IO 迁到新 IO(底层调用 timer_hw_pwm_set_port)。demo 中 PORTD_08 是 timer0 PWM 的固定映射 IO,说明并非任意 IO 都支持 PWM 复用,需查阅芯片 IO 复用表。

捕获模式(GPTIMER_MODE_CAPTURE_*)

配置输入引脚、滤波频率 filter(滤除低于该频率的干扰,单位 Hz)、可选的最大周期 max_period(us,防溢出,默认 0),以及边沿类型。捕获 IO 需设为浮空输入:

static void capture_demo()
{
    printf("capture_demo()\n");
    const struct gptimer_config capture_config = {
        .capture.filter = 1000, //设置滤波频率, 1000Hz
        .capture.max_period = 0,//110000, //最大计时周期, 单位us, 默认给0
        .capture.port = PORTA, //设置捕获口IO_PORTA_04
        .capture.pin = BIT(3), //设置捕获口IO_PORTA_04
        .irq_cb = timer_callback_func_2, //设置回调函数
        .irq_priority = 3, //设置捕获中断优先级
        .mode = GPTIMER_MODE_CAPTURE_EDGE_ANYEDGE, //设置工作模式
    };
    u32 id = gptimer_init(TIMERx, &capture_config);
    printf("capture_demo id:%d\n", id);
    gpio_set_mode(PORTA, BIT(3), PORT_INPUT_FLOATING); //IO口设为浮空输入

    gptimer_start(id); //启动timer
    gptimer_set_capture_filter(id, 100000); //设置滤波参数为10000Hz
    gptimer_set_capture_count(id, 0); //设置cnt寄存器
    u32 cnt = gptimer_get_capture_count(id);
    printf("capture cnt = %d\n", cnt);
    u32 us = gptimer_get_capture_cnt2us(id); //获取捕获时间间隔, 单位:us
    printf("capture_us = %dus \n", us);
}

Source: gptimer_demo.c

设计意图:gptimer_get_capture_cnt2us() 仅在捕获事件发生后调用才有效(头文件注释),它把两次边沿之间的 tick 数按当前时钟换算为微秒;gptimer_set_capture_filter() 可在运行时调整抗干扰门限,无需重建定时器。

时间测量模式(gptimer_measure_time_*)

无需中断与 IO,直接测量两段代码的执行耗时。头文件注明"比实际时间少约 40us"(调用开销),适合粗粒度性能分析:

static void measure_demo()
{
    printf("measure_demo()\n");
    u32 id = gptimer_measure_time_init(TIMER2, 0);
    u32 time_us;
    while (1) {
        gptimer_measure_time_start(id);
        gpio_set_mode(PORTA, BIT(3), PORT_OUTPUT_HIGH);
        udelay(10000); //需配合示波器或逻辑分析仪测量IO间隔时间
        gpio_set_mode(PORTA, BIT(3), PORT_OUTPUT_LOW);
        time_us = gptimer_measure_time_end(id);
        printf("time_us = %d\n", time_us);
        udelay(500 * 1000);
        wdt_clear();
    }
}

Source: gptimer_demo.c

寄存器级自定义用法

当应用层 API 无法满足特殊需求(如自定义时钟源、特殊分频)时,可直接操作 GPTIMER 寄存器。custom_timer_demo 展示了一个完整例子:用 clk_out2 做时钟源(2.5 分频 + 63 分频得到 3MHz),配置 PRD=1000、PWM=500 输出 3kHz、50% 占空比的 PWM:

SYS_PLL_CLK_5DIV2_OE(1); //sys_pll_d2p5 时钟使能 2.5分频使能
SFR(JL_CLOCK->STD_CON1, 10, 4, SYS_PLL_D2P5_CLK_OUT_2); //选择2.5分频时钟
SFR(JL_CLOCK->STD_CON1, 14, 6, 63); //分频设置 1~64分频, 设置值0~63
TIMER->CON = (GPTIMER_PND_CLR | GPTIMER_CLK_SRC_CLKOUT2 | GPTIMER_CLK_DIV_1);//选择时钟源为 clk_out2, 选择预分频系数为 1
//按如上配置,timer_clk = 3MHz
//注意:最终需要满足 timer_clk <= (lsb_clk / 2)
TIMER->CON |= TIMER_PWM_EN; //pwm输出使能
TIMER->PRD = 1000; //设置 PRD 寄存器为 1000, pwm输出频率为 3 * 1000000 / 1000 = 3 * 1000 Hz;
TIMER->PWM = 500; //设置 PWM 寄存器为 500, pwm输出占空比为 500 / 1000 * 100% = 50.00%
gpio_set_function(IO_PORT_SPILT(IO_PORTA_01), PORT_FUNC_TIMER0_PWM); //pwm配置输出IO
gpio_set_mode(IO_PORT_SPILT(IO_PORTA_01), PORT_OUTPUT_LOW); //IO设为输出模式
TIMER->CON |= GPTIMER_TIMER_MODE; //timer启动

Source: gptimer_demo.c

关键约束(源码注释):timer_clk <= lsb_clk / 2,即定时器输入时钟不得超过系统 lsb 时钟的一半,否则时序无法收敛。直接操作寄存器模式需要自行清中断标志(GPTIMER_PND_CLR / TIMER->CON |= BIT(14)),驱动封装模式则由库自动处理。

GPTimer 模式选择流程

flowchart TD
    Start([gptimer_init TIMERx]) --> Mode{"选择 mode"}
    Mode -->|"GPTIMER_MODE_TIMER"| Timer["配置 timer.period_us + irq_cb"]
    Mode -->|"GPTIMER_MODE_PWM"| Pwm["配置 pwm.freq/duty/port/pin"]
    Mode -->|"GPTIMER_MODE_CAPTURE_EDGE_*"| Cap["配置 capture.filter/max_period/port/pin"]
    Mode -->|"时间测量接口"| Meas["gptimer_measure_time_init"]
    Timer --> Tid["返回 tid (失败为 0xFF)"]
    Pwm --> Tid
    Cap --> Tid
    Meas --> Tid
    Tid --> IOset["IO 模式设置 (输出/浮空输入)"]
    IOset --> Start2([gptimer_start])
    Start2 --> Run["硬件运行"]
    Run --> Adjust{"需要动态调整?"}
    Adjust -->|"是"| Dyn["set/get 周期/频率/占空比/捕获参数"]
    Dyn --> Run
    Adjust -->|"否"| Stop([gptimer_deinit 释放资源])

定时中断执行序列

sequenceDiagram
    participant App as 应用代码
    participant Api as gptimer_xxx API
    participant HW as GPTIMER 硬件
    participant ISR as 中断回调 (irq_cb)

    App->>Api: gptimer_init(TIMERx, &config)
    Api-->>App: tid
    App->>Api: gptimer_start(tid)
    Api->>HW: 启动计数
    loop 每个周期
        HW->>ISR: 定时中断触发
        ISR->>ISR: 执行用户逻辑 (含 private_data)
        ISR->>HW: 清除 PND 中断标志
    end
    App->>Api: gptimer_pause(tid) / gptimer_resume(tid)
    App->>Api: gptimer_deinit(tid)

API 参考

GPADC 接口(gpadc_api.h)

所有接口原型见 gpadc_api.h。

函数说明关键参数 / 返回值
void adc_init(void)SARADC 初始化,使用前必须调用一次无
void adc_uninit(void)SARADC 反初始化,释放硬件资源无
void adc_clk_init(int clk)配置 SARADC 时钟clk: 时钟频率
s16 adc_get_value(enum AD_CH ch)获取指定通道 ADC 原始值前置条件:通道已在 CPU 或 DMA 队列中注册
s32 adc_get_voltage(enum AD_CH ch)获取指定通道电压(mV)返回毫伏值
s32 adc_value_to_voltage(u16 adc_vbg, s16 adc_raw_value)用 MVBG 基准将原始值换算为电压adc_vbg: MVBG 通道值;adc_raw_value: 目标通道值
void adc_add_sample_ch(enum AD_CH ch)注册通道到 CPU 采样队列支持 ANA/IO/DIFF 通道
void adc_remove_sample_ch(enum AD_CH ch)从 CPU 采样队列移除通道—
void adc_dma_add_sample_ch(enum AD_CH ch)注册通道到 DMA 采样队列仅支持 IO/DIFF 通道
void adc_dma_remove_sample_ch(enum AD_CH ch)从 DMA 采样队列移除通道—
void adc_dma_enter_single_ch_sample(enum AD_CH ch, void (*cb)(u16 *buf, u32 len))进入 DMA 单通道连续采集(独占 ADC)回调 buf 有效数据需 >>4;必须与 exit 成对使用
void adc_dma_exit_single_ch_sample(enum AD_CH ch)退出单通道连续采集,恢复多通道/CPU 模式必须与 enter 成对使用
s16 adc_get_value_by_blocking(enum AD_CH ch, u8 times)阻塞采样 times 次取平均返回原始值不支持中断调用
s16 adc_get_voltage_by_blocking(enum AD_CH ch, u8 times)阻塞采样并内部滤波取平均返回电压不支持中断调用

GPTimer 应用层接口(gptimer.h)

全部原型见 gptimer.h。

函数说明备注
u32 gptimer_init(const timer_dev timerx, const struct gptimer_config *gt_cfg)按配置初始化定时器成功返回 tid;失败返回 GPTIMER_ERR_INIT_FAIL (0xFF)
u32 gptimer_deinit(u32 tid)释放定时器资源—
u32 gptimer_start(u32 tid)启动定时器—
u32 gptimer_pause(u32 tid) / gptimer_resume(u32 tid)暂停 / 恢复暂停不丢失已配置参数
gptimer_set/get_timer_period(u32 tid, u32 period_us)动态设置 / 获取定时周期(us)仅定时模式
gptimer_set/get_pwm_freq(u32 tid, u32 freq)动态设置 / 获取 PWM 频率(Hz)仅 PWM 模式
gptimer_set/get_pwm_duty(u32 tid, u32 duty)动态设置 / 获取占空比(x10000)仅 PWM 模式
gptimer_set_pwm_port(u32 tid, enum gpio_port port, u32 pin)切换 PWM 输出 IO仅 PWM 模式
gptimer_pwm_flip(u32 tid)PWM 相位翻转—
gptimer_pwm_enable(u32 tid) / gptimer_pwm_disable(u32 tid)使能 / 关闭 PWM 输出—
gptimer_set/get_capture_edge_type(u32 tid, enum gptimer_mode edge_type)设置 / 获取捕获边沿类型仅捕获模式
gptimer_set/get_capture_filter(u32 tid, u32 filter)设置 / 获取捕获滤波频率(Hz)—
gptimer_set/get_capture_count(u32 tid, u32 cnt)设置 / 获取捕获计数寄存器—
u32 gptimer_get_capture_cnt2us(u32 tid)捕获 tick 数转微秒仅在捕获事件发生后有效
gptimer_set/get_count(u32 tid, u32 cnt)直接读写 CNT 寄存器—
gptimer_set/get_prd(u32 tid, u32 prd)直接读写 PRD 寄存器—
void *gptimer_get_private_data(u32 tid) / gptimer_set_private_data(...)读写用户私有参数回调中可经 tid 取回
gptimer_set_irq_callback(u32 tid, void (*irq_cb)(u32 tid, void *private_data))运行时更换中断回调—
gptimer_get/set_work_mode(u32 tid, enum gptimer_mode type)获取 / 切换工作模式—
gptimer_measure_time_init(const timer_dev timerx, u32 max_time_us)初始化时间测量结果比实际少约 40us
gptimer_measure_time_start(u32 tid) / gptimer_measure_time_end(u32 tid)开始 / 结束计时end 返回 us
void gptimer_dump(void)打印所有定时器状态,便于调试demo 中用于输出分配结果

底层寄存器接口(timer_hw_*)

位于 gptimer.h,供高级用户绕过应用层直接操作硬件:

  • 初始化/反初始化:timer_hw_timer_init / timer_hw_pwm_init / timer_hw_capture_init 及对应 *_deinit
  • 启停:timer_hw_start / timer_hw_pause
  • 参数读写:timer_hw_set/get_cur_para、timer_hw_set/get_cnt、timer_hw_set/get_prd
  • PWM 控制:timer_hw_pwm_flip/enable/disable/set_port
  • 捕获:timer_hw_get_capture_filter、timer_hw_set_irq_cb
  • 时钟工具:timer_hw_set_clk、hw_irflt_set_clk、timer_hw_tid2timer、timer_hw_tick2us、timer_hw_idle_check
  • 时间测量:timer_hw_measure_time_init/start/end

配置选项

GPTimer 库行为由一组库级全局变量(需在工程配置中赋值,对应 gptimer.h)控制:

配置项类型默认说明作用
lib_gptimer_src_lsb_clku8按工程时钟配置时钟源选 lsb_clk 时的频率(MHz),决定定时精度换算
lib_gptimer_src_std_clku8按工程时钟配置时钟源选 std_x_clk 时的频率(MHz)
lib_gptimer_timer_mode_enu81编译是否包含定时模式功能
lib_gptimer_pwm_mode_enu81编译是否包含 PWM 模式功能
lib_gptimer_capture_mode_enu81编译是否包含捕获模式功能
lib_gptimer_auto_tid_enu81是否由库自动分配 tid(关闭后需手动管理 tid→TIMERx 映射)

GPADC 侧的可配置项为 adc_clk_init(int clk) 的时钟参数;采样周期由内部队列调度决定,demo 中使用 mdelay(10) 等待首个采样周期完成。

配置结构体运行时参数汇总:

结构体字段类型单位说明
gptimer_timer.period_usu32us定时周期
gptimer_pwm.frequ32HzPWM 输出频率
gptimer_pwm.dutyu16万分比占空比,3456 = 34.56%
gptimer_pwm.port / pinenum/u16—PWM 输出 IO
gptimer_capture.filteru32Hz捕获输入滤波频率
gptimer_capture.max_periodu32us最大捕获周期(防溢出,0 表示不限制)
gptimer_capture.port / pinenum/u16—捕获输入 IO
gptimer_config.irq_priorityu8—中断优先级
gptimer_config.private_datavoid*—回调透传的用户上下文

失败模式、边界情况与并发

GPADC

  • 未注册通道读取:adc_get_value() / adc_get_voltage() 要求通道已存在于 CPU 或 DMA 队列,否则返回无意义数据。必须遵循"先 add 后 get,先 remove 后停止使用"的顺序。
  • 阻塞模式与中断冲突:adc_get_value_by_blocking() / adc_get_voltage_by_blocking() 会打断转换队列,头文件明确"不支持中断调用"。若在中断中调用,队列调度将被破坏。
  • 单通道连续模式的独占性:adc_dma_enter_single_ch_sample() 期间 CPU 模式采样暂停;忘记调用 adc_dma_exit_single_ch_sample() 会导致其他通道永远采不到数据。
  • DMA 回调数据对齐:回调 buf 低 4 位无效,有效数据位于高位,必须 >> 4 后再使用,否则数值错误。
  • ANA 通道限制:adc_dma_add_sample_ch() 仅支持 IO/DIFF 通道,试图 DMA 采样 ANA 通道不会得到预期结果。

GPTimer

  • init 失败:gptimer_init() 返回 GPTIMER_ERR_INIT_FAIL (0xFF) 表示资源不足或配置非法;demo 中 id 为负即失败,后续接口不得使用该 id。
  • 非法 tid:所有 gptimer_* 接口都要求 tid 来自成功的 gptimer_init();other_api_demo 特意注释"id 需 init 申请成功后才可使用",直接使用非法 tid 是未定义行为。
  • 捕获计时的时效性:gptimer_get_capture_cnt2us() 只在捕获事件发生后调用才有效,在捕获间隙调用返回的是过期或无意义数据。
  • 时钟约束:直接配置寄存器时需满足 timer_clk <= lsb_clk / 2;clk_out2 分频链(SYS_PLL_CLK_*_OE 与 STD_CON1 的 SFR 位域)配置错误会导致 PWM 频率与预期不符。
  • 时间测量精度:gptimer_measure_time_* 结果比实际时间少约 40us(函数调用与寄存器读写开销),做高精度测量时需校准或改用外部手段(如示波器测量 IO 翻转间隔)。
  • 中断回调内的操作:irq_cb 运行在中断上下文,内部不应调用阻塞型 API(如 ADC 阻塞采集、udelay 长延时),并应尽快清 PND 标志(驱动封装模式下由库处理)。

并发注意

  • 单通道连续采集与 CPU 队列采集互斥:进入单通道模式前应确保没有关键通道依赖 CPU 采样。
  • 多个 GPTimer 共用时钟树时,修改 lib_gptimer_src_* 或时钟分频会影响所有使用该时钟源的定时器,属于全局性变更,需整体评估。
  • 中断回调与主循环可能同时访问同一 tid 的参数(如周期/占空比),若存在写冲突,建议在临界区(关中断)内修改或使用驱动提供的原子接口。

性能与运行注意事项

  • 采样等待:GPADC 注册通道后并非立即有有效数据,demo 统一使用 mdelay(10) 等待首个采样周期;实际等待时间与 adc_clk_init() 配置的转换时钟及队列长度相关,生产代码应通过通道值有效性判断或按需加长等待。
  • 阻塞采样的代价:adc_get_value_by_blocking(ch, times) 连续采样 times 次并取平均,采样次数越多结果越稳、耗时越长,期间 ADC 队列停摆。高频读取场景应改用 CPU/DMA 队列模式,仅在偶发关键测量时用阻塞模式。
  • PWM 动态调整的开销:gptimer_set_pwm_freq() / gptimer_set_pwm_duty() 会重算 PRD/PWM 寄存器,频繁调整会产生额外指令开销;切换输出 IO(gptimer_set_pwm_port)涉及 GPIO 复用寄存器切换,不适合高频切换。
  • 定时器时钟约束:寄存器级配置务必遵守 timer_clk <= lsb_clk / 2,超限可能导致计数时序错误。使用 clk_out2 作为时钟源时,STD_CON1 的 2.5 分频使能与 1~64 分频(写 0~63)必须配套设置。
  • 调试手段:gptimer_dump() 可打印库内所有定时器分配与状态,是排查 tid 分配冲突的首选工具;demo 中通过翻转 IO(PORT_OUTPUT_HIGH/LOW)配合示波器验证定时周期与 PWM 波形。

扩展点

  • 库功能裁剪:通过 lib_gptimer_timer_mode_en、lib_gptimer_pwm_mode_en、lib_gptimer_capture_mode_en 开关可在编译期裁剪功能,减小代码体积——不需要 PWM 的产品可关掉 PWM 支持。
  • tid 分配策略:lib_gptimer_auto_tid_en 决定 tid 自动分配与否。多任务系统若需要固定 tid 映射,可关闭自动分配并自行维护映射表。
  • 回调上下文透传:gptimer_config.private_data 与 gptimer_set/get_private_data() 允许把任意用户结构体(如设备句柄、软件状态机指针)传入回调,是实现"一个回调服务多个定时器实例"的推荐方式。
  • 底层寄存器直通:timer_hw_* 接口与 custom_timer_demo 展示的寄存器直写方式,为特殊时钟源、特殊分频、非标准波形需求提供逃生通道;扩展时务必遵循 GPTIMER_PND_CLR 清中断、timer_clk <= lsb_clk/2 两条硬约束。
  • GPADC 通道扩展:新通道只需使用 enum AD_CH 中已定义的通道号注册即可;若需新增芯片未枚举的引脚通道,需同时修改 asm/gpadc.h 的通道枚举与底层 MUX 配置(超出本页范围,属于芯片手册级改动)。

测试与验证

  • gpadc_test_demo()(gpadc_demo.c)串联验证四种采集模式:先 adc_init(),依次执行 CPU 模式(PA5 单端 + VPWR 分压)、DMA 模式(PA5 + DIFF 差分)、阻塞模式(PA8,8 次平均)、DMA 单通道连续模式(DIFF 通道 enter/exit),并打印每个通道的原始值与电压,是回归测试 GPADC 的基线用例。
  • gptimer_test_demo()(gptimer_demo.c)默认运行定时模式(1s 周期回调翻转 IO),并注释预留了 pwm_demo / capture_demo / measure_demo / other_api_demo / custom_timer_demo,逐个打开即可验证对应功能;结束时调用 gptimer_dump() 并喂狗(wdt_clear())防止看门狗复位。
  • 验证建议:PWM 用示波器测量频率/占空比/翻转/IO 切换;捕获用信号发生器注入不同频率方波并比对 gptimer_get_capture_cnt2us();时间测量用 IO 翻转间隔与示波器交叉验证 40us 系统误差。

Related Links

  • GPADC 应用层接口头文件 gpadc_api.h
  • GPADC 测试 Demo gpadc_demo.c
  • GPTimer 驱动头文件 gptimer.h
  • GPTimer 测试 Demo gptimer_demo.c
  • GPTimer 硬件寄存器定义 gptimer_hw_v4.h
  • CD09 芯片 GPADC 底层实现 gpadc.c
  • 相关外设页面:GPIO 管脚配置与复用、系统时钟(asm/clock.h)、中断管理(request_irq)
Prev
高精度 ADC(HADC)
Next
UART / SPI / IIC 通信外设