解决一个擦除的bug
This commit is contained in:
16
AGENTS.md
16
AGENTS.md
@@ -143,8 +143,15 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init()
|
||||
- SPI Mode 0(CPOL=0, CPHA=0),时钟 42MHz
|
||||
- 初始化必须按顺序:复位 → 读 ID → 使能 ECC(B0h=10h)→ 解除块保护(A0h=00h)
|
||||
- SET_FEATURE 命令前必须先发写使能(06h)
|
||||
- 块擦除(D8h)参数是字节地址(块编号 × 128KB),不是块编号
|
||||
- 块擦除(D8h)参数是块首页地址(块编号 × 64),不是字节地址(旧版错误)
|
||||
- 读取 ID(9Fh)返回 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)
|
||||
|
||||
@@ -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 02** 数据 Socket 缓冲区重叠
|
||||
- **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 07** TCP 关闭误用 disconnect 导致 FIN_WAIT_2
|
||||
- **Trap 08** RECV 中断电平触发无限循环
|
||||
|
||||
当前已记录陷阱(GD5F2GQ5UE):
|
||||
- **Trap 01** 块擦除地址错误(`byte_addr → page_addr`),导致先前所有实验结论无效。详见 `docs/GD5F2GQ5UE_Trap_Records.md`
|
||||
|
||||
@@ -293,22 +293,24 @@ static int gd5f_set_feature(uint8_t addr, uint8_t data) {
|
||||
}
|
||||
|
||||
/*
|
||||
* 函数功能:块擦除(擦<EFBFBD>?28KB块)
|
||||
* 入口参数:block_addr - 块编<EFBFBD>? uint32_t 0 - 2047
|
||||
* 返回值:0 - 成功,其<EFBFBD>?- 错误<EFBFBD>? * 限定条件:gd5f2gq5ue_init() 已调<E5B7B2>? * 函数说明<E8AFB4>?. 写使<E58699>?- D8h + 3字节字节地址 - 等待完成
|
||||
* 2. 擦除完成后检<E5908E>?E_FAIL <20>? */
|
||||
* 函数功能:块擦除(擦除128KB块)
|
||||
* 入口参数:block_addr - 块编号 uint32_t 0 - 2047
|
||||
* 返回值:0 - 成功,其他 - 错误码
|
||||
* 限定条件:gd5f2gq5ue_init() 已调用
|
||||
* 函数说明:1. 写使能 - D8h + 3字节页地址 - 等待完成
|
||||
* 2. 擦除完成后检查 E_FAIL */
|
||||
int gd5f_block_erase(uint32_t block_addr) {
|
||||
int ret = GD5F_OK;
|
||||
uint8_t cmd[4];
|
||||
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();
|
||||
|
||||
cmd[0] = GD5F_CMD_BLOCK_ERASE;
|
||||
cmd[1] = (byte_addr >> 16) & 0xFF;
|
||||
cmd[2] = (byte_addr >> 8) & 0xFF;
|
||||
cmd[3] = byte_addr & 0xFF;
|
||||
cmd[1] = (page_addr >> 16) & 0xFF;
|
||||
cmd[2] = (page_addr >> 8) & 0xFF;
|
||||
cmd[3] = page_addr & 0xFF;
|
||||
|
||||
GD5F_CS_LOW();
|
||||
HAL_SPI_Transmit(&hspi1, cmd, 4, GD5F_SPI_TIMEOUT);
|
||||
@@ -358,6 +360,42 @@ static int gd5f_bbt_scan(void) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
/* ======================== 公共函数定义 ======================== */
|
||||
|
||||
/*
|
||||
|
||||
@@ -161,6 +161,9 @@ int gd5f2gq5ue_is_block_bad(uint32_t block);
|
||||
* 函数说明:更新 BBT,并尝试物理标记该块最后一页 spare byte 0 为 0x00
|
||||
*/
|
||||
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 共享) ==================== */
|
||||
|
||||
|
||||
21
Drivers/BSP/NET/lftpd/LICENSE.txt
Normal file
21
Drivers/BSP/NET/lftpd/LICENSE.txt
Normal 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.
|
||||
626
Drivers/BSP/NET/lftpd/lftpd.c
Normal file
626
Drivers/BSP/NET/lftpd/lftpd.c
Normal 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;
|
||||
}
|
||||
19
Drivers/BSP/NET/lftpd/lftpd.h
Normal file
19
Drivers/BSP/NET/lftpd/lftpd.h
Normal 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);
|
||||
161
Drivers/BSP/NET/lftpd/lftpd_inet.c
Normal file
161
Drivers/BSP/NET/lftpd/lftpd_inet.c
Normal 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);
|
||||
}
|
||||
204
Drivers/BSP/NET/lftpd/lftpd_io.c
Normal file
204
Drivers/BSP/NET/lftpd/lftpd_io.c
Normal 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);
|
||||
}
|
||||
}
|
||||
18
Drivers/BSP/NET/lftpd/lftpd_log.c
Normal file
18
Drivers/BSP/NET/lftpd/lftpd_log.c
Normal 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);
|
||||
}
|
||||
16
Drivers/BSP/NET/lftpd/lftpd_string.c
Normal file
16
Drivers/BSP/NET/lftpd/lftpd_string.c
Normal 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;
|
||||
}
|
||||
14
Drivers/BSP/NET/lftpd/private/lftpd_inet.h
Normal file
14
Drivers/BSP/NET/lftpd/private/lftpd_inet.h
Normal 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);
|
||||
18
Drivers/BSP/NET/lftpd/private/lftpd_io.h
Normal file
18
Drivers/BSP/NET/lftpd/private/lftpd_io.h
Normal 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);
|
||||
7
Drivers/BSP/NET/lftpd/private/lftpd_log.h
Normal file
7
Drivers/BSP/NET/lftpd/private/lftpd_log.h
Normal 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, ...)
|
||||
42
Drivers/BSP/NET/lftpd/private/lftpd_status.h
Normal file
42
Drivers/BSP/NET/lftpd/private/lftpd_status.h
Normal 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."
|
||||
5
Drivers/BSP/NET/lftpd/private/lftpd_string.h
Normal file
5
Drivers/BSP/NET/lftpd/private/lftpd_string.h
Normal file
@@ -0,0 +1,5 @@
|
||||
#pragma once
|
||||
|
||||
#define CRLF "\r\n"
|
||||
|
||||
char* lftpd_string_trim(char* s);
|
||||
@@ -3,26 +3,26 @@ Rebuild target 'STM32F407-Demo'
|
||||
assembling startup_stm32f407xx.s...
|
||||
compiling crc.c...
|
||||
compiling ringbuf.c...
|
||||
compiling dma.c...
|
||||
compiling gpio.c...
|
||||
compiling spi.c...
|
||||
compiling stm32f4xx_hal_msp.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 sys_clock.c...
|
||||
compiling stm32f4xx_hal_timebase_tim.c...
|
||||
compiling stm32f4xx_hal_rcc_ex.c...
|
||||
compiling usart.c...
|
||||
compiling app_main.c...
|
||||
compiling sys_clock.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 usart.c...
|
||||
compiling stm32f4xx_hal_gpio.c...
|
||||
compiling stm32f4xx_hal_rcc_ex.c...
|
||||
compiling rs485_task.c...
|
||||
compiling adc_task.c...
|
||||
compiling stm32f4xx_hal_rcc.c...
|
||||
compiling net_task.c...
|
||||
compiling freertos.c...
|
||||
compiling stm32f4xx_hal_rcc.c...
|
||||
compiling main.c...
|
||||
compiling stm32f4xx_hal_tim_ex.c...
|
||||
compiling stm32f4xx_hal_tim.c...
|
||||
@@ -33,43 +33,43 @@ compiling fdb_utils.c...
|
||||
compiling fal.c...
|
||||
compiling fal_flash.c...
|
||||
compiling fal_partition.c...
|
||||
compiling croutine.c...
|
||||
compiling stm32f4xx_hal_dma_ex.c...
|
||||
compiling event_groups.c...
|
||||
compiling list.c...
|
||||
compiling stm32f4xx_hal_dma.c...
|
||||
compiling croutine.c...
|
||||
compiling stm32f4xx_hal_pwr_ex.c...
|
||||
compiling stm32f4xx_hal_i2c_ex.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 event_groups.c...
|
||||
compiling system_stm32f4xx.c...
|
||||
compiling stm32f4xx_hal_exti.c...
|
||||
compiling stm32f4xx_hal_cortex.c...
|
||||
compiling list.c...
|
||||
compiling rs485.c...
|
||||
compiling queue.c...
|
||||
compiling fal_flash_gd5f2gq5ue.c...
|
||||
compiling stream_buffer.c...
|
||||
compiling ch395f.c...
|
||||
compiling tpafe5160.c...
|
||||
compiling sd2506.c...
|
||||
compiling gd5f2gq5ue.c...
|
||||
compiling timers.c...
|
||||
compiling stm32f4xx_hal_spi.c...
|
||||
compiling ch395f.c...
|
||||
compiling gd5f2gq5ue.c...
|
||||
compiling queue.c...
|
||||
compiling nand_ftl.c...
|
||||
compiling net_select.c...
|
||||
compiling heap_4.c...
|
||||
compiling stream_buffer.c...
|
||||
compiling stm32f4xx_hal_uart.c...
|
||||
compiling net_select.c...
|
||||
compiling ch395f_test.c...
|
||||
compiling tasks.c...
|
||||
compiling map.c...
|
||||
compiling port.c...
|
||||
compiling journal.c...
|
||||
compiling timers.c...
|
||||
compiling net_socket.c...
|
||||
compiling heap_4.c...
|
||||
compiling tasks.c...
|
||||
compiling stm32f4xx_hal_i2c.c...
|
||||
compiling map.c...
|
||||
compiling journal.c...
|
||||
compiling port.c...
|
||||
compiling ff.c...
|
||||
compiling cmsis_os2.c...
|
||||
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...
|
||||
".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s).
|
||||
Build Time Elapsed: 00:00:17
|
||||
Build Time Elapsed: 00:00:20
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "usart.h"
|
||||
#include "ff.h"
|
||||
#include "flashdb.h"
|
||||
#include "gd5f2gq5ue.h"
|
||||
/* USER CODE END Includes */
|
||||
|
||||
/* Private typedef -----------------------------------------------------------*/
|
||||
@@ -178,8 +179,6 @@ void StartDefaultTask(void *argument)
|
||||
|
||||
/* ==================== 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};
|
||||
@@ -216,6 +215,12 @@ void StartDefaultTask(void *argument)
|
||||
|
||||
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);
|
||||
STORAGE_TEST_CHECK(fdb_ret == FDB_NO_ERR, "fdb_kvdb_init");
|
||||
|
||||
|
||||
31
docs/GD5F2GQ5UE_Trap_Records.md
Normal file
31
docs/GD5F2GQ5UE_Trap_Records.md
Normal 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 必须严格遵守
|
||||
@@ -49,9 +49,18 @@
|
||||
|
||||
**初始化顺序:**
|
||||
```
|
||||
SPI1_Init → gd5f2gq5ue_init() → fdb_kvdb_init() [可选,FlashDB 路径]
|
||||
→ fdb_tsdb_init() [可选,FlashDB 路径]
|
||||
→ f_mount() [触发 FTL 初始化,FatFS 路径]
|
||||
HAL_Init → SystemClock_Config → MX_GPIO_Init → MX_USART1_UART_Init
|
||||
→ MX_SPI2_Init → MX_SPI1_Init
|
||||
→ 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 分区表** — `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 管理的扇区数
|
||||
|
||||
> 调整分区大小时只需修改 `fal_cfg.h` 顶部 6 个宏,所有下游模块自动适配。
|
||||
@@ -220,6 +229,11 @@ DMA 消除了轮询模式下 SPI 状态寄存器查检的逐字节 CPU 开销,
|
||||
- `s_bbt[256]` 位图数组,1 bit 标识 1 个块 (0=好, 1=坏)
|
||||
- `gd5f2gq5ue_is_block_bad(block)` — 查询坏块状态
|
||||
- `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 驱动额外的设备注册。
|
||||
|
||||
#### 已知 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 键值数据库
|
||||
|
||||
适用于存储配置参数、运行状态等少量键值对。
|
||||
@@ -285,6 +308,7 @@ FlashDB 通过 FAL 分区名查找对应的分区设备,无需 FlashDB 驱动
|
||||
- 支持 blob (二进制数据块)
|
||||
- 自动磨损均衡
|
||||
- 支持默认值 (首次启动自动写入)
|
||||
- 支持 select/poll I/O 多路复用(`net_select.c`)
|
||||
|
||||
### 6.4 TSDB 时序数据库
|
||||
|
||||
@@ -445,14 +469,31 @@ f_mkfs("", &opts, work, size)
|
||||
|
||||
| 函数 | 说明 |
|
||||
|------|------|
|
||||
| `gd5f2gq5ue_init()` | 初始化 NAND (含 ECC 使能、块保护解除) |
|
||||
| `gd5f2gq5ue_read_id(mid, did)` | 读芯片 ID |
|
||||
| `gd5f2gq5ue_init()` | 初始化 NAND (BBT 扫描 + ECC 使能 + 块保护解除) |
|
||||
| `gd5f2gq5ue_read_id(p_mid, p_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)` | 标记坏块 |
|
||||
| `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)
|
||||
|
||||
@@ -468,10 +509,12 @@ f_mkfs("", &opts, work, size)
|
||||
| 函数 | 说明 |
|
||||
|------|------|
|
||||
| `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_kv_set(&db, "key", "value")` | 写键值 (字符串) |
|
||||
| `fdb_kv_get(&db, "key")` | 读键值 (返回 char*) |
|
||||
| `fdb_kv_del(&db, "key")` | 删除键值 |
|
||||
| `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_tsl_append(&db, data)` | 追加时序记录 |
|
||||
| `fdb_tsl_iter_by_time(&db, from, to, cb, cb_arg)` | 时间范围查询 |
|
||||
@@ -498,7 +541,7 @@ f_mkfs("", &opts, work, size)
|
||||
#include "fdb_cfg.h"
|
||||
|
||||
/* KVDB 实例 */
|
||||
static fdb_kvdb_t g_kvdb;
|
||||
static struct fdb_kvdb g_kvdb;
|
||||
|
||||
static int kvdb_init(void) {
|
||||
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) {
|
||||
/* 写字符串 */
|
||||
fdb_kvdb_set(&g_kvdb, "device_id", "STM32F407-001");
|
||||
fdb_kv_set(&g_kvdb, "device_id", "STM32F407-001");
|
||||
|
||||
/* 读字符串 */
|
||||
char buf[64];
|
||||
fdb_kvdb_get(&g_kvdb, "device_id", buf, sizeof(buf));
|
||||
const char *val = fdb_kv_get(&g_kvdb, "device_id");
|
||||
|
||||
/* 写二进制 blob */
|
||||
struct fdb_blob blob;
|
||||
uint32_t value = 42;
|
||||
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 初始化与使用
|
||||
|
||||
```c
|
||||
static fdb_tsdb_t g_tsdb;
|
||||
static struct fdb_tsdb g_tsdb;
|
||||
|
||||
static time_t get_timestamp(void) {
|
||||
return sys_clock_now(); // 从 RTC 获取 Unix 时间戳
|
||||
@@ -544,7 +594,9 @@ typedef struct {
|
||||
|
||||
static void tsdb_example(void) {
|
||||
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
31
dump_all_corruptions.py
Normal 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
191
fix_chinese_all.py
Normal 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
154
fix_chinese_final.py
Normal 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
139
fix_chinese_generic.py
Normal 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
107
fix_remaining.py
Normal 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 引脚', '~18ns,RD 引脚'),
|
||||
]
|
||||
|
||||
# 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
32
git_compare.py
Normal 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')
|
||||
Reference in New Issue
Block a user