API GUIDE / NEMC / HAL
NEMC HAL 接口
nemc hal api
本文面向 BSP、驱动和应用开发者,说明如何通过 G32S10 NEMC HAL 配置外部静态存储器控制器,并提供公开 API 参考。NEMC 寄存器位域细节、外部器件时序计算、总线电气特性和板级 pinmux 实现不在本文范围内。
NEMC 的数据访问使用固定内存映射窗口,不提供函数式读写接口。HAL 配置完成后,CPU 直接访问 0x1B000000 起始的静态存储窗口完成读写;HAL 只负责控制器配置、状态和复位控制。
API 头文件:
code/include/drivers/g32s10/nemc/hal_nemc.h
公开实现位于 code/drivers/g32s10/nemc/hal_nemc.c。ll_nemc.h 和 nemc_regs.h 提供寄存器级接口、NEMC 实例和静态窗口常量,属于 HAL 的内部依赖,不属于本文公开 API。
NEMC HAL 不负责 NEMC 时钟、GPIO pinmux、外部器件上下电/复位、WAIT# 外部逻辑或板级地址映射。调用方必须完成这些准备后才能配置 NEMC 并访问静态存储窗口。
1. 模块概览
普通应用只包含 <hal_nemc.h> 即可,不要自行定义 NEMC 寄存器结构或重复实现窗口地址。
- 返回
error_code_t的接口以IM_OK表示成功,其他值表示失败。 - 当前只支持
NEMC实例;传入其他寄存器块指针会返回IM_ERR_INVALID_PARAM。 - 所有需要
hal_nemc_handle_t的接口都要求句柄中的instance == NEMC。句柄由调用方分配,HAL 不负责释放。 hal_nemc_init()只配置 SMC0R/SMC1R,不配置 SACR、时钟、GPIO、外部器件电源或复位。- 默认初始化保留硬件复位后的 SACR 窗口。只有板级映射不同于默认值时,才调用
hal_nemc_window_config()。 - 静态存储数据通过内存映射窗口直接访问。默认窗口起始地址为
HAL_NEMC_STATIC_MEMORY_BASE(0x1B000000),最大访问范围为HAL_NEMC_STATIC_MEMORY_MAX_SIZE(64 KiB)。 - timing 字段是原始寄存器编码,不是统一的“周期数”数值。
TAW_OE、TBP_DS、TRAS、TWAS使用非线性编码,必须使用对应的HAL_NEMC_*_CYCLE_*枚举值。 - HAL 不提供内部互斥或并发保护。多个任务同时修改配置、复位或访问同一 NEMC 窗口时,调用方必须自行串行化。
hal_nemc_soft_reset()只启动软件复位,不等待完成。复位完成后需要重新调用hal_nemc_init()才能恢复READY状态和控制器配置。hal_nemc_deinit()只重置 HAL 软件句柄,不修改时钟、GPIO 或 NEMC 寄存器。- 调试接口
hal_nemc_regs_dump()只有在全局和模块 debug helper 同时启用时才执行寄存器读取;否则保留符号并返回IM_ERR_UNSUPPORTED。 - 本文按
CONFIG_SDK_HAL_PARAM_CHECK=y描述参数检查行为。关闭 SDK/HAL 参数检查后,空指针和非法字段不保证被拦截,调用方必须自行保证参数有效。
2. 典型流程
2.1 Kconfig 配置
NEMC 驱动配置位于 code/drivers/g32s10/nemc/Kconfig,菜单路径为 Drivers -> G32S10 Drivers -> NEMC driver。
| 配置项 | 默认值 | 说明 |
|---|---|---|
CONFIG_DRIVER_NEMC |
n |
编译 NEMC HAL,即 hal_nemc.c |
CONFIG_NEMC_ENABLE_DEBUG_HELPERS |
n |
编译 NEMC 调试辅助代码;需要同时启用全局 debug helper |
仅使用 HAL 时启用:
CONFIG_DRIVER_NEMC=y
需要使用 hal_nemc_regs_dump() 时,再同时启用:
CONFIG_SDK_ENABLE_DEBUG_HELPERS=y
CONFIG_NEMC_ENABLE_DEBUG_HELPERS=y
启用 CONFIG_MIDDLEWARE_SHELL_CMD_NEMC=y 还会注册 nemc_dump_regs Shell 命令。该命令依赖 CONFIG_DRIVER_NEMC,且只有 debug helper 同时启用时才能真正输出寄存器。
参考示例位于 code/examples/hal/nemc_test_example_app/,其 defconfig 已启用 CONFIG_DRIVER_NEMC、CPM、GPIO 和 NEMC 调试功能。示例应用额外依赖 CONFIG_DRIVER_CPM 和 CONFIG_DRIVER_GPIO,但这两个选项不是 HAL 本身编译所必需的。
2.2 板级准备
在调用 hal_nemc_init() 前完成以下准备:
- 使能 NEMC 模块时钟,例如
hal_cpm_clk_enable(HAL_CPM_ID_GATE_NEMC)。 - 按板卡原理图配置 NEMC 引脚复用。16-bit 总线通常需要 CS#、AVD、RD#、WE#、WAIT# 和 AD0..AD15;8-bit 总线使用 AD0..AD7 和相应控制信号。
- 完成外部器件上电、复位释放和总线空闲电平设置。WAIT# 只在实际使用时接入。
- 确认静态存储窗口与系统中其他外设不重叠;默认窗口为
0x1B000000起始的 64 KiB。 - 根据外部器件数据手册计算
TCH、TAW_OE、TBP_DS、TRAS、TWAS、STRV、TRDRV、TWRRV、TAVDH、TAVDP和TAVDS。
时钟使能示例:
#include "hal_cpm.h"
error_code_t board_nemc_clock_enable(void)
{
return hal_cpm_clk_enable(HAL_CPM_ID_GATE_NEMC);
}
2.3 初始化与直接访问
下面的代码只展示 HAL 调用顺序。timing 数值来自现有 FPGA 测试示例,实际产品必须按外部器件手册替换:
#include <stdint.h>
#include "hal_nemc.h"
#include "im_error.h"
#define APP_NEMC_WORD_COUNT (8U)
static hal_nemc_handle_t s_nemc;
static error_code_t app_nemc_init(void)
{
hal_nemc_config_t config;
error_code_t ret;
ret = hal_nemc_config_default(&config);
if (ret != IM_OK) {
return ret;
}
config.bus_width = (uint8_t)HAL_NEMC_BUS_WIDTH_16;
config.wait_pin_enable = 0U;
config.timing.tch = HAL_NEMC_TCH_CYCLE_5;
config.timing.taw_oe = HAL_NEMC_TAW_OE_CYCLE_10;
config.timing.tbp_ds = HAL_NEMC_TBP_DS_CYCLE_10;
config.timing.tras = HAL_NEMC_TRAS_CYCLE_5;
config.timing.twas = HAL_NEMC_TWAS_CYCLE_5;
config.timing.strv = 15U;
config.timing.trdrv = HAL_NEMC_TRDRV_CYCLE_1;
config.timing.twrrv = HAL_NEMC_TWRRV_CYCLE_0;
config.timing.tavdh = HAL_NEMC_TAVDH_CYCLE_10;
config.timing.tavdp = HAL_NEMC_TAVDP_CYCLE_10;
config.timing.tavds = HAL_NEMC_TAVDS_CYCLE_10;
config.timing.double_enable = 0U;
return hal_nemc_init(&s_nemc, NEMC, &config);
}
static error_code_t app_nemc_write_read(void)
{
volatile uint16_t *window =
(volatile uint16_t *)(uintptr_t)HAL_NEMC_STATIC_MEMORY_BASE;
const uint32_t access_size = APP_NEMC_WORD_COUNT * sizeof(uint16_t);
uint32_t index;
if (access_size > HAL_NEMC_STATIC_MEMORY_MAX_SIZE) {
return IM_ERR_INVALID_PARAM;
}
for (index = 0U; index < APP_NEMC_WORD_COUNT; index++) {
window[index] = (uint16_t)(0x1000U + index);
}
for (index = 0U; index < APP_NEMC_WORD_COUNT; index++) {
if (window[index] != (uint16_t)(0x1000U + index)) {
return IM_ERR_IO;
}
}
return IM_OK;
}
说明:
- 示例假定 NEMC 时钟、pinmux 和外部器件已经就绪,并假定外部器件允许连续写后读回。
- 访问宽度必须与
bus_width匹配;16-bit 总线使用uint16_t,8-bit 总线使用uint8_t。 - 所有访问都必须位于实际映射窗口内。默认窗口大小为 64 KiB,不能跨窗口访问。
hal_nemc_init()成功只表示寄存器配置已写入,不表示外部器件已经正确响应。首次集成应使用示波器、逻辑分析仪或回读校验确认总线波形和数据。
2.4 软件复位流程
hal_nemc_soft_reset() 是异步接口。复位完成后 HAL 仍保持 HAL_NEMC_STATE_RESET,必须重新初始化控制器:
#include "im_time.h"
#define APP_NEMC_RESET_TIMEOUT_MS (100U)
static error_code_t app_nemc_soft_reset_and_reinit(
const hal_nemc_config_t *config)
{
uint32_t start_ms;
uint32_t status;
error_code_t ret;
ret = hal_nemc_soft_reset(&s_nemc);
if (ret != IM_OK) {
return ret;
}
start_ms = sys_time_get_ms();
for (;;) {
ret = hal_nemc_soft_reset_status_get(&s_nemc, &status);
if (ret != IM_OK) {
return ret;
}
if ((status & HAL_NEMC_SOFT_RESET_DONE_FLAG) != 0U) {
break;
}
if ((uint32_t)(sys_time_get_ms() - start_ms) >=
APP_NEMC_RESET_TIMEOUT_MS) {
return IM_ERR_TIMEOUT;
}
sys_time_delay_ms(1U);
}
(void)hal_nemc_soft_reset_status_clear(&s_nemc);
return hal_nemc_init(&s_nemc, NEMC, config);
}
注意:
- 复位期间不要访问静态存储窗口,也不要同时修改 SMC0R/SMC1R/SACR。
- 软件复位只复位 NEMC 控制器,不复位外部存储器、NEMC 时钟或 GPIO pinmux。
- 复位会恢复 NEMC 寄存器默认值,因此之前通过
hal_nemc_init()或hal_nemc_window_config()写入的配置需要重新应用。 hal_nemc_soft_reset_status_get()在复位未完成时返回IM_OK且status == 0;只有状态读取失败才返回错误。- 超时由调用方定义;HAL 本身不提供超时参数,也不会返回
IM_ERR_TIMEOUT。
2.5 可选地址窗口配置
默认映射由硬件复位值决定,hal_nemc_init() 不会写 SACR。只有板级必须改变地址窗口时,才在初始化成功后调用:
hal_nemc_window_config_t window;
error_code_t ret;
window.base = 0x1BU;
window.mask = 0xFFU;
ret = hal_nemc_window_config(&s_nemc, &window);
if (ret != IM_OK) {
return ret;
}
base 和 mask 是 SACR 的原始 8-bit 字段,不是 32-bit 物理地址或字节长度。上面的 0x1B/0xFF 对应默认 CS1# 窗口;修改时必须依据 NEMC 规格和板级地址规划,不能只按软件计算的地址范围填写。
3. 配置和关键类型
3.1 HAL 状态
hal_nemc_state_t 用于描述句柄所处的软件生命周期阶段:
typedef enum {
HAL_NEMC_STATE_RESET = 0,
HAL_NEMC_STATE_READY,
} hal_nemc_state_t;
| 状态 | 说明 |
|---|---|
HAL_NEMC_STATE_RESET |
句柄尚未初始化,或已 deinit/soft_reset,不能执行依赖 READY 的配置操作 |
HAL_NEMC_STATE_READY |
hal_nemc_init() 成功,允许修改 WAIT#、DOUBLE 和 SACR 窗口 |
HAL 没有独立的 BUSY 状态。软件复位进行中由调用方通过 hal_nemc_soft_reset_status_get() 判断,期间不要访问 NEMC。
state 是 hal_nemc_handle_t 的字段;本节只定义状态取值,句柄对象及其生命周期约束见 3.2。
3.2 HAL 句柄
HAL 句柄是调用方分配并持有的 HAL 上下文,用于保存 NEMC 实例、当前状态和用户数据。本节描述句柄对象本身及其生命周期。
typedef struct hal_nemc_handle hal_nemc_handle_t;
struct hal_nemc_handle {
nemc_regs_t *instance;
hal_nemc_state_t state;
void *user_data;
};
| 字段 | 说明 |
|---|---|
instance |
NEMC 寄存器块指针,当前必须为 NEMC |
state |
HAL 软件状态,类型为 hal_nemc_state_t;取值见 3.1 |
user_data |
调用方上下文指针,HAL 只保存不解释;init 会清零,deinit 会置为 NULL |
句柄由调用方分配并保证生命周期覆盖所有 HAL 调用。不要复制正在使用的句柄,也不要在多个任务中共享同一个句柄进行并发配置。
3.3 总线位宽
typedef enum {
HAL_NEMC_BUS_WIDTH_8 = NEMC_SMC0_BW_8BIT,
HAL_NEMC_BUS_WIDTH_16 = NEMC_SMC0_BW_16BIT,
} hal_nemc_bus_width_t;
hal_nemc_config_t.bus_width 只接受 0 或 1,对应 8-bit 和 16-bit 外部数据总线。位宽必须与 pinmux 和外部器件连接一致。
3.4 时序配置
typedef struct {
uint8_t tch;
uint8_t taw_oe;
uint8_t tbp_ds;
uint8_t tras;
uint8_t twas;
uint8_t strv;
uint8_t trdrv;
uint8_t twrrv;
uint8_t tavdh;
uint8_t tavdp;
uint8_t tavds;
uint8_t double_enable;
} hal_nemc_timing_config_t;
| 字段 | 寄存器位域 | 编码说明 |
|---|---|---|
tch |
SMC0R.TCH | 0~15 个周期,使用 HAL_NEMC_TCH_CYCLE_* |
taw_oe |
SMC0R.TAW_OE | 0~10、12、15、20、25、31 个周期,使用 HAL_NEMC_TAW_OE_CYCLE_* |
tbp_ds |
SMC0R.TBP_DS | 0~10、12、15、20、25、31 个周期,使用 HAL_NEMC_TBP_DS_CYCLE_* |
tras |
SMC0R.TRAS | 0~10、12、15、20、25、31 个周期,使用 HAL_NEMC_TRAS_CYCLE_* |
twas |
SMC0R.TWAS | 0~10、12、15、20、25、31 个周期,使用 HAL_NEMC_TWAS_CYCLE_* |
strv |
SMC1R.STRV | 0~63 个周期,原始字段范围 0x00..0x3F |
trdrv |
SMC1R.TRDRV | 0~15 个周期,使用 HAL_NEMC_TRDRV_CYCLE_* |
twrrv |
SMC1R.TWRRV | 0~15 个周期,使用 HAL_NEMC_TWRRV_CYCLE_* |
tavdh |
SMC1R.TAVDH | 0~15 个周期,使用 HAL_NEMC_TAVDH_CYCLE_* |
tavdp |
SMC1R.TAVDP | 0~15 个周期,使用 HAL_NEMC_TAVDP_CYCLE_* |
tavds |
SMC1R.TAVDS | 0~15 个周期,使用 HAL_NEMC_TAVDS_CYCLE_* |
double_enable |
SMC1R.DOUBLE | 0 关闭,1 开启 DOUBLE timing;HAL 只接受 0 或 1 |
TAW_OE、TBP_DS、TRAS 和 TWAS 的原始字段不是线性周期值。例如原始值 0xB 表示 12 个周期,而不是 11 个周期。不要手写裸数值,优先使用对应枚举。
3.5 NEMC 配置和默认值
typedef struct {
uint8_t bus_width;
uint8_t wait_pin_enable;
hal_nemc_timing_config_t timing;
} hal_nemc_config_t;
| 字段 | 说明 |
|---|---|
bus_width |
HAL_NEMC_BUS_WIDTH_8 或 HAL_NEMC_BUS_WIDTH_16 |
wait_pin_enable |
0 关闭 WAIT# 动态访问控制,1 开启;HAL 只接受 0 或 1 |
timing |
SMC0R/SMC1R timing 原始字段集合 |
hal_nemc_config_default() 填充以下默认值:
| 字段 | 默认值 |
|---|---|
bus_width |
HAL_NEMC_BUS_WIDTH_8 |
wait_pin_enable |
1 |
timing.tch |
HAL_NEMC_TCH_CYCLE_7 |
timing.taw_oe |
HAL_NEMC_TAW_OE_CYCLE_31 |
timing.tbp_ds |
HAL_NEMC_TBP_DS_CYCLE_31 |
timing.tras |
HAL_NEMC_TRAS_CYCLE_7 |
timing.twas |
HAL_NEMC_TWAS_CYCLE_7 |
timing.strv |
HAL_NEMC_STRV_CYCLE_DEFAULT(15) |
timing.trdrv |
HAL_NEMC_TRDRV_CYCLE_0 |
timing.twrrv |
HAL_NEMC_TWRRV_CYCLE_0 |
timing.tavdh |
HAL_NEMC_TAVDH_CYCLE_1 |
timing.tavdp |
HAL_NEMC_TAVDP_CYCLE_2 |
timing.tavds |
HAL_NEMC_TAVDS_CYCLE_1 |
timing.double_enable |
0 |
这些默认值对应 NEMC 复位字段值,但 hal_nemc_config_default() 只填充软件结构,不访问硬件。默认配置不代表任何外部存储器的最优或可用时序,实际使用时仍需按器件手册修改。
3.6 地址窗口配置
typedef struct {
uint8_t base;
uint8_t mask;
} hal_nemc_window_config_t;
| 字段 | 说明 |
|---|---|
base |
SACR.BASE 原始字段值,范围 0x00..0xFF |
mask |
SACR.MASK 原始字段值,范围 0x00..0xFF |
默认硬件窗口为 0x1B00_0000 起始的 64 KiB,对应 base = 0x1B、mask = 0xFF。头文件说明的可配置物理地址范围是 0x1B00_0000 到 0x1C00_0000;base/mask 与最终地址窗口的精确对应关系以 NEMC 规格为准。HAL 只检查配置指针非空,不会校验字段组合、地址重叠或映射合法性。
3.7 常用常量和公开类型索引
| 常量 | 值 | 说明 |
|---|---|---|
HAL_NEMC_TIMING_4BIT_MAX |
0x0F |
4-bit timing 字段最大值 |
HAL_NEMC_STRV_CYCLE_MAX / HAL_NEMC_STRV_MAX |
0x3F |
SMC1R.STRV 最大值 |
HAL_NEMC_STRV_CYCLE_DEFAULT |
0x0F |
SMC1R.STRV 复位默认值 |
HAL_NEMC_SACR_FIELD_MAX |
0xFF |
SACR.BASE/MASK 字段最大值 |
HAL_NEMC_STATIC_MEMORY_BASE |
0x1B000000 |
固定静态存储窗口起始地址 |
HAL_NEMC_STATIC_MEMORY_MAX_SIZE |
0x00010000 |
窗口最大访问大小,64 KiB |
HAL_NEMC_SOFT_RESET_DONE_FLAG |
0x00000004 |
软件复位完成标志(SWRST bit 2) |
| 类型 | 用途 |
|---|---|
hal_nemc_state_t |
HAL 软件状态 |
hal_nemc_bus_width_t |
8-bit/16-bit 总线位宽 |
hal_nemc_timing_config_t |
SMC0R/SMC1R timing 配置 |
hal_nemc_config_t |
NEMC 初始化配置 |
hal_nemc_window_config_t |
SACR 地址窗口配置 |
hal_nemc_handle_t |
HAL 句柄 |
HAL_NEMC_*_CYCLE_* |
各 timing 字段的编码值枚举 |
3.8 常见错误码
| 错误码 | 常见原因 |
|---|---|
IM_OK |
操作成功 |
IM_ERR_INVALID_PARAM |
空指针、实例不是 NEMC、bus_width 非 8/16、布尔字段不是 0/1、timing 超出字段范围、strv 大于 63 |
IM_ERR_NOT_READY |
在 init 前、deinit 后或 soft_reset 后调用 WAIT#、DOUBLE 或 SACR 配置接口 |
IM_ERR_UNSUPPORTED |
hal_nemc_regs_dump() 编译时未启用全局或模块 debug helper |
IM_ERR_TIMEOUT |
不由 HAL 返回;调用方轮询软件复位超时时自行产生 |
IM_ERR_IO |
不由 HAL 返回;调用方对静态窗口回读校验失败时自行产生 |
3.8.1 常见问题定位
| 现象 | 优先检查 |
|---|---|
hal_nemc_init() 返回 IM_ERR_INVALID_PARAM |
是否先调用 hal_nemc_config_default()、配置结构是否未初始化、实例是否为 NEMC、布尔字段和 timing 是否越界 |
WAIT#/DOUBLE/SACR 配置返回 IM_ERR_NOT_READY |
hal_nemc_init() 是否成功,是否在 deinit 或 soft_reset 后忘记重新初始化 |
| 所有外部窗口访问数据异常 | 时钟、pinmux、片选、AVD/RD#/WE#、位宽和 timing 是否正确;外部器件是否上电并释放复位 |
| 读回数据全为固定值或总线挂起 | bus_width 与硬件连接是否一致,WAIT# 是否应开启,窗口地址是否与 SACR 匹配 |
| 软件复位状态一直为 0 | NEMC 时钟是否已使能、复位是否被外部条件阻塞、是否在复位期间清除了状态位 |
| 软件复位完成后访问仍异常 | 是否重新调用 hal_nemc_init(),是否需要重新配置 SACR,外部器件是否也被复位 |
hal_nemc_regs_dump() 返回 IM_ERR_UNSUPPORTED |
是否同时启用 CONFIG_SDK_ENABLE_DEBUG_HELPERS 和 CONFIG_NEMC_ENABLE_DEBUG_HELPERS |
| 访问超出 64 KiB 后系统异常 | 检查窗口偏移和访问长度,确保不越过 HAL_NEMC_STATIC_MEMORY_MAX_SIZE |
4. API 参考
4.1 API 总览
hal_nemc.h
| API | 用途 |
|---|---|
hal_nemc_config_default() |
填充默认配置 |
hal_nemc_init() |
初始化句柄并配置控制器 |
hal_nemc_deinit() |
反初始化 HAL 软件句柄 |
hal_nemc_window_config() |
配置 SACR 地址窗口(需要 READY) |
hal_nemc_wait_pin_enable() |
开启 WAIT#(需要 READY) |
hal_nemc_wait_pin_disable() |
关闭 WAIT#(需要 READY) |
hal_nemc_double_timing_enable() |
开启 DOUBLE timing(需要 READY) |
hal_nemc_double_timing_disable() |
关闭 DOUBLE timing(需要 READY) |
hal_nemc_soft_reset() |
启动软件复位 |
hal_nemc_soft_reset_status_get() |
读取复位完成状态 |
hal_nemc_soft_reset_status_clear() |
清除复位完成状态 |
hal_nemc_state_get() |
读取 HAL 状态 |
hal_nemc_regs_dump() |
打印寄存器快照 |
4.2 通用约定
- 返回
error_code_t的接口以IM_OK表示成功,其他值表示失败。 - 输出参数必须指向有效内存;接口允许
NULL时会单独说明。 hal_nemc_init()、hal_nemc_deinit()和配置/复位流程建议只在任务上下文调用。HAL 不提供并发保护,调用方必须串行化所有 NEMC 配置和访问。- 以下接口要求句柄处于
HAL_NEMC_STATE_READY:hal_nemc_window_config()、hal_nemc_wait_pin_enable()、hal_nemc_wait_pin_disable()、hal_nemc_double_timing_enable()、hal_nemc_double_timing_disable()。 hal_nemc_soft_reset_status_get()、hal_nemc_soft_reset_status_clear()和hal_nemc_state_get()不要求READY,可用于复位流程和状态诊断。- NEMC 没有 HAL 读/写函数。配置完成后直接访问静态存储窗口;HAL 无法捕获事务级错误。
- 参数检查依赖
CONFIG_SDK_HAL_PARAM_CHECK。关闭检查后,调用方仍需保证指针、实例和字段合法。
4.3 生命周期和初始化 API
hal_nemc_config_default()
error_code_t hal_nemc_config_default(hal_nemc_config_t *config);
把配置结构填充为 SDK 默认值。函数只写软件结构,不访问 NEMC 寄存器,也不初始化句柄。
| 参数 | 说明 |
|---|---|
config |
待填充的配置结构,不能为 NULL |
返回:
IM_OK:填充成功。IM_ERR_INVALID_PARAM:config == NULL。
调用方应在 hal_nemc_config_default() 之后覆盖与板级和外部器件相关的字段,再调用 hal_nemc_init()。
hal_nemc_init()
error_code_t hal_nemc_init(hal_nemc_handle_t *hnemc,
nemc_regs_t *instance,
const hal_nemc_config_t *config);
初始化 HAL 句柄并编程一组 NEMC 静态存储配置。函数会先清零句柄,再设置 instance、写入 SMC0R/SMC1R 相关字段,并把状态置为 READY。成功时 SMC0R.SMT 固定为地址/数据复用模式 NEMC_SMC0_SMT_ADM。
| 参数 | 说明 |
|---|---|
hnemc |
待初始化的 HAL 句柄,不能为 NULL |
instance |
NEMC 寄存器块指针,当前必须为 NEMC |
config |
NEMC 配置,不能为 NULL |
返回:
IM_OK:配置写入成功,句柄进入HAL_NEMC_STATE_READY。IM_ERR_INVALID_PARAM:句柄/配置为空、实例不是NEMC,或配置字段非法。
配置字段非法包括:bus_width 不是 8/16、wait_pin_enable 或 double_enable 不是 0/1、任一 4-bit timing 字段大于 0x0F,或 strv 大于 0x3F。
说明:
- 本接口不配置 NEMC 时钟、GPIO pinmux、外部器件电源/复位或 SACR 地址窗口。
- 本接口不执行软件复位,也不等待外部器件就绪。
- 句柄中的
user_data会被清零。需要保存调用方上下文时,应在hal_nemc_init()成功后自行赋值。 - 可以在已经初始化的句柄上再次调用本接口重新配置控制器,不需要先
deinit();旧配置会被覆盖。 - 如果板级地址映射不同于默认值,应在
hal_nemc_init()成功后调用hal_nemc_window_config()。
hal_nemc_deinit()
error_code_t hal_nemc_deinit(hal_nemc_handle_t *hnemc);
把 HAL 软件句柄恢复为未初始化状态,即清除 user_data 并把 state 置为 HAL_NEMC_STATE_RESET。函数不修改 NEMC 硬件寄存器。
| 参数 | 说明 |
|---|---|
hnemc |
HAL 句柄,不能为 NULL,且 instance 必须为 NEMC |
返回:
IM_OK:软件句柄已反初始化。IM_ERR_INVALID_PARAM:句柄为空或instance不是NEMC。
说明:
- 不关闭 NEMC 时钟,不恢复 GPIO pinmux,也不恢复 SMC0R/SMC1R/SACR。
- 需要对控制器执行硬件复位时,使用
hal_nemc_soft_reset()。 deinit后,依赖READY的接口返回IM_ERR_NOT_READY;状态查询和复位状态读取仍可使用原句柄。- 如需再次使用 NEMC,重新调用
hal_nemc_init()。
4.4 地址窗口 API
hal_nemc_window_config()
error_code_t hal_nemc_window_config(
hal_nemc_handle_t *hnemc,
const hal_nemc_window_config_t *config);
写入 SACR.BASE 和 SACR.MASK,修改静态存储地址窗口。只有板级映射必须改变时才调用。
| 参数 | 说明 |
|---|---|
hnemc |
已初始化的 HAL 句柄 |
config |
窗口配置,不能为 NULL |
返回:
IM_OK:SACR 字段已写入。IM_ERR_INVALID_PARAM:句柄或配置为空,或instance不是NEMC。IM_ERR_NOT_READY:句柄尚未初始化,或已经deinit/soft_reset。
说明:
hal_nemc_init()不写 SACR;不调用本接口时保留硬件复位窗口。base和mask是原始字段值,HAL 只检查指针,不校验映射范围、重叠或合法性。- 默认窗口为
0x1B00_0000起始的 64 KiB;头文件给出的可配置物理地址范围是0x1B00_0000到0x1C00_0000。 - 修改窗口后,静态存储访问地址必须与新的硬件映射一致,不能继续假定
HAL_NEMC_STATIC_MEMORY_BASE仍可用。 - 软件复位会恢复 SACR 默认值,复位完成后需要重新配置窗口。
4.5 WAIT# 控制 API
hal_nemc_wait_pin_enable()
error_code_t hal_nemc_wait_pin_enable(hal_nemc_handle_t *hnemc);
置位 SMC1R.WP_EN,开启 WAIT# 动态访问控制。WAIT# 引脚必须已由板级 pinmux 配置并正确连接。
| 参数 | 说明 |
|---|---|
hnemc |
已初始化的 HAL 句柄 |
返回:
IM_OK:WAIT# 已开启。IM_ERR_INVALID_PARAM:句柄为空或instance不是NEMC。IM_ERR_NOT_READY:句柄未初始化或当前不是READY。
hal_nemc_init() 会根据 config.wait_pin_enable 设置初始状态;本接口用于初始化后动态开启 WAIT#。
hal_nemc_wait_pin_disable()
error_code_t hal_nemc_wait_pin_disable(hal_nemc_handle_t *hnemc);
清除 SMC1R.WP_EN,关闭 WAIT# 动态访问控制。
| 参数 | 说明 |
|---|---|
hnemc |
已初始化的 HAL 句柄 |
返回:
IM_OK:WAIT# 已关闭。IM_ERR_INVALID_PARAM:句柄为空或instance不是NEMC。IM_ERR_NOT_READY:句柄未初始化或当前不是READY。
4.6 DOUBLE timing 控制 API
hal_nemc_double_timing_enable()
error_code_t hal_nemc_double_timing_enable(hal_nemc_handle_t *hnemc);
置位 SMC1R.DOUBLE,开启加倍 timing 等待。该功能适用于需要更长等待周期的外部器件或低速板级环境。
| 参数 | 说明 |
|---|---|
hnemc |
已初始化的 HAL 句柄 |
返回:
IM_OK:DOUBLE timing 已开启。IM_ERR_INVALID_PARAM:句柄为空或instance不是NEMC。IM_ERR_NOT_READY:句柄未初始化或当前不是READY。
hal_nemc_init() 会根据 config.timing.double_enable 设置初始状态;本接口用于初始化后动态开启。
hal_nemc_double_timing_disable()
error_code_t hal_nemc_double_timing_disable(hal_nemc_handle_t *hnemc);
清除 SMC1R.DOUBLE,关闭加倍 timing 等待。
| 参数 | 说明 |
|---|---|
hnemc |
已初始化的 HAL 句柄 |
返回:
IM_OK:DOUBLE timing 已关闭。IM_ERR_INVALID_PARAM:句柄为空或instance不是NEMC。IM_ERR_NOT_READY:句柄未初始化或当前不是READY。
4.7 软件复位 API
hal_nemc_soft_reset()
error_code_t hal_nemc_soft_reset(hal_nemc_handle_t *hnemc);
清除软件复位完成状态,触发 NEMC 软件复位,并把 HAL 状态置为 HAL_NEMC_STATE_RESET。函数在启动复位后立即返回,不等待复位完成。
| 参数 | 说明 |
|---|---|
hnemc |
HAL 句柄,不能为 NULL,且 instance 必须为 NEMC |
返回:
IM_OK:软件复位已启动。IM_ERR_INVALID_PARAM:句柄为空或instance不是NEMC。
说明:
- 本接口不要求
READY,但句柄的instance必须有效。 - 启动复位后,调用
hal_nemc_soft_reset_status_get()轮询HAL_NEMC_SOFT_RESET_DONE_FLAG。 - 复位完成前不要读写静态存储窗口,也不要修改 NEMC 配置寄存器。
- 复位会恢复 NEMC 寄存器默认值;完成后应重新调用
hal_nemc_init(),并按需重新配置 SACR。 - 本接口不复位外部存储器、时钟或 GPIO,也不会自动恢复之前的配置。
hal_nemc_soft_reset_status_get()
error_code_t hal_nemc_soft_reset_status_get(
const hal_nemc_handle_t *hnemc,
uint32_t *status_out);
读取 SWRST 的复位完成位,并以掩码形式输出。
| 参数 | 说明 |
|---|---|
hnemc |
HAL 句柄,不能为 NULL,且 instance 必须为 NEMC |
status_out |
输出状态,不能为 NULL;完成时为 HAL_NEMC_SOFT_RESET_DONE_FLAG,否则为 0 |
返回:
IM_OK:状态读取成功。IM_ERR_INVALID_PARAM:句柄或输出指针为空,或instance不是NEMC。
说明:
- 不要求句柄处于
READY,因此可在hal_nemc_soft_reset()后立即轮询。 - 复位未完成不是错误,此时返回
IM_OK且*status_out == 0。 - 建议调用方设置超时并避免忙等。
hal_nemc_soft_reset_status_clear()
error_code_t hal_nemc_soft_reset_status_clear(hal_nemc_handle_t *hnemc);
清除 SWRST 的复位完成状态。hal_nemc_soft_reset() 在启动复位前已经执行过一次清除;通常在确认完成并处理结果后调用。
| 参数 | 说明 |
|---|---|
hnemc |
HAL 句柄,不能为 NULL,且 instance 必须为 NEMC |
返回:
IM_OK:清除操作已发出。IM_ERR_INVALID_PARAM:句柄为空或instance不是NEMC。
说明:
- 不要求
READY。 - 复位进行中不要清除完成状态,否则可能丢失完成指示。
- 本接口只清除状态位,不启动或停止复位。
4.8 状态和调试 API
hal_nemc_state_get()
error_code_t hal_nemc_state_get(const hal_nemc_handle_t *hnemc,
hal_nemc_state_t *state_out);
读取 HAL 软件状态快照,不访问 NEMC 寄存器。
| 参数 | 说明 |
|---|---|
hnemc |
HAL 句柄,不能为 NULL,且 instance 必须为 NEMC |
state_out |
输出当前 HAL 状态,不能为 NULL |
返回:
IM_OK:状态读取成功。IM_ERR_INVALID_PARAM:句柄或输出指针为空,或instance不是NEMC。
说明:
- 可在
init、deinit或soft_reset后查询。 - HAL 只有
RESET和READY两个软件状态;复位正在进行也表现为RESET,需结合复位状态位判断。
hal_nemc_regs_dump()
error_code_t hal_nemc_regs_dump(const hal_nemc_handle_t *hnemc);
以 INFO 日志输出 SMC0R、SACR、SMC1R 和 SWRST 的当前值以及 NEMC 基地址。
| 参数 | 说明 |
|---|---|
hnemc |
HAL 句柄,不能为 NULL,且 instance 必须为 NEMC |
返回:
IM_OK:寄存器已读取并输出日志(需要编译调试辅助代码)。IM_ERR_INVALID_PARAM:调试功能已编译,但句柄为空或instance不是NEMC。IM_ERR_UNSUPPORTED:未同时启用CONFIG_SDK_ENABLE_DEBUG_HELPERS与CONFIG_NEMC_ENABLE_DEBUG_HELPERS;此分支不检查句柄。
说明:
- 不要求
READY,可用于初始化前、复位后或故障现场诊断。 - 该接口只读取寄存器,不修改配置。
- Shell 命令
nemc_dump_regs使用本接口,需启用CONFIG_MIDDLEWARE_SHELL_CMD_NEMC和上述两个 debug helper。
5. 典型完整示例
5.1 初始化、访问和清理
以下示例假设已完成 NEMC 时钟、pinmux 和外部器件准备。示例中的 timing 枚举值必须替换为按器件手册计算并经硬件验证的值:
#include <stdint.h>
#include "hal_nemc.h"
#include "im_error.h"
#define DEMO_NEMC_WORD_COUNT (8U)
static hal_nemc_handle_t s_demo_nemc;
static error_code_t demo_nemc_init(void)
{
hal_nemc_config_t config;
error_code_t ret;
ret = hal_nemc_config_default(&config);
if (ret != IM_OK) {
return ret;
}
config.bus_width = (uint8_t)HAL_NEMC_BUS_WIDTH_16;
config.wait_pin_enable = 0U;
config.timing.tch = HAL_NEMC_TCH_CYCLE_5;
config.timing.taw_oe = HAL_NEMC_TAW_OE_CYCLE_10;
config.timing.tbp_ds = HAL_NEMC_TBP_DS_CYCLE_10;
config.timing.tras = HAL_NEMC_TRAS_CYCLE_5;
config.timing.twas = HAL_NEMC_TWAS_CYCLE_5;
config.timing.strv = 15U;
config.timing.trdrv = HAL_NEMC_TRDRV_CYCLE_1;
config.timing.twrrv = HAL_NEMC_TWRRV_CYCLE_0;
config.timing.tavdh = HAL_NEMC_TAVDH_CYCLE_10;
config.timing.tavdp = HAL_NEMC_TAVDP_CYCLE_10;
config.timing.tavds = HAL_NEMC_TAVDS_CYCLE_10;
config.timing.double_enable = 0U;
return hal_nemc_init(&s_demo_nemc, NEMC, &config);
}
static error_code_t demo_nemc_write_read(void)
{
volatile uint16_t *window =
(volatile uint16_t *)(uintptr_t)HAL_NEMC_STATIC_MEMORY_BASE;
uint32_t index;
if ((DEMO_NEMC_WORD_COUNT * sizeof(uint16_t)) >
HAL_NEMC_STATIC_MEMORY_MAX_SIZE) {
return IM_ERR_INVALID_PARAM;
}
for (index = 0U; index < DEMO_NEMC_WORD_COUNT; index++) {
window[index] = (uint16_t)(0x2000U + index);
}
for (index = 0U; index < DEMO_NEMC_WORD_COUNT; index++) {
if (window[index] != (uint16_t)(0x2000U + index)) {
return IM_ERR_IO;
}
}
return IM_OK;
}
error_code_t demo_nemc_run(void)
{
error_code_t ret;
ret = demo_nemc_init();
if (ret != IM_OK) {
return ret;
}
ret = demo_nemc_write_read();
(void)hal_nemc_deinit(&s_demo_nemc);
return ret;
}
说明:
- 该示例不包含时钟、pinmux 或外部器件控制代码,不能直接用于未完成板级准备的系统。
- 写后读回能够发现大部分接线和 timing 问题,但不能替代对总线时序的硬件测量。
- 如果调用
hal_nemc_deinit()后仍要继续访问窗口,必须重新调用hal_nemc_init()。 - 如果启用了文件系统、RTOS 或其他外部总线使用者,必须由系统层保证 NEMC 窗口访问互斥。
5.2 调试寄存器快照
启用两个 debug helper 后,可以不经过 hal_nemc_init(),直接调用 hal_nemc_regs_dump() 读取寄存器用于诊断:
#include "hal_nemc.h"
#include "im_log.h"
static void demo_nemc_dump(void)
{
hal_nemc_handle_t hnemc = {0};
hnemc.instance = NEMC;
(void)hal_nemc_regs_dump(&hnemc);
}
如果希望通过 Shell 直接使用该接口,还需要额外启用 CONFIG_MIDDLEWARE_SHELL_CMD_NEMC=y:
nemc_dump_regs
直接使用 nemc_dump_regs 命令需要同时启用以下配置:
CONFIG_SDK_ENABLE_DEBUG_HELPERS=y
CONFIG_NEMC_ENABLE_DEBUG_HELPERS=y
CONFIG_MIDDLEWARE_SHELL_CMD_NEMC=y
前两项使能寄存器读取和日志输出代码,第三项注册 Shell 命令;仅调用 C API 时不需要第三项。只启用 Shell 命令而未同时启用两个 debug helper 时,命令会报告 IM_ERR_UNSUPPORTED。运行该命令还需要 Shell 中间件及可用的控制台后端。
6. 使用约束和检查清单
6.1 关键约束
- HAL 只支持
NEMC实例和地址/数据复用静态存储模式。 - HAL 不配置时钟、GPIO pinmux、外部器件电源/复位或板级地址映射。
hal_nemc_init()不写 SACR;默认窗口由硬件复位值决定。- NEMC 数据访问只能通过内存映射窗口完成,HAL 不提供事务级读写 API,也不返回总线错误。
- 默认窗口为
0x1B000000起始的 64 KiB,访问不得越界。 - timing 字段必须使用对应编码枚举;非线性字段不能按裸值推算周期。
- HAL 不提供并发保护,所有配置、复位和窗口访问都应串行化。
hal_nemc_soft_reset()是异步的;复位完成后必须重新初始化和配置。hal_nemc_deinit()只清理软件句柄,不恢复硬件。- 调试寄存器 dump 需要全局和模块 debug helper 同时启用。
6.2 首次集成检查
- 已启用
CONFIG_DRIVER_NEMC。 - 已使能 NEMC 时钟,并完成 CS#、AVD、RD#、WE#、WAIT#、AD 总线的 pinmux 配置。
- 外部器件已上电、复位已释放,WAIT# 状态符合器件要求。
- 总线位宽与硬件连接一致,8-bit 使用 AD0..AD7,16-bit 使用 AD0..AD15。
- 已根据外部器件数据手册配置全部 timing 字段,未仅依赖 SDK 默认值。
-
hal_nemc_init()成功,并通过hal_nemc_state_get()确认状态为READY。 - 如修改了 SACR,已按板级地址规划完成
hal_nemc_window_config()并验证新映射。 - 所有直接内存访问都位于实际窗口内,且不超过
HAL_NEMC_STATIC_MEMORY_MAX_SIZE。 - 已通过回读或逻辑分析仪验证写、读、WAIT#、AVD、RD#、WE# 和片选时序。
- 软件复位流程包含超时、复位完成轮询和重新初始化。
- 如需寄存器诊断,已同时启用
CONFIG_SDK_ENABLE_DEBUG_HELPERS与CONFIG_NEMC_ENABLE_DEBUG_HELPERS。 - 产品流程在停止或释放 NEMC 前,不再有任务访问静态存储窗口。