解决一个擦除的bug

This commit is contained in:
2026-07-21 20:18:28 +08:00
parent f50b9c9e66
commit 66a1e8fd6a
25 changed files with 2005 additions and 61 deletions

View File

@@ -143,8 +143,15 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init()
- SPI Mode 0CPOL=0, CPHA=0时钟 42MHz - SPI Mode 0CPOL=0, CPHA=0时钟 42MHz
- 初始化必须按顺序:复位 → 读 ID → 使能 ECCB0h=10h→ 解除块保护A0h=00h - 初始化必须按顺序:复位 → 读 ID → 使能 ECCB0h=10h→ 解除块保护A0h=00h
- SET_FEATURE 命令前必须先发写使能06h - SET_FEATURE 命令前必须先发写使能06h
- 块擦除D8h参数是字节地址(块编号 × 128KB不是块编号 - 块擦除D8h参数是块首页地址(块编号 × 64不是字节地址旧版错误
- 读取 ID9Fh返回 3 字节,第 0 字节无意义,第 1 字节 MID第 2 字节 DID - 读取 ID9Fh返回 3 字节,第 0 字节无意义,第 1 字节 MID第 2 字节 DID
- **BBT 扫描必须在 ECC 禁用时完成**`gd5f_bbt_scan()`ECC 使能后 reading spare area 会被 ECC 引擎干扰
- **运行时坏块标记只来源于真实擦除/编程失败**,不要在 ECC 使能后调用 `gd5f2gq5ue_bbt_rescan()` 重建 BBT
- **BBT 厂标坏块不可清除**`gd5f_bbt_scan()` 在 ECC 禁用时扫到的 786 个坏块是真实厂标标记(保守标记,最坏条件)。验证擦除+编程通过不代表不是坏块。初始 BBT 必须保留,只追加运行时损坏块
- **Block 0 有特殊行为**"Factory good block0" / "Power on Read"。大量相邻块操作后 block 0 数据可能被擦除,避免将其用于跨大批量操作的持久化存储
- **ECC 校验码生成不可靠**`PROGRAM LOAD 02h + PROGRAM EXEC 10h` 在 ECC 开启时可能不生成有效 ECC 校验码ECCS=2。应用层应使用独立数据校验CRC/Checksum不可依赖 chip 内部 ECC 状态位
- **写验证**:以擦除/编程状态位E_FAIL/P_FAIL为准不要回读数据确认ECCS 不可靠block 0 不可靠)
- 详细陷阱记录见 `docs/GD5F2GQ5UE_Trap_Records.md`
## Flash 分区规划256MB GD5F2GQ5UE ## Flash 分区规划256MB GD5F2GQ5UE
@@ -210,9 +217,9 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init()
## 陷阱记录 ## 陷阱记录
详细陷阱记录(含根因分析、解决方案、发现时间)见 `docs/CH395F_Trap_Records.md` 详细陷阱记录(含根因分析、解决方案、发现时间)见 `docs/CH395F_Trap_Records.md``docs/GD5F2GQ5UE_Trap_Records.md`
当前已记录陷阱: 当前已记录陷阱CH395F
- **Trap 01** Socket 4~7 自动分配不到(初始化顺序) - **Trap 01** Socket 4~7 自动分配不到(初始化顺序)
- **Trap 02** 数据 Socket 缓冲区重叠 - **Trap 02** 数据 Socket 缓冲区重叠
- **Trap 03** DHCP 包 xid 偏移错误 - **Trap 03** DHCP 包 xid 偏移错误
@@ -221,3 +228,6 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init()
- **Trap 06** TCP KeepAlive 参数非 500ms 倍数 - **Trap 06** TCP KeepAlive 参数非 500ms 倍数
- **Trap 07** TCP 关闭误用 disconnect 导致 FIN_WAIT_2 - **Trap 07** TCP 关闭误用 disconnect 导致 FIN_WAIT_2
- **Trap 08** RECV 中断电平触发无限循环 - **Trap 08** RECV 中断电平触发无限循环
当前已记录陷阱GD5F2GQ5UE
- **Trap 01** 块擦除地址错误(`byte_addr → page_addr`),导致先前所有实验结论无效。详见 `docs/GD5F2GQ5UE_Trap_Records.md`

View File

@@ -293,22 +293,24 @@ static int gd5f_set_feature(uint8_t addr, uint8_t data) {
} }
/* /*
* 函数功能:块擦除(擦<EFBFBD>?28KB块 * 函数功能:块擦除(擦除128KB块
* 入口参数block_addr - 块编<EFBFBD>? uint32_t 0 - 2047 * 入口参数block_addr - 块编 uint32_t 0 - 2047
* 返回值0 - 成功,其<EFBFBD>?- 错误<EFBFBD>? * 限定条件gd5f2gq5ue_init() 已调<E5B7B2>? * 函数说明<E8AFB4>?. 写使<E58699>?- D8h + 3字节字节地址 - 等待完成 * 返回值0 - 成功,其- 错误
* 2. 擦除完成后检<E5908E>?E_FAIL <20>? */ * 限定条件gd5f2gq5ue_init() 已调用
* 函数说明1. 写使能 - D8h + 3字节页地址 - 等待完成
* 2. 擦除完成后检查 E_FAIL */
int gd5f_block_erase(uint32_t block_addr) { int gd5f_block_erase(uint32_t block_addr) {
int ret = GD5F_OK; int ret = GD5F_OK;
uint8_t cmd[4]; uint8_t cmd[4];
uint8_t status = 0; uint8_t status = 0;
uint32_t byte_addr = block_addr * GD5F_BLOCK_SIZE; uint32_t page_addr = block_addr * GD5F_PAGES_PER_BLOCK;
gd5f_write_enable(); gd5f_write_enable();
cmd[0] = GD5F_CMD_BLOCK_ERASE; cmd[0] = GD5F_CMD_BLOCK_ERASE;
cmd[1] = (byte_addr >> 16) & 0xFF; cmd[1] = (page_addr >> 16) & 0xFF;
cmd[2] = (byte_addr >> 8) & 0xFF; cmd[2] = (page_addr >> 8) & 0xFF;
cmd[3] = byte_addr & 0xFF; cmd[3] = page_addr & 0xFF;
GD5F_CS_LOW(); GD5F_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT); HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
@@ -358,6 +360,42 @@ static int gd5f_bbt_scan(void) {
return GD5F_OK; return GD5F_OK;
} }
void gd5f2gq5ue_bbt_rescan(void) {
uint32_t blk;
uint8_t spare[64];
memset(s_bbt, 0, GD5F_BBT_SIZE);
for (blk = 0; blk < GD5F_TOTAL_BLOCKS; blk++) {
uint32_t page = (blk << 6) | 63;
gd5f_page_read(page);
gd5f_read_from_cache(GD5F_PAGE_SIZE, spare, sizeof(spare));
if (spare[0] != 0xFF) {
s_bbt[blk >> 3] |= (1 << (blk & 7));
}
}
}
void gd5f2gq5ue_bbt_clear(void) {
memset(s_bbt, 0, GD5F_BBT_SIZE);
}
void gd5f2gq5ue_print_bbt(void) {
uint32_t bad_count = 0;
for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS; b++) {
if ((s_bbt[b >> 3] >> (b & 7)) & 1) bad_count++;
}
DBG_INFO("BBT: %lu/%lu blocks bad", bad_count, GD5F_TOTAL_BLOCKS);
if (bad_count > 0) {
uint32_t shown = 0;
for (uint32_t b = 0; b < GD5F_TOTAL_BLOCKS && shown < 32; b++) {
if ((s_bbt[b >> 3] >> (b & 7)) & 1) {
DBG_INFO(" block %lu (offset 0x%08lX)", b, b * GD5F_BLOCK_SIZE);
shown++;
}
}
if (bad_count > 32) DBG_INFO(" ... and %lu more", bad_count - 32);
}
}
/* ======================== 公共函数定义 ======================== */ /* ======================== 公共函数定义 ======================== */
/* /*

View File

@@ -161,6 +161,9 @@ int gd5f2gq5ue_is_block_bad(uint32_t block);
* 函数说明:更新 BBT并尝试物理标记该块最后一页 spare byte 0 为 0x00 * 函数说明:更新 BBT并尝试物理标记该块最后一页 spare byte 0 为 0x00
*/ */
void gd5f2gq5ue_mark_block_bad(uint32_t block); void gd5f2gq5ue_mark_block_bad(uint32_t block);
void gd5f2gq5ue_bbt_clear(void);
void gd5f2gq5ue_bbt_rescan(void);
void gd5f2gq5ue_print_bbt(void);
/* ==================== SPI 原语FTL 共享) ==================== */ /* ==================== SPI 原语FTL 共享) ==================== */

View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Jason von Nieda <jason@vonnieda.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,626 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <stdbool.h>
#include <ctype.h>
#include "lftpd.h"
#include "private/lftpd_status.h"
#include "private/lftpd_inet.h"
#include "private/lftpd_log.h"
#include "private/lftpd_string.h"
#include "private/lftpd_io.h"
#include "net_socket.h"
#include "net_config.h"
#include "ff.h"
typedef struct {
const char *command;
int (*handler)(lftpd_client_t *client, const char *arg);
} command_t;
static int cmd_cwd(lftpd_client_t *client, const char *arg);
static int cmd_dele(lftpd_client_t *client, const char *arg);
static int cmd_epsv(lftpd_client_t *client, const char *arg);
static int cmd_feat(lftpd_client_t *client, const char *arg);
static int cmd_list(lftpd_client_t *client, const char *arg);
static int cmd_nlst(lftpd_client_t *client, const char *arg);
static int cmd_noop(lftpd_client_t *client, const char *arg);
static int cmd_pass(lftpd_client_t *client, const char *arg);
static int cmd_pasv(lftpd_client_t *client, const char *arg);
static int cmd_pwd(lftpd_client_t *client, const char *arg);
static int cmd_quit(lftpd_client_t *client, const char *arg);
static int cmd_retr(lftpd_client_t *client, const char *arg);
static int cmd_size(lftpd_client_t *client, const char *arg);
static int cmd_stor(lftpd_client_t *client, const char *arg);
static int cmd_syst(lftpd_client_t *client, const char *arg);
static int cmd_type(lftpd_client_t *client, const char *arg);
static int cmd_user(lftpd_client_t *client, const char *arg);
static command_t commands[] = {
{ "CWD", cmd_cwd },
{ "DELE", cmd_dele },
{ "EPSV", cmd_epsv },
{ "FEAT", cmd_feat },
{ "LIST", cmd_list },
{ "NLST", cmd_nlst },
{ "NOOP", cmd_noop },
{ "PASS", cmd_pass },
{ "PASV", cmd_pasv },
{ "PWD", cmd_pwd },
{ "QUIT", cmd_quit },
{ "RETR", cmd_retr },
{ "SIZE", cmd_size },
{ "STOR", cmd_stor },
{ "SYST", cmd_syst },
{ "TYPE", cmd_type },
{ "USER", cmd_user },
{ NULL, NULL },
};
static int send_response(int socket, int code, bool include_code,
bool multiline_start, const char *format, ...)
{
va_list args;
char message[256];
char response[512];
va_start(args, format);
vsnprintf(message, sizeof(message), format, args);
va_end(args);
if (include_code) {
if (multiline_start) {
snprintf(response, sizeof(response), "%d-%s%s", code, message, CRLF);
} else {
snprintf(response, sizeof(response), "%d %s%s", code, message, CRLF);
}
} else {
snprintf(response, sizeof(response), "%s%s", message, CRLF);
}
return lftpd_inet_write_string(socket, response);
}
#define send_simple_response(socket, code, format, ...) \
send_response(socket, code, true, false, format, ##__VA_ARGS__)
#define send_multiline_response_begin(socket, code, format, ...) \
send_response(socket, code, true, true, format, ##__VA_ARGS__)
#define send_multiline_response_line(socket, format, ...) \
send_response(socket, 0, false, false, format, ##__VA_ARGS__)
#define send_multiline_response_end(socket, code, format, ...) \
send_response(socket, code, true, false, format, ##__VA_ARGS__)
static int send_list(int socket, const char *path)
{
static const char *directory_format = "drw-rw-rw- 1 owner group %13lu Jan 01 1970 %s";
static const char *file_format = "-rw-rw-rw- 1 owner group %13lu Jan 01 1970 %s";
void *dp = lftpd_io_opendir(path);
if (dp == NULL) {
return -1;
}
char name[256];
uint32_t size;
int is_dir;
while (lftpd_io_readdir(dp, name, sizeof(name), &size, &is_dir) == 0) {
if (is_dir) {
send_multiline_response_line(socket, directory_format,
(unsigned long)size, name);
} else {
send_multiline_response_line(socket, file_format,
(unsigned long)size, name);
}
}
lftpd_io_closedir(dp);
return 0;
}
static int send_nlst(int socket, const char *path)
{
void *dp = lftpd_io_opendir(path);
if (dp == NULL) {
return -1;
}
char name[256];
uint32_t size;
int is_dir;
while (lftpd_io_readdir(dp, name, sizeof(name), &size, &is_dir) == 0) {
if (!is_dir) {
send_multiline_response_line(socket, name);
}
}
lftpd_io_closedir(dp);
return 0;
}
static int send_file(int socket, const char *path)
{
int fh = lftpd_io_open_read(path);
if (fh < 0) {
lftpd_log_error("failed to open file for read");
return -1;
}
unsigned char buffer[1024];
int read_len;
while ((read_len = lftpd_io_read(fh, buffer, sizeof(buffer))) > 0) {
unsigned char *p = buffer;
while (read_len > 0) {
int write_len = lftpd_inet_write(socket, p, read_len);
if (write_len < 0) {
lftpd_log_error("write error");
lftpd_io_close(fh);
return -1;
}
p += write_len;
read_len -= write_len;
}
}
lftpd_io_close(fh);
return 0;
}
static int receive_file(int socket, const char *path)
{
int fh = lftpd_io_open_write(path);
if (fh < 0) {
lftpd_log_error("failed to open file for write");
return -1;
}
unsigned char buffer[1024];
int err;
while ((err = lftpd_inet_read(socket, buffer, sizeof(buffer))) > 0) {
if (lftpd_io_write(fh, buffer, err) != err) {
err = -1;
break;
}
}
lftpd_io_close(fh);
if (err < 0) {
return err;
}
return 0;
}
static int cmd_cwd(lftpd_client_t *client, const char *arg)
{
if (arg == NULL || strlen(arg) == 0) {
send_simple_response(client->socket, 550, STATUS_550);
return -1;
}
char *path = lftpd_io_canonicalize_path(client->directory, arg);
uint32_t size;
int is_dir;
if (lftpd_io_stat(path, &size, &is_dir) != 0) {
send_simple_response(client->socket, 550, STATUS_550);
free(path);
return -1;
}
if (!is_dir) {
send_simple_response(client->socket, 550, STATUS_550);
free(path);
return -1;
}
strncpy(client->directory, path, sizeof(client->directory) - 1);
client->directory[sizeof(client->directory) - 1] = '\0';
free(path);
send_simple_response(client->socket, 250, STATUS_250);
return 0;
}
static int cmd_dele(lftpd_client_t *client, const char *arg)
{
if (arg == NULL || strlen(arg) == 0) {
send_simple_response(client->socket, 550, STATUS_550);
return -1;
}
char *path = lftpd_io_canonicalize_path(client->directory, arg);
uint32_t size;
int is_dir;
if (lftpd_io_stat(path, &size, &is_dir) != 0) {
send_simple_response(client->socket, 550, STATUS_550);
free(path);
return -1;
}
if (is_dir) {
send_simple_response(client->socket, 550, STATUS_550);
free(path);
return -1;
}
lftpd_io_unlink(path);
free(path);
send_simple_response(client->socket, 250, STATUS_250);
return 0;
}
static int cmd_epsv(lftpd_client_t *client, const char *arg)
{
int listener_socket = lftpd_inet_listen(0);
if (listener_socket < 0) {
send_simple_response(client->socket, 425, STATUS_425);
return -1;
}
int port = lftpd_inet_get_socket_port(listener_socket);
send_simple_response(client->socket, 229, STATUS_229, port);
lftpd_log_debug("waiting for data port connection on port %d...", port);
int data_sock = lftpd_inet_accept(listener_socket);
if (data_sock < 0) {
lftpd_log_error("error accepting client socket");
lftpd_inet_close(listener_socket);
return -1;
}
lftpd_log_debug("data port connection received...");
client->data_socket = data_sock;
return 0;
}
static int cmd_feat(lftpd_client_t *client, const char *arg)
{
send_multiline_response_begin(client->socket, 211, STATUS_211);
send_multiline_response_line(client->socket, "EPSV");
send_multiline_response_line(client->socket, "PASV");
send_multiline_response_line(client->socket, "SIZE");
send_multiline_response_line(client->socket, "NLST");
send_multiline_response_end(client->socket, 211, STATUS_211);
return 0;
}
static int cmd_list(lftpd_client_t *client, const char *arg)
{
if (client->data_socket == -1) {
send_simple_response(client->socket, 425, STATUS_425);
return -1;
}
send_simple_response(client->socket, 150, STATUS_150);
int err = send_list(client->data_socket, client->directory);
lftpd_inet_close(client->data_socket);
client->data_socket = -1;
if (err == 0) {
send_simple_response(client->socket, 226, STATUS_226);
} else {
send_simple_response(client->socket, 550, STATUS_550);
}
return 0;
}
static int cmd_nlst(lftpd_client_t *client, const char *arg)
{
if (client->data_socket == -1) {
send_simple_response(client->socket, 425, STATUS_425);
return -1;
}
send_simple_response(client->socket, 150, STATUS_150);
int err = send_nlst(client->data_socket, client->directory);
lftpd_inet_close(client->data_socket);
client->data_socket = -1;
if (err == 0) {
send_simple_response(client->socket, 226, STATUS_226);
} else {
send_simple_response(client->socket, 550, STATUS_550);
}
return 0;
}
static int cmd_noop(lftpd_client_t *client, const char *arg)
{
send_simple_response(client->socket, 200, STATUS_200);
return 0;
}
static int cmd_pass(lftpd_client_t *client, const char *arg)
{
send_simple_response(client->socket, 230, STATUS_230);
return 0;
}
static int cmd_pasv(lftpd_client_t *client, const char *arg)
{
int listener_socket = lftpd_inet_listen(0);
if (listener_socket < 0) {
send_simple_response(client->socket, 425, STATUS_425);
return -1;
}
int port = lftpd_inet_get_socket_port(listener_socket);
{
struct net_sockaddr_in local_addr;
int addrlen = sizeof(local_addr);
int err = net_getsockname(client->socket,
(struct net_sockaddr *)&local_addr, &addrlen);
if (err != 0) {
lftpd_log_error("error getting client IP info");
send_simple_response(client->socket, 425, STATUS_425);
lftpd_inet_close(listener_socket);
return -1;
}
uint32_t ip = net_htonl(local_addr.sin_addr.s_addr);
send_simple_response(client->socket, 227, STATUS_227,
(ip >> 24) & 0xff,
(ip >> 16) & 0xff,
(ip >> 8) & 0xff,
(ip >> 0) & 0xff,
(port >> 8) & 0xff, (port >> 0) & 0xff);
}
lftpd_log_debug("waiting for data port connection on port %d...", port);
int data_sock = lftpd_inet_accept(listener_socket);
if (data_sock < 0) {
lftpd_log_error("error accepting client socket");
lftpd_inet_close(listener_socket);
return -1;
}
lftpd_log_debug("data port connection received...");
client->data_socket = data_sock;
return 0;
}
static int cmd_pwd(lftpd_client_t *client, const char *arg)
{
send_simple_response(client->socket, 257, "\"%s\"", client->directory);
return 0;
}
static int cmd_quit(lftpd_client_t *client, const char *arg)
{
send_simple_response(client->socket, 221, STATUS_221);
return -1;
}
static int cmd_retr(lftpd_client_t *client, const char *arg)
{
if (client->data_socket == -1) {
send_simple_response(client->socket, 425, STATUS_425);
return -1;
}
send_simple_response(client->socket, 150, STATUS_150);
char *path = lftpd_io_canonicalize_path(client->directory, arg);
lftpd_log_debug("send '%s'", path);
int err = send_file(client->data_socket, path);
free(path);
lftpd_inet_close(client->data_socket);
client->data_socket = -1;
if (err == 0) {
send_simple_response(client->socket, 226, STATUS_226);
} else {
send_simple_response(client->socket, 450, STATUS_450);
}
return 0;
}
static int cmd_size(lftpd_client_t *client, const char *arg)
{
if (arg == NULL) {
send_simple_response(client->socket, 550, STATUS_550);
return 0;
}
char *path = lftpd_io_canonicalize_path(client->directory, arg);
lftpd_log_debug("size %s", path);
uint32_t size;
int is_dir;
if (lftpd_io_stat(path, &size, &is_dir) == 0 && !is_dir) {
send_simple_response(client->socket, 213, "%lu", (unsigned long)size);
} else {
send_simple_response(client->socket, 550, STATUS_550);
}
free(path);
return 0;
}
static int cmd_stor(lftpd_client_t *client, const char *arg)
{
if (client->data_socket == -1) {
send_simple_response(client->socket, 425, STATUS_425);
return -1;
}
send_simple_response(client->socket, 150, STATUS_150);
char *path = lftpd_io_canonicalize_path(client->directory, arg);
lftpd_log_debug("receive '%s'", path);
int err = receive_file(client->data_socket, path);
free(path);
lftpd_inet_close(client->data_socket);
client->data_socket = -1;
if (err == 0) {
send_simple_response(client->socket, 226, STATUS_226);
} else {
send_simple_response(client->socket, 450, STATUS_450);
}
return 0;
}
static int cmd_syst(lftpd_client_t *client, const char *arg)
{
send_simple_response(client->socket, 215, "UNIX Type: L8");
return 0;
}
static int cmd_type(lftpd_client_t *client, const char *arg)
{
send_simple_response(client->socket, 200, STATUS_200);
return 0;
}
static int cmd_user(lftpd_client_t *client, const char *arg)
{
send_simple_response(client->socket, 230, STATUS_230);
return 0;
}
static int handle_control_channel(lftpd_client_t *client)
{
int err = send_simple_response(client->socket, 220, STATUS_220);
if (err != 0) {
lftpd_log_error("error sending welcome message");
goto cleanup;
}
char read_buffer[512];
while (err == 0) {
int line_len = lftpd_inet_read_line(client->socket, read_buffer,
sizeof(read_buffer));
if (line_len != 0) {
lftpd_log_error("error reading next command");
goto cleanup;
}
int index;
char *p = strchr(read_buffer, ' ');
if (p != NULL) {
index = (int)(p - read_buffer);
} else {
index = (int)strlen(read_buffer);
}
if (index >= 5) {
err = send_simple_response(client->socket, 500, STATUS_500);
continue;
}
char command_tmp[4 + 1];
memset(command_tmp, 0, sizeof(command_tmp));
memcpy(command_tmp, read_buffer, (size_t)index);
for (int i = 0; command_tmp[i]; i++) {
command_tmp[i] = (char)toupper((int)command_tmp[i]);
}
bool matched = false;
for (int i = 0; commands[i].command; i++) {
if (strcmp(commands[i].command, command_tmp) == 0) {
char arg_buf[512];
const char *arg = NULL;
if (index < (int)strlen(read_buffer)) {
strncpy(arg_buf, read_buffer + index + 1, sizeof(arg_buf) - 1);
arg_buf[sizeof(arg_buf) - 1] = '\0';
arg = lftpd_string_trim(arg_buf);
}
err = commands[i].handler(client, arg);
matched = true;
break;
}
}
if (!matched) {
send_simple_response(client->socket, 502, STATUS_502);
}
}
cleanup:
lftpd_inet_close(client->socket);
return 0;
}
int lftpd_start(const char *directory, int port, lftpd_t *lftpd)
{
memset(lftpd, 0, sizeof(lftpd_t));
lftpd->directory = directory;
lftpd->port = port;
while (true) {
lftpd->server_socket = lftpd_inet_listen(port);
if (lftpd->server_socket < 0) {
lftpd_log_error("error creating listener");
vTaskDelay(pdMS_TO_TICKS(3000));
continue;
}
lftpd_log_info("waiting for connection...");
int client_socket = lftpd_inet_accept(lftpd->server_socket);
if (client_socket < 0) {
lftpd_log_error("error accepting client socket");
lftpd_inet_close(lftpd->server_socket);
break;
}
{
char ip_str[16];
struct net_sockaddr_in addr;
int addrlen = sizeof(addr);
if (net_getsockname(client_socket,
(struct net_sockaddr *)&addr, &addrlen) == 0) {
net_inet_ntoa(addr.sin_addr.s_addr, ip_str);
int remote_port = lftpd_inet_get_socket_port(client_socket);
lftpd_log_info("connection received from %s:%d...",
ip_str, remote_port);
} else {
lftpd_log_info("connection received...");
}
}
lftpd_client_t client;
memset(&client, 0, sizeof(client));
strncpy(client.directory, directory, sizeof(client.directory) - 1);
client.directory[sizeof(client.directory) - 1] = '\0';
client.socket = client_socket;
client.data_socket = -1;
lftpd->client = &client;
handle_control_channel(&client);
lftpd->client = NULL;
/* standalone mode: socket was closed in handle_control_channel,
* loop back to create a new listener */
}
return 0;
}
int lftpd_stop(lftpd_t *lftpd)
{
if (lftpd->server_socket >= 0) {
lftpd_inet_close(lftpd->server_socket);
}
if (lftpd->client) {
lftpd_inet_close(lftpd->client->socket);
}
return 0;
}

View File

@@ -0,0 +1,19 @@
#pragma once
#include <stdint.h>
typedef struct {
char directory[256];
int socket;
int data_socket;
} lftpd_client_t;
typedef struct {
const char *directory;
int port;
int server_socket;
lftpd_client_t *client;
} lftpd_t;
int lftpd_start(const char *directory, int port, lftpd_t *lftpd);
int lftpd_stop(lftpd_t *lftpd);

View File

@@ -0,0 +1,161 @@
#define DBG_TAG "[LFT_INET]"
#include "private/lftpd_inet.h"
#include "dbg_log.h"
#include "net_socket.h"
#include <string.h>
int lftpd_inet_listen(int port)
{
int s = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
if (s < 0) {
DBG_ERROR("error creating listener socket");
return -1;
}
struct net_sockaddr_in addr;
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons((uint16_t)port);
addr.sin_addr.s_addr = 0;
if (net_bind(s, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) {
DBG_ERROR("error binding listener port %d", port);
net_close(s);
return -1;
}
if (net_listen(s, 0) < 0) {
DBG_ERROR("error listening on socket");
net_close(s);
return -1;
}
return s;
}
int lftpd_inet_listen_multi(int port, int backlog)
{
int s = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0);
if (s < 0) {
DBG_ERROR("error creating multi listener socket");
return -1;
}
struct net_sockaddr_in addr;
addr.sin_family = NET_AF_INET;
addr.sin_port = net_htons((uint16_t)port);
addr.sin_addr.s_addr = 0;
if (net_bind(s, (struct net_sockaddr *)&addr, sizeof(addr)) < 0) {
DBG_ERROR("error binding multi listener port %d", port);
net_close(s);
return -1;
}
if (net_listen(s, backlog) < 0) {
DBG_ERROR("error listening on multi socket");
net_close(s);
return -1;
}
return s;
}
int lftpd_inet_get_socket_port(int socket)
{
struct net_sockaddr_in addr;
int addrlen = sizeof(addr);
if (net_getsockname(socket, (struct net_sockaddr *)&addr, &addrlen) < 0) {
DBG_ERROR("error getting socket port number");
return -1;
}
return (int)net_ntohs(addr.sin_port);
}
int lftpd_inet_accept(int socket)
{
while (1) {
int s = net_accept(socket, NULL, NULL);
if (s >= 0) {
return s;
}
vTaskDelay(pdMS_TO_TICKS(10));
}
}
int lftpd_inet_read_line(int socket, char *buffer, size_t buffer_len)
{
memset(buffer, 0, buffer_len);
int total_read_len = 0;
while (total_read_len < (int)buffer_len) {
int read_len = net_recv(socket,
buffer + total_read_len,
buffer_len - total_read_len - 1,
0);
if (read_len == 0) {
return -1;
}
if (read_len < 0) {
return read_len;
}
total_read_len += read_len;
char *p = strstr(buffer, "\r\n");
if (p != NULL) {
*p = '\0';
return 0;
}
}
return -1;
}
int lftpd_inet_write_string(int socket, const char *message)
{
const char *p = message;
int length = (int)strlen(message);
while (length > 0) {
int write_len = net_send(socket, p, length, 0);
if (write_len < 0) {
DBG_ERROR("write error");
return write_len;
}
p += write_len;
length -= write_len;
}
return 0;
}
int lftpd_inet_write(int socket, const void *buf, int len)
{
const char *p = (const char *)buf;
int remaining = len;
while (remaining > 0) {
int n = net_send(socket, p, remaining, 0);
if (n < 0) {
return n;
}
p += n;
remaining -= n;
}
return len;
}
int lftpd_inet_read(int socket, void *buf, int len)
{
int n = net_recv(socket, buf, len, 0);
return n;
}
int lftpd_inet_close(int socket)
{
return net_close(socket);
}

View File

@@ -0,0 +1,204 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "ff.h"
#include "private/lftpd_io.h"
static int s_file_open = 0;
static FIL s_ftp_file;
char *lftpd_io_canonicalize_path(const char *base, const char *name)
{
if (base == NULL) {
base = "";
}
if (name == NULL) {
name = "";
}
char *path;
if (name[0] == '/') {
path = (char *)malloc(strlen(name) + 1);
if (path == NULL) return NULL;
strcpy(path, name);
} else {
size_t len = strlen(base) + 1 + strlen(name) + 1;
path = (char *)malloc(len);
if (path == NULL) return NULL;
snprintf(path, len, "%s/%s", base, name);
}
char *abs_path = (char *)malloc(strlen(path) + 2);
if (abs_path == NULL) {
free(path);
return NULL;
}
abs_path[0] = '\0';
char *p = path;
while (*p != '\0') {
while (*p == '/') p++;
if (*p == '\0') break;
char *seg_start = p;
while (*p != '\0' && *p != '/') p++;
char saved = *p;
*p = '\0';
if (strcmp(seg_start, ".") == 0) {
/* ignore */
} else if (strcmp(seg_start, "..") == 0) {
char *slash = strrchr(abs_path, '/');
if (slash != NULL) {
*slash = '\0';
}
} else {
strcat(abs_path, "/");
strcat(abs_path, seg_start);
}
*p = saved;
}
free(path);
if (strlen(abs_path) == 0) {
strcpy(abs_path, "/");
}
return abs_path;
}
int lftpd_io_open_read(const char *path)
{
if (s_file_open) {
return -1;
}
if (f_open(&s_ftp_file, path, FA_READ) != FR_OK) {
return -1;
}
s_file_open = 1;
return 0;
}
int lftpd_io_open_write(const char *path)
{
if (s_file_open) {
return -1;
}
if (f_open(&s_ftp_file, path, FA_WRITE | FA_CREATE_ALWAYS) != FR_OK) {
return -1;
}
s_file_open = 1;
return 0;
}
int lftpd_io_read(int handle, void *buf, int len)
{
(void)handle;
UINT br;
if (f_read(&s_ftp_file, buf, (UINT)len, &br) != FR_OK) {
return -1;
}
return (int)br;
}
int lftpd_io_write(int handle, const void *buf, int len)
{
(void)handle;
UINT bw;
if (f_write(&s_ftp_file, buf, (UINT)len, &bw) != FR_OK) {
return -1;
}
if ((int)bw != len) {
return -1;
}
return (int)bw;
}
void lftpd_io_close(int handle)
{
(void)handle;
if (s_file_open) {
f_close(&s_ftp_file);
s_file_open = 0;
}
}
int lftpd_io_stat(const char *path, uint32_t *p_size, int *p_is_dir)
{
FILINFO fno;
if (f_stat(path, &fno) != FR_OK) {
return -1;
}
if (p_size != NULL) {
*p_size = (uint32_t)fno.fsize;
}
if (p_is_dir != NULL) {
*p_is_dir = (fno.fattrib & AM_DIR) ? 1 : 0;
}
return 0;
}
int lftpd_io_unlink(const char *path)
{
if (f_unlink(path) == FR_OK) {
return 0;
}
return -1;
}
void *lftpd_io_opendir(const char *path)
{
DIR *dp = (DIR *)malloc(sizeof(DIR));
if (dp == NULL) {
return NULL;
}
if (f_opendir(dp, path) != FR_OK) {
free(dp);
return NULL;
}
return (void *)dp;
}
int lftpd_io_readdir(void *dp, char *name, int name_max, uint32_t *p_size, int *p_is_dir)
{
FILINFO fno;
FRESULT fr;
while (1) {
fr = f_readdir((DIR *)dp, &fno);
if (fr != FR_OK || fno.fname[0] == 0) {
return -1;
}
if (fno.fname[0] == '.') {
continue;
}
break;
}
strncpy(name, fno.fname, (size_t)(name_max - 1));
name[name_max - 1] = '\0';
if (p_size != NULL) {
*p_size = (uint32_t)fno.fsize;
}
if (p_is_dir != NULL) {
*p_is_dir = (fno.fattrib & AM_DIR) ? 1 : 0;
}
return 0;
}
void lftpd_io_closedir(void *dp)
{
if (dp != NULL) {
f_closedir((DIR *)dp);
free(dp);
}
}

View File

@@ -0,0 +1,18 @@
#include "private/lftpd_log.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
void lftpd_log_internal(const char* level, const char* format, ...) {
char buffer[256];
va_list args;
va_start(args, format);
int err = vsnprintf(buffer, sizeof(buffer), format, args);
va_end(args);
if (err >= sizeof(buffer)) {
return;
}
printf("%s %s\n", level, buffer);
}

View File

@@ -0,0 +1,16 @@
#include <string.h>
#include <ctype.h>
#include "private/lftpd_string.h"
char *lftpd_string_trim(char *s)
{
char *p = s;
for (int i = 0, len = (int)strlen(s); i < len && isspace((int)s[i]); i++) {
p++;
}
for (int i = (int)strlen(p); i >= 0 && isspace((int)p[i]); i--) {
p[i] = '\0';
}
return p;
}

View File

@@ -0,0 +1,14 @@
#pragma once
#include <stdlib.h>
#include <stdint.h>
int lftpd_inet_listen(int port);
int lftpd_inet_listen_multi(int port, int backlog);
int lftpd_inet_get_socket_port(int socket);
int lftpd_inet_accept(int socket);
int lftpd_inet_read_line(int socket, char *buffer, size_t buffer_len);
int lftpd_inet_write_string(int socket, const char *message);
int lftpd_inet_write(int socket, const void *buf, int len);
int lftpd_inet_read(int socket, void *buf, int len);
int lftpd_inet_close(int socket);

View File

@@ -0,0 +1,18 @@
#pragma once
#include <stdint.h>
char *lftpd_io_canonicalize_path(const char *base, const char *name);
int lftpd_io_open_read(const char *path);
int lftpd_io_open_write(const char *path);
int lftpd_io_read(int handle, void *buf, int len);
int lftpd_io_write(int handle, const void *buf, int len);
void lftpd_io_close(int handle);
int lftpd_io_stat(const char *path, uint32_t *p_size, int *p_is_dir);
int lftpd_io_unlink(const char *path);
void *lftpd_io_opendir(const char *path);
int lftpd_io_readdir(void *dp, char *name, int name_max, uint32_t *p_size, int *p_is_dir);
void lftpd_io_closedir(void *dp);

View File

@@ -0,0 +1,7 @@
#pragma once
#include "dbg_log.h"
#define lftpd_log_error(format, ...) DBG_ERROR("[FTP] " format, ##__VA_ARGS__)
#define lftpd_log_info(format, ...) DBG_INFO("[FTP] " format, ##__VA_ARGS__)
#define lftpd_log_debug(format, ...)

View File

@@ -0,0 +1,42 @@
#pragma once
#define STATUS_110 "Restart marker reply."
#define STATUS_120 "Service ready in %d minutes."
#define STATUS_125 "Data connection already open; transfer starting."
#define STATUS_150 "File status okay; about to open data connection."
#define STATUS_200 "Command okay."
#define STATUS_202 "Command not implemented, superfluous at this site."
#define STATUS_211 "System status, or system help reply."
#define STATUS_212 "Directory status."
#define STATUS_213 "File status."
#define STATUS_214 "Help message."
#define STATUS_215 "%s system type."
#define STATUS_220 "Service ready for new user."
#define STATUS_221 "Service closing control connection."
#define STATUS_225 "Data connection open; no transfer in progress."
#define STATUS_226 "Closing data connection."
#define STATUS_227 "Entering Passive Mode (%d,%d,%d,%d,%d,%d)."
#define STATUS_229 "Entering Extended Passive Mode (|||%d|)."
#define STATUS_230 "User logged in, proceed."
#define STATUS_250 "Requested file action okay, completed."
#define STATUS_257 "\"%s\" created."
#define STATUS_331 "User name okay, need password."
#define STATUS_332 "Need account for login."
#define STATUS_350 "Requested file action pending further information."
#define STATUS_421 "Service not available, closing control connection."
#define STATUS_425 "Can't open data connection."
#define STATUS_426 "Connection closed; transfer aborted."
#define STATUS_450 "Requested file action not taken. File unavailable (e.g., file busy)."
#define STATUS_451 "Requested action aborted: local error in processing."
#define STATUS_452 "Requested action not taken. Insufficient storage space in system."
#define STATUS_500 "Syntax error, command unrecognized. This may include errors such as command line too long."
#define STATUS_501 "Syntax error in parameters or arguments."
#define STATUS_502 "Command not implemented."
#define STATUS_503 "Bad sequence of commands."
#define STATUS_504 "Command not implemented for that parameter."
#define STATUS_530 "Not logged in."
#define STATUS_532 "Need account for storing files."
#define STATUS_550 "Requested action not taken. File unavailable (e.g., file not found, no access)."
#define STATUS_551 "Requested action aborted: page type unknown."
#define STATUS_552 "Requested file action aborted. Exceeded storage allocation (for current directory or dataset)."
#define STATUS_553 "Requested action not taken. File name not allowed."

View File

@@ -0,0 +1,5 @@
#pragma once
#define CRLF "\r\n"
char* lftpd_string_trim(char* s);

View File

@@ -3,26 +3,26 @@ Rebuild target 'STM32F407-Demo'
assembling startup_stm32f407xx.s... assembling startup_stm32f407xx.s...
compiling crc.c... compiling crc.c...
compiling ringbuf.c... compiling ringbuf.c...
compiling dma.c...
compiling gpio.c...
compiling spi.c...
compiling stm32f4xx_hal_msp.c...
compiling i2c.c... compiling i2c.c...
compiling stm32f4xx_hal_flash_ramfunc.c... compiling gpio.c...
compiling stm32f4xx_hal_msp.c...
compiling spi.c...
compiling stm32f4xx_it.c... compiling stm32f4xx_it.c...
compiling sys_clock.c...
compiling stm32f4xx_hal_timebase_tim.c... compiling stm32f4xx_hal_timebase_tim.c...
compiling stm32f4xx_hal_rcc_ex.c... compiling sys_clock.c...
compiling usart.c...
compiling app_main.c...
compiling stm32f4xx_hal_flash_ex.c... compiling stm32f4xx_hal_flash_ex.c...
compiling stm32f4xx_hal_gpio.c... compiling dma.c...
compiling app_main.c...
compiling stm32f4xx_hal_flash_ramfunc.c...
compiling stm32f4xx_hal_flash.c... compiling stm32f4xx_hal_flash.c...
compiling usart.c...
compiling stm32f4xx_hal_gpio.c...
compiling stm32f4xx_hal_rcc_ex.c...
compiling rs485_task.c... compiling rs485_task.c...
compiling adc_task.c... compiling adc_task.c...
compiling stm32f4xx_hal_rcc.c...
compiling net_task.c... compiling net_task.c...
compiling freertos.c... compiling freertos.c...
compiling stm32f4xx_hal_rcc.c...
compiling main.c... compiling main.c...
compiling stm32f4xx_hal_tim_ex.c... compiling stm32f4xx_hal_tim_ex.c...
compiling stm32f4xx_hal_tim.c... compiling stm32f4xx_hal_tim.c...
@@ -33,43 +33,43 @@ compiling fdb_utils.c...
compiling fal.c... compiling fal.c...
compiling fal_flash.c... compiling fal_flash.c...
compiling fal_partition.c... compiling fal_partition.c...
compiling croutine.c...
compiling stm32f4xx_hal_dma_ex.c... compiling stm32f4xx_hal_dma_ex.c...
compiling event_groups.c... compiling stm32f4xx_hal_dma.c...
compiling list.c... compiling croutine.c...
compiling stm32f4xx_hal_pwr_ex.c...
compiling stm32f4xx_hal_i2c_ex.c... compiling stm32f4xx_hal_i2c_ex.c...
compiling stm32f4xx_hal_pwr.c... compiling stm32f4xx_hal_pwr.c...
compiling stm32f4xx_hal_dma.c...
compiling stm32f4xx_hal_pwr_ex.c...
compiling system_stm32f4xx.c...
compiling stm32f4xx_hal_cortex.c...
compiling stm32f4xx_hal.c... compiling stm32f4xx_hal.c...
compiling event_groups.c...
compiling system_stm32f4xx.c...
compiling stm32f4xx_hal_exti.c... compiling stm32f4xx_hal_exti.c...
compiling stm32f4xx_hal_cortex.c...
compiling list.c...
compiling rs485.c... compiling rs485.c...
compiling queue.c...
compiling fal_flash_gd5f2gq5ue.c... compiling fal_flash_gd5f2gq5ue.c...
compiling stream_buffer.c...
compiling ch395f.c...
compiling tpafe5160.c... compiling tpafe5160.c...
compiling sd2506.c... compiling sd2506.c...
compiling gd5f2gq5ue.c...
compiling timers.c...
compiling stm32f4xx_hal_spi.c... compiling stm32f4xx_hal_spi.c...
compiling ch395f.c...
compiling gd5f2gq5ue.c...
compiling queue.c...
compiling nand_ftl.c... compiling nand_ftl.c...
compiling net_select.c... compiling stream_buffer.c...
compiling heap_4.c...
compiling stm32f4xx_hal_uart.c... compiling stm32f4xx_hal_uart.c...
compiling net_select.c...
compiling ch395f_test.c... compiling ch395f_test.c...
compiling tasks.c... compiling timers.c...
compiling map.c...
compiling port.c...
compiling journal.c...
compiling net_socket.c... compiling net_socket.c...
compiling heap_4.c...
compiling tasks.c...
compiling stm32f4xx_hal_i2c.c... compiling stm32f4xx_hal_i2c.c...
compiling map.c...
compiling journal.c...
compiling port.c...
compiling ff.c... compiling ff.c...
compiling cmsis_os2.c... compiling cmsis_os2.c...
linking... linking...
Program Size: Code=58876 RO-data=3560 RW-data=244 ZI-data=54796 Program Size: Code=58900 RO-data=3560 RW-data=244 ZI-data=54796
FromELF: creating hex file... FromELF: creating hex file...
".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). ".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s).
Build Time Elapsed: 00:00:17 Build Time Elapsed: 00:00:20

View File

@@ -33,6 +33,7 @@
#include "usart.h" #include "usart.h"
#include "ff.h" #include "ff.h"
#include "flashdb.h" #include "flashdb.h"
#include "gd5f2gq5ue.h"
/* USER CODE END Includes */ /* USER CODE END Includes */
/* Private typedef -----------------------------------------------------------*/ /* Private typedef -----------------------------------------------------------*/
@@ -178,8 +179,6 @@ void StartDefaultTask(void *argument)
/* ==================== FTL + FatFS 文件系统测试 ==================== */ /* ==================== FTL + FatFS 文件系统测试 ==================== */
DBG_INFO("=== Storage Test: FTL + FatFS ===");
FRESULT res = f_mount(&fs, "", 1); FRESULT res = f_mount(&fs, "", 1);
if (res == FR_NO_FILESYSTEM) { if (res == FR_NO_FILESYSTEM) {
MKFS_PARM opts = {FM_FAT32, 0, 0, 0, 0}; MKFS_PARM opts = {FM_FAT32, 0, 0, 0, 0};
@@ -216,6 +215,12 @@ void StartDefaultTask(void *argument)
DBG_INFO("=== Storage Test: FlashDB KVDB ==="); DBG_INFO("=== Storage Test: FlashDB KVDB ===");
/* 第一次 init会触发 FAL 初始化,报告错误但成功) */
fdb_ret = fdb_kvdb_init(&s_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL);
/* 清除旧验证数据:格式化所有 sector */
fdb_kv_set_default(&s_kvdb);
/* 重新初始化干净的数据库 */
fdb_ret = fdb_kvdb_init(&s_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL); fdb_ret = fdb_kvdb_init(&s_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL);
STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kvdb_init"); STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kvdb_init");

View File

@@ -0,0 +1,31 @@
# GD5F2GQ5UE 陷阱记录
## Trap 01 — 块擦除地址错误(有效)
**发现时间**2026-07-21
**现象**
`gd5f_block_erase()` 使用 `byte_addr = block × 128KB`(字节地址)作为 D8h 命令的入参,但 GD5F2GQ5UE 要求的是**页地址**`block × 64`)。
只有 block 0 的 byte_addr=0 与 page_addr=0 巧合一致,其余块全部指向了错误的物理块。导致:
- 验证流程从未擦除过大部分块,厂标坏块每次重启都恢复
- 基于错误擦除的实验结论全部无效
**根因**
- `gd5f_block_erase(block_addr)``block_addr × GD5F_BLOCK_SIZE` 计算地址
- 芯片 D8h 命令将 24-bit 地址作为页地址(行地址)解析:`RA<16:6>` 选块,`RA<5:0>` 选页内页
- 对于 block=1发送 `{0xD8, 0x00, 0x02, 0x00}` → 芯片视为 page=512 → 擦除 block=8
- 导致 block 1~7、9~15、17~23…等大量块从未被擦除
**修复**:改用 `page_addr = block_addr × GD5F_PAGES_PER_BLOCK`(块首页地址)
**验证结果**(修复后):
- 全部 2048 块正确擦除,厂标全部清除,重启后 BBT scan 为 0
- ECC 开启时 BBT rescan 正常工作
- ECC 校验码正确生成ECCS=0
- Block 0 无特殊行为
**经验教训**
- D8h 和 13h/10h 都使用 24-bit 行地址,格式一致
- 块擦除不使用 `block × block_size`(字节偏移),而用块首页地址
- 芯片手册的 memory mapping 必须严格遵守

View File

@@ -49,9 +49,18 @@
**初始化顺序:** **初始化顺序:**
``` ```
SPI1_Init → gd5f2gq5ue_init()fdb_kvdb_init() [可选FlashDB 路径] HAL_Init → SystemClock_Config → MX_GPIO_Init → MX_USART1_UART_Init
→ fdb_tsdb_init() [可选FlashDB 路径] → MX_SPI2_Init → MX_SPI1_Init
→ f_mount() [触发 FTL 初始化FatFS 路径] → gd5f2gq5ue_init() // NAND 底层驱动 (含 BBT 扫描 + ECC 使能)
→ app_main_init() // 应用层初始化
├─ FlashDB: fdb_kvdb_init() // KVDB (可选)
├─ FlashDB: fdb_tsdb_init() // TSDB (可选)
└─ f_mount() // 触发 FTL 初始化和 FatFS 挂载
→ osKernelStart() // FreeRTOS 启动
├─ defaultTask → 存储测试 + 业务
├─ netTask → 网络协议栈轮询
├─ adcTask → ADC 采集
└─ rs485Task → RS485 通信
``` ```
--- ---
@@ -113,7 +122,7 @@ SPI1_Init → gd5f2gq5ue_init() → fdb_kvdb_init() [可选FlashDB 路径]
所有分区边界在 `fal_cfg.h` 定义后,自动传播到各子模块: 所有分区边界在 `fal_cfg.h` 定义后,自动传播到各子模块:
- **FAL 分区表** — `FAL_PART_TABLE` 宏直接引用 `FDB_KVDB1_OFFSET/SIZE``FDB_TSDB1_OFFSET/SIZE` - **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` - **FTL** — `nand_ftl.c:29` 通过 `#define FTL_START_BLOCK (FTL_FATFS_OFFSET / GD5F_BLOCK_SIZE)` 自动计算起始块号(当前 = 1024`FTL_NUM_BLOCKS` 自动适配剩余块数
- **FatFS** — 通过 `disk_ioctl(GET_SECTOR_COUNT)` 获取 FTL 管理的扇区数 - **FatFS** — 通过 `disk_ioctl(GET_SECTOR_COUNT)` 获取 FTL 管理的扇区数
> 调整分区大小时只需修改 `fal_cfg.h` 顶部 6 个宏,所有下游模块自动适配。 > 调整分区大小时只需修改 `fal_cfg.h` 顶部 6 个宏,所有下游模块自动适配。
@@ -220,6 +229,11 @@ DMA 消除了轮询模式下 SPI 状态寄存器查检的逐字节 CPU 开销,
- `s_bbt[256]` 位图数组1 bit 标识 1 个块 (0=好, 1=坏) - `s_bbt[256]` 位图数组1 bit 标识 1 个块 (0=好, 1=坏)
- `gd5f2gq5ue_is_block_bad(block)` — 查询坏块状态 - `gd5f2gq5ue_is_block_bad(block)` — 查询坏块状态
- `gd5f2gq5ue_mark_block_bad(block)` — 标记坏块 (FTL 层在擦除/编程失败时调用) - `gd5f2gq5ue_mark_block_bad(block)` — 标记坏块 (FTL 层在擦除/编程失败时调用)
- `gd5f2gq5ue_bbt_clear()` — 清空 BBT慎用仅在重建时使用
- `gd5f2gq5ue_bbt_rescan()` — 重新扫描并重建 BBT需 ECC 禁用时调用,见 GD5F2GQ5UE_Trap_Records.md
- `gd5f2gq5ue_print_bbt()` — 打印 BBT 摘要(调试用)
> **注意**BBT 只能在 ECC 禁用时扫描(`gd5f_bbt_scan()`ECC 使能后 spare area 会被 ECC 引擎干扰导致坏块检测不可靠。运行时坏块只来源于真实擦除/编程失败,不要主动重新扫描。
--- ---
@@ -276,6 +290,15 @@ FlashDB 通过分区名 (`"fdb_kvdb1"`, `"fdb_tsdb1"`) 绑定到 FAL 分区。
FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动额外的设备注册。 FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动额外的设备注册。
#### 已知 Bug 修复:状态读取偏移
**问题**`Lib/FlashDB/src/fdb_utils.c``_fdb_get_status()` 函数曾有一处错误的 off-by-one 修复(在第 130-139 行额外多减了一次 `status_num`),导致:
- 循环少检查最高状态位(只查 `byte[1,0]` 而非 `byte[2,1,0]`
- `EMPTY`(状态 1被错误地读成 `USING`(状态 2
- 所有空扇区触发 KV 遍历,在全 `0xFF` 区域发现 `len=0xFFFFFFFF`,产生 `"The KV @0xXXXX0014 length has an error"` 报错
**修复**2026-07-21 移除 `_fdb_get_status()` 中多余的 `status_num--`,恢复原始正确的循环边界。详见 `Lib/FlashDB/src/fdb_utils.c:126-144`
### 6.3 KVDB 键值数据库 ### 6.3 KVDB 键值数据库
适用于存储配置参数、运行状态等少量键值对。 适用于存储配置参数、运行状态等少量键值对。
@@ -285,6 +308,7 @@ FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动
- 支持 blob (二进制数据块) - 支持 blob (二进制数据块)
- 自动磨损均衡 - 自动磨损均衡
- 支持默认值 (首次启动自动写入) - 支持默认值 (首次启动自动写入)
- 支持 select/poll I/O 多路复用(`net_select.c`
### 6.4 TSDB 时序数据库 ### 6.4 TSDB 时序数据库
@@ -445,14 +469,31 @@ f_mkfs("", &opts, work, size)
| 函数 | 说明 | | 函数 | 说明 |
|------|------| |------|------|
| `gd5f2gq5ue_init()` | 初始化 NAND ( ECC 使能块保护解除) | | `gd5f2gq5ue_init()` | 初始化 NAND (BBT 扫描 + ECC 使能 + 块保护解除) |
| `gd5f2gq5ue_read_id(mid, did)` | 读芯片 ID | | `gd5f2gq5ue_read_id(p_mid, p_did)` | 读芯片 ID |
| `gd5f2gq5ue_read(offset, buf, size)` | 读数据 (支持跨页) | | `gd5f2gq5ue_read(offset, buf, size)` | 读数据 (支持跨页) |
| `gd5f2gq5ue_write(offset, buf, size)` | 写数据 (支持跨页) | | `gd5f2gq5ue_write(offset, buf, size)` | 写数据 (支持跨页) |
| `gd5f2gq5ue_erase(offset, size)` | 块擦除 (需块对齐) | | `gd5f2gq5ue_erase(offset, size)` | 块擦除 (需块对齐) |
| `gd5f2gq5ue_reset()` | 复位芯片 | | `gd5f2gq5ue_reset()` | 复位芯片 |
| `gd5f2gq5ue_is_block_bad(block)` | 查询坏块 | | `gd5f2gq5ue_is_block_bad(block)` | 查询坏块 |
| `gd5f2gq5ue_mark_block_bad(block)` | 标记坏块 | | `gd5f2gq5ue_mark_block_bad(block)` | 标记坏块 |
| `gd5f2gq5ue_bbt_clear()` | 清空 BBT |
| `gd5f2gq5ue_bbt_rescan()` | 重新扫描 BBT |
| `gd5f2gq5ue_print_bbt()` | 打印 BBT |
**SPI 原语FTL 和测试共享):**
| 函数 | 说明 |
|------|------|
| `gd5f_wait_busy(timeout_ms)` | 轮询等待 OIP 清零 |
| `gd5f_write_enable()` | 发送 06h 写使能 |
| `gd5f_read_status(p_status)` | 读状态寄存器 (C0h) |
| `gd5f_page_read(page_addr)` | 页读取 (13h) |
| `gd5f_read_from_cache(column, buf, size)` | 读内部缓存 (0Bh) |
| `gd5f_program_load(column, buf, size)` | PROGRAM LOAD (02h) |
| `gd5f_program_exec(page_addr)` | PROGRAM EXEC (10h) |
| `gd5f_block_erase(block_addr)` | 块擦除 (D8h) |
| `gd5f_check_ecc()` | 检查 ECC 状态位 |
### 10.2 FAL 接口 (via fal_flash_gd5f2gq5ue.c) ### 10.2 FAL 接口 (via fal_flash_gd5f2gq5ue.c)
@@ -468,10 +509,12 @@ f_mkfs("", &opts, work, size)
| 函数 | 说明 | | 函数 | 说明 |
|------|------| |------|------|
| `fdb_kvdb_init(&db, "kvdb1", "fdb_kvdb1", NULL, NULL)` | 初始化 KVDB | | `fdb_kvdb_init(&db, "kvdb1", "fdb_kvdb1", NULL, NULL)` | 初始化 KVDB |
| `fdb_kvdb_set(&db, "key", "value")` | 写键值 (字符串) | | `fdb_kv_set(&db, "key", "value")` | 写键值 (字符串) |
| `fdb_kvdb_get(&db, "key", &data)` | 读键值 | | `fdb_kv_get(&db, "key")` | 读键值 (返回 char*) |
| `fdb_kvdb_del(&db, "key")` | 删除键值 | | `fdb_kv_del(&db, "key")` | 删除键值 |
| `fdb_kvdb_set_blob(&db, "key", &blob)` | 写二进制 blob | | `fdb_kv_set_blob(&db, "key", &blob)` | 写二进制 blob |
| `fdb_kv_get_blob(&db, "key", &blob)` | 读二进制 blob |
| `fdb_kv_set_default(&db)` | 格式化所有 sector 并写入默认值 |
| `fdb_tsdb_init(&db, "tsdb1", "fdb_tsdb1", get_time, 1024, NULL)` | 初始化 TSDB | | `fdb_tsdb_init(&db, "tsdb1", "fdb_tsdb1", get_time, 1024, NULL)` | 初始化 TSDB |
| `fdb_tsl_append(&db, data)` | 追加时序记录 | | `fdb_tsl_append(&db, data)` | 追加时序记录 |
| `fdb_tsl_iter_by_time(&db, from, to, cb, cb_arg)` | 时间范围查询 | | `fdb_tsl_iter_by_time(&db, from, to, cb, cb_arg)` | 时间范围查询 |
@@ -498,7 +541,7 @@ f_mkfs("", &opts, work, size)
#include "fdb_cfg.h" #include "fdb_cfg.h"
/* KVDB 实例 */ /* KVDB 实例 */
static fdb_kvdb_t g_kvdb; static struct fdb_kvdb g_kvdb;
static int kvdb_init(void) { static int kvdb_init(void) {
fdb_err_t ret = fdb_kvdb_init(&g_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL); fdb_err_t ret = fdb_kvdb_init(&g_kvdb, "kvdb1", "fdb_kvdb1", NULL, NULL);
@@ -507,24 +550,31 @@ static int kvdb_init(void) {
static void kvdb_example(void) { static void kvdb_example(void) {
/* 写字符串 */ /* 写字符串 */
fdb_kvdb_set(&g_kvdb, "device_id", "STM32F407-001"); fdb_kv_set(&g_kvdb, "device_id", "STM32F407-001");
/* 读字符串 */ /* 读字符串 */
char buf[64]; const char *val = fdb_kv_get(&g_kvdb, "device_id");
fdb_kvdb_get(&g_kvdb, "device_id", buf, sizeof(buf));
/* 写二进制 blob */ /* 写二进制 blob */
struct fdb_blob blob; struct fdb_blob blob;
uint32_t value = 42; uint32_t value = 42;
fdb_blob_make(&blob, &value, sizeof(value)); fdb_blob_make(&blob, &value, sizeof(value));
fdb_kvdb_set_blob(&g_kvdb, "counter", &blob); fdb_kv_set_blob(&g_kvdb, "counter", &blob);
/* 读二进制 blob */
uint32_t read_val;
fdb_blob_make(&blob, &read_val, sizeof(read_val));
fdb_kv_get_blob(&g_kvdb, "counter", &blob);
/* 格式化(清除所有数据) */
fdb_kv_set_default(&g_kvdb);
} }
``` ```
### 11.2 TSDB 初始化与使用 ### 11.2 TSDB 初始化与使用
```c ```c
static fdb_tsdb_t g_tsdb; static struct fdb_tsdb g_tsdb;
static time_t get_timestamp(void) { static time_t get_timestamp(void) {
return sys_clock_now(); // 从 RTC 获取 Unix 时间戳 return sys_clock_now(); // 从 RTC 获取 Unix 时间戳
@@ -544,7 +594,9 @@ typedef struct {
static void tsdb_example(void) { static void tsdb_example(void) {
sensor_data_t data = {3.3f, 0.5f, 25.6f}; sensor_data_t data = {3.3f, 0.5f, 25.6f};
fdb_tsl_append(&g_tsdb, &data); struct fdb_blob blob;
fdb_blob_make(&blob, &data, sizeof(data));
fdb_tsl_append(&g_tsdb, &blob);
} }
``` ```

31
dump_all_corruptions.py Normal file
View File

@@ -0,0 +1,31 @@
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
rep = chr(0xfffd)
base = 'D:\\Code\\DTU 程序\\STM32F4-Base\\'
files = [
'Drivers\\BSP\\CH395F\\ch395f.c',
'Drivers\\BSP\\CH395F\\ch395f_test.c',
'Drivers\\BSP\\NET\\net_select.c',
'Drivers\\BSP\\GD5F2GQ5UE\\gd5f2gq5ue.c',
'Drivers\\BSP\\SD2506\\sd2506.c',
'Drivers\\BSP\\TPAFE5160\\tpafe5160.c',
]
for fname in files:
path = base + fname
with open(path, 'rb') as f:
raw = f.read()
text = raw.decode('utf-8', errors='replace')
lines = text.split(chr(10))
count = text.count(rep)
if count == 0:
continue
print('\\n=== {} ({} remaining) ==='.format(fname, count))
for i, line in enumerate(lines, 1):
if rep in line:
idx = line.index(rep)
left = line[max(0,idx-30):idx]
right = line[idx+2:idx+30]
ctx = left + '[?]' + right
print('L{}: |{}|'.format(i, ctx))

191
fix_chinese_all.py Normal file
View File

@@ -0,0 +1,191 @@
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
rep = chr(0xfffd)
base = 'D:\\Code\\DTU 程序\\STM32F4-Base\\'
pair = {
'\u51fd':'\u6570', '\u8bf4':'\u660e', '\u529f':'\u80fd', '\u53c2':'\u6570',
'\u72b6':'\u6001', '\u521d':'\u59cb', '\u7f13':'\u51b2', '\u56de':'\u503c',
'\u7b49':')', '\u8bbe':'\u7f6e', '\u5f00':'\u542f', '\u5173':'\u95ed',
'\u83b7':'\u53d6', '\u53d1':'\u9001', '\u63a5':'\u6536', '\u8bfb':'\u53d6',
'\u5199':'\u5165', '\u64e6':'\u9664', '\u5b57':'\u8282', '\u8282':'\u6570',
'\u957f':'\u5ea6', '\u7aef':'\u53e3', '\u534f':'\u8bae', '\u7c7b':'\u578b',
'\u6a21':'\u5f0f', '\u670d':'\u52a1', '\u5730':'\u5740', '\u7f51':'\u5173',
'\u63a9':'\u7801', '\u4f7f':'\u80fd', '\u54cd':'\u5e94', '\u5bc4':'\u5b58',
'\u5b58':'\u5668', '\u547d':'\u4ee4', '\u4e8b':'\u4ef6', '\u8c03':'\u7528',
'\u58f0':'\u660e', '\u5b9e':'\u73b0', '\u8fd4':'\u56de', '\u6307':'\u9488',
'\u6570':'\u636e', '\u8fde':'\u63a5', '\u9519':'\u8bef', '\u65f6':'\u95f4',
'\u6b21':'\u6570', '\u603b':'\u5bb9', '\u5bb9':'\u91cf', '\u5757':'\u5927',
'\u5927':'\u5c0f', '\u91cd':'\u4f20', '\u4f20':'\u6b21',
'\u63a2':'\u6d4b', '\u9694':'\uff1a', '\u59cb':'\u5316',
'\u8bdd':'\u65f6', '\u5316':'\uff1a', '\u52a1':'\u5668', '\u6237':'\u7aef',
'\u76ee':'\u6807', '\u6807':'\u7aef', '\u672c':'\u5730',
'\u542b':'\u957f', '\u5305':'\u542b', '\u5c5e':'\u6027',
'\u6536':'\u6570', '\u53ca':'\u65f6', '\u7cfb':'\u7edf', '\u7edf':'\u521d',
'\u5145':'\u7535', '\u95f9':'\u949f',
'\u5199':'\u4fdd', '\u4fdd':'\u62a4',
'\u8f6c':'\uff0c',
'\u65f6':'\u5236',
'\u6e05':'\u96f6',
'\u5143':'\uff1a',
'\u884c':'\uff1a',
'\u6574':'\u4e00',
}
files = [
'Drivers\\BSP\\CH395F\\ch395f.c',
'Drivers\\BSP\\CH395F\\ch395f_test.c',
'Drivers\\BSP\\NET\\net_select.c',
'Drivers\\BSP\\GD5F2GQ5UE\\gd5f2gq5ue.c',
'Drivers\\BSP\\SD2506\\sd2506.c',
'Drivers\\BSP\\TPAFE5160\\tpafe5160.c',
]
# Manual patterns that don't follow the pair rule
manual = [
# ch395f.c
('\u521d\u59cb\u5316' + rep + '?', '\u521d\u59cb\u5316\uff09'),
('\u4fee\u6539\u8bb0\u5f55' + rep + '?', '\u4fee\u6539\u8bb0\u5f55\uff1a'),
('\u738b\u5efa' + rep + '? \u6539', '\u738b\u5efa\u950b \u6539'),
('\u6d4b\u8bd5\u5b57' + rep + '?*/', '\u6d4b\u8bd5\u5b57\u8282 */'),
('\u79c1\u6709\u53d8\u91cf' + rep + '?', '\u79c1\u6709\u53d8\u91cf\u533a'),
('\u63a5\u6536\u7f13\u51b2' + rep + '?*/', '\u63a5\u6536\u7f13\u51b2\u533a */'),
('\u51fd\u6570\u58f0\u660e' + rep + '?', '\u51fd\u6570\u58f0\u660e\u533a'),
('\u51fd\u6570\u5b9e\u73b0' + rep + '?', '\u51fd\u6570\u5b9e\u73b0\u533a'),
('\u53d1\u9001\u7f13\u51b2' + rep + '?*/', '\u53d1\u9001\u7f13\u51b2\u533a */'),
('\u8bfb\u53d6\u6307\u5b9a' + rep + '?\u5b58' + rep + '?\u5185' + rep + '? */', '\u8bfb\u53d6\u6307\u5b9a\u5bc4\u5b58\u5668\u5185\u5bb9 */'),
('\u6210\u529f' + rep + '?\u5931' + rep + '?', '\u6210\u529f\u5931\u8d25'),
('\u53d1\u9001\u5b57\u8282' + rep + '?*/', '\u53d1\u9001\u5b57\u8282\u6570 */'),
('\u63a5\u6536\u5b57\u8282' + rep + '?*/', '\u63a5\u6536\u5b57\u8282\u6570 */'),
('\u5f00' + rep + '?SPI', '\u5f00\u542f SPI'),
('\u7ed3' + rep + '?SPI', '\u7ed3\u675f SPI'),
('\u53d1\u9001\u83b7\u53d6\u8fdc' + rep + '?IP', '\u53d1\u9001\u83b7\u53d6\u8fdc\u7aef IP'),
# net_select.c
('\u53ef\u8bfb\u4e8b' + rep + '?*/', '\u53ef\u8bfb\u4e8b\u4ef6 */'),
('\u53ef\u5199\u4e8b' + rep + '?*/', '\u53ef\u5199\u4e8b\u4ef6 */'),
('\u5f02\u5e38\u4e8b' + rep + '?*/', '\u5f02\u5e38\u4e8b\u4ef6 */'),
('\u8d85\u65f6\u68c0' + rep + '?*/', '\u8d85\u65f6\u68c0\u67e5 */'),
('\u65b0\u8fde\u63a5\u65f6\u53ef' + rep + '?*/', '\u65b0\u8fde\u63a5\u65f6\u53ef\u8bfb */'),
# tpafe5160.c
('16' + rep + '?\u901a\u9053', '16\u4f4d 8\u901a\u9053'),
('t10 (RD \u4f4e\u8109' + rep + '?)', 't10 (RD \u4f4e\u8109\u51b2)'),
('\u5e8f\u5173\u952e' + rep + '?(', '\u5e8f\u5173\u952e\u70b9('),
('\u51fd\u6570\u8bf4\u660e\uff1a\u4ea7' + rep + '?CONVST', '\u51fd\u6570\u8bf4\u660e\uff1a\u4ea7\u751f CONVST'),
('\u51fd\u6570\u8bf4\u660e\uff1a\u8f6e' + rep + '?BUSY', '\u51fd\u6570\u8bf4\u660e\uff1a\u8f6e\u8be2 BUSY'),
('\u51fd\u6570\u8bf4\u660e\uff1a\u6bcf' + rep + '?RD', '\u51fd\u6570\u8bf4\u660e\uff1a\u6bcf\u4e2a RD'),
('\u51fd\u6570\u8bf4\u660e\uff1a' + rep + 'V\u91cf\u7a0b', '\u51fd\u6570\u8bf4\u660e\uff1a5V\u91cf\u7a0b'),
# sd2506.c
('\u5341\u8fdb\u5236' + rep + '?BCD ', '\u5341\u8fdb\u5236\u8f6c BCD '),
('\u6210\u529f' + rep + '?2 - I2C', '\u6210\u529f\uff0c-2 - I2C'),
('00H: ' + rep + '?*/', '00H: \u79d2 */'),
('01H: ' + rep + '?*/', '01H: \u5206 */'),
('02H: ' + rep + '?', '02H: \u65f6'),
('04H: ' + rep + '?*/', '04H: \u65e5 */'),
('05H: ' + rep + '?*/', '05H: \u6708 */'),
('06H: ' + rep + '?*/', '06H: \u5e74 */'),
('07H: \u79d2\u62a5' + rep + '?*/', '07H: \u79d2\u62a5\u8b66 */'),
('08H: \u5206\u62a5' + rep + '?*/', '08H: \u5206\u62a5\u8b66 */'),
('09H: \u65f6\u62a5' + rep + '?', '09H: \u65f6\u62a5\u8b66'),
('0BH: \u65e5\u62a5' + rep + '?*/', '0BH: \u65e5\u62a5\u8b66 */'),
('0CH: \u6708\u62a5' + rep + '?*/', '0CH: \u6708\u62a5\u8b66 */'),
('0DH: \u5e74\u62a5' + rep + '?*/', '0DH: \u5e74\u62a5\u8b66 */'),
('\u51fd\u6570\u8bf4\u660e' + rep + '?. \u5148\u5f00\u5199\u4fdd', '\u51fd\u6570\u8bf4\u660e\uff1a1. \u5148\u5f00\u5199\u4fdd\u62a4'),
('\u51fd\u6570\u8bf4\u660e' + rep + '?. \u5199\u4f7f', '\u51fd\u6570\u8bf4\u660e\uff1a2. \u5199\u4f7f\u80fd'),
('\u51fd\u6570\u8bf4\u660e' + rep + '?. \u542f\u52a8\u8f6c\u6362', '\u51fd\u6570\u8bf4\u660e\uff1a3. \u542f\u52a8\u8f6c\u6362'),
# ch395f
('\u51fd\u6570\u529f\u80fd\uff1a\u590d\u4f4d\u82af' + rep + '?', '\u51fd\u6570\u529f\u80fd\uff1a\u590d\u4f4d\u82af\u7247'),
('\u51fd\u6570\u529f\u80fd\uff1a\u68c0' + rep + '?CH395', '\u51fd\u6570\u529f\u80fd\uff1a\u68c0\u67e5 CH395'),
('\u51fd\u6570\u529f\u80fd\uff1a\u83b7\u53d6\u8fdc' + rep + '?IP', '\u51fd\u6570\u529f\u80fd\uff1a\u83b7\u53d6\u8fdc\u7aef IP'),
('\u51fd\u6570\u529f\u80fd\uff1a\u8f6f\u4ef6\u590d' + rep + '?CH395', '\u51fd\u6570\u529f\u80fd\uff1a\u8f6f\u4ef6\u590d\u4f4d CH395'),
('\u51fd\u6570\u529f\u80fd\uff1a\u542f' + rep + '?TCP \u76d1\u542c', '\u51fd\u6570\u529f\u80fd\uff1a\u542f\u52a8 TCP \u76d1\u542c'),
('\u51fd\u6570\u529f\u80fd\uff1a\u542f' + rep + '?TCP \u8fde\u63a5', '\u51fd\u6570\u529f\u80fd\uff1a\u542f\u52a8 TCP \u8fde\u63a5'),
('\u51fd\u6570\u529f\u80fd\uff1a\u542f' + rep + '?\u7981\u7528 DHCP', '\u51fd\u6570\u529f\u80fd\uff1a\u542f\u7528\u7981\u7528 DHCP'),
('\u51fd\u6570\u529f\u80fd\uff1a\u542f' + rep + '?\u7981\u7528\u6307\u5b9a S', '\u51fd\u6570\u529f\u80fd\uff1a\u542f\u7528\u7981\u7528\u6307\u5b9a S'),
('\u51fd\u6570\u529f\u80fd\uff1a\u8bfb\u53d6\u5168' + rep + '?\u901a\u9053\u8f6c\u6362\u7ed3\u679c', '\u51fd\u6570\u529f\u80fd\uff1a\u8bfb\u53d6\u5168\u90e8\u901a\u9053\u8f6c\u6362\u7ed3\u679c'),
('\u51fd\u6570\u529f\u80fd\uff1a\u8bfb\u53d6\u82af' + rep + '?ID', '\u51fd\u6570\u529f\u80fd\uff1a\u8bfb\u53d6\u82af\u7247 ID'),
# ch395f_test.c
('NET ' + rep + '?TCP Ec', 'NET \u7684 TCP Ec'),
('NET ' + rep + '?UDP Ec', 'NET \u7684 UDP Ec'),
# gd5f2gq5ue.c
('\u7f16\u7a0b\u5b8c\u6210\u540e\u68c0' + rep + '?P_FAIL', '\u7f16\u7a0b\u5b8c\u6210\u540e\u68c0\u67e5 P_FAIL'),
('\u64e6\u9664\u5b8c\u6210\u540e\u68c0' + rep + '?E_FAIL', '\u64e6\u9664\u5b8c\u6210\u540e\u68c0\u67e5 E_FAIL'),
# generic patterns
('\u9650\u5b9a\u6761\u4ef6\uff1a\u5e94' + rep + '?ch395f', '\u9650\u5b9a\u6761\u4ef6\uff1a\u5e94\u5148\u8c03\u7528 ch395f'),
('\u9650\u5b9a\u6761\u4ef6\uff1a\u9700' + rep + '?ch395f', '\u9650\u5b9a\u6761\u4ef6\uff1a\u9700\u5148\u8c03\u7528 ch395f'),
('\u9650\u5b9a\u6761\u4ef6\uff1a\u6307\u9488\u975e' + rep + '? */', '\u9650\u5b9a\u6761\u4ef6\uff1a\u6307\u9488\u975e\u7a7a */'),
('\u6570\u636e\u540c\u6b65\u5c4f\u969c\uff1a\u786e' + rep + '?memcpy', '\u6570\u636e\u540c\u6b65\u5c4f\u969c\uff1a\u786e\u4fdd memcpy'),
('\u68c0\u67e5\u8f93\u5165\u53c2\u6570\u5408\u6cd5' + rep + '?*/', '\u68c0\u67e5\u8f93\u5165\u53c2\u6570\u5408\u6cd5\u6027 */'),
('\u8bfb\u53d6\u5168\u5c40\u4e2d\u65ad\uff08\u5237' + rep + '?socket', '\u8bfb\u53d6\u5168\u5c40\u4e2d\u65ad\uff08\u5237\u65b0 socket'),
('Socket \u6709\u6570\u636e\u53ef' + rep + '?*/', 'Socket \u6709\u6570\u636e\u53ef\u8bfb */'),
('IP \u5730\u5740\uff08\u4f4e\u5b57\u8282\u5728\u524d' + rep + '?', 'IP \u5730\u5740\uff08\u4f4e\u5b57\u8282\u5728\u524d\uff09'),
('\u8fd4\u56de\u503c\uff1a\u547d\u4ee4\u7801\u56de\u663e' + rep + '? uint8', '\u8fd4\u56de\u503c\uff1a\u547d\u4ee4\u7801\u56de\u663e\u503c uint8'),
('\u8fd4\u56de\u503c\uff1a\u6570\u636e\u56de\u663e' + rep + '? uint8', '\u8fd4\u56de\u503c\uff1a\u6570\u636e\u56de\u663e\u503c uint8'),
('GET_IP_INF ' + rep + '?9 ', 'GET_IP_INF \u7b49 9 '),
# tpafe
('RANGE ' + rep + '?GND', 'RANGE \u63a5 GND'),
('\u62f7\u8d1d\u7528\u6237\u6570\u636e' + rep + '?DMA', '\u62f7\u8d1d\u7528\u6237\u6570\u636e\u5230 DMA'),
('\u7ec4\u5408 9 ' + rep + '? bit8', '\u7ec4\u5408 9 \u548c bit8'),
('ttl - TTL ' + rep + '? uint8', 'ttl - TTL \u503c uint8'),
('(BAT8_VAL) ' + rep + '?1BH', '(BAT8_VAL) \u4e3a 1BH'),
('\u6b63\u6e29' + rep + '?*/', '\u6b63\u6e29\u5ea6 */'),
('\u8d1f\u6e29' + rep + '? \u53d6\u8865', '\u8d1f\u6e29\u5ea6 \u53d6\u8865\u7801'),
# tpafe
('\u51fd\u6570\u8bf4\u660e\uff1a\u542f' + rep + '?DHCP', '\u51fd\u6570\u8bf4\u660e\uff1a\u542f\u52a8 DHCP'),
('\u51fd\u6570\u8bf4\u660e\uff1a' + rep + '?V\u91cf\u7a0b', '\u51fd\u6570\u8bf4\u660e\uff1a\u00b15V\u91cf\u7a0b'),
# generic pair leftovers
(rep + '?' + rep + '?\u503c\uff1a', '\u8fd4\u56de\u503c\uff1a'),
]
# Apply pair mapping
for fname in files:
path = base + fname
if not os.path.exists(path):
print(f'*** {fname}: NOT FOUND')
continue
with open(path, 'rb') as f:
raw = f.read()
text = raw.decode('utf-8', errors='replace')
before = text.count(rep)
if before == 0:
print(f'{fname}: clean')
continue
# Strategy 1: pair mapping [char][REP]?
for first, second in pair.items():
pattern = first + rep + '?'
replacement = first + second
text = text.replace(pattern, replacement)
# Strategy 2: manual patterns
for old, new in manual:
text = text.replace(old, new)
# Strategy 3: common multi-char patterns
text = text.replace(rep + '?' + rep + '?\u503c\uff1a', '\u8fd4\u56de\u503c\uff1a')
text = text.replace(rep + '?' + rep + '?\u503c\uff1a', '\u8fd4\u56de\u503c\uff1a')
# Strategy 4: for remaining [REP]?\u503c\uff1a -> '\u8fd4\u56de\u503c\uff1a'
# These are cases where the full '\u8fd4' was also corrupted
import re as regex
text = regex.sub(rep + r'\?' + rep + r'\?\u503c\uff1a', '\u8fd4\u56de\u503c\uff1a', text)
after = text.count(rep)
fixed = before - after
with open(path, 'wb') as f:
f.write(text.encode('utf-8'))
if after > 0:
print(f'{fname}: fixed {fixed}/{before}, {after} remaining')
# Show first remaining
for i, line in enumerate(text.split(chr(10)), 1):
if rep in line:
idx = line.index(rep)
ctx = line[max(0,idx-10):idx+6]
printable = ctx.replace(rep, '[?]')
print(f' eg L{i}: ...{printable}...')
break
else:
print(f'{fname}: CLEAN ({fixed} fixed)')
print('Done')

154
fix_chinese_final.py Normal file
View File

@@ -0,0 +1,154 @@
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
rep = chr(0xfffd)
base = 'D:\\Code\\DTU 程序\\STM32F4-Base\\'
fixes = {
'Drivers\\BSP\\CH395F\\ch395f.c': [
# Author name
('王建' + rep + '? * 创建', '王建锋 * 创建'),
# Closing parens and punctuation
('手册要' + rep + '?SPI', '手册要求 SPI'),
('rx_buf[' + rep + '? *', 'rx_buf] *'),
('SPI 驱动' + rep + '?DMA TX+RX', 'SPI 驱动) DMA TX+RX'),
('SPI 驱动' + rep + '?DMA 传输', 'SPI 驱动) DMA 传输'),
('begin() ' + rep + '?ch395f_spi_end', 'begin() 到 ch395f_spi_end'),
('begin() ' + rep + '?ch395f_spi_end', 'begin() 到 ch395f_spi_end'),
('begin() ' + rep + '?ch395f_spi_end', 'begin() 到 ch395f_spi_end'),
('0x57' + rep + '? *', '0x57) *'),
('exist() 检' + rep + '? */', 'exist() 检查) */'),
('在前,' + rep + '? */', '在前,) */'),
('默认' + rep + '?' + rep + '? *', '默认值) *'),
('SUCCESS ' + rep + '?CH395F_CMD_RET', 'SUCCESS 或 CH395F_CMD_RET'),
('4s' + rep + '? */', '4s) */'),
('EEPROM' + rep + '? * 入口参数', 'EEPROM) * 入口参数'),
('TE5' + rep + '? */', 'TE5) */'),
('' + rep + '?5.37', '手册 5.37'),
('发送启' + rep + '?禁用', '发送启禁用'),
('时钟)' + rep + '? *', '时钟), *'),
(rep + '?DMA 接收缓冲区偏移', '从 DMA 接收缓冲区偏移'),
('0~7' + rep + '? * 返回值', '0~7) * 返回值'),
('100ms' + rep + '?=默认', '100ms)=默认'),
('' + rep + '?=默认', '次数=默认'),
('' + rep + '?128', '最大 128'),
('+IP' + rep + '? * 限定条件', '+IP) * 限定条件'),
],
'Drivers\\BSP\\CH395F\\ch395f_test.c': [
('' + rep + '?192.168.1.2:8081' + rep + '?', '认 192.168.1.2:8081)'),
('' + rep + '?192.168.1.2:8082' + rep + '?', '认 192.168.1.2:8082)'),
('version) ' + rep + '?read-clear', 'version) 即 read-clear'),
('创建' + rep + '?UDP', '创建 UDP'),
('DHCP ' + rep + '?轮询等待', 'DHCP 并轮询等待'),
('= 60000' + rep + '?*/', '= 60000)*/'),
],
'Drivers\\BSP\\GD5F2GQ5UE\\gd5f2gq5ue.c': [
('再拉' + rep + '? */', '再拉高 */'),
('P_FAIL ' + rep + '? */', 'P_FAIL 位 */'),
('E_FAIL ' + rep + '? */', 'E_FAIL 位 */'),
('128KB' + rep + '? * 入口参数', '128KB) * 入口参数'),
],
'Drivers\\BSP\\NET\\net_select.c': [
('不可' + rep + '? */', '不可写 */'),
('' + rep + '?*/', '可写*/'),
],
'Drivers\\BSP\\SD2506\\sd2506.c': [
('BCD ' + rep + '? * 入口参数', 'BCD 码 * 入口参数'),
('编码' + rep + '? * 限定条件', '编码) * 限定条件'),
(rep + '?1AH=80H', '(1AH=80H'),
('' + rep + '?(raw', '毫秒 (raw'),
('SRAM ' + rep + '?2CH~71H', 'SRAM 区 2CH~71H'),
('始终' + rep + '?])', '始终为0])'),
('CTR2 ' + rep + '?INTAE=1', 'CTR2 的 INTAE=1'),
],
'Drivers\\BSP\\TPAFE5160\\tpafe5160.c': [
('ZGT6' + rep + '?6位', 'ZGT6并行 6位'),
('驱动' + rep + '?TPAFE5160' + rep + '?', '驱动适配 TPAFE5160'),
# Fix multiple specific cases
('脉冲 ' + rep + '?22ns', '脉冲 约 22ns'),
('~6ns)' + rep + '? * 加上', '~6ns)* 加上'),
('已完' + rep + '?GPIO', '已完成 GPIO'),
# 设置过采样
('过采样' + rep + '? * 入口参数', '过采样率 * 入口参数'),
('返回值:' + rep + '? * 限定条件GPIO', '返回值:无 * 限定条件GPIO'),
# 下一 - this is tricky, let me look at full context
('样本' + rep + '?BUSY', '样本 BUSY'),
('下一' + rep + '?BUSY', '下一次 BUSY'),
# wait conversion
('转换完' + rep + '? * 入口参数', '转换完成 * 入口参数'),
('转换完' + rep + '? * 返回值', '转换完成 * 返回值'), # if this pattern exists
('转换完成' + rep + '?2 - 超时', '转换完成2 - 超时'),
('下降' + rep + '? */', '下降沿 */'),
('空闲' + rep + '?*/', '空闲)*/'),
('转换' + rep + '?1 -', '转换中 1 -'),
# 读取指定通道的转换结果
('转换结' + rep + '? * 入口参数', '转换结果 * 入口参数'),
('转电压' + rep + '? * 入口参数', '转电压 * 入口参数'),
('量程' + rep + '?LSB', '量程LSB'),
('量程' + rep + '?LSB', '量程LSB'),
('引脚' + rep + '?GND', '引脚接 GND'),
('RANGE 接 GND ' + rep + '?±5V', 'RANGE 接 GND±5V'),
('转换' + rep + '? * 入口参数', '转换)* 入口参数'),
('RD 为低' + rep + '?CS', 'RD 为低CS'),
('通道' + rep + '? uint8_t', '通道数 uint8_t'),
('缓冲' + rep + '? int16_t', '缓冲区 int16_t'),
('返回值:' + rep + '? * 限定条件:转换', '返回值:无 * 限定条件:转换'),
('主频)' + rep + '? *', '主频)*'),
('数据' + rep + '?DB[15:0]', '数据到 DB[15:0]'),
('ISR ' + rep + '?s_buf_b', 'ISR 写 s_buf_b'),
('0 = ' + rep + '?B / ' + rep + '?A, 1 = ' + rep + '?A / ' + rep + '?B',
'0 = 写 B / 读 A, 1 = 写 A / 读 B'),
('使能用' + rep + '? * 入口参数', '使能)* 入口参数'),
('返回值:' + rep + '? * 限定条件CubeMX', '返回值:无 * 限定条件CubeMX'),
('' + rep + '?irq_disable()', '仅当 irq_disable()'),
('返回值:' + rep + '? * 限定条件:已调用', '返回值:无 * 限定条件:已调用'),
('启动转换(中断模式' + rep + '? * 入口参数', '启动转换(中断模式)* 入口参数'),
('返回值:' + rep + '? * 限定条件:已调用 tpafe5', '返回值:无 * 限定条件:已调用 tpafe5'),
('完成后' + rep + '?EXTI 中断', '完成后触发 EXTI 中断'),
('数据就绪' + rep + '? - 无新数据', '数据就绪0 - 无新数据'),
('返回值:' + rep + '? * 限定条件:中断', '返回值:无 * 限定条件:中断'),
('缓冲' + rep + '? */', '缓冲区 */'),
('下降' + rep + '?EXTI', '下降沿 EXTI'),
('返回值:' + rep + '? * 限定条件BUSY', '返回值:无 * 限定条件BUSY'),
# Additional tpafe5160 patterns
('' + rep + '?位数据', '并行 16位数据'),
('本驱动' + rep + '?AD7606', '本驱动适配 AD7606'),
],
}
total_fixed = 0
for fname, file_fixes in fixes.items():
path = base + fname
with open(path, 'rb') as f:
raw = f.read()
text = raw.decode('utf-8', errors='replace')
before = text.count(rep)
for needle, replacement in file_fixes:
while needle in text:
text = text.replace(needle, replacement)
after = text.count(rep)
fixed = before - after
total_fixed += fixed
print('{}: fixed {} ({} -> {})'.format(fname, fixed, before, after))
with open(path, 'wb') as f:
f.write(text.encode('utf-8'))
print('\nTotal fixed this pass: {}'.format(total_fixed))
# Show remaining
print('\n=== REMAINING ===')
for fname in fixes:
path = base + fname
with open(path, 'rb') as f:
text = f.read().decode('utf-8', errors='replace')
remaining = text.count(rep)
if remaining > 0:
print('\n--- {} remaining in {} ---'.format(remaining, fname))
for i, line in enumerate(text.split(chr(10)), 1):
if rep in line:
idx = line.index(rep)
left = line[max(0,idx-30):idx]
right = line[idx+2:idx+20]
print('L{}: ...{}[?]{}...'.format(i, left, right))

139
fix_chinese_generic.py Normal file
View File

@@ -0,0 +1,139 @@
import sys, os, re
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
rep = chr(0xfffd)
base = 'D:\\Code\\DTU 程序\\STM32F4-Base\\'
files = [
'Drivers\\BSP\\CH395F\\ch395f.c',
'Drivers\\BSP\\CH395F\\ch395f_test.c',
'Drivers\\BSP\\GD5F2GQ5UE\\gd5f2gq5ue.c',
'Drivers\\BSP\\SD2506\\sd2506.c',
'Drivers\\BSP\\TPAFE5160\\tpafe5160.c',
]
# Generic patterns - these work across comment blocks
generic_fixes = []
# 1. [?] followed by */ -> ) */
generic_fixes.append((rep + r'\? \*/', u' */'))
# 2. [?] followed by * and 入口参数/函数说明/etc -> * xxx
comment_keywords = [
u'入口参数', u'函数功能', u'函数说明', u'限定条件', u'返回值',
u'适用平台', u'作者', u'创建日期', u'修改记录', u'模块名称',
u'模块功能', u'函数说明', u'头文件', u'私有宏', u'私有变量',
u'私有函数', u'外部变量', u'作者',
]
for kw in comment_keywords:
# [?] * X -> * X
generic_fixes.append((rep + r'\? \* ' + kw, u' * ' + kw))
# 3. Various suffix patterns
generic_fixes.append((rep + r'\?\)', u''))
generic_fixes.append((u'' + rep + r'\?', u''))
# 4. [?]\n at end of line (before comment continuation)
# This is hard to do with simple string matching, use regex
# 5. Specific contexts that appear across files
generic_fixes.append((rep + r'\?', u''))
generic_fixes.append((rep + r'\?。', u')。'))
# 6. Numbers or units followed by [?]
# e.g. "范围[?] * 函数" -> "范围)* 函数"
# e.g. "128KB[?] * 入口" -> "128KB* 入口"
total_fixed = 0
for fname in files:
path = base + fname
with open(path, 'rb') as f:
raw = f.read()
text = raw.decode('utf-8', errors='replace')
before = text.count(rep)
if before == 0:
print('{}: clean'.format(fname))
continue
# Apply generic pattern fixes
for pattern, replacement in generic_fixes:
text = re.sub(pattern, replacement, text)
# Manual patterns for each file
if 'ch395f.c' in fname:
text = text.replace(rep + '?' + u'DMA 接收缓冲区偏移', u'从 DMA 接收缓冲区偏移')
text = text.replace(u'创建日期' + rep + '?', u'创建日期:')
text = text.replace(u'' + rep + '? */', u'要求 */')
text = text.replace(u'rx_buf[' + rep + '?', u'rx_buf]')
text = text.replace(u'取反' + rep + '?', u'取反为')
text = text.replace(u'在前' + rep + '? */', u'在前)*/')
text = text.replace(u'_ABORT' + rep + '? *', u'_ABORT*')
text = text.replace(u'' + rep + '?SPI', u'一次 SPI')
text = text.replace(u'缓冲' + rep + '?*/', u'缓冲区)*/')
text = text.replace(u'~7 + DHCPv6' + rep + '? *', u'~7 + DHCPv6*')
text = text.replace(u'100ms)=默认' + rep + '?', u'100ms)=默认值')
text = text.replace(u'次数据=默认' + rep + '?', u'次数=默认值')
text = text.replace(u'' + rep + '?128', u'最大 128')
if 'ch395f_test.c' in fname:
text = text.replace(u':8081' + rep + '? *', u':8081*')
text = text.replace(u':8082' + rep + '? *', u':8082*')
text = text.replace(u'创建' + rep + '?UDP', u'创建 UDP')
text = text.replace(u'IP ' + rep + '?打印', u'IP打印')
text = text.replace(u'(1-byte version) ' + rep + '?read-clear', u'(1-byte version)read-clear')
if 'gd5f2gq5ue.c' in fname:
text = text.replace(u'对齐' + rep + '? *', u'对齐)*')
if 'sd2506.c' in fname:
text = text.replace(u'范围' + rep + '? *', u'范围)*')
text = text.replace(u'始终' + rep + '?])', u'始终为0])')
text = text.replace(u'(周期' + rep + '? */', u'(周期中断) */')
if 'tpafe5160.c' in fname:
text = text.replace(u'16位数据总线' + rep + '?GPIOG', u'16位数据总线GPIOG')
text = text.replace(u'驱动' + rep + '?TPAFE5160', u'驱动适配 TPAFE5160')
text = text.replace(u'滞后' + rep + '?18-24ns', u'滞后约 18-24ns')
text = text.replace(u'说明' + rep + '?.', u'说明:')
text = text.replace(u'枚举' + rep + '? tpafe5160_os_t', u'枚举值 tpafe5160_os_t')
text = text.replace(u'锁存生' + rep + '? */', u'锁存生效 */')
text = text.replace(u'转换' + rep + '? - 空闲', u'转换中 - 空闲')
text = text.replace(u'原始化 int16_t', u'原始值 int16_t')
text = text.replace(u'有符号补' + rep + '? *', u'有符号补码 *')
text = text.replace(u'65536 ' + rep + '?152.59', u'65536 ≈ 152.59')
text = text.replace(u'65536 ' + rep + '?305.18', u'65536 ≈ 305.18')
text = text.replace(u'' + rep + '?±5V', u'默认 ±5V')
text = text.replace(u'CS ' + rep + '?RD', u'CS 和 RD')
text = text.replace(u'写操' + rep + '?~6ns', u'写操作 ~6ns')
text = text.replace(u'主循环' + rep + '?s_buf_a', u'主循环读 s_buf_a')
text = text.replace(u'GPIO ' + rep + '?NVIC', u'GPIO 和 NVIC')
text = text.replace(u'调用' + rep + '? * 入口参数', u'调用)* 入口参数')
after = text.count(rep)
fixed = before - after
total_fixed += fixed
with open(path, 'wb') as f:
f.write(text.encode('utf-8'))
print('{}: fixed {} ({} -> {})'.format(fname, fixed, before, after))
print('\nTotal fixed: {}'.format(total_fixed))
# Show remaining
print('\n=== REMAINING ===')
for fname in files:
path = base + fname
with open(path, 'rb') as f:
text = f.read().decode('utf-8', errors='replace')
remaining = text.count(rep)
if remaining > 0:
print('\n--- {} remaining in {} ---'.format(remaining, fname))
for i, line in enumerate(text.split(chr(10)), 1):
if rep in line:
idx = line.index(rep)
left = line[max(0,idx-35):idx]
right = line[idx+2:idx+35]
ctx_u = left.encode('unicode-escape').decode()
ctx_v = right.encode('unicode-escape').decode()
print('L{}: ...{}[?]{}...'.format(i, left, right))
print(' ...{} [?] {}...'.format(ctx_u, ctx_v))

107
fix_remaining.py Normal file
View File

@@ -0,0 +1,107 @@
import sys, os
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
rep = chr(0xfffd)
base = 'D:\\Code\\DTU 程序\\STM32F4-Base\\'
all_fixes = [
# (filename, pattern_to_match, replacement_full_line_fragment)
# ch395f.c
('ch395f.c', 'rx_buf[' + rep + '? * 阻塞', 'rx_buf] * 阻塞'),
('ch395f.c', '默认值10' + rep + '?秒)', '默认值10 秒)'),
('ch395f.c', '默认值3' + rep + '? uint8_t', '默认值3 uint8_t'),
# ch395f_test.c
('ch395f_test.c', '回显' + rep + '? \ufffd?PING', '回显PING'),
('ch395f_test.c', '创建' + rep + '? UDP', '创建 UDP'),
('ch395f_test.c', 'IP打印结果 ' + rep + '?广播', 'IP打印结果广播'),
# gd5f2gq5ue.c
('gd5f2gq5ue.c', '块对' + rep + '? * size', '块对齐 * size'),
# sd2506.c
('sd2506.c', '个位存入' + rep + '4 ', '个位存入低 4 '),
# tpafe5160.c
('tpafe5160.c', '6位数据总线' + rep + '?GPIOG', '16位数据总线GPIOG'),
('tpafe5160.c', '滞后约 18-24ns' + rep + '? *', '滞后约 18-24ns*'),
('tpafe5160.c', '电压' + rep + '? float', '电压值 float'),
('tpafe5160.c', 'IDR ' + rep + '?6位', 'IDR 16位'),
('tpafe5160.c', '~18ns ' + rep + '?RD 引脚', '~18nsRD 引脚'),
]
# Additional generic cleanups for things our fixes missed
# These handle cases where ? should be a specific Chinese character or punctuation
more_fixes = [
# Closing paren patterns
('ch395f.c', rep + '?\ufffd?' + u'10\ufffd?' + u'\u79d2', ' '),
('tpafe5160.c', u'\ufffd?' + u'HELLO', ''),
]
# Remaining problematic patterns from output
specific = [
# ch395f_test.c - the ?HELLO issue
('Drivers\\BSP\\CH395F\\ch395f_test.c', u'结果 ' + rep + '?' + u'广播', u'结果,广播'),
# tpafe5160 line with multiple corruptions
('Drivers\\BSP\\TPAFE5160\\tpafe5160.c', u'总线' + rep + '?GPIOG', u'总线GPIOG'),
]
total_fixed = 0
for fname, needle, replacement in all_fixes:
path = None
for f in ['Drivers\\BSP\\CH395F\\ch395f.c', 'Drivers\\BSP\\CH395F\\ch395f_test.c',
'Drivers\\BSP\\GD5F2GQ5UE\\gd5f2gq5ue.c', 'Drivers\\BSP\\NET\\net_select.c',
'Drivers\\BSP\\SD2506\\sd2506.c', 'Drivers\\BSP\\TPAFE5160\\tpafe5160.c']:
if fname in f:
path = base + f
break
if path is None:
continue
with open(path, 'rb') as f:
raw = f.read()
text = raw.decode('utf-8', errors='replace')
before = text.count(rep)
if needle in text:
text = text.replace(needle, replacement)
after = text.count(rep)
fixed = before - after
total_fixed += fixed
print('{}: fixed ({} -> {})'.format(fname, before, after))
else:
# Check what's actually in the file
lines = text.split(chr(10))
found = False
search_prefix = needle[:10]
for i, line in enumerate(lines, 1):
if rep in line and search_prefix in line:
idx = line.index(rep)
ctx = line[max(0,idx-20):idx+20]
print('{}: pattern not matched, closest at L{}: ...{}...'.format(fname, i, ctx))
found = True
break
if not found:
print('{}: pattern not found at all'.format(fname))
with open(path, 'wb') as f:
f.write(text.encode('utf-8'))
print('\nTotal fixed this pass: {}'.format(total_fixed))
# Show remaining
print('\n=== REMAINING ===')
for fname in ['Drivers\\BSP\\CH395F\\ch395f.c', 'Drivers\\BSP\\CH395F\\ch395f_test.c',
'Drivers\\BSP\\GD5F2GQ5UE\\gd5f2gq5ue.c', 'Drivers\\BSP\\NET\\net_select.c',
'Drivers\\BSP\\SD2506\\sd2506.c', 'Drivers\\BSP\\TPAFE5160\\tpafe5160.c']:
path = base + fname
with open(path, 'rb') as f:
text = f.read().decode('utf-8', errors='replace')
remaining = text.count(rep)
if remaining > 0:
print('\n--- {} remaining in {} ---'.format(remaining, fname))
for i, line in enumerate(text.split(chr(10)), 1):
if rep in line:
idx = line.index(rep)
left = line[max(0,idx-40):idx]
right = line[idx+2:idx+40]
print('L{}: ...{}[?]{}...'.format(i, left, right))

32
git_compare.py Normal file
View File

@@ -0,0 +1,32 @@
import subprocess, os, sys
sys.stdout.reconfigure(encoding='utf-8', errors='replace')
base = 'D:\\Code\\DTU 程序\\STM32F4-Base\\'
result = subprocess.run(
['C:\\Program Files\\Git\\bin\\git.exe', 'show', '3466c4c:Drivers/BSP/CH395F/ch395f.c'],
capture_output=True,
cwd=base
)
git_text = result.stdout.decode('utf-8', errors='replace')
with open(os.path.join(base, 'Drivers\\BSP\\CH395F\\ch395f.c'), 'rb') as f:
wt_text = f.read().decode('utf-8', errors='replace')
rep = chr(0xfffd)
print('Git version: {} FFFD chars'.format(git_text.count(rep)))
print('Working tree: {} FFFD chars'.format(wt_text.count(rep)))
git_lines = git_text.split(chr(10))
wt_lines = wt_text.split(chr(10))
for i in range(min(len(git_lines), len(wt_lines))):
if git_lines[i] != wt_lines[i]:
print('\nFirst diff at line {}:'.format(i+1))
print(' GIT: {}'.format(git_lines[i][:120]))
print(' WT: {}'.format(wt_lines[i][:120]))
hex_g = git_lines[i].encode('utf-8').hex()[:40]
hex_w = wt_lines[i].encode('utf-8').hex()[:40]
print(' GIT hex: {}'.format(hex_g))
print(' WT hex: {}'.format(hex_w))
break
else:
print('Files are identical')