通用 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_PWM | PWM 波形输出,可动态调频、调占空比、翻转、切换输出 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_clk | u8 | 按工程时钟配置 | 时钟源选 lsb_clk 时的频率(MHz),决定定时精度换算 |
lib_gptimer_src_std_clk | u8 | 按工程时钟配置 | 时钟源选 std_x_clk 时的频率(MHz) |
lib_gptimer_timer_mode_en | u8 | 1 | 编译是否包含定时模式功能 |
lib_gptimer_pwm_mode_en | u8 | 1 | 编译是否包含 PWM 模式功能 |
lib_gptimer_capture_mode_en | u8 | 1 | 编译是否包含捕获模式功能 |
lib_gptimer_auto_tid_en | u8 | 1 | 是否由库自动分配 tid(关闭后需手动管理 tid→TIMERx 映射) |
GPADC 侧的可配置项为 adc_clk_init(int clk) 的时钟参数;采样周期由内部队列调度决定,demo 中使用 mdelay(10) 等待首个采样周期完成。
配置结构体运行时参数汇总:
| 结构体字段 | 类型 | 单位 | 说明 |
|---|---|---|---|
gptimer_timer.period_us | u32 | us | 定时周期 |
gptimer_pwm.freq | u32 | Hz | PWM 输出频率 |
gptimer_pwm.duty | u16 | 万分比 | 占空比,3456 = 34.56% |
gptimer_pwm.port / pin | enum/u16 | — | PWM 输出 IO |
gptimer_capture.filter | u32 | Hz | 捕获输入滤波频率 |
gptimer_capture.max_period | u32 | us | 最大捕获周期(防溢出,0 表示不限制) |
gptimer_capture.port / pin | enum/u16 | — | 捕获输入 IO |
gptimer_config.irq_priority | u8 | — | 中断优先级 |
gptimer_config.private_data | void* | — | 回调透传的用户上下文 |
失败模式、边界情况与并发
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)