增加文件系统

This commit is contained in:
2026-07-21 00:30:59 +08:00
parent e06d849007
commit fbe0726991
50 changed files with 12921 additions and 893 deletions

View File

@@ -0,0 +1,14 @@
---
description: Build the STM32F4-Base Keil MDK project. Use ONLY when the user asks to build, compile, or verify the project.
---
Run the Keil MDK-ARM build from the project root:
```
cmd /c "cd /d "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM" && build.bat"
```
Check the last 10 lines of `MDK-ARM/build_log.txt` for errors/warnings.
Return code 0 = success (0 errors, 0 warnings).
Return code 1 = warnings-only (also failure per project rules).
Return code 2+ = compile errors.

View File

@@ -1,5 +1,7 @@
# STM32F4-Base # STM32F4-Base
> **⚠️ 编译验证:所有代码修改后必须执行 `@build` 验证编译通过0 错误 0 警告),否则不要提交。**
## 项目概述 ## 项目概述
STM32F407ZGTx (Cortex-M4 FPU) 基础固件项目,集成 CH395F 以太网控制器 SPI 驱动、GD5F2GQ5UE SPI NAND Flash 存储(含 FlashDB KVDB/TSDB 数据库)和 TPAFE5160 16位8通道同步采样 ADC 并行接口驱动。 STM32F407ZGTx (Cortex-M4 FPU) 基础固件项目,集成 CH395F 以太网控制器 SPI 驱动、GD5F2GQ5UE SPI NAND Flash 存储(含 FlashDB KVDB/TSDB 数据库)和 TPAFE5160 16位8通道同步采样 ADC 并行接口驱动。
@@ -58,33 +60,19 @@ STM32F4-Base/
仅支持 Keil MDK-ARM v5 (ARMCC)。打开 `MDK-ARM/STM32F407-Demo.uvprojx` 编译。 仅支持 Keil MDK-ARM v5 (ARMCC)。打开 `MDK-ARM/STM32F407-Demo.uvprojx` 编译。
- 编译器ARMCC V5.06 update 7 - 编译器ARMCC V5.06 update 7,优化 `-O4``spi.c`/`usart.c`/HAL 源文件 `-O0`C99`USE_HAL_DRIVER, STM32F407xx`
- 优化等级:`-O4` (项目级)`spi.c`/`usart.c` / HAL 源文件使用 `-O0`
- C 标准C99
- 全局宏定义:`USE_HAL_DRIVER, STM32F407xx`
### 命令行编译Agent 使用) ### 命令行编译Agent 使用)
修改代码后必须执行命令行编译验证,编译脚本位于 `MDK-ARM/build.bat` 修改代码后必须执行命令行编译验证,**仅使用** `@build`,不得直接调用 UV4.exe
``` ```
cmd /c "cd /d "工作目录" && build.bat" @build
``` ```
实际执行示例(从项目根目录): 该命令定义在 `.opencode/command/build.md`,自动执行 `MDK-ARM/build.bat` 进行全量编译(`-r`)。
``` **退出码:** 0 = 成功0 Error, 0 Warning| 1 = 有警告(同样不通过)| 2+ = 错误,需查看 `MDK-ARM/build_log.txt` 定位
cmd /c "cd /d "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM" && build.bat"
```
**编译结果判读:**
- 退出码 0无错误无警告编译成功
- 退出码 1有警告无错误编译失败警告不可接受需修复
- 退出码 2+:编译失败,需查看 `MDK-ARM/build_log.txt` 定位错误
**UV4.exe 路径:** `C:\Keil_v5\UV4\UV4.exe`
**工程文件:** `MDK-ARM/STM32F407-Demo.uvprojx`
**目标名称:** `STM32F407-Demo`
## 硬件配置 ## 硬件配置
@@ -158,12 +146,17 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init()
- 块擦除D8h参数是字节地址块编号 × 128KB不是块编号 - 块擦除D8h参数是字节地址块编号 × 128KB不是块编号
- 读取 ID9Fh返回 3 字节,第 0 字节无意义,第 1 字节 MID第 2 字节 DID - 读取 ID9Fh返回 3 字节,第 0 字节无意义,第 1 字节 MID第 2 字节 DID
## FlashDB 分区规划 ## Flash 分区规划256MB GD5F2GQ5UE
| 分区名 | 偏移 | 大小 | 用途 | | 分区名 | 偏移 | 大小 | 块范围 | 用途 |
|--------|------|------|------| |--------|------|------|--------|------|
| fdb_kvdb1 | 0 | 64MB | KVDB 键值数据库 | | fdb_kvdb1 | 0 | 64MB | Block 0~511 | FlashDB KVDB 键值数据库 |
| fdb_tsdb1 | 64MB | 64MB | TSDB 时序数据库 | | fdb_tsdb1 | 64MB | 64MB | Block 512~1023 | FlashDB TSDB 时序数据库 |
| ftl_fatfs | 128MB | 128MB | Block 1024~2047 | dhara FTL + FatFS 文件系统 |
- FlashDB 通过 FAL 层访问 `fdb_kvdb1` / `fdb_tsdb1``Drivers/BSP/GD5F2GQ5UE/fal_cfg.h`
- FatFS 通过 dhara FTL 访问 `ftl_fatfs` 分区,`nand_ftl.c``FTL_START_BLOCK = FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE`(与 `fal_cfg.h` 联动)
- 三个分区物理隔离,互不重叠,分区配置统一在 `fal_cfg.h:25-30`
## TPAFE5160 驱动关键点 ## TPAFE5160 驱动关键点

View File

@@ -22,12 +22,6 @@ extern "C" {
*/ */
#define APP_SYS_TIME_SRC 0 #define APP_SYS_TIME_SRC 0
/*
* 时间戳输出使能
* 定义后在日志行首自动追加 [T ssssss.uuuuuu] 微秒时间戳
*/
#define APP_TIMESTAMP_ENABLE
/* /*
* 功能模块使能开关 * 功能模块使能开关
*/ */

View File

@@ -10,7 +10,6 @@
#include "app_main.h" #include "app_main.h"
#include "app_cfg.h" #include "app_cfg.h"
#include "sys_time.h"
#define DBG_TAG "[APP]" #define DBG_TAG "[APP]"
#include "dbg_log.h" #include "dbg_log.h"
@@ -24,10 +23,6 @@
*/ */
int app_main_init(void) int app_main_init(void)
{ {
if (sys_time_init() != 0) {
DBG_ERROR("sys_time_init failed");
return -1;
}
DBG_INFO("App init OK"); DBG_INFO("App init OK");
return 0; return 0;
} }

199
App/sys_clock.c Normal file
View File

@@ -0,0 +1,199 @@
/*
* 模块名称System Clock — Wall Clock Time Maintenance
* 模块功能:实现系统墙钟时间维护,包括 RTC 初始读取、HAL Tick 漂移维持
* 适用平台STM32F407ZGTx (Cortex-M4 FPU, 168MHz)
* 作者:王建锋
* 创建日期2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#include "sys_clock.h"
#include "main.h"
#include <string.h>
#include <stdio.h>
#define DBG_TAG "[CLK]"
#include "dbg_log.h"
/* ============================================================
* 内部状态:系统墙钟
* ============================================================ */
static volatile uint32_t s_base_tick = 0; /* HAL_GetTick() 基准 */
static volatile uint32_t s_base_unix_secs = UINT32_MAX;
/* ============================================================
* 内部辅助函数Unix time ↔ sd2506_time_t 转换
* ============================================================ */
static void unix_to_sd2506(uint32_t unix_secs, sd2506_time_t *p_sd_time)
{
uint32_t y, m, d;
uint8_t h, mi, s;
int is_leap;
/* 时分秒 */
{
uint64_t total = (uint64_t)unix_secs;
s = (uint8_t)(total % 60uLL);
total /= 60uLL;
mi = (uint8_t)(total % 60uLL);
total /= 60uLL;
h = (uint8_t)(total % 24uL);
}
/* 年月日 */
{
uint64_t day_count = ((uint64_t)unix_secs / 86400uL);
y = 1970u;
while (day_count >= 365uLL) {
uint64_t diy = 365uLL;
if ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0)) {
diy = 366uLL;
}
if (day_count < diy) break;
day_count -= diy;
y++;
}
is_leap = ((y % 4 == 0 && y % 100 != 0) || (y % 400 == 0));
uint8_t mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (is_leap) mdays[1] = 29;
m = 0;
while (m < 12u) {
if (day_count < (uint64_t)mdays[m]) break;
day_count -= mdays[m];
m++;
}
d = (uint32_t)(day_count + 1uL);
/* 星期Zeller's congruence0=Sunday */
uint64_t wy = y;
uint64_t wm = m;
if (wm <= 2) {
wy--;
wm += 12uL;
}
uint8_t week = ((uint32_t)(wy % 100uLL + wy / 100uLL / 4uLL - wy / 100uLL / 15uLL) +
(uint32_t)(((uint64_t)26 * (wm + 1uL)) / 10uLL) +
(uint32_t)(d % 100uLL)) % 7uLL;
/* SD2506 year 字段为 0~99+2000 */
uint16_t sd_year = y >= 2000 ? (y - 2000) : (y + 2000);
p_sd_time->year = sd_year;
p_sd_time->month = m + 1uL; /* 月份从 0 → 1 */
p_sd_time->day = d;
p_sd_time->hour = h;
p_sd_time->minute = mi;
p_sd_time->second = s;
p_sd_time->week = week;
}
}
/* ============================================================
* 系统墙钟时间维护
* ============================================================ */
int sys_clock_init(void)
{
sd2506_time_t sd_time;
uint32_t year, month, day, hour, minute, second;
int64_t unix_secs = 0;
int i;
/* Step 1: 从 SD2506 RTC 读取初始时间 */
sd2506_get_time(&sd_time);
year = (uint32_t)sd_time.year + 2000uL;
month = (uint32_t)sd_time.month;
day = (uint32_t)sd_time.day;
hour = (uint32_t)sd_time.hour;
minute = (uint32_t)sd_time.minute;
second = (uint32_t)sd_time.second;
DBG_INFO("sys_clock_init: RTC initial time %04d-%02d-%02d %02d:%02d:%02d",
year, month, day, hour, minute, second);
/* Step 2: Unix epoch → seconds简化算法适用于 1970~2038 */
{
uint64_t total_days = 0;
for (i = 1970; i < year; i++) {
if ((i % 4 == 0 && i % 100 != 0) || (i % 400 == 0)) {
total_days += 366uLL;
} else {
total_days += 365uLL;
}
}
int is_leap = ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0));
uint8_t mdays[12] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (is_leap) mdays[1] = 29;
for (i = 0; i < month - 1; i++) {
total_days += mdays[i];
}
total_days += day - 1uL;
unix_secs = (int64_t)(total_days * 86400LL) + hour * 3600LL + minute * 60LL + second;
}
/* Step 3: 设置系统墙钟基准 */
s_base_tick = HAL_GetTick();
s_base_unix_secs = (uint32_t)unix_secs;
DBG_INFO("sys_clock_init: wall clock base = unix %lu, base_tick=%lu",
(unsigned long)s_base_unix_secs, (unsigned long)s_base_tick);
return 0;
}
uint32_t sys_clock_get(void)
{
uint32_t now_tick = HAL_GetTick();
int64_t unix_secs = (int64_t)s_base_unix_secs + (int64_t)((uint32_t)(now_tick - s_base_tick) / 1000uL);
if (unix_secs < 0) {
return UINT32_MAX; /* 未初始化 */
}
return (uint32_t)unix_secs;
}
char *sys_clock_get_str(char *buf, size_t buf_size)
{
uint32_t unix_secs = sys_clock_get();
if (unix_secs == UINT32_MAX) {
snprintf(buf, buf_size, "----/--/-- --:--:--");
return buf;
}
sd2506_time_t sd;
unix_to_sd2506(unix_secs, &sd);
snprintf(buf, buf_size, "%04d-%02d-%02d %02d:%02d:%02d",
sd.year + 2000uL, sd.month, sd.day,
sd.hour, sd.minute, sd.second);
return buf;
}
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time)
{
/* Step 1: 更新墙钟基准 */
s_base_tick = HAL_GetTick();
s_base_unix_secs = unix_secs;
DBG_INFO("sys_clock_set: wall clock updated to %lu", (unsigned long)unix_secs);
/* Step 2: 写回 SD2506 RTC若提供了 sd_time */
if (sd_time != NULL) {
sd2506_set_time(sd_time);
DBG_INFO("sys_clock_set: SD2506 RTC written");
}
return 0;
}

65
App/sys_clock.h Normal file
View File

@@ -0,0 +1,65 @@
/*
* 模块名称System Clock — Wall Clock Time Maintenance
* 模块功能:基于 HAL Tick + SD2506 RTC 的系统墙钟时间维护
* 启动时从 SD2506 RTC 读取初始时间,运行期间以 HAL Tick 计数维持
* 适用平台STM32F407ZGTx (Cortex-M4 FPU, 168MHz)
* 作者:王建锋
* 创建日期2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#ifndef __SYS_CLOCK_H
#define __SYS_CLOCK_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
#include "sd2506.h"
/*
* 函数功能:系统时钟初始化(从 SD2506 RTC 读取初始时间)
* 入口参数:无
* 返回值0 - 成功, -1 - 失败
* 限定条件sd2506_init() 已调用
* 函数说明:读取 SD2506 RTC 当前值转换为 Unix timestamp设为系统墙钟初始时间
*/
int sys_clock_init(void);
/*
* 函数功能获取当前系统墙钟时间Unix epoch seconds
* 入口参数:无
* 返回值Unix time (秒),失败返回 UINT32_MAX
* 限定条件sys_clock_init() 已调用
* 函数说明:基于 HAL Tick 计数 + 上次校准时刻的 Unix 秒数计算当前时间,
* 精度为毫秒级
*/
uint32_t sys_clock_get(void);
/*
* 函数功能:格式化系统墙钟时间为字符串
* 入口参数buf - 输出缓冲区(至少 20 字节)
* buf_size - 缓冲区大小
* 返回值buf 指针,失败返回 NULL
* 限定条件sys_clock_init() 已调用
* 函数说明:格式 "YYYY-MM-DD HH:MM:SS"
*/
char *sys_clock_get_str(char *buf, size_t buf_size);
/*
* 函数功能:设置系统墙钟时间
* 入口参数unix_secs - Unix epoch time (秒)
* sd_time - SD2506 RTC 时间结构体(可选,非 NULL 时同时写入 RTC
* 返回值0 - 成功, -1 - 失败
* 限定条件sys_clock_init() 已调用
* 函数说明:更新系统墙钟基准 + 写回 SD2506 RTC 保持长期准确性
*/
int sys_clock_set(uint32_t unix_secs, const sd2506_time_t *sd_time);
#ifdef __cplusplus
}
#endif
#endif /* __SYS_CLOCK_H */

View File

@@ -1,107 +0,0 @@
/*
* 模块名称System Timer
* 模块功能DWT CYCCNT 实现 64-bit 微秒级时间戳
* 在 SysTick 中断中检测 CYCCNT 溢出以扩展至 64-bit
* 适用平台STM32F407ZGTx (168MHz)
* 作者:王建锋
* 创建日期2026-07-19
*
* 限定条件:
* - CPU 主频 168MHz若更改须同步 SYS_TIME_CYCLE_PER_US 宏)
* - 须在 FreeRTOS 启动前调用 sys_time_init()
* - sys_time_systick_hook() 须在 SysTick 中断中调用
*
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#include "sys_time.h"
#include "app_cfg.h"
#include "stm32f4xx.h"
/*
* 每微秒 CPU 周期数168MHz → 168 cycles/µs
*/
#define SYS_TIME_CYCLE_PER_US 168UL
/*
* CYCCNT 每溢出一次代表的微秒数2^32 / 168 ≈ 25,564,904 µs ≈ 25.56s
*/
#define SYS_TIME_OVERFLOW_US (uint64_t)(((uint64_t)1 << 32) / SYS_TIME_CYCLE_PER_US)
static volatile uint32_t s_overflow_cnt;
/*
* 函数功能:初始化 DWT CYCCNT
* 入口参数:无
* 返 回 值0 - 成功
* 限定条件:须在 FreeRTOS 启动前调用
* 函数说明:启用 DWT 和 CYCCNT 计数器,计数器从 0 开始
*/
int sys_time_init(void)
{
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
s_overflow_cnt = 0;
return 0;
}
/*
* 函数功能:获取微秒级时间戳
* 入口参数:无
* 返 回 值64 位微秒时间戳
* 函数说明:由当前 CYCCNT 值与溢出计数合成 64-bit 周期数再转换
*/
uint64_t sys_time_us(void)
{
uint32_t cyccnt = 0;
uint64_t ticks = 0;
uint32_t ov = 0;
uint32_t ov2 = 0;
/*
* 原子读取溢出计数与 CYCCNT处理潜在的读取竞争
* 若两次读取溢出计数不一致则重读 CYCCNT
*/
do {
ov = s_overflow_cnt;
cyccnt = DWT->CYCCNT;
ov2 = s_overflow_cnt;
} while (ov != ov2);
ticks = ((uint64_t)ov << 32) | cyccnt;
return ticks / SYS_TIME_CYCLE_PER_US;
}
/*
* 函数功能:获取毫秒级时间戳
* 入口参数:无
* 返 回 值64 位毫秒时间戳
* 限定条件sys_time_init() 已成功调用
* 函数说明:通过对 sys_time_us() 取整实现
*/
uint64_t sys_time_ms(void)
{
return sys_time_us() / 1000;
}
/*
* 函数功能SysTick 中断钩子,检测 CYCCNT 溢出
* 入口参数:无
* 返 回 值:无
* 函数说明:必须在 SysTick_Handler() 中调用
* 每 1ms 检查一次 CYCCNT 是否已回绕
*/
void sys_time_systick_hook(void)
{
static uint32_t s_last_cyccnt = 0;
uint32_t cyccnt = DWT->CYCCNT;
if (cyccnt < s_last_cyccnt) {
s_overflow_cnt++;
}
s_last_cyccnt = cyccnt;
}

View File

@@ -1,29 +0,0 @@
/*
* 模块名称System Timer
* 模块功能:基于 DWT CYCCNT + SysTick 溢出的 64 位微秒级时间戳
* 适用平台STM32F407ZGTx (Cortex-M4 FPU)
* 作者:王建锋
* 创建日期2026-07-19
* 修改记录:
* v1.0 2026-07-19 王建锋 创建初始版本
*/
#ifndef __SYS_TIME_H
#define __SYS_TIME_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
int sys_time_init(void);
uint64_t sys_time_us(void);
uint64_t sys_time_ms(void);
void sys_time_systick_hook(void);
#ifdef __cplusplus
}
#endif
#endif /* __SYS_TIME_H */

View File

@@ -46,10 +46,11 @@ void net_task_func(void const *arg)
(void)arg; (void)arg;
osDelay(2000); osDelay(2000);
DBG_INFO("netTask: started"); DBG_INFO("netTask: started, processing messages");
for (;;) { for (;;) {
net_poll(); net_poll();
net_process_messages();
#ifdef ENABLE_PHASE4_TESTS #ifdef ENABLE_PHASE4_TESTS
for (int i = 1; i < 8; i++) { for (int i = 1; i < 8; i++) {
@@ -58,9 +59,9 @@ void net_task_func(void const *arg)
continue; continue;
} }
int n = net_recv(i, s_net_buf, sizeof(s_net_buf), NET_MSG_DONTWAIT); int n = net_recv_sock(p_sk, s_net_buf, sizeof(s_net_buf), NET_MSG_DONTWAIT);
if (n > 0) { if (n > 0) {
net_send(i, s_net_buf, n, 0); net_send_sock(p_sk, s_net_buf, n);
} }
} }
#endif #endif

File diff suppressed because it is too large Load Diff

View File

@@ -2,8 +2,8 @@
* 模块名称CH395F Driver Test Suite * 模块名称CH395F Driver Test Suite
* 模块功能CH395F 驱动各功能模块的测试入口,分阶段组织测试用例 * 模块功能CH395F 驱动各功能模块的测试入口,分阶段组织测试用例
* 适用平台STM32F4 系列CH395F 以太网芯片) * 适用平台STM32F4 系列CH395F 以太网芯片)
* 作者:王建ï¿? * 创建日期ï¿?026-07-19 * 作者王建<EFBFBD>? * 创建日期<E697A5>?026-07-19
* 修改记录ï¿? * 2026-07-19 Phase 1/2/3 implemented * 修改记录<EFBFBD>? * 2026-07-19 Phase 1/2/3 implemented
*/ */
/* 头文件包含区 */ /* 头文件包含区 */
@@ -18,7 +18,7 @@
test_stats_t g_test_stats; test_stats_t g_test_stats;
/* /*
* Phase 2 é…<EFBFBD>置默认ï¿? */ * Phase 2 配置默认<EFBFBD>? */
#ifndef PHASE2_REMOTE_IP #ifndef PHASE2_REMOTE_IP
#define PHASE2_REMOTE_IP "192.168.1.2" #define PHASE2_REMOTE_IP "192.168.1.2"
#endif #endif
@@ -36,7 +36,7 @@ test_stats_t g_test_stats;
#endif #endif
/* /*
* Phase 3 é…<EFBFBD>置默认ï¿? */ * Phase 3 配置默认<EFBFBD>? */
#ifndef PHASE3_REMOTE_IP #ifndef PHASE3_REMOTE_IP
#define PHASE3_REMOTE_IP "192.168.1.2" #define PHASE3_REMOTE_IP "192.168.1.2"
#endif #endif
@@ -54,9 +54,9 @@ test_stats_t g_test_stats;
#endif #endif
/* /*
* 函数功能:æ<EFBFBD>¢ï¿?TCP Server 多连接模å¼<C3A5>的数æ<C2B0>® Socket é…<EFBFBD>ç½® * 函数功能<EFBFBD>?TCP Server 多连接模式的数据 Socket 配置
* 入口参数port - TCP 源端口号 * 入口参数port - TCP 源端口号
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šCH395F å<><C3A5>议栈已åˆ<C3A5>å§ï¿? * 函数说明:é‡<C3A9>æ°è®¾ç½®æ•°ï¿?Socket çš„å<E2809E><C3A5>议类åžåŒæº<C3A6>端ï¿? */ * <EFBFBD>?<3F>?值<E580BC>? * 限定条件CH395F 协议栈已初始<E5889D>? * 函数说明重新设置数<E7BDAE>?Socket 的协议类型和源端<E6BA90>? */
static void restore_data_sockets(uint16_t port) { static void restore_data_sockets(uint16_t port) {
int i = 0; int i = 0;
for (i = NET_TCP_SERVER_DATA_SOCK_START; i < NET_MAX_SOCKETS; i++) { for (i = NET_TCP_SERVER_DATA_SOCK_START; i < NET_MAX_SOCKETS; i++) {
@@ -70,8 +70,8 @@ static void restore_data_sockets(uint16_t port) {
* ============================ Phase 1 ============================ * ============================ Phase 1 ============================
* 函数功能:寄存器读写 + 中断一致性基础测试 * 函数功能:寄存器读写 + 中断一致性基础测试
* 入口参数:无 * 入口参数:无
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šCH395F 芯片正常应答 * <EFBFBD>?<3F>?值<E580BC>? * 限定条件CH395F 芯片正常应答
* 函数说明:包ï¿?GET_VERSION / GET_CMD_STATUS / GET_IP_INF ï¿?9 ï¿? */ * 函数说明<EFBFBD>?GET_VERSION / GET_CMD_STATUS / GET_IP_INF <EFBFBD>?9 <EFBFBD>? */
void ch395f_phase1_tests(void) { void ch395f_phase1_tests(void) {
uint8_t i = 0; uint8_t i = 0;
@@ -112,7 +112,7 @@ void ch395f_phase1_tests(void) {
"GINT_STATUS_ALL: 0x%04X (lo=0x%02X hi=0x%02X)", "GINT_STATUS_ALL: 0x%04X (lo=0x%02X hi=0x%02X)",
gint_all, gint_all & 0xFF, (gint_all >> 8) & 0xFF); gint_all, gint_all & 0xFF, (gint_all >> 8) & 0xFF);
/* Test 5: GINT_STATUS (1-byte version) ï¿?read-clear check */ /* Test 5: GINT_STATUS (1-byte version) <EFBFBD>?read-clear check */
uint8_t gint_1b = ch395f_get_glob_int_status(); uint8_t gint_1b = ch395f_get_glob_int_status();
TEST_CHECK(gint_1b == 0x00, TEST_CHECK(gint_1b == 0x00,
"GINT_STATUS_1byte: 0x%02X (expect 0x00 after read-clear)", gint_1b); "GINT_STATUS_1byte: 0x%02X (expect 0x00 after read-clear)", gint_1b);
@@ -146,8 +146,8 @@ void ch395f_phase1_tests(void) {
/* /*
* ============================ Phase 2 ============================ * ============================ Phase 2 ============================
* 函数功能:TCP Client æ”¶å<C2B6> + 关闭é‡<C3A9>连 ×3 ï¿? * å…¥å<C2A5>£å<C2A3>数:无 * 函数功能TCP Client 收发 + 关闭重连 ×3 <20>? * 入口参数:无
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šPC 端已å<C2B2>¯åЍ TCP Server(默ï¿?192.168.1.2:8081ï¿? * 函数说明:æ¯<EFBFBD>轮连接â†å<EFBFBD>é€<EFBFBD>â†æŽ¥æ”¶â†å…³é—­ï¼Œè¿žç»­ 3 ï¿? */ * <EFBFBD>?<3F>?值<E580BC>? * 限定条件PC 端已启动 TCP Server<EFBFBD>?192.168.1.2:8081<EFBFBD>? * 函数说明:每轮连接→发送→接收→关闭,连续 3 <EFBFBD>? */
void ch395f_phase2_tests(void) { void ch395f_phase2_tests(void) {
uint8_t sock = 0; uint8_t sock = 0;
@@ -174,7 +174,7 @@ void ch395f_phase2_tests(void) {
sock = (uint8_t)(NET_MAX_SOCKETS - 1); sock = (uint8_t)(NET_MAX_SOCKETS - 1);
/* 一次性é…<EFBFBD>ï¿?*/ /* 一次性配<EFBFBD>?*/
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP); ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
ch395f_set_send_buf(sock, 28, 2); ch395f_set_send_buf(sock, 28, 2);
ch395f_set_recv_buf(sock, 30, 2); ch395f_set_recv_buf(sock, 30, 2);
@@ -204,7 +204,7 @@ void ch395f_phase2_tests(void) {
HAL_Delay(100); HAL_Delay(100);
/* å<EFBFBD>é€<EFBFBD>æµè¯•æ•°ï¿?*/ /* 发送测试数<EFBFBD>?*/
{ {
uint8_t test_buf[64] = {0}; uint8_t test_buf[64] = {0};
const char *msg = "HelloFromCH395F_TCP_Client! This is a 64-byte test message."; const char *msg = "HelloFromCH395F_TCP_Client! This is a 64-byte test message.";
@@ -214,7 +214,7 @@ void ch395f_phase2_tests(void) {
ch395f_write_send_buf(sock, test_buf, 64); ch395f_write_send_buf(sock, test_buf, 64);
} }
/* Echo 循环:收数æ<EFBFBD>® ï¿?åžå<C5BE> */ /* Echo 循环:收数据 <20>?回发 */
{ {
int got_echo = 0; int got_echo = 0;
tick = HAL_GetTick(); tick = HAL_GetTick();
@@ -266,7 +266,7 @@ phase2_iter_done:
* ============================ Phase 3 ============================ * ============================ Phase 3 ============================
* 函数功能UDP Server Echo + PING + 大包测试 * 函数功能UDP Server Echo + PING + 大包测试
* 入口参数:无 * 入口参数:无
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šPC 端已å<C2B2>¯åЍ UDP Client(默ï¿?192.168.1.2:8082ï¿? * 函数说明:包ï¿?20 次回显�? ï¿?PINGï¿?0 次大包收ï¿? */ * <EFBFBD>?<3F>?值<E580BC>? * 限定条件PC 端已启动 UDP Client<EFBFBD>?192.168.1.2:8082<EFBFBD>? * 函数说明<EFBFBD>?20 次回显<E59B9E>? <20>?PING<EFBFBD>?0 次大包收<EFBFBD>? */
void ch395f_phase3_tests(void) { void ch395f_phase3_tests(void) {
uint8_t sock = 0; uint8_t sock = 0;
@@ -279,7 +279,7 @@ void ch395f_phase3_tests(void) {
sock = (uint8_t)(NET_MAX_SOCKETS - 1); sock = (uint8_t)(NET_MAX_SOCKETS - 1);
/* UDP Server 模å¼<EFBFBD>:DesIP=0xFFFFFFFF ï¿?接å<C2A5>—ä»»æ„<C3A6>æ<EFBFBD>¥æº<C3A6> */ /* UDP Server 模式:DesIP=0xFFFFFFFF <EFBFBD>?接受任意来源 */
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_UDP); ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_UDP);
ch395f_set_send_buf(sock, 28, 2); ch395f_set_send_buf(sock, 28, 2);
ch395f_set_recv_buf(sock, 30, 2); ch395f_set_recv_buf(sock, 30, 2);
@@ -295,7 +295,7 @@ void ch395f_phase3_tests(void) {
HAL_Delay(200); HAL_Delay(200);
/* Echo 循环:收ï¿?ï¿?è§£æž<C3A6>æ<EFBFBD>¥æº<C3A6> ï¿?回显 */ /* Echo 循环<EFBFBD>?<3F>?解析来源 <20>?回显 */
for (i = 0; i < PHASE3_ECHO_COUNT; i++) { for (i = 0; i < PHASE3_ECHO_COUNT; i++) {
tick = HAL_GetTick(); tick = HAL_GetTick();
echo_ok = 0; echo_ok = 0;
@@ -348,7 +348,7 @@ void ch395f_phase3_tests(void) {
/* /*
* ============================ Phase 4 ============================ * ============================ Phase 4 ============================
* NET ï¿?TCP Echo:在主循环中调用,用 net_recv + net_send 回显 * NET <EFBFBD>?TCP Echo:在主循环中调用,用 net_recv + net_send 回显
*/ */
#ifdef ENABLE_PHASE4_TESTS #ifdef ENABLE_PHASE4_TESTS
@@ -356,9 +356,9 @@ static char s_phase4_buf[2048];
#endif #endif
/* /*
* 函数功能:NET ï¿?TCP Echo 测试 * 函数功能:NET <EFBFBD>?TCP Echo 测试
* 入口参数:无 * 入口参数:无
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šnet_init 已完ï¿? * 函数说明:在 netTask 中è¿<C3A8>行,轮询 Socket 1~7 åžæ˜¾æŽ¥æ”¶æ•°æ<C2B0>® * <EFBFBD>?<3F>?值<E580BC>? * 限定条件net_init 已完<E5B7B2>? * 函数说明:在 netTask 中运行,轮询 Socket 1~7 回显接收数据
*/ */
void ch395f_phase4_tests(void) { void ch395f_phase4_tests(void) {
#ifdef ENABLE_PHASE4_TESTS #ifdef ENABLE_PHASE4_TESTS
@@ -368,9 +368,9 @@ void ch395f_phase4_tests(void) {
continue; continue;
} }
int n = net_recv(i, s_phase4_buf, sizeof(s_phase4_buf), NET_MSG_DONTWAIT); int n = net_recv_sock(sk, s_phase4_buf, sizeof(s_phase4_buf), NET_MSG_DONTWAIT);
if (n > 0) { if (n > 0) {
net_send(i, s_phase4_buf, n, 0); net_send_sock(sk, s_phase4_buf, n);
} }
} }
#endif #endif
@@ -378,7 +378,7 @@ void ch395f_phase4_tests(void) {
/* /*
* ============================ Phase 5 ============================ * ============================ Phase 5 ============================
* NET ï¿?UDP Echo:netTask 中è¿<EFBFBD>行,ï¿?net_recvfrom + net_sendto 回显 * NET <EFBFBD>?UDP EchonetTask 中运行<EFBFBD>?net_recvfrom + net_sendto 回显
*/ */
#ifndef PHASE5_LOCAL_PORT #ifndef PHASE5_LOCAL_PORT
@@ -392,8 +392,8 @@ static uint32_t s_phase5_count = 0;
#endif #endif
/* /*
* 函数功能:NET ï¿?UDP Echo åˆ<EFBFBD>å§ï¿? * å…¥å<C2A5>£å<C2A3>数:无 * 函数功能:NET <EFBFBD>?UDP Echo 初始<EFBFBD>? * 入口参数:无
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šnet_init 已完ï¿? * 函数说明:创ï¿?UDP Socket 并绑定本地端ï¿? */ * <EFBFBD>?<3F>?值<E580BC>? * 限定条件net_init 已完<E5B7B2>? * 函数说明<EFBC9A>?UDP Socket 并绑定本地端<E59CB0>? */
void ch395f_phase5_init(void) { void ch395f_phase5_init(void) {
#ifdef ENABLE_PHASE5_TESTS #ifdef ENABLE_PHASE5_TESTS
struct net_sockaddr_in addr; struct net_sockaddr_in addr;
@@ -410,7 +410,7 @@ void ch395f_phase5_init(void) {
if (net_bind(s_phase5_sock, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) { if (net_bind(s_phase5_sock, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) {
DBG_ERROR("Phase5: net_bind failed"); DBG_ERROR("Phase5: net_bind failed");
net_close(s_phase5_sock); net_close_sock(net_get_sock(s_phase5_sock));
s_phase5_sock = -1; s_phase5_sock = -1;
return; return;
} }
@@ -421,9 +421,9 @@ void ch395f_phase5_init(void) {
} }
/* /*
* 函数功能:NET ï¿?UDP Echo 测试 * 函数功能:NET <EFBFBD>?UDP Echo 测试
* 入口参数:无 * 入口参数:无
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šch395f_phase5_init å·²æˆ<C3A6>功调ï¿? * 函数说明:在 netTask 中è¿<C3A8>è¡Œï¼Œåžæ˜¾ UDP 接收数æ<C2B0>® * <EFBFBD>?<3F>?值<E580BC>? * 限定条件ch395f_phase5_init 已成功调<E58A9F>? * 函数说明:在 netTask 中运行,回显 UDP 接收数据
*/ */
void ch395f_phase5_tests(void) { void ch395f_phase5_tests(void) {
#ifdef ENABLE_PHASE5_TESTS #ifdef ENABLE_PHASE5_TESTS
@@ -445,7 +445,7 @@ void ch395f_phase5_tests(void) {
} }
/* /*
* Phase 6 é…<EFBFBD>置默认ï¿? */ * Phase 6 配置默认<EFBFBD>? */
#ifndef PHASE6_ANNOUNCE_PORT #ifndef PHASE6_ANNOUNCE_PORT
#define PHASE6_ANNOUNCE_PORT 60001 #define PHASE6_ANNOUNCE_PORT 60001
#endif #endif
@@ -457,8 +457,8 @@ void ch395f_phase5_tests(void) {
* ============================ Phase 6 ============================ * ============================ Phase 6 ============================
* 函数功能DHCP 自动获取 IP 测试 * 函数功能DHCP 自动获取 IP 测试
* 入口参数:无 * 入口参数:无
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šå±€åŸŸç½å†…å­˜ï¿?DHCP Server * <EFBFBD>?<3F>?值<E580BC>? * 限定条件局域网内存<E58685>?DHCP Server
* 函数说明:å<EFBFBD>¯ï¿?DHCP ï¿?轮询等待 IP ï¿?打å<E2809C>°ç»“æžœ ï¿?广播宣告 ï¿?HELLO 回显 * 函数说明<EFBFBD>?DHCP <20>?轮询等待 IP <20>?打印结果 <20>?广播宣告 <20>?HELLO 回显
*/ */
void ch395f_phase6_tests(void) { void ch395f_phase6_tests(void) {
@@ -498,7 +498,7 @@ void ch395f_phase6_tests(void) {
goto phase6_end; goto phase6_end;
} }
/* 3. 读å<EFBFBD>获å<EFBFBD>ï¿?IP */ /* 3. 读取获取<EFBFBD>?IP */
ch395f_get_ip_inf(ip_info); ch395f_get_ip_inf(ip_info);
DBG_INFO(" IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]); DBG_INFO(" IP: %d.%d.%d.%d", ip_info[0], ip_info[1], ip_info[2], ip_info[3]);
DBG_INFO(" GW: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]); DBG_INFO(" GW: %d.%d.%d.%d", ip_info[4], ip_info[5], ip_info[6], ip_info[7]);
@@ -515,7 +515,7 @@ void ch395f_phase6_tests(void) {
uint8_t bcast_ip[4] = {0xFF, 0xFF, 0xFF, 0xFF}; uint8_t bcast_ip[4] = {0xFF, 0xFF, 0xFF, 0xFF};
uint8_t rx_buf[64] = {0}; uint8_t rx_buf[64] = {0};
/* UDP Server 模å¼<EFBFBD>:本地端ï¿?60000(接ï¿?HELLO),DesIP=0xFFFFFFFF */ /* UDP Server 模式本地端<EFBFBD>?60000<EFBC88>?HELLODesIP=0xFFFFFFFF */
ch395f_set_proto_type(anno_sock, CH395F_PROTO_TYPE_UDP); ch395f_set_proto_type(anno_sock, CH395F_PROTO_TYPE_UDP);
ch395f_set_send_buf(anno_sock, 24, 2); ch395f_set_send_buf(anno_sock, 24, 2);
ch395f_set_recv_buf(anno_sock, 26, 2); ch395f_set_recv_buf(anno_sock, 26, 2);
@@ -524,7 +524,7 @@ void ch395f_phase6_tests(void) {
ch395f_set_des_port(anno_sock, 0); ch395f_set_des_port(anno_sock, 0);
if (ch395f_open_socket(anno_sock) == CH395F_ERR_SUCCESS) { if (ch395f_open_socket(anno_sock) == CH395F_ERR_SUCCESS) {
/* å<EFBFBD>é€<EFBFBD>广æ­å®£åŠï¼ˆå®£åŠç«¯å<EFBFBD>£ = 60001,æ<C592>¥æº<C3A6>端ï¿?= 60000ï¿?*/ /* 发送广播宣告(宣告端口 = 60001来源端<E6BA90>?= 60000<EFBFBD>?*/
ch395f_set_des_ip(anno_sock, bcast_ip); ch395f_set_des_ip(anno_sock, bcast_ip);
ch395f_set_des_port(anno_sock, PHASE6_ANNOUNCE_PORT); ch395f_set_des_port(anno_sock, PHASE6_ANNOUNCE_PORT);
ch395f_write_send_buf(anno_sock, ip_info, 4); ch395f_write_send_buf(anno_sock, ip_info, 4);
@@ -546,14 +546,14 @@ void ch395f_phase6_tests(void) {
PHASE6_HELLO_PORT, PHASE6_ANNOUNCE_PORT); PHASE6_HELLO_PORT, PHASE6_ANNOUNCE_PORT);
TEST_CHECK(1, "DHCP announcement sent"); TEST_CHECK(1, "DHCP announcement sent");
/* é‡<EFBFBD>ç½® Server 模å¼<EFBFBD>,等ï¿?PC å<>�?HELLO */ /* 重置 Server 模式<EFBFBD>?PC 发<>?HELLO */
ch395f_set_des_ip(anno_sock, bcast_ip); ch395f_set_des_ip(anno_sock, bcast_ip);
ch395f_set_des_port(anno_sock, 0); ch395f_set_des_port(anno_sock, 0);
uint32_t wait = HAL_GetTick(); uint32_t wait = HAL_GetTick();
uint8_t hello_ok = 0; uint8_t hello_ok = 0;
while (HAL_GetTick() - wait < 5000) { while (HAL_GetTick() - wait < 5000) {
/* 读å<EFBFBD>全局中æ­ï¼ˆåˆ·ï¿?socket 中æ­çжæ€<C3A6>) */ /* 读取全局中断<EFBFBD>?socket 中断状态) */
ch395f_get_glob_int_status_all(); ch395f_get_glob_int_status_all();
uint16_t rlen = ch395f_get_recv_len(anno_sock); uint16_t rlen = ch395f_get_recv_len(anno_sock);
if (rlen > 8) { if (rlen > 8) {
@@ -593,7 +593,7 @@ phase6_end:
/* /*
* ============================ Phase 7 ============================ * ============================ Phase 7 ============================
* Select/Poll I/O 多路å¤<EFBFBD>用æµè¯• ï¿?net_select() + net_recv/send echo * Select/Poll I/O 多路复用测试 <20>?net_select() + net_recv/send echo
*/ */
#include "net_select.h" #include "net_select.h"
@@ -605,7 +605,7 @@ static char s_phase7_buf[2048];
/* /*
* 函数功能Select/Poll I/O 多路复用测试 * 函数功能Select/Poll I/O 多路复用测试
* 入口参数:无 * 入口参数:无
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šnet_init 已完æˆ<C3A6>,多连ï¿?TCP Server å·²å<C2B2>¯ï¿? * 函数说明:在 netTask 中è¿<C3A8>行,验è¯<C3A8> select/poll 正确返回读写事件 * <EFBFBD>?<3F>?值<E580BC>? * 限定条件net_init 已完成多连<E5A49A>?TCP Server 已启<E5B7B2>? * 函数说明:在 netTask 中运行,验证 select/poll 正确返回读写事件
*/ */
void ch395f_phase7_tests(void) { void ch395f_phase7_tests(void) {
#ifdef ENABLE_PHASE7_TESTS #ifdef ENABLE_PHASE7_TESTS
@@ -630,11 +630,12 @@ void ch395f_phase7_tests(void) {
if (n <= 0) return; if (n <= 0) return;
for (int i = 0; i < NET_MAX_SOCKETS; i++) { for (int i = 0; i < NET_MAX_SOCKETS; i++) {
if (!NET_FD_ISSET(i, &readfds)) continue; net_sock_t *sk = net_get_sock(i);
if (!sk || !NET_FD_ISSET(i, &readfds)) continue;
int len = net_recv(i, s_phase7_buf, sizeof(s_phase7_buf), NET_MSG_DONTWAIT); int len = net_recv_sock(sk, s_phase7_buf, sizeof(s_phase7_buf), NET_MSG_DONTWAIT);
if (len > 0) { if (len > 0) {
net_send(i, s_phase7_buf, len, 0); net_send_sock(sk, s_phase7_buf, len);
s_phase7_count++; s_phase7_count++;
TEST_CHECK(1, "Phase7: select echo #%lu (sock=%d, %d bytes)", s_phase7_count, i, len); TEST_CHECK(1, "Phase7: select echo #%lu (sock=%d, %d bytes)", s_phase7_count, i, len);
} }

View File

@@ -3,43 +3,57 @@
/* /*
* 模块名称FAL 配置 * 模块名称FAL 配置
* 模å<EFBFBD>—功能:定ä¹?FAL Flash 设备表和分区è¡? * é€ç”¨å¹³å<C2B3>°ï¼šSTM32F407ZGT6 * 模块功能:定义 Flash 物理分区布局和 FAL 设备/分区表
* 作者:王建é”? * 创建日期ï¼?026-07-16 * 适用平台STM32F407ZGT6
* 修改记录ï¼? * 2026-07-16 王建é”? åˆå»ºåˆ<C3A5>å§ç‰ˆæœ¬ * 作者:王建锋
* 创建日期2026-07-16
* 修改记录:
* 2026-07-20 王建锋 统一分区配置,添加 FTL 分区宏供 nand_ftl.c 使用
*/ */
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
/* FAL 调试开关:0-关闭ï¼?-å¼€å<E282AC>?*/ /* ================== 物理分区布局256MB GD5F2GQ5UE ================== */
/*
* 分区名 偏移 大小 块范围 用途
* fdb_kvdb1 0 64MB Block 0~511 FlashDB KVDB
* fdb_tsdb1 64MB 64MB Block 512~1023 FlashDB TSDB
* ftl_fatfs 128MB 128MB Block 1024~2047 dhara FTL + FatFS
*/
#define FDB_KVDB1_OFFSET 0
#define FDB_KVDB1_SIZE (64 * 1024 * 1024)
#define FDB_TSDB1_OFFSET (FDB_KVDB1_OFFSET + FDB_KVDB1_SIZE)
#define FDB_TSDB1_SIZE (64 * 1024 * 1024)
#define FTL_FATFS_OFFSET (FDB_TSDB1_OFFSET + FDB_TSDB1_SIZE)
#define FTL_FATFS_SIZE (128 * 1024 * 1024)
/* ====================== FAL 配置(需 FAL 头文件) ====================== */
#define FAL_DEBUG 0 #define FAL_DEBUG 0
/* å<EFBFBD>¯ç”¨åˆ†åŒºè¡¨é…<EFBFBD>ç½?*/ /* 启用分区表配置FAL 库要求此宏) */
#define FAL_PART_HAS_TABLE_CFG #define FAL_PART_HAS_TABLE_CFG
/* ======================== Flash 设备è¡?======================== */ /* ======================== Flash 设备表 ======================== */
extern const struct fal_flash_dev g_gd5f2gq5ue_flash; extern const struct fal_flash_dev g_gd5f2gq5ue_flash;
#define FAL_FLASH_DEV_TABLE \ #define FAL_FLASH_DEV_TABLE \
{ \ { \
&g_gd5f2gq5ue_flash, \ &g_gd5f2gq5ue_flash, \
} }
/* ======================== 分区è¡?======================== */ /* ======================== 分区表 ======================== */
/*
* 分区å<C2BA><C3A5>ç§° 设备å<E280A1><C3A5>ç§° å<><C3A5>ç§» 大å°<C3A5>
* KVDB: 64MB @ 0
* TSDB: 64MB @ 64MB
*/
#ifdef FAL_PART_HAS_TABLE_CFG #ifdef FAL_PART_HAS_TABLE_CFG
#define FAL_PART_TABLE \ #define FAL_PART_TABLE \
{ \ { \
{FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", 0, 64*1024*1024, 0}, \ {FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", FDB_KVDB1_OFFSET, FDB_KVDB1_SIZE, 0}, \
{FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", 64*1024*1024, 64*1024*1024, 0}, \ {FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", FDB_TSDB1_OFFSET, FDB_TSDB1_SIZE, 0}, \
} }
#endif #endif /* FAL_PART_HAS_TABLE_CFG */
#ifdef __cplusplus #ifdef __cplusplus
} }

View File

@@ -1,9 +1,9 @@
/* /*
* 模块名称GD5F2GQ5UE SPI NAND Flash 驱动 * 模块名称GD5F2GQ5UE SPI NAND Flash 驱动
* 妯″潡鍔熻兘锛氭彁锟?GD5F2GQ5UE SPI NAND Flash 鐨勫垵濮嬪寲銆佽<EFBFBD>銆佸啓銆佹摝闄ゆ帴锟? * 閫傜敤骞冲彴锛歋TM32F407ZGT6 + SPI1 <EFBFBD> SPI * 模块功能<EFBFBD>?GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接<EFBFBD>? * 适用平台STM32F407ZGT6 + SPI1 硬件 SPI
* 浣滆€咃細鐜嬪缓锟? * 鍒涘缓鏃ユ湡锟?026-07-16 * 作者王建<EFBFBD>? * 创建日期<E697A5>?026-07-16
* <EFBFBD>敼璁板綍锟? * 2026-07-16 鐜嬪缓锟? 鍒涘缓鍒濆<E98D92>鐗堟湰 * 修改记录<EFBFBD>? * 2026-07-16 王建<EFBFBD>? 创建初始版本
* 2026-07-17 鐜嬪缓锟? 鍒囨崲纭<E5B4B2>欢 SPI锛屼慨姝摝闄ゅ湴鍧€锛屽弬锟?NuttX 椹卞姩 * 2026-07-17 王建<EFBFBD>? 切换硬件 SPI修正擦除地址<EFBC8C>?NuttX 驱动
*/ */
/* 头文件包含区 */ /* 头文件包含区 */
@@ -17,34 +17,58 @@
#include "dbg_log.h" #include "dbg_log.h"
/* 私有宏定义区 */ /* 私有宏定义区 */
#define GD5F_SPI_TIMEOUT 100 #define GD5F_SPI_TIMEOUT 100
#define GD5F_BBT_SIZE (GD5F_TOTAL_BLOCKS / 8)
/* BBT 静态数组1 bit 表示一个块0=好块 1=坏块) */
static uint8_t s_bbt[GD5F_BBT_SIZE];
/* 外部 SPI 句柄声明 */ /* 外部 SPI 句柄声明 */
extern SPI_HandleTypeDef hspi1; extern SPI_HandleTypeDef hspi1;
/* DMA 完成标志ch395f.c 中的 HAL_SPI_TxRxCpltCallback 会设置此标志) */
volatile uint8_t g_spi1_dma_done;
/* DMA 阈值(字节数超过此值使用 DMA 传输) */
#define GD5F_DMA_THRESHOLD 32
/*
* 函数功能SPI1 TX DMA 完成回调PROGRAM_LOAD 数据发送用)
*/
void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
g_spi1_dma_done = 1U;
}
}
/*
* 函数功能SPI1 RX DMA 完成回调READ_FROM_CACHE 数据接收用)
* 函数说明HAL_SPI_Receive_DMA() 在 2 线 master 模式下
* 内部走 HAL_SPI_TransmitReceive_DMA 但 state=BUSY_RX
* 完成回调是 HAL_SPI_RxCpltCallback 而非 TxRxCpltCallback
*/
void HAL_SPI_RxCpltCallback(SPI_HandleTypeDef *hspi) {
if (hspi->Instance == SPI1) {
g_spi1_dma_done = 1U;
}
}
/* ======================== 私有函数声明 ======================== */ /* ======================== 私有函数声明 ======================== */
static int gd5f_wait_busy(uint32_t timeout_ms);
static int gd5f_write_enable(void);
static int gd5f_read_status(uint8_t *p_status);
static int gd5f_page_read(uint32_t page_addr);
static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
size_t size);
static int gd5f_page_program(uint32_t page_addr, uint16_t column, static int gd5f_page_program(uint32_t page_addr, uint16_t column,
const uint8_t *p_buf, size_t size); const uint8_t *p_buf, size_t size);
static int gd5f_set_feature(uint8_t addr, uint8_t data); static int gd5f_set_feature(uint8_t addr, uint8_t data);
static int gd5f_block_erase(uint32_t block_addr);
/* ======================== 绉佹湁鍑芥暟瀹氫箟 ======================== */ /* ======================== SPI 原语定义 ======================== */
/* /*
* 函数功能:等待芯片操作完成(轮询 OIP 位) * 函数功能:等待芯片操作完成(轮询 OIP 位)
* 入口参数timeout_ms - 超时时间 uint32_t > 0 * 入口参数timeout_ms - 超时时间 uint32_t > 0
* 杩斿洖鍊硷細0 - 鎿嶄綔瀹屾垚锟?2 - 瓒呮椂 * 返回值0 - 操作完成,-2 - 超时
* 限定条件SPI 已初始化 * 限定条件SPI 已初始化
* 函数说明:循环读取状态寄存器直到 OIP 位清零或超时 * 函数说明:循环读取状态寄存器直到 OIP 位清零或超时
*/ */
static int gd5f_wait_busy(uint32_t timeout_ms) { int gd5f_wait_busy(uint32_t timeout_ms) {
uint8_t cmd = GD5F_CMD_GET_FEATURE; uint8_t cmd = GD5F_CMD_GET_FEATURE;
uint8_t addr = GD5F_REG_STATUS; uint8_t addr = GD5F_REG_STATUS;
uint8_t status = 0; uint8_t status = 0;
@@ -71,8 +95,8 @@ static int gd5f_wait_busy(uint32_t timeout_ms) {
* 入口参数:无 * 入口参数:无
* 返回值0 - 成功 * 返回值0 - 成功
* 限定条件SPI 已初始化 * 限定条件SPI 已初始化
* 鍑芥暟璇存槑锛欳S# 鎷変綆鍚庡彂锟?06h 鍛戒护鍐嶆媺锟? */ * 函数说明CS# 拉低后发<E5908E>?06h 命令再拉<E5868D>? */
static int gd5f_write_enable(void) { int gd5f_write_enable(void) {
uint8_t cmd = GD5F_CMD_WRITE_ENABLE; uint8_t cmd = GD5F_CMD_WRITE_ENABLE;
GD5F_CS_LOW(); GD5F_CS_LOW();
@@ -84,11 +108,11 @@ static int gd5f_write_enable(void) {
/* /*
* 函数功能:读取状态寄存器 * 函数功能:读取状态寄存器
* 鍏ュ彛鍙傛暟锛歱_status - 鐘舵€佸€艰緭鍑烘寚锟? uint8_t* 涓嶄负 NULL * 入口参数p_status - 状态值输出指<EFBFBD>? uint8_t* 不为 NULL
* 返回值0 - 成功 * 返回值0 - 成功
* 限定条件SPI 已初始化 * 限定条件SPI 已初始化
* 鍑芥暟璇存槑锛氬彂锟?0Fh + C0h 鍦板潃鍚庤<EFBFBD>锟?瀛楄妭鐘舵€侊拷? */ * 函数说明<EFBFBD>?0Fh + C0h 地址后读<EFBFBD>?字节状态<E78AB6>? */
static int gd5f_read_status(uint8_t *p_status) { int gd5f_read_status(uint8_t *p_status) {
uint8_t cmd = GD5F_CMD_GET_FEATURE; uint8_t cmd = GD5F_CMD_GET_FEATURE;
uint8_t addr = GD5F_REG_STATUS; uint8_t addr = GD5F_REG_STATUS;
@@ -102,11 +126,11 @@ static int gd5f_read_status(uint8_t *p_status) {
} }
/* /*
* 鍑芥暟鍔熻兘锛氶〉璇诲彇锛堝皢鏁版嵁浠庡瓨鍌ㄩ樀鍒楀姞杞藉埌鍐呴儴缂撳瓨锟? * 鍏ュ彛鍙傛暟锛歱age_addr - 椤靛湴鍧€ uint32_t * 函数功能页读取将数据从存储阵列加载到内部缓存<EFBFBD>? * 入口参数page_addr - 页地址 uint32_t
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉欢锛歋PI 宸插垵濮嬪寲 * 返回值0 - 成功<EFBC8C>?- 错误<E99499>? * 限定条件SPI 已初始化
* 鍑芥暟璇存槑锛氬彂锟?13h + 3瀛楄妭琛屽湴鍧€锛岀瓑锟?OIP 娓呴浂 * 函数说明<EFBFBD>?13h + 3字节行地址<EFBC8C>?OIP 清零
*/ */
static int gd5f_page_read(uint32_t page_addr) { int gd5f_page_read(uint32_t page_addr) {
uint8_t cmd[4] = {0}; uint8_t cmd[4] = {0};
cmd[0] = GD5F_CMD_PAGE_READ; cmd[0] = GD5F_CMD_PAGE_READ;
@@ -124,12 +148,12 @@ static int gd5f_page_read(uint32_t page_addr) {
/* /*
* 函数功能:从内部缓存读取数据 * 函数功能:从内部缓存读取数据
* 入口参数column - 列地址(页内偏移) uint16_t 0 - 2047 * 入口参数column - 列地址(页内偏移) uint16_t 0 - 2047
* p_buf - 鏁版嵁杈撳嚭缂撳啿锟? uint8_t* 涓嶄负 NULL * p_buf - 数据输出缓冲<EFBFBD>? uint8_t* 不为 NULL
* size - 璇诲彇瀛楄妭锟? size_t > 0 * size - 读取字节<EFBFBD>? size_t > 0
* 返回值0 - 成功 * 返回值0 - 成功
* 限定条件:必须先调用 gd5f_page_read 完成数据加载 * 限定条件:必须先调用 gd5f_page_read 完成数据加载
* 鍑芥暟璇存槑锛氬彂锟?0Bh + 2瀛楄妭鍒楀湴鍧€ +1瀛楄妭 dummy 鍚庤<E98D9A>鍙栨暟锟? */ * 函数说明<EFBFBD>?0Bh + 2字节列地址 +1字节 dummy 后读取数<E58F96>? */
static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
size_t size) { size_t size) {
uint8_t cmd[4] = {0}; uint8_t cmd[4] = {0};
@@ -140,7 +164,24 @@ static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
GD5F_CS_LOW(); GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT); HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
HAL_SPI_Receive(&hspi1, p_buf, size, GD5F_SPI_TIMEOUT);
if (size > GD5F_DMA_THRESHOLD) {
uint32_t tick_start = HAL_GetTick();
g_spi1_dma_done = 0U;
if (HAL_SPI_Receive_DMA(&hspi1, p_buf, (uint16_t)size) != HAL_OK) {
GD5F_CS_HIGH();
return GD5F_ERROR;
}
while (g_spi1_dma_done == 0U) {
if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) {
HAL_SPI_DMAStop(&hspi1);
GD5F_CS_HIGH();
return GD5F_BUSY_TIMEOUT;
}
}
} else {
HAL_SPI_Receive(&hspi1, p_buf, size, GD5F_SPI_TIMEOUT);
}
GD5F_CS_HIGH(); GD5F_CS_HIGH();
return GD5F_OK; return GD5F_OK;
@@ -150,29 +191,57 @@ static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
* 函数功能:页编程(将数据写入指定页) * 函数功能:页编程(将数据写入指定页)
* 入口参数page_addr - 页地址 uint32_t * 入口参数page_addr - 页地址 uint32_t
* column - 列地址 uint16_t 0 - 2047 * column - 列地址 uint16_t 0 - 2047
* p_buf - 鏁版嵁缂撳啿锟? const uint8_t* * p_buf - 数据缓冲<EFBFBD>? const uint8_t*
* size - 鍐欏叆瀛楄妭锟? size_t > 0 * size - 写入字节<EFBFBD>? size_t > 0
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉′欢锛氱洰鏍囧尯鍩熷凡鎿﹂櫎 * 返回值0 - 成功<EFBC8C>?- 错误<E99499>? * 限定条件:目标区域已擦除
* 鍑芥暟璇存槑锟?. 鍐欎娇锟?- 02h 鍔犺浇鏁版嵁 - 10h 鎵ц<EFBFBD>缂栫▼ - 绛夊緟瀹屾垚 * 函数说明<EFBFBD>?. 写使<E58699>?- 02h 加载数据 - 10h 执行编程 - 等待完成
* 2. 缂栫▼瀹屾垚鍚庢<EFBFBD>?P_FAIL ? */ * 2. 编程完成后检<EFBFBD>?P_FAIL <EFBFBD>? */
static int gd5f_page_program(uint32_t page_addr, uint16_t column, static int gd5f_page_program(uint32_t page_addr, uint16_t column,
const uint8_t *p_buf, size_t size) { const uint8_t *p_buf, size_t size) {
int ret = GD5F_OK; int ret;
uint8_t cmd[4];
uint8_t status = 0;
gd5f_write_enable(); gd5f_write_enable();
ret = gd5f_program_load(column, p_buf, size);
if (ret != GD5F_OK) return ret;
return gd5f_program_exec(page_addr);
}
int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size) {
uint8_t cmd[3];
cmd[0] = GD5F_CMD_PROGRAM_LOAD; cmd[0] = GD5F_CMD_PROGRAM_LOAD;
cmd[1] = (column >> 8) & 0xFF; cmd[1] = (uint8_t)(column >> 8);
cmd[2] = column & 0xFF; cmd[2] = (uint8_t)(column);
GD5F_CS_LOW(); GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT); HAL_SPI_Transmit(&hspi1, cmd, 3, GD5F_SPI_TIMEOUT);
HAL_SPI_Transmit(&hspi1, (uint8_t *)p_buf, size,
GD5F_SPI_TIMEOUT); if (size > GD5F_DMA_THRESHOLD) {
uint32_t tick_start = HAL_GetTick();
g_spi1_dma_done = 0U;
if (HAL_SPI_Transmit_DMA(&hspi1, (uint8_t *)p_buf, (uint16_t)size)
!= HAL_OK) {
GD5F_CS_HIGH();
return GD5F_ERROR;
}
while (g_spi1_dma_done == 0U) {
if ((HAL_GetTick() - tick_start) >= GD5F_SPI_TIMEOUT) {
HAL_SPI_DMAStop(&hspi1);
GD5F_CS_HIGH();
return GD5F_BUSY_TIMEOUT;
}
}
} else {
HAL_SPI_Transmit(&hspi1, (uint8_t *)p_buf, size, GD5F_SPI_TIMEOUT);
}
GD5F_CS_HIGH(); GD5F_CS_HIGH();
return GD5F_OK;
}
int gd5f_program_exec(uint32_t page_addr) {
int ret;
uint8_t cmd[4];
uint8_t status;
cmd[0] = GD5F_CMD_PROGRAM_EXEC; cmd[0] = GD5F_CMD_PROGRAM_EXEC;
cmd[1] = (page_addr >> 16) & 0xFF; cmd[1] = (page_addr >> 16) & 0xFF;
cmd[2] = (page_addr >> 8) & 0xFF; cmd[2] = (page_addr >> 8) & 0xFF;
@@ -183,24 +252,28 @@ static int gd5f_page_program(uint32_t page_addr, uint16_t column,
GD5F_CS_HIGH(); GD5F_CS_HIGH();
ret = gd5f_wait_busy(1000); ret = gd5f_wait_busy(1000);
if (ret != GD5F_OK) { if (ret != GD5F_OK) return ret;
return ret;
}
gd5f_read_status(&status); gd5f_read_status(&status);
if (status & GD5F_STATUS_P_FAIL) { if (status & GD5F_STATUS_P_FAIL) return GD5F_PROGRAM_FAIL;
return GD5F_PROGRAM_FAIL;
}
return GD5F_OK; return GD5F_OK;
} }
int gd5f_check_ecc(void) {
uint8_t status;
gd5f_read_status(&status);
uint8_t ecc = (status >> 4) & 0x03;
if (ecc == 0x02) return GD5F_ECC_ERROR;
return GD5F_OK;
}
/* /*
* 鍑芥暟鍔熻兘锛氳<EFBFBD>锟?Feature 瀵勫瓨锟? * 鍏ュ彛鍙傛暟锛歛ddr - 瀵勫瓨鍣ㄥ湴鍧€ uint8_t * 函数功能<EFBFBD>?Feature 寄存<E5AF84>? * 入口参数addr - 寄存器地址 uint8_t
* data - 写入数据 uint8_t * data - 写入数据 uint8_t
* 返回值0 - 成功 * 返回值0 - 成功
* 限定条件SPI 已初始化 * 限定条件SPI 已初始化
* 鍑芥暟璇存槑锛氬厛鍐欎娇鑳斤紝鍙戯拷?1Fh + 鍦板潃 + 鏁版嵁锛岀瓑寰呮搷浣滃畬锟? */ * 函数说明先写使能<EFBFBD>?1Fh + 地址 + 数据等待操作完<E4BD9C>? */
static int gd5f_set_feature(uint8_t addr, uint8_t data) { static int gd5f_set_feature(uint8_t addr, uint8_t data) {
uint8_t cmd[3]; uint8_t cmd[3];
@@ -220,11 +293,11 @@ static int gd5f_set_feature(uint8_t addr, uint8_t data) {
} }
/* /*
* 鍑芥暟鍔熻兘锛氬潡鎿﹂櫎锛堟摝锟?28KB鍧楋級 * 函数功能块擦除<EFBFBD>?28KB块)
* 鍏ュ彛鍙傛暟锛歜lock_addr - 鍧楃紪锟? uint32_t 0 - 2047 * 入口参数block_addr - 块编<EFBFBD>? uint32_t 0 - 2047
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉欢锛歡d5f2gq5ue_init() 宸茶皟锟? * 鍑芥暟璇存槑锟?. 鍐欎娇锟?- D8h + 3瀛楄妭瀛楄妭鍦板潃 - 绛夊緟瀹屾垚 * 返回值0 - 成功<EFBC8C>?- 错误<E99499>? * 限定条件gd5f2gq5ue_init() 已调<E5B7B2>? * 函数说明<E8AFB4>?. 写使<E58699>?- D8h + 3字节字节地址 - 等待完成
* 2. 鎿﹂櫎瀹屾垚鍚庢<EFBFBD>?E_FAIL ? */ * 2. 擦除完成后检<EFBFBD>?E_FAIL <EFBFBD>? */
static int gd5f_block_erase(uint32_t block_addr) { int gd5f_block_erase(uint32_t block_addr) {
int ret = GD5F_OK; int ret = GD5F_OK;
uint8_t cmd[4]; uint8_t cmd[4];
uint8_t status = 0; uint8_t status = 0;
@@ -254,14 +327,49 @@ static int gd5f_block_erase(uint32_t block_addr) {
return GD5F_OK; return GD5F_OK;
} }
/*
* 函数功能:扫描所有块构建 BBT在 ECC 使能前调用)
* 入口参数:无
* 返回值0 - 成功
* 限定条件SPI 已初始化ECC 尚未使能
* 函数说明读取每个块最后一页page 63的 spare byte 0
* 若不为 0xFF 则为出厂坏块
*/
static int gd5f_bbt_scan(void) {
uint32_t block;
uint8_t spare[8];
uint32_t bad_count = 0;
memset(s_bbt, 0, GD5F_BBT_SIZE);
for (block = 0; block < GD5F_TOTAL_BLOCKS; block++) {
uint32_t page = (block << 6) | 63;
gd5f_page_read(page);
gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, 1);
if (spare[0] != 0xFF) {
s_bbt[block >> 3] |= (1 << (block & 7));
bad_count++;
}
}
DBG_INFO("BBT scan: %lu bad blocks found", bad_count);
return GD5F_OK;
}
/* ======================== 公共函数定义 ======================== */ /* ======================== 公共函数定义 ======================== */
/* /*
* 函数功能:初始化 GD5F2GQ5UE * 函数功能:初始化 GD5F2GQ5UE
* 入口参数:无 * 入口参数:无
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉欢锛歋PI1 鍜岀浉锟?GPIO 宸茬敱 CubeMX 鍒濆<E98D92>鍖栧畬锟? * 鍑芥暟璇存槑锟?. 鍙戦€佸<E282AC>浣嶅懡浠ゅ苟绛夊緟瀹屾垚 * 返回值0 - 成功,其他 - 错误码
* 2. 璇诲彇鑺<E5BD87>墖 ID 骞舵牎锟? * 3. 浣胯兘鍐呴儴 ECC (B0h bit4) * 限定条件SPI1 和相关 GPIO 已由 CubeMX 初始化完成
* 4. 瑙i櫎鎵€鏈夊潡淇濇姢 (A0h = 0x00) * 函数说明1. 发送复位命令并等待完成
* 2. 读取芯片 ID 并校验
* 3. 扫描出厂坏块构建 BBTECC 使能前)
* 4. 使能内部 ECC (B0h bit4)
* 5. 解除所有块保护 (A0h = 0x00)
*/ */
int gd5f2gq5ue_init(void) { int gd5f2gq5ue_init(void) {
int ret = GD5F_OK; int ret = GD5F_OK;
@@ -298,6 +406,9 @@ int gd5f2gq5ue_init(void) {
return GD5F_ID_MISMATCH; return GD5F_ID_MISMATCH;
} }
DBG_INFO("Scanning bad blocks...");
gd5f_bbt_scan();
DBG_INFO("Enabling ECC..."); DBG_INFO("Enabling ECC...");
gd5f_set_feature(0xB0, 0x10); gd5f_set_feature(0xB0, 0x10);
gd5f_wait_busy(100); gd5f_wait_busy(100);
@@ -311,11 +422,11 @@ int gd5f2gq5ue_init(void) {
} }
/* /*
* 鍑芥暟鍔熻兘锛氳<EFBFBD>鍙栬姱锟?ID锛圡ID + DID? * 鍏ュ彛鍙傛暟锛歮id - 鍒堕€犲晢 ID 杈撳嚭鎸囬拡 uint8_t* 涓嶄负 NULL * 函数功能读取芯<EFBFBD>?IDMID + DID<EFBFBD>? * 入口参数mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
* did - 设备 ID 输出指针 uint8_t* 不为 NULL * did - 设备 ID 输出指针 uint8_t* 不为 NULL
* 返回值0 - 成功 * 返回值0 - 成功
* 限定条件SPI 已初始化 * 限定条件SPI 已初始化
* 鍑芥暟璇存槑锛氬彂锟?9Fh 鍛戒护鍚庢帴锟?锟?dummy + MID + DID * 函数说明<EFBFBD>?9Fh 命令后接<E5908E>?<3F>?dummy + MID + DID
*/ */
int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) { int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) {
uint8_t cmd = GD5F_CMD_READ_ID; uint8_t cmd = GD5F_CMD_READ_ID;
@@ -334,10 +445,10 @@ int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) {
/* /*
* 函数功能:从 NAND 读取数据(支持跨页) * 函数功能:从 NAND 读取数据(支持跨页)
* 鍏ュ彛鍙傛暟锛歰ffset - 璧峰<E792A7>瀛楄妭鍋忕Щ long 0 ~ 鎬诲<EFBFBD>?1 * 入口参数offset - 起始字节偏移 long 0 ~ 总容<EFBFBD>?1
* p_buf - 鏁版嵁缂撳啿锟? uint8_t* 涓嶄负 NULL * p_buf - 数据缓冲<EFBFBD>? uint8_t* 不为 NULL
* size - 璇诲彇瀛楄妭锟? size_t > 0 * size - 读取字节<EFBFBD>? size_t > 0
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉欢锛歡d5f2gq5ue_init() 宸叉垚鍔熻皟锟? * 鍑芥暟璇存槑锛氳嚜鍔ㄥ<E98D94>鐞嗚法椤佃<E6A4A4>? */ * 返回值0 - 成功<EFBC8C>?- 错误<E99499>? * 限定条件gd5f2gq5ue_init() 已成功调<E58A9F>? * 函数说明自动处理跨页读<E9A1B5>? */
int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) { int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) {
int ret = GD5F_OK; int ret = GD5F_OK;
@@ -370,10 +481,10 @@ int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) {
/* /*
* 函数功能:向 NAND 写入数据(支持跨页) * 函数功能:向 NAND 写入数据(支持跨页)
* 鍏ュ彛鍙傛暟锛歰ffset - 璧峰<E792A7>瀛楄妭鍋忕Щ long 0 ~ 鎬诲<EFBFBD>?1 * 入口参数offset - 起始字节偏移 long 0 ~ 总容<EFBFBD>?1
* p_buf - 鏁版嵁缂撳啿锟? const uint8_t* 涓嶄负 NULL * p_buf - 数据缓冲<EFBFBD>? const uint8_t* 不为 NULL
* size - 鍐欏叆瀛楄妭锟? size_t > 0 * size - 写入字节<EFBFBD>? size_t > 0
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉欢锛歡d5f2gq5ue_init() 宸叉垚鍔熻皟鐢<E79A9F>紝鐩<E7B49D>爣鍖哄煙宸叉摝锟? * 鍑芥暟璇存槑锛氳嚜鍔ㄥ<E98D94>鐞嗚法椤靛啓锟? */ * 返回值0 - 成功<EFBC8C>?- 错误<E99499>? * 限定条件gd5f2gq5ue_init() 已成功调用目标区域已擦<E5B7B2>? * 函数说明自动处理跨页写<E9A1B5>? */
int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) { int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) {
int ret = GD5F_OK; int ret = GD5F_OK;
@@ -400,7 +511,7 @@ int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) {
} }
/* /*
* 鍑芥暟鍔熻兘锛氭摝闄ゅ潡锛堟寜鍧楁摝闄わ紝鏈€灏忓崟锟?128KB? * 鍏ュ彛鍙傛暟锛歰ffset - 璧峰<E792A7>瀛楄妭鍋忕Щ long 蹇呴』鍧楀<EFBFBD>? * size - 鎿﹂櫎瀛楄妭锟? size_t 蹇呴』鍧楀ぇ灏忔暣鏁帮拷? * 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉欢锛歡d5f2gq5ue_init() 宸叉垚鍔熻皟锟? * 鍑芥暟璇存槑锛氭摝闄ゆ搷浣滀互鍧椾负鍗曚綅 * 函数功能擦除块按块擦除最小单<EFBFBD>?128KB<EFBFBD>? * 入口参数offset - 起始字节偏移 long 必须块对<EFBFBD>? * size - 擦除字节<EFBFBD>? size_t 必须块大小整数<EFBFBD>? * 返回值0 - 成功<EFBC8C>?- 错误<E99499>? * 限定条件gd5f2gq5ue_init() 已成功调<E58A9F>? * 函数说明:擦除操作以块为单位
*/ */
int gd5f2gq5ue_erase(long offset, size_t size) { int gd5f2gq5ue_erase(long offset, size_t size) {
int ret = GD5F_OK; int ret = GD5F_OK;
@@ -428,10 +539,10 @@ int gd5f2gq5ue_erase(long offset, size_t size) {
} }
/* /*
* 鍑芥暟鍔熻兘锛氬<EFBFBD>浣嶈姱锟? * 鍏ュ彛鍙傛暟锛氭棤 * 函数功能复位芯<EFBFBD>? * 入口参数:无
* 返回值0 - 成功 * 返回值0 - 成功
* 限定条件SPI 已初始化 * 限定条件SPI 已初始化
* 鍑芥暟璇存槑锛氬彂锟?FFh 澶嶄綅鍛戒护鍚庣瓑锟?5ms * 函数说明<EFBFBD>?FFh 复位命令后等<E5908E>?5ms
*/ */
int gd5f2gq5ue_reset(void) { int gd5f2gq5ue_reset(void) {
uint8_t cmd = GD5F_CMD_RESET; uint8_t cmd = GD5F_CMD_RESET;
@@ -445,3 +556,31 @@ int gd5f2gq5ue_reset(void) {
return GD5F_OK; return GD5F_OK;
} }
/*
* 函数功能:查询块是否坏块
* 入口参数block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值0 - 好块1 - 坏块
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明:通过 BBT 查询
*/
int gd5f2gq5ue_is_block_bad(uint32_t block) {
if (block >= GD5F_TOTAL_BLOCKS) {
return 1;
}
return (s_bbt[block >> 3] >> (block & 7)) & 1;
}
/*
* 函数功能:标记块为坏块
* 入口参数block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值:无
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明:更新 BBT
*/
void gd5f2gq5ue_mark_block_bad(uint32_t block) {
if (block >= GD5F_TOTAL_BLOCKS) {
return;
}
s_bbt[block >> 3] |= (1 << (block & 7));
}

View File

@@ -144,6 +144,36 @@ int gd5f2gq5ue_erase(long offset, size_t size);
*/ */
int gd5f2gq5ue_reset(void); int gd5f2gq5ue_reset(void);
/*
* 函数功能:查询块是否坏块
* 入口参数block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值0 - 好块1 - 坏块
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明:通过 BBTBad Block Table查询BBT 在 init 阶段 ECC 使能前扫描构建
*/
int gd5f2gq5ue_is_block_bad(uint32_t block);
/*
* 函数功能:标记块为坏块
* 入口参数block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
* 返回值:无
* 限定条件gd5f2gq5ue_init() 已成功调用
* 函数说明:更新 BBT并尝试物理标记该块最后一页 spare byte 0 为 0x00
*/
void gd5f2gq5ue_mark_block_bad(uint32_t block);
/* ==================== SPI 原语FTL 共享) ==================== */
int gd5f_wait_busy(uint32_t timeout_ms);
int gd5f_write_enable(void);
int gd5f_read_status(uint8_t *p_status);
int gd5f_page_read(uint32_t page_addr);
int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf, size_t size);
int gd5f_program_load(uint16_t column, const uint8_t *p_buf, size_t size);
int gd5f_program_exec(uint32_t page_addr);
int gd5f_block_erase(uint32_t block_addr);
int gd5f_check_ecc(void);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -0,0 +1,386 @@
/*
* 模块名称NAND FTL 适配层dhara + FatFS diskio 胶水)
* 模块功能:实现 dhara NAND HAL 和 FatFS 磁盘 I/O连接 GD5F2GQ5UE 底层驱动
* 适用平台STM32F407ZGT6
* 作者:王建锋
* 创建日期2026-07-20
* 修改记录:
* 2026-07-20 王建锋 创建初始版本
*/
/* 头文件包含区 */
#include <string.h>
#include "gd5f2gq5ue.h"
#include "ff.h"
#include "diskio.h"
#include "nand.h"
#include "map.h"
/* 统一分区配置 */
#include "fal_cfg.h"
/* 调试输出配置 */
#define DBG_TAG "[NAND_FTL]"
#include "dbg_log.h"
/* 私有宏定义区 */
#define FTL_PAGE_SIZE GD5F_PAGE_SIZE
#define FTL_SECTORS_PER_PAGE (FTL_PAGE_SIZE / 512)
#define FTL_START_BLOCK (FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE)
#define FTL_NUM_BLOCKS (GD5F_TOTAL_BLOCKS - FTL_START_BLOCK)
#define FTL_START_PAGE (FTL_START_BLOCK * GD5F_PAGES_PER_BLOCK)
/* dhara 逻辑地址到物理地址转换 */
static inline uint32_t ftl_phy_block(dhara_block_t b) { return b + FTL_START_BLOCK; }
static inline uint32_t ftl_phy_page(dhara_page_t p) { return p + FTL_START_PAGE; }
/* ======================== 私有变量 ======================== */
/* dhara 核心数据结构 */
static struct dhara_nand s_nand;
static struct dhara_map s_map;
static uint8_t s_page_buf[FTL_PAGE_SIZE]; /* dhara 内部页缓冲区 */
/* 页面缓存FatFS 读写缓存) */
static uint8_t s_cache_buf[FTL_PAGE_SIZE];
static dhara_sector_t s_cached_lpn;
static uint8_t s_cache_dirty;
/* 拷贝临时缓冲区 */
static uint8_t s_copy_buf[FTL_PAGE_SIZE];
/* 初始化状态 */
static uint8_t s_initialized;
/* ======================== 私有函数声明 ======================== */
static int ftl_flush_cache(void);
static int ftl_read_page(dhara_sector_t lpn);
/* SPI 原语从 gd5f2gq5ue.h 获取 */
/* ======================== 页面缓存管理 ======================== */
static int ftl_flush_cache(void) {
if (!s_cache_dirty) {
return GD5F_OK;
}
dhara_error_t err;
if (dhara_map_write(&s_map, s_cached_lpn, s_cache_buf, &err) < 0) {
DBG_ERROR("Cache flush failed: LPN=%lu err=%d", s_cached_lpn, err);
return GD5F_ERROR;
}
s_cache_dirty = 0;
return GD5F_OK;
}
static int ftl_read_page(dhara_sector_t lpn) {
dhara_error_t err;
if (dhara_map_read(&s_map, lpn, s_cache_buf, &err) < 0) {
DBG_ERROR("Page read failed: LPN=%lu err=%d", lpn, err);
return GD5F_ERROR;
}
s_cached_lpn = lpn;
s_cache_dirty = 0;
return GD5F_OK;
}
/* ======================== dhara NAND HAL ======================== */
int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b) {
(void)n;
return gd5f2gq5ue_is_block_bad(ftl_phy_block(b));
}
void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b) {
(void)n;
gd5f2gq5ue_mark_block_bad(ftl_phy_block(b));
}
int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b, dhara_error_t *err) {
(void)n;
int ret = gd5f_block_erase(ftl_phy_block(b));
if (ret == GD5F_ERASE_FAIL || ret != GD5F_OK) {
dhara_nand_mark_bad(n, b);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
return 0;
}
int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p, const uint8_t *data, dhara_error_t *err) {
int ret;
uint32_t phy_pg = ftl_phy_page(p);
gd5f_write_enable();
ret = gd5f_program_load(0, data, FTL_PAGE_SIZE);
if (ret != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
ret = gd5f_program_exec(phy_pg);
if (ret == GD5F_PROGRAM_FAIL || ret != GD5F_OK) {
dhara_nand_mark_bad(n, p >> n->log2_ppb);
dhara_set_error(err, DHARA_E_BAD_BLOCK);
return -1;
}
return 0;
}
int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p) {
(void)n;
uint32_t i;
uint8_t buf[64];
uint32_t phy_pg = ftl_phy_page(p);
if (gd5f_page_read(phy_pg) != GD5F_OK) {
return 0;
}
gd5f_read_from_cache(0, buf, sizeof(buf));
for (i = 0; i < sizeof(buf); i++) {
if (buf[i] != 0xFF) {
return 0;
}
}
return 1;
}
int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p, size_t offset, size_t length, uint8_t *data, dhara_error_t *err) {
(void)n;
uint32_t phy_pg = ftl_phy_page(p);
if (gd5f_page_read(phy_pg) != GD5F_OK) {
dhara_set_error(err, DHARA_E_ECC);
return -1;
}
if (gd5f_check_ecc() != GD5F_OK) {
dhara_set_error(err, DHARA_E_ECC);
return -1;
}
gd5f_read_from_cache((uint16_t)offset, data, length);
return 0;
}
int dhara_nand_copy(const struct dhara_nand *n, dhara_page_t src, dhara_page_t dst, dhara_error_t *err) {
(void)n;
if (dhara_nand_read(n, src, 0, FTL_PAGE_SIZE, s_copy_buf, err) < 0) {
return -1;
}
if (dhara_nand_prog(n, dst, s_copy_buf, err) < 0) {
return -1;
}
return 0;
}
/* ======================== FatFS 磁盘 I/O ======================== */
DSTATUS disk_initialize(BYTE pdrv) {
if (pdrv != 0) {
return STA_NOINIT;
}
if (s_initialized) {
return 0;
}
DBG_INFO("Initializing NAND FTL...");
s_nand.log2_page_size = 11;
s_nand.log2_ppb = 6;
s_nand.num_blocks = FTL_NUM_BLOCKS;
dhara_map_init(&s_map, &s_nand, s_page_buf, 4);
memset(s_cache_buf, 0, FTL_PAGE_SIZE);
s_cached_lpn = 0;
s_cache_dirty = 0;
{
dhara_error_t err;
if (dhara_map_resume(&s_map, &err) < 0) {
DBG_INFO("No valid map found, creating fresh (err=%d)", err);
dhara_map_clear(&s_map);
} else {
DBG_INFO("Map resumed: %lu/%lu sectors used",
dhara_map_size(&s_map), dhara_map_capacity(&s_map));
}
}
s_initialized = 1;
DBG_INFO("NAND FTL ready (capacity: %lu pages = %lu MB)",
dhara_map_capacity(&s_map),
(dhara_map_capacity(&s_map) * FTL_PAGE_SIZE) / (1024 * 1024));
return 0;
}
DSTATUS disk_status(BYTE pdrv) {
if (pdrv != 0) {
return STA_NOINIT;
}
if (!s_initialized) {
return STA_NOINIT;
}
return 0;
}
DRESULT disk_read(BYTE pdrv, BYTE *buff, LBA_t sector, UINT count) {
if (pdrv != 0 || !buff) {
return RES_PARERR;
}
if (!s_initialized && disk_initialize(pdrv) != 0) {
return RES_NOTRDY;
}
while (count > 0) {
dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE);
uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512;
uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE);
if (batch > count) {
batch = count;
}
if (lpn != s_cached_lpn) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
if (ftl_read_page(lpn) != GD5F_OK) {
return RES_ERROR;
}
}
memcpy(buff, s_cache_buf + offset, batch * 512);
buff += batch * 512;
sector += batch;
count -= (UINT)batch;
}
return RES_OK;
}
DRESULT disk_write(BYTE pdrv, const BYTE *buff, LBA_t sector, UINT count) {
if (pdrv != 0 || !buff) {
return RES_PARERR;
}
if (!s_initialized && disk_initialize(pdrv) != 0) {
return RES_NOTRDY;
}
while (count > 0) {
dhara_sector_t lpn = (dhara_sector_t)(sector / FTL_SECTORS_PER_PAGE);
uint32_t offset = (uint32_t)(sector % FTL_SECTORS_PER_PAGE) * 512;
uint32_t batch = FTL_SECTORS_PER_PAGE - (sector % FTL_SECTORS_PER_PAGE);
if (batch > count) {
batch = count;
}
if (lpn != s_cached_lpn) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
if (ftl_read_page(lpn) != GD5F_OK) {
return RES_ERROR;
}
}
memcpy(s_cache_buf + offset, buff, batch * 512);
s_cache_dirty = 1;
if (offset == 0 && batch == FTL_SECTORS_PER_PAGE) {
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
}
buff += batch * 512;
sector += batch;
count -= (UINT)batch;
}
return RES_OK;
}
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void *buff) {
if (pdrv != 0) {
return RES_PARERR;
}
if (!s_initialized) {
return RES_NOTRDY;
}
switch (cmd) {
case CTRL_SYNC:
if (ftl_flush_cache() != GD5F_OK) {
return RES_ERROR;
}
{
dhara_error_t err;
if (dhara_map_sync(&s_map, &err) < 0) {
DBG_ERROR("Map sync failed: %d", err);
return RES_ERROR;
}
}
return RES_OK;
case GET_SECTOR_COUNT: {
LBA_t *p_sectors = (LBA_t *)buff;
dhara_sector_t pages = dhara_map_capacity(&s_map);
*p_sectors = pages * FTL_SECTORS_PER_PAGE;
return RES_OK;
}
case GET_SECTOR_SIZE:
*(WORD *)buff = 512;
return RES_OK;
case GET_BLOCK_SIZE:
*(DWORD *)buff = 1;
return RES_OK;
default:
return RES_PARERR;
}
}
/* ======================== 公共函数定义 ======================== */
int nand_ftl_format(void) {
dhara_error_t err;
if (!s_initialized) {
if (disk_initialize(0) != 0) {
return GD5F_ERROR;
}
}
if (ftl_flush_cache() != GD5F_OK) {
return GD5F_ERROR;
}
dhara_map_clear(&s_map);
if (dhara_map_sync(&s_map, &err) < 0) {
DBG_ERROR("Map sync after clear failed: %d", err);
return GD5F_ERROR;
}
s_cache_dirty = 0;
DBG_INFO("NAND FTL formatted, capacity: %lu pages", dhara_map_capacity(&s_map));
return GD5F_OK;
}

View File

@@ -0,0 +1,17 @@
#ifndef __NAND_FTL_H
#define __NAND_FTL_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
int nand_ftl_init(void);
int nand_ftl_format(void);
#ifdef __cplusplus
}
#endif
#endif

View File

@@ -98,6 +98,13 @@ extern "C" {
#define NET_POLL_DELAY_MS 1 /* 非阻塞轮询延时 */ #define NET_POLL_DELAY_MS 1 /* 非阻塞轮询延时 */
#endif #endif
/*
* 消息队列配置
*/
#ifndef NET_MSG_QUEUE_LENGTH
#define NET_MSG_QUEUE_LENGTH 8 /* 消息队列深度 */
#endif
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1,24 +1,50 @@
/* /*
* 模块名称Network Select Implementation * 模块名称Network Select Implementation
* 模块功能I/O 多路复用实现,包<EFBFBD>?select <EFBFBD>?poll 模式<EFBFBD>? * 用于同时监控多个 Socket 的读写事<EFBFBD>? * 适用平台STM32F4 系列CH395F 以太网芯片) * 模块功能I/O 多路复用实现,包select poll 两种模式,用于同时监控多个 Socket 的读写事
* 作者王建<EFBFBD>? * 创建日期<E697A5>?026-07-18 * 适用平台STM32F407ZGTx + CH395F 以太网芯片
* 修改记录<EFBFBD>? */ * 作者:王建锋
* 创建日期2026-07-18
* 修改记录:
* 2026-07-18 王建锋 创建初始版本
*/
#include <string.h>
#include <stdio.h>
#include "cmsis_os.h" /* CMSIS-RTOS2 API: osDelay() */
#include "FreeRTOS.h"
#include "task.h"
#include "net_select.h" #include "net_select.h"
#include "net_socket.h" #include "net_socket.h"
#include "ch395f.h" #include "ch395f.h"
#include "main.h"
/* /*
* 私有函数声明<EFBFBD>? */ * 私有函数声明
*/
static int check_socket_readable(int sockfd); static int check_socket_readable(int sockfd);
static int check_socket_writable(int sockfd); static int check_socket_writable(int sockfd);
static int check_socket_error(int sockfd); static int check_socket_error(int sockfd);
/* /*
* 公共函数实现<EFBFBD>? */ * 公共函数实现
*/
/* /*
* 函数功能I/O 多路复用select 模式<EFBFBD>? */ * 函数功能I/O 多路复用select 模式),等待多个 Socket 的读写事件就绪
* 入口参数nfds - 最大文件描述符数 +1 int > 0
* readfds - 读事件集合指针 net_fd_set *(可为 NULL
* writefds - 写事件集合指针 net_fd_set *(可为 NULL
* exceptfds- 异常事件集合指针 net_fd_set *(可为 NULL
* timeout - 超时时间结构体指针 net_timeval *NULL=无限等待)
* 返回值:就绪的 Socket 数量 int >= 0 || -1
* -1 - 参数无效 int
* 限定条件net_init() + MX_FREERTOS_Init() 已执行;必须在 netTask 上下文中调用
* 函数说明:轮询所有 Socket检查读/写/异常事件是否就绪。阻塞等待直到有事件或超时
*/
int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
net_fd_set *exceptfds, net_timeval *timeout) { net_fd_set *exceptfds, net_timeval *timeout) {
uint32_t start_tick; uint32_t start_tick;
@@ -89,8 +115,8 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
} }
} }
/* 短暂延时避免忙等 */ /* 让出 CPUFreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */
HAL_Delay(NET_POLL_DELAY_MS); osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */
} }
/* 复制结果 */ /* 复制结果 */
@@ -108,7 +134,15 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
} }
/* /*
* 函数功能I/O 多路复用poll 模式<EFBFBD>? */ * 函数功能I/O 多路复用poll 模式),等待多个 Socket 的读写事件就绪
* 入口参数fds - pollfd 数组指针 net_pollfd *
* nfds - 数组元素数量 int > 0
* timeout - 超时时间(毫秒) int >= 0
* 返回值:就绪的 fd 数量 int >= 0 || -1
* -1 - 参数无效 int
* 限定条件net_init() + MX_FREERTOS_Init() 已执行;必须在 netTask 上下文中调用
* 函数说明:轮询所有 Socket检查读/写/错误/挂起事件是否就绪。阻塞等待直到有事件或超时
*/
int net_poll_events(net_pollfd *fds, int nfds, int timeout) { int net_poll_events(net_pollfd *fds, int nfds, int timeout) {
uint32_t start_tick; uint32_t start_tick;
int ready = 0; int ready = 0;
@@ -185,7 +219,8 @@ int net_poll_events(net_pollfd *fds, int nfds, int timeout) {
} }
} }
HAL_Delay(NET_POLL_DELAY_MS); /* 让出 CPUFreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */
osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */
} }
return ready; return ready;

View File

@@ -1,12 +1,21 @@
/* /*
* 模块名称Network Socket Core * 模块名称Network Socket Core
* 模块功能BSD Socket API 核心实现,包<EFBFBD>?Socket 管理、状态机<EFBFBD>? * 事件处理、数据收发等功能,底层调<EFBFBD>?CH395F 驱动 * 模块功能BSD Socket API 核心实现,包Socket 管理、状态机、消息队列(线程安全)、事件处理、数据收发等功能,底层调CH395F 驱动
* 适用平台STM32F4 系列(CH395F 以太网芯片 * 适用平台STM32F407ZGTx + CH395F 以太网芯片
* 作者:王建<EFBFBD>? * 创建日期<E697A5>?026-07-18 * 作者:王建
* 修改记录<EFBFBD>? */ * 创建日期2026-07-18
* 修改记录:
* 2026-07-18 王建锋 创建初始版本
* 2026-07-19 王建锋 添加消息队列线程安全机制,替换 xQueueCreate 为 CubeMX osMessageQueue
*/
#include <string.h> #include <string.h>
#include <stdio.h> #include <stdio.h>
#include "FreeRTOS.h"
#include "task.h"
#include "cmsis_os.h"
#include "net_socket.h" #include "net_socket.h"
#include "ch395f.h" #include "ch395f.h"
#include "main.h" #include "main.h"
@@ -20,21 +29,32 @@
/* /*
* 私有宏定义区 * 私有宏定义区
*/ */
#define NET_PHY_CHECK_INTERVAL_MS 5000U /* PHY 状态定期检查间隔ms */
/* /*
* 私有变量<EFBFBD>? */ * 全局变量定义区 - 尽量避免使用全局变量
static net_sock_t s_net_socks[NET_MAX_SOCKETS]; /* Socket 控制块数<E59D97>?*/
static int s_net_errno = 0; /* 最后错误码 */
static uint8_t s_net_initialized = 0; /* 初始化标<E58C96>?*/
/*
* 私有宏定义区 - 补充
*/ */
#define NET_PHY_CHECK_INTERVAL_MS 5000U /* PHY 状态定期检查间隔ms<6D>?*/ static net_sock_t s_net_socks[NET_MAX_SOCKETS]; /* Socket 控制块数组 */
static int s_net_errno = 0; /* 最后错误码 */
static uint8_t s_net_initialized = 0; /* 初始化标志 */
/* /*
* 私有函数声明<EFBFBD>? */ * 消息队列 - 线程安全核心
* 外部任务通过 net_send/net_recv/net_close 等 API 将请求放入队列,
* netTask 调用 net_process_messages() 串行处理所有 CH395F 操作
* 由 CubeMX 在 MX_FREERTOS_Init()osKernelInitialize 之后)中创建
*/
extern osMessageQueueId_t netMsgQueueHandle; /* CubeMX 生成的消息队列句柄 */
/*
* 私有函数声明区
*/
static int alloc_socket(void); static int alloc_socket(void);
static void handle_connect_event(net_sock_t *p_sock); static void handle_connect_event(net_sock_t *p_sock);
static void handle_disconnect_event(net_sock_t *p_sock); static void handle_disconnect_event(net_sock_t *p_sock);
@@ -43,31 +63,50 @@ static void handle_send_event(net_sock_t *p_sock);
static void handle_timeout_event(net_sock_t *p_sock); static void handle_timeout_event(net_sock_t *p_sock);
static void fire_event(net_sock_t *p_sock, net_event_t event); static void fire_event(net_sock_t *p_sock, net_event_t event);
static void parse_ip_string(const char *str, uint8_t *ip_arr); static void parse_ip_string(const char *str, uint8_t *ip_arr);
static int net_send_locked(net_sock_t *p_sock, const void *buf, int len);
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags);
static int net_close_locked(net_sock_t *p_sock);
/* /*
* 公共函数实现<EFBFBD>? */ * 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
* 入口参数ip - IP 地址字符串 "192.168.1.100"NULL 使用 DHCP 自动获取 const char *
/* * mask - 子网掩码字符串 "255.255.255.0"NULL 使用默认值 const char *
* 函数功能网络子系统初始<EFBFBD>? */ * gateway - 网关地址字符串 "192.168.1.1"NULL 使用默认值 const char *
* 返回值0 - 初始化成功 int
* -1 - 初始化失败(芯片检测/复位/协议栈初始化失败) int
* 限定条件SPI2 已正确初始化netTask 尚未启动
* 函数说明1. 检查 CH395F 芯片是否存在并复位
* 2. 启用 TCP Server 多连接模式bit1=1设置 TCP MSS、重传参数、KeepAlive 参数
* 3. 配置 IP/网关/子网掩码,或启用 DHCP
* 4. 初始化 CH395F 协议栈并使能 PING 响应
*/
int net_init(const char *ip, const char *mask, const char *gateway) { int net_init(const char *ip, const char *mask, const char *gateway) {
ch395f_status_t ret; ch395f_status_t ret;
/* 清零 Socket 控制<E68EA7>?*/ DBG_INFO("net_init: start");
/* 清零 Socket 控制块 */
memset(s_net_socks, 0, sizeof(s_net_socks)); memset(s_net_socks, 0, sizeof(s_net_socks));
s_net_errno = 0; s_net_errno = 0;
s_net_initialized = 0; s_net_initialized = 0;
/* 检<>?CH395F 芯片 */ DBG_INFO("net_init: queue=%p", (void *)netMsgQueueHandle);
DBG_INFO("net_init: check_exist...");
/* 检查 CH395F 芯片 */
if (ch395f_check_exist() != CH395F_STATUS_OK) { if (ch395f_check_exist() != CH395F_STATUS_OK) {
DBG_ERROR("net_init: ch395f_check_exist FAILED");
s_net_errno = NET_ERR; s_net_errno = NET_ERR;
return -1; return -1;
} }
DBG_INFO("net_init: reset...");
/* 复位芯片 */ /* 复位芯片 */
ch395f_reset(); ch395f_reset();
DBG_INFO("net_init: set_fun_para...");
/* 启用 TCP Server 多连接模式bit1=1<EFBFBD>? * Socket 0 专职监听Socket 1~7 自动分配给新连接 */ /* 启用 TCP Server 多连接模式bit1=1 */
ch395f_set_fun_para(0x02); ch395f_set_fun_para(0x02);
DBG_INFO("net_init: set_tcp_mss...");
/* 设置 TCP MSS */ /* 设置 TCP MSS */
ch395f_set_tcp_mss(1460); ch395f_set_tcp_mss(1460);
@@ -115,7 +154,14 @@ int net_init(const char *ip, const char *mask, const char *gateway) {
} }
/* /*
* 函数功能:轮询<EFBFBD>?Socket 状<>? */ * 函数功能:轮询 CH395F 中断状态,更新 Socket 连接状态并处理事件
* 返回值:本次轮询中发生事件的 Socket 数量 int >= 0
* 限定条件s_net_initialized == 1必须在 netTask 上下文中调用
* 函数说明1. GPIO 轮询 CH395F INT# 引脚电平(低电平有效),无需 EXTI 中断
* 2. 读取 GET_GLOB_INT_STATUS_ALL2 字节版)获取所有 Socket 的中断状态
* 3. 处理连接/断开/数据接收/发送完成/超时等事件,更新 Socket 状态机
* 4. RECV 中断为电平触发,每次 net_poll() 只处理一批中断,避免主循环饿死
*/
int net_poll(void) { int net_poll(void) {
int event_count = 0; int event_count = 0;
int i; int i;
@@ -126,7 +172,9 @@ int net_poll(void) {
} }
/* /*
* GPIO 轮询模式:仅<EFBFBD>?INT# 有效(低电平)时读取中断状<EFBFBD>? * 读取 GET_GLOB_INT_STATUS_ALL <20>?CH395F 拉高 INT#若仍有中断则再次拉<E6ACA1>? */ * GPIO 轮询模式:仅INT# 有效(低电平)时读取中断状态。
* 读取 GET_GLOB_INT_STATUS_ALLCH395F 拉高 INT#;若仍有中断则再次拉低。
*/
do do
{ {
if (HAL_GPIO_ReadPin(CH395_INT_GPIO_Port, CH395_INT_Pin) == GPIO_PIN_SET) { if (HAL_GPIO_ReadPin(CH395_INT_GPIO_Port, CH395_INT_Pin) == GPIO_PIN_SET) {
@@ -139,7 +187,8 @@ int net_poll(void) {
break; break;
} }
/* 诊断:每 3 秒打印一次全局中断状态和 Socket 状<EFBFBD>? * 注意:使用已读取的 int_status不要再读 GINT读即清除<E6B885>?*/ /* 诊断:每 3 秒打印一次全局中断状态和 Socket 状态。
* 注意:使用已读取的 int_status不要再读 GINT读即清除。 */
static uint32_t s_last_diag = 0; static uint32_t s_last_diag = 0;
uint32_t now_diag = HAL_GetTick(); uint32_t now_diag = HAL_GetTick();
if ((now_diag - s_last_diag >= 3000) || s_last_diag == 0) { if ((now_diag - s_last_diag >= 3000) || s_last_diag == 0) {
@@ -208,11 +257,13 @@ int net_poll(void) {
} }
/* /*
* 处理完毕后检<EFBFBD>?INT# 是否仍为低电<EFBFBD>? * 如果是,说明在处理期间有新中断产生,需要再次读<EFBFBD>? * * 处理完毕后检INT# 是否仍为低电平。如果是,说明在处理期间有新中断产生,需要再次读取。
* 注意CH395F <EFBFBD>?RECV 中断是电平触发的(只要接收缓冲区有数据就保持低电平)<EFBFBD>? * 如果不跳出循环net_poll() 会无限循环处<E78EAF>?RECV 中断<E4B8AD>? * 导致主循环中的应用代码<E7A081>?echo永远无法执行<E689A7>? * 因此每次 net_poll() 调用只处理一批中断由主循环负责读取数据<E695B0>? */ * 注意CH395F RECV 中断是电平触发的(只要接收缓冲区有数据就保持低电平)
* 如果不跳出循环net_poll() 会无限循环处理 RECV 中断,导致主循环中的应用代码(如 echo永远无法执行。
* 因此每次 net_poll() 调用只处理一批中断,由主循环负责读取数据。 */
} while (0); } while (0);
/* <EFBFBD>?2 秒打印一次诊断信息(调试用) */ /* 2 秒打印一次诊断信息(调试用) */
{ {
static uint32_t s_last_dbg = 0; static uint32_t s_last_dbg = 0;
uint32_t now = HAL_GetTick(); uint32_t now = HAL_GetTick();
@@ -235,13 +286,76 @@ int net_poll(void) {
} }
/* /*
* 函数功能:<EFBFBD>?socket * 函数功能:处理消息队列中的请求
* 限定条件:必须在 netTask 上下文中调用(独占 CH395F 和 s_net_socks 访问权)
* 函数说明:每次调用处理一条消息。处理 send/recv 时内部会调用 net_poll()
* 等待操作完成。处理完毕后通知调用方任务。
*/
void net_process_messages(void) {
net_msg_t msg;
if (netMsgQueueHandle == NULL) {
return;
}
while (osMessageQueueGet(netMsgQueueHandle, &msg, NULL, 0) == osOK) {
switch (msg.type) {
case NET_MSG_SEND: {
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL || p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
msg.result = -1;
} else {
msg.result = net_send_locked(p_sock, msg.buf, msg.len);
}
break;
}
case NET_MSG_RECV: {
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
msg.result = -1;
} else {
msg.result = net_recv_locked(p_sock, msg.buf, msg.len, msg.flags);
}
break;
}
case NET_MSG_CLOSE: {
net_sock_t *p_sock = net_get_sock(msg.sockfd);
if (p_sock == NULL) {
msg.result = -1;
} else {
msg.result = net_close_locked(p_sock);
}
break;
}
case NET_MSG_CONNECT: {
msg.result = net_connect(msg.sockfd,
(const struct net_sockaddr *)&msg.addr,
msg.addrlen);
break;
}
default:
msg.result = -1;
break;
}
xTaskNotifyGive(msg.caller);
}
}
/*
* 函数功能:创建 Socket分配空闲 Socket 控制块并初始化状态
* 入口参数domain - 地址族,仅支持 NET_AF_INET int
* type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP) int
* protocol - 通常为 0 int
* 返回值Socket 描述符 (0~7),失败返回 -1 int
* 限定条件net_init() 已调用成功s_net_initialized == 1
* 函数说明:自动分配一个空闲的 Socket 控制块in_use=0初始化为 CREATED 状态
*/ */
int net_socket(int domain, int type, int protocol) { int net_socket(int domain, int type, int protocol) {
int sockfd; int sockfd;
net_sock_t *p_sock; net_sock_t *p_sock;
/* 参数检<EFBFBD>?*/ /* 参数检*/
if (domain != NET_AF_INET) { if (domain != NET_AF_INET) {
s_net_errno = NET_ERR_INVAL; s_net_errno = NET_ERR_INVAL;
return -1; return -1;
@@ -265,7 +379,17 @@ int net_socket(int domain, int type, int protocol) {
} }
/* /*
* 函数功能:绑定本地地址和端<EFBFBD>? */ * 函数功能:绑定 Socket 到本地 IP 地址和端口号
* 入口参数sockfd - Socket 描述符 (0~7) int
* addr - 本地地址结构体指针 const struct net_sockaddr *
* addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 返回值0 - 绑定成功 int
* -1 - 绑定失败参数无效、Socket 未使用等) int
* 限定条件Socket 已通过 net_socket() 创建且未被其他操作占用net_init() 已调用
* 函数说明1. TCP Socket 仅记录本地端口,进入 BOUND 状态等待 listen
* 2. UDP SocketSOCK_DGRAM立即打开 CH395F Socket 并配置为 Server 模式
* - Socket 4~7 需显式分配收发缓冲区
*/
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) { int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
net_sock_t *p_sock; net_sock_t *p_sock;
const struct net_sockaddr_in *p_addr_in; const struct net_sockaddr_in *p_addr_in;
@@ -327,7 +451,15 @@ int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
} }
/* /*
* 函数功能TCP Server 监听 * 函数功能:启动 TCP Server 监听(多连接模式)
* 入口参数sockfd - Socket 描述符 (0~7),必须为已绑定的 TCP Socket int
* backlog - 最大并发数据 Socket 数量 int >= 1
* 返回值0 - 监听启动成功 int
* -1 - 监听失败(参数无效、状态错误等) int
* 限定条件Socket 已通过 net_socket() + net_bind() 创建并绑定type == NET_SOCK_STREAM
* 函数说明1. 多连接模式下,先为数据 SocketNET_TCP_SERVER_DATA_SOCK_START ~ 7配置协议类型、源端口和独立缓冲区
* 2. CH395F 根据源端口匹配、协议类型为 TCP、状态为 CLOSED 查找可用 Socket 进行自动分配
* 3. 最后配置监听 Socket 并启动 TCP_LISTEN
*/ */
int net_listen(int sockfd, int backlog) { int net_listen(int sockfd, int backlog) {
net_sock_t *p_sock; net_sock_t *p_sock;
@@ -351,8 +483,10 @@ int net_listen(int sockfd, int backlog) {
return -1; return -1;
} }
/* 多连接模式:先为数据 Socket 设置协议类型、源端口和独立缓冲区<EFBFBD>? * 必须在监听话<E590AC>?Socket OPEN+LISTEN 之前配置<EFBC8C>?CH395F <20>? * 自动分配机制能扫描到所有可<E69C89>?Socket /* 多连接模式:先为数据 Socket 设置协议类型、源端口和独立缓冲区
* CH395F 根据源端口匹配、协议类<E8AEAE>?TCP、状<E38081>?CLOSED 查找可用 Socket */ * 必须在监听 Socket OPEN+LISTEN 之前配置,确保 CH395F
* 自动分配机制能扫描到所有可用 Socket。
* CH395F 根据源端口匹配、协议类型为 TCP、状态为 CLOSED 查找可用 Socket。 */
for (int ds = NET_TCP_SERVER_DATA_SOCK_START; ds < NET_MAX_SOCKETS; ds++) { for (int ds = NET_TCP_SERVER_DATA_SOCK_START; ds < NET_MAX_SOCKETS; ds++) {
uint8_t base = (uint8_t)(ds * 4); uint8_t base = (uint8_t)(ds * 4);
ch395f_set_send_buf((uint8_t)ds, base, 2); ch395f_set_send_buf((uint8_t)ds, base, 2);
@@ -393,7 +527,13 @@ int net_listen(int sockfd, int backlog) {
} }
/* /*
* 函数功能TCP Server 接受连接 * 函数功能TCP Server 接受连接,返回已建立连接的 Socket 描述符
* 入口参数sockfd - 监听 Socket 描述符 int
* addr - 输出:客户端地址结构体指针 struct net_sockaddr *(可为 NULL
* addrlen - 输入输出:地址结构体长度 int *(可为 NULL
* 返回值:新连接的 Socket 描述符 (1~7),失败返回 -1 int >= 0 || -1
* 限定条件Socket 处于 LISTENING 状态net_poll() 已检测到 CONNECT 事件
* 函数说明:遍历所有数据 Socket查找与客户端地址匹配且已建立连接ESTABLISHED的 Socket
*/ */
int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) { int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
net_sock_t *p_listen_sock; net_sock_t *p_listen_sock;
@@ -438,7 +578,15 @@ int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) {
} }
/* /*
* 函数功能TCP Client 发起连接 * 函数功能TCP Client 发起与服务器的连接
* 入口参数sockfd - Socket 描述符 (0~7),必须为已创建的 TCP Socket int
* addr - 服务器地址结构体指针 const struct net_sockaddr *
* addrlen - 地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 返回值0 - 连接请求发送成功(异步,需等待 ESTABLISHED int
* -1 - 连接失败(参数无效、状态错误等) int
* 限定条件Socket 已通过 net_socket() 创建且 type == NET_SOCK_STREAM尚未连接
* 函数说明:设置目标 IP 和端口后打开 CH395F Socket进入 CONNECTING 状态。连接建立需等待
* net_poll() 检测到 CONNECT 事件并将状态更新为 ESTABLISHED
*/ */
int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) { int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
net_sock_t *p_sock; net_sock_t *p_sock;
@@ -494,47 +642,37 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
} }
/* /*
* 函数功能:发送数据(TCP<EFBFBD>? */ * 函数功能:发送数据(内部锁定版本,必须在 netTask 上下文调用)
int net_send(int sockfd, const void *buf, int len, int flags) { */
net_sock_t *p_sock; static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
int sent = 0; int sent = 0;
uint32_t tick_start; uint32_t tick_start;
int sockfd = (int)(p_sock - s_net_socks);
/* 参数检<E695B0>?*/
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use || p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
s_net_errno = NET_ERR_NOTCONN;
return -1;
}
if (buf == NULL || len <= 0) { if (buf == NULL || len <= 0) {
s_net_errno = NET_ERR_INVAL; s_net_errno = NET_ERR_INVAL;
return -1; return -1;
} }
if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
s_net_errno = NET_ERR_NOTCONN;
return -1;
}
tick_start = HAL_GetTick(); tick_start = HAL_GetTick();
/* 分块发<E59D97>?*/
while (sent < len) { while (sent < len) {
int chunk = len - sent; int chunk = len - sent;
if (chunk > NET_SEND_BUF_SIZE) { if (chunk > NET_SEND_BUF_SIZE) {
chunk = NET_SEND_BUF_SIZE; chunk = NET_SEND_BUF_SIZE;
} }
/* 检查发送缓冲区是否空闲 */
if (!p_sock->send_ready) { if (!p_sock->send_ready) {
/* 超时检<E697B6>?*/
if (NET_SEND_TIMEOUT_MS > 0 && if (NET_SEND_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) { (HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
s_net_errno = NET_ERR_TIMEDOUT; s_net_errno = NET_ERR_TIMEDOUT;
break; break;
} }
/* 继续轮询 */
net_poll(); net_poll();
continue; continue;
} }
@@ -544,7 +682,6 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
sent += chunk; sent += chunk;
p_sock->send_bytes += (uint32_t)chunk; p_sock->send_bytes += (uint32_t)chunk;
/* 短暂延时等待发送完<E98081>?*/
tick_start = HAL_GetTick(); tick_start = HAL_GetTick();
while (!p_sock->send_ready) { while (!p_sock->send_ready) {
net_poll(); net_poll();
@@ -559,30 +696,12 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
} }
/* /*
* 函数功能:接收数据(TCP<EFBFBD>? */ * 函数功能:接收数据(内部锁定版本,必须在 netTask 上下文调用)
int net_recv(int sockfd, void *buf, int len, int flags) { */
net_sock_t *p_sock; static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
uint16_t recv_len; uint16_t recv_len;
uint32_t tick_start; uint32_t tick_start;
int sockfd = (int)(p_sock - s_net_socks);
/* 参数检<E695B0>?*/
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use) {
s_net_errno = NET_ERR_BADF;
return -1;
}
/* 检查连接状<E68EA5>?*/
if (p_sock->type == NET_SOCK_STREAM &&
p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
s_net_errno = NET_ERR_NOTCONN;
return -1;
}
if (buf == NULL || len <= 0) { if (buf == NULL || len <= 0) {
s_net_errno = NET_ERR_INVAL; s_net_errno = NET_ERR_INVAL;
@@ -596,35 +715,183 @@ int net_recv(int sockfd, void *buf, int len, int flags) {
recv_len = ch395f_get_recv_len(sockfd); recv_len = ch395f_get_recv_len(sockfd);
if (recv_len > 0) { if (recv_len > 0) {
/* 有数据可<E68DAE>?*/
int read_len = (recv_len > (uint16_t)len) ? len : (int)recv_len; int read_len = (recv_len > (uint16_t)len) ? len : (int)recv_len;
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len); ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
p_sock->recv_bytes += (uint32_t)read_len; p_sock->recv_bytes += (uint32_t)read_len;
return read_len; return read_len;
} }
/* 非阻塞模式检<E5BC8F>?*/
if (flags & NET_MSG_DONTWAIT) { if (flags & NET_MSG_DONTWAIT) {
s_net_errno = NET_ERR_WOULDBLOCK; s_net_errno = NET_ERR_WOULDBLOCK;
return -1; return -1;
} }
/* 超时检<E697B6>?*/
if (NET_RECV_TIMEOUT_MS > 0 && if (NET_RECV_TIMEOUT_MS > 0 &&
(HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) { (HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) {
s_net_errno = NET_ERR_TIMEDOUT; s_net_errno = NET_ERR_TIMEDOUT;
return -1; return -1;
} }
/* 连接已断开检<E5BC80>?*/
if (p_sock->state == NET_SOCK_STATE_TCP_ACCEPT) { if (p_sock->state == NET_SOCK_STATE_TCP_ACCEPT) {
return 0; /* 对端关闭 */ return 0;
} }
} }
} }
/* /*
* 函数功能:<EFBFBD>?UDP 数据 * 函数功能:关闭 socket内部锁定版本必须在 netTask 上下文调用)
*/
static int net_close_locked(net_sock_t *p_sock) {
int sockfd = (int)(p_sock - s_net_socks);
ch395f_close_socket(sockfd);
memset(p_sock, 0, sizeof(net_sock_t));
return 0;
}
/*
* 函数功能:发送数据(线程安全版本,通过消息队列委托给 netTask
* 入口参数sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* flags - 标志位,通常为 0 int
* 返回值:实际发送字节数 int >= 0 || -1
* -1 - 发送失败(参数无效、队列满等) int
* 限定条件Socket 处于 ESTABLISHED 状态net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将发送请求放入消息队列,阻塞等待 netTask 处理完毕并返回结果。netTask 内请使用 net_send_sock()
*/
int net_send(int sockfd, const void *buf, int len, int flags) {
(void)flags;
net_msg_t msg;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (netMsgQueueHandle == NULL) {
s_net_errno = NET_ERR;
return -1;
}
msg.type = NET_MSG_SEND;
msg.sockfd = sockfd;
msg.buf = (void *)buf;
msg.len = len;
msg.flags = 0;
msg.caller = xTaskGetCurrentTaskHandle();
msg.result = -1;
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (msg.result < 0) {
s_net_errno = NET_ERR;
}
return msg.result;
}
/*
* 函数功能:接收数据(线程安全版本,通过消息队列委托给 netTask
* 入口参数sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位0=阻塞NET_MSG_DONTWAIT=非阻塞 int
* 返回值:实际接收字节数 int >= 0 || 0 || -1
* 0 - 对端关闭连接 int
* -1 - 接收失败(参数无效、队列满等) int
* 限定条件Socket 处于 ESTABLISHED 状态net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将接收请求放入消息队列,阻塞等待 netTask 处理完毕并返回结果。netTask 内请使用 net_recv_sock()
*/
int net_recv(int sockfd, void *buf, int len, int flags) {
net_msg_t msg;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (netMsgQueueHandle == NULL) {
s_net_errno = NET_ERR;
return -1;
}
msg.type = NET_MSG_RECV;
msg.sockfd = sockfd;
msg.buf = buf;
msg.len = len;
msg.flags = flags;
msg.caller = xTaskGetCurrentTaskHandle();
msg.result = -1;
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (msg.result < 0) {
s_net_errno = NET_ERR;
}
return msg.result;
}
/*
* 函数功能:关闭 Socket线程安全版本通过消息队列委托给 netTask
* 入口参数sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* 返回值0 - 关闭成功 int
* -1 - 关闭失败 int
* 限定条件Socket 已创建且处于非 CLOSED 状态net_init() + MX_FREERTOS_Init() 已执行;非 netTask 上下文
* 函数说明:将关闭请求放入消息队列,阻塞等待 netTask 处理完毕。TCP Socket 先断开连接再释放控制块。
* netTask 内请使用 net_close_sock()
*/
int net_close(int sockfd) {
net_msg_t msg;
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF;
return -1;
}
if (netMsgQueueHandle == NULL) {
s_net_errno = NET_ERR;
return -1;
}
msg.type = NET_MSG_CLOSE;
msg.sockfd = sockfd;
msg.caller = xTaskGetCurrentTaskHandle();
msg.result = -1;
if (osMessageQueuePut(netMsgQueueHandle, &msg, 0, osWaitForever) != osOK) {
s_net_errno = NET_ERR_BUSY;
return -1;
}
ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
if (msg.result < 0) {
s_net_errno = NET_ERR;
}
return msg.result;
}
/*
* 函数功能:发送 UDP 数据报(含目标地址)
* 入口参数sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 待发送数据缓冲区指针 const void *
* len - 数据长度 int > 0
* flags - 标志位,通常为 0 int
* dest_addr - 目标地址结构体指针 const struct net_sockaddr *
* addrlen - 目标地址结构体长度 int >= sizeof(struct net_sockaddr_in)
* 返回值:实际发送字节数 int > 0 || -1
* -1 - 发送失败 int
* 限定条件Socket 已创建且为 UDP 类型SOCK_DGRAMnet_init() 已调用
*/ */
int net_sendto(int sockfd, const void *buf, int len, int flags, int net_sendto(int sockfd, const void *buf, int len, int flags,
const struct net_sockaddr *dest_addr, int addrlen) { const struct net_sockaddr *dest_addr, int addrlen) {
@@ -704,9 +971,17 @@ int net_sendto(int sockfd, const void *buf, int len, int flags,
return sent; return sent;
} }
/* /*
* 函数功能:接<EFBFBD>?UDP 数据 * 函数功能:接UDP 数据报(含源地址)
* 入口参数sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位,通常为 0 int
* src_addr - 输出:发送方地址结构体指针 struct net_sockaddr *(可为 NULL
* addrlen - 输入输出:源地址长度 int *(可为 NULL
* 返回值:实际接收字节数 int >= 0 || -1
* -1 - 接收失败 int
* 限定条件Socket 已创建且为 UDP 类型SOCK_DGRAMnet_init() 已调用;有数据可用
*/ */
int net_recvfrom(int sockfd, void *buf, int len, int flags, int net_recvfrom(int sockfd, void *buf, int len, int flags,
struct net_sockaddr *src_addr, int *addrlen) { struct net_sockaddr *src_addr, int *addrlen) {
@@ -784,34 +1059,59 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
} }
/* /*
* 函数功能:<EFBFBD>?socket * 函数功能:发送数据(内部版本,绕过消息队列,供 netTask 直接使用)
*/ */
int net_close(int sockfd) { int net_send_sock(net_sock_t *p_sock, const void *buf, int len) {
net_sock_t *p_sock; if (p_sock == NULL) {
/* 参数检<E695B0>?*/
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
s_net_errno = NET_ERR_BADF; s_net_errno = NET_ERR_BADF;
return -1; return -1;
} }
return net_send_locked(p_sock, buf, len);
p_sock = &s_net_socks[sockfd];
if (!p_sock->in_use) {
s_net_errno = NET_ERR_BADF;
return -1;
}
/* 关闭 CH395F Socket */
ch395f_close_socket(sockfd);
/* 清零控制<E68EA7>?*/
memset(p_sock, 0, sizeof(net_sock_t));
return 0;
} }
/* /*
* 函数功能:注册事件回<EFBFBD>? */ * 函数功能:接收数据(内部版本,绕过消息队列,供 netTask 直接使用)
* 入口参数p_sock - Socket 控制块指针 net_sock_t *
* buf - 接收缓冲区指针 void *
* len - 缓冲区大小 int > 0
* flags - 标志位NET_MSG_DONTWAIT=非阻塞 int
* 返回值:实际接收字节数 int >= 0 || 0 || -1
* 0 - 对端关闭连接 int
* -1 - 接收失败 int
* 限定条件:必须在 netTask 上下文中调用p_sock->in_use == 1
* 函数说明:直接操作 Socket 控制块,不经过消息队列。避免 netTask 向自身发送消息造成死锁
*/
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags) {
if (p_sock == NULL) {
s_net_errno = NET_ERR_BADF;
return -1;
}
return net_recv_locked(p_sock, buf, len, flags);
}
/*
* 函数功能:关闭 Socket内部版本绕过消息队列供 netTask 直接使用)
* 入口参数p_sock - Socket 控制块指针 net_sock_t *
* 返回值0 - 关闭成功 int
* -1 - 关闭失败 int
* 限定条件:必须在 netTask 上下文中调用p_sock->in_use == 1
* 函数说明:直接操作 Socket 控制块不经过消息队列。TCP Socket 先断开连接再释放控制块
*/
int net_close_sock(net_sock_t *p_sock) {
if (p_sock == NULL) {
s_net_errno = NET_ERR_BADF;
return -1;
}
return net_close_locked(p_sock);
}
/*
* 函数功能:注册 Socket 事件回调函数
* 入口参数sockfd - Socket 描述符 int >= 0 && < NET_MAX_SOCKETS
* cb - 回调函数指针 net_event_cb_t可为 NULL 取消注册)
* arg - 用户自定义参数指针 void *
* 返回值0 - 注册成功 int
* -1 - 注册失败 int
* 限定条件Socket 已创建且 in_use == 1net_init() 已调用
*/
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) { int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
net_sock_t *p_sock; net_sock_t *p_sock;
@@ -832,10 +1132,13 @@ int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
return 0; return 0;
} }
/* /*
* 函数功能:获<EFBFBD>?socket 最后错误码 * 函数功能:获取 Socket 最后一次操作返回的错误码
* 返回值当前错误码值NET_OK=0, NET_ERR_xxx=-N int
* 限定条件:无
* 函数说明:每次 net_*() API 调用失败后,错误码被设置。调用成功时不自动清零
*/ */
int net_get_errno(void) { int net_get_errno(void) {
return s_net_errno; return s_net_errno;
} }

View File

@@ -16,6 +16,31 @@ extern "C" {
#endif #endif
#include "net_types.h" #include "net_types.h"
#include "FreeRTOS.h"
#include "task.h"
/*
* 消息队列类型定义
*/
typedef enum {
NET_MSG_SEND,
NET_MSG_RECV,
NET_MSG_CLOSE,
NET_MSG_CONNECT,
} net_msg_type_t;
typedef struct {
net_msg_type_t type; /* 操作类型 */
int sockfd; /* Socket 描述符 */
void *buf; /* 数据缓冲区send/recv */
int len; /* 数据长度/缓冲区大小 */
int flags; /* 接收标志 */
TaskHandle_t caller; /* 调用方任务句柄 */
int result; /* 操作结果 */
/* 连接参数 */
struct net_sockaddr_in addr; /* 连接地址 */
int addrlen; /* 地址长度 */
} net_msg_t;
/* /*
* 网络初始化接口 * 网络初始化接口
@@ -245,6 +270,14 @@ uint32_t net_htonl(uint32_t hostlong);
*/ */
uint32_t net_ntohl(uint32_t netlong); uint32_t net_ntohl(uint32_t netlong);
/*
* 函数功能:处理消息队列中的请求(必须在 netTask 主循环中调用)
* 限定条件net_init() 已调用
* 函数说明:处理其他任务通过 net_send/net_recv 等接口发送的请求,
* 在 netTask 上下文中串行化执行所有 CH395F 操作
*/
void net_process_messages(void);
/* /*
* 函数功能:获取 Socket 控制块指针(内部使用) * 函数功能:获取 Socket 控制块指针(内部使用)
* 入口参数sockfd - socket 描述符 * 入口参数sockfd - socket 描述符
@@ -252,6 +285,39 @@ uint32_t net_ntohl(uint32_t netlong);
*/ */
net_sock_t *net_get_sock(int sockfd); net_sock_t *net_get_sock(int sockfd);
/*
* 内部 API需在 netTask 上下文中调用,绕过消息队列)
*/
/*
* 函数功能:发送数据(内部版本,直接操作,不经过消息队列)
* 入口参数p_sock - Socket 控制块指针
* buf - 数据缓冲区
* len - 数据长度
* 返回值:实际发送字节数,失败返回 -1
* 限定条件:必须在 netTask 上下文中调用
*/
int net_send_sock(net_sock_t *p_sock, const void *buf, int len);
/*
* 函数功能:接收数据(内部版本,直接操作,不经过消息队列)
* 入口参数p_sock - Socket 控制块指针
* buf - 接收缓冲区
* len - 缓冲区大小
* flags - 通常为 0可设置 NET_MSG_DONTWAIT
* 返回值实际接收字节数0=对端关闭,-1=错误
* 限定条件:必须在 netTask 上下文中调用
*/
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags);
/*
* 函数功能:关闭 socket内部版本直接操作不经过消息队列
* 入口参数p_sock - Socket 控制块指针
* 返回值0 成功,-1 失败
* 限定条件:必须在 netTask 上下文中调用
*/
int net_close_sock(net_sock_t *p_sock);
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -1,75 +1,105 @@
/* /*
* 模块名称RS-485 半双工通信驱动 * 模块名称RS-485 半双工通信驱动
* 模å<EFBFBD>—功能:RS-485 å<>Šå<C5A0>Œå·¥é€šä¿¡é©±åŠ¨å®žçŽ°ï¼Œæ ¸å¿ƒè¦<C3A8>ç¹ï¼š * 模块功能RS-485 半双工通信驱动实现,封装任意 UART 外设实现 485 方向控制,
* 1. å<>é€<C3A9>å‰<C3A5>æ‰é«˜ DE(切æ<E280A1>¢åˆ°å<C2B0>é€<C3A9>æ€<C3A6>) * 提供阻塞发送、中断接收IDLE 空闲帧检测)。使用 DWT CYCCNT 提供
* 2. HAL_UART_Transmit 内部已等ï¿?TC(å<CB86>é€<C3A9>完æˆ<C3A6>)标志 * 微秒级延时,确保 RS485 收发器 DE 使能稳定。
* 3. å<>é€<C3A9>完毕å<E280A2>Žæ‰ä½Ž DEï¼ˆåˆ‡åžæŽ¥æ”¶æ€<C3A6>)
* 4. 使用 HAL_UARTEx_ReceiveToIdle_IT 实现å<C2B0>˜é•¿å¸§æŽ¥ï¿? * å<>考:ST AN3070 应用笔记ã€<C3A3>controllerstech.com RS485 教程
* 适用平台STM32F4 系列 * 适用平台STM32F4 系列
* 作者:王建ï¿? * 创建日期ï¿?026-07-18 * 作者:王建锋
* 修改记录ï¿? * 2026-07-18 王建ï¿? åˆå»ºåˆ<C3A5>å§ç‰ˆæœ¬ * 创建日期2026-07-18
* 修改记录:
* 2026-07-18 王建锋 创建初始版本
* 2026-07-20 王建锋 修复 DE 时序:添加 DWT 微秒延时替代 for 循环
* 2026-07-20 王建锋 清理中文注释乱码,对齐代码规范
*/ */
#include "rs485.h" #include "rs485.h"
/* ======================== 内部辅助函数 ======================== */
/* /*
* 内部辅助ï¿?- æ¹å<C2B9>æŽ§åˆ * 函数功能DWT 微秒级延时
* 入口参数us - 延时微秒数 uint32_t 1 - N
* 返回值:无
* 限定条件:无(首次调用自动初始化 DWT CYCCNT
* 函数说明:利用 Cortex-M4 DWT CYCCNT 实现精确微秒延时168MHz 下 1us = 168 计数。
* 首次调用时自动使能 DWT 和 CYCCNT后续只读 CYCCNT 寄存器,不影响其他模块。
*/ */
static void delay_us(uint32_t us)
{
if (!(CoreDebug->DEMCR & CoreDebug_DEMCR_TRCENA_Msk)) {
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CYCCNT = 0;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
}
uint32_t start = DWT->CYCCNT;
uint32_t ticks = us * (SystemCoreClock / 1000000uL);
while ((DWT->CYCCNT - start) < ticks) { ; }
}
/* ======================== 方向控制宏 ======================== */
#define RS485_DIR_TX(h) HAL_GPIO_WritePin((h)->dir_port, (h)->dir_pin, GPIO_PIN_SET) #define RS485_DIR_TX(h) HAL_GPIO_WritePin((h)->dir_port, (h)->dir_pin, GPIO_PIN_SET)
#define RS485_DIR_RX(h) HAL_GPIO_WritePin((h)->dir_port, (h)->dir_pin, GPIO_PIN_RESET) #define RS485_DIR_RX(h) HAL_GPIO_WritePin((h)->dir_port, (h)->dir_pin, GPIO_PIN_RESET)
/* /* ======================== 公共函数实现 ======================== */
* 公共函数实现
*/
/* /*
* 函数功能:初始化 RS-485 句柄 * 函数功能:初始化 RS-485 句柄
* å…¥å<EFBFBD>£å<EFBFBD>数:handle - RS-485 å<EFBFBD>¥æŸ„æŒ‡éˆ * 入口参数p_handle - RS-485 句柄指针 rs485_handle_t*(非空)
* huart - UART å¤è®¾å<EFBFBD>¥æŸ„ * p_huart - UART 外设句柄指针(已由 CubeMX 初始化) UART_HandleTypeDef*(非空)
* dir_port - DE 引脚 GPIO 端å<C2AF>£ * p_dir_port - 方向控制 GPIO 端口(如 GPIOD GPIO_TypeDef*(非空)
* dir_pin - DE 引脚 GPIO ç¼å<E28093>· * dir_pin - 方向控制 GPIO 引脚号(如 GPIO_PIN_0 uint16_t
* 返回值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šUART ï¿?GPIO 须先ï¿?CubeMX 完æˆ<C3A6>åˆ<C3A5>å§ï¿? * 函数说明:åˆ<C3A5>å§åŒå<E28093>Žç«å<E280B9>³å°† DE 拉低,使 RS-485 æ”¶å<C2B6>器处于接收状ï¿? */ * 返回值:无
* 限定条件UART 和 GPIO 须先由 CubeMX 完成初始化
* 函数说明初始化后默认处于接收状态DE 引脚拉低)
*/
void rs485_init(rs485_handle_t *p_handle, void rs485_init(rs485_handle_t *p_handle,
UART_HandleTypeDef *p_huart, UART_HandleTypeDef *p_huart,
GPIO_TypeDef *p_dir_port, GPIO_TypeDef *p_dir_port,
uint16_t dir_pin) { uint16_t dir_pin)
{
p_handle->huart = p_huart; p_handle->huart = p_huart;
p_handle->dir_port = p_dir_port; p_handle->dir_port = p_dir_port;
p_handle->dir_pin = dir_pin; p_handle->dir_pin = dir_pin;
p_handle->rx_size = 0; p_handle->rx_size = 0;
/* 默认进入接收状�?*/
RS485_DIR_RX(p_handle); RS485_DIR_RX(p_handle);
} }
/* /*
* 函数功能:阻塞æ¹å¼<EFBFBD>å<EFBFBD>é€<EFBFBD>æ•°ï¿? * å…¥å<C2A5>£å<C2A3>数:handle - RS-485 å<>¥æŸ„æŒ‡éˆ * 函数功能:阻塞方式发送数据
* data - å¾…å<E280A6>é€<C3A9>æ•°æ<C2B0>®ç¼“冲区 * 入口参数p_handle - RS-485 句柄指针 rs485_handle_t*(非空)
* len - å¾…å<E280A6>é€<C3A9>å­—èŠæ•° * p_data - 待发送数据缓冲区 uint8_t*(非空)
* timeout - å<>é€<C3A9>超时(ms),ï¿?0 使用默认ï¿? * è¿”åžå€¼ï¼šHAL_OK / HAL_TIMEOUT / HAL_ERROR * len - 待发送字节数 uint16_t 1 - N
* é™<EFBFBD>定æ<EFBFBD>¡ä»¶ï¼šé¡»åœ¨ä¸»å¾ªçޝï¿?RTOS 任务中调用,ä¸<C3A4>å<EFBFBD>¯ï¿?UART 中断中调ï¿? * 函数说明ï¿? * æ—¶åº<C3A5>:DE=HIGH ï¿?UART å<>�?N 字节 ï¿?ï¿?TC ï¿?DE=LOW * timeout - 发送超时ms传 0 使用 RS485_TIMEOUT_DEFAULT uint32_t 0 / 1 - N
* HAL_UART_Transmit 内部会等ï¿?TXE(å<CB86>é€<C3A9>寄存器空)é€<C3A9>å­—èŠæ<E2809A>¬æ•°æ<C2B0>®ï¿? * 最å<E282AC>Žè¿˜ä¼šç­‰ï¿?TC(å<CB86>é€<C3A9>完æˆ<C3A6>)标志,确ä¿<C3A4>最å<E282AC>Žä¸€å­—èŠçš„å<E2809E>œæ­¢ä½<C3A4>已移出�? * å æ­¤è¿”åžå<C5BE>Žå†<C3A5>æ‰ä½Ž DE 是安全的 * 返回值HAL_OK - 发送成功 HAL_StatusTypeDef
* HAL_TIMEOUT - 发送超时
* HAL_ERROR - 发送错误
* 限定条件:须在主循环或 FreeRTOS 任务中调用,不可在 UART 中断中调用
* 函数说明:发送前拉高 DE等待 50us 确保 RS485 收发器驱动使能稳定后再启动 UART 发送。
* HAL_UART_Transmit 内部等待 TC发送完成标志确保最后一字节完全移出后
* 再拉低 DE 切回接收状态。
*/ */
HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle, HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle,
const uint8_t *p_data, const uint8_t *p_data,
uint16_t len, uint16_t len,
uint32_t timeout) { uint32_t timeout)
{
HAL_StatusTypeDef status = HAL_OK; HAL_StatusTypeDef status = HAL_OK;
if (timeout == 0U) { if (timeout == 0U) {
timeout = RS485_TIMEOUT_DEFAULT; timeout = RS485_TIMEOUT_DEFAULT;
} }
/* 1. 切æ<E280A1>¢åˆ°å<C2B0>é€<C3A9>状ï¿?*/
RS485_DIR_TX(p_handle); RS485_DIR_TX(p_handle);
/* 2. 阻塞å<C5BE>é€<C3A9>(内部等待 TCï¿?*/ delay_us(50);
status = HAL_UART_Transmit(p_handle->huart, status = HAL_UART_Transmit(p_handle->huart,
(uint8_t *)p_data, (uint8_t *)p_data,
len, len,
timeout); timeout);
/* 3. å<>é€<C3A9>完毕æˆè¶…æ—¶ï¼Œåˆ‡åžæŽ¥æ”¶çŠ¶ï¿?*/
RS485_DIR_RX(p_handle); RS485_DIR_RX(p_handle);
return status; return status;
@@ -77,28 +107,46 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle,
/* /*
* 函数功能启动中断方式接收IDLE 空闲帧检测) * 函数功能启动中断方式接收IDLE 空闲帧检测)
* å…¥å<EFBFBD>£å<EFBFBD>数:handle - RS-485 å<EFBFBD>¥æŸ„æŒ‡éˆ * 入口参数p_handle - RS-485 句柄指针 rs485_handle_t*(非空)
* buf - 接收缓冲ï¿? * buf_size - 缓冲区最大容ï¿? * è¿”åžå€¼ï¼šHAL_OK / HAL_ERROR * p_buf - 接收缓冲区 uint8_t*(非空)
* é™<EFBFBD>定æ<EFBFBD>¡ä»¶ï¼šUART 须已开å<E282AC>¯å…¨å±€ä¸­æ­ï¼ˆNVIC 使能ï¿? * 函数说明ï¿? * 使用 HAL_UARTEx_ReceiveToIdle_IT 实现å<C2B0>˜é•¿å¸§æŽ¥æ”¶ï¿½? * ï¿?UART 总线空闲超过 1 个字符时间å<C2B4>Žï¼Œç¡¬ä»¶è§¦ï¿?IDLE 中断ï¿? * HAL 自动调用 HAL_UARTEx_RxEventCallback 并返回已接收字节数�? * 用户须在该åžè°ƒä¸­é‡<C3A9>æ°è°ƒç”¨æœ¬å‡½æ•°é‡<C3A9>æ°å¼€å<E282AC>¯æŽ¥ï¿? */ * buf_size - 缓冲区大小 uint16_t
* 返回值HAL_OK - 启动成功 HAL_StatusTypeDef
* HAL_ERROR - 启动失败
* 限定条件UART 须已开启全局中断NVIC 使能)
* 函数说明:使用 HAL_UARTEx_ReceiveToIdle_IT 实现变长帧接收。
* 当 UART 总线空闲超过 1 个字符时间后硬件触发 IDLE 中断,
* HAL 自动调用 HAL_UARTEx_RxEventCallback 并返回已接收字节数。
* 用户须在回调中重新调用本函数重新开启接收。
*/
HAL_StatusTypeDef rs485_receive_start(rs485_handle_t *p_handle, HAL_StatusTypeDef rs485_receive_start(rs485_handle_t *p_handle,
uint8_t *p_buf, uint8_t *p_buf,
uint16_t buf_size) { uint16_t buf_size)
{
p_handle->rx_size = 0; p_handle->rx_size = 0;
return HAL_UARTEx_ReceiveToIdle_IT(p_handle->huart, p_buf, buf_size); return HAL_UARTEx_ReceiveToIdle_IT(p_handle->huart, p_buf, buf_size);
} }
/* /*
* 函数功能:缓存最近一次接收的字节ï¿? * å…¥å<C2A5>£å<C2A3>数:handle - RS-485 å<>¥æŸ„æŒ‡éˆ * 函数功能:缓存最近一次接收的字节数
* size - 本次接收到的字节ï¿? * 返回值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šé¡»ï¿?HAL_UARTEx_RxEventCallback 中调ï¿? */ * 入口参数p_handle - RS-485 句柄指针 rs485_handle_t*(非空)
void rs485_rx_set_size(rs485_handle_t *p_handle, uint16_t size) { * size - 本次接收到的字节数 uint16_t
* 返回值:无
* 限定条件:须在 HAL_UARTEx_RxEventCallback 中调用
* 函数说明:将 HAL 回调中接收到的数据大小回写到句柄的 rx_size 字段
*/
void rs485_rx_set_size(rs485_handle_t *p_handle, uint16_t size)
{
p_handle->rx_size = size; p_handle->rx_size = size;
} }
/* /*
* 函数功能:获å<EFBFBD>最è¿ä¸€æ¬¡æŽ¥æ”¶çš„å­—èŠï¿? * å…¥å<C2A5>£å<C2A3>数:handle - RS-485 å<>¥æŸ„æŒ‡éˆ * 函数功能:获取最近一次接收的字节数
* 返回值:字节ï¿? */ * 入口参数p_handle - RS-485 句柄指针 const rs485_handle_t*(非空)
uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle) { * 返回值:最近一次接收的字节数 uint16_t
* 限定条件:须在 HAL_UARTEx_RxEventCallback 触发后调用
* 函数说明:获取 rx_size 字段中缓存的接收字节数
*/
uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle)
{
return p_handle->rx_size; return p_handle->rx_size;
} }

View File

@@ -1,12 +1,16 @@
/* /*
* 模块名称SD2506API-G RTC 实时时钟驱动 * 模块名称SD2506API-G RTC Driver
* 模块功能SD2506API-G 高精度温补实时时钟<EFBFBD>?I2C 驱动 * 模块功能SD2506API-G 高精度温补实时时钟驱动,I2C 接口
* 适用平台STM32F407ZGT6I2C1 接口 (PB6-SCL, PB7-SDA) * 适用平台STM32F407ZGTx + SD2506API-G 实时时钟芯片I2C1: PB6-SCL, PB7-SDA
* 作者:王建<EFBFBD>? * 创建日期<E697A5>?026-07-17 * 作者:王建
* 修改记录<EFBFBD>? * 2026-07-17 王建<E78E8B>? 创建初始版本<EFBC8C>?SD2506API-G Ver2.0 手册 * 创建日期2026-07-17
* 修改记录:
* 2026-07-17 王建锋 创建初始版本,参考 SD2506API-G Ver2.0 手册
*/ */
#include <string.h> #include <string.h>
#include <stdio.h>
#include "sd2506.h" #include "sd2506.h"
#include "i2c.h" #include "i2c.h"
@@ -15,8 +19,11 @@ extern I2C_HandleTypeDef hi2c1;
/* ======================== 内部辅助函数 ======================== */ /* ======================== 内部辅助函数 ======================== */
/* /*
* 函数功能BCD 码转十进<EFBFBD>? * 入口参数bcd - BCD 编码<E7BC96>? uint8_t 0x00 - 0x99 * 函数功能BCD 码转十进制数
* <EFBFBD>?<3F>?值十进制数<E588B6>? * 限定条件:输入为合法 BCD <20>? * 函数说明:高 4 位为十位,低 4 位为个位 * 入口参数bcd - BCD 编码值 uint8_t 0x00 - 0x99
* 返回值:十进制数值 uint8_t
* 限定条件:输入为合法 BCD 编码(高 4 位和低 4 位均 <= 9
* 函数说明:高 4 位为十位,低 4 位为个位,分别乘 10 和加后返回
*/ */
uint8_t sd2506_bcd_to_dec(uint8_t bcd) { uint8_t sd2506_bcd_to_dec(uint8_t bcd) {
return ((bcd >> 4) * 10) + (bcd & 0x0FU); return ((bcd >> 4) * 10) + (bcd & 0x0FU);
@@ -31,9 +38,11 @@ uint8_t sd2506_dec_to_bcd(uint8_t dec) {
/* /*
* 函数功能:写单字节寄存器 * 函数功能:写单字节寄存器
* 入口参数reg - 寄存器地址 uint8_t 00H - 79H * 入口参数reg - 寄存器地址 uint8_t 0x00 - 0x79
* val - 写入<EFBFBD>? uint8_t 0x00 - 0xFF * val - 写入数据 uint8_t 0x00 - 0xFF
* <EFBFBD>?<3F>?值0 - 成功<E68890>?2 - I2C 错误 * 返回值0-成功2-I2C 错误 int
* 限定条件I2C 外设已初始化
* 函数说明:通过 I2C 向指定地址写入 1 字节数据
* 限定条件I2C 外设已初始化 * 限定条件I2C 外设已初始化
* 函数说明:通过 I2C 向指定地址写入 1 字节 * 函数说明:通过 I2C 向指定地址写入 1 字节
*/ */
@@ -48,10 +57,12 @@ static int sd2506_write_reg(uint8_t reg, uint8_t val) {
/* /*
* 函数功能:读单字节寄存器 * 函数功能:读单字节寄存器
* 入口参数reg - 寄存器地址 uint8_t 00H - 79H * 入口参数reg - 寄存器地址 uint8_t 0x00 - 0x79
* val - 读取值指<EFBFBD>? uint8_t* * p_val - 读取值指 uint8_t*(非空)
* <EFBFBD>?<3F>?值0 - 成功<E68890>?2 - I2C 错误 * 返回值0-成功2-I2C 错误 int
* 限定条件I2C 外设已初始化val 为非空指<EFBFBD>? * 函数说明:通过 I2C 从指定地址读取 1 字节 * 限定条件I2C 外设已初始化,p_val 为非空指
* 函数说明:通过 I2C 从指定地址读取 1 字节数据
* 限定条件I2C 外设已初始化val 为非空指针函数说明:通过 I2C 从指定地址读取 1 字节
*/ */
static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) { static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) {
if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg,
@@ -63,12 +74,14 @@ static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) {
} }
/* /*
* 函数功能:写多字节寄存器 * 函数功能:写多字节寄存器(连续写)
* 入口参数reg - 起始寄存器地址 uint8_t 00H - 79H * 入口参数reg - 起始寄存器地址 uint8_t 0x00 - 0x79
* data - 数据缓冲区指<EFBFBD>? uint8_t* * p_data - 数据缓冲区指 uint8_t*(非空)
* len - 数据长度 uint8_t 1 - N * len - 数据长度 uint8_t 1 - N
* <EFBFBD>?<3F>?值0 - 成功<E68890>?2 - I2C 错误 * 返回值0-成功2-I2C 错误 int
* 限定条件I2C 外设已初始化data 为非空指<EFBFBD>? * 函数说明:从 reg 开始连续写<E7BBAD>?len 字节 * 限定条件I2C 外设已初始化,p_data 为非空指
* 函数说明:从 reg 开始连续写入 len 字节数据
* 限定条件I2C 外设已初始化data 为非空指针
*/ */
static int sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) { static int sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) {
if (HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg, if (HAL_I2C_Mem_Write(&hi2c1, SD2506_I2C_ADDR_WRITE, reg,
@@ -80,12 +93,14 @@ static int sd2506_write_regs(uint8_t reg, const uint8_t *p_data, uint8_t len) {
} }
/* /*
* 函数功能:读多字节寄存器 * 函数功能:读多字节寄存器(连续读)
* 入口参数reg - 起始寄存器地址 uint8_t 00H - 79H * 入口参数reg - 起始寄存器地址 uint8_t 0x00 - 0x79
* data - 数据缓冲区指<EFBFBD>? uint8_t* * p_data - 数据缓冲区指 uint8_t*(非空)
* len - 数据长度 uint8_t 1 - N * len - 数据长度 uint8_t 1 - N
* <EFBFBD>?<3F>?值0 - 成功<E68890>?2 - I2C 错误 * 返回值0-成功2-I2C 错误 int
* 限定条件I2C 外设已初始化data 为非空指<EFBFBD>? * 函数说明:从 reg 开始连续读<E7BBAD>?len 字节 * 限定条件I2C 外设已初始化,p_data 为非空指
* 函数说明:从 reg 开始连续读取 len 字节数据
* 限定条件I2C 外设已初始化data 为非空指针
*/ */
static int sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) { static int sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) {
if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg, if (HAL_I2C_Mem_Read(&hi2c1, SD2506_I2C_ADDR_READ, reg,
@@ -97,16 +112,16 @@ static int sd2506_read_regs(uint8_t reg, uint8_t *p_data, uint8_t len) {
} }
/* /*
* 函数功能:开启写保护(允许写<EFBFBD>?00H~71H 寄存器) * 函数功能:开启写保护(允许写00H~71H 寄存器)
* 入口参数:无 * 入口参数:无
* 返回值0 - 成功<EFBFBD>?2 - I2C 错误 * 返回值0 - 成功 2 - I2C 错误
* 限定条件I2C 外设已初始化 * 限定条件I2C 外设已初始化
* 函数说明:顺序:先写 WRTC1=1, 再写 WRTC2=1 + WRTC3=1 * 函数说明:顺序:先写 WRTC1=1, 再写 WRTC2=1 + WRTC3=1
*/ */
static int sd2506_write_enable(void) { static int sd2506_write_enable(void) {
int ret = 0; int ret = 0;
/* 先置 WRTC1=1 (bit6=1), 其它位参考手<EFBFBD>? 0x84 */ /* 先置 WRTC1=1 (bit6=1), 其它位参考手0x84 */
ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U); ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U);
if (ret != SD2506_OK) return ret; if (ret != SD2506_OK) return ret;
@@ -118,9 +133,9 @@ static int sd2506_write_enable(void) {
} }
/* /*
* 函数功能:关闭写保护(禁止写<EFBFBD>?00H~71H 寄存器) * 函数功能:关闭写保护(禁止写00H~71H 寄存器)
* 入口参数:无 * 入口参数:无
* 返回值0 - 成功<EFBFBD>?2 - I2C 错误 * 返回值0 - 成功 2 - I2C 错误
* 限定条件I2C 外设已初始化 * 限定条件I2C 外设已初始化
* 函数说明:顺序:先写 WRTC2=0 + WRTC3=0, 再写 WRTC1=0 * 函数说明:顺序:先写 WRTC2=0 + WRTC3=0, 再写 WRTC1=0
*/ */
@@ -143,10 +158,14 @@ static int sd2506_write_disable(void) {
/* /*
* 函数功能:初始化 SD2506API-G RTC * 函数功能:初始化 SD2506API-G RTC
* 入口参数:无 * 入口参数:无
* 返回值0 - 成功<E68890>?2 - I2C 错误 * 返回值0-成功2-I2C 错误 int
* 限定条件CubeMX 已完<EFBFBD>?I2C1 初始<EFBFBD>? * 限定条件CubeMX 已完I2C1 初始PB6-SCL, PB7-SDA
* 函数说明<EFBFBD>?. 读取芯片 ID 验证通信 * 函数说明1. 读取芯片 ID 验证通信
* 2. 上电重置充电寄存<EFBFBD>?18H=82H * 2. 上电重置充电寄存器 (0x18=0x82)
* 3. 配置 24 小时制、开自动复位
* 限定条件CubeMX 已完成I2C1 初始化
* 函数说明:读取芯片 ID 验证通信
* 2. 上电重置充电寄存器18H=82H
* 3. 配置 24 小时制、开自动复位 * 3. 配置 24 小时制、开自动复位
*/ */
int sd2506_init(void) { int sd2506_init(void) {
@@ -221,9 +240,11 @@ int sd2506_set_time(const sd2506_time_t *p_time) {
} }
/* /*
* function: read RTC time * 函数功能:读取 RTC 时间日期
* param: p_time - output time struct * 入口参数:p_time - 输出时间结构体指针 sd2506_time_t*(非空)
* return: 0=OK, -2=I2C error * 返回值0-成功2-I2C 错误 int
* 限定条件sd2506_init() 已调用成功
* 函数说明:一次读取 7 字节时间数据 (00H~06H),所有实时数据被锁存避免错误
* note: sd2506_init() must be called first * note: sd2506_init() must be called first
*/ */
int sd2506_get_time(sd2506_time_t *p_time) { int sd2506_get_time(sd2506_time_t *p_time) {
@@ -253,8 +274,11 @@ int sd2506_get_time(sd2506_time_t *p_time) {
} }
/* /*
* function: read internal temperature * 函数功能:读取内部温度
* param: p_temp - output temperature * 入口参数:p_temp - 输出温度值指针 int8_t*(非空)
* 返回值0-成功2-I2C 错误 int
* 限定条件sd2506_init() 已调用成功
* 函数说明:读取寄存器 0x16bit7 为符号位
* return: 0=OK, -2=I2C error * return: 0=OK, -2=I2C error
* note: reads register 0x16 * note: reads register 0x16
*/ */
@@ -284,8 +308,11 @@ int sd2506_get_temperature(int8_t *p_temp) {
} }
/* /*
* function: read battery voltage (mV) * 函数功能:读取电池电压(毫伏)
* param: p_voltage - output voltage * 入口参数:p_voltage - 输出电压指针 uint16_t*(非空)
* 返回值0-成功2-I2C 错误 int
* 限定条件sd2506_init() 已调用成功
* 函数说明:组合 9 位值 (bit8=BAT8_VAL, bit7~0=BAT_VL),转换为毫伏
* return: 0=OK, -2=I2C error * return: 0=OK, -2=I2C error
* note: combines 9-bit value * note: combines 9-bit value
*/ */
@@ -314,8 +341,11 @@ int sd2506_get_battery_voltage(uint16_t *p_voltage) {
} }
/* /*
* function: read 8-byte chip ID * 函数功能:读取 8 字节芯片 ID
* param: p_id - 8-byte output buffer * 入口参数p_id - 输出缓冲区指针 uint8_t[8](非空)
* 返回值0-成功2-I2C 错误 int
* 限定条件sd2506_init() 已调用成功
* 函数说明:读取寄存器 0x72~0x79
* return: 0=OK, -2=I2C error * return: 0=OK, -2=I2C error
* note: reads registers 0x72-0x79 * note: reads registers 0x72-0x79
*/ */
@@ -328,10 +358,13 @@ int sd2506_get_id(uint8_t p_id[8]) {
} }
/* /*
* function: read SRAM data * 函数功能:读取 SRAM 数据
* param: addr - start address (0-69) * 入口参数addr - 起始地址 uint8_t 0-69
* p_buf - output buffer * p_buf - 输出缓冲区指针 uint8_t*(非空)
* len - read length * len - 读取长度 uint8_t 1-N
* 返回值0-成功,-1=参数错误,-2=I2C 错误 int
* 限定条件sd2506_init() 已调用成功addr+len <= SD2506_SRAM_SIZE(70)
* 函数说明SRAM 范围 0x2C~0x71
* return: 0=OK, -1=param err, -2=I2C err * return: 0=OK, -1=param err, -2=I2C err
* note: SRAM range 0x2C-0x71 * note: SRAM range 0x2C-0x71
*/ */
@@ -348,10 +381,13 @@ int sd2506_read_sram(uint8_t addr, uint8_t *p_buf, uint8_t len) {
} }
/* /*
* function: write SRAM data * 函数功能:写入 SRAM 数据
* param: addr - start address (0-69) * 入口参数addr - 起始地址 uint8_t 0-69
* p_buf - input data * p_buf - 输入数据指针 uint8_t*(非空)
* len - write length * len - 写入长度 uint8_t 1-N
* 返回值0-成功,-1=参数错误,-2=I2C 错误 int
* 限定条件sd2506_init() 已调用成功addr+len <= SD2506_SRAM_SIZE(70)
* 函数说明SRAM 写入需走写保护流程(虽 SRAM 本身无需)
* return: 0=OK, -1=param err, -2=I2C err * return: 0=OK, -1=param err, -2=I2C err
*/ */
int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) { int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) {
@@ -377,9 +413,12 @@ int sd2506_write_sram(uint8_t addr, const uint8_t *p_buf, uint8_t len) {
} }
/* /*
* function: set alarm interrupt * 函数功能:设置闹钟中断
* param: p_time - alarm time struct * 入口参数:p_time - 报警时间结构体指针 sd2506_time_t*(非空)
* mask - alarm match mask * mask - 报警匹配掩码 uint8_t
* 返回值0-成功,-2=I2C 错误 int
* 限定条件sd2506_init() 已调用成功
* 函数说明:写入 8 字节报警数据 (07H~0EH),使能 CTR2.INTAE+INTS0+IM
* return: 0=OK, -2=I2C error * return: 0=OK, -2=I2C error
*/ */
int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) { int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) {
@@ -421,8 +460,11 @@ int sd2506_set_alarm(const sd2506_time_t *p_time, uint8_t mask) {
} }
/* /*
* function: clear alarm interrupt flag * 函数功能:清除闹钟中断标志
* param: none * 入口参数:无
* 返回值0-成功,-2=I2C 错误 int
* 限定条件sd2506_init() 已调用成功ARST=1 时自动清除 INTAF
* 函数说明:读取 CTR1 即可清除 INTAF 标志
* return: 0=OK, -2=I2C error * return: 0=OK, -2=I2C error
* note: reading CTR1 clears INTAF * note: reading CTR1 clears INTAF
*/ */
@@ -440,8 +482,10 @@ int sd2506_clear_alarm(void) {
} }
/* /*
* function: read control register 1 * 函数功能:读取控制寄存器 1 (CTR1)
* param: p_val - output value * 入口参数p_val - 输出值指针 uint8_t*(非空)
* 返回值0-成功,-2=I2C 错误 int
* 限定条件sd2506_init() 已调用成功
* return: 0=OK, -2=I2C error * return: 0=OK, -2=I2C error
*/ */
int sd2506_read_ctr1(uint8_t *p_val) { int sd2506_read_ctr1(uint8_t *p_val) {

View File

@@ -33,6 +33,12 @@ extern "C" {
*/ */
/* #define DBG_DEBUG_ENABLE */ /* #define DBG_DEBUG_ENABLE */
/*
* 系统时间戳开关:取消注释在所有日志行首追加 [YYYY-MM-DD HH:MM:SS] 时间戳
* 依赖 sys_clock_init() 已调用SD2506 RTC 初始化完成)
*/
#define APP_TIMESTAMP_ENABLE
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif

View File

@@ -22,8 +22,13 @@ extern "C" {
#endif #endif
#include <stdio.h> #include <stdio.h>
#include <stddef.h>
#include "dbg_cfg.h" #include "dbg_cfg.h"
#ifdef APP_TIMESTAMP_ENABLE
#include "sys_clock.h"
#endif
/* /*
* 总开关DBG_ENABLE * 总开关DBG_ENABLE
* 定义 → 输出 DBG_ERROR / DBG_INFO若 DBG_DEBUG_ENABLE 也定义则输出 DBG_DEBUG * 定义 → 输出 DBG_ERROR / DBG_INFO若 DBG_DEBUG_ENABLE 也定义则输出 DBG_DEBUG
@@ -48,22 +53,65 @@ extern "C" {
#define DBG_TAG "" #define DBG_TAG ""
#endif #endif
#ifdef APP_TIMESTAMP_ENABLE
/* /*
* DBG_ERROR - 错误输出,受 DBG_ENABLE 控制 * 时间戳输出宏 — 在日志行首追加 [YYYY-MM-DD HH:MM:SS] 格式的系统墙钟时间
* sys_clock_get_str() 线程安全(内部使用 DWT US 计数,无锁)
*/
#define DBG_TS() sys_clock_get_str((char[32]){0}, 32)
/*
* DBG_ERROR - 错误输出,受 DBG_ENABLE + APP_TIMESTAMP_ENABLE 控制
*/
#define DBG_ERROR(fmt, ...) do { \
char __ts_buf[32]; \
(void)__ts_buf; \
DBG_PRINT("[%s]" "[ERR]" DBG_TAG " " fmt "\r\n", \
sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), ##__VA_ARGS__); \
} while (0)
/*
* DBG_INFO - 信息输出,受 DBG_ENABLE + APP_TIMESTAMP_ENABLE 控制
*/
#define DBG_INFO(fmt, ...) do { \
char __ts_buf[32]; \
(void)__ts_buf; \
DBG_PRINT("[%s]" DBG_TAG " " fmt "\r\n", \
sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), ##__VA_ARGS__); \
} while (0)
/*
* DBG_DEBUG - 调试输出,需 DBG_ENABLE + DBG_DEBUG_ENABLE + APP_TIMESTAMP_ENABLE 同时定义
*/
#ifdef DBG_DEBUG_ENABLE
#define DBG_DEBUG(fmt, ...) do { \
char __ts_buf[32]; \
(void)__ts_buf; \
DBG_PRINT("[%s]" DBG_TAG "(%s:%d) " fmt "\r\n", \
sys_clock_get_str(__ts_buf, sizeof(__ts_buf)), __func__, __LINE__, ##__VA_ARGS__); \
} while (0)
#else
#define DBG_DEBUG(fmt, ...)
#endif
#else /* APP_TIMESTAMP_ENABLE not defined */
/*
* DBG_ERROR - 错误输出,受 DBG_ENABLE 控制(无时间戳)
*/ */
#define DBG_ERROR(fmt, ...) do { \ #define DBG_ERROR(fmt, ...) do { \
DBG_PRINT("[ERR]" DBG_TAG " " fmt "\r\n", ##__VA_ARGS__); \ DBG_PRINT("[ERR]" DBG_TAG " " fmt "\r\n", ##__VA_ARGS__); \
} while (0) } while (0)
/* /*
* DBG_INFO - 信息输出,受 DBG_ENABLE 控制 * DBG_INFO - 信息输出,受 DBG_ENABLE 控制(无时间戳)
*/ */
#define DBG_INFO(fmt, ...) do { \ #define DBG_INFO(fmt, ...) do { \
DBG_PRINT(DBG_TAG " " fmt "\r\n", ##__VA_ARGS__); \ DBG_PRINT(DBG_TAG " " fmt "\r\n", ##__VA_ARGS__); \
} while (0) } while (0)
/* /*
* DBG_DEBUG - 调试输出,需 DBG_ENABLE + DBG_DEBUG_ENABLE 同时定义 * DBG_DEBUG - 调试输出,需 DBG_ENABLE + DBG_DEBUG_ENABLE 同时定义(无时间戳)
*/ */
#ifdef DBG_DEBUG_ENABLE #ifdef DBG_DEBUG_ENABLE
#define DBG_DEBUG(fmt, ...) do { \ #define DBG_DEBUG(fmt, ...) do { \
@@ -73,6 +121,8 @@ extern "C" {
#define DBG_DEBUG(fmt, ...) #define DBG_DEBUG(fmt, ...)
#endif #endif
#endif /* APP_TIMESTAMP_ENABLE */
#else /* DBG_ENABLE not defined — all debug output disabled */ #else /* DBG_ENABLE not defined — all debug output disabled */
#define DBG_ERROR(fmt, ...) #define DBG_ERROR(fmt, ...)

View File

@@ -55,6 +55,7 @@ void DebugMon_Handler(void);
void DMA1_Stream3_IRQHandler(void); void DMA1_Stream3_IRQHandler(void);
void DMA1_Stream4_IRQHandler(void); void DMA1_Stream4_IRQHandler(void);
void EXTI9_5_IRQHandler(void); void EXTI9_5_IRQHandler(void);
void SPI1_IRQHandler(void);
void SPI2_IRQHandler(void); void SPI2_IRQHandler(void);
void USART1_IRQHandler(void); void USART1_IRQHandler(void);
void USART2_IRQHandler(void); void USART2_IRQHandler(void);
@@ -62,7 +63,9 @@ void USART3_IRQHandler(void);
void UART4_IRQHandler(void); void UART4_IRQHandler(void);
void UART5_IRQHandler(void); void UART5_IRQHandler(void);
void TIM7_IRQHandler(void); void TIM7_IRQHandler(void);
void DMA2_Stream0_IRQHandler(void);
void DMA2_Stream2_IRQHandler(void); void DMA2_Stream2_IRQHandler(void);
void DMA2_Stream3_IRQHandler(void);
void DMA2_Stream7_IRQHandler(void); void DMA2_Stream7_IRQHandler(void);
/* USER CODE BEGIN EFP */ /* USER CODE BEGIN EFP */

43
Lib/FatFs/diskio.h Normal file
View File

@@ -0,0 +1,43 @@
#ifndef _DISKIO_DEFINED
#define _DISKIO_DEFINED
#ifdef __cplusplus
extern "C" {
#endif
typedef BYTE DSTATUS;
typedef enum {
RES_OK = 0,
RES_ERROR,
RES_WRPRT,
RES_NOTRDY,
RES_PARERR
} DRESULT;
DSTATUS disk_initialize(BYTE pdrv);
DSTATUS disk_status(BYTE pdrv);
DRESULT disk_read(BYTE pdrv, BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_write(BYTE pdrv, const BYTE* buff, LBA_t sector, UINT count);
DRESULT disk_ioctl(BYTE pdrv, BYTE cmd, void* buff);
#define STA_NOINIT 0x01
#define STA_NODISK 0x02
#define STA_PROTECT 0x04
#define CTRL_SYNC 0
#define GET_SECTOR_COUNT 1
#define GET_SECTOR_SIZE 2
#define GET_BLOCK_SIZE 3
#define CTRL_TRIM 4
#define CTRL_POWER 5
#define CTRL_LOCK 6
#define CTRL_EJECT 7
#define CTRL_FORMAT 8
#ifdef __cplusplus
}
#endif
#endif

7084
Lib/FatFs/ff.c Normal file

File diff suppressed because it is too large Load Diff

429
Lib/FatFs/ff.h Normal file
View File

@@ -0,0 +1,429 @@
/*----------------------------------------------------------------------------/
/ FatFs - Generic FAT Filesystem module R0.15 /
/-----------------------------------------------------------------------------/
/
/ Copyright (C) 2022, ChaN, all right reserved.
/
/ FatFs module is an open source software. Redistribution and use of FatFs in
/ source and binary forms, with or without modification, are permitted provided
/ that the following condition is met:
/
/ 1. Redistributions of source code must retain the above copyright notice,
/ this condition and the following disclaimer.
/
/ This software is provided by the copyright holder and contributors "AS IS"
/ and any warranties related to this software are DISCLAIMED.
/ The copyright owner or contributors be NOT LIABLE for any damages caused
/ by use of this software.
/
/----------------------------------------------------------------------------*/
#ifndef FF_DEFINED
#define FF_DEFINED 80286 /* Revision ID */
#ifdef __cplusplus
extern "C" {
#endif
#include "ffconf.h" /* FatFs configuration options */
#if FF_DEFINED != FFCONF_DEF
#error Wrong configuration file (ffconf.h).
#endif
/* Integer types used for FatFs API */
#if defined(_WIN32) /* Windows VC++ (for development only) */
#define FF_INTDEF 2
#include <windows.h>
typedef unsigned __int64 QWORD;
#include <float.h>
#define isnan(v) _isnan(v)
#define isinf(v) (!_finite(v))
#elif (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L) || defined(__cplusplus) /* C99 or later */
#define FF_INTDEF 2
#include <stdint.h>
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef uint16_t WORD; /* 16-bit unsigned integer */
typedef uint32_t DWORD; /* 32-bit unsigned integer */
typedef uint64_t QWORD; /* 64-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
#else /* Earlier than C99 */
#define FF_INTDEF 1
typedef unsigned int UINT; /* int must be 16-bit or 32-bit */
typedef unsigned char BYTE; /* char must be 8-bit */
typedef unsigned short WORD; /* 16-bit unsigned integer */
typedef unsigned long DWORD; /* 32-bit unsigned integer */
typedef WORD WCHAR; /* UTF-16 character type */
#endif
/* Type of file size and LBA variables */
#if FF_FS_EXFAT
#if FF_INTDEF != 2
#error exFAT feature wants C99 or later
#endif
typedef QWORD FSIZE_t;
#if FF_LBA64
typedef QWORD LBA_t;
#else
typedef DWORD LBA_t;
#endif
#else
#if FF_LBA64
#error exFAT needs to be enabled when enable 64-bit LBA
#endif
typedef DWORD FSIZE_t;
typedef DWORD LBA_t;
#endif
/* Type of path name strings on FatFs API (TCHAR) */
#if FF_USE_LFN && FF_LFN_UNICODE == 1 /* Unicode in UTF-16 encoding */
typedef WCHAR TCHAR;
#define _T(x) L ## x
#define _TEXT(x) L ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 2 /* Unicode in UTF-8 encoding */
typedef char TCHAR;
#define _T(x) u8 ## x
#define _TEXT(x) u8 ## x
#elif FF_USE_LFN && FF_LFN_UNICODE == 3 /* Unicode in UTF-32 encoding */
typedef DWORD TCHAR;
#define _T(x) U ## x
#define _TEXT(x) U ## x
#elif FF_USE_LFN && (FF_LFN_UNICODE < 0 || FF_LFN_UNICODE > 3)
#error Wrong FF_LFN_UNICODE setting
#else /* ANSI/OEM code in SBCS/DBCS */
typedef char TCHAR;
#define _T(x) x
#define _TEXT(x) x
#endif
/* Definitions of volume management */
#if FF_MULTI_PARTITION /* Multiple partition configuration */
typedef struct {
BYTE pd; /* Physical drive number */
BYTE pt; /* Partition: 0:Auto detect, 1-4:Forced partition) */
} PARTITION;
extern PARTITION VolToPart[]; /* Volume - Partition mapping table */
#endif
#if FF_STR_VOLUME_ID
#ifndef FF_VOLUME_STRS
extern const char* VolumeStr[FF_VOLUMES]; /* User defied volume ID */
#endif
#endif
/* Filesystem object structure (FATFS) */
typedef struct {
BYTE fs_type; /* Filesystem type (0:not mounted) */
BYTE pdrv; /* Volume hosting physical drive */
BYTE ldrv; /* Logical drive number (used only when FF_FS_REENTRANT) */
BYTE n_fats; /* Number of FATs (1 or 2) */
BYTE wflag; /* win[] status (b0:dirty) */
BYTE fsi_flag; /* FSINFO status (b7:disabled, b0:dirty) */
WORD id; /* Volume mount ID */
WORD n_rootdir; /* Number of root directory entries (FAT12/16) */
WORD csize; /* Cluster size [sectors] */
#if FF_MAX_SS != FF_MIN_SS
WORD ssize; /* Sector size (512, 1024, 2048 or 4096) */
#endif
#if FF_USE_LFN
WCHAR* lfnbuf; /* LFN working buffer */
#endif
#if FF_FS_EXFAT
BYTE* dirbuf; /* Directory entry block scratchpad buffer for exFAT */
#endif
#if !FF_FS_READONLY
DWORD last_clst; /* Last allocated cluster */
DWORD free_clst; /* Number of free clusters */
#endif
#if FF_FS_RPATH
DWORD cdir; /* Current directory start cluster (0:root) */
#if FF_FS_EXFAT
DWORD cdc_scl; /* Containing directory start cluster (invalid when cdir is 0) */
DWORD cdc_size; /* b31-b8:Size of containing directory, b7-b0: Chain status */
DWORD cdc_ofs; /* Offset in the containing directory (invalid when cdir is 0) */
#endif
#endif
DWORD n_fatent; /* Number of FAT entries (number of clusters + 2) */
DWORD fsize; /* Number of sectors per FAT */
LBA_t volbase; /* Volume base sector */
LBA_t fatbase; /* FAT base sector */
LBA_t dirbase; /* Root directory base sector (FAT12/16) or cluster (FAT32/exFAT) */
LBA_t database; /* Data base sector */
#if FF_FS_EXFAT
LBA_t bitbase; /* Allocation bitmap base sector */
#endif
LBA_t winsect; /* Current sector appearing in the win[] */
BYTE win[FF_MAX_SS]; /* Disk access window for Directory, FAT (and file data at tiny cfg) */
} FATFS;
/* Object ID and allocation information (FFOBJID) */
typedef struct {
FATFS* fs; /* Pointer to the hosting volume of this object */
WORD id; /* Hosting volume's mount ID */
BYTE attr; /* Object attribute */
BYTE stat; /* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
DWORD sclust; /* Object data start cluster (0:no cluster or root directory) */
FSIZE_t objsize; /* Object size (valid when sclust != 0) */
#if FF_FS_EXFAT
DWORD n_cont; /* Size of first fragment - 1 (valid when stat == 3) */
DWORD n_frag; /* Size of last fragment needs to be written to FAT (valid when not zero) */
DWORD c_scl; /* Containing directory start cluster (valid when sclust != 0) */
DWORD c_size; /* b31-b8:Size of containing directory, b7-b0: Chain status (valid when c_scl != 0) */
DWORD c_ofs; /* Offset in the containing directory (valid when file object and sclust != 0) */
#endif
#if FF_FS_LOCK
UINT lockid; /* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} FFOBJID;
/* File object structure (FIL) */
typedef struct {
FFOBJID obj; /* Object identifier (must be the 1st member to detect invalid object pointer) */
BYTE flag; /* File status flags */
BYTE err; /* Abort flag (error code) */
FSIZE_t fptr; /* File read/write pointer (Zeroed on file open) */
DWORD clust; /* Current cluster of fpter (invalid when fptr is 0) */
LBA_t sect; /* Sector number appearing in buf[] (0:invalid) */
#if !FF_FS_READONLY
LBA_t dir_sect; /* Sector number containing the directory entry (not used at exFAT) */
BYTE* dir_ptr; /* Pointer to the directory entry in the win[] (not used at exFAT) */
#endif
#if FF_USE_FASTSEEK
DWORD* cltbl; /* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !FF_FS_TINY
BYTE buf[FF_MAX_SS]; /* File private data read/write window */
#endif
} FIL;
/* Directory object structure (DIR) */
typedef struct {
FFOBJID obj; /* Object identifier */
DWORD dptr; /* Current read/write offset */
DWORD clust; /* Current cluster */
LBA_t sect; /* Current sector (0:Read operation has terminated) */
BYTE* dir; /* Pointer to the directory item in the win[] */
BYTE fn[12]; /* SFN (in/out) {body[8],ext[3],status[1]} */
#if FF_USE_LFN
DWORD blk_ofs; /* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if FF_USE_FIND
const TCHAR* pat; /* Pointer to the name matching pattern */
#endif
} DIR;
/* File information structure (FILINFO) */
typedef struct {
FSIZE_t fsize; /* File size */
WORD fdate; /* Modified date */
WORD ftime; /* Modified time */
BYTE fattrib; /* File attribute */
#if FF_USE_LFN
TCHAR altname[FF_SFN_BUF + 1];/* Alternative file name */
TCHAR fname[FF_LFN_BUF + 1]; /* Primary file name */
#else
TCHAR fname[12 + 1]; /* File name */
#endif
} FILINFO;
/* Format parameter structure (MKFS_PARM) */
typedef struct {
BYTE fmt; /* Format option (FM_FAT, FM_FAT32, FM_EXFAT and FM_SFD) */
BYTE n_fat; /* Number of FATs */
UINT align; /* Data area alignment (sector) */
UINT n_root; /* Number of root directory entries */
DWORD au_size; /* Cluster size (byte) */
} MKFS_PARM;
/* File function return code (FRESULT) */
typedef enum {
FR_OK = 0, /* (0) Succeeded */
FR_DISK_ERR, /* (1) A hard error occurred in the low level disk I/O layer */
FR_INT_ERR, /* (2) Assertion failed */
FR_NOT_READY, /* (3) The physical drive cannot work */
FR_NO_FILE, /* (4) Could not find the file */
FR_NO_PATH, /* (5) Could not find the path */
FR_INVALID_NAME, /* (6) The path name format is invalid */
FR_DENIED, /* (7) Access denied due to prohibited access or directory full */
FR_EXIST, /* (8) Access denied due to prohibited access */
FR_INVALID_OBJECT, /* (9) The file/directory object is invalid */
FR_WRITE_PROTECTED, /* (10) The physical drive is write protected */
FR_INVALID_DRIVE, /* (11) The logical drive number is invalid */
FR_NOT_ENABLED, /* (12) The volume has no work area */
FR_NO_FILESYSTEM, /* (13) There is no valid FAT volume */
FR_MKFS_ABORTED, /* (14) The f_mkfs() aborted due to any problem */
FR_TIMEOUT, /* (15) Could not get a grant to access the volume within defined period */
FR_LOCKED, /* (16) The operation is rejected according to the file sharing policy */
FR_NOT_ENOUGH_CORE, /* (17) LFN working buffer could not be allocated */
FR_TOO_MANY_OPEN_FILES, /* (18) Number of open files > FF_FS_LOCK */
FR_INVALID_PARAMETER /* (19) Given parameter is invalid */
} FRESULT;
/*--------------------------------------------------------------*/
/* FatFs Module Application Interface */
/*--------------------------------------------------------------*/
FRESULT f_open (FIL* fp, const TCHAR* path, BYTE mode); /* Open or create a file */
FRESULT f_close (FIL* fp); /* Close an open file object */
FRESULT f_read (FIL* fp, void* buff, UINT btr, UINT* br); /* Read data from the file */
FRESULT f_write (FIL* fp, const void* buff, UINT btw, UINT* bw); /* Write data to the file */
FRESULT f_lseek (FIL* fp, FSIZE_t ofs); /* Move file pointer of the file object */
FRESULT f_truncate (FIL* fp); /* Truncate the file */
FRESULT f_sync (FIL* fp); /* Flush cached data of the writing file */
FRESULT f_opendir (DIR* dp, const TCHAR* path); /* Open a directory */
FRESULT f_closedir (DIR* dp); /* Close an open directory */
FRESULT f_readdir (DIR* dp, FILINFO* fno); /* Read a directory item */
FRESULT f_findfirst (DIR* dp, FILINFO* fno, const TCHAR* path, const TCHAR* pattern); /* Find first file */
FRESULT f_findnext (DIR* dp, FILINFO* fno); /* Find next file */
FRESULT f_mkdir (const TCHAR* path); /* Create a sub directory */
FRESULT f_unlink (const TCHAR* path); /* Delete an existing file or directory */
FRESULT f_rename (const TCHAR* path_old, const TCHAR* path_new); /* Rename/Move a file or directory */
FRESULT f_stat (const TCHAR* path, FILINFO* fno); /* Get file status */
FRESULT f_chmod (const TCHAR* path, BYTE attr, BYTE mask); /* Change attribute of a file/dir */
FRESULT f_utime (const TCHAR* path, const FILINFO* fno); /* Change timestamp of a file/dir */
FRESULT f_chdir (const TCHAR* path); /* Change current directory */
FRESULT f_chdrive (const TCHAR* path); /* Change current drive */
FRESULT f_getcwd (TCHAR* buff, UINT len); /* Get current directory */
FRESULT f_getfree (const TCHAR* path, DWORD* nclst, FATFS** fatfs); /* Get number of free clusters on the drive */
FRESULT f_getlabel (const TCHAR* path, TCHAR* label, DWORD* vsn); /* Get volume label */
FRESULT f_setlabel (const TCHAR* label); /* Set volume label */
FRESULT f_forward (FIL* fp, UINT(*func)(const BYTE*,UINT), UINT btf, UINT* bf); /* Forward data to the stream */
FRESULT f_expand (FIL* fp, FSIZE_t fsz, BYTE opt); /* Allocate a contiguous block to the file */
FRESULT f_mount (FATFS* fs, const TCHAR* path, BYTE opt); /* Mount/Unmount a logical drive */
FRESULT f_mkfs (const TCHAR* path, const MKFS_PARM* opt, void* work, UINT len); /* Create a FAT volume */
FRESULT f_fdisk (BYTE pdrv, const LBA_t ptbl[], void* work); /* Divide a physical drive into some partitions */
FRESULT f_setcp (WORD cp); /* Set current code page */
int f_putc (TCHAR c, FIL* fp); /* Put a character to the file */
int f_puts (const TCHAR* str, FIL* cp); /* Put a string to the file */
int f_printf (FIL* fp, const TCHAR* str, ...); /* Put a formatted string to the file */
TCHAR* f_gets (TCHAR* buff, int len, FIL* fp); /* Get a string from the file */
/* Some API fucntions are implemented as macro */
#define f_eof(fp) ((int)((fp)->fptr == (fp)->obj.objsize))
#define f_error(fp) ((fp)->err)
#define f_tell(fp) ((fp)->fptr)
#define f_size(fp) ((fp)->obj.objsize)
#define f_rewind(fp) f_lseek((fp), 0)
#define f_rewinddir(dp) f_readdir((dp), 0)
#define f_rmdir(path) f_unlink(path)
#define f_unmount(path) f_mount(0, path, 0)
/*--------------------------------------------------------------*/
/* Additional Functions */
/*--------------------------------------------------------------*/
/* RTC function (provided by user) */
#if !FF_FS_READONLY && !FF_FS_NORTC
DWORD get_fattime (void); /* Get current time */
#endif
/* LFN support functions (defined in ffunicode.c) */
#if FF_USE_LFN >= 1
WCHAR ff_oem2uni (WCHAR oem, WORD cp); /* OEM code to Unicode conversion */
WCHAR ff_uni2oem (DWORD uni, WORD cp); /* Unicode to OEM code conversion */
DWORD ff_wtoupper (DWORD uni); /* Unicode upper-case conversion */
#endif
/* O/S dependent functions (samples available in ffsystem.c) */
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
void* ff_memalloc (UINT msize); /* Allocate memory block */
void ff_memfree (void* mblock); /* Free memory block */
#endif
#if FF_FS_REENTRANT /* Sync functions */
int ff_mutex_create (int vol); /* Create a sync object */
void ff_mutex_delete (int vol); /* Delete a sync object */
int ff_mutex_take (int vol); /* Lock sync object */
void ff_mutex_give (int vol); /* Unlock sync object */
#endif
/*--------------------------------------------------------------*/
/* Flags and Offset Address */
/*--------------------------------------------------------------*/
/* File access mode and open method flags (3rd argument of f_open) */
#define FA_READ 0x01
#define FA_WRITE 0x02
#define FA_OPEN_EXISTING 0x00
#define FA_CREATE_NEW 0x04
#define FA_CREATE_ALWAYS 0x08
#define FA_OPEN_ALWAYS 0x10
#define FA_OPEN_APPEND 0x30
/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP ((FSIZE_t)0 - 1)
/* Format options (2nd argument of f_mkfs) */
#define FM_FAT 0x01
#define FM_FAT32 0x02
#define FM_EXFAT 0x04
#define FM_ANY 0x07
#define FM_SFD 0x08
/* Filesystem type (FATFS.fs_type) */
#define FS_FAT12 1
#define FS_FAT16 2
#define FS_FAT32 3
#define FS_EXFAT 4
/* File attribute bits for directory entry (FILINFO.fattrib) */
#define AM_RDO 0x01 /* Read only */
#define AM_HID 0x02 /* Hidden */
#define AM_SYS 0x04 /* System */
#define AM_DIR 0x10 /* Directory */
#define AM_ARC 0x20 /* Archive */
#ifdef __cplusplus
}
#endif
#endif /* FF_DEFINED */

41
Lib/FatFs/ffconf.h Normal file
View File

@@ -0,0 +1,41 @@
#define FFCONF_DEF 80286
#define FF_FS_READONLY 0
#define FF_FS_MINIMIZE 0
#define FF_USE_FIND 0
#define FF_USE_MKFS 1
#define FF_USE_FASTSEEK 0
#define FF_USE_EXPAND 0
#define FF_USE_CHMOD 0
#define FF_USE_LABEL 0
#define FF_USE_FORWARD 0
#define FF_USE_STRFUNC 0
#define FF_PRINT_LLI 1
#define FF_PRINT_FLOAT 1
#define FF_STRF_ENCODE 3
#define FF_CODE_PAGE 437
#define FF_USE_LFN 0
#define FF_MAX_LFN 255
#define FF_LFN_UNICODE 0
#define FF_LFN_BUF 255
#define FF_SFN_BUF 12
#define FF_FS_RPATH 0
#define FF_VOLUMES 1
#define FF_STR_VOLUME_ID 0
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
#define FF_MULTI_PARTITION 0
#define FF_MIN_SS 512
#define FF_MAX_SS 512
#define FF_LBA64 0
#define FF_MIN_GPT 0x10000000
#define FF_USE_TRIM 0
#define FF_FS_TINY 0
#define FF_FS_EXFAT 0
#define FF_FS_NORTC 1
#define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2026
#define FF_FS_NOFSINFO 0
#define FF_FS_LOCK 0
#define FF_FS_REENTRANT 0
#define FF_FS_TIMEOUT 1000

50
Lib/dhara/bytes.h Normal file
View File

@@ -0,0 +1,50 @@
/* Dhara - NAND flash management layer
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef DHARA_BYTES_H_
#define DHARA_BYTES_H_
#include <stdint.h>
static inline uint16_t dhara_r16(const uint8_t *data)
{
return ((uint16_t)data[0]) |
(((uint16_t)data[1]) << 8);
}
static inline void dhara_w16(uint8_t *data, uint16_t v)
{
data[0] = v;
data[1] = v >> 8;
}
static inline uint32_t dhara_r32(const uint8_t *data)
{
return ((uint32_t)data[0]) |
(((uint32_t)data[1]) << 8) |
(((uint32_t)data[2]) << 16) |
(((uint32_t)data[3]) << 24);
}
static inline void dhara_w32(uint8_t *data, uint32_t v)
{
data[0] = v;
data[1] = v >> 8;
data[2] = v >> 16;
data[3] = v >> 24;
}
#endif

45
Lib/dhara/error.h Normal file
View File

@@ -0,0 +1,45 @@
/* Dhara - NAND flash management layer
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef DHARA_ERROR_H_
#define DHARA_ERROR_H_
typedef enum {
DHARA_E_NONE = 0,
DHARA_E_BAD_BLOCK,
DHARA_E_ECC,
DHARA_E_TOO_BAD,
DHARA_E_RECOVER,
DHARA_E_JOURNAL_FULL,
DHARA_E_NOT_FOUND,
DHARA_E_MAP_FULL,
DHARA_E_CORRUPT_MAP,
DHARA_E_MAX
} dhara_error_t;
/* Produce a human-readable error message. This function is kept in a
* separate compilation unit and can be omitted to reduce binary size.
*/
const char *dhara_strerror(dhara_error_t err);
/* Save an error */
static inline void dhara_set_error(dhara_error_t *err, dhara_error_t v)
{
if (err)
*err = v;
}
#endif

884
Lib/dhara/journal.c Normal file
View File

@@ -0,0 +1,884 @@
/* Dhara - NAND flash management layer
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <string.h>
#include "journal.h"
#include "bytes.h"
/************************************************************************
* Metapage binary format
*/
/* Does the page buffer contain a valid checkpoint page? */
static inline int hdr_has_magic(const uint8_t *buf)
{
return (buf[0] == 'D') &&
(buf[1] == 'h') &&
(buf[2] == 'a');
}
static inline void hdr_put_magic(uint8_t *buf)
{
buf[0] = 'D';
buf[1] = 'h';
buf[2] = 'a';
}
/* What epoch is this page? */
static inline uint8_t hdr_get_epoch(const uint8_t *buf)
{
return buf[3];
}
static inline void hdr_set_epoch(uint8_t *buf, uint8_t e)
{
buf[3] = e;
}
static inline dhara_page_t hdr_get_tail(const uint8_t *buf)
{
return dhara_r32(buf + 4);
}
static inline void hdr_set_tail(uint8_t *buf, dhara_page_t tail)
{
dhara_w32(buf + 4, tail);
}
static inline dhara_page_t hdr_get_bb_current(const uint8_t *buf)
{
return dhara_r32(buf + 8);
}
static inline void hdr_set_bb_current(uint8_t *buf, dhara_page_t count)
{
dhara_w32(buf + 8, count);
}
static inline dhara_page_t hdr_get_bb_last(const uint8_t *buf)
{
return dhara_r32(buf + 12);
}
static inline void hdr_set_bb_last(uint8_t *buf, dhara_page_t count)
{
dhara_w32(buf + 12, count);
}
/* Clear user metadata */
static inline void hdr_clear_user(uint8_t *buf, uint8_t log2_page_size)
{
memset(buf + DHARA_HEADER_SIZE + DHARA_COOKIE_SIZE, 0xff,
(1 << log2_page_size) - DHARA_HEADER_SIZE - DHARA_COOKIE_SIZE);
}
/* Obtain pointers to user data */
static inline size_t hdr_user_offset(uint8_t which)
{
return DHARA_HEADER_SIZE + DHARA_COOKIE_SIZE +
which * DHARA_META_SIZE;
}
/************************************************************************
* Page geometry helpers
*/
/* Is this page index aligned to N bits? */
static inline int is_aligned(dhara_page_t p, int n)
{
return !(p & ((1 << n) - 1));
}
/* Are these two pages from the same alignment group? */
static inline int align_eq(dhara_page_t a, dhara_page_t b,
int n)
{
return !((a ^ b) >> n);
}
/* What is the successor of this block? */
static dhara_block_t next_block(const struct dhara_nand *n, dhara_block_t blk)
{
blk++;
if (blk >= n->num_blocks)
blk = 0;
return blk;
}
static dhara_page_t next_upage(const struct dhara_journal *j,
dhara_page_t p)
{
p++;
if (is_aligned(p + 1, j->log2_ppc))
p++;
if (p >= (j->nand->num_blocks << j->nand->log2_ppb))
p = 0;
return p;
}
/* Calculate a checkpoint period: the largest value of ppc such that
* (2**ppc - 1) metadata blocks can fit on a page with one journal
* header.
*/
static int choose_ppc(int log2_page_size, int max)
{
const int max_meta = (1 << log2_page_size) -
DHARA_HEADER_SIZE - DHARA_COOKIE_SIZE;
int total_meta = DHARA_META_SIZE;
int ppc = 1;
while (ppc < max) {
total_meta <<= 1;
total_meta += DHARA_META_SIZE;
if (total_meta > max_meta)
break;
ppc++;
}
return ppc;
}
/************************************************************************
* Journal setup/resume
*/
/* Clear recovery status */
static void clear_recovery(struct dhara_journal *j)
{
j->recover_next = DHARA_PAGE_NONE;
j->recover_root = DHARA_PAGE_NONE;
j->recover_meta = DHARA_PAGE_NONE;
j->flags &= ~(DHARA_JOURNAL_F_BAD_META |
DHARA_JOURNAL_F_RECOVERY |
DHARA_JOURNAL_F_ENUM_DONE);
}
/* Set up an empty journal */
static void reset_journal(struct dhara_journal *j)
{
/* We don't yet have a bad block estimate, so make a
* conservative guess.
*/
j->epoch = 0;
j->bb_last = j->nand->num_blocks >> 6;
j->bb_current = 0;
j->flags = 0;
/* Empty journal */
j->head = 0;
j->tail = 0;
j->tail_sync = 0;
j->root = DHARA_PAGE_NONE;
/* No recovery required */
clear_recovery(j);
/* Empty metadata buffer */
memset(j->page_buf, 0xff, 1 << j->nand->log2_page_size);
}
static void roll_stats(struct dhara_journal *j)
{
j->bb_last = j->bb_current;
j->bb_current = 0;
j->epoch++;
}
void dhara_journal_init(struct dhara_journal *j,
const struct dhara_nand *n,
uint8_t *page_buf)
{
/* Set fixed parameters */
j->nand = n;
j->page_buf = page_buf;
j->log2_ppc = choose_ppc(n->log2_page_size, n->log2_ppb);
reset_journal(j);
}
/* Find the first checkpoint-containing block. If a block contains any
* checkpoints at all, then it must contain one in the first checkpoint
* location -- otherwise, we would have considered the block eraseable.
*/
static int find_checkblock(struct dhara_journal *j,
dhara_block_t blk, dhara_block_t *where,
dhara_error_t *err)
{
int i;
for (i = 0; (blk < j->nand->num_blocks) &&
(i < DHARA_MAX_RETRIES); i++) {
const dhara_page_t p =
(blk << j->nand->log2_ppb) |
((1 << j->log2_ppc) - 1);
if (!(dhara_nand_is_bad(j->nand, blk) ||
dhara_nand_read(j->nand, p,
0, 1 << j->nand->log2_page_size,
j->page_buf, err)) &&
hdr_has_magic(j->page_buf)) {
*where = blk;
return 0;
}
blk++;
}
dhara_set_error(err, DHARA_E_TOO_BAD);
return -1;
}
static dhara_block_t find_last_checkblock(struct dhara_journal *j,
dhara_block_t first)
{
dhara_block_t low = first;
dhara_block_t high = j->nand->num_blocks - 1;
while (low <= high) {
const dhara_block_t mid = (low + high) >> 1;
dhara_block_t found;
if ((find_checkblock(j, mid, &found, NULL) < 0) ||
(hdr_get_epoch(j->page_buf) != j->epoch)) {
if (!mid)
return first;
high = mid - 1;
} else {
dhara_block_t nf;
if (((found + 1) >= j->nand->num_blocks) ||
(find_checkblock(j, found + 1,
&nf, NULL) < 0) ||
(hdr_get_epoch(j->page_buf) != j->epoch))
return found;
low = nf;
}
}
return first;
}
/* Test whether a checkpoint group is in a state fit for reprogramming,
* but allow for the fact that is_free() might not have any way of
* distinguishing between an unprogrammed page, and a page programmed
* with all-0xff bytes (but if so, it must be ok to reprogram such a
* page).
*
* We used to test for an unprogrammed checkpoint group by checking to
* see if the first user-page had been programmed since last erase (by
* testing only the first page with is_free). This works if is_free is
* precise, because the pages are written in order.
*
* If is_free is imprecise, we need to check all pages in the group.
* That also works, because the final page in a checkpoint group is
* guaranteed to contain non-0xff bytes. Therefore, we return 1 only if
* the group is truly unprogrammed, or if it was partially programmed
* with some all-0xff user pages (which changes nothing for us).
*/
static int cp_free(struct dhara_journal *j, dhara_page_t first_user)
{
const int count = 1 << j->log2_ppc;
int i;
for (i = 0; i < count; i++)
if (!dhara_nand_is_free(j->nand, first_user + i))
return 0;
return 1;
}
static dhara_page_t find_last_group(struct dhara_journal *j,
dhara_block_t blk)
{
const int num_groups = 1 << (j->nand->log2_ppb - j->log2_ppc);
int low = 0;
int high = num_groups - 1;
/* If a checkpoint group is completely unprogrammed, everything
* following it will be completely unprogrammed also.
*
* Therefore, binary search checkpoint groups until we find the
* last programmed one.
*/
while (low <= high) {
int mid = (low + high) >> 1;
const dhara_page_t p = (mid << j->log2_ppc) |
(blk << j->nand->log2_ppb);
if (cp_free(j, p)) {
high = mid - 1;
} else if (((mid + 1) >= num_groups) ||
cp_free(j, p + (1 << j->log2_ppc))) {
return p;
} else {
low = mid + 1;
}
}
return blk << j->nand->log2_ppb;
}
static int find_root(struct dhara_journal *j, dhara_page_t start,
dhara_error_t *err)
{
const dhara_block_t blk = start >> j->nand->log2_ppb;
int i = (start & ((1 << j->nand->log2_ppb) - 1)) >> j->log2_ppc;
while (i >= 0) {
const dhara_page_t p = (blk << j->nand->log2_ppb) +
((i + 1) << j->log2_ppc) - 1;
if (!dhara_nand_read(j->nand, p,
0, 1 << j->nand->log2_page_size,
j->page_buf, err) &&
(hdr_has_magic(j->page_buf)) &&
(hdr_get_epoch(j->page_buf) == j->epoch)) {
j->root = p - 1;
return 0;
}
i--;
}
dhara_set_error(err, DHARA_E_TOO_BAD);
return -1;
}
static int find_head(struct dhara_journal *j, dhara_page_t start,
dhara_error_t *err)
{
j->head = next_upage(j, start);
if (!j->head)
roll_stats(j);
/* Starting from the last good checkpoint, find either:
*
* (a) the next free user-page in the same block
* (b) or, the first page of the next block
*
* The block we end up on might be bad, but that's ok -- we'll
* skip it when we go to prepare the next write.
*/
for (;;) {
/* How many free pages trail this checkpoint group? */
const unsigned int ppc = 1 << j->log2_ppc;
unsigned int n = 0;
dhara_page_t first = j->head & ~(dhara_page_t)(ppc - 1);
while (n < ppc &&
dhara_nand_is_free(j->nand, first + ppc - n - 1))
n++;
/* If we have some, then we've found our next free
* userpage.
*/
if (n > 1) {
j->head = first + ppc - n;
break;
}
/* Skip to the next checkpoint group */
j->head = first + ppc;
if (j->head >= (j->nand->num_blocks << j->nand->log2_ppb)) {
j->head = 0;
roll_stats(j);
}
/* If we hit the end of the block, we're done */
if (is_aligned(j->head, j->nand->log2_ppb)) {
/* Make sure we don't chase over the tail */
if (align_eq(j->head, j->tail, j->nand->log2_ppb))
j->tail = next_block(j->nand,
j->tail >> j->nand->log2_ppb) <<
j->nand->log2_ppb;
break;
}
}
return 0;
}
int dhara_journal_resume(struct dhara_journal *j, dhara_error_t *err)
{
dhara_block_t first, last;
dhara_page_t last_group;
/* Find the first checkpoint-containing block */
if (find_checkblock(j, 0, &first, err) < 0) {
reset_journal(j);
return -1;
}
/* Find the last checkpoint-containing block in this epoch */
j->epoch = hdr_get_epoch(j->page_buf);
last = find_last_checkblock(j, first);
/* Find the last programmed checkpoint group in the block */
last_group = find_last_group(j, last);
/* Perform a linear scan to find the last good checkpoint (and
* therefore the root).
*/
if (find_root(j, last_group, err) < 0) {
reset_journal(j);
return -1;
}
/* Restore settings from checkpoint */
j->tail = hdr_get_tail(j->page_buf);
j->bb_current = hdr_get_bb_current(j->page_buf);
j->bb_last = hdr_get_bb_last(j->page_buf);
hdr_clear_user(j->page_buf, j->nand->log2_page_size);
/* Perform another linear scan to find the next free user page */
if (find_head(j, last_group, err) < 0) {
reset_journal(j);
return -1;
}
j->flags = 0;
j->tail_sync = j->tail;
clear_recovery(j);
return 0;
}
/**************************************************************************
* Public interface
*/
dhara_page_t dhara_journal_capacity(const struct dhara_journal *j)
{
const dhara_block_t max_bad = j->bb_last > j->bb_current ?
j->bb_last : j->bb_current;
const dhara_block_t good_blocks = j->nand->num_blocks - max_bad - 1;
const int log2_cpb = j->nand->log2_ppb - j->log2_ppc;
const dhara_page_t good_cps = good_blocks << log2_cpb;
/* Good checkpoints * (checkpoint period - 1) */
return (good_cps << j->log2_ppc) - good_cps;
}
dhara_page_t dhara_journal_size(const struct dhara_journal *j)
{
/* Find the number of raw pages, and the number of checkpoints
* between the head and the tail. The difference between the two
* is the number of user pages (upper limit).
*/
dhara_page_t num_pages = j->head;
dhara_page_t num_cps = j->head >> j->log2_ppc;
if (j->head < j->tail_sync) {
const dhara_page_t total_pages =
j->nand->num_blocks << j->nand->log2_ppb;
num_pages += total_pages;
num_cps += total_pages >> j->log2_ppc;
}
num_pages -= j->tail_sync;
num_cps -= j->tail_sync >> j->log2_ppc;
return num_pages - num_cps;
}
int dhara_journal_read_meta(struct dhara_journal *j, dhara_page_t p,
uint8_t *buf, dhara_error_t *err)
{
/* Offset of metadata within the metadata page */
const dhara_page_t ppc_mask = (1 << j->log2_ppc) - 1;
const size_t offset = hdr_user_offset(p & ppc_mask);
/* Special case: buffered metadata */
if (align_eq(p, j->head, j->log2_ppc)) {
memcpy(buf, j->page_buf + offset, DHARA_META_SIZE);
return 0;
}
/* Special case: incomplete metadata dumped at start of
* recovery.
*/
if ((j->recover_meta != DHARA_PAGE_NONE) &&
align_eq(p, j->recover_root, j->log2_ppc))
return dhara_nand_read(j->nand, j->recover_meta,
offset, DHARA_META_SIZE,
buf, err);
/* General case: fetch from metadata page for checkpoint group */
return dhara_nand_read(j->nand, p | ppc_mask,
offset, DHARA_META_SIZE,
buf, err);
}
dhara_page_t dhara_journal_peek(struct dhara_journal *j)
{
if (j->head == j->tail)
return DHARA_PAGE_NONE;
if (is_aligned(j->tail, j->nand->log2_ppb)) {
dhara_block_t blk = j->tail >> j->nand->log2_ppb;
int i;
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
if ((blk == (j->head >> j->nand->log2_ppb)) ||
!dhara_nand_is_bad(j->nand, blk)) {
j->tail = blk << j->nand->log2_ppb;
if (j->tail == j->head)
j->root = DHARA_PAGE_NONE;
return j->tail;
}
blk = next_block(j->nand, blk);
}
}
return j->tail;
}
static dhara_page_t wrap(dhara_page_t a, dhara_page_t b)
{
return a >= b ? (a - b) : a;
}
void dhara_journal_dequeue(struct dhara_journal *j)
{
if (j->head == j->tail)
return;
j->tail = next_upage(j, j->tail);
/* If the journal is clean at the time of dequeue, then this
* data was always obsolete, and can be reused immediately.
*/
if (!(j->flags & (DHARA_JOURNAL_F_DIRTY | DHARA_JOURNAL_F_RECOVERY)))
j->tail_sync = j->tail;
const dhara_page_t chip_size = j->nand->num_blocks << j->nand->log2_ppb;
const dhara_page_t raw_size = wrap(j->head + chip_size - j->tail,
chip_size);
const dhara_page_t root_offset = wrap(j->head + chip_size - j->root,
chip_size);
if (root_offset > raw_size)
j->root = DHARA_PAGE_NONE;
}
void dhara_journal_clear(struct dhara_journal *j)
{
j->tail = j->head;
j->root = DHARA_PAGE_NONE;
j->flags |= DHARA_JOURNAL_F_DIRTY;
hdr_clear_user(j->page_buf, j->nand->log2_page_size);
}
static int skip_block(struct dhara_journal *j, dhara_error_t *err)
{
const dhara_block_t next = next_block(j->nand,
j->head >> j->nand->log2_ppb);
/* We can't roll onto the same block as the tail */
if ((j->tail_sync >> j->nand->log2_ppb) == next) {
dhara_set_error(err, DHARA_E_JOURNAL_FULL);
return -1;
}
j->head = next << j->nand->log2_ppb;
if (!j->head)
roll_stats(j);
return 0;
}
/* Make sure the head pointer is on a ready-to-program page. */
static int prepare_head(struct dhara_journal *j, dhara_error_t *err)
{
const dhara_page_t next = next_upage(j, j->head);
int i;
/* We can't write if doing so would cause the head pointer to
* roll onto the same block as the last-synced tail.
*/
if (align_eq(next, j->tail_sync, j->nand->log2_ppb) &&
!align_eq(next, j->head, j->nand->log2_ppb)) {
dhara_set_error(err, DHARA_E_JOURNAL_FULL);
return -1;
}
j->flags |= DHARA_JOURNAL_F_DIRTY;
if (!is_aligned(j->head, j->nand->log2_ppb))
return 0;
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
const dhara_block_t blk = j->head >> j->nand->log2_ppb;
if (!dhara_nand_is_bad(j->nand, blk))
return dhara_nand_erase(j->nand, blk, err);
j->bb_current++;
if (skip_block(j, err) < 0)
return -1;
}
dhara_set_error(err, DHARA_E_TOO_BAD);
return -1;
}
static void restart_recovery(struct dhara_journal *j, dhara_page_t old_head)
{
/* Mark the current head bad immediately, unless we're also
* using it to hold our dumped metadata (it will then be marked
* bad at the end of recovery).
*/
if ((j->recover_meta == DHARA_PAGE_NONE) ||
!align_eq(j->recover_meta, old_head, j->nand->log2_ppb))
dhara_nand_mark_bad(j->nand, old_head >> j->nand->log2_ppb);
else
j->flags |= DHARA_JOURNAL_F_BAD_META;
/* Start recovery again. Reset the source enumeration to
* the start of the original bad block, and reset the
* destination enumeration to the newly found good
* block.
*/
j->flags &= ~DHARA_JOURNAL_F_ENUM_DONE;
j->recover_next =
j->recover_root & ~((1 << j->nand->log2_ppb) - 1);
j->root = j->recover_root;
}
static int dump_meta(struct dhara_journal *j, dhara_error_t *err)
{
int i;
/* We've just begun recovery on a new erasable block, but we
* have buffered metadata from the failed block.
*/
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
dhara_error_t my_err;
/* Try to dump metadata on this page */
if (!(prepare_head(j, &my_err) ||
dhara_nand_prog(j->nand, j->head,
j->page_buf, &my_err))) {
j->recover_meta = j->head;
j->head = next_upage(j, j->head);
if (!j->head)
roll_stats(j);
hdr_clear_user(j->page_buf, j->nand->log2_page_size);
return 0;
}
/* Report fatal errors */
if (my_err != DHARA_E_BAD_BLOCK) {
dhara_set_error(err, my_err);
return -1;
}
j->bb_current++;
dhara_nand_mark_bad(j->nand, j->head >> j->nand->log2_ppb);
if (skip_block(j, err) < 0)
return -1;
}
dhara_set_error(err, DHARA_E_TOO_BAD);
return -1;
}
static int recover_from(struct dhara_journal *j,
dhara_error_t write_err,
dhara_error_t *err)
{
const dhara_page_t old_head = j->head;
if (write_err != DHARA_E_BAD_BLOCK) {
dhara_set_error(err, write_err);
return -1;
}
/* Advance to the next free page */
j->bb_current++;
if (skip_block(j, err) < 0)
return -1;
/* Are we already in the middle of a recovery? */
if (dhara_journal_in_recovery(j)) {
restart_recovery(j, old_head);
dhara_set_error(err, DHARA_E_RECOVER);
return -1;
}
/* Were we block aligned? No recovery required! */
if (is_aligned(old_head, j->nand->log2_ppb)) {
dhara_nand_mark_bad(j->nand, old_head >> j->nand->log2_ppb);
return 0;
}
j->recover_root = j->root;
j->recover_next =
j->recover_root & ~((1 << j->nand->log2_ppb) - 1);
/* Are we holding buffered metadata? Dump it first. */
if (!is_aligned(old_head, j->log2_ppc) &&
dump_meta(j, err) < 0)
return -1;
j->flags |= DHARA_JOURNAL_F_RECOVERY;
dhara_set_error(err, DHARA_E_RECOVER);
return -1;
}
static void finish_recovery(struct dhara_journal *j)
{
/* We just recovered the last page. Mark the recovered
* block as bad.
*/
dhara_nand_mark_bad(j->nand,
j->recover_root >> j->nand->log2_ppb);
/* If we had to dump metadata, and the page on which we
* did this also went bad, mark it bad too.
*/
if (j->flags & DHARA_JOURNAL_F_BAD_META)
dhara_nand_mark_bad(j->nand,
j->recover_meta >> j->nand->log2_ppb);
/* Was the tail on this page? Skip it forward */
clear_recovery(j);
}
static int push_meta(struct dhara_journal *j, const uint8_t *meta,
dhara_error_t *err)
{
const dhara_page_t old_head = j->head;
dhara_error_t my_err;
const size_t offset =
hdr_user_offset(j->head & ((1 << j->log2_ppc) - 1));
/* We've just written a user page. Add the metadata to the
* buffer.
*/
if (meta)
memcpy(j->page_buf + offset, meta, DHARA_META_SIZE);
else
memset(j->page_buf + offset, 0xff, DHARA_META_SIZE);
/* Unless we've filled the buffer, don't do any IO */
if (!is_aligned(j->head + 2, j->log2_ppc)) {
j->root = j->head;
j->head++;
return 0;
}
/* We don't need to check for immediate recover, because that'll
* never happen -- we're not block-aligned.
*/
hdr_put_magic(j->page_buf);
hdr_set_epoch(j->page_buf, j->epoch);
hdr_set_tail(j->page_buf, j->tail);
hdr_set_bb_current(j->page_buf, j->bb_current);
hdr_set_bb_last(j->page_buf, j->bb_last);
if (dhara_nand_prog(j->nand, j->head + 1, j->page_buf, &my_err) < 0)
return recover_from(j, my_err, err);
j->flags &= ~DHARA_JOURNAL_F_DIRTY;
j->root = old_head;
j->head = next_upage(j, j->head);
if (!j->head)
roll_stats(j);
if (j->flags & DHARA_JOURNAL_F_ENUM_DONE)
finish_recovery(j);
if (!(j->flags & DHARA_JOURNAL_F_RECOVERY))
j->tail_sync = j->tail;
return 0;
}
int dhara_journal_enqueue(struct dhara_journal *j,
const uint8_t *data, const uint8_t *meta,
dhara_error_t *err)
{
dhara_error_t my_err;
int i;
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
if (!(prepare_head(j, &my_err) ||
(data && dhara_nand_prog(j->nand, j->head, data,
&my_err))))
return push_meta(j, meta, err);
if (recover_from(j, my_err, err) < 0)
return -1;
}
dhara_set_error(err, DHARA_E_TOO_BAD);
return -1;
}
int dhara_journal_copy(struct dhara_journal *j,
dhara_page_t p, const uint8_t *meta,
dhara_error_t *err)
{
dhara_error_t my_err;
int i;
for (i = 0; i < DHARA_MAX_RETRIES; i++) {
if (!(prepare_head(j, &my_err) ||
dhara_nand_copy(j->nand, p, j->head, &my_err)))
return push_meta(j, meta, err);
if (recover_from(j, my_err, err) < 0)
return -1;
}
dhara_set_error(err, DHARA_E_TOO_BAD);
return -1;
}
dhara_page_t dhara_journal_next_recoverable(struct dhara_journal *j)
{
const dhara_page_t n = j->recover_next;
if (!dhara_journal_in_recovery(j))
return DHARA_PAGE_NONE;
if (j->flags & DHARA_JOURNAL_F_ENUM_DONE)
return DHARA_PAGE_NONE;
if (j->recover_next == j->recover_root)
j->flags |= DHARA_JOURNAL_F_ENUM_DONE;
else
j->recover_next = next_upage(j, j->recover_next);
return n;
}

256
Lib/dhara/journal.h Normal file
View File

@@ -0,0 +1,256 @@
/* Dhara - NAND flash management layer
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef DHARA_JOURNAL_H_
#define DHARA_JOURNAL_H_
#include <stdint.h>
#include "nand.h"
/* Number of bytes used by the journal checkpoint header. */
#define DHARA_HEADER_SIZE 16
/* Global metadata available for a higher layer. This metadata is
* persistent once the journal reaches a checkpoint, and is restored on
* startup.
*/
#define DHARA_COOKIE_SIZE 4
/* This is the size of the metadata slice which accompanies each written
* page. This is independent of the underlying page/OOB size.
*/
#define DHARA_META_SIZE 132
/* When a block fails, or garbage is encountered, we try again on the
* next block/checkpoint. We can do this up to the given number of
* times.
*/
#define DHARA_MAX_RETRIES 8
/* This is a page number which can be used to represent "no such page".
* It's guaranteed to never be a valid user page.
*/
#define DHARA_PAGE_NONE ((dhara_page_t)0xffffffff)
/* State flags */
#define DHARA_JOURNAL_F_DIRTY 0x01
#define DHARA_JOURNAL_F_BAD_META 0x02
#define DHARA_JOURNAL_F_RECOVERY 0x04
#define DHARA_JOURNAL_F_ENUM_DONE 0x08
/* The journal layer presents the NAND pages as a double-ended queue.
* Pages, with associated metadata may be pushed onto the end of the
* queue, and pages may be popped from the end.
*
* Block erase, metadata storage are handled automatically. Bad blocks
* are handled by relocating data to the next available non-bad page in
* the sequence.
*
* It's up to the user to ensure that the queue doesn't grow beyond the
* capacity of the NAND chip, but helper functions are provided to
* assist with this. If the head meets the tail, the journal will refuse
* to enqueue more pages.
*/
struct dhara_journal {
const struct dhara_nand *nand;
uint8_t *page_buf;
/* In the journal, user data is grouped into checkpoints of
* 2**log2_ppc contiguous aligned pages.
*
* The last page of each checkpoint contains the journal header
* and the metadata for the other pages in the period (the user
* pages).
*/
uint8_t log2_ppc;
/* Epoch counter. This is incremented whenever the journal head
* passes the end of the chip and wraps around.
*/
uint8_t epoch;
/* General purpose flags field */
uint8_t flags;
/* Bad-block counters. bb_last is our best estimate of the
* number of bad blocks in the chip as a whole. bb_current is
* the number of bad blocks in all blocks before the current
* head.
*/
dhara_block_t bb_current;
dhara_block_t bb_last;
/* Log head and tail. The tail pointer points to the last user
* page in the log, and the head pointer points to the next free
* raw page. The root points to the last written user page.
*/
dhara_page_t tail_sync;
dhara_page_t tail;
dhara_page_t head;
/* This points to the last written user page in the journal */
dhara_page_t root;
/* Recovery mode: recover_root points to the last valid user
* page in the block requiring recovery. recover_next points to
* the next user page needing recovery.
*
* If we had buffered metadata before recovery started, it will
* have been dumped to a free page, indicated by recover_meta.
* If this block later goes bad, we will have to defer bad-block
* marking until recovery is complete (F_BAD_META).
*/
dhara_page_t recover_next;
dhara_page_t recover_root;
dhara_page_t recover_meta;
};
/* Initialize a journal. You must supply a pointer to a NAND chip
* driver, and a single page buffer. This page buffer will be used
* exclusively by the journal, but you are responsible for allocating
* it, and freeing it (if necessary) at the end.
*
* No NAND operations are performed at this point.
*/
void dhara_journal_init(struct dhara_journal *j,
const struct dhara_nand *n,
uint8_t *page_buf);
/* Start up the journal -- search the NAND for the journal head, or
* initialize a blank journal if one isn't found. Returns 0 on success
* or -1 if a (fatal) error occurs.
*
* This operation is O(log N), where N is the number of pages in the
* NAND chip. All other operations are O(1).
*
* If this operation fails, the journal will be reset to an empty state.
*/
int dhara_journal_resume(struct dhara_journal *j, dhara_error_t *err);
/* Obtain an upper bound on the number of user pages storable in the
* journal.
*/
dhara_page_t dhara_journal_capacity(const struct dhara_journal *j);
/* Obtain an upper bound on the number of user pages consumed by the
* journal.
*/
dhara_page_t dhara_journal_size(const struct dhara_journal *j);
/* Obtain a pointer to the cookie data */
static inline uint8_t *dhara_journal_cookie(const struct dhara_journal *j)
{
return j->page_buf + DHARA_HEADER_SIZE;
}
/* Obtain the locations of the first and last pages in the journal.
*/
static inline dhara_page_t dhara_journal_root(const struct dhara_journal *j)
{
return j->root;
}
/* Read metadata associated with a page. This assumes that the page
* provided is a valid data page. The actual page data is read via the
* normal NAND interface.
*/
int dhara_journal_read_meta(struct dhara_journal *j, dhara_page_t p,
uint8_t *buf, dhara_error_t *err);
/* Advance the tail to the next non-bad block and return the page that's
* ready to read. If no page is ready, return DHARA_PAGE_NONE.
*/
dhara_page_t dhara_journal_peek(struct dhara_journal *j);
/* Remove the last page from the journal. This doesn't take permanent
* effect until the next checkpoint.
*/
void dhara_journal_dequeue(struct dhara_journal *j);
/* Remove all pages form the journal. This doesn't take permanent effect
* until the next checkpoint.
*/
void dhara_journal_clear(struct dhara_journal *j);
/* Append a page to the journal. Both raw page data and metadata must be
* specified. The push operation is not persistent until a checkpoint is
* reached.
*
* This operation may fail with the error code E_RECOVER. If this
* occurs, the upper layer must complete the assisted recovery procedure
* and then try again.
*
* This operation may be used as part of a recovery. If further errors
* occur during recovery, E_RECOVER is returned, and the procedure must
* be restarted.
*/
int dhara_journal_enqueue(struct dhara_journal *j,
const uint8_t *data, const uint8_t *meta,
dhara_error_t *err);
/* Copy an existing page to the front of the journal. New metadata must
* be specified. This operation is not persistent until a checkpoint is
* reached.
*
* This operation may fail with the error code E_RECOVER. If this
* occurs, the upper layer must complete the assisted recovery procedure
* and then try again.
*
* This operation may be used as part of a recovery. If further errors
* occur during recovery, E_RECOVER is returned, and the procedure must
* be restarted.
*/
int dhara_journal_copy(struct dhara_journal *j,
dhara_page_t p, const uint8_t *meta,
dhara_error_t *err);
/* Mark the journal dirty. */
static inline void dhara_journal_mark_dirty(struct dhara_journal *j)
{
j->flags |= DHARA_JOURNAL_F_DIRTY;
}
/* Is the journal checkpointed? If true, then all pages enqueued are now
* persistent.
*/
static inline int dhara_journal_is_clean(const struct dhara_journal *j)
{
return !(j->flags & DHARA_JOURNAL_F_DIRTY);
}
/* If an operation returns E_RECOVER, you must begin the recovery
* procedure. You must then:
*
* - call dhara_journal_next_recoverable() to obtain the next block
* to be recovered (if any). If there are no blocks remaining to be
* recovered, DHARA_JOURNAL_PAGE_NONE is returned.
*
* - proceed to the next checkpoint. Once the journal is clean,
* recovery will finish automatically.
*
* If any operation during recovery fails due to a bad block, E_RECOVER
* is returned again, and recovery restarts. Do not add new data to the
* journal (rewrites of recovered data are fine) until recovery is
* complete.
*/
static inline int dhara_journal_in_recovery(const struct dhara_journal *j)
{
return j->flags & DHARA_JOURNAL_F_RECOVERY;
}
dhara_page_t dhara_journal_next_recoverable(struct dhara_journal *j);
#endif

530
Lib/dhara/map.c Normal file
View File

@@ -0,0 +1,530 @@
/* Dhara - NAND flash management layer
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include <string.h>
#include "bytes.h"
#include "map.h"
#define DHARA_RADIX_DEPTH (sizeof(dhara_sector_t) << 3)
static inline dhara_sector_t d_bit(int depth)
{
return ((dhara_sector_t)1) << (DHARA_RADIX_DEPTH - depth - 1);
}
/************************************************************************
* Metadata/cookie layout
*/
static inline void ck_set_count(uint8_t *cookie, dhara_sector_t count)
{
dhara_w32(cookie, count);
}
static inline dhara_sector_t ck_get_count(const uint8_t *cookie)
{
return dhara_r32(cookie);
}
static inline void meta_clear(uint8_t *meta)
{
memset(meta, 0xff, DHARA_META_SIZE);
}
static inline dhara_sector_t meta_get_id(const uint8_t *meta)
{
return dhara_r32(meta);
}
static inline void meta_set_id(uint8_t *meta, dhara_sector_t id)
{
dhara_w32(meta, id);
}
static inline dhara_page_t meta_get_alt(const uint8_t *meta, int level)
{
return dhara_r32(meta + 4 + (level << 2));
}
static inline void meta_set_alt(uint8_t *meta, int level, dhara_page_t alt)
{
dhara_w32(meta + 4 + (level << 2), alt);
}
/************************************************************************
* Public interface
*/
void dhara_map_init(struct dhara_map *m, const struct dhara_nand *n,
uint8_t *page_buf, uint8_t gc_ratio)
{
if (!gc_ratio)
gc_ratio = 1;
dhara_journal_init(&m->journal, n, page_buf);
m->gc_ratio = gc_ratio;
}
int dhara_map_resume(struct dhara_map *m, dhara_error_t *err)
{
if (dhara_journal_resume(&m->journal, err) < 0) {
m->count = 0;
return -1;
}
m->count = ck_get_count(dhara_journal_cookie(&m->journal));
return 0;
}
void dhara_map_clear(struct dhara_map *m)
{
if (m->count) {
m->count = 0;
dhara_journal_clear(&m->journal);
}
}
dhara_sector_t dhara_map_capacity(const struct dhara_map *m)
{
const dhara_sector_t cap = dhara_journal_capacity(&m->journal);
const dhara_sector_t reserve = cap / (m->gc_ratio + 1);
const dhara_sector_t safety_margin =
DHARA_MAX_RETRIES << m->journal.nand->log2_ppb;
if (reserve + safety_margin >= cap)
return 0;
return cap - reserve - safety_margin;
}
/* Trace the path from the root to the given sector, emitting
* alt-pointers and alt-full bits in the given metadata buffer. This
* also returns the physical page containing the given sector, if it
* exists.
*
* If the page can't be found, a suitable path will be constructed
* (containing PAGE_NONE alt-pointers), and DHARA_E_NOT_FOUND will be
* returned.
*/
static int trace_path(struct dhara_map *m, dhara_sector_t target,
dhara_page_t *loc, uint8_t *new_meta,
dhara_error_t *err)
{
uint8_t meta[DHARA_META_SIZE];
int depth = 0;
dhara_page_t p = dhara_journal_root(&m->journal);
if (new_meta)
meta_set_id(new_meta, target);
if (p == DHARA_PAGE_NONE)
goto not_found;
if (dhara_journal_read_meta(&m->journal, p, meta, err) < 0)
return -1;
while (depth < DHARA_RADIX_DEPTH) {
const dhara_sector_t id = meta_get_id(meta);
if (id == DHARA_SECTOR_NONE)
goto not_found;
if ((target ^ id) & d_bit(depth)) {
if (new_meta)
meta_set_alt(new_meta, depth, p);
p = meta_get_alt(meta, depth);
if (p == DHARA_PAGE_NONE) {
depth++;
goto not_found;
}
if (dhara_journal_read_meta(&m->journal, p,
meta, err) < 0)
return -1;
} else {
if (new_meta)
meta_set_alt(new_meta, depth,
meta_get_alt(meta, depth));
}
depth++;
}
if (loc)
*loc = p;
return 0;
not_found:
if (new_meta) {
while (depth < DHARA_RADIX_DEPTH)
meta_set_alt(new_meta, depth++, DHARA_SECTOR_NONE);
}
dhara_set_error(err, DHARA_E_NOT_FOUND);
return -1;
}
int dhara_map_find(struct dhara_map *m, dhara_sector_t target,
dhara_page_t *loc, dhara_error_t *err)
{
return trace_path(m, target, loc, NULL, err);
}
int dhara_map_read(struct dhara_map *m, dhara_sector_t s,
uint8_t *data, dhara_error_t *err)
{
const struct dhara_nand *n = m->journal.nand;
dhara_error_t my_err;
dhara_page_t p;
if (dhara_map_find(m, s, &p, &my_err) < 0) {
if (my_err == DHARA_E_NOT_FOUND) {
memset(data, 0xff, 1 << n->log2_page_size);
return 0;
}
dhara_set_error(err, my_err);
return -1;
}
return dhara_nand_read(n, p, 0, 1 << n->log2_page_size, data, err);
}
/* Check the given page. If it's garbage, do nothing. Otherwise, rewrite
* it at the front of the map. Return raw errors from the journal (do
* not perform recovery).
*/
static int raw_gc(struct dhara_map *m, dhara_page_t src,
dhara_error_t *err)
{
dhara_sector_t target;
dhara_page_t current;
dhara_error_t my_err;
uint8_t meta[DHARA_META_SIZE];
if (dhara_journal_read_meta(&m->journal, src, meta, err) < 0)
return -1;
/* Is the page just filler/garbage? */
target = meta_get_id(meta);
if (target == DHARA_SECTOR_NONE)
return 0;
/* Find out where the sector once represented by this page
* currently resides (if anywhere).
*/
if (trace_path(m, target, &current, meta, &my_err) < 0) {
if (my_err == DHARA_E_NOT_FOUND)
return 0;
dhara_set_error(err, my_err);
return -1;
}
/* Is this page still the most current representative? If not,
* do nothing.
*/
if (current != src)
return 0;
/* Rewrite it at the front of the journal with updated metadata */
ck_set_count(dhara_journal_cookie(&m->journal), m->count);
if (dhara_journal_copy(&m->journal, src, meta, err) < 0)
return -1;
return 0;
}
static int pad_queue(struct dhara_map *m, dhara_error_t *err)
{
dhara_page_t p = dhara_journal_root(&m->journal);
uint8_t root_meta[DHARA_META_SIZE];
ck_set_count(dhara_journal_cookie(&m->journal), m->count);
if (p == DHARA_PAGE_NONE)
return dhara_journal_enqueue(&m->journal, NULL, NULL, err);
if (dhara_journal_read_meta(&m->journal, p, root_meta, err) < 0)
return -1;
return dhara_journal_copy(&m->journal, p, root_meta, err);
}
/* Attempt to recover the journal */
static int try_recover(struct dhara_map *m, dhara_error_t cause,
dhara_error_t *err)
{
int restart_count = 0;
if (cause != DHARA_E_RECOVER) {
dhara_set_error(err, cause);
return -1;
}
while (dhara_journal_in_recovery(&m->journal)) {
dhara_page_t p = dhara_journal_next_recoverable(&m->journal);
dhara_error_t my_err;
int ret;
if (p == DHARA_PAGE_NONE)
ret = pad_queue(m, &my_err);
else
ret = raw_gc(m, p, &my_err);
if (ret < 0) {
if (my_err != DHARA_E_RECOVER) {
dhara_set_error(err, my_err);
return -1;
}
if (restart_count >= DHARA_MAX_RETRIES) {
dhara_set_error(err, DHARA_E_TOO_BAD);
return -1;
}
restart_count++;
}
}
return 0;
}
static int auto_gc(struct dhara_map *m, dhara_error_t *err)
{
int i;
if (dhara_journal_size(&m->journal) < dhara_map_capacity(m))
return 0;
for (i = 0; i <= m->gc_ratio; i++)
if (dhara_map_gc(m, err) < 0)
return -1;
return 0;
}
static int prepare_write(struct dhara_map *m, dhara_sector_t dst,
uint8_t *meta, dhara_error_t *err)
{
dhara_error_t my_err;
if (auto_gc(m, err) < 0)
return -1;
if (trace_path(m, dst, NULL, meta, &my_err) < 0) {
if (my_err != DHARA_E_NOT_FOUND) {
dhara_set_error(err, my_err);
return -1;
}
if (m->count >= dhara_map_capacity(m)) {
dhara_set_error(err, DHARA_E_MAP_FULL);
return -1;
}
m->count++;
}
ck_set_count(dhara_journal_cookie(&m->journal), m->count);
return 0;
}
int dhara_map_write(struct dhara_map *m, dhara_sector_t dst,
const uint8_t *data, dhara_error_t *err)
{
for (;;) {
uint8_t meta[DHARA_META_SIZE];
dhara_error_t my_err;
const dhara_sector_t old_count = m->count;
if (prepare_write(m, dst, meta, err) < 0)
return -1;
if (!dhara_journal_enqueue(&m->journal, data, meta, &my_err))
break;
m->count = old_count;
if (try_recover(m, my_err, err) < 0)
return -1;
}
return 0;
}
int dhara_map_copy_page(struct dhara_map *m, dhara_page_t src,
dhara_sector_t dst, dhara_error_t *err)
{
for (;;) {
uint8_t meta[DHARA_META_SIZE];
dhara_error_t my_err;
const dhara_sector_t old_count = m->count;
if (prepare_write(m, dst, meta, err) < 0)
return -1;
if (!dhara_journal_copy(&m->journal, src, meta, &my_err))
break;
m->count = old_count;
if (try_recover(m, my_err, err) < 0)
return -1;
}
return 0;
}
int dhara_map_copy_sector(struct dhara_map *m, dhara_sector_t src,
dhara_sector_t dst, dhara_error_t *err)
{
dhara_error_t my_err;
dhara_page_t p;
if (dhara_map_find(m, src, &p, &my_err) < 0) {
if (my_err == DHARA_E_NOT_FOUND)
return dhara_map_trim(m, dst, err);
dhara_set_error(err, my_err);
return -1;
}
return dhara_map_copy_page(m, p, dst, err);
}
static int try_delete(struct dhara_map *m, dhara_sector_t s,
dhara_error_t *err)
{
dhara_error_t my_err;
uint8_t meta[DHARA_META_SIZE];
dhara_page_t alt_page;
uint8_t alt_meta[DHARA_META_SIZE];
int level = DHARA_RADIX_DEPTH - 1;
int i;
if (trace_path(m, s, NULL, meta, &my_err) < 0) {
if (my_err == DHARA_E_NOT_FOUND)
return 0;
dhara_set_error(err, my_err);
return -1;
}
/* Select any of the closest cousins of this node which are
* subtrees of at least the requested order.
*/
while (level >= 0) {
alt_page = meta_get_alt(meta, level);
if (alt_page != DHARA_PAGE_NONE)
break;
level--;
}
/* Special case: deletion of last sector */
if (level < 0) {
m->count = 0;
dhara_journal_clear(&m->journal);
return 0;
}
/* Rewrite the cousin with an up-to-date path which doesn't
* point to the original node.
*/
if (dhara_journal_read_meta(&m->journal, alt_page, alt_meta, err) < 0)
return -1;
meta_set_id(meta, meta_get_id(alt_meta));
meta_set_alt(meta, level, DHARA_PAGE_NONE);
for (i = level + 1; i < DHARA_RADIX_DEPTH; i++)
meta_set_alt(meta, i, meta_get_alt(alt_meta, i));
meta_set_alt(meta, level, DHARA_PAGE_NONE);
ck_set_count(dhara_journal_cookie(&m->journal), m->count - 1);
if (dhara_journal_copy(&m->journal, alt_page, meta, err) < 0)
return -1;
m->count--;
return 0;
}
int dhara_map_trim(struct dhara_map *m, dhara_sector_t s, dhara_error_t *err)
{
for (;;) {
dhara_error_t my_err;
if (auto_gc(m, err) < 0)
return -1;
if (!try_delete(m, s, &my_err))
break;
if (try_recover(m, my_err, err) < 0)
return -1;
}
return 0;
}
int dhara_map_sync(struct dhara_map *m, dhara_error_t *err)
{
while (!dhara_journal_is_clean(&m->journal)) {
dhara_page_t p = dhara_journal_peek(&m->journal);
dhara_error_t my_err;
int ret;
if (p == DHARA_PAGE_NONE) {
ret = pad_queue(m, &my_err);
} else {
ret = raw_gc(m, p, &my_err);
if (!ret)
dhara_journal_dequeue(&m->journal);
}
if ((ret < 0) && (try_recover(m, my_err, err) < 0))
return -1;
}
return 0;
}
int dhara_map_gc(struct dhara_map *m, dhara_error_t *err)
{
if (!m->count)
return 0;
for (;;) {
dhara_page_t tail = dhara_journal_peek(&m->journal);
dhara_error_t my_err;
if (tail == DHARA_PAGE_NONE)
break;
if (!raw_gc(m, tail, &my_err)) {
dhara_journal_dequeue(&m->journal);
break;
}
if (try_recover(m, my_err, err) < 0)
return -1;
}
return 0;
}

114
Lib/dhara/map.h Normal file
View File

@@ -0,0 +1,114 @@
/* Dhara - NAND flash management layer
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef DHARA_MAP_H_
#define DHARA_MAP_H_
#include "journal.h"
/* The map is a journal indexing format. It maps virtual sectors to
* pages of data in flash memory.
*/
typedef uint32_t dhara_sector_t;
/* This sector value is reserved */
#define DHARA_SECTOR_NONE 0xffffffff
struct dhara_map {
struct dhara_journal journal;
uint8_t gc_ratio;
dhara_sector_t count;
};
/* Initialize a map. You need to supply a buffer for page metadata, and
* a garbage collection ratio. This is the ratio of garbage collection
* operations to real writes when automatic collection is active.
*
* Smaller values lead to faster and more predictable IO, at the
* expense of capacity. You should always initialize the same chip with
* the same garbage collection ratio.
*/
void dhara_map_init(struct dhara_map *m, const struct dhara_nand *n,
uint8_t *page_buf, uint8_t gc_ratio);
/* Recover stored state, if possible. If there is no valid stored state
* on the chip, -1 is returned, and an empty map is initialized.
*/
int dhara_map_resume(struct dhara_map *m, dhara_error_t *err);
/* Clear the map (delete all sectors). */
void dhara_map_clear(struct dhara_map *m);
/* Obtain the maximum capacity of the map. */
dhara_sector_t dhara_map_capacity(const struct dhara_map *m);
/* Obtain the current number of allocated sectors. */
static inline dhara_sector_t dhara_map_size(const struct dhara_map *m)
{
return m->count;
}
/* Find the physical page which holds the current data for this sector.
* Returns 0 on success or -1 if an error occurs. If the sector doesn't
* exist, the error is E_NOT_FOUND.
*/
int dhara_map_find(struct dhara_map *m, dhara_sector_t s,
dhara_page_t *loc, dhara_error_t *err);
/* Read from the given logical sector. If the sector is unmapped, a
* blank page (0xff) will be returned.
*/
int dhara_map_read(struct dhara_map *m, dhara_sector_t s,
uint8_t *data, dhara_error_t *err);
/* Write data to a logical sector. */
int dhara_map_write(struct dhara_map *m, dhara_sector_t s,
const uint8_t *data, dhara_error_t *err);
/* Copy any flash page to a logical sector. */
int dhara_map_copy_page(struct dhara_map *m, dhara_page_t src,
dhara_sector_t dst, dhara_error_t *err);
/* Copy one sector to another. If the source sector is unmapped, the
* destination sector will be trimmed.
*/
int dhara_map_copy_sector(struct dhara_map *m, dhara_sector_t src,
dhara_sector_t dst, dhara_error_t *err);
/* Delete a logical sector. You don't necessarily need to do this, but
* it's a useful hint if you no longer require the sector's data to be
* kept.
*
* If order is non-zero, it specifies that all sectors in the
* (2**order)-aligned group of s are to be deleted.
*/
int dhara_map_trim(struct dhara_map *m, dhara_sector_t s,
dhara_error_t *err);
/* Synchronize the map. Once this returns successfully, all changes to
* date are persistent and durable. Conversely, there is no guarantee
* that unsynchronized changes will be persistent.
*/
int dhara_map_sync(struct dhara_map *m, dhara_error_t *err);
/* Perform one garbage collection step. You can do this whenever you
* like, but it's not necessary -- garbage collection happens
* automatically and is interleaved with other operations.
*/
int dhara_map_gc(struct dhara_map *m, dhara_error_t *err);
#endif

105
Lib/dhara/nand.h Normal file
View File

@@ -0,0 +1,105 @@
/* Dhara - NAND flash management layer
* Copyright (C) 2013 Daniel Beer <dlbeer@gmail.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#ifndef DHARA_NAND_H_
#define DHARA_NAND_H_
#include <stdint.h>
#include <stddef.h>
#include "error.h"
/* Each page in a NAND device is indexed, starting at 0. It's required
* that there be a power-of-two number of pages in a eraseblock, so you can
* view a page number is being a concatenation (in binary) of a block
* number and the number of a page within a block.
*/
typedef uint32_t dhara_page_t;
/* Blocks are also indexed, starting at 0. */
typedef uint32_t dhara_block_t;
/* Each NAND chip must be represented by one of these structures. It's
* intended that this structure be embedded in a larger structure for
* context.
*
* The functions declared below are not implemented -- they must be
* provided and satisfy the documented conditions.
*/
struct dhara_nand {
/* Base-2 logarithm of the page size. If your device supports
* partial programming, you may want to subdivide the actual
* pages into separate ECC-correctable regions and present those
* as pages.
*/
uint8_t log2_page_size;
/* Base-2 logarithm of the number of pages within an eraseblock */
uint8_t log2_ppb;
/* Total number of eraseblocks */
unsigned int num_blocks;
};
/* Is the given block bad? */
int dhara_nand_is_bad(const struct dhara_nand *n, dhara_block_t b);
/* Mark bad the given block (or attempt to). No return value is
* required, because there's nothing that can be done in response.
*/
void dhara_nand_mark_bad(const struct dhara_nand *n, dhara_block_t b);
/* Erase the given block. This function should return 0 on success or -1
* on failure.
*
* The status reported by the chip should be checked. If an erase
* operation fails, return -1 and set err to E_BAD_BLOCK.
*/
int dhara_nand_erase(const struct dhara_nand *n, dhara_block_t b,
dhara_error_t *err);
/* Program the given page. The data pointer is a pointer to an entire
* page ((1 << log2_page_size) bytes). The operation status should be
* checked. If the operation fails, return -1 and set err to
* E_BAD_BLOCK.
*
* Pages will be programmed sequentially within a block, and will not be
* reprogrammed.
*/
int dhara_nand_prog(const struct dhara_nand *n, dhara_page_t p,
const uint8_t *data,
dhara_error_t *err);
/* Check that the given page is erased */
int dhara_nand_is_free(const struct dhara_nand *n, dhara_page_t p);
/* Read a portion of a page. ECC must be handled by the NAND
* implementation. Returns 0 on sucess or -1 if an error occurs. If an
* uncorrectable ECC error occurs, return -1 and set err to E_ECC.
*/
int dhara_nand_read(const struct dhara_nand *n, dhara_page_t p,
size_t offset, size_t length,
uint8_t *data,
dhara_error_t *err);
/* Read a page from one location and reprogram it in another location.
* This might be done using the chip's internal buffers, but it must use
* ECC.
*/
int dhara_nand_copy(const struct dhara_nand *n,
dhara_page_t src, dhara_page_t dst,
dhara_error_t *err);
#endif

View File

@@ -338,9 +338,9 @@
<v6Rtti>0</v6Rtti> <v6Rtti>0</v6Rtti>
<VariousControls> <VariousControls>
<MiscControls></MiscControls> <MiscControls></MiscControls>
<Define>USE_HAL_DRIVER,STM32F407xx</Define> <Define>USE_HAL_DRIVER,STM32F407xx,NDEBUG</Define>
<Undefine></Undefine> <Undefine></Undefine>
<IncludePath>../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Lib/FlashDB/port/fal/inc;../Lib/FlashDB/inc;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../App;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F</IncludePath> <IncludePath>../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Lib/FlashDB/port/fal/inc;../Lib/FlashDB/inc;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET;../App;../App/task;../App/util;../Middlewares/Third_Party/FreeRTOS/Source/include;../Middlewares/Third_Party/FreeRTOS/Source/CMSIS_RTOS_V2;../Middlewares/Third_Party/FreeRTOS/Source/portable/RVDS/ARM_CM4F;../Lib/dhara;../Lib/FatFs</IncludePath>
</VariousControls> </VariousControls>
</Cads> </Cads>
<Aads> <Aads>
@@ -761,9 +761,9 @@
<FilePath>..\App\app_main.c</FilePath> <FilePath>..\App\app_main.c</FilePath>
</File> </File>
<File> <File>
<FileName>sys_time.c</FileName> <FileName>sys_clock.c</FileName>
<FileType>1</FileType> <FileType>1</FileType>
<FilePath>..\App\sys_time.c</FilePath> <FilePath>..\App\sys_clock.c</FilePath>
</File> </File>
<File> <File>
<FileName>net_task.c</FileName> <FileName>net_task.c</FileName>
@@ -1256,6 +1256,11 @@
<FileType>1</FileType> <FileType>1</FileType>
<FilePath>..\Drivers\BSP\NET\net_socket.c</FilePath> <FilePath>..\Drivers\BSP\NET\net_socket.c</FilePath>
</File> </File>
<File>
<FileName>nand_ftl.c</FileName>
<FileType>1</FileType>
<FilePath>..\Drivers\BSP\GD5F2GQ5UE\nand_ftl.c</FilePath>
</File>
</Files> </Files>
</Group> </Group>
<Group> <Group>
@@ -1932,6 +1937,100 @@
</File> </File>
</Files> </Files>
</Group> </Group>
<Group>
<GroupName>lib/dhara</GroupName>
<GroupOption>
<CommonProperty>
<UseCPPCompiler>0</UseCPPCompiler>
<RVCTCodeConst>0</RVCTCodeConst>
<RVCTZI>0</RVCTZI>
<RVCTOtherData>0</RVCTOtherData>
<ModuleSelection>0</ModuleSelection>
<IncludeInBuild>2</IncludeInBuild>
<AlwaysBuild>2</AlwaysBuild>
<GenerateAssemblyFile>2</GenerateAssemblyFile>
<AssembleAssemblyFile>2</AssembleAssemblyFile>
<PublicsOnly>2</PublicsOnly>
<StopOnExitCode>11</StopOnExitCode>
<CustomArgument></CustomArgument>
<IncludeLibraryModules></IncludeLibraryModules>
<ComprImg>1</ComprImg>
</CommonProperty>
<GroupArmAds>
<Cads>
<interw>2</interw>
<Optim>0</Optim>
<oTime>2</oTime>
<SplitLS>2</SplitLS>
<OneElfS>2</OneElfS>
<Strict>2</Strict>
<EnumInt>2</EnumInt>
<PlainCh>2</PlainCh>
<Ropi>2</Ropi>
<Rwpi>2</Rwpi>
<wLevel>4</wLevel>
<uThumb>2</uThumb>
<uSurpInc>2</uSurpInc>
<uC99>2</uC99>
<uGnu>2</uGnu>
<useXO>2</useXO>
<v6Lang>0</v6Lang>
<v6LangP>0</v6LangP>
<vShortEn>2</vShortEn>
<vShortWch>2</vShortWch>
<v6Lto>2</v6Lto>
<v6WtE>2</v6WtE>
<v6Rtti>2</v6Rtti>
<VariousControls>
<MiscControls>--diag_suppress=177</MiscControls>
<Define></Define>
<Undefine></Undefine>
<IncludePath></IncludePath>
</VariousControls>
</Cads>
<Aads>
<interw>2</interw>
<Ropi>2</Ropi>
<Rwpi>2</Rwpi>
<thumb>2</thumb>
<SplitLS>2</SplitLS>
<SwStkChk>2</SwStkChk>
<NoWarn>2</NoWarn>
<uSurpInc>2</uSurpInc>
<useXO>2</useXO>
<ClangAsOpt>1</ClangAsOpt>
<VariousControls>
<MiscControls></MiscControls>
<Define></Define>
<Undefine></Undefine>
<IncludePath></IncludePath>
</VariousControls>
</Aads>
</GroupArmAds>
</GroupOption>
<Files>
<File>
<FileName>journal.c</FileName>
<FileType>1</FileType>
<FilePath>..\Lib\dhara\journal.c</FilePath>
</File>
<File>
<FileName>map.c</FileName>
<FileType>1</FileType>
<FilePath>..\Lib\dhara\map.c</FilePath>
</File>
</Files>
</Group>
<Group>
<GroupName>lib/FatFs</GroupName>
<Files>
<File>
<FileName>ff.c</FileName>
<FileType>1</FileType>
<FilePath>..\Lib\FatFs\ff.c</FilePath>
</File>
</Files>
</Group>
<Group> <Group>
<GroupName>::CMSIS</GroupName> <GroupName>::CMSIS</GroupName>
</Group> </Group>

1
MDK-ARM/_build.cmd Normal file
View File

@@ -0,0 +1 @@
@echo off && cd /d "%~dp0" && C:\Keil_v5\UV4\UV4.exe -r "STM32F407-Demo.uvprojx" -t STM32F407-Demo -j0

View File

@@ -1,71 +1,75 @@
*** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin' *** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin'
Rebuild target 'STM32F407-Demo' Rebuild target 'STM32F407-Demo'
assembling startup_stm32f407xx.s... assembling startup_stm32f407xx.s...
compiling net_task.c...
compiling app_main.c...
compiling crc.c...
compiling ringbuf.c... compiling ringbuf.c...
compiling dma.c... compiling crc.c...
compiling stm32f4xx_hal_flash_ex.c...
compiling sys_time.c...
compiling stm32f4xx_hal_msp.c... compiling stm32f4xx_hal_msp.c...
compiling stm32f4xx_hal_timebase_tim.c...
compiling stm32f4xx_hal_flash_ramfunc.c...
compiling i2c.c...
compiling spi.c...
compiling gpio.c...
compiling stm32f4xx_it.c... compiling stm32f4xx_it.c...
compiling stm32f4xx_hal_rcc_ex.c... compiling i2c.c...
compiling usart.c... compiling gpio.c...
compiling stm32f4xx_hal_dma.c... compiling spi.c...
compiling stm32f4xx_hal_timebase_tim.c...
compiling app_main.c...
compiling sys_clock.c...
compiling dma.c...
compiling stm32f4xx_hal_flash_ramfunc.c...
compiling stm32f4xx_hal_gpio.c... compiling stm32f4xx_hal_gpio.c...
compiling freertos.c... compiling usart.c...
compiling stm32f4xx_hal_flash.c... compiling stm32f4xx_hal_flash.c...
compiling stm32f4xx_hal_flash_ex.c...
compiling stm32f4xx_hal_rcc_ex.c...
compiling rs485_task.c... compiling rs485_task.c...
compiling stm32f4xx_hal_rcc.c... compiling net_task.c...
compiling adc_task.c... compiling adc_task.c...
compiling main.c... compiling stm32f4xx_hal_rcc.c...
compiling stm32f4xx_hal_dma_ex.c...
compiling stm32f4xx_hal_tim_ex.c... compiling stm32f4xx_hal_tim_ex.c...
compiling freertos.c...
compiling main.c...
compiling stm32f4xx_hal_tim.c... compiling stm32f4xx_hal_tim.c...
compiling fdb.c... compiling fdb.c...
compiling fdb_utils.c...
compiling fdb_tsdb.c...
compiling fal.c...
compiling fdb_kvdb.c... compiling fdb_kvdb.c...
compiling fdb_tsdb.c...
compiling fdb_utils.c...
compiling fal.c...
compiling fal_flash.c... compiling fal_flash.c...
compiling croutine.c...
compiling fal_partition.c... compiling fal_partition.c...
compiling list.c... compiling croutine.c...
compiling event_groups.c... compiling event_groups.c...
compiling stream_buffer.c... compiling list.c...
compiling queue.c... compiling stm32f4xx_hal_dma.c...
compiling timers.c...
compiling tasks.c...
compiling port.c...
compiling heap_4.c...
compiling stm32f4xx_hal_pwr.c...
compiling stm32f4xx_hal_i2c_ex.c...
compiling stm32f4xx_hal_pwr_ex.c...
compiling system_stm32f4xx.c... compiling system_stm32f4xx.c...
compiling stm32f4xx_hal_i2c_ex.c...
compiling stm32f4xx_hal_exti.c... compiling stm32f4xx_hal_exti.c...
compiling fal_flash_gd5f2gq5ue.c... compiling stm32f4xx_hal_pwr.c...
compiling stm32f4xx_hal.c... compiling stm32f4xx_hal_pwr_ex.c...
compiling stm32f4xx_hal_cortex.c... compiling stm32f4xx_hal_cortex.c...
compiling rs485.c... compiling stm32f4xx_hal.c...
compiling sd2506.c... compiling sd2506.c...
compiling rs485.c...
compiling stm32f4xx_hal_dma_ex.c...
compiling fal_flash_gd5f2gq5ue.c...
compiling tpafe5160.c... compiling tpafe5160.c...
compiling net_select.c...
compiling ch395f_test.c...
compiling ch395f.c... compiling ch395f.c...
compiling nand_ftl.c...
compiling gd5f2gq5ue.c... compiling gd5f2gq5ue.c...
compiling stm32f4xx_hal_spi.c... compiling stm32f4xx_hal_spi.c...
compiling net_socket.c... compiling ch395f_test.c...
compiling stm32f4xx_hal_uart.c... compiling stm32f4xx_hal_uart.c...
compiling queue.c...
compiling net_select.c...
compiling stream_buffer.c...
compiling timers.c...
compiling heap_4.c...
compiling net_socket.c...
compiling map.c...
compiling tasks.c...
compiling journal.c...
compiling port.c...
compiling stm32f4xx_hal_i2c.c... compiling stm32f4xx_hal_i2c.c...
compiling ff.c...
compiling cmsis_os2.c... compiling cmsis_os2.c...
linking... linking...
Program Size: Code=29372 RO-data=888 RW-data=180 ZI-data=41420 Program Size: Code=58468 RO-data=3488 RW-data=248 ZI-data=54792
FromELF: creating hex file... FromELF: creating hex file...
".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:16 Build Time Elapsed: 00:00:19

0
MDK-ARM/err.txt Normal file
View File

0
MDK-ARM/out.txt Normal file
View File

View File

@@ -6,7 +6,29 @@ Dma.Request0=USART1_TX
Dma.Request1=USART1_RX Dma.Request1=USART1_RX
Dma.Request2=SPI2_RX Dma.Request2=SPI2_RX
Dma.Request3=SPI2_TX Dma.Request3=SPI2_TX
Dma.RequestsNb=4 Dma.Request4=SPI1_RX
Dma.Request5=SPI1_TX
Dma.RequestsNb=6
Dma.SPI1_RX.4.Direction=DMA_PERIPH_TO_MEMORY
Dma.SPI1_RX.4.FIFOMode=DMA_FIFOMODE_DISABLE
Dma.SPI1_RX.4.Instance=DMA2_Stream0
Dma.SPI1_RX.4.MemDataAlignment=DMA_MDATAALIGN_BYTE
Dma.SPI1_RX.4.MemInc=DMA_MINC_ENABLE
Dma.SPI1_RX.4.Mode=DMA_NORMAL
Dma.SPI1_RX.4.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
Dma.SPI1_RX.4.PeriphInc=DMA_PINC_DISABLE
Dma.SPI1_RX.4.Priority=DMA_PRIORITY_LOW
Dma.SPI1_RX.4.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode
Dma.SPI1_TX.5.Direction=DMA_MEMORY_TO_PERIPH
Dma.SPI1_TX.5.FIFOMode=DMA_FIFOMODE_DISABLE
Dma.SPI1_TX.5.Instance=DMA2_Stream3
Dma.SPI1_TX.5.MemDataAlignment=DMA_MDATAALIGN_BYTE
Dma.SPI1_TX.5.MemInc=DMA_MINC_ENABLE
Dma.SPI1_TX.5.Mode=DMA_NORMAL
Dma.SPI1_TX.5.PeriphDataAlignment=DMA_PDATAALIGN_BYTE
Dma.SPI1_TX.5.PeriphInc=DMA_PINC_DISABLE
Dma.SPI1_TX.5.Priority=DMA_PRIORITY_LOW
Dma.SPI1_TX.5.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode
Dma.SPI2_RX.2.Direction=DMA_PERIPH_TO_MEMORY Dma.SPI2_RX.2.Direction=DMA_PERIPH_TO_MEMORY
Dma.SPI2_RX.2.FIFOMode=DMA_FIFOMODE_DISABLE Dma.SPI2_RX.2.FIFOMode=DMA_FIFOMODE_DISABLE
Dma.SPI2_RX.2.Instance=DMA1_Stream3 Dma.SPI2_RX.2.Instance=DMA1_Stream3
@@ -48,7 +70,8 @@ Dma.USART1_TX.0.PeriphInc=DMA_PINC_DISABLE
Dma.USART1_TX.0.Priority=DMA_PRIORITY_LOW Dma.USART1_TX.0.Priority=DMA_PRIORITY_LOW
Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode Dma.USART1_TX.0.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode
FREERTOS.FootprintOK=true FREERTOS.FootprintOK=true
FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configTOTAL_HEAP_SIZE FREERTOS.IPParameters=Tasks01,configENABLE_FPU,FootprintOK,configTOTAL_HEAP_SIZE,Queues01
FREERTOS.Queues01=netMsgQueue,8,net_msg_t,0,Dynamic,NULL,NULL
FREERTOS.Tasks01=defaultTask,24,512,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;netTask,24,1024,StartNetTask,Default,NULL,Dynamic,NULL,NULL FREERTOS.Tasks01=defaultTask,24,512,StartDefaultTask,Default,NULL,Dynamic,NULL,NULL;netTask,24,1024,StartNetTask,Default,NULL,Dynamic,NULL,NULL
FREERTOS.configENABLE_FPU=1 FREERTOS.configENABLE_FPU=1
FREERTOS.configTOTAL_HEAP_SIZE=30720 FREERTOS.configTOTAL_HEAP_SIZE=30720
@@ -141,7 +164,9 @@ MxDb.Version=DB.6.0.170
NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false
NVIC.DMA1_Stream3_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true NVIC.DMA1_Stream3_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true
NVIC.DMA1_Stream4_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true NVIC.DMA1_Stream4_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true
NVIC.DMA2_Stream0_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true
NVIC.DMA2_Stream2_IRQn=true\:9\:0\:true\:false\:true\:true\:false\:true\:true NVIC.DMA2_Stream2_IRQn=true\:9\:0\:true\:false\:true\:true\:false\:true\:true
NVIC.DMA2_Stream3_IRQn=true\:7\:0\:true\:false\:true\:true\:false\:true\:true
NVIC.DMA2_Stream7_IRQn=true\:9\:0\:true\:false\:true\:true\:false\:true\:true NVIC.DMA2_Stream7_IRQn=true\:9\:0\:true\:false\:true\:true\:false\:true\:true
NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false
NVIC.EXTI9_5_IRQn=true\:6\:0\:true\:false\:true\:true\:true\:true\:true NVIC.EXTI9_5_IRQn=true\:6\:0\:true\:false\:true\:true\:true\:true\:true
@@ -151,6 +176,7 @@ NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:
NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false\:false
NVIC.PendSV_IRQn=true\:15\:0\:false\:false\:false\:true\:false\:false\:false NVIC.PendSV_IRQn=true\:15\:0\:false\:false\:false\:true\:false\:false\:false
NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4
NVIC.SPI1_IRQn=true\:7\:0\:true\:false\:true\:true\:true\:true\:true
NVIC.SPI2_IRQn=true\:7\:0\:true\:false\:true\:true\:true\:true\:true NVIC.SPI2_IRQn=true\:7\:0\:true\:false\:true\:true\:true\:true\:true
NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:false\:false\:false\:false\:false NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:false\:false\:false\:false\:false
NVIC.SavedPendsvIrqHandlerGenerated=true NVIC.SavedPendsvIrqHandlerGenerated=true
@@ -301,9 +327,10 @@ PCC.PartNumber=STM32F407ZGTx
PCC.Series=STM32F4 PCC.Series=STM32F4
PCC.Temperature=25 PCC.Temperature=25
PCC.Vdd=3.3 PCC.Vdd=3.3
PD0.GPIOParameters=GPIO_PuPd,GPIO_Label PD0.GPIOParameters=GPIO_Speed,GPIO_PuPd,GPIO_Label
PD0.GPIO_Label=ST_DIR4 PD0.GPIO_Label=ST_DIR4
PD0.GPIO_PuPd=GPIO_NOPULL PD0.GPIO_PuPd=GPIO_NOPULL
PD0.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH
PD0.Locked=true PD0.Locked=true
PD0.Signal=GPIO_Output PD0.Signal=GPIO_Output
PD1.GPIOParameters=GPIO_Label PD1.GPIOParameters=GPIO_Label
@@ -524,8 +551,8 @@ RCC.VCOOutputFreq_Value=336000000
RCC.VcooutputI2S=192000000 RCC.VcooutputI2S=192000000
SH.GPXTI7.0=GPIO_EXTI7 SH.GPXTI7.0=GPIO_EXTI7
SH.GPXTI7.ConfNb=1 SH.GPXTI7.ConfNb=1
SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_8 SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_2
SPI1.CalculateBaudRate=10.5 MBits/s SPI1.CalculateBaudRate=42.0 MBits/s
SPI1.Direction=SPI_DIRECTION_2LINES SPI1.Direction=SPI_DIRECTION_2LINES
SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler
SPI1.Mode=SPI_MODE_MASTER SPI1.Mode=SPI_MODE_MASTER
@@ -551,3 +578,4 @@ VP_FREERTOS_VS_CMSIS_V2.Signal=FREERTOS_VS_CMSIS_V2
VP_SYS_VS_tim7.Mode=TIM7 VP_SYS_VS_tim7.Mode=TIM7
VP_SYS_VS_tim7.Signal=SYS_VS_tim7 VP_SYS_VS_tim7.Signal=SYS_VS_tim7
board=custom board=custom
rtos.0.ip=FREERTOS

View File

@@ -50,9 +50,15 @@ void MX_DMA_Init(void)
/* DMA1_Stream4_IRQn interrupt configuration */ /* DMA1_Stream4_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 7, 0); HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 7, 0);
HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn);
/* DMA2_Stream0_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream0_IRQn, 7, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream0_IRQn);
/* DMA2_Stream2_IRQn interrupt configuration */ /* DMA2_Stream2_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 9, 0); HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 9, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn); HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn);
/* DMA2_Stream3_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream3_IRQn, 7, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream3_IRQn);
/* DMA2_Stream7_IRQn interrupt configuration */ /* DMA2_Stream7_IRQn interrupt configuration */
HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 9, 0); HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 9, 0);
HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn); HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);

View File

@@ -25,9 +25,14 @@
/* Private includes ----------------------------------------------------------*/ /* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */ /* USER CODE BEGIN Includes */
#include <string.h>
#include "net_task.h" #include "net_task.h"
#include "ch395f.h" #include "ch395f.h"
#include "dbg_log.h" #include "dbg_log.h"
#include "net_socket.h"
#include "usart.h"
#include "ff.h"
#include "flashdb.h"
/* USER CODE END Includes */ /* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/
@@ -38,6 +43,29 @@
/* Private define ------------------------------------------------------------*/ /* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */ /* USER CODE BEGIN PD */
/* 存储测试统计 */
typedef struct {
uint16_t total;
uint16_t passed;
uint16_t failed;
} storage_test_stats_t;
#define STORAGE_TEST_CHECK(cond, fmt, ...) do { \
g_storage_stats.total++; \
if (cond) { \
g_storage_stats.passed++; \
DBG_INFO(" [PASS] " fmt, ##__VA_ARGS__); \
} else { \
g_storage_stats.failed++; \
DBG_ERROR(" [FAIL] " fmt, ##__VA_ARGS__); \
} \
} while (0)
#define STORAGE_TEST_REPORT(name) do { \
DBG_INFO("=== %s: %d/%d PASSED (failed=%d) ===", \
name, g_storage_stats.passed, g_storage_stats.total, g_storage_stats.failed); \
} while (0)
/* USER CODE END PD */ /* USER CODE END PD */
/* Private macro -------------------------------------------------------------*/ /* Private macro -------------------------------------------------------------*/
@@ -47,7 +75,9 @@
/* Private variables ---------------------------------------------------------*/ /* Private variables ---------------------------------------------------------*/
/* USER CODE BEGIN Variables */ /* USER CODE BEGIN Variables */
static struct fdb_kvdb s_kvdb;
static storage_test_stats_t g_storage_stats;
static uint8_t s_perf_buf[4096];
/* USER CODE END Variables */ /* USER CODE END Variables */
/* Definitions for defaultTask */ /* Definitions for defaultTask */
osThreadId_t defaultTaskHandle; osThreadId_t defaultTaskHandle;
@@ -60,9 +90,14 @@ const osThreadAttr_t defaultTask_attributes = {
osThreadId_t netTaskHandle; osThreadId_t netTaskHandle;
const osThreadAttr_t netTask_attributes = { const osThreadAttr_t netTask_attributes = {
.name = "netTask", .name = "netTask",
.stack_size = 1024 * 8, .stack_size = 1024 * 4,
.priority = (osPriority_t) osPriorityNormal, .priority = (osPriority_t) osPriorityNormal,
}; };
/* Definitions for netMsgQueue */
osMessageQueueId_t netMsgQueueHandle;
const osMessageQueueAttr_t netMsgQueue_attributes = {
.name = "netMsgQueue"
};
/* Private function prototypes -----------------------------------------------*/ /* Private function prototypes -----------------------------------------------*/
/* USER CODE BEGIN FunctionPrototypes */ /* USER CODE BEGIN FunctionPrototypes */
@@ -96,6 +131,10 @@ void MX_FREERTOS_Init(void) {
/* start timers, add new ones, ... */ /* start timers, add new ones, ... */
/* USER CODE END RTOS_TIMERS */ /* USER CODE END RTOS_TIMERS */
/* Create the queue(s) */
/* creation of netMsgQueue */
netMsgQueueHandle = osMessageQueueNew (8, sizeof(net_msg_t), &netMsgQueue_attributes);
/* USER CODE BEGIN RTOS_QUEUES */ /* USER CODE BEGIN RTOS_QUEUES */
/* add queues, ... */ /* add queues, ... */
/* USER CODE END RTOS_QUEUES */ /* USER CODE END RTOS_QUEUES */
@@ -127,20 +166,147 @@ void MX_FREERTOS_Init(void) {
void StartDefaultTask(void *argument) void StartDefaultTask(void *argument)
{ {
/* USER CODE BEGIN StartDefaultTask */ /* USER CODE BEGIN StartDefaultTask */
/* Infinite loop */
for(;;) static FATFS fs;
static FIL fil;
static uint8_t work[FF_MAX_SS];
char read_buf[64];
const char *test_str = "Hello FatFS + dhara FTL!";
fdb_err_t fdb_ret;
memset(&g_storage_stats, 0, sizeof(g_storage_stats));
/* ==================== FTL + FatFS 文件系统测试 ==================== */
DBG_INFO("=== Storage Test: FTL + FatFS ===");
FRESULT res = f_mount(&fs, "", 1);
if (res == FR_NO_FILESYSTEM) {
MKFS_PARM opts = {FM_FAT32, 0, 0, 0, 0};
res = f_mkfs("", &opts, work, sizeof(work));
STORAGE_TEST_CHECK(res == FR_OK, "f_mkfs");
res = f_mount(&fs, "", 1);
}
STORAGE_TEST_CHECK(res == FR_OK, "f_mount");
if (res == FR_OK) {
UINT bw, br;
res = f_open(&fil, "test.txt", FA_CREATE_ALWAYS | FA_WRITE);
STORAGE_TEST_CHECK(res == FR_OK, "f_open(write)");
if (res == FR_OK) {
f_write(&fil, test_str, strlen(test_str), &bw);
STORAGE_TEST_CHECK(bw == strlen(test_str), "f_write (%u bytes)", bw);
f_close(&fil);
res = f_open(&fil, "test.txt", FA_READ);
STORAGE_TEST_CHECK(res == FR_OK, "f_open(read)");
if (res == FR_OK) {
f_read(&fil, read_buf, sizeof(read_buf) - 1, &br);
f_close(&fil);
read_buf[br] = '\0';
STORAGE_TEST_CHECK(br == strlen(test_str) && memcmp(test_str, read_buf, br) == 0,
"data verify \"%s\"", read_buf);
}
}
f_unlink("test.txt");
}
/* ==================== FlashDB KVDB 测试 ==================== */
DBG_INFO("=== Storage Test: FlashDB KVDB ===");
fdb_ret = fdb_kvdb_init(&s_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL);
STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kvdb_init");
if (fdb_ret == FDB_NO_ERR) {
fdb_ret = fdb_kv_set(&s_kvdb, "hello", "FlashDB KVDB!");
STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kv_set(hello)");
char *val = fdb_kv_get(&s_kvdb, "hello");
STORAGE_TEST_CHECK(val != NULL && strcmp(val, "FlashDB KVDB!") == 0,
"fdb_kv_get(hello) -> \"%s\"", val ? val : "NULL");
uint32_t blob_data = 0xDEADBEEF;
struct fdb_blob blob;
fdb_blob_make(&blob, &blob_data, sizeof(blob_data));
fdb_ret = fdb_kv_set_blob(&s_kvdb, "counter", &blob);
STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kv_set_blob(counter)");
uint32_t read_data = 0;
struct fdb_blob read_blob;
fdb_blob_make(&read_blob, &read_data, sizeof(read_data));
size_t len = fdb_kv_get_blob(&s_kvdb, "counter", &read_blob);
STORAGE_TEST_CHECK(len == sizeof(read_data) && read_data == 0xDEADBEEF,
"fdb_kv_get_blob(counter) -> 0x%08lX", (unsigned long)read_data);
fdb_kv_del(&s_kvdb, "hello");
fdb_kv_del(&s_kvdb, "counter");
}
/* ==================== FTL + FatFS 性能测试 ==================== */
DBG_INFO("=== Storage Test: Performance ===");
{ {
osDelay(1); FIL pfile;
UINT pw, pr;
uint32_t t0, tw, tr;
uint32_t i;
uint32_t total_kb = 128;
uint32_t chunk = sizeof(s_perf_buf);
uint32_t count = (total_kb * 1024) / chunk;
memset(s_perf_buf, 0xA5, sizeof(s_perf_buf));
/* 写入 */
t0 = HAL_GetTick();
if (f_open(&pfile, "perf.dat", FA_CREATE_ALWAYS | FA_WRITE) == FR_OK) {
for (i = 0; i < count; i++) {
f_write(&pfile, s_perf_buf, chunk, &pw);
}
f_close(&pfile);
}
tw = HAL_GetTick() - t0;
/* 读取 */
t0 = HAL_GetTick();
if (f_open(&pfile, "perf.dat", FA_READ) == FR_OK) {
for (i = 0; i < count; i++) {
f_read(&pfile, s_perf_buf, chunk, &pr);
}
f_close(&pfile);
}
tr = HAL_GetTick() - t0;
f_unlink("perf.dat");
if (tw > 0) {
DBG_INFO(" Write: %lu KB in %lu ms = %lu KB/s",
total_kb, tw, (total_kb * 1000) / tw);
}
if (tr > 0) {
DBG_INFO(" Read: %lu KB in %lu ms = %lu KB/s",
total_kb, tr, (total_kb * 1000) / tr);
}
}
/* ==================== 汇总报告 ==================== */
STORAGE_TEST_REPORT("Storage Tests");
for (;;) {
osDelay(1000);
} }
/* USER CODE END StartDefaultTask */ /* USER CODE END StartDefaultTask */
} }
/* USER CODE BEGIN Header_StartNetTask */ /* USER CODE BEGIN Header_StartNetTask */
/** /**
* @brief Function implementing the netTask thread. * @brief Function implementing the netTask thread.
* @param argument: Not used * @param argument: Not used
* @retval None * @retval None
*/ */
/* USER CODE END Header_StartNetTask */ /* USER CODE END Header_StartNetTask */
void StartNetTask(void *argument) void StartNetTask(void *argument)
{ {

View File

@@ -128,12 +128,12 @@ void MX_GPIO_Init(void)
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(CH395_SCS_GPIO_Port, &GPIO_InitStruct); HAL_GPIO_Init(CH395_SCS_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pin : ST_DIR4_Pin */ /*Configure GPIO pins : ST_DIR4_Pin TP_RD_Pin TP_CONVST_Pin */
GPIO_InitStruct.Pin = ST_DIR4_Pin; GPIO_InitStruct.Pin = ST_DIR4_Pin|TP_RD_Pin|TP_CONVST_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(ST_DIR4_GPIO_Port, &GPIO_InitStruct); HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/*Configure GPIO pin : TP_FRSTDATA_Pin */ /*Configure GPIO pin : TP_FRSTDATA_Pin */
GPIO_InitStruct.Pin = TP_FRSTDATA_Pin; GPIO_InitStruct.Pin = TP_FRSTDATA_Pin;
@@ -141,13 +141,6 @@ void MX_GPIO_Init(void)
GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(TP_FRSTDATA_GPIO_Port, &GPIO_InitStruct); HAL_GPIO_Init(TP_FRSTDATA_GPIO_Port, &GPIO_InitStruct);
/*Configure GPIO pins : TP_RD_Pin TP_CONVST_Pin */
GPIO_InitStruct.Pin = TP_RD_Pin|TP_CONVST_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
/*Configure GPIO pin : TP_BUSY_Pin */ /*Configure GPIO pin : TP_BUSY_Pin */
GPIO_InitStruct.Pin = TP_BUSY_Pin; GPIO_InitStruct.Pin = TP_BUSY_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING; GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;

View File

@@ -37,6 +37,8 @@
#include "net_socket.h" #include "net_socket.h"
#include "net_select.h" #include "net_select.h"
#include "ch395f_test.h" #include "ch395f_test.h"
#include "app_main.h"
#include "sys_clock.h"
/* USER CODE END Includes */ /* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/
@@ -118,6 +120,21 @@ int main(void)
/* USER CODE BEGIN 2 */ /* USER CODE BEGIN 2 */
HAL_Delay(100); HAL_Delay(100);
/*
* 时间子系统初始化(尽早完成,后续所有日志带时间戳)
* sd2506_init() 从 RTC 读取初始时间
* sys_clock_init() 将 RTC 时间转为 Unix epoch建立墙钟基准
*/
app_main_init();
sd2506_init();
sys_clock_init();
/* RS-485 初始化UART5 + PD0 方向控制) */
rs485_init(&g_rs485, &huart5, ST_DIR4_GPIO_Port, ST_DIR4_Pin);
DBG_INFO("RS485 init: OK (UART5, DE=PD0)");
/* 启动第一次 RS-485 接收 */
rs485_receive_start(&g_rs485, g_rs485_rx_buf, RS485_RX_BUF_SIZE);
/* BSD Socket API 初始化(包含 CH395F 复位/配置/协议栈初始化/多连接模式) */ /* BSD Socket API 初始化(包含 CH395F 复位/配置/协议栈初始化/多连接模式) */
DBG_INFO("net init: %s", DBG_INFO("net init: %s",
(net_init("192.168.1.100", "255.255.255.0", "192.168.1.1") == 0) ? "OK" : "FAIL"); (net_init("192.168.1.100", "255.255.255.0", "192.168.1.1") == 0) ? "OK" : "FAIL");
@@ -155,12 +172,8 @@ int main(void)
DBG_INFO("ADC init: %s", DBG_INFO("ADC init: %s",
(tp_ret == TPAFE5160_OK) ? "OK" : "TIMEOUT"); (tp_ret == TPAFE5160_OK) ? "OK" : "TIMEOUT");
/* SD2506 RTC 初始化测试 */ /* SD2506 RTC 诊断信息 */
int sd_ret = sd2506_init(); {
DBG_INFO("SD2506 init: %s",
(sd_ret == SD2506_OK) ? "OK" : "I2C ERROR");
if (sd_ret == SD2506_OK) {
/* 读取 ID */ /* 读取 ID */
uint8_t sd_id[8]; uint8_t sd_id[8];
sd2506_get_id(sd_id); sd2506_get_id(sd_id);
@@ -188,13 +201,6 @@ int main(void)
sd_vbat / 1000, (sd_vbat % 1000) / 10); sd_vbat / 1000, (sd_vbat % 1000) / 10);
} }
/* RS-485 初始化UART5 + PD0 方向控制) */
rs485_init(&g_rs485, &huart5, ST_DIR4_GPIO_Port, ST_DIR4_Pin);
DBG_INFO("RS485 init: OK (UART5, DE=PD0)");
/* 启动第一次 RS-485 接收 */
rs485_receive_start(&g_rs485, g_rs485_rx_buf, RS485_RX_BUF_SIZE);
/* 创建 TCP Server Echo 测试 */ /* 创建 TCP Server Echo 测试 */
{ {
s_listen_sock = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0); s_listen_sock = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
@@ -256,10 +262,8 @@ int main(void)
/* USER CODE END WHILE */ /* USER CODE END WHILE */
/* USER CODE BEGIN 3 */ /* USER CODE BEGIN 3 */
/* 网络轮询已移到 FreeRTOS netTask 中 */
;
/* USER CODE END 3 */
} }
/* USER CODE END 3 */
} }
/** /**

View File

@@ -26,6 +26,8 @@
SPI_HandleTypeDef hspi1; SPI_HandleTypeDef hspi1;
SPI_HandleTypeDef hspi2; SPI_HandleTypeDef hspi2;
DMA_HandleTypeDef hdma_spi1_rx;
DMA_HandleTypeDef hdma_spi1_tx;
DMA_HandleTypeDef hdma_spi2_rx; DMA_HandleTypeDef hdma_spi2_rx;
DMA_HandleTypeDef hdma_spi2_tx; DMA_HandleTypeDef hdma_spi2_tx;
@@ -47,7 +49,7 @@ void MX_SPI1_Init(void)
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
@@ -119,6 +121,46 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1; GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);
/* SPI1 DMA Init */
/* SPI1_RX Init */
hdma_spi1_rx.Instance = DMA2_Stream0;
hdma_spi1_rx.Init.Channel = DMA_CHANNEL_3;
hdma_spi1_rx.Init.Direction = DMA_PERIPH_TO_MEMORY;
hdma_spi1_rx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_rx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_rx.Init.Mode = DMA_NORMAL;
hdma_spi1_rx.Init.Priority = DMA_PRIORITY_LOW;
hdma_spi1_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi1_rx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(spiHandle,hdmarx,hdma_spi1_rx);
/* SPI1_TX Init */
hdma_spi1_tx.Instance = DMA2_Stream3;
hdma_spi1_tx.Init.Channel = DMA_CHANNEL_3;
hdma_spi1_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi1_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi1_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi1_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi1_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi1_tx.Init.Mode = DMA_NORMAL;
hdma_spi1_tx.Init.Priority = DMA_PRIORITY_LOW;
hdma_spi1_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
if (HAL_DMA_Init(&hdma_spi1_tx) != HAL_OK)
{
Error_Handler();
}
__HAL_LINKDMA(spiHandle,hdmatx,hdma_spi1_tx);
/* SPI1 interrupt Init */
HAL_NVIC_SetPriority(SPI1_IRQn, 7, 0);
HAL_NVIC_EnableIRQ(SPI1_IRQn);
/* USER CODE BEGIN SPI1_MspInit 1 */ /* USER CODE BEGIN SPI1_MspInit 1 */
/* USER CODE END SPI1_MspInit 1 */ /* USER CODE END SPI1_MspInit 1 */
@@ -215,6 +257,12 @@ void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle)
*/ */
HAL_GPIO_DeInit(GPIOB, GD_SCLK_Pin|GD_SO_Pin|GD_SI_Pin); HAL_GPIO_DeInit(GPIOB, GD_SCLK_Pin|GD_SO_Pin|GD_SI_Pin);
/* SPI1 DMA DeInit */
HAL_DMA_DeInit(spiHandle->hdmarx);
HAL_DMA_DeInit(spiHandle->hdmatx);
/* SPI1 interrupt Deinit */
HAL_NVIC_DisableIRQ(SPI1_IRQn);
/* USER CODE BEGIN SPI1_MspDeInit 1 */ /* USER CODE BEGIN SPI1_MspDeInit 1 */
/* USER CODE END SPI1_MspDeInit 1 */ /* USER CODE END SPI1_MspDeInit 1 */

View File

@@ -55,8 +55,11 @@
/* USER CODE END 0 */ /* USER CODE END 0 */
/* External variables --------------------------------------------------------*/ /* External variables --------------------------------------------------------*/
extern DMA_HandleTypeDef hdma_spi1_rx;
extern DMA_HandleTypeDef hdma_spi1_tx;
extern DMA_HandleTypeDef hdma_spi2_rx; extern DMA_HandleTypeDef hdma_spi2_rx;
extern DMA_HandleTypeDef hdma_spi2_tx; extern DMA_HandleTypeDef hdma_spi2_tx;
extern SPI_HandleTypeDef hspi1;
extern SPI_HandleTypeDef hspi2; extern SPI_HandleTypeDef hspi2;
extern DMA_HandleTypeDef hdma_usart1_tx; extern DMA_HandleTypeDef hdma_usart1_tx;
extern DMA_HandleTypeDef hdma_usart1_rx; extern DMA_HandleTypeDef hdma_usart1_rx;
@@ -211,6 +214,20 @@ void EXTI9_5_IRQHandler(void)
/* USER CODE END EXTI9_5_IRQn 1 */ /* USER CODE END EXTI9_5_IRQn 1 */
} }
/**
* @brief This function handles SPI1 global interrupt.
*/
void SPI1_IRQHandler(void)
{
/* USER CODE BEGIN SPI1_IRQn 0 */
/* USER CODE END SPI1_IRQn 0 */
HAL_SPI_IRQHandler(&hspi1);
/* USER CODE BEGIN SPI1_IRQn 1 */
/* USER CODE END SPI1_IRQn 1 */
}
/** /**
* @brief This function handles SPI2 global interrupt. * @brief This function handles SPI2 global interrupt.
*/ */
@@ -309,6 +326,20 @@ void TIM7_IRQHandler(void)
/* USER CODE END TIM7_IRQn 1 */ /* USER CODE END TIM7_IRQn 1 */
} }
/**
* @brief This function handles DMA2 stream0 global interrupt.
*/
void DMA2_Stream0_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream0_IRQn 0 */
/* USER CODE END DMA2_Stream0_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi1_rx);
/* USER CODE BEGIN DMA2_Stream0_IRQn 1 */
/* USER CODE END DMA2_Stream0_IRQn 1 */
}
/** /**
* @brief This function handles DMA2 stream2 global interrupt. * @brief This function handles DMA2 stream2 global interrupt.
*/ */
@@ -323,6 +354,20 @@ void DMA2_Stream2_IRQHandler(void)
/* USER CODE END DMA2_Stream2_IRQn 1 */ /* USER CODE END DMA2_Stream2_IRQn 1 */
} }
/**
* @brief This function handles DMA2 stream3 global interrupt.
*/
void DMA2_Stream3_IRQHandler(void)
{
/* USER CODE BEGIN DMA2_Stream3_IRQn 0 */
/* USER CODE END DMA2_Stream3_IRQn 0 */
HAL_DMA_IRQHandler(&hdma_spi1_tx);
/* USER CODE BEGIN DMA2_Stream3_IRQn 1 */
/* USER CODE END DMA2_Stream3_IRQn 1 */
}
/** /**
* @brief This function handles DMA2 stream7 global interrupt. * @brief This function handles DMA2 stream7 global interrupt.
*/ */

View File

@@ -4,15 +4,16 @@
1. [概述](#1-概述) 1. [概述](#1-概述)
2. [架构说明](#2-架构说明) 2. [架构说明](#2-架构说明)
3. [快速开始](#3-快速开始) 3. [线程安全设计](#3-线程安全设计)
4. [TCP 编程](#4-tcp-编程) 4. [快速开始](#4-快速开始)
5. [UDP 编程](#5-udp-编程) 5. [TCP 编程](#5-tcp-编程)
6. [I/O 多路复用](#6-io-多路复用) 6. [UDP 编程](#6-udp-编程)
7. [事件回调机制](#7-事件回调机制) 7. [I/O 多路复用](#7-io-多路复用)
8. [字节序处理](#8-字节序处理) 8. [事件回调机制](#8-事件回调机制)
9. [API 参考](#9-api-参考) 9. [字节序处理](#9-字节序处理)
10. [完整示例](#10-完整示例) 10. [API 参考](#10-api-参考)
11. [常见问题](#11-常见问题) 11. [完整示例](#11-完整示例)
12. [常见问题](#12-常见问题)
--- ---
@@ -46,32 +47,42 @@
## 2. 架构说明 ## 2. 架构说明
### 2.1 分层架构
``` ```
┌─────────────────────────────────────────────────────────┐ ┌──────────────────────────────────────────────────────────────
│ 应用层 (App) │ 应用层任务 (多个 FreeRTOS Task)
├─────────────────────────────────────────────────────────┤ │ net_send() / net_recv() / net_close() (线程安全封装) │
│ net_socket.h (BSD Socket API) │ ← 用户接口 ├──────────────────────────────────────────────────────────────┤
├─────────────────────────────────────────────────────────┤ │ 消息队列 (osMessageQueue) │
net_socket.c + net_select.c (状态机+select) ← 核心逻辑 由 CubeMX 生成,在 MX_FREERTOS_Init() 中创建
├─────────────────────────────────────────────────────────┤ ├──────────────────────────────────────────────────────────────
ch395f.c/h (底层驱动) ← 硬件驱动 netTask (网络任务线程)
├─────────────────────────────────────────────────────────┤ │ net_process_messages() + net_poll() — 串行化执行 │
CH395F 硬件 net_send_sock() / net_recv_sock() / net_close_sock()
───────────────────────────────────────────────────────── ├──────────────────────────────────────────────────────────────
│ net_socket.c (BSD Socket API 核心) │
│ Socket 状态机 + CH395F 命令封装 │
├──────────────────────────────────────────────────────────────┤
│ ch395f.c/h (底层 SPI 驱动) │
│ 所有 SPI 事务必须由 netTask 发起 │
├──────────────────────────────────────────────────────────────┤
│ CH395F 硬件 (SPI2) │
└──────────────────────────────────────────────────────────────┘
``` ```
### 2.1 文件说明 ### 2.2 文件说明
| 文件 | 说明 | | 文件 | 说明 |
|------|------| |------|------|
| `net_config.h` | 配置宏定义(最大 Socket 数、超时时间等) | | `net_config.h` | 配置宏定义(最大 Socket 数、超时时间等) |
| `net_types.h` | 类型定义地址结构、Socket 控制块等) | | `net_types.h` | 类型定义地址结构、Socket 控制块等) |
| `net_socket.h` | BSD Socket API 头文件 | | `net_socket.h` | BSD Socket API 头文件,含 `net_msg_t` 消息类型定义 |
| `net_socket.c` | API 核心实现 | | `net_socket.c` | API 核心实现,含消息队列处理、线程安全封装、内部 `_sock()` 版本 |
| `net_select.h` | select/poll API 头文件 | | `net_select.h` | select/poll API 头文件 |
| `net_select.c` | select/poll 实现 | | `net_select.c` | select/poll 实现 |
### 2.1a 底层驱动改进v2.1 ### 2.3 底层驱动改进v2.1
ch395f.c/h 已完成以下优化: ch395f.c/h 已完成以下优化:
@@ -84,7 +95,7 @@ ch395f.c/h 已完成以下优化:
| 新增函数 | `ch395f_set_arp()` / `ch395f_set_ttl()` / `ch395f_get_unreach_info()` / `ch395f_poll_cmd_status()` | | 新增函数 | `ch395f_set_arp()` / `ch395f_set_ttl()` / `ch395f_get_unreach_info()` / `ch395f_poll_cmd_status()` |
| KeepAlive | 参数单位为毫秒(非秒),必须为 500 的倍数 | | KeepAlive | 参数单位为毫秒(非秒),必须为 500 的倍数 |
### 2.2 Socket 状态机 ### 2.4 Socket 状态机
``` ```
net_socket() net_socket()
@@ -120,16 +131,86 @@ ch395f.c/h 已完成以下优化:
--- ---
## 3. 快速开始 ## 3. 线程安全设计
### 3.1 包含头文件 ### 3.1 设计背景
NET 层(`net_socket.c` + `ch395f.c`)不是线程安全的:
- Socket 控制块(`s_net_socks[]`)无互斥保护
- CH395F SPI 事务不重入STM32 HAL SPI 无内部锁)
- 状态机在多个任务中并发访问会崩溃
引入消息队列将所有的 CH395F 操作串行化到 `netTask` 中执行。
### 3.2 消息队列模式
```
App Task A App Task B netTask
| | |
net_send() net_recv() net_process_messages()
| | + net_poll()
▼ ▼ │
┌─────────┐ ┌─────────┐ │
│send msg │ │recv msg │ │
└────┬────┘ └────┬────┘ │
│ │ │
└─────────┬─────────┘ │
▼ ▼
┌──────────────────┐ ┌──────────────┐
│ 消息队列 │──────────│ 取出消息 │
│ osMessageQueue │ dequeue │ 执行操作 │
└──────────────────┘ │ 写结果 │
▲ │ 通知调用方 │
│ └──────┬───────┘
│ ulTaskNotifyTake() │
│ (阻塞等待结果) │ xTaskNotifyGive()
│ │
┌─────┴──────┐ │
│ 返回结果给 │◄──────────────────────┘
│ 调用方 │
└────────────┘
```
### 3.3 关键规则
| 规则 | 说明 |
|------|------|
| **`netTask` 外调用** | 使用 `net_send()` / `net_recv()` / `net_close()` 公共 API |
| **`netTask` 内调用** | 使用 `net_send_sock()` / `net_recv_sock()` / `net_close_sock()` 内部 API |
| **创建/绑定/监听** | 在 `main()` 初始化阶段完成(`osKernelStart()` 之前),无需队列 |
| **`net_poll()``net_process_messages()`** | 必须在 `netTask` 主循环中调用 |
### 3.4 内部 API 说明
内部 API`_sock` 后缀)直接操作 Socket 控制块和 CH395F不经过消息队列。用于 `netTask` 中的网络业务逻辑,避免向自身发送消息造成死锁:
```c
/* 在 netTask 中直接操作 Socket绕过消息队列 */
int net_send_sock(net_sock_t *p_sock, const void *buf, int len);
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags);
int net_close_sock(net_sock_t *p_sock);
```
### 3.5 注意事项
- **消息队列由 CubeMX 创建**:在 `MX_FREERTOS_Init()``osKernelInitialize()` 之后)中调用 `osMessageQueueNew(8, sizeof(net_msg_t), NULL)` 创建
- **队列句柄全局可见**:通过 `extern osMessageQueueId_t netMsgQueueHandle` 引用
- **队列满时**`net_send()` / `net_recv()` 会阻塞等待,直到 `netTask` 处理完消息腾出空间
- **`netTask` 不可向自身发消息**`netTask` 中直接调 `net_send()` 会死锁(等待自己处理消息),必须在 `netTask` 中使用 `net_send_sock()` 等内部版本
- **`net_socket()` / `net_bind()` / `net_listen()` 无需线程安全**:这些仅在初始化阶段调用,消息队列仅保护数据收发和连接关闭
---
## 4. 快速开始
### 4.1 包含头文件
```c ```c
#include "net_socket.h" #include "net_socket.h"
#include "net_select.h" #include "net_select.h"
``` ```
### 3.2 初始化网络 ### 4.2 初始化网络
```c ```c
int main(void) int main(void)
@@ -154,29 +235,55 @@ int main(void)
} }
``` ```
### 3.3 主循环要求 ### 4.3 主循环要求
**重要**`net_poll()` 必须在主循环中定期调用,它负责: **重要**本项目使用 FreeRTOS 多任务架构,`net_poll()``net_process_messages()` 均在 `netTask`FreeRTOS 线程)中定期调用,而非主循环。
- 轮询 CH395F INT# 引脚电平GPIO 轮询,无需 EXTI 中断)
- 通过 `GET_GLOB_INT_STATUS_ALL`2字节版读取所有 8 个 Socket 的中断状态 - **主循环**:系统初始化完成后进入空循环,等待 `osKernelStart()` 启动调度器
- 更新所有 Socket 的连接状态CONNECT / DISCONNECT / RECV / TIMEOUT - **netTask**:在任务函数中持续运行,每 10ms 调用一次 `net_poll()` + `net_process_messages()`,负责:
- 触发事件回调 - 轮询 CH395F INT# 引脚电平GPIO 轮询,无需 EXTI 中断)
- 处理数据接收 - 通过 `GET_GLOB_INT_STATUS_ALL`2字节版读取所有 8 个 Socket 的中断状态
- 更新所有 Socket 的连接状态CONNECT / DISCONNECT / RECV / TIMEOUT
- 触发事件回调
- 处理数据接收
```c ```c
while (1) int main(void)
{ {
net_poll(); /* 建议至少每 10ms 调用一次 */ /* 外设初始化... */
/* 其他应用代码... */ /* BSD Socket API 初始化 */
net_init("192.168.1.100", "255.255.255.0", "192.168.1.1");
/* 创建 Socket、绑定、监听... */
osKernelInitialize();
MX_FREERTOS_Init(); /* 创建 netTask 等 FreeRTOS 对象 */
osKernelStart(); /* 启动调度器 */
while (1) { } /* 空循环,控制流已交给调度器 */
}
/* netTask 中freertos.c */
void StartNetTask(void *argument)
{
for (;;)
{
net_poll(); /* CH395F 中断轮询 + Socket 状态更新 */
net_process_messages(); /* 处理其他任务发送的网络操作请求 */
/* 应用层网络业务逻辑... */
osDelay(10); /* 10ms 周期 */
}
} }
``` ```
--- ---
## 4. TCP 编程 ## 5. TCP 编程
### 4.1 TCP Server单连接 ### 5.1 TCP Server单连接
单连接模式下1 个 Socket 既做监听又做数据通信: 单连接模式下1 个 Socket 既做监听又做数据通信:
@@ -258,7 +365,7 @@ void tcp_server_single(void)
} }
``` ```
### 4.2 TCP Server多连接 ### 5.2 TCP Server多连接
多连接模式使用 1 个监听 Socket + N 个数据 Socket支持并发连接 多连接模式使用 1 个监听 Socket + N 个数据 Socket支持并发连接
@@ -348,7 +455,7 @@ void tcp_server_multi(void)
} }
``` ```
### 4.3 TCP Client ### 5.3 TCP Client
```c ```c
#include "net_socket.h" #include "net_socket.h"
@@ -413,9 +520,9 @@ void tcp_client(void)
--- ---
## 5. UDP 编程 ## 6. UDP 编程
### 5.1 UDP Client ### 6.1 UDP Client
```c ```c
#include "net_socket.h" #include "net_socket.h"
@@ -472,7 +579,7 @@ void udp_client(void)
} }
``` ```
### 5.2 UDP Server ### 6.2 UDP Server
```c ```c
#include "net_socket.h" #include "net_socket.h"
@@ -531,9 +638,9 @@ void udp_server(void)
--- ---
## 6. I/O 多路复用 ## 7. I/O 多路复用
### 6.1 select 使用 ### 7.1 select 使用
`net_select()` 可以同时监控多个 Socket 的读写事件: `net_select()` 可以同时监控多个 Socket 的读写事件:
@@ -578,7 +685,7 @@ void select_example(void)
} }
``` ```
### 6.2 poll 使用 ### 7.2 poll 使用
`net_poll_events()` 使用 `pollfd` 结构体数组: `net_poll_events()` 使用 `pollfd` 结构体数组:
@@ -616,7 +723,7 @@ void poll_example(void)
} }
``` ```
### 6.3 事件掩码说明 ### 7.3 事件掩码说明
| 事件 | 说明 | | 事件 | 说明 |
|------|------| |------|------|
@@ -628,11 +735,11 @@ void poll_example(void)
--- ---
## 7. 事件回调机制 ## 8. 事件回调机制
可以通过注册回调函数,异步获取 Socket 事件通知: 可以通过注册回调函数,异步获取 Socket 事件通知:
### 7.1 定义回调函数 ### 8.1 定义回调函数
```c ```c
#include "net_socket.h" #include "net_socket.h"
@@ -669,7 +776,7 @@ void my_event_callback(int sockfd, net_event_t event, void *arg)
} }
``` ```
### 7.2 注册回调 ### 8.2 注册回调
```c ```c
void event_callback_example(void) void event_callback_example(void)
@@ -691,11 +798,11 @@ void event_callback_example(void)
--- ---
## 8. 字节序处理 ## 9. 字节序处理
网络协议使用**大端序**(网络字节序),而 STM32 使用**小端序**(主机字节序)。发送和接收数据时需要进行字节序转换。 网络协议使用**大端序**(网络字节序),而 STM32 使用**小端序**(主机字节序)。发送和接收数据时需要进行字节序转换。
### 8.1 端口号转换 ### 9.1 端口号转换
```c ```c
uint16_t port = 8080; uint16_t port = 8080;
@@ -707,7 +814,7 @@ uint16_t net_port = net_htons(port);
uint16_t host_port = net_ntohs(net_port); uint16_t host_port = net_ntohs(net_port);
``` ```
### 8.2 IP 地址转换 ### 9.2 IP 地址转换
```c ```c
/* 字符串转网络序 IP */ /* 字符串转网络序 IP */
@@ -718,7 +825,7 @@ char ip_str[16];
net_inet_ntoa(ip, ip_str); /* ip_str = "192.168.1.100" */ net_inet_ntoa(ip, ip_str); /* ip_str = "192.168.1.100" */
``` ```
### 8.3 填充地址结构体 ### 9.3 填充地址结构体
```c ```c
struct net_sockaddr_in addr; struct net_sockaddr_in addr;
@@ -733,7 +840,7 @@ addr.sin_addr.s_addr = net_inet_addr("0.0.0.0");
addr.sin_addr.s_addr = INADDR_ANY; addr.sin_addr.s_addr = INADDR_ANY;
``` ```
### 8.4 读取地址信息 ### 9.4 读取地址信息
```c ```c
struct net_sockaddr_in client_addr; struct net_sockaddr_in client_addr;
@@ -757,9 +864,9 @@ if (client_sock >= 0)
--- ---
## 9. API 参考 ## 10. API 参考
### 9.1 核心 API ### 10.1 核心 API
#### net_init - 网络初始化 #### net_init - 网络初始化
@@ -898,7 +1005,41 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
int net_close(int sockfd); int net_close(int sockfd);
``` ```
### 9.2 辅助 API ### 10.2 消息队列 API
#### net_process_messages - 处理消息队列
```c
void net_process_messages(void);
```
处理其他任务通过 `net_send()` / `net_recv()` / `net_close()` 发送到消息队列的请求。必须在 `netTask` 主循环中调用。
#### net_send_sock - 发送数据(内部版本)
```c
int net_send_sock(net_sock_t *p_sock, const void *buf, int len);
```
直接操作 Socket 控制块进行发送,不经过消息队列。**只能在 `netTask` 中调用**,用于 `netTask` 内的网络业务逻辑。
#### net_recv_sock - 接收数据(内部版本)
```c
int net_recv_sock(net_sock_t *p_sock, void *buf, int len, int flags);
```
直接操作 Socket 控制块进行接收,不经过消息队列。**只能在 `netTask` 中调用**。
#### net_close_sock - 关闭 Socket内部版本
```c
int net_close_sock(net_sock_t *p_sock);
```
直接操作 Socket 控制块关闭连接,不经过消息队列。**只能在 `netTask` 中调用**。
### 10.3 辅助 API
#### net_set_event_cb - 注册事件回调 #### net_set_event_cb - 注册事件回调
@@ -918,7 +1059,7 @@ net_sock_t *net_get_sock(int sockfd);
int net_get_errno(void); int net_get_errno(void);
``` ```
### 9.3 字节序转换 ### 10.4 字节序转换
```c ```c
uint16_t net_htons(uint16_t hostshort); /* 主机序 -> 网络序 */ uint16_t net_htons(uint16_t hostshort); /* 主机序 -> 网络序 */
@@ -929,7 +1070,7 @@ uint32_t net_inet_addr(const char *cp); /* 字符串 -> 网络序 IP */
char *net_inet_ntoa(uint32_t addr, char *buf); /* 网络序 IP -> 字符串 */ char *net_inet_ntoa(uint32_t addr, char *buf); /* 网络序 IP -> 字符串 */
``` ```
### 9.4 select/poll ### 10.5 select/poll
```c ```c
int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
@@ -938,7 +1079,7 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
int net_poll_events(net_pollfd *fds, int nfds, int timeout); int net_poll_events(net_pollfd *fds, int nfds, int timeout);
``` ```
### 9.5 fd_set 操作宏 ### 10.6 fd_set 操作宏
```c ```c
NET_FD_ZERO(fdset) /* 清空集合 */ NET_FD_ZERO(fdset) /* 清空集合 */
@@ -949,9 +1090,9 @@ NET_FD_ISSET(fd, fdset) /* 检查 fd 是否在集合中 */
--- ---
## 10. 完整示例 ## 11. 完整示例
### 10.1 TCP Echo Server多连接 ### 11.1 TCP Echo Server多连接
```c ```c
#include "net_socket.h" #include "net_socket.h"
@@ -1047,7 +1188,7 @@ void tcp_echo_server(void)
} }
``` ```
### 10.2 TCP Client 自动重连 ### 11.2 TCP Client 自动重连
```c ```c
#include "net_socket.h" #include "net_socket.h"
@@ -1135,22 +1276,22 @@ void tcp_client_reconnect(void)
--- ---
## 11. 常见问题 ## 12. 常见问题
### 11.1 net_poll() 必须调用吗? ### 12.1 net_poll() 必须调用吗?
**是的**`net_poll()` 负责轮询 CH395F 的中断状态并更新所有 Socket 的状态。如果不调用,连接状态不会更新,数据也无法接收。 **是的**`net_poll()` 负责轮询 CH395F 的中断状态并更新所有 Socket 的状态。如果不调用,连接状态不会更新,数据也无法接收。
建议至少每 10ms 调用一次。 建议至少每 10ms 调用一次。
### 11.2 Socket 数量有限制吗? ### 12.2 Socket 数量有限制吗?
是的CH395F 最多支持 8 个 Socket索引 0~7。其中 是的CH395F 最多支持 8 个 Socket索引 0~7。其中
- TCP Server 多连接模式1 个监听 Socket + 最多 7 个数据 Socket - TCP Server 多连接模式1 个监听 Socket + 最多 7 个数据 Socket
- TCP Client最多 8 个 - TCP Client最多 8 个
- UDP最多 8 个 - UDP最多 8 个
### 11.3 TCP Server 多连接模式有什么要求? ### 12.3 TCP Server 多连接模式有什么要求?
**模式工作原理:** **模式工作原理:**
- CH395F 多连接模式下Socket 0 专职监听Socket 1~7 由芯片自动分配 - CH395F 多连接模式下Socket 0 专职监听Socket 1~7 由芯片自动分配
@@ -1160,13 +1301,13 @@ void tcp_client_reconnect(void)
**硬件要求:** **硬件要求:**
- CH395F 固件版本 >= 0x44支持 Socket 4~7 - CH395F 固件版本 >= 0x44支持 Socket 4~7
### 11.4 发送数据有什么限制? ### 12.4 发送数据有什么限制?
- 每次发送的数据长度不能超过 `NET_SEND_BUF_SIZE`(默认 4096 字节) - 每次发送的数据长度不能超过 `NET_SEND_BUF_SIZE`(默认 4096 字节)
- TCP 需要等待发送缓冲区空闲(`send_ready` 标志) - TCP 需要等待发送缓冲区空闲(`send_ready` 标志)
- UDP 单次发送最大 1460 字节 - UDP 单次发送最大 1460 字节
### 11.5 如何处理连接断开? ### 12.5 如何处理连接断开?
**方式一:阻塞接收** **方式一:阻塞接收**
```c ```c
@@ -1197,7 +1338,7 @@ void callback(int sockfd, net_event_t event, void *arg)
} }
``` ```
### 11.6 select 和 poll 的区别? ### 12.6 select 和 poll 的区别?
| 特性 | select | poll | | 特性 | select | poll |
|------|--------|------| |------|--------|------|
@@ -1207,7 +1348,7 @@ void callback(int sockfd, net_event_t event, void *arg)
两者功能相同,推荐使用 `net_select()`,更接近 Linux 编程习惯。 两者功能相同,推荐使用 `net_select()`,更接近 Linux 编程习惯。
### 11.7 错误码说明 ### 12.7 错误码说明
| 错误码 | 宏定义 | 说明 | | 错误码 | 宏定义 | 说明 |
|--------|--------|------| |--------|--------|------|
@@ -1224,7 +1365,7 @@ void callback(int sockfd, net_event_t event, void *arg)
| -10 | NET_ERR_NOSPACE | 发送缓冲区无空间 | | -10 | NET_ERR_NOSPACE | 发送缓冲区无空间 |
| -11 | NET_ERR_BUSY | 操作未完成BUSY | | -11 | NET_ERR_BUSY | 操作未完成BUSY |
### 11.8 CH395F 命令执行状态码 ### 12.8 CH395F 命令执行状态码
CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()``ch395f_poll_cmd_status()` CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()``ch395f_poll_cmd_status()`
@@ -1249,7 +1390,7 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
| 0x5F | CH395F_CMD_RET_ABORT | 命令中止 | | 0x5F | CH395F_CMD_RET_ABORT | 命令中止 |
| 0xFA | CH395F_ERR_UNKNOW | 未知错误 | | 0xFA | CH395F_ERR_UNKNOW | 未知错误 |
### 11.9 故障排查 ### 12.9 故障排查
**问题TCP Client 连接超时,无 CONNECT 中断** **问题TCP Client 连接超时,无 CONNECT 中断**
- 检查多连接模式下数据 Socket1~7是否设置了 `SET_PROTO_TCP` + `SET_SOUR_PORT`(与监听端口相同) - 检查多连接模式下数据 Socket1~7是否设置了 `SET_PROTO_TCP` + `SET_SOUR_PORT`(与监听端口相同)
@@ -1266,3 +1407,21 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
**问题CH395F 初始化超时** **问题CH395F 初始化超时**
- 芯片 INIT 需要约 200ms轮询间隔应为 20ms过短的查询可能干扰内部处理 - 芯片 INIT 需要约 200ms轮询间隔应为 20ms过短的查询可能干扰内部处理
- 超时值建议设为 4 秒以上 - 超时值建议设为 4 秒以上
### 12.10 线程安全问题
**问题:`net_send()` / `net_recv()` / `net_close()` 可以在任意任务中调用吗?**
可以。这些公共 API 通过消息队列将请求委托给 `netTask` 串行执行,自身阻塞等待结果,是线程安全的。
**问题:`netTask` 内可以直接调用 `net_send()` / `net_recv()` 吗?**
不可以。`netTask` 中必须使用 `net_send_sock()` / `net_recv_sock()` 等内部版本,否则会死锁(任务向自身发送消息,永远无人处理)。内部版本直接操作 Socket 控制块,不经过消息队列。
**问题:`net_socket()` / `net_bind()` / `net_listen()` 线程安全吗?**
这些函数仅在系统初始化阶段(`osKernelStart()` 之前)调用,不存在并发访问。消息队列仅保护数据收发和连接关闭操作。
**问题:消息队列由谁创建?在哪里创建?**
由 CubeMX 在 `MX_FREERTOS_Init()` 中创建(`osMessageQueueNew(8, sizeof(net_msg_t), NULL)`),在 `osKernelInitialize()` 之后执行,确保 FreeRTOS 内核已就绪。

View File

@@ -0,0 +1,611 @@
# STM32F4-Base 存储架构说明
## 1. 概述
本项目在 GD5F2GQ5UE SPI NAND Flash256MB上实现了四层存储软件栈
```
┌─────────────────────────────────────────────────────────────┐
│ 应用程序 (main, freertos) │
├──────────────────────┬──────────────────┬──────────────────┤
│ │ │ │
│ FlashDB KVDB │ FlashDB TSDB │ FatFs │
│ (键值数据库) │ (时序数据库) │ (文件系统) │
│ │ │ │
├──────────┬───────────┴──────┬───────────┴──────────────────┤
│ │ │ │
│ FAL 抽象层 (分区访问) │ dhara FTL │
│ (Flash Abstraction Layer)│ (地址映射 · 磨损均衡 │
│ 直接转发到底层驱动) │ 坏块管理 · 垃圾回收) │
│ │ │ │
├──────────┴──────────────────┴──────────────────────────────┤
│ GD5F2GQ5UE NAND Flash 驱动 (底层 SPI) │
│ 硬件 SPI · 页读写 · 块擦除 · BBT · 内部 ECC 使能 │
├─────────────────────────────────────────────────────────────┤
│ SPI1 硬件外设 (PB3 SCK, PB4 MISO, PB5 MOSI) │
│ 42MHz · Mode 0 · MSB First · DMA2 (S0-RX, S3-TX) │
│ 小包轮询(≤32B) · 页数据 DMA (>32B) │
└─────────────────────────────────────────────────────────────┘
```
```
┌─────────────────────────────────────────────────────────────┐
│ 物理分区布局 (256MB) │
│ │
│ ┌──────────────────────┬──────────────────┬───────────────┐ │
│ │ fdb_kvdb1 (64MB) │ fdb_tsdb1 (64MB) │ ftl_fatfs │ │
│ │ Block 0~511 │ Block 512~1023 │ Block 1024~2047│ │
│ │ FlashDB 键值数据库 │ FlashDB 时序数据库│ dhara + FatFS │ │
│ └──────────────────────┴──────────────────┴───────────────┘ │
│ 偏移: 0 64MB 128MB 256MB│
└─────────────────────────────────────────────────────────────┘
```
**两条独立路径:**
- **FlashDB 路径**`FlashDB → FAL → gd5f2gq5ue 驱动`FAL 是薄封装层,将 FAL 读写请求直接转发给底层 NAND 驱动。FlashDB 在 FAL 之上自行管理磨损均衡和掉电安全。
- **FatFS 路径**`FatFS → dhara FTL → gd5f2gq5ue 驱动`FTL 提供 LBA 到物理页的映射、磨损均衡、垃圾回收和坏块管理FatFS 通过标准 `disk_*` 接口访问 FTL 提供的块设备。
两个路径共享最底层 NAND 驱动但各自管理不同的物理分区FlashDB 管理 Block 0~1023FTL 管理 Block 1024~2047互不干扰。
**初始化顺序:**
```
SPI1_Init → gd5f2gq5ue_init() → fdb_kvdb_init() [可选FlashDB 路径]
→ fdb_tsdb_init() [可选FlashDB 路径]
→ f_mount() [触发 FTL 初始化FatFS 路径]
```
---
## 2. 硬件规格GD5F2GQ5UE
| 参数 | 值 |
|------|-----|
| 容量 | 256 MB (2 Gb) |
| 页大小 (主区) | 2048 bytes (2 KB) |
| 备校区 (Spare) | 64 bytes/页 |
| 每块页数 | 64 |
| 块大小 | **128 KB** (64 × 2 KB) |
| 总块数 | 2048 |
| 总页面数 | 131072 |
| 内部 ECC | 支持 (每页 8bit ECC) |
| 接口 | SPI (Mode 0, CPOL=0 CPHA=0) |
| 最高时钟 | 42 MHz |
| 读页延迟 | < 60 μs |
| 编程延迟 | < 600 μs |
| 块擦除延迟 | < 3 ms |
**SPI 引脚分配:**
| 信号 | GPIO | 说明 |
|------|------|------|
| CS# | PE0 | 片选 (低有效) |
| SCK | PB3 | SPI1 SCK |
| MISO | PB4 | SPI1 MISO (主入从出) |
| MOSI | PB5 | SPI1 MOSI (主出从入) |
| WP# | PB8 | 写保护 (本驱动恒拉高) |
| HOLD# | PE1 | 保持 (本驱动恒拉高) |
---
## 3. 分区布局
三个分区物理隔离、互不重叠,配置集中在 `Drivers/BSP/GD5F2GQ5UE/fal_cfg.h:18-30`
```c
#define FDB_KVDB1_OFFSET 0
#define FDB_KVDB1_SIZE (64 * 1024 * 1024) // 64MB
#define FDB_TSDB1_OFFSET (FDB_KVDB1_OFFSET + FDB_KVDB1_SIZE)
#define FDB_TSDB1_SIZE (64 * 1024 * 1024) // 64MB
#define FTL_FATFS_OFFSET (FDB_TSDB1_OFFSET + FDB_TSDB1_SIZE) // 128MB
#define FTL_FATFS_SIZE (128 * 1024 * 1024) // 128MB
```
### 3.1 分区对照表
| 分区名 | 偏移 | 大小 | 物理块范围 | 逻辑用途 |
|--------|------|------|-----------|----------|
| `fdb_kvdb1` | 0 | 64 MB | Block 0~511 | FlashDB 键值数据库 (KVDB) |
| `fdb_tsdb1` | 64 MB | 64 MB | Block 512~1023 | FlashDB 时序数据库 (TSDB) |
| `ftl_fatfs` | 128 MB | 128 MB | Block 1024~2047 | dhara FTL + FatFS |
### 3.2 配置联动
所有分区边界在 `fal_cfg.h` 定义后,自动传播到各子模块:
- **FAL 分区表** — `FAL_PART_TABLE` 宏直接引用 `FDB_KVDB1_OFFSET/SIZE``FDB_TSDB1_OFFSET/SIZE`
- **FTL** — `nand_ftl.c` 通过 `#include "fal_cfg.h"` 引用 `FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE` 计算 `FTL_START_BLOCK = 1024`
- **FatFS** — 通过 `disk_ioctl(GET_SECTOR_COUNT)` 获取 FTL 管理的扇区数
> 调整分区大小时只需修改 `fal_cfg.h` 顶部 6 个宏,所有下游模块自动适配。
---
## 4. 驱动层gd5f2gq5ue.c/h
### 4.1 SPI 命令集
| 命令 | 编码 | 用途 |
|------|------|------|
| `WRITE_ENABLE` | `0x06` | 写使能 (每个写/擦除前必须发送) |
| `WRITE_DISABLE` | `0x04` | 写禁止 |
| `GET_FEATURE` | `0x0F` | 读状态/feature 寄存器 |
| `SET_FEATURE` | `0x1F` | 写 feature 寄存器 (前需写使能) |
| `READ_ID` | `0x9F` | 读芯片 ID |
| `PAGE_READ` | `0x13` | 将页数据从存储阵列载入内部缓存 |
| `READ_FROM_CACHE` | `0x0B` | 从内部缓存读取数据 |
| `PROGRAM_LOAD` | `0x02` | 将数据写入内部缓存 |
| `PROGRAM_EXEC` | `0x10` | 将缓存数据编程到存储阵列 |
| `BLOCK_ERASE` | `0xD8` | 块擦除 (128KB) |
| `RESET` | `0xFF` | 芯片复位 |
### 4.2 Feature 寄存器
| 地址 | 名称 | 说明 |
|------|------|------|
| `0xA0` | Protect | 块保护 (写入 `0x00` 解除全部保护) |
| `0xB0` | Feature | ECC 使能 (bit4=1 启用) |
| `0xC0` | Status | 状态标志 (OIP/WEL/E_FAIL/P_FAIL/ECC) |
| `0xD0` | Driver | 驱动强度 |
### 4.3 初始化序列
```
1. RESET (FFh)
└─ 等待 5ms
2. READ_ID (9Fh)
└─ 校验 MID=0xC8, DID=0x52
3. BBT 扫描
└─ 读每块最后一页 (page 63) spare byte 0
└─ 非 0xFF 即出厂坏块,写入 s_bbt[]
4. SET_FEATURE (B0h=10h) — 使能内部 8bit ECC
5. SET_FEATURE (A0h=00h) — 解除全部块保护
```
### 4.4 读写擦除操作
**页读取**(任何字节偏移均可,驱动自动定位到页):
```
PAGE_READ (13h + 3字节行地址) → 等待 OIP 清零 → READ_FROM_CACHE (0Bh + 2字节列地址 + dummy)
```
**页编程**NAND 只能将 bit 从 1→0 翻转,编程前必须擦除):
```
WRITE_ENABLE → PROGRAM_LOAD (02h + 列地址 + 数据) → PROGRAM_EXEC (10h + 行地址) → 等待完成 → 检查 P_FAIL
```
**块擦除**(最小擦除单位 128KB参数是字节地址而非块编号
```
WRITE_ENABLE → BLOCK_ERASE (D8h + 3字节字节地址) → 等待完成 → 检查 E_FAIL
```
### 4.5 DMA 传输策略2026-07-21 新增)
SPI1 使用 DMA2 实现页数据级别的不阻塞传输,配置如下:
| 通道 | DMA | 流 | 通道 | 方向 | 优先级 | 模式 |
|------|-----|----|------|------|--------|------|
| SPI1_RX | DMA2 | Stream 0 | CH3 | 外设→内存 | LOW | NORMAL |
| SPI1_TX | DMA2 | Stream 3 | CH3 | 内存→外设 | LOW | NORMAL |
**传输策略:**
- **小包轮询(≤ 32 bytes**:命令字、地址、状态寄存器等短数据使用 `HAL_SPI_Transmit/Receive` 轮询模式,避免 DMA 初始化开销
- **页数据 DMA> 32 bytes**`READ_FROM_CACHE``PROGRAM_LOAD` 的页数据段使用 `HAL_SPI_Receive_DMA` / `HAL_SPI_Transmit_DMA`
**HAL 内部路由说明:**
`HAL_SPI_Receive_DMA()` 在 2 线 Master 模式内部调用 `HAL_SPI_TransmitReceive_DMA()`,但由于 SPI 状态设置为 `HAL_SPI_STATE_BUSY_RX`HAL 的完成回调为 `HAL_SPI_RxCpltCallback`(而非 `TxRxCpltCallback`),需确保该回调在 `gd5f2gq5ue.c` 中实现。
**完整回调链条:**
| 操作 | 触发回调 | 实现位置 |
|------|---------|---------|
| `PROGRAM_LOAD` TX DMA | `HAL_SPI_TxCpltCallback` | `gd5f2gq5ue.c` |
| `READ_FROM_CACHE` RX DMA | `HAL_SPI_RxCpltCallback` | `gd5f2gq5ue.c` |
| 任意外设错误 | `HAL_SPI_ErrorCallback` | `ch395f.c`(含 SPI1 分支) |
**性能(顺序 128KB 读写FatFS + FTL**
| 模式 | 写 | 读 |
|------|----|----|
| 轮询(改造前) | 330 KB/s | 547 KB/s |
| DMA改造后 | **831 KB/s** | **1855 KB/s** |
| 42MHz SPI 理论极限 | ~5.25 MB/s受 NAND tPROG ≈ 500μs/页 限制) | ~5.25 MB/s |
DMA 消除了轮询模式下 SPI 状态寄存器查检的逐字节 CPU 开销,读写性能分别提升 **2.5×****3.4×**。进一步优化需考虑 Cache Read 模式(重叠 NAND 内部 tR 延迟)或批量编程(减少 tPROG 次数)。
### 4.6 BBT (Bad Block Table)
- 初始化时扫描全部 2048 块最后一页的 spare byte 0
- `s_bbt[256]` 位图数组1 bit 标识 1 个块 (0=好, 1=坏)
- `gd5f2gq5ue_is_block_bad(block)` — 查询坏块状态
- `gd5f2gq5ue_mark_block_bad(block)` — 标记坏块 (FTL 层在擦除/编程失败时调用)
---
## 5. FAL 抽象层
FAL (Flash Abstraction Layer) 是 FlashDB 自带的 flash 抽象层,**仅 FlashDB 路径使用**。它是一个薄封装层,将 FAL 的 read/write/erase 请求直接转发给底层 NAND 驱动,不做地址映射或磨损均衡(这些由 FlashDB 自身在 FAL 之上完成)。
FAL 不参与 FatFS/FTL 路径。FTL 有自己的 HAL 回调直接对接底层 NAND 驱动,与 FAL 无关。
```
FlashDB ──→ FAL ──→ gd5f2gq5ue_read/write/erase (直接透传)
FatFs ──→ dhara FTL ──→ dhara_nand_* 回调 ──→ gd5f2gq5ue_* (独立路径)
```
### 5.1 设备注册 (fal_flash_gd5f2gq5ue.c)
```c
const struct fal_flash_dev g_gd5f2gq5ue_flash = {
.name = "gd5f2gq5ue",
.addr = 0,
.len = GD5F_TOTAL_SIZE, // 256MB
.blk_size = GD5F_BLOCK_SIZE, // 128KB
.ops = { .init, .read, .write, .erase },
.write_gran = 8, // byte programmable
};
```
### 5.2 分区表 (fal_cfg.h)
```c
#define FAL_PART_TABLE \
{ \
{FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", FDB_KVDB1_OFFSET, FDB_KVDB1_SIZE, 0}, \
{FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", FDB_TSDB1_OFFSET, FDB_TSDB1_SIZE, 0}, \
}
```
FlashDB 通过分区名 (`"fdb_kvdb1"`, `"fdb_tsdb1"`) 绑定到 FAL 分区。
---
## 6. FlashDB 数据库
### 6.1 配置 (fdb_cfg.h)
```c
#define FDB_USING_KVDB // 启用键值数据库
#define FDB_USING_TSDB // 启用时序数据库
#define FDB_USING_FAL_MODE // 使用 FAL 存储模式
#define FDB_WRITE_GRAN 8 // 写粒度 8bit (字节可编程)
```
### 6.2 查询接口
FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动额外的设备注册。
### 6.3 KVDB 键值数据库
适用于存储配置参数、运行状态等少量键值对。
**特点:**
- 掉电安全 (写操作带 CRC 校验)
- 支持 blob (二进制数据块)
- 自动磨损均衡
- 支持默认值 (首次启动自动写入)
### 6.4 TSDB 时序数据库
适用于存储采样数据、日志记录等时间序列数据。
**特点:**
- 固定长度记录
- 时间戳索引 (需提供 `get_time` 回调)
- 自动擦除 (新数据覆盖最旧数据)
- 支持按时间范围查询
---
## 7. dhara FTL (Flash Translation Layer)
### 7.1 概述
FTL 是 NAND Flash 上方最重要的组件,功能包括:
| 功能 | 说明 |
|------|------|
| **地址映射** | 将 FatFS 的逻辑扇区号 (LBA) 映射到 NAND 物理页 |
| **磨损均衡** | 均匀分布擦除次数,延长芯片寿命 |
| **垃圾回收** | 回收无效页面空间 (GC) |
| **坏块管理** | 擦除/编程失败时自动跳过并标记坏块 |
| **ECC 处理** | 检测和上报硬件 ECC 不可纠正错误 |
### 7.2 数据结构
```c
struct dhara_nand {
int log2_page_size; // 页大小对数 (2KB → 11)
int log2_ppb; // 每块页数对数 (64 → 6)
int num_blocks; // 管理的物理块数 (1024)
};
struct dhara_map {
// 内部状态GC 队列、journal、映射表等
};
```
### 7.3 NAND HAL (nand_ftl.c)
dhara 通过 7 个回调函数访问物理 NAND
| 回调 | 功能 | 对应底层函数 |
|------|------|-------------|
| `dhara_nand_is_bad` | 查询坏块 | `gd5f2gq5ue_is_block_bad(block + 1024)` |
| `dhara_nand_mark_bad` | 标记坏块 | `gd5f2gq5ue_mark_block_bad(block + 1024)` |
| `dhara_nand_erase` | 擦除块 | `nand_block_erase(block + 1024)` |
| `dhara_nand_prog` | 写页 | `nand_program_load + nand_program_exec` |
| `dhara_nand_read` | 读页 | `nand_page_read_to_cache + nand_read_from_cache` |
| `dhara_nand_is_free` | 检查页空闲 | 读前 64 字节判断全为 0xFF |
| `dhara_nand_copy` | 页拷贝 (GC 用) | read + prog 组合 |
所有回调自动将 dhara 逻辑块/页加 `FTL_START_BLOCK` 偏移转换为物理地址。
### 7.4 初始化流程 (disk_initialize)
```
1. 设置 nand 参数 (log2_page_size=11, log2_ppb=6, num_blocks=1024)
2. dhara_map_init(&s_map, &s_nand, s_page_buf, 4)
├─ s_page_buf: dhara 内部使用的 2KB 工作缓冲区
└─ 4: journal 页面数量 (影响 GC 效率, 增大可减少写入放大)
3. dhara_map_resume(&s_map)
├─ 成功: 加载已有映射表
└─ 失败: dhara_map_clear 创建空映射表
4. 页面缓存初始化 (s_cache_buf, s_cached_lpn, s_cache_dirty)
```
### 7.5 页面缓存策略
FTL 之上还有一个 **单页写回缓存 (write-back cache)**
- **读命中**: 直接返回 s_cache_buf 数据
- **读未命中**: 刷出脏页 → 读新页到缓存
- **写**: 写入缓存 → 标记脏
- **全页写入**: 直接刷出 (跳过缓存)
- **同步 (CTRL_SYNC)**: 刷出脏页 + dhara_map_sync
---
## 8. FatFS 集成
### 8.1 配置 (ffconf.h)
```c
#define FF_FS_READONLY 0 // 读写模式
#define FF_USE_MKFS 1 // 启用格式化
#define FF_MIN_SS 512 // 最小扇区大小
#define FF_MAX_SS 512 // 最大扇区大小
#define FF_VOLUMES 1 // 单卷
#define FF_FS_TINY 0 // 非 tiny 模式
#define FF_FS_NORTC 1 // 无 RTC (固定时间戳)
```
### 8.2 disk I/O 接口
| 函数 | 功能 | 关键实现 |
|------|------|----------|
| `disk_initialize` | 初始化 FTL | 见 7.4 节 |
| `disk_status` | 查询状态 | 返回初始化状态 |
| `disk_read` | 读扇区 | 通过 FTL 映射读物理页 |
| `disk_write` | 写扇区 | 通过 FTL 映射写 (缓存优化) |
| `disk_ioctl` | 控制命令 | GET_SECTOR_COUNT/SIZE/BLOCK_SIZE + CTRL_SYNC |
### 8.3 容量计算
```
FTL 管理块数 = 1024 (Block 1024~2047)
每块页数 = 64
每页扇区数 (512B) = 4
总扇区数 = 1024 × 64 × 4 = 262144
总容量 = 262144 × 512 = 128MB (原始容量)
FTL 开销后 ≈ 93 MB (随 GC 和 journal 使用量波动)
```
---
## 9. 完整数据流
### 9.1 写文件流程
```
f_write("hello.txt")
└─ FatFS: 计算 LBA, 写扇区
└─ disk_write(0, data, sector=100, count=2)
├─ 计算 LPN = sector / 4 = 25
├─ 缓存未命中 → ftl_flush_cache() → ftl_read_page(25)
├─ 拷贝数据到 s_cache_buf → 标记脏
└─ 全页面写入 → ftl_flush_cache()
└─ dhara_map_write(&s_map, 25, s_cache_buf)
├─ 查找页映射 (或分配新页)
├─ dhara_nand_prog(pg, data) → 物理写
└─ 更新映射表
f_close → disk_ioctl(CTRL_SYNC)
└─ ftl_flush_cache() → dhara_map_sync()
└─ 写 journal 到 NAND (持久化映射表)
```
### 9.2 f_mkfs 格式化流程
```
f_mkfs("", &opts, work, size)
├─ disk_initialize(0) → FTL 初始化
├─ disk_write: 写入引导扇区 (MBR/PBR)
├─ disk_write: 写入 FAT 表
├─ disk_write: 创建根目录
└─ disk_ioctl(CTRL_SYNC) → FTL sync
```
---
## 10. API 参考
### 10.1 底层 NAND 驱动 (gd5f2gq5ue.h)
| 函数 | 说明 |
|------|------|
| `gd5f2gq5ue_init()` | 初始化 NAND (含 ECC 使能、块保护解除) |
| `gd5f2gq5ue_read_id(mid, did)` | 读芯片 ID |
| `gd5f2gq5ue_read(offset, buf, size)` | 读数据 (支持跨页) |
| `gd5f2gq5ue_write(offset, buf, size)` | 写数据 (支持跨页) |
| `gd5f2gq5ue_erase(offset, size)` | 块擦除 (需块对齐) |
| `gd5f2gq5ue_reset()` | 复位芯片 |
| `gd5f2gq5ue_is_block_bad(block)` | 查询坏块 |
| `gd5f2gq5ue_mark_block_bad(block)` | 标记坏块 |
### 10.2 FAL 接口 (via fal_flash_gd5f2gq5ue.c)
| 函数 | 说明 |
|------|------|
| `g_gd5f2gq5ue_flash` | FAL 设备实例 |
| `fal_flash_device_find("gd5f2gq5ue")` | 查找 Flash 设备 |
| `fal_partition_find("fdb_kvdb1")` | 查找分区 |
| `fal_partition_read/write/erase` | 分区读写擦除 |
### 10.3 FlashDB (via FlashDB 库)
| 函数 | 说明 |
|------|------|
| `fdb_kvdb_init(&db, "kvdb1", "fdb_kvdb1", NULL, NULL)` | 初始化 KVDB |
| `fdb_kvdb_set(&db, "key", "value")` | 写键值 (字符串) |
| `fdb_kvdb_get(&db, "key", &data)` | 读键值 |
| `fdb_kvdb_del(&db, "key")` | 删除键值 |
| `fdb_kvdb_set_blob(&db, "key", &blob)` | 写二进制 blob |
| `fdb_tsdb_init(&db, "tsdb1", "fdb_tsdb1", get_time, 1024, NULL)` | 初始化 TSDB |
| `fdb_tsl_append(&db, data)` | 追加时序记录 |
| `fdb_tsl_iter_by_time(&db, from, to, cb, cb_arg)` | 时间范围查询 |
### 10.4 FTL/FatFS (via nand_ftl.c / ff.h)
| 函数 | 说明 |
|------|------|
| `f_mount(&fs, "", 1)` | 挂载文件系统 (首次调用自动初始化 FTL) |
| `f_mkfs("", &opts, work, size)` | 格式化 FAT32 |
| `f_open/f_close/f_read/f_write` | 标准文件操作 |
| `f_unlink("test.txt")` | 删除文件 |
| `disk_initialize(0)` | 显式初始化 FTL (通常由 f_mount 自动调用) |
| `nand_ftl_format()` | 直接格式化 FTL (清空映射表, 慎用) |
---
## 11. 使用示例
### 11.1 KVDB 初始化与使用
```c
#include "flashdb.h"
#include "fdb_cfg.h"
/* KVDB 实例 */
static fdb_kvdb_t g_kvdb;
static int kvdb_init(void) {
fdb_err_t ret = fdb_kvdb_init(&g_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL);
return (ret == FDB_NO_ERR) ? 0 : -1;
}
static void kvdb_example(void) {
/* 写字符串 */
fdb_kvdb_set(&g_kvdb, "device_id", "STM32F407-001");
/* 读字符串 */
char buf[64];
fdb_kvdb_get(&g_kvdb, "device_id", buf, sizeof(buf));
/* 写二进制 blob */
struct fdb_blob blob;
uint32_t value = 42;
fdb_blob_make(&blob, &value, sizeof(value));
fdb_kvdb_set_blob(&g_kvdb, "counter", &blob);
}
```
### 11.2 TSDB 初始化与使用
```c
static fdb_tsdb_t g_tsdb;
static time_t get_timestamp(void) {
return sys_clock_now(); // 从 RTC 获取 Unix 时间戳
}
static int tsdb_init(void) {
fdb_err_t ret = fdb_tsdb_init(&g_tsdb, "tsdb1", "fdb_tsdb1",
get_timestamp, 1024, NULL);
return (ret == FDB_NO_ERR) ? 0 : -1;
}
typedef struct {
float voltage;
float current;
float temperature;
} sensor_data_t;
static void tsdb_example(void) {
sensor_data_t data = {3.3f, 0.5f, 25.6f};
fdb_tsl_append(&g_tsdb, &data);
}
```
### 11.3 FatFS 文件操作
```c
#include "ff.h"
static FATFS fs;
static int fatfs_init(void) {
FRESULT res = f_mount(&fs, "", 1);
if (res == FR_NO_FILESYSTEM) {
/* 首次使用需格式化 */
MKFS_PARM opts = {FM_FAT32, 0, 0, 0, 0};
uint8_t work[512];
res = f_mkfs("", &opts, work, sizeof(work));
if (res != FR_OK) return -1;
res = f_mount(&fs, "", 1);
}
return (res == FR_OK) ? 0 : -1;
}
static void fatfs_write_read(void) {
FIL fil;
UINT bw, br;
const char *msg = "Hello Storage!";
char buf[32];
/* 写文件 */
f_open(&fil, "data.txt", FA_CREATE_ALWAYS | FA_WRITE);
f_write(&fil, msg, strlen(msg), &bw);
f_close(&fil);
/* 读文件 */
f_open(&fil, "data.txt", FA_READ);
f_read(&fil, buf, sizeof(buf), &br);
buf[br] = '\0';
f_close(&fil);
}
```
### 11.4 FTL 格式化
```c
#include "nand_ftl.h"
/* 注意: 此操作将清空 FTL 分区全部数据, 慎用 */
if (nand_ftl_format() == 0) {
DBG_INFO("FTL formatted");
}
```
---
## 12. 注意事项
1. **擦除对齐**`gd5f2gq5ue_erase()` 的 offset 和 size 必须严格按 GD5F_BLOCK_SIZE (128KB) 对齐和整数倍。
2. **写前擦除** — NAND 不能原地覆写FTL 和 FlashDB 内部自动管理擦除,但直接调用 `gd5f2gq5ue_write()` 前必须确保目标块已擦除。
3. **FTL 首个扇区**`dhara_map_init` 的第 5 个参数 (journal 页数) 影响 GC 效率,当前为 4增大可减少写入放大但占用更多内存。
4. **坏块传播** — FTL 在擦除/编程失败后自动调用 `dhara_nand_mark_bad``gd5f2gq5ue_mark_block_bad`BBT 在 RAM 中更新,下次复位后重新扫描出厂坏块并叠加运行时坏块。
5. **功耗** — 擦除操作最大耗时约 5ms (驱动超时设为 5s),页编程约 600ms (超时 1s),读写操作快。在低功耗场景需注意合理安排操作时序。
6. **分区隔离** — 三个分区物理隔离FlashDB 操作不会影响 FTL 数据,反之亦然。修改分区布局时需同步更新 `fal_cfg.h` 和确认 FTL 宏自动适配。
7. **缓存一致性** — 单页缓存 (s_cache_buf) 仅对 FatFS 层可见,多任务读写同一文件需在应用层同步。