系统服务与外设管理
AC79xx AIoT SDK 中负责系统级服务(复位原因、唤醒源、电源/时钟管理、看门狗)与片内外设(GPIO、GPCNT 等)驱动的统一管理入口,为应用层提供安全、可追溯的硬件控制能力。
Purpose and Scope
本页面涵盖 AC79xx 芯片上"系统服务与外设管理"这一能力的完整实现,包括:
- GPIO 外设管理:方向、上下拉、驱动强度、数字/模拟功能切换及多核安全访问;
- 系统复位原因管理:上电、软复位、看门狗、IO 唤醒、闹钟唤醒、LPTMR 唤醒等复位源的检测与查询;
- 系统电源与时钟管理:按系统时钟档位联动调整内核电压、VDC14 电压与 IO 电压的查表机制;
- 看门狗与外设计数器:
watchdog_close、GPCNT 通用计数器的 IO 配置接口。
本页面不覆盖以下内容(它们属于独立目录页):存储设备(Flash/SD)、音频编解码、网络协议栈、文件系统与电源管理策略(低功耗状态机)本身。相关页面见 Related Links。
Overview
在嵌入式 AIoT 系统中,应用层对硬件的访问必须经过一层"受控的服务抽象",否则会出现引脚冲突、时钟/电压不匹配、复位原因不可追溯等问题。AC79xx SDK 将这类能力集中放在 include_lib/driver/device/(外设驱动头文件)与 apps/common/system/(系统服务实现)中:
- GPIO 提供引脚级的最基本操作,并利用自旋锁保证在双核(甚至中断)并发访问时端口配置不被破坏;
- 复位原因模块 通过读取
JL_SYSTEM->RST_SRC与 PMU(p33)寄存器,把一次冷/热启动归类为可读的枚举原因,供应用决定"是上电首次运行、还是唤醒后继续业务"; - VDD/时钟模块 用两张以时钟档位为索引的查表(
sys_vdd_table、sys_clock_table)把"选时钟"与"配电压"绑定,避免手工改频率导致内核电压不足; - 看门狗 作为系统保底,防止异常死循环拖垮设备(
watchdog_close等 API 位于include_lib/system/debug.h)。
这些服务通常由 init.h 的初始化链(late_initcall 等)在系统启动早期完成装配,应用层只调用公开 API,不需要直接操作寄存器。
Architecture
下图展示了系统服务与外设管理的分层结构:应用层通过设备头文件访问驱动服务,系统服务层依赖寄存器层与 PMU 通信通道,最终落到 CPU 外设寄存器。
flowchart TD
subgraph sg_App["应用层 (apps)"]
AppLogic["业务代码 / 唤醒恢复逻辑"]
VddClockTest["sysvddclock_test(时钟切换测试)"]
end
subgraph sg_SysSvc["系统服务层 (apps/common/system)"]
ResetReason["system_reset_reason_check / system_wakeup_reset"]
VddClock["system_vdd_clock 查表服务<br/>sys_vdd_table / sys_clock_table"]
end
subgraph sg_Drv["外设驱动接口 (include_lib/driver/device)"]
Gpio["gpio_direction_input / gpio_set_pull_up<br/>gpio_port_lock"]
Gpcnt["GPCNT 计数器驱动 (gpcnt.h)"]
Watchdog["watchdog_close (debug.h)"]
end
subgraph sg_Hw["硬件寄存器层"]
SysReg["JL_SYSTEM->RST_SRC"]
P33Reg["P33 PMU 寄存器<br/>P3_RST_SRC / R3_WKUP_SRC / P3_PMU_CON1"]
ClkReg["时钟/电压寄存器<br/>SYSVDD / VDC14 / VDDIOM"]
IoReg["IO 端口寄存器"]
end
AppLogic --> Gpio
AppLogic --> ResetReason
AppLogic --> Watchdog
VddClockTest --> VddClock
ResetReason --> SysReg
ResetReason --> P33Reg
VddClock --> ClkReg
Gpio --> IoReg
Gpcnt --> IoReg
各组件职责:
| 组件 | 职责 | 关键入口 |
|---|---|---|
| GPIO 驱动接口 | 引脚方向/上下拉/驱动能力/数字模拟切换,自旋锁保护 | gpio_direction_input()、gpio_port_lock() |
| 复位原因服务 | 汇总 RST_SRC 与 PMU 寄存器,输出统一复位原因 | system_reset_reason_get()、system_wakeup_reset() |
| VDD/时钟服务 | 以时钟档位为索引,查表联动设置电压与各总线时钟 | sys_vdd_table_get()、sys_clock_table_get() |
| 看门狗 | 系统保底复位 | watchdog_close() |
| GPCNT | 通用脉冲计数,采样时钟与 IO 通道可配置 | IOCTL_SET_GPIO、IOCTL_SET_GSS_CLK_SOURCE |
设计意图:把"硬件事实"(如复位原因、电压档位)集中收敛为稳定的 API,应用层无需感知寄存器细节;同时用查表方式保证电压与频率的匹配关系在编译期就固定下来,规避运行期调频导致的稳定性风险。
GPIO 外设管理
GPIO 是系统外设管理中最基础也最常用的一层。include_lib/driver/device/gpio.h 定义了完整的引脚操作 API,其内部再包含 asm/gpio.h 的低层实现。所有 API 都以 unsigned int gpio(形如 IO_PORTA_00 的宏)作为引脚标识,并通过 GPIO2PORT(gpio) = gpio / IO_GROUP_NUM 换算端口号。
引脚方向控制
/**
* @brief gpio_direction_input,将引脚直接设为输入模式
*
* @param gpio 参考宏IO_PORTx_xx,如IO_PORTA_00
*
* @return 0:成功 非0:失败
*/
/// \cond DO_NOT_DOCUMENT
int __gpio_direction_input(unsigned int gpio);
/// \endcond
int gpio_direction_input(unsigned int gpio);
/**
* @brief gpio_direction_output,设置引脚的方向为输出,并设置一下电平
*
* @param gpio 参考宏IO_PORTx_xx,如IO_PORTA_00
* @param value 1,输出1, 0,输出0
*
* @return 0:成功 非0:失败
*/
/// \cond DO_NOT_DOCUMENT
int __gpio_direction_output(unsigned int gpio, int value);
/// \endcond
int gpio_direction_output(unsigned int gpio, int value);
来源:gpio.h
注意头文件中存在带 __ 前缀的低层函数(__gpio_direction_input 等),它们被 \cond DO_NOT_DOCUMENT 隐藏,属于内部实现;应用层应使用不带下划线的公开 API。gpio_direction_output 在设置方向的同时直接给出初始电平,可避免"先切输出、电平未定"的毛刺窗口——这是嵌入式 GPIO 驱动常见的细节设计。
上下拉与驱动能力
/**
* @brief gpio_set_pull_up,设置引脚的上拉,上拉电阻10K,当引脚为输入模式时才有效
*/
int gpio_set_pull_up(unsigned int gpio, int value);
/**
* @brief gpio_set_pull_down,设置引脚的下拉,下拉电阻10K,当引脚为输入模式时才有效
*/
int gpio_set_pull_down(unsigned int gpio, int value);
/**
* @brief gpio_set_hd, 设置引脚的内阻,当引脚为输出模式时才有效
*
* @param value 1,关闭内阻增强输出 0,存在内阻,芯片默认的
*/
int gpio_set_hd(unsigned int gpio, int value);
/**
* @brief gpio_set_hd1, 设置引脚的输出电流,当引脚为输出模式时才有效
*
* @param value 1,电流变大增强输出; 0,默认电流
*/
int gpio_set_hd1(unsigned int gpio, int value);
/**
* @brief gpio_set_die,设置引脚为数字功能还是模拟功能,比如引脚作为ADC的模拟输入,则die要置0
*
* @param value 1,数字功能,即01信号; 0,跟电压先关的模拟功能
*/
int gpio_set_die(unsigned int gpio, int value);
来源:gpio.h
这几个 API 的设计要点:
- 上下拉(10K)仅在输入模式有效——SDK 不检查方向而依赖调用方遵守约定,因此在初始化 GPIO 时建议先设方向再配上/下拉;
gpio_set_hd/gpio_set_hd1仅在输出模式有效,分别控制"是否关闭内阻增强输出"与"是否增大输出电流",用于长走线、大负载场景;gpio_set_die控制数字/模拟功能:当引脚复用为 ADC 模拟输入时必须置 0,否则数字输入电路会干扰模拟采样。这是多路复用(mux)场景最容易踩的坑之一。
多核并发保护:端口自旋锁
/**
* @brief gpio_port_lock,自旋锁,即不能被另一个cpu打断,也不能被中断打断
*
* @param gpio 参考宏IO_PORTx_xx,如IO_PORTA_00
*/
void gpio_port_lock(unsigned int port);
/**
* @brief gpio_port_unlock,解除自旋锁
*/
void gpio_port_unlock(unsigned int port);
来源:gpio.h
AC79xx 为多核 SoC(CPU 与协处理器共享 IO 端口)。头文件注释明确该自旋锁既不能被另一个 CPU 打断,也不能被中断打断,即对端口寄存器的"读-改-写"序列提供互斥。对同一端口的批量配置(例如同时设置方向、上下拉)应包裹在 gpio_port_lock / gpio_port_unlock 之间,防止两核并发写坏端口配置寄存器。
其他外设:GPCNT 通用计数器
include_lib/driver/device/gpcnt.h 提供通用脉冲计数外设的配置接口,其平台数据结构与 ioctl 命令如下:
struct gpcnt_platform_data {
u8 gpcnt_gpio;
u8 gss_clk_source;//采样时钟源480M
...
};
typedef enum { //gpio输出通道
GPCNT_INPUT_CHANNEL1 = 1,
...
};
enum {
IOCTL_SET_CYCLE,
IOCTL_SET_GPIO,
IOCTL_SET_GSS_CLK_SOURCE,//采样时钟
};
来源:gpcnt.h
GPCNT 通过 gpcnt_gpio 指定计数输入引脚,通过 IOCTL_SET_GSS_CLK_SOURCE 选择采样时钟源(如 480M),IOCTL_SET_GPIO / IOCTL_SET_CYCLE 分别配置通道与计数周期。它通常用于测频、脉冲累计等场景,与 GPIO 数字输入配合实现"事件计数"能力。
系统复位原因与唤醒管理
apps/common/system/system_reset_reason.c 实现了"本次启动是从哪里来"的判定:是冷上电、软件复位、看门狗复位,还是从低功耗唤醒(闹钟/IO/LPTMR)。该模块是所有电源管理业务(如"唤醒后恢复现场"、"首次开机初始化")的前提。
判定流程
核心函数 system_reset_reason_check() 依次读取系统寄存器与 PMU(p33)寄存器,并按优先级判定复位原因:
static int system_reset_reason_check(void)
{
int reset_flag = JL_SYSTEM->RST_SRC;
u8 p3_rst = p33_rx_1byte(P3_RST_SRC);
u8 r3_wkup = p33_rx_1byte(R3_WKUP_SRC);
u8 port_wkup = p33_rx_1byte(P3_WKUP_PND);
u8 p3_pmu = p33_rx_1byte(P3_PMU_CON1);
u8 lptmr_con = p33_rx_1byte(P3_LP_TMR0_CON);
int rst_src = 0;
char rsn[16];
if (r3_wkup & BIT(0)) {
rst_src |= SYS_RST_ALM_WKUP;
strcpy(rsn, SYS_ALM_WAKUP);
} else if ((r3_wkup & BIT(3)) || (port_wkup && port_wkup != 0XFF) || (reset_flag & BIT(6))) {
rst_src |= SYS_RST_PORT_WKUP;
rst_src |= (port_wkup && port_wkup != 0XFF) ? (port_wkup << 16) : 0;
strcpy(rsn, SYS_PORT_WAKUP);
} else if ((reset_flag & BIT(5)) || (p3_rst & BIT(6))) {
rst_src |= SYS_RST_SOFT;
strcpy(rsn, SYS_SOFT);
} else if (lptmr_con & (BIT(5) | BIT(7))) {
rst_src |= SYS_RST_LPTMR_WKUP;
strcpy(rsn, SYS_LPTMR_WAKUP);
} else {
/* ...逐位检查 P3_RST_SRC 的上电/低压/看门狗/VCM/长按/12V 复位... */
}
p33_or_1byte(P3_PMU_CON1, BIT(6));//清空上电标记
printf("\n========= system reset reason: %s ========= \n", rsn);
sys_rst_rs = rst_src;
return rst_src;
}
判定优先级(从高到低)体现了"唤醒优先于复位"的设计:先看唤醒源(闹钟/IO),再看软复位,然后是 LPTMR,最后才是各类硬复位。原因是低功耗唤醒路径与复位路径共用 RST_SRC 寄存器,必须优先识别出"唤醒"语义,应用才能区分"首次开机"与"恢复运行"。
flowchart TD
Start([系统启动]) --> Read["读取 JL_SYSTEM->RST_SRC<br/>及 P3_RST_SRC / R3_WKUP_SRC / P3_WKUP_PND<br/>P3_PMU_CON1 / P3_LP_TMR0_CON"]
Read --> A{"R3_WKUP BIT0?"}
A -->|"是"| Alm["SYS_RST_ALM_WKUP 闹钟唤醒"]
A -->|"否"| B{"R3_WKUP BIT3 或<br/>P3_WKUP_PND 非0/非FF<br/>或 RST_SRC BIT6?"}
B -->|"是"| Port["SYS_RST_PORT_WKUP IO唤醒<br/>(唤醒组号左移16位暂存)"]
B -->|"否"| C{"RST_SRC BIT5 或<br/>P3_RST_SRC BIT6?"}
C -->|"是"| Soft["SYS_RST_SOFT 软件复位"]
C -->|"否"| D{"LPTMR_CON BIT5/BIT7?"}
D -->|"是"| Lpt["SYS_RST_LPTMR_WKUP 定时器唤醒"]
D -->|"否"| E["逐位检查 P3_RST_SRC:<br/>BIT0上电 / BIT1低压 / BIT2看门狗<br/>BIT3 VCM / BIT4长按 / BIT5 12V"]
Alm --> Clear["p33_or_1byte P3_PMU_CON1 BIT6<br/>清空上电标记"]
Port --> Clear
Soft --> Clear
Lpt --> Clear
E --> Clear
Clear --> Cache["缓存到 sys_rst_rs 静态变量<br/>printf 打印复位原因"]
Cache --> End([返回 rst_src])
对外查询 API
int system_reset_reason_get(void)
{
if (!sys_rst_rs) {
sys_rst_rs = system_reset_reason_check();
}
return sys_rst_rs & 0xFFFF;
}
int system_wakeup_reset(void)
{
if (!sys_rst_rs) {
sys_rst_rs = system_reset_reason_check();
}
if (sys_rst_rs & (SYS_RST_PORT_WKUP | SYS_RST_ALM_WKUP | SYS_RST_VDDIO_PWR_ON)) {
return true;
}
return false;
}
int system_wakeup_port_get(void)
{
if (!sys_rst_rs) {
sys_rst_rs = system_reset_reason_check();
}
if (sys_rst_rs & SYS_RST_PORT_WKUP) {
if (p33_rx_1byte(P3_PINR_CON) & BIT(0)) { //有开长按唤醒则IO唤醒从1-7
return (sys_rst_rs >> 17) & 0xFF;
} else {
return (sys_rst_rs >> 16) & 0xFF;
}
}
return -EINVAL;
}
void system_alarm_wakeup_clear(void)
{
sys_rst_rs &= ~(SYS_RST_ALM_WKUP);
}
要点:
- 惰性求值 + 缓存:
sys_rst_rs静态变量保证一次启动只做一次寄存器判定,后续查询直接返回缓存;system_reset_reason_get()只返回低 16 位原因位,高 16 位保留给 IO 唤醒组号; - 唤醒组号语义:
system_wakeup_port_get()返回的唤醒端口组号,且当P3_PINR_CON BIT0(开长按唤醒)时组号从 1 偏移到 7,需要按此规则解释; system_alarm_wakeup_clear()仅清除内存中的闹钟唤醒标志(不写寄存器),用于"闹钟唤醒已被消费"的业务语义。
系统电源与时钟管理
apps/common/system/system_vdd_clock.c 是"系统服务"中与功耗、性能直接相关的模块:它把 系统时钟档位、内核电压(SYSVDD)、VDC14 电压 与 IO 电压(VDDIOM) 绑定为一张查表,应用只需选定时钟档位,电压随之联动,避免人工分别设置导致电压不足或浪费功耗。整个模块由 CONFIG_SYS_VDD_CLOCK_ENABLE 编译开关门控。
时钟档位枚举
enum SYSTEM_CLOCK {
SYSTEM_CLK_3M, SYSTEM_CLK_4M, SYSTEM_CLK_6M, SYSTEM_CLK_8M, SYSTEM_CLK_9M,
SYSTEM_CLK_12M, SYSTEM_CLK_13M, SYSTEM_CLK_16M, SYSTEM_CLK_19M, SYSTEM_CLK_20M,
SYSTEM_CLK_24M, SYSTEM_CLK_26M, SYSTEM_CLK_27M, SYSTEM_CLK_32M, SYSTEM_CLK_38M,
SYSTEM_CLK_40M, SYSTEM_CLK_48M, SYSTEM_CLK_53M, SYSTEM_CLK_60M, SYSTEM_CLK_64M,
SYSTEM_CLK_80M, SYSTEM_CLK_96M, SYSTEM_CLK_106M, SYSTEM_CLK_120M, SYSTEM_CLK_160M,
SYSTEM_CLK_192M, SYSTEM_CLK_240M, SYSTEM_CLK_320M,
#ifdef CONFIG_OVERCLOCKING_ENABLE //超频
SYSTEM_CLK_396M,
#endif
};
从 3M 到 320M 共 28 个档位,若开启 CONFIG_OVERCLOCKING_ENABLE 则追加 396M 超频档。枚举顺序即查表索引,因此新增档位必须同步扩展两张表。
电压表与时钟表
static const u8 sys_vdd_table[][SYS_VDD_NUM] ALIGNE(4) = {//务必4对齐
//1.2V内核电压 VDC1.4V电压 IO电压
{SYSVDD_VOL_SEL_120V, VDC14_VOL_SEL_140V, VDDIOM_VOL_30V},//3M
...
{SYSVDD_VOL_SEL_126V, VDC14_VOL_SEL_145V, VDDIOM_VOL_32V},//160M
...
#ifdef CONFIG_OVERCLOCKING_ENABLE //超频
{SYSVDD_VOL_SEL_129V, VDC14_VOL_SEL_145V, VDDIOM_VOL_32V},//396M
#endif
};
#define SDRAM_FREQ_CAN_NOT_CHANGE 0 //SDRAM时钟频率默认不能动态改变(经测试,改变可能有风险,SYS:320MHZ+SDRAM:192MHZ与SYS:320MHZ+SDRAM:120MHZ的功耗相差2mA)
static const u32 sys_clock_table[][SYS_CLK_NUM] ALIGNE(4) = {//务必4对齐
//时钟倍数关系: hsb_clk = sys_clk/n, sfc_clk = hsb_clk/n, lsb_clk = hsb_clk/n, p33_clk = lsb_clk/n
// sys_clk hsb_clk:<=160M sfc_clk:<=80M lsb_clk:<=80M p33_clk:<=20M sdram_clk:默认不能动态改变
{ 3 * MHZ_UNIT, 3 * MHZ_UNIT, 3 * MHZ_UNIT, 2 * MHZ_UNIT, 2 * MHZ_UNIT, SDRAM_FREQ_CAN_NOT_CHANGE},//3M
...
{320 * MHZ_UNIT, 160 * MHZ_UNIT, 80 * MHZ_UNIT, 53 * MHZ_UNIT, 17 * MHZ_UNIT, SDRAM_FREQ_CAN_NOT_CHANGE},//320M
#ifdef CONFIG_OVERCLOCKING_ENABLE
{396 * MHZ_UNIT, 198 * MHZ_UNIT, 98 * MHZ_UNIT, 98 * MHZ_UNIT, 19 * MHZ_UNIT, 240 * MHZ_UNIT},//396M
#endif
};
#define SYSVDDCLK_MAX_NUM SYSTEM_CLK_320M
两张表的约束揭示了硬件设计意图:
- 电压联动:低频段(≤120M)内核 1.2V / VDC14 1.4V / IO 3.0V;160M~320M 提升到 1.26V / 1.45V / 3.2V;396M 超频档内核再加到 1.29V。频率越高必须加压,这是 CMOS 时序收敛的基本要求;
- 总线分频上限:hsb_clk ≤160M、sfc_clk/lsb_clk ≤80M、p33_clk ≤20M,表内注释直接给出约束,保证外设总线不超规格;
- SDRAM 频率固定:
SDRAM_FREQ_CAN_NOT_CHANGE = 0,注释说明动态改 SDRAM 频率有风险,且 320M 主频下 SDRAM 从 192M 降到 120M 仅省 2mA 功耗——收益低风险高,故默认锁死。
对外接口与切换时序
/* 系统使用函数 */
void *sys_vdd_table_get(void)
{
return (void *)sys_vdd_table;
}
void *sys_clock_table_get(void)
{
return (void *)sys_clock_table;
}
//=========应用层用户测试函数,用户需根据实际场景进行电源和时钟,自行进行调节===========//
//系统时钟切换时间参考如下:
//24M->320M: 6.20ms
//320M->24M: 4.05ms
//24M->32M: 7.16ms
//240M->320M: 3.21ms
//原始时钟较慢,则切换时间就较长
//====================================================================================//
sys_vdd_table_get() / sys_clock_table_get() 供系统内部(如 system_vdd_clock_set(index) 实现)取表;注释给出了实测切换耗时,供上层评估"调频期间业务可接受的中断时长":
| 切换路径 | 耗时 |
|---|---|
| 24M → 320M | 6.20 ms |
| 320M → 24M | 4.05 ms |
| 24M → 32M | 7.16 ms |
| 240M → 320M | 3.21 ms |
规律:原始时钟越低,锁相环重锁越慢。调频属于全局操作,应避开对时间敏感的音频/射频窗口。
调频验证示例(SDK 自带测试)
源码中保留了一个默认关闭(#if 0)的调频+SDRAM 数据完整性测试任务:
static void sysvddclock_test()
{
char index = 0;
u32 cnt = 0;
u8 step = 0;
os_time_dly(500);
while (1) {
os_time_dly(300);
system_vdd_clock_set(index); //随机切换时钟档位
index = JL_RAND->R64L % SYSVDDCLK_MAX_NUM;
...
//时钟修改+sdram测试:写随机数据 -> CRC32 -> flushinv_dcache -> 回读CRC对比
crcw = CRC32(tmp, len - 4);
__asm_csync();
flushinv_dcache(tmp, len);
crcw != crcr 时打印 "---> err in crcw = 0x%x , crcr = 0x%x"
}
}
static void sys_vdd_clock_init(void)
{
os_task_create(sysvddclock_test, NULL, 22, 1000, 0, "sysvddclock_test");
}
late_initcall(sys_vdd_clock_init);
该测试演示了正确用法:随机切档后立即对 SDRAM 做写-刷 cache-回读-CRC 校验,验证调频过程中 SDRAM 数据不损坏。它通过 late_initcall 注册为系统任务,开发者可在发布前开启 CONFIG_SYS_VDD_CLOCK_ENABLE 并放开 #if 0 做全档位压力测试。
看门狗
include_lib/system/debug.h 中声明了看门狗控制接口:
void watchdog_close(void);
来源:debug.h
看门狗是系统服务的保底机制:正常运行时喂狗,异常死循环时由硬件强制复位,配合 SYS_RST_WDT 复位原因(见上文 P3_RST_SRC BIT2)即可在重启后定位"上次是否看门狗复位"。watchdog_close 用于在特定场景(如量产测试、低功耗长睡眠)关闭看门狗。
Configuration Options
| 配置项 | 类型 | 默认 | 说明 |
|---|---|---|---|
CONFIG_SYS_VDD_CLOCK_ENABLE | 编译宏 | 未定义(需显式开启) | 使能系统 VDD/时钟查表服务,system_vdd_clock.c 整体编译进固件 |
CONFIG_OVERCLOCKING_ENABLE | 编译宏 | 未定义 | 使能 396M 超频档位(追加 SYSTEM_CLK_396M 及对应电压/时钟表项) |
SDRAM_FREQ_CAN_NOT_CHANGE | 宏常量 | 0 | SDRAM 时钟是否允许动态改变;默认锁死,改档位时保持原频率 |
SYSVDDCLK_MAX_NUM | 宏常量 | SYSTEM_CLK_320M | 随机调频测试的档位上限(含超频档时为 396M 档) |
P3_PINR_CON BIT(0) | 寄存器位 | 由 PMU 配置 | 是否开启长按唤醒;开启后 IO 唤醒组号从 1~7 解释 |
gpcnt_platform_data.gpcnt_gpio | 结构体字段 | 平台配置 | GPCNT 计数输入引脚 |
gpcnt_platform_data.gss_clk_source | 结构体字段 | 平台配置 | GPCNT 采样时钟源(如 480M) |
API Reference
GPIO(gpio.h)
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
gpio_port_lock(port) | port: IO 端口号 | void | 对端口加自旋锁(防另一 CPU 与中断打断) |
gpio_port_unlock(port) | port: IO 端口号 | void | 解除端口自旋锁 |
gpio_direction_input(gpio) | gpio: IO_PORTx_xx 宏 | 0 成功 / 非 0 失败 | 设为输入模式 |
gpio_direction_output(gpio, value) | value: 1/0 初始电平 | 0 成功 / 非 0 失败 | 设为输出模式并置电平 |
gpio_set_pull_up(gpio, value) | value: 1 上拉 / 0 不上拉 | 0 成功 / 非 0 失败 | 输入模式下 10K 上拉 |
gpio_set_pull_down(gpio, value) | value: 1 下拉 / 0 不下拉 | 0 成功 / 非 0 失败 | 输入模式下 10K 下拉 |
gpio_set_hd(gpio, value) | value: 1 关内阻增强 / 0 默认 | 0 成功 / 非 0 失败 | 输出模式内阻配置 |
gpio_set_hd1(gpio, value) | value: 1 加大电流 / 0 默认 | 0 成功 / 非 0 失败 | 输出模式电流增强 |
gpio_set_die(gpio, value) | value: 1 数字 / 0 模拟 | 0 成功 / 非 0 失败 | 数字/模拟功能切换(ADC 输入需置 0) |
系统复位/唤醒(system_reset_reason.c)
| 方法 | 返回 | 说明 |
|---|---|---|
system_reset_reason_get() | int(低 16 位原因位) | 获取本次启动的复位/唤醒原因,惰性初始化并缓存 |
system_wakeup_reset() | bool | 是否为唤醒复位(PORT_WKUP / ALM_WKUP / VDDIO_PWR_ON) |
system_wakeup_port_get() | int(IO 唤醒组号,无效返回 -EINVAL) | 获取 IO 唤醒组号;长按唤醒开启时按 1~7 解释 |
system_alarm_wakeup_clear() | void | 清除闹钟唤醒标志(仅内存,供业务消费语义使用) |
复位原因位(SYS_RST_*):SYS_RST_ALM_WKUP(闹钟唤醒)、SYS_RST_PORT_WKUP(IO 唤醒)、SYS_RST_SOFT(软复位)、SYS_RST_LPTMR_WKUP(低功耗定时器唤醒)、SYS_RST_VDDIO_PWR_ON(上电)、SYS_RST_VDDIO_LOW_PWR(低压复位)、SYS_RST_WDT(看门狗)、SYS_RST_VCM、SYS_RST_LONG_PRESS(长按)、SYS_RST_12V。
电源/时钟(system_vdd_clock.c)
| 方法 | 返回 | 说明 |
|---|---|---|
sys_vdd_table_get() | void *(电压表指针) | 供系统内部取电压档位表 |
sys_clock_table_get() | void *(时钟表指针) | 供系统内部取时钟档位表 |
system_vdd_clock_set(index) | 见实现 | 按 SYSTEM_CLK_* 枚举切档,联动设置电压与总线时钟 |
看门狗 / GPCNT
watchdog_close()(debug.h):关闭看门狗。- GPCNT ioctl(gpcnt.h):
IOCTL_SET_GPIO配置输入通道、IOCTL_SET_CYCLE配置周期、IOCTL_SET_GSS_CLK_SOURCE配置采样时钟源。
Failure Modes, Edge Cases & Concurrency
- 多核 GPIO 竞争:AC79xx 多核共享 IO 端口,若两核同时改写同一端口配置寄存器会互相覆盖。必须用
gpio_port_lock/gpio_port_unlock包裹批量配置;该锁不可被中断打断,临界区应尽量短。 - IO 唤醒组号边界:
P3_WKUP_PND == 0xFF被显式判为"非有效唤醒组",避免把全 1 的空闲值误当作组号;system_wakeup_port_get()在非唤醒复位时返回-EINVAL,调用方需判错。 - 长按唤醒偏移:开启长按唤醒(
P3_PINR_CON BIT0)后 IO 唤醒组号解释从 1 偏移到 7,同一寄存器在不同配置下含义不同,属硬件约定,勿硬编码。 - 时钟切换风险:SDRAM 频率默认锁死(改频有数据损坏风险,收益仅 2mA 级);调频应避开音频/射频时序敏感窗口,切换耗时 3~7ms 不等。
- 超频档位:
CONFIG_OVERCLOCKING_ENABLE追加 396M 档并提升内核电压至 1.29V,属于非标工作点,量产前需全温度范围验证。 - 复位原因覆盖:
P3_RST_SRC的多位可同时置位(如低压+看门狗),代码按位 OR 累加,调用方应检查"包含"而非"相等"。
Performance & Operational Considerations
- 惰性缓存:复位原因只判定一次(静态变量缓存),后续 API 调用零寄存器开销,适合启动早期频繁查询。
- 查表 O(1) 切换:时钟/电压切换为纯查表+寄存器写,复杂度恒定;表使用
ALIGNE(4)对齐,确保 DMA/硬件加速读取安全。 - 调频实测时序:24M→320M 约 6.2ms、320M→24M 约 4.05ms;原始时钟越低切换越慢,需按最坏情况预算业务中断窗口。
- 调试辅助:复位原因判定后通过
printf打印system reset reason: xxx,串口日志可快速定位复位来源(WDT/低压/长按等)。
Extension Points
- 新增时钟档位:在
enum SYSTEM_CLOCK追加枚举,并同步扩展sys_vdd_table与sys_clock_table(保持索引一致、ALIGNE(4)对齐),再更新SYSVDDCLK_MAX_NUM。 - 自定义唤醒策略:在业务层基于
system_reset_reason_get()/system_wakeup_port_get()分支处理"首次开机初始化"与"唤醒恢复";闹钟唤醒业务完成后调用system_alarm_wakeup_clear()。 - GPCNT 采样配置:通过
gpcnt_platform_data与 ioctl 命令扩展计数通道/时钟源,可复用 GPIO 输入引脚做事件计数。 - 发布前压力测试:放开
sysvddclock_test()的#if 0并开启CONFIG_SYS_VDD_CLOCK_ENABLE,用内置 CRC32 校验 SDRAM 数据完整性,覆盖全档位组合。
Related Links
- 系统电源管理(低功耗状态机)(同目录族:唤醒/睡眠状态机)
- 时钟与系统初始化(
init.h、late_initcall装配链) - 设备驱动框架(device/ioctl 模型)(GPCNT 等设备驱动的注册与 ioctl 机制)
- 源码:
apps/common/system/system_reset_reason.c、apps/common/system/system_vdd_clock.c、include_lib/driver/device/gpio.h、include_lib/driver/device/gpcnt.h、include_lib/system/debug.h