增加文件系统

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

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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