设备驱动框架(USB/RTC)
AC63 系列蓝牙 SoC 的 USB(主机/从机)与 RTC(含虚拟 RTC)设备驱动框架:涵盖 USB 协议栈的配置、DMA 缓冲管理、中断注册,以及 RTC 的时间读写、闹钟与低功耗唤醒机制。
Purpose and Scope
本文档系统性地介绍 AC63_BT_SDK 中的设备驱动框架,聚焦两大子系统:
- USB 驱动框架:位于
apps/common/device/usb/的 USB 从机(Device)与主机(Host)协议栈封装,包括usb_config.c/h配置层、端点 DMA 缓冲管理、中断注册接口,以及 U 盘(MSD)、HID、CDC、Audio 等标准设备类。 - RTC 驱动框架:包括各 CPU 的硬件 RTC 驱动头文件(
include_lib/driver/cpu/*/asm/rtc.h)与面向应用的虚拟 RTC 接口(include_lib/driver/device/rtc/virtual_rtc.h),以及基于 VM 存储的系统时间/闹钟读写与低功耗唤醒。
以下内容不在本文档范围内,属于相邻目录页的主题:蓝牙协议栈(GATT/SPP)、音频编解码驱动(cpu/*/audio_dec/)、电源管理(asm/power/)与具体应用示例(如键盘 HID 应用)。对于具体的外设应用开发,可参考对应应用页。
Overview
USB 驱动框架的定位
USB 是 AC63 系列芯片的重要外设接口,既可作**从机(Device)被 PC/手机枚举为 U 盘、HID 键盘、声卡、CDC 串口等,也可作主机(Host)**连接 U 盘、键鼠等外部设备。SDK 将 USB 硬件控制器(asm/usb.h)、传输协议(控制/批量传输)、标准设备类(SCSI、HID、CDC)与上层应用之间用 usb_config.c/h 这一配置层解耦:
- 应用只需调用
usb_config(usb_id)/usb_host_config(usb_id)即可完成初始化; - 端点缓冲通过统一的 lbuf(轻量内存池)管理,避免各设备类各自分配导致的内存碎片;
- 中断服务函数(ISR)以表驱动方式注册,支持按 CPU 与优先级挂接。
RTC 驱动框架的定位
RTC(实时时钟)负责系统时间的维护。由于部分低功耗场景下硬件 RTC 需要掉电保持,SDK 提供了虚拟 RTC(Virtual RTC):把系统时间与闹钟时间持久化到 VM(虚拟机参数存储)区域,并用低功耗定时器(LP Timer)模拟 RTC 的走时与闹钟唤醒。这使得 sys_time 结构(年/月/日/时/分/秒)成为上层(闹钟、定时提醒、日志时间戳)唯一的数据契约,而底层是硬件 RTC 还是模拟实现,对应用透明。
关键概念
| 概念 | 说明 |
|---|---|
usb_dev usb_id | USB 硬件实例编号,多 USB 控制器芯片按 0/1 区分 |
usb_ep_addr_t | 端点地址表,管理端点号与方向 |
| lbuf | 轻量环形缓冲内存池,USB DMA 缓冲的分配器 |
struct sys_time | 统一的日历时间结构(year/month/day/hour/min/sec) |
| VM 存储 | 掉电保存的参数区,虚拟 RTC 用它持久化时间与闹钟 |
| LP Timer | 低功耗定时器,虚拟 RTC 的"心跳",支持闹钟唤醒 |
Architecture
USB 驱动框架架构
flowchart TD
subgraph sg_App["应用层 (apps)"]
AppHID["usb_hid_devices.c (HID 设备)"]
AppStorage["usb_storage.c (U盘/存储)"]
end
subgraph sg_Cfg["配置层 (apps/common/device/usb)"]
CfgC["usb_config.c / usb_config.h"]
HostCfg["usb_host_config.c"]
end
subgraph sg_Dev["从机协议栈"]
DevStack["usb/device/usb_stack.h"]
DevC["usb_device.c"]
StdClass["usb_std_class_def.h / usb_common_def.h"]
end
subgraph sg_Host["主机协议栈"]
HostMain["usb_host.c"]
CtrlXfer["usb_ctrl_transfer.c (控制传输)"]
BulkXfer["usb_bulk_transfer.c (批量传输)"]
end
subgraph sg_HW["硬件层"]
AsmUsb["asm/usb.h (USB 控制器)"]
DmaBuf["usb_dma_buf (DMA 缓冲池)"]
IsrTable["usb_interrupt_tx/rx (ISR 表)"]
end
AppHID --> CfgC
AppStorage --> CfgC
CfgC --> DevC
CfgC --> HostMain
DevC --> DevStack
DevC --> StdClass
HostMain --> CtrlXfer
HostMain --> BulkXfer
DevC --> AsmUsb
CtrlXfer --> AsmUsb
BulkXfer --> AsmUsb
DevC --> DmaBuf
HostMain --> DmaBuf
DevC --> IsrTable
HostMain --> IsrTable
架构分层自下而上说明:
- 硬件层:
asm/usb.h提供寄存器级控制器访问;DMA 缓冲池usb_dma_buf与 ISR 表(usb_interrupt_tx[USB_MAX_HW_NUM][MAX_EP_TX]、usb_interrupt_rx[USB_MAX_HW_NUM][MAX_EP_RX])为协议栈提供数据通路与中断分发。 - 协议栈层:从机侧
usb_device.c配合usb_stack.h处理枚举与各类请求;主机侧usb_host.c负责枚举与设备管理,usb_ctrl_transfer.c/usb_bulk_transfer.c分别实现控制传输与批量传输。 - 配置层:
usb_config.c/h是上下两层的"胶水",负责分配端点缓冲、注册中断、按编译宏(TCFG_USB_SLAVE_MSD_ENABLE等)裁剪设备类能力。 - 应用层:如键盘 HID 设备示例
usb_hid_devices.c,只需调用配置层 API 即可获得枚举好的 USB 通道。
RTC 驱动框架架构
flowchart TD
subgraph sg_App["应用层"]
RtcAlarm["rtc_alarm.c (闹钟/定时模块)"]
VirDemo["virtual_rtc_demo.c (演示代码)"]
end
subgraph sg_Vir["虚拟 RTC 层 (include_lib/driver/device/rtc)"]
VirH["virtual_rtc.h (对外接口)"]
VirSim["vir_rtc_simulate (时间模拟)"]
VmIds["VM 分区 (rtc_vm_id / alm_vm_id / sec_vm_id)"]
end
subgraph sg_HwRTC["硬件与定时器"]
AsmRtc["cpu/*/asm/rtc.h (硬件 RTC)"]
LpTimer["LP Timer (低功耗定时器)"]
P33["asm/power/p33.h (电源域)"]
end
RtcAlarm --> VirH
VirDemo --> VirH
VirH --> VirSim
VirSim --> VmIds
VirSim --> AsmRtc
VirSim --> LpTimer
VirSim --> P33
RTC 架构的关键设计意图:让"时间"成为可插拔的服务。virtual_rtc.h 定义统一的读写接口(vir_read_sys_time / vir_write_sys_time、vir_read_alarm / vir_write_alarm),底层既可由真实硬件 RTC 驱动,也可由"LP Timer + VM 存储"模拟(支持 AC695/AC635 等平台)。闹钟模块 rtc_alarm.c 与上层应用只依赖接口,不关心实现细节。
USB 驱动框架的实现细节
DMA 缓冲池的统一管理
USB 传输要求端点缓冲满足 DMA 对齐要求(8 字节对齐)。usb_config.c 将所有设备类的缓冲需求静态汇总为一个全局数组,再交给 lbuf 内存池动态分配,这是该框架最核心的设计决策:
#define MAX_EP_TX 5
#define MAX_EP_RX 5
static usb_interrupt usb_interrupt_tx[USB_MAX_HW_NUM][MAX_EP_TX];// SEC(.usb_g_bss);
static usb_interrupt usb_interrupt_rx[USB_MAX_HW_NUM][MAX_EP_RX];// SEC(.usb_h_bss);
static u8 ep0_dma_buffer[EP0_SETUP_LEN] __attribute__((aligned(4))) SEC(.usb_ep0) ;
#if TCFG_USB_SLAVE_MSD_ENABLE
#define MSD_DMA_SIZE (64*2)
#else
#define MSD_DMA_SIZE 0
#endif
#if TCFG_USB_SLAVE_HID_ENABLE
#define HID_DMA_SIZE 64
#if CONFIG_APP_DONGLE
#define HID_DMA_SIZE 64*CONFIG_BT_GATT_CLIENT_NUM
#endif
#else
#define HID_DMA_SIZE 0
#endif
#if TCFG_USB_CUSTOM_HID_ENABLE
#define CUSTOM_HID_DMA_SIZE 64 * 2
#else
#define CUSTOM_HID_DMA_SIZE 0
#endif
#if TCFG_USB_SLAVE_AUDIO_ENABLE
#define AUDIO_DMA_SIZE 256+192
#else
#define AUDIO_DMA_SIZE 0
#endif
Source: usb_config.c
设计意图分析:
- 编译期裁剪:每个设备类(MSD/HID/CustomHID/Audio/CDC)都对应一个
TCFG_USB_xxx_ENABLE编译宏。未使能的类其 DMA 大小被置 0,从而零运行时开销地控制内存占用——这在 RAM 紧张的蓝牙 SoC 上至关重要。 - 分段放置:
SEC(.usb_ep0)、SEC(.usb_msd_dma)、SEC(.usb_g_bss)、SEC(.usb_h_bss)等 section 属性把关键缓冲放到指定的内存段,便于链接器统一管理并满足 USB 控制器对物理地址的要求。 - 对齐保证:
usb_dma_buf以aligned(8)声明,lbuf 以USB_DMA_BUF_ALIGN(8)初始化,确保从池中切出的每一块缓冲都满足 DMA 对齐。
缓冲池的初始化与分配:
#define USB_DMA_BUF_ALIGN (8)
#ifndef USB_DMA_BUF_MAX_SIZE
#define USB_DMA_BUF_MAX_SIZE (HID_DMA_SIZE +USB_DMA_BUF_ALIGN+ AUDIO_DMA_SIZE +USB_DMA_BUF_ALIGN+ MSD_DMA_SIZE*2 + USB_DMA_BUF_ALIGN+CDC_DMA_SIZE + USB_DMA_BUF_ALIGN + CUSTOM_HID_DMA_SIZE + USB_DMA_BUF_ALIGN+ 100)
#endif//USB_DMA_BUF_MAX_SIZE
static u8 usb_dma_buf[USB_DMA_BUF_MAX_SIZE] SEC(.usb_msd_dma) __attribute__((aligned(8)));
struct lbuff_head *usb_dma_lbuf = NULL;
void usb_memory_init()
{
usb_dma_lbuf = lbuf_init(usb_dma_buf, sizeof(usb_dma_buf), USB_DMA_BUF_ALIGN, 0);
log_info("%s() total dma size %x @%x", __func__, sizeof(usb_dma_buf), usb_dma_buf);
}
__attribute__((always_inline_when_const_args))
void *usb_alloc_ep_dmabuffer(const usb_dev usb_id, u32 ep, u32 dma_size)
{
u8 *ep_buffer = NULL;
...
}
Sources:
usb_memory_init() 在系统启动早期被调用,把静态数组注册为 lbuf 池;此后各协议栈组件通过 usb_alloc_ep_dmabuffer() 按需申请端点缓冲。USB_DMA_BUF_MAX_SIZE 的求和公式里每个类之间插入 USB_DMA_BUF_ALIGN,是对 lbuf 按块切分时的对齐冗余的预计算,保证总容量足够。
中断注册机制
USB 协议栈与主控的中断系统通过表驱动方式衔接,usb_config.h 暴露了三类中断注册接口:
void usb_h_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id); // 主机模式中断
void usb_g_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id); // 从机模式中断
void usb_sof_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id); // SOF 帧起始中断
Source: usb_config.h
参数语义:usb_id 选择硬件实例;priority 设定中断优先级;cpu_id 在多核(如 BR 系列双核)场景下指定中断挂载到哪个核,避免中断与主任务在不同核间频繁竞争。ISR 处理函数存放在 usb_interrupt_tx/rx 表中,按 [USB_MAX_HW_NUM][MAX_EP_TX/RX] 二维索引(每个控制器最多 5 个 TX / 5 个 RX 端点),协议栈把端点事件分发表项后,硬件中断到来时即可 O(1) 定位到对应的处理函数。
USB 配置层对外 API 总览
usb_config.h 是 USB 子系统对外的"门面",核心接口如下:
| 接口 | 作用 | 关键参数 |
|---|---|---|
u32 usb_config(const usb_dev usb_id) | 从机模式初始化(枚举就绪) | 返回 0 表示成功 |
usb_host_config(usb_dev usb_id) | 主机模式初始化 | 无返回 |
usb_host_free(usb_dev usb_id) | 释放主机模式资源 | 无返回 |
usb_h_get_ep_buffer(usb_dev usb_id, u32 ep) | 获取主机端点缓冲地址 | 返回 void* |
usb_alloc_ep_dmabuffer(usb_dev usb_id, u32 ep, u32 dma_size) | 从 DMA 池分配端点缓冲 | 返回 void* |
usb_h_isr_reg / usb_g_isr_reg / usb_sof_isr_reg | 注册主机/从机/SOF 中断 | priority、cpu_id |
Source: usb_config.h
该 API 设计刻意保持"薄":初始化与释放、缓冲与中断四个维度即可覆盖从 U 盘、键盘 HID 到 CDC 串口的所有常用场景,上层无需感知 SCSI 命令、描述符等协议细节。
主机协议栈与存储设备
主机侧文件结构(apps/common/device/usb/host/):
usb_host.c:主机控制器管理、设备枚举、地址分配;usb_ctrl_transfer.c/h:控制传输(Setup 阶段 + 数据阶段 + 状态阶段);usb_bulk_transfer.c/h:批量传输(U 盘读写的主要通路);usb_storage.c/h:SCSI/BOT 协议封装,把批量传输组织为 U 盘读写命令;usb_hid_keys.h:HID 键值码表,供主机解析键盘/鼠标上报。
从机侧 usb_device.c 与 usb_std_class_def.h(标准设备类定义)、usb_common_def.h(公共描述符/常量)配合,完成设备枚举与类请求分发。usb_host_config.c 则提供主机模式的板级配置入口。
RTC 驱动框架的实现细节
虚拟 RTC 对外接口
include_lib/driver/device/rtc/virtual_rtc.h 定义了完整的虚拟 RTC API:
void vir_rtc_simulate_dump();
void get_lp_timer1_status(void);
int vir_rtc_simulate_init(const struct dev_node *node, void *arg);
void vir_write_alarm(struct sys_time *alarm_time);
void vir_read_alarm(struct sys_time *alarm_time);
void vir_write_sys_time(struct sys_time *curr_time);
void vir_read_sys_time(struct sys_time *curr_time);
void vir_alarm_enable(u8 set_alarm);
u8 vir_get_alarm_enable(void);
void vir_set_vm_id(u8 rtc_vm_id, u8 alm_vm_id, u8 sec_vm_id);
Source: virtual_rtc.h
接口设计要点:
- 统一时间契约:所有时间参数均为
struct sys_time(来自system/sys_time.h),应用层无需区分"硬件时间"与"模拟时间"。 - 初始化即设备注册:
vir_rtc_simulate_init(const struct dev_node *node, void *arg)采用设备节点(dev_node)签名,与 SDK 的设备模型(driver 框架)兼容——虚拟 RTC 被注册为一个标准设备,可被dev_open等机制调用。 - VM 分区解耦:
vir_set_vm_id(rtc_vm_id, alm_vm_id, sec_vm_id)允许调用方把时间、闹钟、秒计数分别落到不同的 VM ID,便于按需控制掉电保存的粒度与频率(秒计数分区通常用于高频写入而不过度磨损 VM)。 - 闹钟使能状态可查询:
vir_get_alarm_enable()配合vir_alarm_enable(u8)支持运行时开关闹钟,供上层在低功耗策略中判断是否需要维持唤醒源。
演示代码:完整的虚拟 RTC 使用流程
apps/spp_and_le/modules/virtual_rtc_demo.c 展示了从初始化到闹钟读写的完整调用序列(该文件整体被 #if 0 关闭,作为可参考的示例代码):
void virtual_rtc_test(void)
{
sys_timer_add(NULL, vir_rtc_test, 10);
vir_rtc_simulate_init(NULL, NULL);
vir_read_sys_time(&read_sys_time_test);
printf("vir_rtc_read_sys_time>>>>>>>>>>>>>>>>>>>>>>\n");
printf("vir_rtc_sys_time: %d-%d-%d %d:%d:%d\n",
read_sys_time_test.year,
read_sys_time_test.month,
read_sys_time_test.day,
read_sys_time_test.hour,
read_sys_time_test.min,
read_sys_time_test.sec);
vir_alarm_enable(1);
write_alarm_test.year = 2020;
write_alarm_test.month = 2;
write_alarm_test.day = 28;
write_alarm_test.hour = 23;
write_alarm_test.min = 59;
write_alarm_test.sec = 50;
vir_write_alarm(&write_alarm_test);
vir_read_alarm(&read_alarm_test);
printf("vir_rtc_alarm_time: %d-%d-%d %d:%d:%d\n",
read_alarm_test.year,
read_alarm_test.month,
read_alarm_test.day,
read_alarm_test.hour,
read_alarm_test.min,
read_alarm_test.sec);
}
Source: virtual_rtc_demo.c
同文件中的 alm_wakeup_isr() 是闹钟触发时的中断回调示例,set_rtc_default_time() 给出出厂默认时间的填充方式,vir_rtc_test() 则通过 sys_timer_add(NULL, vir_rtc_test, 10) 每 10ms 周期轮询并打印系统时间,同时检测按键进入软关机(power_set_soft_poweroff()),演示了"虚拟 RTC 走时 + 低功耗入口"的典型组合。
硬件 RTC 与闹钟模块
- 硬件驱动:
include_lib/driver/cpu/{bd19,bd29,br23,br25,br30,br34}/asm/rtc.h为各芯片平台的 RTC 硬件寄存器与底层读写函数,虚拟 RTC 在支持硬件 RTC 的平台可直接复用其走时与闹钟比较能力。 - 闹钟模块:
apps/{hid,spp_and_le,mesh}/modules/rtc_alarm.c(及对应include/rtc_alarm.h)是面向应用的闹钟服务,封装了"设置闹钟 → 等待触发 → 唤醒/提示"的完整状态机,不同 APP(HID、SPP+LE、Mesh)各自维护一份副本,说明闹钟服务与应用场景(如蓝牙闹钟提醒)强相关。
核心流程
USB DMA 缓冲分配流程
flowchart TD
Start["系统启动 (board_init)"] --> MemInit["usb_memory_init()"]
MemInit --> Lbuf["lbuf_init(usb_dma_buf, USB_DMA_BUF_MAX_SIZE, 8, 0)"]
Lbuf --> Mode{"USB 模式?"}
Mode -->|"从机 usb_config(id)"| DevInit["设备枚举, 加载设备类 (MSD/HID/CDC...)"]
Mode -->|"主机 usb_host_config(id)"| HostInit["主机枚举外部设备"]
DevInit --> Alloc["usb_alloc_ep_dmabuffer(id, ep, size)"]
HostInit --> Alloc
Alloc --> Ok{"lbuf 分配成功?"}
Ok -->|"是"| Ret["返回 8 字节对齐 DMA 缓冲"]
Ok -->|"否"| Null["返回 NULL, 上层降级/报错"]
虚拟 RTC 闹钟时序
sequenceDiagram
participant App as 应用 (virtual_rtc_test)
participant Vir as virtual_rtc.h 接口
participant Sim as vir_rtc_simulate
participant VM as VM 存储分区
participant Timer as LP Timer
App->>Vir: vir_rtc_simulate_init(NULL, NULL)
Vir->>Sim: 初始化模拟器(注册设备节点)
App->>Vir: vir_write_sys_time(&t)
Vir->>Sim: 写入系统时间
Sim->>VM: 持久化到 rtc_vm_id
App->>Vir: vir_write_alarm(&t)
Vir->>Sim: 写入闹钟时间
Sim->>VM: 持久化到 alm_vm_id
App->>Vir: vir_alarm_enable(1)
Vir->>Sim: 使能闹钟
Sim->>Timer: 按闹钟时间设置 LP Timer 唤醒点
Timer-->>Sim: 时间到, 低功耗唤醒中断
Sim-->>App: 触发 alm_wakeup_isr 回调
时序说明:初始化必须在任何读写之前完成(vir_rtc_simulate_init);时间与闹钟写入后立即落入 VM,保证掉电不丢;闹钟使能后由 LP Timer 承担"未来某一时刻唤醒芯片"的职责——这正是虚拟 RTC 能在无外部 32K 晶振场景下实现 RTC 功能的关键。周期性的 vir_rtc_test(每 10ms)则利用 vir_read_sys_time 驱动软件走时累计。
配置选项
USB 配置宏(在 app_config.h / 板级配置中定义)
| 配置宏 | 类型 | 默认值 | 说明 |
|---|---|---|---|
TCFG_USB_SLAVE_MSD_ENABLE | bool | 0 | 使能从机 U 盘(Mass Storage),DMA 缓冲 64×2 字节 |
TCFG_USB_SLAVE_HID_ENABLE | bool | 0 | 使能从机 HID 设备,DMA 缓冲 64 字节 |
TCFG_USB_CUSTOM_HID_ENABLE | bool | 0 | 使能自定义 HID,DMA 缓冲 64×2 字节 |
TCFG_USB_SLAVE_AUDIO_ENABLE | bool | 0 | 使能 USB 声卡类,DMA 缓冲 256+192 字节 |
TCFG_USB_SLAVE_CDC_ENABLE | bool | 0 | 使能 CDC 虚拟串口,DMA 缓冲 64×2(或 64×3)字节 |
CONFIG_APP_DONGLE | bool | 0 | Dongle 固件:HID DMA 放大为 64×GATT 客户端数 |
USB_MALLOC_ENABLE | bool | 0 | 置 1 时 USB 配置变量走堆分配,否则静态段 |
TCFG_USE_VIRTUAL_RTC | bool | 0 | 使用虚拟 RTC 模拟(示例中被 #if 0 关闭,仅参考) |
虚拟 RTC 运行期配置
| 配置项 | 类型 | 说明 |
|---|---|---|
rtc_vm_id | u8 | 系统时间持久化的 VM 分区 ID |
alm_vm_id | u8 | 闹钟时间持久化的 VM 分区 ID |
sec_vm_id | u8 | 秒计数持久化的 VM 分区 ID |
| 闹钟使能 | u8 | 通过 vir_alarm_enable(1/0) 开关 |
API Reference
USB 配置层
u32 usb_config(const usb_dev usb_id)
USB 从机模式初始化。启动枚举、注册设备类。
- 参数:
usb_id— USB 硬件实例编号 - 返回:0 表示成功,非 0 表示失败
- 注意:需在
usb_memory_init()之后调用,且端点缓冲由usb_alloc_ep_dmabuffer()预先分配
void usb_host_config(const usb_dev usb_id)
USB 主机模式初始化,开始枚举总线上的外部设备。
void usb_host_free(const usb_dev usb_id)
释放主机模式占用的资源,退出主机角色。
void *usb_alloc_ep_dmabuffer(const usb_dev usb_id, u32 ep, u32 dma_size)
从全局 DMA 缓冲池(lbuf)为指定端点分配缓冲。
- 参数:
usb_id硬件实例;ep端点号;dma_size期望长度 - 返回:8 字节对齐的缓冲地址;池耗尽时返回 NULL
void *usb_h_get_ep_buffer(const usb_dev usb_id, u32 ep)
查询主机模式指定端点当前绑定的缓冲地址。
void usb_h_isr_reg / usb_g_isr_reg / usb_sof_isr_reg(const usb_dev usb_id, u8 priority, u8 cpu_id)
分别注册主机、从机、SOF 中断处理。
- 参数:
priority中断优先级;cpu_id目标核编号(多核场景)
虚拟 RTC 层
int vir_rtc_simulate_init(const struct dev_node *node, void *arg)
初始化虚拟 RTC 模拟器。以设备节点形式注册,兼容 SDK 设备模型。
- 参数:
node设备节点(可传 NULL);arg附加参数 - 返回:0 成功,非 0 失败
- 前置条件:必须先于任何读写接口调用
void vir_write_sys_time(struct sys_time *curr_time)
写入系统时间并持久化到 rtc_vm_id。
void vir_read_sys_time(struct sys_time *curr_time)
读取当前系统时间;周期调用可驱动软件走时累计。
void vir_write_alarm(struct sys_time *alarm_time)
写入闹钟时间并持久化到 alm_vm_id。
void vir_read_alarm(struct sys_time *alarm_time)
读取已设置的闹钟时间。
void vir_alarm_enable(u8 set_alarm)
使能/关闭闹钟。使能后由 LP Timer 设置唤醒点。
u8 vir_get_alarm_enable(void)
查询闹钟使能状态(1 为使能)。
void vir_set_vm_id(u8 rtc_vm_id, u8 alm_vm_id, u8 sec_vm_id)
设置时间/闹钟/秒计数三个 VM 分区 ID,必须在读写前完成,用于控制持久化布局与 VM 磨损。
失败模式、边界情况与并发
USB 侧
- DMA 池耗尽:
usb_alloc_ep_dmabuffer()在 lbuf 无足够连续空间时返回 NULL。设计上通过USB_DMA_BUF_MAX_SIZE的预聚合公式(各设备类大小 + 对齐 + 100 字节余量)尽量规避;若多个设备类同时使能导致超卖,上层必须检查返回值并降级(如关闭对应功能),否则 DMA 写越界会造成内存破坏。 - 多控制器竞争:ISR 表按
[USB_MAX_HW_NUM][MAX_EP]索引,不同usb_id之间天然隔离;同一控制器的多个端点共享 lbuf 池,因此端点缓冲分配与释放必须串行化(初始化阶段集中分配),运行期动态分配需加临界区保护。 - 枚举失败/设备热插拔(主机模式):
usb_host.c负责枚举状态机,外部设备未就绪时应由上层周期重试;usb_host_free()必须与usb_host_config()配对,否则 ISR 表残留悬空回调。 - Dongle 多连接场景:
CONFIG_APP_DONGLE下 HID DMA 缓冲按64 * CONFIG_BT_GATT_CLIENT_NUM放大,说明同一 HID 端点需要为每个 GATT 客户端维护独立的 64 字节上报缓冲,缓冲不足会直接表现为 HID 上报失败。
RTC 侧
- VM 磨损:时间类数据高频写入会加速 VM 闪存擦写损耗,因此
vir_set_vm_id()单独划分sec_vm_id分区,把高频变化的秒计数与低频变化的日期时间分离;上层应避免在vir_rtc_test类周期回调中无谓地vir_write_sys_time()。 - 初始化顺序:
vir_rtc_simulate_init()之前调用任何读写接口属于未定义行为(设备节点未注册、VM 分区未绑定),示例代码严格遵循"init → read/write → alarm_enable"顺序,上层必须同样保证。 - 低功耗唤醒竞态:闹钟使能依赖 LP Timer 的唤醒点设置;若在设置唤醒点与进入休眠之间时间被改写(
vir_write_sys_time),唤醒时刻可能漂移。设计上要求时间写操作与power_set_soft_poweroff()之类的休眠入口互斥或串行执行。 - 演示代码默认关闭:
virtual_rtc_demo.c整体包在#if 0中,且注释指明虚拟 RTC 仅支持 AC695/AC635 等特定平台;在其他芯片(如 BR 系列)移植时必须核对include_lib/driver/cpu/<chip>/asm/rtc.h的硬件能力,避免把模拟实现硬套到有真实 RTC 的平台。
性能与运维注意事项
- 零拷贝 DMA:USB 数据直接经由对齐的 DMA 池缓冲收发,避免中间拷贝;
ep0_dma_buffer独立放置于.usb_ep0段,保证 Setup 包处理不被大数据传输阻塞。 - 内存静态化:默认
USB_MALLOC_ENABLE=0时,配置变量(usb_config_var_t)位于静态段.usb_config_var,避免运行期堆分配的不确定性与碎片化;仅在 RAM 极度紧张或需要运行时裁剪时才开启堆分配。 - 中断亲和性:
cpu_id参数把 USB ISR 绑定到指定核,双核平台建议将 USB 中断与高频音频中断分核放置,降低中断嵌套延迟。 - 日志开关:
usb_config.c通过LOG_TAG "[USB]"与LOG_ERROR/DEBUG/INFO/CLI_ENABLE控制日志级别,LOG_DUMP_ENABLE默认关闭——排查枚举失败时可临时打开 Dump 观察 Setup/数据包,量产固件应保持关闭以节省串口带宽与 Flash 空间。
扩展点
- 新增 USB 设备类:在
usb_config.c中仿照 MSD/HID/CDC 增加TCFG_USB_SLAVE_XXX_ENABLE宏与对应XXX_DMA_SIZE,并在usb_device.c/usb_std_class_def.h中注册描述符与类请求处理,最后在USB_DMA_BUF_MAX_SIZE聚合公式中加入新类缓冲即可。 - 多核/多控制器:
USB_MAX_HW_NUM与MAX_EP_TX/RX是上层可调的编译常量,扩展 USB 控制器数量只需提升数组维度并保持 ISR 表与usb_id一一对应。 - 自定义 RTC 后端:
virtual_rtc.h的读写接口即扩展契约——若平台有真实硬件 RTC,可在vir_rtc_simulate内部切换到硬件驱动(cpu/*/asm/rtc.h),对外 API 与struct sys_time契约完全不变,应用层零改动。 - 闹钟业务:
rtc_alarm.c在各 APP 目录独立维护,新应用(如 Mesh 定时广播)可复制该模块并在alm_wakeup_isr回调中挂接自己的业务逻辑。
测试
- USB:
apps/hid/examples/standard_keyboard/usb_hid_devices.c与apps/spp_and_le/examples/dongle/usb_hid_devices.c(含usb_second_hid_devices.c)是可编译的从机 HID 示例,覆盖单/双 HID 设备与 Dongle 多 GATT 客户端两种典型形态,可直接作为功能验证与回归基线。 - RTC:
virtual_rtc_demo.c即为自测程序——初始化后打印系统时间、写入并回读闹钟时间,配合sys_timer_add周期打印可验证走时精度;get_lp_timer1_status()提供 LP Timer 状态查询,用于确认休眠期间定时器未被误关。 - 未发现针对本框架的独立单元测试工程;协议栈正确性主要依赖上述示例固件在目标板上的枚举/读写联调验证。
Related Links
- USB 配置头文件 usb_config.h — 配置层全部 API 声明
- USB 配置实现 usb_config.c — DMA 池与中断表实现
- USB 主机协议栈目录 — 主机枚举与批量/控制传输
- 虚拟 RTC 接口 virtual_rtc.h — RTC 抽象层契约
- 虚拟 RTC 演示 virtual_rtc_demo.c — 完整调用示例
- RTC 闹钟模块 rtc_alarm.c — 应用级闹钟服务
- 相邻主题:蓝牙协议栈(GATT/SPP)、电源管理(低功耗休眠入口)、系统时间服务(
system/sys_time.h)请参阅对应目录页。