增加文件系统
This commit is contained in:
14
.opencode/command/build.md
Normal file
14
.opencode/command/build.md
Normal 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.
|
||||
41
AGENTS.md
41
AGENTS.md
@@ -1,5 +1,7 @@
|
||||
# STM32F4-Base
|
||||
|
||||
> **⚠️ 编译验证:所有代码修改后必须执行 `@build` 验证编译通过(0 错误 0 警告),否则不要提交。**
|
||||
|
||||
## 项目概述
|
||||
|
||||
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` 编译。
|
||||
|
||||
- 编译器:ARMCC V5.06 update 7
|
||||
- 优化等级:`-O4` (项目级),`spi.c`/`usart.c` / HAL 源文件使用 `-O0`
|
||||
- C 标准:C99
|
||||
- 全局宏定义:`USE_HAL_DRIVER, STM32F407xx`
|
||||
- 编译器:ARMCC V5.06 update 7,优化 `-O4`(`spi.c`/`usart.c`/HAL 源文件 `-O0`),C99,`USE_HAL_DRIVER, STM32F407xx`
|
||||
|
||||
### 命令行编译(Agent 使用)
|
||||
|
||||
修改代码后必须执行命令行编译验证,编译脚本位于 `MDK-ARM/build.bat`:
|
||||
修改代码后必须执行命令行编译验证,**仅使用** `@build`,不得直接调用 UV4.exe:
|
||||
|
||||
```
|
||||
cmd /c "cd /d "工作目录" && build.bat"
|
||||
@build
|
||||
```
|
||||
|
||||
实际执行示例(从项目根目录):
|
||||
该命令定义在 `.opencode/command/build.md`,自动执行 `MDK-ARM/build.bat` 进行全量编译(`-r`)。
|
||||
|
||||
```
|
||||
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`
|
||||
**退出码:** 0 = 成功(0 Error, 0 Warning)| 1 = 有警告(同样不通过)| 2+ = 错误,需查看 `MDK-ARM/build_log.txt` 定位
|
||||
|
||||
## 硬件配置
|
||||
|
||||
@@ -158,12 +146,17 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init()
|
||||
- 块擦除(D8h)参数是字节地址(块编号 × 128KB),不是块编号
|
||||
- 读取 ID(9Fh)返回 3 字节,第 0 字节无意义,第 1 字节 MID,第 2 字节 DID
|
||||
|
||||
## FlashDB 分区规划
|
||||
## Flash 分区规划(256MB GD5F2GQ5UE)
|
||||
|
||||
| 分区名 | 偏移 | 大小 | 用途 |
|
||||
|--------|------|------|------|
|
||||
| fdb_kvdb1 | 0 | 64MB | KVDB 键值数据库 |
|
||||
| fdb_tsdb1 | 64MB | 64MB | TSDB 时序数据库 |
|
||||
| 分区名 | 偏移 | 大小 | 块范围 | 用途 |
|
||||
|--------|------|------|--------|------|
|
||||
| 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 文件系统 |
|
||||
|
||||
- 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 驱动关键点
|
||||
|
||||
|
||||
@@ -22,12 +22,6 @@ extern "C" {
|
||||
*/
|
||||
#define APP_SYS_TIME_SRC 0
|
||||
|
||||
/*
|
||||
* 时间戳输出使能
|
||||
* 定义后在日志行首自动追加 [T ssssss.uuuuuu] 微秒时间戳
|
||||
*/
|
||||
#define APP_TIMESTAMP_ENABLE
|
||||
|
||||
/*
|
||||
* 功能模块使能开关
|
||||
*/
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include "app_main.h"
|
||||
#include "app_cfg.h"
|
||||
#include "sys_time.h"
|
||||
|
||||
#define DBG_TAG "[APP]"
|
||||
#include "dbg_log.h"
|
||||
@@ -24,10 +23,6 @@
|
||||
*/
|
||||
int app_main_init(void)
|
||||
{
|
||||
if (sys_time_init() != 0) {
|
||||
DBG_ERROR("sys_time_init failed");
|
||||
return -1;
|
||||
}
|
||||
DBG_INFO("App init OK");
|
||||
return 0;
|
||||
}
|
||||
|
||||
199
App/sys_clock.c
Normal file
199
App/sys_clock.c
Normal 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 congruence,0=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
65
App/sys_clock.h
Normal 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 */
|
||||
107
App/sys_time.c
107
App/sys_time.c
@@ -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;
|
||||
}
|
||||
@@ -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 */
|
||||
@@ -46,10 +46,11 @@ void net_task_func(void const *arg)
|
||||
(void)arg;
|
||||
|
||||
osDelay(2000);
|
||||
DBG_INFO("netTask: started");
|
||||
DBG_INFO("netTask: started, processing messages");
|
||||
|
||||
for (;;) {
|
||||
net_poll();
|
||||
net_process_messages();
|
||||
|
||||
#ifdef ENABLE_PHASE4_TESTS
|
||||
for (int i = 1; i < 8; i++) {
|
||||
@@ -58,9 +59,9 @@ void net_task_func(void const *arg)
|
||||
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) {
|
||||
net_send(i, s_net_buf, n, 0);
|
||||
net_send_sock(p_sk, s_net_buf, n);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -2,8 +2,8 @@
|
||||
* 模块名称:CH395F Driver Test Suite
|
||||
* 模块功能:CH395F 驱动各功能模块的测试入口,分阶段组织测试用例
|
||||
* 适用平台:STM32F4 系列(CH395F 以太网芯片)
|
||||
* 作者:王建� * 创建日期�026-07-19
|
||||
* 修改记录� * 2026-07-19 Phase 1/2/3 implemented
|
||||
* 作者:王建<EFBFBD>? * 创建日期<E697A5>?026-07-19
|
||||
* 修改记录<EFBFBD>? * 2026-07-19 Phase 1/2/3 implemented
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -18,7 +18,7 @@
|
||||
test_stats_t g_test_stats;
|
||||
|
||||
/*
|
||||
* Phase 2 é…<EFBFBD>置默认ï¿? */
|
||||
* Phase 2 配置默认<EFBFBD>? */
|
||||
#ifndef PHASE2_REMOTE_IP
|
||||
#define PHASE2_REMOTE_IP "192.168.1.2"
|
||||
#endif
|
||||
@@ -36,7 +36,7 @@ test_stats_t g_test_stats;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Phase 3 é…<EFBFBD>置默认ï¿? */
|
||||
* Phase 3 配置默认<EFBFBD>? */
|
||||
#ifndef PHASE3_REMOTE_IP
|
||||
#define PHASE3_REMOTE_IP "192.168.1.2"
|
||||
#endif
|
||||
@@ -54,9 +54,9 @@ test_stats_t g_test_stats;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 函数功能:æ<EFBFBD>¢ï¿?TCP Server 多连接模å¼<C3A5>的数æ<C2B0>® Socket é…<EFBFBD>ç½®
|
||||
* 函数功能:恢<EFBFBD>?TCP Server 多连接模式的数据 Socket 配置
|
||||
* 入口参数: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) {
|
||||
int i = 0;
|
||||
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 ============================
|
||||
* 函数功能:寄存器读写 + 中断一致性基础测试
|
||||
* 入口参数:无
|
||||
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šCH395F 芯片æ£å¸¸åº”ç”
|
||||
* 函数说明:包�GET_VERSION / GET_CMD_STATUS / GET_IP_INF �9 � */
|
||||
* <EFBFBD>?<3F>?值:<E580BC>? * 限定条件:CH395F 芯片正常应答
|
||||
* 函数说明:包<EFBFBD>?GET_VERSION / GET_CMD_STATUS / GET_IP_INF <EFBFBD>?9 <EFBFBD>? */
|
||||
|
||||
void ch395f_phase1_tests(void) {
|
||||
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_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();
|
||||
TEST_CHECK(gint_1b == 0x00,
|
||||
"GINT_STATUS_1byte: 0x%02X (expect 0x00 after read-clear)", gint_1b);
|
||||
@@ -146,8 +146,8 @@ void ch395f_phase1_tests(void) {
|
||||
|
||||
/*
|
||||
* ============================ Phase 2 ============================
|
||||
* 函数功能:TCP Client æ”¶å<C2B6>‘ + å…³é—é‡<C3A9>连 ×3 ï¿? * å…¥å<C2A5>£å<C2A3>‚数:æ—
|
||||
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šPC 端已å<C2B2>¯åЍ TCP Server(默ï¿?192.168.1.2:8081ï¿? * 函数说明:æ¯<EFBFBD>轮连接→å<EFBFBD>‘é€<EFBFBD>→接收→关é—ï¼Œè¿žç» 3 ï¿? */
|
||||
* 函数功能:TCP Client 收发 + 关闭重连 ×3 <20>? * 入口参数:无
|
||||
* <EFBFBD>?<3F>?值:<E580BC>? * 限定条件:PC 端已启动 TCP Server(默<EFBFBD>?192.168.1.2:8081<EFBFBD>? * 函数说明:每轮连接→发送→接收→关闭,连续 3 <EFBFBD>? */
|
||||
|
||||
void ch395f_phase2_tests(void) {
|
||||
uint8_t sock = 0;
|
||||
@@ -174,7 +174,7 @@ void ch395f_phase2_tests(void) {
|
||||
|
||||
sock = (uint8_t)(NET_MAX_SOCKETS - 1);
|
||||
|
||||
/* 一次性é…<EFBFBD>ï¿?*/
|
||||
/* 一次性配<EFBFBD>?*/
|
||||
ch395f_set_proto_type(sock, CH395F_PROTO_TYPE_TCP);
|
||||
ch395f_set_send_buf(sock, 28, 2);
|
||||
ch395f_set_recv_buf(sock, 30, 2);
|
||||
@@ -204,7 +204,7 @@ void ch395f_phase2_tests(void) {
|
||||
|
||||
HAL_Delay(100);
|
||||
|
||||
/* å<EFBFBD>‘é€<EFBFBD>测试数ï¿?*/
|
||||
/* 发送测试数<EFBFBD>?*/
|
||||
{
|
||||
uint8_t test_buf[64] = {0};
|
||||
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);
|
||||
}
|
||||
|
||||
/* Echo 循环:收数æ<EFBFBD>® ï¿?回å<C5BE>‘ */
|
||||
/* Echo 循环:收数据 <20>?回发 */
|
||||
{
|
||||
int got_echo = 0;
|
||||
tick = HAL_GetTick();
|
||||
@@ -266,7 +266,7 @@ phase2_iter_done:
|
||||
* ============================ Phase 3 ============================
|
||||
* 函数功能: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) {
|
||||
uint8_t sock = 0;
|
||||
@@ -279,7 +279,7 @@ void ch395f_phase3_tests(void) {
|
||||
|
||||
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_send_buf(sock, 28, 2);
|
||||
ch395f_set_recv_buf(sock, 30, 2);
|
||||
@@ -295,7 +295,7 @@ void ch395f_phase3_tests(void) {
|
||||
|
||||
HAL_Delay(200);
|
||||
|
||||
/* Echo 循环:收ï¿?ï¿?è§£æž<C3A6>æ<EFBFBD>¥æº<C3A6> ï¿?回显 */
|
||||
/* Echo 循环:收<EFBFBD>?<3F>?解析来源 <20>?回显 */
|
||||
for (i = 0; i < PHASE3_ECHO_COUNT; i++) {
|
||||
tick = HAL_GetTick();
|
||||
echo_ok = 0;
|
||||
@@ -348,7 +348,7 @@ void ch395f_phase3_tests(void) {
|
||||
|
||||
/*
|
||||
* ============================ Phase 4 ============================
|
||||
* NET ï¿?TCP Echo:在主循环ä¸è°ƒç”¨ï¼Œç”¨ net_recv + net_send 回显
|
||||
* NET <EFBFBD>?TCP Echo:在主循环中调用,用 net_recv + net_send 回显
|
||||
*/
|
||||
|
||||
#ifdef ENABLE_PHASE4_TESTS
|
||||
@@ -356,9 +356,9 @@ static char s_phase4_buf[2048];
|
||||
#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) {
|
||||
#ifdef ENABLE_PHASE4_TESTS
|
||||
@@ -368,9 +368,9 @@ void ch395f_phase4_tests(void) {
|
||||
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) {
|
||||
net_send(i, s_phase4_buf, n, 0);
|
||||
net_send_sock(sk, s_phase4_buf, n);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -378,7 +378,7 @@ void ch395f_phase4_tests(void) {
|
||||
|
||||
/*
|
||||
* ============================ Phase 5 ============================
|
||||
* NET ï¿?UDP Echo:netTask ä¸è¿<EFBFBD>行,ï¿?net_recvfrom + net_sendto 回显
|
||||
* NET <EFBFBD>?UDP Echo:netTask 中运行,<EFBFBD>?net_recvfrom + net_sendto 回显
|
||||
*/
|
||||
|
||||
#ifndef PHASE5_LOCAL_PORT
|
||||
@@ -392,8 +392,8 @@ static uint32_t s_phase5_count = 0;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 函数功能:NET ï¿?UDP Echo åˆ<EFBFBD>å§‹ï¿? * å…¥å<C2A5>£å<C2A3>‚数:æ—
|
||||
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šnet_init 已完ï¿? * 函数说明:创ï¿?UDP Socket 并绑定本地端ï¿? */
|
||||
* 函数功能:NET <EFBFBD>?UDP Echo 初始<EFBFBD>? * 入口参数:无
|
||||
* <EFBFBD>?<3F>?值:<E580BC>? * 限定条件:net_init 已完<E5B7B2>? * 函数说明:创<EFBC9A>?UDP Socket 并绑定本地端<E59CB0>? */
|
||||
void ch395f_phase5_init(void) {
|
||||
#ifdef ENABLE_PHASE5_TESTS
|
||||
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) {
|
||||
DBG_ERROR("Phase5: net_bind failed");
|
||||
net_close(s_phase5_sock);
|
||||
net_close_sock(net_get_sock(s_phase5_sock));
|
||||
s_phase5_sock = -1;
|
||||
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) {
|
||||
#ifdef ENABLE_PHASE5_TESTS
|
||||
@@ -445,7 +445,7 @@ void ch395f_phase5_tests(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* Phase 6 é…<EFBFBD>置默认ï¿? */
|
||||
* Phase 6 配置默认<EFBFBD>? */
|
||||
#ifndef PHASE6_ANNOUNCE_PORT
|
||||
#define PHASE6_ANNOUNCE_PORT 60001
|
||||
#endif
|
||||
@@ -457,8 +457,8 @@ void ch395f_phase5_tests(void) {
|
||||
* ============================ Phase 6 ============================
|
||||
* 函数功能:DHCP 自动获取 IP 测试
|
||||
* 入口参数:无
|
||||
* ï¿?ï¿?值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šå±€åŸŸç½‘内å˜ï¿?DHCP Server
|
||||
* 函数说明:å<EFBFBD>¯ï¿?DHCP ï¿?轮询ç‰å¾… IP ï¿?打å<E2809C>°ç»“æžœ ï¿?广æ’宣告 ï¿?HELLO 回显
|
||||
* <EFBFBD>?<3F>?值:<E580BC>? * 限定条件:局域网内存<E58685>?DHCP Server
|
||||
* 函数说明:启<EFBFBD>?DHCP <20>?轮询等待 IP <20>?打印结果 <20>?广播宣告 <20>?HELLO 回显
|
||||
*/
|
||||
|
||||
void ch395f_phase6_tests(void) {
|
||||
@@ -498,7 +498,7 @@ void ch395f_phase6_tests(void) {
|
||||
goto phase6_end;
|
||||
}
|
||||
|
||||
/* 3. 读å<EFBFBD>–获å<EFBFBD>–ï¿?IP */
|
||||
/* 3. 读取获取<EFBFBD>?IP */
|
||||
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(" 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 rx_buf[64] = {0};
|
||||
|
||||
/* UDP Server 模å¼<EFBFBD>:本地端ï¿?60000(接ï¿?HELLO),DesIP=0xFFFFFFFF */
|
||||
/* UDP Server 模式:本地端<EFBFBD>?60000(接<EFBC88>?HELLO),DesIP=0xFFFFFFFF */
|
||||
ch395f_set_proto_type(anno_sock, CH395F_PROTO_TYPE_UDP);
|
||||
ch395f_set_send_buf(anno_sock, 24, 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);
|
||||
|
||||
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_port(anno_sock, PHASE6_ANNOUNCE_PORT);
|
||||
ch395f_write_send_buf(anno_sock, ip_info, 4);
|
||||
@@ -546,14 +546,14 @@ void ch395f_phase6_tests(void) {
|
||||
PHASE6_HELLO_PORT, PHASE6_ANNOUNCE_PORT);
|
||||
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_port(anno_sock, 0);
|
||||
|
||||
uint32_t wait = HAL_GetTick();
|
||||
uint8_t hello_ok = 0;
|
||||
while (HAL_GetTick() - wait < 5000) {
|
||||
/* 读å<EFBFBD>–免局䏿–(刷ï¿?socket 䏿–状æ€<C3A6>) */
|
||||
/* 读取全局中断(刷<EFBFBD>?socket 中断状态) */
|
||||
ch395f_get_glob_int_status_all();
|
||||
uint16_t rlen = ch395f_get_recv_len(anno_sock);
|
||||
if (rlen > 8) {
|
||||
@@ -593,7 +593,7 @@ phase6_end:
|
||||
|
||||
/*
|
||||
* ============================ 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"
|
||||
@@ -605,7 +605,7 @@ static char s_phase7_buf[2048];
|
||||
/*
|
||||
* 函数功能: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) {
|
||||
#ifdef ENABLE_PHASE7_TESTS
|
||||
@@ -630,11 +630,12 @@ void ch395f_phase7_tests(void) {
|
||||
if (n <= 0) return;
|
||||
|
||||
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) {
|
||||
net_send(i, s_phase7_buf, len, 0);
|
||||
net_send_sock(sk, s_phase7_buf, len);
|
||||
s_phase7_count++;
|
||||
TEST_CHECK(1, "Phase7: select echo #%lu (sock=%d, %d bytes)", s_phase7_count, i, len);
|
||||
}
|
||||
|
||||
@@ -3,22 +3,40 @@
|
||||
|
||||
/*
|
||||
* 模块名称:FAL 配置
|
||||
* 模å<EFBFBD>—功能:定ä¹?FAL Flash 设备表和分区è¡? * 适用平å<C2B3>°ï¼šSTM32F407ZGT6
|
||||
* 作者:王建� * 创建日期�026-07-16
|
||||
* 修改记录ï¼? * 2026-07-16 王建é”? 创建åˆ<C3A5>始版本
|
||||
* 模块功能:定义 Flash 物理分区布局和 FAL 设备/分区表
|
||||
* 适用平台:STM32F407ZGT6
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-07-16
|
||||
* 修改记录:
|
||||
* 2026-07-20 王建锋 统一分区配置,添加 FTL 分区宏供 nand_ftl.c 使用
|
||||
*/
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#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
|
||||
|
||||
/* å<EFBFBD>¯ç”¨åˆ†åŒºè¡¨é…<EFBFBD>ç½?*/
|
||||
/* 启用分区表配置(FAL 库要求此宏) */
|
||||
#define FAL_PART_HAS_TABLE_CFG
|
||||
|
||||
/* ======================== Flash 设备�======================== */
|
||||
/* ======================== Flash 设备表 ======================== */
|
||||
|
||||
extern const struct fal_flash_dev g_gd5f2gq5ue_flash;
|
||||
|
||||
@@ -27,19 +45,15 @@ extern const struct fal_flash_dev g_gd5f2gq5ue_flash;
|
||||
&g_gd5f2gq5ue_flash, \
|
||||
}
|
||||
|
||||
/* ======================== 分区�======================== */
|
||||
/*
|
||||
* 分区å<C2BA><C3A5>ç§° 设备å<E280A1><C3A5>ç§° å<><C3A5>ç§» 大å°<C3A5>
|
||||
* KVDB: 64MB @ 0
|
||||
* TSDB: 64MB @ 64MB
|
||||
*/
|
||||
/* ======================== 分区表 ======================== */
|
||||
|
||||
#ifdef FAL_PART_HAS_TABLE_CFG
|
||||
#define FAL_PART_TABLE \
|
||||
{ \
|
||||
{FAL_PART_MAGIC_WORD, "fdb_kvdb1", "gd5f2gq5ue", 0, 64*1024*1024, 0}, \
|
||||
{FAL_PART_MAGIC_WORD, "fdb_tsdb1", "gd5f2gq5ue", 64*1024*1024, 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", FDB_TSDB1_OFFSET, FDB_TSDB1_SIZE, 0}, \
|
||||
}
|
||||
#endif
|
||||
#endif /* FAL_PART_HAS_TABLE_CFG */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
/*
|
||||
* 模块名称:GD5F2GQ5UE SPI NAND Flash 驱动
|
||||
* 妯″潡鍔熻兘锛氭彁锟?GD5F2GQ5UE SPI NAND Flash 鐨勫垵濮嬪寲銆佽<EFBFBD>銆佸啓銆佹摝闄ゆ帴锟? * 閫傜敤骞冲彴锛歋TM32F407ZGT6 + SPI1 纭<EFBFBD>欢 SPI
|
||||
* 浣滆€咃細鐜嬪缓锟? * 鍒涘缓鏃ユ湡锟?026-07-16
|
||||
* 淇<EFBFBD>敼璁板綍锟? * 2026-07-16 鐜嬪缓锟? 鍒涘缓鍒濆<E98D92>鐗堟湰
|
||||
* 2026-07-17 鐜嬪缓锟? 鍒囨崲纭<E5B4B2>欢 SPI锛屼慨姝f摝闄ゅ湴鍧€锛屽弬锟?NuttX 椹卞姩
|
||||
* 模块功能:提<EFBFBD>?GD5F2GQ5UE SPI NAND Flash 的初始化、读、写、擦除接<EFBFBD>? * 适用平台:STM32F407ZGT6 + SPI1 硬件 SPI
|
||||
* 作者:王建<EFBFBD>? * 创建日期<E697A5>?026-07-16
|
||||
* 修改记录<EFBFBD>? * 2026-07-16 王建<EFBFBD>? 创建初始版本
|
||||
* 2026-07-17 王建<EFBFBD>? 切换硬件 SPI,修正擦除地址,参<EFBC8C>?NuttX 驱动
|
||||
*/
|
||||
|
||||
/* 头文件包含区 */
|
||||
@@ -18,33 +18,57 @@
|
||||
|
||||
/* 私有宏定义区 */
|
||||
#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 句柄声明 */
|
||||
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,
|
||||
const uint8_t *p_buf, size_t size);
|
||||
static int gd5f_set_feature(uint8_t addr, uint8_t data);
|
||||
static int gd5f_block_erase(uint32_t block_addr);
|
||||
|
||||
/* ======================== 绉佹湁鍑芥暟瀹氫箟 ======================== */
|
||||
/* ======================== SPI 原语定义 ======================== */
|
||||
|
||||
/*
|
||||
* 函数功能:等待芯片操作完成(轮询 OIP 位)
|
||||
* 入口参数:timeout_ms - 超时时间 uint32_t > 0
|
||||
* 杩斿洖鍊硷細0 - 鎿嶄綔瀹屾垚锟?2 - 瓒呮椂
|
||||
* 返回值:0 - 操作完成,-2 - 超时
|
||||
* 限定条件:SPI 已初始化
|
||||
* 函数说明:循环读取状态寄存器直到 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 addr = GD5F_REG_STATUS;
|
||||
uint8_t status = 0;
|
||||
@@ -71,8 +95,8 @@ static int gd5f_wait_busy(uint32_t timeout_ms) {
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 成功
|
||||
* 限定条件:SPI 已初始化
|
||||
* 鍑芥暟璇存槑锛欳S# 鎷変綆鍚庡彂锟?06h 鍛戒护鍐嶆媺锟? */
|
||||
static int gd5f_write_enable(void) {
|
||||
* 函数说明:CS# 拉低后发<E5908E>?06h 命令再拉<E5868D>? */
|
||||
int gd5f_write_enable(void) {
|
||||
uint8_t cmd = GD5F_CMD_WRITE_ENABLE;
|
||||
|
||||
GD5F_CS_LOW();
|
||||
@@ -84,11 +108,11 @@ static int gd5f_write_enable(void) {
|
||||
|
||||
/*
|
||||
* 函数功能:读取状态寄存器
|
||||
* 鍏ュ彛鍙傛暟锛歱_status - 鐘舵€佸€艰緭鍑烘寚锟? uint8_t* 涓嶄负 NULL
|
||||
* 入口参数:p_status - 状态值输出指<EFBFBD>? uint8_t* 不为 NULL
|
||||
* 返回值:0 - 成功
|
||||
* 限定条件:SPI 已初始化
|
||||
* 鍑芥暟璇存槑锛氬彂锟?0Fh + C0h 鍦板潃鍚庤<EFBFBD>锟?瀛楄妭鐘舵€侊拷? */
|
||||
static int gd5f_read_status(uint8_t *p_status) {
|
||||
* 函数说明:发<EFBFBD>?0Fh + C0h 地址后读<EFBFBD>?字节状态<E78AB6>? */
|
||||
int gd5f_read_status(uint8_t *p_status) {
|
||||
uint8_t cmd = GD5F_CMD_GET_FEATURE;
|
||||
uint8_t addr = GD5F_REG_STATUS;
|
||||
|
||||
@@ -102,11 +126,11 @@ static int gd5f_read_status(uint8_t *p_status) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 鍑芥暟鍔熻兘锛氶〉璇诲彇锛堝皢鏁版嵁浠庡瓨鍌ㄩ樀鍒楀姞杞藉埌鍐呴儴缂撳瓨锟? * 鍏ュ彛鍙傛暟锛歱age_addr - 椤靛湴鍧€ uint32_t
|
||||
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉′欢锛歋PI 宸插垵濮嬪寲
|
||||
* 鍑芥暟璇存槑锛氬彂锟?13h + 3瀛楄妭琛屽湴鍧€锛岀瓑锟?OIP 娓呴浂
|
||||
* 函数功能:页读取(将数据从存储阵列加载到内部缓存<EFBFBD>? * 入口参数:page_addr - 页地址 uint32_t
|
||||
* 返回值:0 - 成功,其<EFBC8C>?- 错误<E99499>? * 限定条件:SPI 已初始化
|
||||
* 函数说明:发<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};
|
||||
|
||||
cmd[0] = GD5F_CMD_PAGE_READ;
|
||||
@@ -124,12 +148,12 @@ static int gd5f_page_read(uint32_t page_addr) {
|
||||
/*
|
||||
* 函数功能:从内部缓存读取数据
|
||||
* 入口参数:column - 列地址(页内偏移) uint16_t 0 - 2047
|
||||
* p_buf - 鏁版嵁杈撳嚭缂撳啿锟? uint8_t* 涓嶄负 NULL
|
||||
* size - 璇诲彇瀛楄妭锟? size_t > 0
|
||||
* p_buf - 数据输出缓冲<EFBFBD>? uint8_t* 不为 NULL
|
||||
* size - 读取字节<EFBFBD>? size_t > 0
|
||||
* 返回值:0 - 成功
|
||||
* 限定条件:必须先调用 gd5f_page_read 完成数据加载
|
||||
* 鍑芥暟璇存槑锛氬彂锟?0Bh + 2瀛楄妭鍒楀湴鍧€ +1瀛楄妭 dummy 鍚庤<E98D9A>鍙栨暟锟? */
|
||||
static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
|
||||
* 函数说明:发<EFBFBD>?0Bh + 2字节列地址 +1字节 dummy 后读取数<E58F96>? */
|
||||
int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
|
||||
size_t size) {
|
||||
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();
|
||||
HAL_SPI_Transmit(&hspi1, cmd, 4, 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();
|
||||
|
||||
return GD5F_OK;
|
||||
@@ -150,28 +191,56 @@ static int gd5f_read_from_cache(uint16_t column, uint8_t *p_buf,
|
||||
* 函数功能:页编程(将数据写入指定页)
|
||||
* 入口参数:page_addr - 页地址 uint32_t
|
||||
* column - 列地址 uint16_t 0 - 2047
|
||||
* p_buf - 鏁版嵁缂撳啿锟? const uint8_t*
|
||||
* size - 鍐欏叆瀛楄妭锟? size_t > 0
|
||||
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉′欢锛氱洰鏍囧尯鍩熷凡鎿﹂櫎
|
||||
* 鍑芥暟璇存槑锟?. 鍐欎娇锟?- 02h 鍔犺浇鏁版嵁 - 10h 鎵ц<EFBFBD>缂栫▼ - 绛夊緟瀹屾垚
|
||||
* 2. 缂栫▼瀹屾垚鍚庢<EFBFBD>锟?P_FAIL 锟? */
|
||||
* p_buf - 数据缓冲<EFBFBD>? const uint8_t*
|
||||
* size - 写入字节<EFBFBD>? size_t > 0
|
||||
* 返回值:0 - 成功,其<EFBC8C>?- 错误<E99499>? * 限定条件:目标区域已擦除
|
||||
* 函数说明<EFBFBD>?. 写使<E58699>?- 02h 加载数据 - 10h 执行编程 - 等待完成
|
||||
* 2. 编程完成后检<EFBFBD>?P_FAIL <EFBFBD>? */
|
||||
static int gd5f_page_program(uint32_t page_addr, uint16_t column,
|
||||
const uint8_t *p_buf, size_t size) {
|
||||
int ret = GD5F_OK;
|
||||
uint8_t cmd[4];
|
||||
uint8_t status = 0;
|
||||
|
||||
int ret;
|
||||
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[1] = (column >> 8) & 0xFF;
|
||||
cmd[2] = column & 0xFF;
|
||||
cmd[1] = (uint8_t)(column >> 8);
|
||||
cmd[2] = (uint8_t)(column);
|
||||
|
||||
GD5F_CS_LOW();
|
||||
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();
|
||||
|
||||
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[1] = (page_addr >> 16) & 0xFF;
|
||||
@@ -183,24 +252,28 @@ static int gd5f_page_program(uint32_t page_addr, uint16_t column,
|
||||
GD5F_CS_HIGH();
|
||||
|
||||
ret = gd5f_wait_busy(1000);
|
||||
if (ret != GD5F_OK) {
|
||||
return ret;
|
||||
}
|
||||
if (ret != GD5F_OK) return ret;
|
||||
|
||||
gd5f_read_status(&status);
|
||||
if (status & GD5F_STATUS_P_FAIL) {
|
||||
return GD5F_PROGRAM_FAIL;
|
||||
}
|
||||
if (status & GD5F_STATUS_P_FAIL) return GD5F_PROGRAM_FAIL;
|
||||
|
||||
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
|
||||
* 返回值:0 - 成功
|
||||
* 限定条件:SPI 已初始化
|
||||
* 鍑芥暟璇存槑锛氬厛鍐欎娇鑳斤紝鍙戯拷?1Fh + 鍦板潃 + 鏁版嵁锛岀瓑寰呮搷浣滃畬锟? */
|
||||
* 函数说明:先写使能,发<EFBFBD>?1Fh + 地址 + 数据,等待操作完<E4BD9C>? */
|
||||
static int gd5f_set_feature(uint8_t addr, uint8_t data) {
|
||||
uint8_t cmd[3];
|
||||
|
||||
@@ -220,11 +293,11 @@ static int gd5f_set_feature(uint8_t addr, uint8_t data) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 鍑芥暟鍔熻兘锛氬潡鎿﹂櫎锛堟摝锟?28KB鍧楋級
|
||||
* 鍏ュ彛鍙傛暟锛歜lock_addr - 鍧楃紪锟? uint32_t 0 - 2047
|
||||
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉′欢锛歡d5f2gq5ue_init() 宸茶皟锟? * 鍑芥暟璇存槑锟?. 鍐欎娇锟?- D8h + 3瀛楄妭瀛楄妭鍦板潃 - 绛夊緟瀹屾垚
|
||||
* 2. 鎿﹂櫎瀹屾垚鍚庢<EFBFBD>锟?E_FAIL 锟? */
|
||||
static int gd5f_block_erase(uint32_t block_addr) {
|
||||
* 函数功能:块擦除(擦<EFBFBD>?28KB块)
|
||||
* 入口参数:block_addr - 块编<EFBFBD>? uint32_t 0 - 2047
|
||||
* 返回值:0 - 成功,其<EFBC8C>?- 错误<E99499>? * 限定条件:gd5f2gq5ue_init() 已调<E5B7B2>? * 函数说明<E8AFB4>?. 写使<E58699>?- D8h + 3字节字节地址 - 等待完成
|
||||
* 2. 擦除完成后检<EFBFBD>?E_FAIL <EFBFBD>? */
|
||||
int gd5f_block_erase(uint32_t block_addr) {
|
||||
int ret = GD5F_OK;
|
||||
uint8_t cmd[4];
|
||||
uint8_t status = 0;
|
||||
@@ -254,14 +327,49 @@ static int gd5f_block_erase(uint32_t block_addr) {
|
||||
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
|
||||
* 入口参数:无
|
||||
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉′欢锛歋PI1 鍜岀浉锟?GPIO 宸茬敱 CubeMX 鍒濆<E98D92>鍖栧畬锟? * 鍑芥暟璇存槑锟?. 鍙戦€佸<E282AC>浣嶅懡浠ゅ苟绛夊緟瀹屾垚
|
||||
* 2. 璇诲彇鑺<E5BD87>墖 ID 骞舵牎锟? * 3. 浣胯兘鍐呴儴 ECC (B0h bit4)
|
||||
* 4. 瑙i櫎鎵€鏈夊潡淇濇姢 (A0h = 0x00)
|
||||
* 返回值:0 - 成功,其他 - 错误码
|
||||
* 限定条件:SPI1 和相关 GPIO 已由 CubeMX 初始化完成
|
||||
* 函数说明:1. 发送复位命令并等待完成
|
||||
* 2. 读取芯片 ID 并校验
|
||||
* 3. 扫描出厂坏块构建 BBT(ECC 使能前)
|
||||
* 4. 使能内部 ECC (B0h bit4)
|
||||
* 5. 解除所有块保护 (A0h = 0x00)
|
||||
*/
|
||||
int gd5f2gq5ue_init(void) {
|
||||
int ret = GD5F_OK;
|
||||
@@ -298,6 +406,9 @@ int gd5f2gq5ue_init(void) {
|
||||
return GD5F_ID_MISMATCH;
|
||||
}
|
||||
|
||||
DBG_INFO("Scanning bad blocks...");
|
||||
gd5f_bbt_scan();
|
||||
|
||||
DBG_INFO("Enabling ECC...");
|
||||
gd5f_set_feature(0xB0, 0x10);
|
||||
gd5f_wait_busy(100);
|
||||
@@ -311,11 +422,11 @@ int gd5f2gq5ue_init(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 鍑芥暟鍔熻兘锛氳<EFBFBD>鍙栬姱锟?ID锛圡ID + DID锟? * 鍏ュ彛鍙傛暟锛歮id - 鍒堕€犲晢 ID 杈撳嚭鎸囬拡 uint8_t* 涓嶄负 NULL
|
||||
* 函数功能:读取芯<EFBFBD>?ID(MID + DID<EFBFBD>? * 入口参数:mid - 制造商 ID 输出指针 uint8_t* 不为 NULL
|
||||
* did - 设备 ID 输出指针 uint8_t* 不为 NULL
|
||||
* 返回值:0 - 成功
|
||||
* 限定条件:SPI 已初始化
|
||||
* 鍑芥暟璇存槑锛氬彂锟?9Fh 鍛戒护鍚庢帴锟?锟?dummy + MID + DID
|
||||
* 函数说明:发<EFBFBD>?9Fh 命令后接<E5908E>?<3F>?dummy + MID + DID
|
||||
*/
|
||||
int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) {
|
||||
uint8_t cmd = GD5F_CMD_READ_ID;
|
||||
@@ -334,10 +445,10 @@ int gd5f2gq5ue_read_id(uint8_t *p_mid, uint8_t *p_did) {
|
||||
|
||||
/*
|
||||
* 函数功能:从 NAND 读取数据(支持跨页)
|
||||
* 鍏ュ彛鍙傛暟锛歰ffset - 璧峰<E792A7>瀛楄妭鍋忕Щ long 0 ~ 鎬诲<EFBFBD>锟?1
|
||||
* p_buf - 鏁版嵁缂撳啿锟? uint8_t* 涓嶄负 NULL
|
||||
* size - 璇诲彇瀛楄妭锟? size_t > 0
|
||||
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉′欢锛歡d5f2gq5ue_init() 宸叉垚鍔熻皟锟? * 鍑芥暟璇存槑锛氳嚜鍔ㄥ<E98D94>鐞嗚法椤佃<E6A4A4>锟? */
|
||||
* 入口参数:offset - 起始字节偏移 long 0 ~ 总容<EFBFBD>?1
|
||||
* p_buf - 数据缓冲<EFBFBD>? uint8_t* 不为 NULL
|
||||
* size - 读取字节<EFBFBD>? size_t > 0
|
||||
* 返回值:0 - 成功,其<EFBC8C>?- 错误<E99499>? * 限定条件:gd5f2gq5ue_init() 已成功调<E58A9F>? * 函数说明:自动处理跨页读<E9A1B5>? */
|
||||
int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) {
|
||||
int ret = GD5F_OK;
|
||||
|
||||
@@ -370,10 +481,10 @@ int gd5f2gq5ue_read(long offset, uint8_t *p_buf, size_t size) {
|
||||
|
||||
/*
|
||||
* 函数功能:向 NAND 写入数据(支持跨页)
|
||||
* 鍏ュ彛鍙傛暟锛歰ffset - 璧峰<E792A7>瀛楄妭鍋忕Щ long 0 ~ 鎬诲<EFBFBD>锟?1
|
||||
* p_buf - 鏁版嵁缂撳啿锟? const uint8_t* 涓嶄负 NULL
|
||||
* size - 鍐欏叆瀛楄妭锟? size_t > 0
|
||||
* 杩斿洖鍊硷細0 - 鎴愬姛锛屽叾锟?- 閿欒<E996BF>锟? * 闄愬畾鏉′欢锛歡d5f2gq5ue_init() 宸叉垚鍔熻皟鐢<E79A9F>紝鐩<E7B49D>爣鍖哄煙宸叉摝锟? * 鍑芥暟璇存槑锛氳嚜鍔ㄥ<E98D94>鐞嗚法椤靛啓锟? */
|
||||
* 入口参数:offset - 起始字节偏移 long 0 ~ 总容<EFBFBD>?1
|
||||
* p_buf - 数据缓冲<EFBFBD>? const uint8_t* 不为 NULL
|
||||
* size - 写入字节<EFBFBD>? size_t > 0
|
||||
* 返回值:0 - 成功,其<EFBC8C>?- 错误<E99499>? * 限定条件:gd5f2gq5ue_init() 已成功调用,目标区域已擦<E5B7B2>? * 函数说明:自动处理跨页写<E9A1B5>? */
|
||||
int gd5f2gq5ue_write(long offset, const uint8_t *p_buf, size_t size) {
|
||||
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 ret = GD5F_OK;
|
||||
@@ -428,10 +539,10 @@ int gd5f2gq5ue_erase(long offset, size_t size) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 鍑芥暟鍔熻兘锛氬<EFBFBD>浣嶈姱锟? * 鍏ュ彛鍙傛暟锛氭棤
|
||||
* 函数功能:复位芯<EFBFBD>? * 入口参数:无
|
||||
* 返回值:0 - 成功
|
||||
* 限定条件:SPI 已初始化
|
||||
* 鍑芥暟璇存槑锛氬彂锟?FFh 澶嶄綅鍛戒护鍚庣瓑锟?5ms
|
||||
* 函数说明:发<EFBFBD>?FFh 复位命令后等<E5908E>?5ms
|
||||
*/
|
||||
int gd5f2gq5ue_reset(void) {
|
||||
uint8_t cmd = GD5F_CMD_RESET;
|
||||
@@ -445,3 +556,31 @@ int gd5f2gq5ue_reset(void) {
|
||||
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));
|
||||
}
|
||||
|
||||
|
||||
@@ -144,6 +144,36 @@ int gd5f2gq5ue_erase(long offset, size_t size);
|
||||
*/
|
||||
int gd5f2gq5ue_reset(void);
|
||||
|
||||
/*
|
||||
* 函数功能:查询块是否坏块
|
||||
* 入口参数:block - 块编号 uint32_t 0 ~ GD5F_TOTAL_BLOCKS-1
|
||||
* 返回值:0 - 好块,1 - 坏块
|
||||
* 限定条件:gd5f2gq5ue_init() 已成功调用
|
||||
* 函数说明:通过 BBT(Bad 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
|
||||
}
|
||||
#endif
|
||||
|
||||
386
Drivers/BSP/GD5F2GQ5UE/nand_ftl.c
Normal file
386
Drivers/BSP/GD5F2GQ5UE/nand_ftl.c
Normal 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;
|
||||
}
|
||||
17
Drivers/BSP/GD5F2GQ5UE/nand_ftl.h
Normal file
17
Drivers/BSP/GD5F2GQ5UE/nand_ftl.h
Normal 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
|
||||
@@ -98,6 +98,13 @@ extern "C" {
|
||||
#define NET_POLL_DELAY_MS 1 /* 非阻塞轮询延时 */
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 消息队列配置
|
||||
*/
|
||||
#ifndef NET_MSG_QUEUE_LENGTH
|
||||
#define NET_MSG_QUEUE_LENGTH 8 /* 消息队列深度 */
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,24 +1,50 @@
|
||||
/*
|
||||
* 模块名称:Network Select Implementation
|
||||
* 模块功能:I/O 多路复用实现,包<EFBFBD>?select <EFBFBD>?poll 模式<EFBFBD>? * 用于同时监控多个 Socket 的读写事<EFBFBD>? * 适用平台:STM32F4 系列(CH395F 以太网芯片)
|
||||
* 作者:王建<EFBFBD>? * 创建日期<E697A5>?026-07-18
|
||||
* 修改记录<EFBFBD>? */
|
||||
* 模块功能:I/O 多路复用实现,包括 select 和 poll 两种模式,用于同时监控多个 Socket 的读写事件
|
||||
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
|
||||
* 作者:王建锋
|
||||
* 创建日期: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_socket.h"
|
||||
#include "ch395f.h"
|
||||
#include "main.h"
|
||||
|
||||
/*
|
||||
* 私有函数声明<EFBFBD>? */
|
||||
* 私有函数声明区
|
||||
*/
|
||||
|
||||
static int check_socket_readable(int sockfd);
|
||||
static int check_socket_writable(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,
|
||||
net_fd_set *exceptfds, net_timeval *timeout) {
|
||||
uint32_t start_tick;
|
||||
@@ -89,8 +115,8 @@ int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds,
|
||||
}
|
||||
}
|
||||
|
||||
/* 短暂延时避免忙等 */
|
||||
HAL_Delay(NET_POLL_DELAY_MS);
|
||||
/* 让出 CPU:FreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */
|
||||
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) {
|
||||
uint32_t start_tick;
|
||||
int ready = 0;
|
||||
@@ -185,7 +219,8 @@ int net_poll_events(net_pollfd *fds, int nfds, int timeout) {
|
||||
}
|
||||
}
|
||||
|
||||
HAL_Delay(NET_POLL_DELAY_MS);
|
||||
/* 让出 CPU:FreeRTOS osDelay() 会正确进入休眠,允许其他任务运行 */
|
||||
osDelay(1); /* 1ms 周期轮询,平衡实时性和 CPU 利用率 */
|
||||
}
|
||||
|
||||
return ready;
|
||||
|
||||
@@ -1,12 +1,21 @@
|
||||
/*
|
||||
* 模块名称:Network Socket Core
|
||||
* 模块功能:BSD Socket API 核心实现,包<EFBFBD>?Socket 管理、状态机<EFBFBD>? * 事件处理、数据收发等功能,底层调<EFBFBD>?CH395F 驱动
|
||||
* 适用平台:STM32F4 系列(CH395F 以太网芯片)
|
||||
* 作者:王建<EFBFBD>? * 创建日期<E697A5>?026-07-18
|
||||
* 修改记录<EFBFBD>? */
|
||||
* 模块功能:BSD Socket API 核心实现,包含 Socket 管理、状态机、消息队列(线程安全)、事件处理、数据收发等功能,底层调用 CH395F 驱动
|
||||
* 适用平台:STM32F407ZGTx + CH395F 以太网芯片
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-07-18
|
||||
* 修改记录:
|
||||
* 2026-07-18 王建锋 创建初始版本
|
||||
* 2026-07-19 王建锋 添加消息队列线程安全机制,替换 xQueueCreate 为 CubeMX osMessageQueue
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "FreeRTOS.h"
|
||||
#include "task.h"
|
||||
#include "cmsis_os.h"
|
||||
|
||||
#include "net_socket.h"
|
||||
#include "ch395f.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 void handle_connect_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 fire_event(net_sock_t *p_sock, net_event_t event);
|
||||
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>? */
|
||||
|
||||
/*
|
||||
* 函数功能:网络子系统初始<EFBFBD>? */
|
||||
* 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式)
|
||||
* 入口参数:ip - IP 地址字符串 "192.168.1.100",NULL 使用 DHCP 自动获取 const char *
|
||||
* mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认值 const char *
|
||||
* 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) {
|
||||
ch395f_status_t ret;
|
||||
|
||||
/* 清零 Socket 控制<E68EA7>?*/
|
||||
DBG_INFO("net_init: start");
|
||||
|
||||
/* 清零 Socket 控制块 */
|
||||
memset(s_net_socks, 0, sizeof(s_net_socks));
|
||||
s_net_errno = 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) {
|
||||
DBG_ERROR("net_init: ch395f_check_exist FAILED");
|
||||
s_net_errno = NET_ERR;
|
||||
return -1;
|
||||
}
|
||||
|
||||
DBG_INFO("net_init: 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);
|
||||
DBG_INFO("net_init: set_tcp_mss...");
|
||||
|
||||
/* 设置 TCP MSS */
|
||||
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_ALL(2 字节版)获取所有 Socket 的中断状态
|
||||
* 3. 处理连接/断开/数据接收/发送完成/超时等事件,更新 Socket 状态机
|
||||
* 4. RECV 中断为电平触发,每次 net_poll() 只处理一批中断,避免主循环饿死
|
||||
*/
|
||||
int net_poll(void) {
|
||||
int event_count = 0;
|
||||
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_ALL,CH395F 拉高 INT#;若仍有中断则再次拉低。
|
||||
*/
|
||||
do
|
||||
{
|
||||
if (HAL_GPIO_ReadPin(CH395_INT_GPIO_Port, CH395_INT_Pin) == GPIO_PIN_SET) {
|
||||
@@ -139,7 +187,8 @@ int net_poll(void) {
|
||||
break;
|
||||
}
|
||||
|
||||
/* 诊断:每 3 秒打印一次全局中断状态和 Socket 状<EFBFBD>? * 注意:使用已读取的 int_status,不要再读 GINT(读即清除<E6B885>?*/
|
||||
/* 诊断:每 3 秒打印一次全局中断状态和 Socket 状态。
|
||||
* 注意:使用已读取的 int_status,不要再读 GINT(读即清除)。 */
|
||||
static uint32_t s_last_diag = 0;
|
||||
uint32_t now_diag = HAL_GetTick();
|
||||
if ((now_diag - s_last_diag >= 3000) || s_last_diag == 0) {
|
||||
@@ -208,11 +257,13 @@ int net_poll(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 处理完毕后检<EFBFBD>?INT# 是否仍为低电<EFBFBD>? * 如果是,说明在处理期间有新中断产生,需要再次读<EFBFBD>? *
|
||||
* 注意:CH395F <EFBFBD>?RECV 中断是电平触发的(只要接收缓冲区有数据就保持低电平)<EFBFBD>? * 如果不跳出循环,net_poll() 会无限循环处<E78EAF>?RECV 中断<E4B8AD>? * 导致主循环中的应用代码(<E7A081>?echo)永远无法执行<E689A7>? * 因此每次 net_poll() 调用只处理一批中断,由主循环负责读取数据<E695B0>? */
|
||||
* 处理完毕后检查 INT# 是否仍为低电平。如果是,说明在处理期间有新中断产生,需要再次读取。
|
||||
* 注意:CH395F 的 RECV 中断是电平触发的(只要接收缓冲区有数据就保持低电平)。
|
||||
* 如果不跳出循环,net_poll() 会无限循环处理 RECV 中断,导致主循环中的应用代码(如 echo)永远无法执行。
|
||||
* 因此每次 net_poll() 调用只处理一批中断,由主循环负责读取数据。 */
|
||||
} while (0);
|
||||
|
||||
/* <EFBFBD>?2 秒打印一次诊断信息(调试用) */
|
||||
/* 2 秒打印一次诊断信息(调试用) */
|
||||
{
|
||||
static uint32_t s_last_dbg = 0;
|
||||
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 sockfd;
|
||||
net_sock_t *p_sock;
|
||||
|
||||
/* 参数检<EFBFBD>?*/
|
||||
/* 参数检查*/
|
||||
if (domain != NET_AF_INET) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
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 Socket(SOCK_DGRAM)立即打开 CH395F Socket 并配置为 Server 模式
|
||||
* - Socket 4~7 需显式分配收发缓冲区
|
||||
*/
|
||||
int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) {
|
||||
net_sock_t *p_sock;
|
||||
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. 多连接模式下,先为数据 Socket(NET_TCP_SERVER_DATA_SOCK_START ~ 7)配置协议类型、源端口和独立缓冲区
|
||||
* 2. CH395F 根据源端口匹配、协议类型为 TCP、状态为 CLOSED 查找可用 Socket 进行自动分配
|
||||
* 3. 最后配置监听 Socket 并启动 TCP_LISTEN
|
||||
*/
|
||||
int net_listen(int sockfd, int backlog) {
|
||||
net_sock_t *p_sock;
|
||||
@@ -351,8 +483,10 @@ int net_listen(int sockfd, int backlog) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 多连接模式:先为数据 Socket 设置协议类型、源端口和独立缓冲区<EFBFBD>? * 必须在监听话<E590AC>?Socket OPEN+LISTEN 之前配置,确<EFBC8C>?CH395F <20>? * 自动分配机制能扫描到所有可<E69C89>?Socket
|
||||
* CH395F 根据源端口匹配、协议类<E8AEAE>?TCP、状<E38081>?CLOSED 查找可用 Socket */
|
||||
/* 多连接模式:先为数据 Socket 设置协议类型、源端口和独立缓冲区。
|
||||
* 必须在监听 Socket OPEN+LISTEN 之前配置,确保 CH395F
|
||||
* 自动分配机制能扫描到所有可用 Socket。
|
||||
* CH395F 根据源端口匹配、协议类型为 TCP、状态为 CLOSED 查找可用 Socket。 */
|
||||
for (int ds = NET_TCP_SERVER_DATA_SOCK_START; ds < NET_MAX_SOCKETS; ds++) {
|
||||
uint8_t base = (uint8_t)(ds * 4);
|
||||
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) {
|
||||
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) {
|
||||
net_sock_t *p_sock;
|
||||
@@ -494,47 +642,37 @@ int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:发送数据(TCP<EFBFBD>? */
|
||||
int net_send(int sockfd, const void *buf, int len, int flags) {
|
||||
net_sock_t *p_sock;
|
||||
* 函数功能:发送数据(内部锁定版本,必须在 netTask 上下文调用)
|
||||
*/
|
||||
static int net_send_locked(net_sock_t *p_sock, const void *buf, int len) {
|
||||
int sent = 0;
|
||||
uint32_t tick_start;
|
||||
|
||||
/* 参数检<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;
|
||||
}
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
|
||||
if (buf == NULL || len <= 0) {
|
||||
s_net_errno = NET_ERR_INVAL;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (p_sock->state != NET_SOCK_STATE_ESTABLISHED) {
|
||||
s_net_errno = NET_ERR_NOTCONN;
|
||||
return -1;
|
||||
}
|
||||
|
||||
tick_start = HAL_GetTick();
|
||||
|
||||
/* 分块发<E59D97>?*/
|
||||
while (sent < len) {
|
||||
int chunk = len - sent;
|
||||
if (chunk > NET_SEND_BUF_SIZE) {
|
||||
chunk = NET_SEND_BUF_SIZE;
|
||||
}
|
||||
|
||||
/* 检查发送缓冲区是否空闲 */
|
||||
if (!p_sock->send_ready) {
|
||||
/* 超时检<E697B6>?*/
|
||||
if (NET_SEND_TIMEOUT_MS > 0 &&
|
||||
(HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) {
|
||||
s_net_errno = NET_ERR_TIMEDOUT;
|
||||
break;
|
||||
}
|
||||
/* 继续轮询 */
|
||||
net_poll();
|
||||
continue;
|
||||
}
|
||||
@@ -544,7 +682,6 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
|
||||
sent += chunk;
|
||||
p_sock->send_bytes += (uint32_t)chunk;
|
||||
|
||||
/* 短暂延时等待发送完<E98081>?*/
|
||||
tick_start = HAL_GetTick();
|
||||
while (!p_sock->send_ready) {
|
||||
net_poll();
|
||||
@@ -559,30 +696,12 @@ int net_send(int sockfd, const void *buf, int len, int flags) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接收数据(TCP<EFBFBD>? */
|
||||
int net_recv(int sockfd, void *buf, int len, int flags) {
|
||||
net_sock_t *p_sock;
|
||||
* 函数功能:接收数据(内部锁定版本,必须在 netTask 上下文调用)
|
||||
*/
|
||||
static int net_recv_locked(net_sock_t *p_sock, void *buf, int len, int flags) {
|
||||
uint16_t recv_len;
|
||||
uint32_t tick_start;
|
||||
|
||||
/* 参数检<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;
|
||||
}
|
||||
int sockfd = (int)(p_sock - s_net_socks);
|
||||
|
||||
if (buf == NULL || len <= 0) {
|
||||
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);
|
||||
if (recv_len > 0) {
|
||||
/* 有数据可<E68DAE>?*/
|
||||
int read_len = (recv_len > (uint16_t)len) ? len : (int)recv_len;
|
||||
ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len);
|
||||
p_sock->recv_bytes += (uint32_t)read_len;
|
||||
return read_len;
|
||||
}
|
||||
|
||||
/* 非阻塞模式检<E5BC8F>?*/
|
||||
if (flags & NET_MSG_DONTWAIT) {
|
||||
s_net_errno = NET_ERR_WOULDBLOCK;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 超时检<E697B6>?*/
|
||||
if (NET_RECV_TIMEOUT_MS > 0 &&
|
||||
(HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) {
|
||||
s_net_errno = NET_ERR_TIMEDOUT;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* 连接已断开检<E5BC80>?*/
|
||||
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_DGRAM);net_init() 已调用
|
||||
*/
|
||||
int net_sendto(int sockfd, const void *buf, int len, int flags,
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:接<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_DGRAM);net_init() 已调用;有数据可用
|
||||
*/
|
||||
int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
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) {
|
||||
net_sock_t *p_sock;
|
||||
|
||||
/* 参数检<E695B0>?*/
|
||||
if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) {
|
||||
int net_send_sock(net_sock_t *p_sock, const void *buf, int len) {
|
||||
if (p_sock == NULL) {
|
||||
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;
|
||||
return net_send_locked(p_sock, buf, len);
|
||||
}
|
||||
|
||||
/* 关闭 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 == 1;net_init() 已调用
|
||||
*/
|
||||
|
||||
int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) {
|
||||
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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:获<EFBFBD>?socket 最后错误码
|
||||
* 函数功能:获取 Socket 最后一次操作返回的错误码
|
||||
* 返回值:当前错误码值(NET_OK=0, NET_ERR_xxx=-N) int
|
||||
* 限定条件:无
|
||||
* 函数说明:每次 net_*() API 调用失败后,错误码被设置。调用成功时不自动清零
|
||||
*/
|
||||
|
||||
int net_get_errno(void) {
|
||||
return s_net_errno;
|
||||
}
|
||||
|
||||
@@ -16,6 +16,31 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#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);
|
||||
|
||||
/*
|
||||
* 函数功能:处理消息队列中的请求(必须在 netTask 主循环中调用)
|
||||
* 限定条件:net_init() 已调用
|
||||
* 函数说明:处理其他任务通过 net_send/net_recv 等接口发送的请求,
|
||||
* 在 netTask 上下文中串行化执行所有 CH395F 操作
|
||||
*/
|
||||
void net_process_messages(void);
|
||||
|
||||
/*
|
||||
* 函数功能:获取 Socket 控制块指针(内部使用)
|
||||
* 入口参数:sockfd - socket 描述符
|
||||
@@ -252,6 +285,39 @@ uint32_t net_ntohl(uint32_t netlong);
|
||||
*/
|
||||
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
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,75 +1,105 @@
|
||||
/*
|
||||
* 模块名称:RS-485 半双工通信驱动
|
||||
* 模å<EFBFBD>—功能:RS-485 å<>Šå<C5A0>Œå·¥é€šä¿¡é©±åŠ¨å®žçŽ°ï¼Œæ ¸å¿ƒè¦<C3A8>点:
|
||||
* 1. å<>‘é€<C3A9>å‰<C3A5>拉高 DE(切æ<E280A1>¢åˆ°å<C2B0>‘é€<C3A9>æ€<C3A6>)
|
||||
* 2. HAL_UART_Transmit 内部已ç‰ï¿?TC(å<CB86>‘é€<C3A9>完æˆ<C3A6>ï¼‰æ ‡å¿—
|
||||
* 3. å<>‘é€<C3A9>完毕å<E280A2>Žæ‹‰ä½Ž DE(切回接收æ€<C3A6>)
|
||||
* 4. 使用 HAL_UARTEx_ReceiveToIdle_IT 实现å<C2B0>˜é•¿å¸§æŽ¥ï¿? * å<>‚考:ST AN3070 应用笔记ã€<C3A3>controllerstech.com RS485 教程
|
||||
* 模块功能:RS-485 半双工通信驱动实现,封装任意 UART 外设实现 485 方向控制,
|
||||
* 提供阻塞发送、中断接收(IDLE 空闲帧检测)。使用 DWT CYCCNT 提供
|
||||
* 微秒级延时,确保 RS485 收发器 DE 使能稳定。
|
||||
* 适用平台: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"
|
||||
|
||||
/* ======================== 内部辅助函数 ======================== */
|
||||
|
||||
/*
|
||||
* 内部辅助ï¿?- æ–¹å<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_RX(h) HAL_GPIO_WritePin((h)->dir_port, (h)->dir_pin, GPIO_PIN_RESET)
|
||||
|
||||
/*
|
||||
* 公共函数实现
|
||||
*/
|
||||
/* ======================== 公共函数实现 ======================== */
|
||||
|
||||
/*
|
||||
* 函数功能:初始化 RS-485 句柄
|
||||
* å…¥å<EFBFBD>£å<EFBFBD>‚数:handle - RS-485 å<EFBFBD>¥æŸ„指针
|
||||
* huart - UART 外设å<EFBFBD>¥æŸ„
|
||||
* dir_port - DE 引脚 GPIO 端å<C2AF>£
|
||||
* dir_pin - DE 引脚 GPIO ç¼–å<E28093>·
|
||||
* 返回值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šUART ï¿?GPIO 须先ï¿?CubeMX 完æˆ<C3A6>åˆ<C3A5>å§‹ï¿? * 函数说明:åˆ<C3A5>始化å<E28093>Žç«‹å<E280B9>³å°† DE 拉低,使 RS-485 æ”¶å<C2B6>‘器处于接收状ï¿? */
|
||||
* 入口参数:p_handle - RS-485 句柄指针 rs485_handle_t*(非空)
|
||||
* p_huart - UART 外设句柄指针(已由 CubeMX 初始化) UART_HandleTypeDef*(非空)
|
||||
* p_dir_port - 方向控制 GPIO 端口(如 GPIOD) GPIO_TypeDef*(非空)
|
||||
* dir_pin - 方向控制 GPIO 引脚号(如 GPIO_PIN_0) uint16_t
|
||||
* 返回值:无
|
||||
* 限定条件:UART 和 GPIO 须先由 CubeMX 完成初始化
|
||||
* 函数说明:初始化后默认处于接收状态(DE 引脚拉低)
|
||||
*/
|
||||
void rs485_init(rs485_handle_t *p_handle,
|
||||
UART_HandleTypeDef *p_huart,
|
||||
GPIO_TypeDef *p_dir_port,
|
||||
uint16_t dir_pin) {
|
||||
uint16_t dir_pin)
|
||||
{
|
||||
p_handle->huart = p_huart;
|
||||
p_handle->dir_port = p_dir_port;
|
||||
p_handle->dir_pin = dir_pin;
|
||||
p_handle->rx_size = 0;
|
||||
|
||||
/* 默认进入接收状�?*/
|
||||
RS485_DIR_RX(p_handle);
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:阻塞方å¼<EFBFBD>å<EFBFBD>‘é€<EFBFBD>æ•°ï¿? * å…¥å<C2A5>£å<C2A3>‚数:handle - RS-485 å<>¥æŸ„指针
|
||||
* data - å¾…å<E280A6>‘é€<C3A9>æ•°æ<C2B0>®ç¼“冲区
|
||||
* len - å¾…å<E280A6>‘é€<C3A9>å—节数
|
||||
* timeout - å<>‘é€<C3A9>超时(ms),ï¿?0 使用默认ï¿? * 返回值:HAL_OK / HAL_TIMEOUT / HAL_ERROR
|
||||
* é™<EFBFBD>定æ<EFBFBD>¡ä»¶ï¼šé¡»åœ¨ä¸»å¾ªçޝï¿?RTOS 任务ä¸è°ƒç”¨ï¼Œä¸<C3A4>å<EFBFBD>¯ï¿?UART 䏿–ä¸è°ƒï¿? * 函数说明ï¿? * æ—¶åº<C3A5>:DE=HIGH ï¿?UART å<>‘�?N å—节 ï¿?ï¿?TC ï¿?DE=LOW
|
||||
* HAL_UART_Transmit 内部会ç‰ï¿?TXE(å<CB86>‘é€<C3A9>寄å˜å™¨ç©ºï¼‰é€<C3A9>å—节æ<E2809A>¬æ•°æ<C2B0>®ï¿? * 最å<E282AC>Žè¿˜ä¼šç‰ï¿?TC(å<CB86>‘é€<C3A9>完æˆ<C3A6>ï¼‰æ ‡å¿—ï¼Œç¡®ä¿<C3A4>最å<E282AC>Žä¸€å—节的å<E2809E>œæ¢ä½<C3A4>已移出�? * å› æ¤è¿”回å<C5BE>Žå†<C3A5>拉低 DE 是安全的
|
||||
* 函数功能:阻塞方式发送数据
|
||||
* 入口参数:p_handle - RS-485 句柄指针 rs485_handle_t*(非空)
|
||||
* p_data - 待发送数据缓冲区 uint8_t*(非空)
|
||||
* len - 待发送字节数 uint16_t 1 - N
|
||||
* timeout - 发送超时(ms),传 0 使用 RS485_TIMEOUT_DEFAULT uint32_t 0 / 1 - N
|
||||
* 返回值: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,
|
||||
const uint8_t *p_data,
|
||||
uint16_t len,
|
||||
uint32_t timeout) {
|
||||
uint32_t timeout)
|
||||
{
|
||||
HAL_StatusTypeDef status = HAL_OK;
|
||||
|
||||
if (timeout == 0U) {
|
||||
timeout = RS485_TIMEOUT_DEFAULT;
|
||||
}
|
||||
|
||||
/* 1. 切æ<E280A1>¢åˆ°å<C2B0>‘é€<C3A9>状ï¿?*/
|
||||
RS485_DIR_TX(p_handle);
|
||||
|
||||
/* 2. 阻塞å<C5BE>‘é€<C3A9>(内部ç‰å¾… TCï¿?*/
|
||||
delay_us(50);
|
||||
|
||||
status = HAL_UART_Transmit(p_handle->huart,
|
||||
(uint8_t *)p_data,
|
||||
len,
|
||||
timeout);
|
||||
|
||||
/* 3. å<>‘é€<C3A9>完毕或超时,切回接收状ï¿?*/
|
||||
RS485_DIR_RX(p_handle);
|
||||
|
||||
return status;
|
||||
@@ -77,28 +107,46 @@ HAL_StatusTypeDef rs485_transmit(rs485_handle_t *p_handle,
|
||||
|
||||
/*
|
||||
* 函数功能:启动中断方式接收(IDLE 空闲帧检测)
|
||||
* å…¥å<EFBFBD>£å<EFBFBD>‚数:handle - RS-485 å<EFBFBD>¥æŸ„指针
|
||||
* buf - 接收缓冲� * buf_size - 缓冲区最大容� * 返回值:HAL_OK / HAL_ERROR
|
||||
* é™<EFBFBD>定æ<EFBFBD>¡ä»¶ï¼šUART 须已开å<E282AC>¯å…¨å±€ä¸æ–(NVIC 使能ï¿? * 函数说明ï¿? * 使用 HAL_UARTEx_ReceiveToIdle_IT 实现å<C2B0>˜é•¿å¸§æŽ¥æ”¶ï¿½? * ï¿?UART 总线空闲超过 1 个å—符时间å<C2B4>Žï¼Œç¡¬ä»¶è§¦ï¿?IDLE 䏿–ï¿? * HAL 自动调用 HAL_UARTEx_RxEventCallback 并返回已接收å—节数�? * 用户须在该回调ä¸é‡<C3A9>新调用本函数é‡<C3A9>æ–°å¼€å<E282AC>¯æŽ¥ï¿? */
|
||||
* 入口参数:p_handle - RS-485 句柄指针 rs485_handle_t*(非空)
|
||||
* p_buf - 接收缓冲区 uint8_t*(非空)
|
||||
* 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,
|
||||
uint8_t *p_buf,
|
||||
uint16_t buf_size) {
|
||||
uint16_t buf_size)
|
||||
{
|
||||
p_handle->rx_size = 0;
|
||||
return HAL_UARTEx_ReceiveToIdle_IT(p_handle->huart, p_buf, buf_size);
|
||||
}
|
||||
|
||||
/*
|
||||
* å‡½æ•°åŠŸèƒ½ï¼šç¼“å˜æœ€è¿‘一次接收的å—节ï¿? * å…¥å<C2A5>£å<C2A3>‚数:handle - RS-485 å<>¥æŸ„指针
|
||||
* size - 本次接收到的å—节ï¿? * 返回值:ï¿? * é™<C3A9>定æ<C5A1>¡ä»¶ï¼šé¡»ï¿?HAL_UARTEx_RxEventCallback ä¸è°ƒï¿? */
|
||||
void rs485_rx_set_size(rs485_handle_t *p_handle, uint16_t size) {
|
||||
* 函数功能:缓存最近一次接收的字节数
|
||||
* 入口参数:p_handle - RS-485 句柄指针 rs485_handle_t*(非空)
|
||||
* 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;
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:获å<EFBFBD>–最近一次接收的å—节ï¿? * å…¥å<C2A5>£å<C2A3>‚数:handle - RS-485 å<>¥æŸ„指针
|
||||
* 返回值:å—节ï¿? */
|
||||
uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle) {
|
||||
* 函数功能:获取最近一次接收的字节数
|
||||
* 入口参数:p_handle - RS-485 句柄指针 const rs485_handle_t*(非空)
|
||||
* 返回值:最近一次接收的字节数 uint16_t
|
||||
* 限定条件:须在 HAL_UARTEx_RxEventCallback 触发后调用
|
||||
* 函数说明:获取 rx_size 字段中缓存的接收字节数
|
||||
*/
|
||||
uint16_t rs485_rx_get_size(const rs485_handle_t *p_handle)
|
||||
{
|
||||
return p_handle->rx_size;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
/*
|
||||
* 模块名称:SD2506API-G RTC 实时时钟驱动
|
||||
* 模块功能:SD2506API-G 高精度温补实时时钟模<EFBFBD>?I2C 驱动
|
||||
* 适用平台:STM32F407ZGT6,I2C1 接口 (PB6-SCL, PB7-SDA)
|
||||
* 作者:王建<EFBFBD>? * 创建日期<E697A5>?026-07-17
|
||||
* 修改记录<EFBFBD>? * 2026-07-17 王建<E78E8B>? 创建初始版本,参<EFBC8C>?SD2506API-G Ver2.0 手册
|
||||
* 模块名称:SD2506API-G RTC Driver
|
||||
* 模块功能:SD2506API-G 高精度温补实时时钟驱动,I2C 接口
|
||||
* 适用平台:STM32F407ZGTx + SD2506API-G 实时时钟芯片(I2C1: PB6-SCL, PB7-SDA)
|
||||
* 作者:王建锋
|
||||
* 创建日期:2026-07-17
|
||||
* 修改记录:
|
||||
* 2026-07-17 王建锋 创建初始版本,参考 SD2506API-G Ver2.0 手册
|
||||
*/
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "sd2506.h"
|
||||
#include "i2c.h"
|
||||
|
||||
@@ -15,8 +19,11 @@ extern I2C_HandleTypeDef hi2c1;
|
||||
/* ======================== 内部辅助函数 ======================== */
|
||||
|
||||
/*
|
||||
* 函数功能:BCD 码转十进<EFBFBD>? * 入口参数:bcd - BCD 编码<E7BC96>? uint8_t 0x00 - 0x99
|
||||
* <EFBFBD>?<3F>?值:十进制数<E588B6>? * 限定条件:输入为合法 BCD <20>? * 函数说明:高 4 位为十位,低 4 位为个位
|
||||
* 函数功能:BCD 码转十进制数
|
||||
* 入口参数:bcd - BCD 编码值 uint8_t 0x00 - 0x99
|
||||
* 返回值:十进制数值 uint8_t
|
||||
* 限定条件:输入为合法 BCD 编码(高 4 位和低 4 位均 <= 9)
|
||||
* 函数说明:高 4 位为十位,低 4 位为个位,分别乘 10 和加后返回
|
||||
*/
|
||||
uint8_t sd2506_bcd_to_dec(uint8_t bcd) {
|
||||
return ((bcd >> 4) * 10) + (bcd & 0x0FU);
|
||||
@@ -31,9 +38,11 @@ uint8_t sd2506_dec_to_bcd(uint8_t dec) {
|
||||
|
||||
/*
|
||||
* 函数功能:写单字节寄存器
|
||||
* 入口参数:reg - 寄存器地址 uint8_t 00H - 79H
|
||||
* val - 写入<EFBFBD>? uint8_t 0x00 - 0xFF
|
||||
* <EFBFBD>?<3F>?值:0 - 成功<E68890>?2 - I2C 错误
|
||||
* 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79
|
||||
* val - 写入数据 uint8_t 0x00 - 0xFF
|
||||
* 返回值:0-成功,2-I2C 错误 int
|
||||
* 限定条件:I2C 外设已初始化
|
||||
* 函数说明:通过 I2C 向指定地址写入 1 字节数据
|
||||
* 限定条件:I2C 外设已初始化
|
||||
* 函数说明:通过 I2C 向指定地址写入 1 字节
|
||||
*/
|
||||
@@ -48,10 +57,12 @@ static int sd2506_write_reg(uint8_t reg, uint8_t val) {
|
||||
|
||||
/*
|
||||
* 函数功能:读单字节寄存器
|
||||
* 入口参数:reg - 寄存器地址 uint8_t 00H - 79H
|
||||
* val - 读取值指<EFBFBD>? uint8_t*
|
||||
* <EFBFBD>?<3F>?值:0 - 成功<E68890>?2 - I2C 错误
|
||||
* 限定条件:I2C 外设已初始化,val 为非空指<EFBFBD>? * 函数说明:通过 I2C 从指定地址读取 1 字节
|
||||
* 入口参数:reg - 寄存器地址 uint8_t 0x00 - 0x79
|
||||
* p_val - 读取值指针 uint8_t*(非空)
|
||||
* 返回值:0-成功,2-I2C 错误 int
|
||||
* 限定条件:I2C 外设已初始化,p_val 为非空指针
|
||||
* 函数说明:通过 I2C 从指定地址读取 1 字节数据
|
||||
* 限定条件:I2C 外设已初始化,val 为非空指针函数说明:通过 I2C 从指定地址读取 1 字节
|
||||
*/
|
||||
static int sd2506_read_reg(uint8_t reg, uint8_t *p_val) {
|
||||
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
|
||||
* data - 数据缓冲区指<EFBFBD>? uint8_t*
|
||||
* 函数功能:写多字节寄存器(连续写)
|
||||
* 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79
|
||||
* p_data - 数据缓冲区指针 uint8_t*(非空)
|
||||
* len - 数据长度 uint8_t 1 - N
|
||||
* <EFBFBD>?<3F>?值:0 - 成功<E68890>?2 - I2C 错误
|
||||
* 限定条件:I2C 外设已初始化,data 为非空指<EFBFBD>? * 函数说明:从 reg 开始连续写<E7BBAD>?len 字节
|
||||
* 返回值:0-成功,2-I2C 错误 int
|
||||
* 限定条件:I2C 外设已初始化,p_data 为非空指针
|
||||
* 函数说明:从 reg 开始连续写入 len 字节数据
|
||||
* 限定条件:I2C 外设已初始化,data 为非空指针
|
||||
*/
|
||||
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,
|
||||
@@ -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
|
||||
* data - 数据缓冲区指<EFBFBD>? uint8_t*
|
||||
* 函数功能:读多字节寄存器(连续读)
|
||||
* 入口参数:reg - 起始寄存器地址 uint8_t 0x00 - 0x79
|
||||
* p_data - 数据缓冲区指针 uint8_t*(非空)
|
||||
* len - 数据长度 uint8_t 1 - N
|
||||
* <EFBFBD>?<3F>?值:0 - 成功<E68890>?2 - I2C 错误
|
||||
* 限定条件:I2C 外设已初始化,data 为非空指<EFBFBD>? * 函数说明:从 reg 开始连续读<E7BBAD>?len 字节
|
||||
* 返回值:0-成功,2-I2C 错误 int
|
||||
* 限定条件:I2C 外设已初始化,p_data 为非空指针
|
||||
* 函数说明:从 reg 开始连续读取 len 字节数据
|
||||
* 限定条件:I2C 外设已初始化,data 为非空指针
|
||||
*/
|
||||
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,
|
||||
@@ -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 外设已初始化
|
||||
* 函数说明:顺序:先写 WRTC1=1, 再写 WRTC2=1 + WRTC3=1
|
||||
*/
|
||||
static int sd2506_write_enable(void) {
|
||||
int ret = 0;
|
||||
|
||||
/* 先置 WRTC1=1 (bit6=1), 其它位参考手<EFBFBD>? 0x84 */
|
||||
/* 先置 WRTC1=1 (bit6=1), 其它位参考手册0x84 */
|
||||
ret = sd2506_write_reg(SD2506_REG_CTR1, 0x84U);
|
||||
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 外设已初始化
|
||||
* 函数说明:顺序:先写 WRTC2=0 + WRTC3=0, 再写 WRTC1=0
|
||||
*/
|
||||
@@ -143,10 +158,14 @@ static int sd2506_write_disable(void) {
|
||||
/*
|
||||
* 函数功能:初始化 SD2506API-G RTC
|
||||
* 入口参数:无
|
||||
* 返回值:0 - 成功<E68890>?2 - I2C 错误
|
||||
* 限定条件:CubeMX 已完<EFBFBD>?I2C1 初始<EFBFBD>?
|
||||
* 函数说明<EFBFBD>?. 读取芯片 ID 验证通信
|
||||
* 2. 上电重置充电寄存<EFBFBD>?18H=82H
|
||||
* 返回值:0-成功,2-I2C 错误 int
|
||||
* 限定条件:CubeMX 已完成 I2C1 初始化(PB6-SCL, PB7-SDA)
|
||||
* 函数说明:1. 读取芯片 ID 验证通信
|
||||
* 2. 上电重置充电寄存器 (0x18=0x82)
|
||||
* 3. 配置 24 小时制、开自动复位
|
||||
* 限定条件:CubeMX 已完成I2C1 初始化
|
||||
* 函数说明:读取芯片 ID 验证通信
|
||||
* 2. 上电重置充电寄存器18H=82H
|
||||
* 3. 配置 24 小时制、开自动复位
|
||||
*/
|
||||
int sd2506_init(void) {
|
||||
@@ -221,9 +240,11 @@ int sd2506_set_time(const sd2506_time_t *p_time) {
|
||||
}
|
||||
|
||||
/*
|
||||
* function: read RTC time
|
||||
* param: p_time - output time struct
|
||||
* return: 0=OK, -2=I2C error
|
||||
* 函数功能:读取 RTC 时间日期
|
||||
* 入口参数:p_time - 输出时间结构体指针 sd2506_time_t*(非空)
|
||||
* 返回值:0-成功,2-I2C 错误 int
|
||||
* 限定条件:sd2506_init() 已调用成功
|
||||
* 函数说明:一次读取 7 字节时间数据 (00H~06H),所有实时数据被锁存避免错误
|
||||
* note: sd2506_init() must be called first
|
||||
*/
|
||||
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() 已调用成功
|
||||
* 函数说明:读取寄存器 0x16,bit7 为符号位
|
||||
* return: 0=OK, -2=I2C error
|
||||
* 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
|
||||
* note: combines 9-bit value
|
||||
*/
|
||||
@@ -314,8 +341,11 @@ int sd2506_get_battery_voltage(uint16_t *p_voltage) {
|
||||
}
|
||||
|
||||
/*
|
||||
* function: read 8-byte chip ID
|
||||
* param: p_id - 8-byte output buffer
|
||||
* 函数功能:读取 8 字节芯片 ID
|
||||
* 入口参数:p_id - 输出缓冲区指针 uint8_t[8](非空)
|
||||
* 返回值:0-成功,2-I2C 错误 int
|
||||
* 限定条件:sd2506_init() 已调用成功
|
||||
* 函数说明:读取寄存器 0x72~0x79
|
||||
* return: 0=OK, -2=I2C error
|
||||
* note: reads registers 0x72-0x79
|
||||
*/
|
||||
@@ -328,10 +358,13 @@ int sd2506_get_id(uint8_t p_id[8]) {
|
||||
}
|
||||
|
||||
/*
|
||||
* function: read SRAM data
|
||||
* param: addr - start address (0-69)
|
||||
* p_buf - output buffer
|
||||
* len - read length
|
||||
* 函数功能:读取 SRAM 数据
|
||||
* 入口参数:addr - 起始地址 uint8_t 0-69
|
||||
* p_buf - 输出缓冲区指针 uint8_t*(非空)
|
||||
* 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
|
||||
* 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
|
||||
* param: addr - start address (0-69)
|
||||
* p_buf - input data
|
||||
* len - write length
|
||||
* 函数功能:写入 SRAM 数据
|
||||
* 入口参数:addr - 起始地址 uint8_t 0-69
|
||||
* p_buf - 输入数据指针 uint8_t*(非空)
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
* mask - alarm match mask
|
||||
* 函数功能:设置闹钟中断
|
||||
* 入口参数:p_time - 报警时间结构体指针 sd2506_time_t*(非空)
|
||||
* mask - 报警匹配掩码 uint8_t
|
||||
* 返回值:0-成功,-2=I2C 错误 int
|
||||
* 限定条件:sd2506_init() 已调用成功
|
||||
* 函数说明:写入 8 字节报警数据 (07H~0EH),使能 CTR2.INTAE+INTS0+IM
|
||||
* return: 0=OK, -2=I2C error
|
||||
*/
|
||||
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
|
||||
* note: reading CTR1 clears INTAF
|
||||
*/
|
||||
@@ -440,8 +482,10 @@ int sd2506_clear_alarm(void) {
|
||||
}
|
||||
|
||||
/*
|
||||
* function: read control register 1
|
||||
* param: p_val - output value
|
||||
* 函数功能:读取控制寄存器 1 (CTR1)
|
||||
* 入口参数:p_val - 输出值指针 uint8_t*(非空)
|
||||
* 返回值:0-成功,-2=I2C 错误 int
|
||||
* 限定条件:sd2506_init() 已调用成功
|
||||
* return: 0=OK, -2=I2C error
|
||||
*/
|
||||
int sd2506_read_ctr1(uint8_t *p_val) {
|
||||
|
||||
@@ -33,6 +33,12 @@ extern "C" {
|
||||
*/
|
||||
/* #define DBG_DEBUG_ENABLE */
|
||||
|
||||
/*
|
||||
* 系统时间戳开关:取消注释在所有日志行首追加 [YYYY-MM-DD HH:MM:SS] 时间戳
|
||||
* 依赖 sys_clock_init() 已调用(SD2506 RTC 初始化完成)
|
||||
*/
|
||||
#define APP_TIMESTAMP_ENABLE
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -22,8 +22,13 @@ extern "C" {
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include "dbg_cfg.h"
|
||||
|
||||
#ifdef APP_TIMESTAMP_ENABLE
|
||||
#include "sys_clock.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* 总开关:DBG_ENABLE
|
||||
* 定义 → 输出 DBG_ERROR / DBG_INFO;若 DBG_DEBUG_ENABLE 也定义则输出 DBG_DEBUG
|
||||
@@ -48,22 +53,65 @@ extern "C" {
|
||||
#define DBG_TAG ""
|
||||
#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 { \
|
||||
DBG_PRINT("[ERR]" DBG_TAG " " fmt "\r\n", ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* DBG_INFO - 信息输出,受 DBG_ENABLE 控制
|
||||
* DBG_INFO - 信息输出,受 DBG_ENABLE 控制(无时间戳)
|
||||
*/
|
||||
#define DBG_INFO(fmt, ...) do { \
|
||||
DBG_PRINT(DBG_TAG " " fmt "\r\n", ##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* DBG_DEBUG - 调试输出,需 DBG_ENABLE + DBG_DEBUG_ENABLE 同时定义
|
||||
* DBG_DEBUG - 调试输出,需 DBG_ENABLE + DBG_DEBUG_ENABLE 同时定义(无时间戳)
|
||||
*/
|
||||
#ifdef DBG_DEBUG_ENABLE
|
||||
#define DBG_DEBUG(fmt, ...) do { \
|
||||
@@ -73,6 +121,8 @@ extern "C" {
|
||||
#define DBG_DEBUG(fmt, ...)
|
||||
#endif
|
||||
|
||||
#endif /* APP_TIMESTAMP_ENABLE */
|
||||
|
||||
#else /* DBG_ENABLE not defined — all debug output disabled */
|
||||
|
||||
#define DBG_ERROR(fmt, ...)
|
||||
|
||||
@@ -55,6 +55,7 @@ void DebugMon_Handler(void);
|
||||
void DMA1_Stream3_IRQHandler(void);
|
||||
void DMA1_Stream4_IRQHandler(void);
|
||||
void EXTI9_5_IRQHandler(void);
|
||||
void SPI1_IRQHandler(void);
|
||||
void SPI2_IRQHandler(void);
|
||||
void USART1_IRQHandler(void);
|
||||
void USART2_IRQHandler(void);
|
||||
@@ -62,7 +63,9 @@ void USART3_IRQHandler(void);
|
||||
void UART4_IRQHandler(void);
|
||||
void UART5_IRQHandler(void);
|
||||
void TIM7_IRQHandler(void);
|
||||
void DMA2_Stream0_IRQHandler(void);
|
||||
void DMA2_Stream2_IRQHandler(void);
|
||||
void DMA2_Stream3_IRQHandler(void);
|
||||
void DMA2_Stream7_IRQHandler(void);
|
||||
/* USER CODE BEGIN EFP */
|
||||
|
||||
|
||||
43
Lib/FatFs/diskio.h
Normal file
43
Lib/FatFs/diskio.h
Normal 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
7084
Lib/FatFs/ff.c
Normal file
File diff suppressed because it is too large
Load Diff
429
Lib/FatFs/ff.h
Normal file
429
Lib/FatFs/ff.h
Normal 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
41
Lib/FatFs/ffconf.h
Normal 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
50
Lib/dhara/bytes.h
Normal 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
45
Lib/dhara/error.h
Normal 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
884
Lib/dhara/journal.c
Normal 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
256
Lib/dhara/journal.h
Normal 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
530
Lib/dhara/map.c
Normal 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, ¤t, 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
114
Lib/dhara/map.h
Normal 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
105
Lib/dhara/nand.h
Normal 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
|
||||
@@ -338,9 +338,9 @@
|
||||
<v6Rtti>0</v6Rtti>
|
||||
<VariousControls>
|
||||
<MiscControls></MiscControls>
|
||||
<Define>USE_HAL_DRIVER,STM32F407xx</Define>
|
||||
<Define>USE_HAL_DRIVER,STM32F407xx,NDEBUG</Define>
|
||||
<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>
|
||||
</Cads>
|
||||
<Aads>
|
||||
@@ -761,9 +761,9 @@
|
||||
<FilePath>..\App\app_main.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>sys_time.c</FileName>
|
||||
<FileName>sys_clock.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\App\sys_time.c</FilePath>
|
||||
<FilePath>..\App\sys_clock.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>net_task.c</FileName>
|
||||
@@ -1256,6 +1256,11 @@
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\NET\net_socket.c</FilePath>
|
||||
</File>
|
||||
<File>
|
||||
<FileName>nand_ftl.c</FileName>
|
||||
<FileType>1</FileType>
|
||||
<FilePath>..\Drivers\BSP\GD5F2GQ5UE\nand_ftl.c</FilePath>
|
||||
</File>
|
||||
</Files>
|
||||
</Group>
|
||||
<Group>
|
||||
@@ -1932,6 +1937,100 @@
|
||||
</File>
|
||||
</Files>
|
||||
</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>
|
||||
<GroupName>::CMSIS</GroupName>
|
||||
</Group>
|
||||
|
||||
1
MDK-ARM/_build.cmd
Normal file
1
MDK-ARM/_build.cmd
Normal file
@@ -0,0 +1 @@
|
||||
@echo off && cd /d "%~dp0" && C:\Keil_v5\UV4\UV4.exe -r "STM32F407-Demo.uvprojx" -t STM32F407-Demo -j0
|
||||
@@ -1,71 +1,75 @@
|
||||
*** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin'
|
||||
Rebuild target 'STM32F407-Demo'
|
||||
assembling startup_stm32f407xx.s...
|
||||
compiling net_task.c...
|
||||
compiling app_main.c...
|
||||
compiling crc.c...
|
||||
compiling ringbuf.c...
|
||||
compiling dma.c...
|
||||
compiling stm32f4xx_hal_flash_ex.c...
|
||||
compiling sys_time.c...
|
||||
compiling crc.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_hal_rcc_ex.c...
|
||||
compiling usart.c...
|
||||
compiling stm32f4xx_hal_dma.c...
|
||||
compiling i2c.c...
|
||||
compiling gpio.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 freertos.c...
|
||||
compiling usart.c...
|
||||
compiling stm32f4xx_hal_flash.c...
|
||||
compiling stm32f4xx_hal_flash_ex.c...
|
||||
compiling stm32f4xx_hal_rcc_ex.c...
|
||||
compiling rs485_task.c...
|
||||
compiling stm32f4xx_hal_rcc.c...
|
||||
compiling net_task.c...
|
||||
compiling adc_task.c...
|
||||
compiling main.c...
|
||||
compiling stm32f4xx_hal_dma_ex.c...
|
||||
compiling stm32f4xx_hal_rcc.c...
|
||||
compiling stm32f4xx_hal_tim_ex.c...
|
||||
compiling freertos.c...
|
||||
compiling main.c...
|
||||
compiling stm32f4xx_hal_tim.c...
|
||||
compiling fdb.c...
|
||||
compiling fdb_utils.c...
|
||||
compiling fdb_tsdb.c...
|
||||
compiling fal.c...
|
||||
compiling fdb_kvdb.c...
|
||||
compiling fdb_tsdb.c...
|
||||
compiling fdb_utils.c...
|
||||
compiling fal.c...
|
||||
compiling fal_flash.c...
|
||||
compiling croutine.c...
|
||||
compiling fal_partition.c...
|
||||
compiling list.c...
|
||||
compiling croutine.c...
|
||||
compiling event_groups.c...
|
||||
compiling stream_buffer.c...
|
||||
compiling queue.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 list.c...
|
||||
compiling stm32f4xx_hal_dma.c...
|
||||
compiling system_stm32f4xx.c...
|
||||
compiling stm32f4xx_hal_i2c_ex.c...
|
||||
compiling stm32f4xx_hal_exti.c...
|
||||
compiling fal_flash_gd5f2gq5ue.c...
|
||||
compiling stm32f4xx_hal.c...
|
||||
compiling stm32f4xx_hal_pwr.c...
|
||||
compiling stm32f4xx_hal_pwr_ex.c...
|
||||
compiling stm32f4xx_hal_cortex.c...
|
||||
compiling rs485.c...
|
||||
compiling stm32f4xx_hal.c...
|
||||
compiling sd2506.c...
|
||||
compiling rs485.c...
|
||||
compiling stm32f4xx_hal_dma_ex.c...
|
||||
compiling fal_flash_gd5f2gq5ue.c...
|
||||
compiling tpafe5160.c...
|
||||
compiling net_select.c...
|
||||
compiling ch395f_test.c...
|
||||
compiling ch395f.c...
|
||||
compiling nand_ftl.c...
|
||||
compiling gd5f2gq5ue.c...
|
||||
compiling stm32f4xx_hal_spi.c...
|
||||
compiling net_socket.c...
|
||||
compiling ch395f_test.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 ff.c...
|
||||
compiling cmsis_os2.c...
|
||||
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...
|
||||
".\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
0
MDK-ARM/err.txt
Normal file
0
MDK-ARM/out.txt
Normal file
0
MDK-ARM/out.txt
Normal file
@@ -6,7 +6,29 @@ Dma.Request0=USART1_TX
|
||||
Dma.Request1=USART1_RX
|
||||
Dma.Request2=SPI2_RX
|
||||
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.FIFOMode=DMA_FIFOMODE_DISABLE
|
||||
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.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode
|
||||
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.configENABLE_FPU=1
|
||||
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.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.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_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.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
|
||||
@@ -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.PendSV_IRQn=true\:15\:0\:false\:false\:false\:true\:false\:false\:false
|
||||
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.SVCall_IRQn=true\:0\:0\:false\:false\:false\:false\:false\:false\:false
|
||||
NVIC.SavedPendsvIrqHandlerGenerated=true
|
||||
@@ -301,9 +327,10 @@ PCC.PartNumber=STM32F407ZGTx
|
||||
PCC.Series=STM32F4
|
||||
PCC.Temperature=25
|
||||
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_PuPd=GPIO_NOPULL
|
||||
PD0.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH
|
||||
PD0.Locked=true
|
||||
PD0.Signal=GPIO_Output
|
||||
PD1.GPIOParameters=GPIO_Label
|
||||
@@ -524,8 +551,8 @@ RCC.VCOOutputFreq_Value=336000000
|
||||
RCC.VcooutputI2S=192000000
|
||||
SH.GPXTI7.0=GPIO_EXTI7
|
||||
SH.GPXTI7.ConfNb=1
|
||||
SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_8
|
||||
SPI1.CalculateBaudRate=10.5 MBits/s
|
||||
SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_2
|
||||
SPI1.CalculateBaudRate=42.0 MBits/s
|
||||
SPI1.Direction=SPI_DIRECTION_2LINES
|
||||
SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler
|
||||
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.Signal=SYS_VS_tim7
|
||||
board=custom
|
||||
rtos.0.ip=FREERTOS
|
||||
|
||||
@@ -50,9 +50,15 @@ void MX_DMA_Init(void)
|
||||
/* DMA1_Stream4_IRQn interrupt configuration */
|
||||
HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 7, 0);
|
||||
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 */
|
||||
HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 9, 0);
|
||||
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 */
|
||||
HAL_NVIC_SetPriority(DMA2_Stream7_IRQn, 9, 0);
|
||||
HAL_NVIC_EnableIRQ(DMA2_Stream7_IRQn);
|
||||
|
||||
176
Src/freertos.c
176
Src/freertos.c
@@ -25,9 +25,14 @@
|
||||
|
||||
/* Private includes ----------------------------------------------------------*/
|
||||
/* USER CODE BEGIN Includes */
|
||||
#include <string.h>
|
||||
#include "net_task.h"
|
||||
#include "ch395f.h"
|
||||
#include "dbg_log.h"
|
||||
#include "net_socket.h"
|
||||
#include "usart.h"
|
||||
#include "ff.h"
|
||||
#include "flashdb.h"
|
||||
/* USER CODE END Includes */
|
||||
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
@@ -38,6 +43,29 @@
|
||||
/* Private define ------------------------------------------------------------*/
|
||||
/* 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 */
|
||||
|
||||
/* Private macro -------------------------------------------------------------*/
|
||||
@@ -47,7 +75,9 @@
|
||||
|
||||
/* Private 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 */
|
||||
/* Definitions for defaultTask */
|
||||
osThreadId_t defaultTaskHandle;
|
||||
@@ -60,9 +90,14 @@ const osThreadAttr_t defaultTask_attributes = {
|
||||
osThreadId_t netTaskHandle;
|
||||
const osThreadAttr_t netTask_attributes = {
|
||||
.name = "netTask",
|
||||
.stack_size = 1024 * 8,
|
||||
.stack_size = 1024 * 4,
|
||||
.priority = (osPriority_t) osPriorityNormal,
|
||||
};
|
||||
/* Definitions for netMsgQueue */
|
||||
osMessageQueueId_t netMsgQueueHandle;
|
||||
const osMessageQueueAttr_t netMsgQueue_attributes = {
|
||||
.name = "netMsgQueue"
|
||||
};
|
||||
|
||||
/* Private function prototypes -----------------------------------------------*/
|
||||
/* USER CODE BEGIN FunctionPrototypes */
|
||||
@@ -96,6 +131,10 @@ void MX_FREERTOS_Init(void) {
|
||||
/* start timers, add new ones, ... */
|
||||
/* 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 */
|
||||
/* add queues, ... */
|
||||
/* USER CODE END RTOS_QUEUES */
|
||||
@@ -127,10 +166,137 @@ void MX_FREERTOS_Init(void) {
|
||||
void StartDefaultTask(void *argument)
|
||||
{
|
||||
/* 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 */
|
||||
}
|
||||
|
||||
15
Src/gpio.c
15
Src/gpio.c
@@ -128,12 +128,12 @@ void MX_GPIO_Init(void)
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
|
||||
HAL_GPIO_Init(CH395_SCS_GPIO_Port, &GPIO_InitStruct);
|
||||
|
||||
/*Configure GPIO pin : ST_DIR4_Pin */
|
||||
GPIO_InitStruct.Pin = ST_DIR4_Pin;
|
||||
/*Configure GPIO pins : ST_DIR4_Pin TP_RD_Pin TP_CONVST_Pin */
|
||||
GPIO_InitStruct.Pin = ST_DIR4_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_LOW;
|
||||
HAL_GPIO_Init(ST_DIR4_GPIO_Port, &GPIO_InitStruct);
|
||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
|
||||
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
|
||||
|
||||
/*Configure GPIO pin : TP_FRSTDATA_Pin */
|
||||
GPIO_InitStruct.Pin = TP_FRSTDATA_Pin;
|
||||
@@ -141,13 +141,6 @@ void MX_GPIO_Init(void)
|
||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||
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 */
|
||||
GPIO_InitStruct.Pin = TP_BUSY_Pin;
|
||||
GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
|
||||
|
||||
36
Src/main.c
36
Src/main.c
@@ -37,6 +37,8 @@
|
||||
#include "net_socket.h"
|
||||
#include "net_select.h"
|
||||
#include "ch395f_test.h"
|
||||
#include "app_main.h"
|
||||
#include "sys_clock.h"
|
||||
/* USER CODE END Includes */
|
||||
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
@@ -118,6 +120,21 @@ int main(void)
|
||||
/* USER CODE BEGIN 2 */
|
||||
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 复位/配置/协议栈初始化/多连接模式) */
|
||||
DBG_INFO("net init: %s",
|
||||
(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",
|
||||
(tp_ret == TPAFE5160_OK) ? "OK" : "TIMEOUT");
|
||||
|
||||
/* SD2506 RTC 初始化测试 */
|
||||
int sd_ret = sd2506_init();
|
||||
DBG_INFO("SD2506 init: %s",
|
||||
(sd_ret == SD2506_OK) ? "OK" : "I2C ERROR");
|
||||
|
||||
if (sd_ret == SD2506_OK) {
|
||||
/* SD2506 RTC 诊断信息 */
|
||||
{
|
||||
/* 读取 ID */
|
||||
uint8_t sd_id[8];
|
||||
sd2506_get_id(sd_id);
|
||||
@@ -188,13 +201,6 @@ int main(void)
|
||||
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 测试 */
|
||||
{
|
||||
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 BEGIN 3 */
|
||||
/* 网络轮询已移到 FreeRTOS netTask 中 */
|
||||
;
|
||||
/* USER CODE END 3 */
|
||||
}
|
||||
/* USER CODE END 3 */
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
50
Src/spi.c
50
Src/spi.c
@@ -26,6 +26,8 @@
|
||||
|
||||
SPI_HandleTypeDef hspi1;
|
||||
SPI_HandleTypeDef hspi2;
|
||||
DMA_HandleTypeDef hdma_spi1_rx;
|
||||
DMA_HandleTypeDef hdma_spi1_tx;
|
||||
DMA_HandleTypeDef hdma_spi2_rx;
|
||||
DMA_HandleTypeDef hdma_spi2_tx;
|
||||
|
||||
@@ -47,7 +49,7 @@ void MX_SPI1_Init(void)
|
||||
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW;
|
||||
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE;
|
||||
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.TIMode = SPI_TIMODE_DISABLE;
|
||||
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
|
||||
@@ -119,6 +121,46 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
|
||||
GPIO_InitStruct.Alternate = GPIO_AF5_SPI1;
|
||||
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 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);
|
||||
|
||||
/* 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 END SPI1_MspDeInit 1 */
|
||||
|
||||
@@ -55,8 +55,11 @@
|
||||
/* USER CODE END 0 */
|
||||
|
||||
/* 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_tx;
|
||||
extern SPI_HandleTypeDef hspi1;
|
||||
extern SPI_HandleTypeDef hspi2;
|
||||
extern DMA_HandleTypeDef hdma_usart1_tx;
|
||||
extern DMA_HandleTypeDef hdma_usart1_rx;
|
||||
@@ -211,6 +214,20 @@ void EXTI9_5_IRQHandler(void)
|
||||
/* 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.
|
||||
*/
|
||||
@@ -309,6 +326,20 @@ void TIM7_IRQHandler(void)
|
||||
/* 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.
|
||||
*/
|
||||
@@ -323,6 +354,20 @@ void DMA2_Stream2_IRQHandler(void)
|
||||
/* 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.
|
||||
*/
|
||||
|
||||
@@ -4,15 +4,16 @@
|
||||
|
||||
1. [概述](#1-概述)
|
||||
2. [架构说明](#2-架构说明)
|
||||
3. [快速开始](#3-快速开始)
|
||||
4. [TCP 编程](#4-tcp-编程)
|
||||
5. [UDP 编程](#5-udp-编程)
|
||||
6. [I/O 多路复用](#6-io-多路复用)
|
||||
7. [事件回调机制](#7-事件回调机制)
|
||||
8. [字节序处理](#8-字节序处理)
|
||||
9. [API 参考](#9-api-参考)
|
||||
10. [完整示例](#10-完整示例)
|
||||
11. [常见问题](#11-常见问题)
|
||||
3. [线程安全设计](#3-线程安全设计)
|
||||
4. [快速开始](#4-快速开始)
|
||||
5. [TCP 编程](#5-tcp-编程)
|
||||
6. [UDP 编程](#6-udp-编程)
|
||||
7. [I/O 多路复用](#7-io-多路复用)
|
||||
8. [事件回调机制](#8-事件回调机制)
|
||||
9. [字节序处理](#9-字节序处理)
|
||||
10. [API 参考](#10-api-参考)
|
||||
11. [完整示例](#11-完整示例)
|
||||
12. [常见问题](#12-常见问题)
|
||||
|
||||
---
|
||||
|
||||
@@ -46,32 +47,42 @@
|
||||
|
||||
## 2. 架构说明
|
||||
|
||||
### 2.1 分层架构
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────┐
|
||||
│ 应用层 (App) │
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ net_socket.h (BSD Socket API) │ ← 用户接口
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ net_socket.c + net_select.c (状态机+select) │ ← 核心逻辑
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ ch395f.c/h (底层驱动) │ ← 硬件驱动
|
||||
├─────────────────────────────────────────────────────────┤
|
||||
│ CH395F 硬件 │
|
||||
└─────────────────────────────────────────────────────────┘
|
||||
┌──────────────────────────────────────────────────────────────┐
|
||||
│ 应用层任务 (多个 FreeRTOS Task) │
|
||||
│ net_send() / net_recv() / net_close() (线程安全封装) │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ 消息队列 (osMessageQueue) │
|
||||
│ 由 CubeMX 生成,在 MX_FREERTOS_Init() 中创建 │
|
||||
├──────────────────────────────────────────────────────────────┤
|
||||
│ netTask (网络任务线程) │
|
||||
│ net_process_messages() + net_poll() — 串行化执行 │
|
||||
│ 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_types.h` | 类型定义(地址结构、Socket 控制块等) |
|
||||
| `net_socket.h` | BSD Socket API 头文件 |
|
||||
| `net_socket.c` | API 核心实现 |
|
||||
| `net_socket.h` | BSD Socket API 头文件,含 `net_msg_t` 消息类型定义 |
|
||||
| `net_socket.c` | API 核心实现,含消息队列处理、线程安全封装、内部 `_sock()` 版本 |
|
||||
| `net_select.h` | select/poll API 头文件 |
|
||||
| `net_select.c` | select/poll 实现 |
|
||||
|
||||
### 2.1a 底层驱动改进(v2.1)
|
||||
### 2.3 底层驱动改进(v2.1)
|
||||
|
||||
ch395f.c/h 已完成以下优化:
|
||||
|
||||
@@ -84,7 +95,7 @@ ch395f.c/h 已完成以下优化:
|
||||
| 新增函数 | `ch395f_set_arp()` / `ch395f_set_ttl()` / `ch395f_get_unreach_info()` / `ch395f_poll_cmd_status()` |
|
||||
| KeepAlive | 参数单位为毫秒(非秒),必须为 500 的倍数 |
|
||||
|
||||
### 2.2 Socket 状态机
|
||||
### 2.4 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
|
||||
#include "net_socket.h"
|
||||
#include "net_select.h"
|
||||
```
|
||||
|
||||
### 3.2 初始化网络
|
||||
### 4.2 初始化网络
|
||||
|
||||
```c
|
||||
int main(void)
|
||||
@@ -154,9 +235,12 @@ int main(void)
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 主循环要求
|
||||
### 4.3 主循环要求
|
||||
|
||||
**重要**:`net_poll()` 必须在主循环中定期调用,它负责:
|
||||
**重要**:本项目使用 FreeRTOS 多任务架构,`net_poll()` 和 `net_process_messages()` 均在 `netTask`(FreeRTOS 线程)中定期调用,而非主循环。
|
||||
|
||||
- **主循环**:系统初始化完成后进入空循环,等待 `osKernelStart()` 启动调度器
|
||||
- **netTask**:在任务函数中持续运行,每 10ms 调用一次 `net_poll()` + `net_process_messages()`,负责:
|
||||
- 轮询 CH395F INT# 引脚电平(GPIO 轮询,无需 EXTI 中断)
|
||||
- 通过 `GET_GLOB_INT_STATUS_ALL`(2字节版)读取所有 8 个 Socket 的中断状态
|
||||
- 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT)
|
||||
@@ -164,19 +248,42 @@ int main(void)
|
||||
- 处理数据接收
|
||||
|
||||
```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 既做监听又做数据通信:
|
||||
|
||||
@@ -258,7 +365,7 @@ void tcp_server_single(void)
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 TCP Server(多连接)
|
||||
### 5.2 TCP Server(多连接)
|
||||
|
||||
多连接模式使用 1 个监听 Socket + N 个数据 Socket,支持并发连接:
|
||||
|
||||
@@ -348,7 +455,7 @@ void tcp_server_multi(void)
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 TCP Client
|
||||
### 5.3 TCP Client
|
||||
|
||||
```c
|
||||
#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
|
||||
#include "net_socket.h"
|
||||
@@ -472,7 +579,7 @@ void udp_client(void)
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 UDP Server
|
||||
### 6.2 UDP Server
|
||||
|
||||
```c
|
||||
#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 的读写事件:
|
||||
|
||||
@@ -578,7 +685,7 @@ void select_example(void)
|
||||
}
|
||||
```
|
||||
|
||||
### 6.2 poll 使用
|
||||
### 7.2 poll 使用
|
||||
|
||||
`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 事件通知:
|
||||
|
||||
### 7.1 定义回调函数
|
||||
### 8.1 定义回调函数
|
||||
|
||||
```c
|
||||
#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
|
||||
void event_callback_example(void)
|
||||
@@ -691,11 +798,11 @@ void event_callback_example(void)
|
||||
|
||||
---
|
||||
|
||||
## 8. 字节序处理
|
||||
## 9. 字节序处理
|
||||
|
||||
网络协议使用**大端序**(网络字节序),而 STM32 使用**小端序**(主机字节序)。发送和接收数据时需要进行字节序转换。
|
||||
|
||||
### 8.1 端口号转换
|
||||
### 9.1 端口号转换
|
||||
|
||||
```c
|
||||
uint16_t port = 8080;
|
||||
@@ -707,7 +814,7 @@ uint16_t net_port = net_htons(port);
|
||||
uint16_t host_port = net_ntohs(net_port);
|
||||
```
|
||||
|
||||
### 8.2 IP 地址转换
|
||||
### 9.2 IP 地址转换
|
||||
|
||||
```c
|
||||
/* 字符串转网络序 IP */
|
||||
@@ -718,7 +825,7 @@ char ip_str[16];
|
||||
net_inet_ntoa(ip, ip_str); /* ip_str = "192.168.1.100" */
|
||||
```
|
||||
|
||||
### 8.3 填充地址结构体
|
||||
### 9.3 填充地址结构体
|
||||
|
||||
```c
|
||||
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;
|
||||
```
|
||||
|
||||
### 8.4 读取地址信息
|
||||
### 9.4 读取地址信息
|
||||
|
||||
```c
|
||||
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 - 网络初始化
|
||||
|
||||
@@ -898,7 +1005,41 @@ int net_recvfrom(int sockfd, void *buf, int len, int flags,
|
||||
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 - 注册事件回调
|
||||
|
||||
@@ -918,7 +1059,7 @@ net_sock_t *net_get_sock(int sockfd);
|
||||
int net_get_errno(void);
|
||||
```
|
||||
|
||||
### 9.3 字节序转换
|
||||
### 10.4 字节序转换
|
||||
|
||||
```c
|
||||
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 -> 字符串 */
|
||||
```
|
||||
|
||||
### 9.4 select/poll
|
||||
### 10.5 select/poll
|
||||
|
||||
```c
|
||||
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);
|
||||
```
|
||||
|
||||
### 9.5 fd_set 操作宏
|
||||
### 10.6 fd_set 操作宏
|
||||
|
||||
```c
|
||||
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
|
||||
#include "net_socket.h"
|
||||
@@ -1047,7 +1188,7 @@ void tcp_echo_server(void)
|
||||
}
|
||||
```
|
||||
|
||||
### 10.2 TCP Client 自动重连
|
||||
### 11.2 TCP Client 自动重连
|
||||
|
||||
```c
|
||||
#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 的状态。如果不调用,连接状态不会更新,数据也无法接收。
|
||||
|
||||
建议至少每 10ms 调用一次。
|
||||
|
||||
### 11.2 Socket 数量有限制吗?
|
||||
### 12.2 Socket 数量有限制吗?
|
||||
|
||||
是的,CH395F 最多支持 8 个 Socket(索引 0~7)。其中:
|
||||
- TCP Server 多连接模式:1 个监听 Socket + 最多 7 个数据 Socket
|
||||
- TCP Client:最多 8 个
|
||||
- UDP:最多 8 个
|
||||
|
||||
### 11.3 TCP Server 多连接模式有什么要求?
|
||||
### 12.3 TCP Server 多连接模式有什么要求?
|
||||
|
||||
**模式工作原理:**
|
||||
- CH395F 多连接模式下,Socket 0 专职监听,Socket 1~7 由芯片自动分配
|
||||
@@ -1160,13 +1301,13 @@ void tcp_client_reconnect(void)
|
||||
**硬件要求:**
|
||||
- CH395F 固件版本 >= 0x44(支持 Socket 4~7)
|
||||
|
||||
### 11.4 发送数据有什么限制?
|
||||
### 12.4 发送数据有什么限制?
|
||||
|
||||
- 每次发送的数据长度不能超过 `NET_SEND_BUF_SIZE`(默认 4096 字节)
|
||||
- TCP 需要等待发送缓冲区空闲(`send_ready` 标志)
|
||||
- UDP 单次发送最大 1460 字节
|
||||
|
||||
### 11.5 如何处理连接断开?
|
||||
### 12.5 如何处理连接断开?
|
||||
|
||||
**方式一:阻塞接收**
|
||||
```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 |
|
||||
|------|--------|------|
|
||||
@@ -1207,7 +1348,7 @@ void callback(int sockfd, net_event_t event, void *arg)
|
||||
|
||||
两者功能相同,推荐使用 `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 | 发送缓冲区无空间 |
|
||||
| -11 | NET_ERR_BUSY | 操作未完成(BUSY) |
|
||||
|
||||
### 11.8 CH395F 命令执行状态码
|
||||
### 12.8 CH395F 命令执行状态码
|
||||
|
||||
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 | 命令中止 |
|
||||
| 0xFA | CH395F_ERR_UNKNOW | 未知错误 |
|
||||
|
||||
### 11.9 故障排查
|
||||
### 12.9 故障排查
|
||||
|
||||
**问题:TCP Client 连接超时,无 CONNECT 中断**
|
||||
- 检查多连接模式下数据 Socket(1~7)是否设置了 `SET_PROTO_TCP` + `SET_SOUR_PORT`(与监听端口相同)
|
||||
@@ -1266,3 +1407,21 @@ CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_po
|
||||
**问题:CH395F 初始化超时**
|
||||
- 芯片 INIT 需要约 200ms,轮询间隔应为 20ms(过短的查询可能干扰内部处理)
|
||||
- 超时值建议设为 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 内核已就绪。
|
||||
|
||||
611
docs/STM32F4-Base存储架构说明.md
Normal file
611
docs/STM32F4-Base存储架构说明.md
Normal file
@@ -0,0 +1,611 @@
|
||||
# STM32F4-Base 存储架构说明
|
||||
|
||||
## 1. 概述
|
||||
|
||||
本项目在 GD5F2GQ5UE SPI NAND Flash(256MB)上实现了四层存储软件栈:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 应用程序 (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~1023,FTL 管理 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 层可见,多任务读写同一文件需在应用层同步。
|
||||
Reference in New Issue
Block a user