From f72d08328f26979e618ebfae9603e714ff148bf5 Mon Sep 17 00:00:00 2001 From: wandering1 <1624155937@qq.com> Date: Sun, 19 Jul 2026 00:59:56 +0800 Subject: [PATCH] =?UTF-8?q?TCP=E5=8F=AF=E4=BB=A5=E6=AD=A3=E5=B8=B8?= =?UTF-8?q?=E4=BD=BF=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 59 ++ Drivers/BSP/CH395F/ch395f.c | 689 +++++++++++++--- Drivers/BSP/CH395F/ch395f.h | 277 ++++++- Drivers/BSP/NET/net_config.h | 105 +++ Drivers/BSP/NET/net_select.c | 319 ++++++++ Drivers/BSP/NET/net_select.h | 114 +++ Drivers/BSP/NET/net_socket.c | 1219 +++++++++++++++++++++++++++ Drivers/BSP/NET/net_socket.h | 227 ++++++ Drivers/BSP/NET/net_types.h | 175 ++++ Drivers/BSP/TPAFE5160/tpafe5160.c | 5 +- Drivers/BSP/TPAFE5160/tpafe5160.h | 9 + Inc/main.h | 2 + Inc/stm32f4xx_it.h | 3 + MDK-ARM/STM32F407-Demo.uvprojx | 205 +++-- MDK-ARM/build.bat | 19 + MDK-ARM/build_log.txt | 49 ++ STM32F407-Demo.ioc | 164 ++-- Src/dma.c | 7 + Src/gpio.c | 8 +- Src/main.c | 143 ++-- Src/spi.c | 52 +- Src/stm32f4xx_it.c | 45 + docs/BSD_Socket_API_使用指南.md | 1268 +++++++++++++++++++++++++++++ test/ch395f_socket_test.py | 694 ++++++++++++++++ 24 files changed, 5505 insertions(+), 352 deletions(-) create mode 100644 Drivers/BSP/NET/net_config.h create mode 100644 Drivers/BSP/NET/net_select.c create mode 100644 Drivers/BSP/NET/net_select.h create mode 100644 Drivers/BSP/NET/net_socket.c create mode 100644 Drivers/BSP/NET/net_socket.h create mode 100644 Drivers/BSP/NET/net_types.h create mode 100644 MDK-ARM/build.bat create mode 100644 MDK-ARM/build_log.txt create mode 100644 docs/BSD_Socket_API_使用指南.md create mode 100644 test/ch395f_socket_test.py diff --git a/AGENTS.md b/AGENTS.md index f0337b8..08541cc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -13,6 +13,13 @@ STM32F4-Base/ ├── Drivers/ │ ├── BSP/ │ │ ├── CH395F/ # CH395F 以太网芯片驱动(手写) +│ │ ├── NET/ # BSD Socket API 网络抽象层(手写) +│ │ │ ├── net_config.h # 网络配置宏定义 +│ │ │ ├── net_types.h # 类型定义、地址结构、Socket 控制块 +│ │ │ ├── net_socket.h # BSD Socket API 头文件 +│ │ │ ├── net_socket.c # API 核心实现 + 状态机 +│ │ │ ├── net_select.h # select/poll API 头文件 +│ │ │ └── net_select.c # select/poll 实现 │ │ ├── GD5F2GQ5UE/ # GD5F2GQ5UE NAND Flash 驱动(手写) │ │ │ ├── gd5f2gq5ue.h/c # 底层 SPI 驱动 │ │ │ ├── fal_flash_gd5f2gq5ue.c # FAL 设备适配层 @@ -37,6 +44,8 @@ STM32F4-Base/ |---|---| | `Src/main.c` | 程序入口,初始化序列及主循环 | | `Drivers/BSP/CH395F/ch395f.c/h` | CH395F 以太网芯片 SPI 驱动 | +| `Drivers/BSP/NET/net_socket.c/h` | BSD Socket API 网络抽象层 | +| `Drivers/BSP/NET/net_select.c/h` | select/poll I/O 多路复用 | | `Drivers/BSP/GD5F2GQ5UE/gd5f2gq5ue.c/h` | GD5F2GQ5UE NAND Flash SPI 驱动 | | `Drivers/BSP/GD5F2GQ5UE/fal_flash_gd5f2gq5ue.c` | FAL 设备适配层 | | `Drivers/BSP/TPAFE5160/tpafe5160.c/h` | TPAFE5160 ADC 并行接口驱动 | @@ -52,6 +61,29 @@ STM32F4-Base/ - C 标准:C99 - 全局宏定义:`USE_HAL_DRIVER, STM32F407xx` +### 命令行编译(Agent 使用) + +修改代码后必须执行命令行编译验证,编译脚本位于 `MDK-ARM/build.bat`: + +``` +cmd /c "cd /d "工作目录" && build.bat" +``` + +实际执行示例(从项目根目录): + +``` +cmd /c "cd /d "D:\Code\DTU 程序\STM32F4-Base\MDK-ARM" && build.bat" +``` + +**编译结果判读:** +- 退出码 0:无错误无警告,编译成功 +- 退出码 1:有警告无错误,编译失败(警告不可接受,需修复) +- 退出码 2+:编译失败,需查看 `MDK-ARM/build_log.txt` 定位错误 + +**UV4.exe 路径:** `C:\Keil_v5\UV4\UV4.exe` +**工程文件:** `MDK-ARM/STM32F407-Demo.uvprojx` +**目标名称:** `STM32F407-Demo` + ## 硬件配置 - **主频:** HSE 25MHz → PLL 168MHz (4/168/2) @@ -96,6 +128,11 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init() - **`SET_PHY` 必须在 `INIT_CH395` 之后**,它会复位 MAC/PHY 建立物理链路,不影响已锁定的协议栈参数 - **`CMD_PING_ENABLE` 不需要显式调用**,INIT 后默认可用 - 每次 SPI 事务需调用 `ch395f_spi_begin()` / `ch395f_spi_end()` 包裹 +- **大数据量收发使用 SPI2 DMA**:`ch395f_write_send_buf()` 和 `ch395f_read_recv_buf()` 已改造为 DMA 批量传输(DMA1_Stream3 RX, DMA1_Stream4 TX),命令和配置操作仍使用逐字节轮询 +- DMA 缓冲区:`s_spi2_dma_tx_buf[1500]` / `s_spi2_dma_rx_buf[1500]`,4 字节对齐 +- **TCP 重传参数**:`SET_RETRAN_COUNT`(默认12次,最大20)和 `SET_RETRAN_PERIOD`(默认500ms,最大1000ms)必须在 `INIT_CH395` 之前设置,总重传时间 = 次数 × 周期 +- **KeepAlive 参数必须为 500ms 的倍数**:`SET_KEEPALIVE_IDLE`(默认20000ms)、`SET_KEEPALIVE_INTVL`(默认15000ms)单位为 ms,且 IDLE 必须 > INTVL,均为 500 的倍数;传入非 500 倍数的值会导致未定义行为(CH395F 内部定时器异常,TCP 连接几秒内 TIMEOUT) +- **KeepAlive 默认关闭**:需在 `SINT_STAT_CONNECT` 后调用 `ch395f_set_keepalive_enable(sock, 1)` 启用 ## GD5F2GQ5UE 驱动关键点 @@ -124,6 +161,28 @@ HAL_Init() → SystemClock_Config() → MX_GPIO_Init() → MX_USART1_UART_Init() - 读取时序:168MHz 下 GPIO 写操作 + 5个 NOP (~30ns) 覆盖 t10=22ns 和 t14=21ns 要求 - 硬件 RANGE 接 GND → ±5V 量程,LSB=152.59µV +## NET 网络层关键点 + +- **文件结构**:`Drivers/BSP/NET/` 下包含 net_config.h、net_types.h、net_socket.h/c、net_select.h/c +- **使用前必须调用** `net_init(ip, mask, gateway)` 初始化网络子系统 +- **主循环必须调用** `net_poll()` 轮询所有 Socket 状态,处理中断事件 +- **TCP Server 多连接模式**:1 个监听 Socket + 最多 7 个数据 Socket +- **select/poll 支持**:`net_select()` 和 `net_poll_events()` 用于 I/O 多路复用 +- **事件回调**:可通过 `net_set_event_cb()` 注册连接/断开/数据到达等事件回调 +- **非阻塞模式**:`net_recv()` 使用 `NET_MSG_DONTWAIT` 标志,或使用 select/poll +- **字节序转换**:使用 `net_htons()/net_ntohs()/net_htonl()/net_ntohl()` 进行主机序/网络序转换 +- **IP 地址转换**:`net_inet_addr("192.168.1.100")` 字符串转网络序,`net_inet_ntoa()` 反向转换 +- **注意**:新文件需手动添加到 Keil MDK 工程中才能编译 +- **RECV 中断是电平触发的**:`net_poll()` 中 `do { ... } while(0)` 只处理一批中断,由主循环读取数据,避免无限循环导致主循环饿死 +- **TCP Server 多连接模式**:Socket 0 专职监听,Socket 1~7 自动分配 +- **多连接模式初始化顺序(关键!遗漏将导致 TCP 连接失败)**: + 1. `ch395f_set_fun_para(0x02)` — 启用多连接(在 `INIT_CH395` 之前) + 2. `ch395f_init()` — 协议栈初始化 + 3. Socket 0: `SET_PROTO_TCP → SET_SOUR_PORT → OPEN → TCP_LISTEN` + 4. **Socket 1~7: `SET_PROTO_TCP → SET_SOUR_PORT(相同端口)`(不 OPEN!)** + - 如果不配置数据 Socket 的协议类型和源端口,CH395F 不知道哪些 Socket 可用于多连接分配,客户端连接会被静默拒绝(无 CONNECT 中断) +- **TCP Server 单连接模式**:Socket 先 `OPEN` → `SET_PROTO_TCP` → `SET_SOUR_PORT` → `OPEN` → `TCP_LISTEN`;收到 `SINT_STAT_CONNECT` 后 Socket 自动切换为数据通道;`DISCONNECT`/`TIMEOUT` 后需重新 `OPEN` → `TCP_LISTEN` + ## 已知问题 ### CH395F 与 RTL8305NBI 自动协商不兼容 diff --git a/Drivers/BSP/CH395F/ch395f.c b/Drivers/BSP/CH395F/ch395f.c index 44fd117..82f70a6 100644 --- a/Drivers/BSP/CH395F/ch395f.c +++ b/Drivers/BSP/CH395F/ch395f.c @@ -7,12 +7,15 @@ * 创建日期:2026-03-14 * 修改记录: * 2026-03-14 王建锋 创建初始版本,按代码规范重构 + * 2026-07-18 王建锋 改造 write_send_buf / read_recv_buf 使用 SPI2 DMA 批量传输 */ /* 头文件包含区 - 先包含本模块头文件,再包含依赖头文件 */ #include "ch395f.h" #include "spi.h" +#include "usart.h" #include +#include /* * 私有宏定义区 @@ -21,12 +24,24 @@ #define CH395F_RESET_DELAY_MS 20U /* 复位后等待延时(ms) */ #define CH395F_INIT_TIMEOUT_MS 500U /* 初始化超时时间(ms) */ #define CH395F_SPI_BEGIN_DELAY_MS 1U /* SPI 事务开始延时(ms) */ +#define CH395F_DMA_BUF_SIZE 1500U /* DMA 缓冲区最大字节数 */ + +/* + * 私有变量区 - DMA 传输相关 + */ +static volatile uint8_t s_spi2_dma_done = 0U; /* DMA 传输完成标志 */ + +__attribute__((aligned(4))) +static uint8_t s_spi2_dma_tx_buf[CH395F_DMA_BUF_SIZE]; /* DMA 发送缓冲区 */ +__attribute__((aligned(4))) +static uint8_t s_spi2_dma_rx_buf[CH395F_DMA_BUF_SIZE]; /* DMA 接收缓冲区 */ /* * 私有函数声明区 */ static void ch395f_spi_begin(void); static void ch395f_spi_end(void); +static void ch395f_spi_dma_transfer(uint16_t len); /* * 私有函数实现区 @@ -34,14 +49,14 @@ static void ch395f_spi_end(void); /* * 函数功能:开始 SPI 事务(CS 拉高 -> 拉低) - * 函数说明:手册要求 SPI 模式下字节间无需间隔 + * 函数说明:手册要求 SPI 模式下字节间无需间隔, + * CS 拉高再拉低满足最小片选间隔要求 */ static void ch395f_spi_begin(void) { /* CS 先拉高,确保空闲状态 */ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); - HAL_Delay(CH395F_SPI_BEGIN_DELAY_MS); - /* CS 拉低,启动事务 */ + /* CS 拉低,启动事务(无需额外延时,SPI 时序由 SCK 频率决定) */ HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_RESET); } @@ -54,6 +69,81 @@ static void ch395f_spi_end(void) HAL_GPIO_WritePin(CH395_SCS_GPIO_Port, CH395_SCS_Pin, GPIO_PIN_SET); } +/* + * 函数功能:DMA 全双工传输并等待完成 + * 入口参数:len - 传输字节数 uint16_t + * 函数说明:发送 s_spi2_dma_tx_buf,接收到 s_spi2_dma_rx_buf, + * 阻塞等待 DMA 完成回调置位标志 + */ +static void ch395f_spi_dma_transfer(uint16_t len) +{ + HAL_StatusTypeDef status; + uint32_t tickstart; + volatile uint32_t loop_cnt = 0U; + + s_spi2_dma_done = 0U; + status = HAL_SPI_TransmitReceive_DMA(&hspi2, s_spi2_dma_tx_buf, s_spi2_dma_rx_buf, len); + if (status != HAL_OK) + { + printf("[DMA] HAL_SPI_TransmitReceive_DMA FAIL: %d, SPI_State=%d, SPI_Error=0x%lX, DMA_State=%d\r\n", + (int)status, + (int)hspi2.State, + hspi2.ErrorCode, + (int)hspi2.hdmarx->State); + return; + } + + tickstart = HAL_GetTick(); + while (s_spi2_dma_done == 0U) + { + loop_cnt++; + if ((HAL_GetTick() - tickstart) > 1000U) + { + printf("[DMA] TIMEOUT after 1s, len=%d, loop=%lu, SPI_State=%d, SPI_Error=0x%lX\r\n", + (int)len, loop_cnt, + (int)hspi2.State, + hspi2.ErrorCode); + printf("[DMA] DMA_RX: State=%d, Dir=%lu, CNT=%lu\r\n", + (int)hspi2.hdmarx->State, + hspi2.hdmarx->Init.Direction, + __HAL_DMA_GET_COUNTER(hspi2.hdmarx)); + printf("[DMA] DMA_TX: State=%d, Dir=%lu, CNT=%lu\r\n", + (int)hspi2.hdmatx->State, + hspi2.hdmatx->Init.Direction, + __HAL_DMA_GET_COUNTER(hspi2.hdmatx)); + printf("[DMA] SPI2_CR1=0x%lX, CR2=0x%lX, SR=0x%lX\r\n", + SPI2->CR1, SPI2->CR2, SPI2->SR); + /* 强制复位 SPI2 DMA 状态,避免死锁 */ + HAL_SPI_Abort(&hspi2); + return; + } + } +} + +/* + * 函数功能:SPI 收发完成回调 + * 函数说明:由 HAL SPI 驱动在 DMA TX+RX 完成后调用(HAL_SPI_TransmitReceive_DMA 触发此回调) + */ +void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) +{ + if (hspi->Instance == SPI2) + { + s_spi2_dma_done = 1U; + } +} + +/* + * 函数功能:SPI 错误回调 + * 函数说明:由 HAL SPI 驱动在 DMA 传输出错时调用,释放等待 + */ +void HAL_SPI_ErrorCallback(SPI_HandleTypeDef *hspi) +{ + if (hspi->Instance == SPI2) + { + s_spi2_dma_done = 1U; + } +} + /* * 公共函数实现区 */ @@ -164,6 +254,299 @@ uint8_t ch395f_get_version(void) return version; } +/* + * 函数功能:获取远端 IP 和端口 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * p_buf - 6 字节输出缓冲区(4字节IP + 2字节端口) + * 限定条件:TCP Server 模式下连接建立后调用 + * 函数说明:IP 低字节在前,端口低字节在前 + */ +void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf) +{ + uint8_t i; + + /* 检查输入参数合法性 */ + if (p_buf == NULL) + { + return; + } + + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送获取远端 IP 和端口命令 */ + ch395f_write_cmd(CH395F_CMD_GET_REMOT_IPP_SN); + + /* 发送 Socket 索引 */ + ch395f_write_data(sock); + + /* 读取 4 字节 IP 地址 */ + for (i = 0; i < 4; i++) + { + p_buf[i] = ch395f_read_data(); + } + + /* 读取 2 字节端口号 */ + for (i = 4; i < 6; i++) + { + p_buf[i] = ch395f_read_data(); + } + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 CH395 功能参数 + * 入口参数:para - 4 字节功能参数 uint32_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_fun_para(uint32_t para) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置功能参数命令 */ + ch395f_write_cmd(CH395F_CMD_SET_FUN_PARA); + + /* 发送 4 字节参数(低字节在前) */ + ch395f_write_data((uint8_t)(para)); + ch395f_write_data((uint8_t)(para >> 8)); + ch395f_write_data((uint8_t)(para >> 16)); + ch395f_write_data((uint8_t)(para >> 24)); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 TCP MSS + * 入口参数:mss - MSS 值 uint16_t 60~1460 + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_tcp_mss(uint16_t mss) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置 TCP MSS 命令 */ + ch395f_write_cmd(CH395F_CMD_SET_TCP_MSS); + + /* 发送 2 字节 MSS 值(低字节在前) */ + ch395f_write_data(mss & 0xFFU); + ch395f_write_data((mss >> 8) & 0xFFU); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 Socket 接收缓冲区 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * start_block - 起始块地址 uint8_t + * block_count - 块个数 uint8_t + * 限定条件:必须在 OPEN_SOCKET_SN 之前调用 + */ +void ch395f_set_recv_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置接收缓冲区命令 */ + ch395f_write_cmd(CH395F_CMD_SET_RECV_BUF); + + /* 发送 Socket 索引 */ + ch395f_write_data(sock); + + /* 发送起始块地址 */ + ch395f_write_data(start_block); + + /* 发送块个数 */ + ch395f_write_data(block_count); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 Socket 发送缓冲区 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * start_block - 起始块地址 uint8_t + * block_count - 块个数 uint8_t + * 限定条件:必须在 OPEN_SOCKET_SN 之前调用 + */ +void ch395f_set_send_buf(uint8_t sock, uint8_t start_block, uint8_t block_count) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置发送缓冲区命令 */ + ch395f_write_cmd(CH395F_CMD_SET_SEND_BUF); + + /* 发送 Socket 索引 */ + ch395f_write_data(sock); + + /* 发送起始块地址 */ + ch395f_write_data(start_block); + + /* 发送块个数 */ + ch395f_write_data(block_count); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:清空 Socket 接收缓冲区 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 限定条件:Socket 已打开 + */ +void ch395f_clear_recv_buf(uint8_t sock) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送清空接收缓冲区命令 */ + ch395f_write_cmd(CH395F_CMD_CLEAR_RECV_BUF_SN); + + /* 发送 Socket 索引 */ + ch395f_write_data(sock); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 TCP KeepAlive 空闲时间 + * 入口参数:time - 空闲时间(秒) uint32_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_keepalive_idle(uint32_t time) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置 KeepAlive 空闲时间命令 */ + ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_IDLE); + + /* 发送 4 字节时间值(低字节在前) */ + ch395f_write_data((uint8_t)(time)); + ch395f_write_data((uint8_t)(time >> 8)); + ch395f_write_data((uint8_t)(time >> 16)); + ch395f_write_data((uint8_t)(time >> 24)); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 TCP KeepAlive 探测间隔 + * 入口参数:time - 间隔时间(秒) uint32_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_keepalive_intvl(uint32_t time) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置 KeepAlive 探测间隔命令 */ + ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_INTVL); + + /* 发送 4 字节时间值(低字节在前) */ + ch395f_write_data((uint8_t)(time)); + ch395f_write_data((uint8_t)(time >> 8)); + ch395f_write_data((uint8_t)(time >> 16)); + ch395f_write_data((uint8_t)(time >> 24)); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 TCP KeepAlive 探测次数 + * 入口参数:cnt - 探测次数 uint8_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_keepalive_cnt(uint8_t cnt) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置 KeepAlive 探测次数命令 */ + ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_CNT); + + /* 发送探测次数 */ + ch395f_write_data(cnt); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:启用/禁用指定 Socket 的 KeepAlive + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * enable - 1 启用,0 禁用 uint8_t + * 限定条件:Socket 已打开 + */ +void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置 KeepAlive 使能命令 */ + ch395f_write_cmd(CH395F_CMD_SET_KEEPALIVE_SN); + + /* 发送 Socket 索引 */ + ch395f_write_data(sock); + + /* 发送使能标志 */ + ch395f_write_data(enable); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 TCP 重传次数 + * 入口参数:cnt - 重传次数 uint8_t(0=使用默认值5) + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_retrans_count(uint8_t cnt) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置重传次数命令 */ + ch395f_write_cmd(CH395F_CMD_SET_RETRAN_COUNT); + + /* 发送重传次数 */ + ch395f_write_data(cnt); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + +/* + * 函数功能:设置 TCP 重传周期 + * 入口参数:period - 重传周期(毫秒) uint16_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_retrans_period(uint16_t period) +{ + /* 开始 SPI 事务 */ + ch395f_spi_begin(); + + /* 发送设置重传周期命令 */ + ch395f_write_cmd(CH395F_CMD_SET_RETRAN_PERIOD); + + /* 发送 2 字节周期值(低字节在前) */ + ch395f_write_data(period & 0xFFU); + ch395f_write_data((period >> 8) & 0xFFU); + + /* 结束 SPI 事务 */ + ch395f_spi_end(); +} + /* * 函数功能:软件复位 CH395 * 返回值:CH395F_STATUS_OK @@ -211,55 +594,58 @@ uint8_t ch395f_get_cmd_status(void) return status; } +/* + * 函数功能:轮询等待命令执行完成 + * 入口参数:timeout_ms - 超时时间(毫秒) uint32_t + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 或 CH395F_CMD_RET_ABORT) + * 限定条件:在发送长执行命令后调用 + * 函数说明:每 20ms 查询一次,直到命令完成或超时 + */ +uint8_t ch395f_poll_cmd_status(uint32_t timeout_ms) +{ + uint8_t status; + uint32_t retry_count = 0U; + uint32_t max_retries = (timeout_ms + 20U - 1U) / 20U; + + while (1) + { + HAL_Delay(20U); + status = ch395f_get_cmd_status(); + + if (status != CH395F_ERR_BUSY) + { + return status; + } + + if (++retry_count >= max_retries) + { + return CH395F_ERR_BUSY; /* 超时 */ + } + } +} + /* * 函数功能:初始化 CH395(包含 MAC、PHY、TCP/IP 协议栈) * 返回值:CH395F_STATUS_OK - 成功,CH395F_STATUS_TIMEOUT - 超时失败 * 限定条件:芯片已通过 ch395f_reset() 复位 - * 函数说明:阻塞等待初始化完成或超时(典型 5ms,TE1) + * 函数说明:阻塞等待初始化完成或超时(芯片需要约 200ms,这里等最多 4s) */ ch395f_status_t ch395f_init(void) { uint8_t status; - uint32_t retry_count = 0U; - /* 开始 SPI 事务 */ ch395f_spi_begin(); - - /* 发送初始化命令 */ ch395f_write_cmd(CH395F_CMD_INIT_CH395); - - /* 结束 SPI 事务 */ ch395f_spi_end(); - /* 轮询等待初始化完成 */ - while (1) - { - /* 每次轮询延时 1ms */ - HAL_Delay(1U); + status = ch395f_poll_cmd_status(4000U); - /* 查询命令执行状态 */ - status = ch395f_get_cmd_status(); - - /* 状态非 BUSY 表示执行完毕 */ - if (status != CH395F_ERR_BUSY) - { - break; - } - - /* 超时判断 */ - if (++retry_count > CH395F_INIT_TIMEOUT_MS) - { - return CH395F_STATUS_TIMEOUT; - } - } - - /* 返回最终执行结果 */ if (status == CH395F_ERR_SUCCESS) { return CH395F_STATUS_OK; } - return CH395F_STATUS_ERROR; + return (status == CH395F_ERR_BUSY) ? CH395F_STATUS_TIMEOUT : CH395F_STATUS_ERROR; } /* @@ -673,114 +1059,95 @@ void ch395f_set_sour_port(uint8_t sock, uint16_t port) /* * 函数功能:打开 Socket - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:协议类型、目标 IP、端口已设置 */ -void ch395f_open_socket(uint8_t sock) +uint8_t ch395f_open_socket(uint8_t sock) { - /* 开始 SPI 事务 */ ch395f_spi_begin(); - - /* 发送打开 Socket 命令 */ ch395f_write_cmd(CH395F_CMD_OPEN_SOCKET_SN); - - /* 发送 Socket 索引 */ ch395f_write_data(sock); - - /* 结束 SPI 事务 */ ch395f_spi_end(); + + return ch395f_poll_cmd_status(4000U); } /* * 函数功能:关闭 Socket - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:Socket 已打开 */ -void ch395f_close_socket(uint8_t sock) +uint8_t ch395f_close_socket(uint8_t sock) { - /* 开始 SPI 事务 */ ch395f_spi_begin(); - - /* 发送关闭 Socket 命令 */ ch395f_write_cmd(CH395F_CMD_CLOSE_SOCKET_SN); - - /* 发送 Socket 索引 */ ch395f_write_data(sock); - - /* 结束 SPI 事务 */ ch395f_spi_end(); + + return ch395f_poll_cmd_status(4000U); } /* * 函数功能:启动 TCP 监听模式 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:Socket 已打开且协议类型为 TCP */ -void ch395f_tcp_listen(uint8_t sock) +uint8_t ch395f_tcp_listen(uint8_t sock) { - /* 开始 SPI 事务 */ ch395f_spi_begin(); - - /* 发送 TCP 监听命令 */ ch395f_write_cmd(CH395F_CMD_TCP_LISTEN_SN); - - /* 发送 Socket 索引 */ ch395f_write_data(sock); - - /* 结束 SPI 事务 */ ch395f_spi_end(); + + return ch395f_poll_cmd_status(4000U); } /* * 函数功能:启动 TCP 连接 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:Socket 已打开且协议类型为 TCP */ -void ch395f_tcp_connect(uint8_t sock) +uint8_t ch395f_tcp_connect(uint8_t sock) { - /* 开始 SPI 事务 */ ch395f_spi_begin(); - - /* 发送 TCP 连接命令 */ ch395f_write_cmd(CH395F_CMD_TCP_CONNECT_SN); - - /* 发送 Socket 索引 */ ch395f_write_data(sock); - - /* 结束 SPI 事务 */ ch395f_spi_end(); + + return ch395f_poll_cmd_status(4000U); } /* * 函数功能:断开 TCP 连接 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:TCP 已建立连接 */ -void ch395f_tcp_disconnect(uint8_t sock) +uint8_t ch395f_tcp_disconnect(uint8_t sock) { - /* 开始 SPI 事务 */ ch395f_spi_begin(); - - /* 发送 TCP 断开命令 */ ch395f_write_cmd(CH395F_CMD_TCP_DISCONNECT_SN); - - /* 发送 Socket 索引 */ ch395f_write_data(sock); - - /* 结束 SPI 事务 */ ch395f_spi_end(); + + return ch395f_poll_cmd_status(4000U); } /* - * 函数功能:向 Socket 发送缓冲区写入数据 + * 函数功能:向 Socket 发送缓冲区写入数据(DMA 批量传输) * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * p_data - 数据指针 uint8_t* * len - 数据长度 uint16_t * 限定条件:指针非空,长度大于 0,TCP 已连接或 UDP 已打开 + * 函数说明:将命令头和数据打包到 DMA 缓冲区,一次 SPI DMA 传输完成 */ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { - uint16_t i; + uint16_t total; /* 检查输入参数合法性 */ if (p_data == NULL) @@ -788,41 +1155,40 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len) return; } - /* 开始 SPI 事务 */ - ch395f_spi_begin(); - - /* 发送写入发送缓冲区命令 */ - ch395f_write_cmd(CH395F_CMD_WRITE_SEND_BUF_SN); - - /* 发送 Socket 索引 */ - ch395f_write_data(sock); - - /* 发送数据长度低字节 */ - ch395f_write_data(len & 0xFFU); - - /* 发送数据长度高字节 */ - ch395f_write_data((len >> 8) & 0xFFU); - - /* 逐字节发送数据 */ - for (i = 0; i < len; i++) + /* 计算总长度:1(cmd) + 1(sock) + 2(len) + data */ + total = 4U + len; + if (total > CH395F_DMA_BUF_SIZE) { - ch395f_write_data(p_data[i]); + return; } - /* 结束 SPI 事务 */ + /* 打包命令头到 DMA 发送缓冲区 */ + s_spi2_dma_tx_buf[0] = CH395F_CMD_WRITE_SEND_BUF_SN; + s_spi2_dma_tx_buf[1] = sock; + s_spi2_dma_tx_buf[2] = len & 0xFFU; + s_spi2_dma_tx_buf[3] = (len >> 8) & 0xFFU; + + /* 拷贝用户数据到 DMA 发送缓冲区 */ + memcpy(&s_spi2_dma_tx_buf[4], p_data, len); + + /* CS 拉低 -> DMA 批量传输 -> CS 拉高 */ + ch395f_spi_begin(); + ch395f_spi_dma_transfer(total); ch395f_spi_end(); } /* - * 函数功能:从 Socket 接收缓冲区读取数据 + * 函数功能:从 Socket 接收缓冲区读取数据(DMA 批量传输) * 入口参数:sock - Socket 索引 uint8_t 0 - 3 * p_data - 输出缓冲区指针 uint8_t* * len - 待读取数据长度 uint16_t * 限定条件:指针非空,长度大于 0,接收缓冲区有数据 + * 函数说明:命令头打包到发送缓冲区(数据部分填 0xFF 作时钟), + * 一次 SPI DMA 全双工传输,有效数据从接收缓冲区偏移 4 字节处拷出 */ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) { - uint16_t i; + uint16_t total; /* 检查输入参数合法性 */ if (p_data == NULL) @@ -830,29 +1196,29 @@ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len) return; } - /* 开始 SPI 事务 */ - ch395f_spi_begin(); - - /* 发送读取接收缓冲区命令 */ - ch395f_write_cmd(CH395F_CMD_READ_RECV_BUF_SN); - - /* 发送 Socket 索引 */ - ch395f_write_data(sock); - - /* 发送读取长度低字节 */ - ch395f_write_data(len & 0xFFU); - - /* 发送读取长度高字节 */ - ch395f_write_data((len >> 8) & 0xFFU); - - /* 逐字节读取数据 */ - for (i = 0; i < len; i++) + /* 计算总长度:1(cmd) + 1(sock) + 2(len) + data */ + total = 4U + len; + if (total > CH395F_DMA_BUF_SIZE) { - p_data[i] = ch395f_read_data(); + return; } - /* 结束 SPI 事务 */ + /* 命令头打包到 DMA 发送缓冲区 */ + s_spi2_dma_tx_buf[0] = CH395F_CMD_READ_RECV_BUF_SN; + s_spi2_dma_tx_buf[1] = sock; + s_spi2_dma_tx_buf[2] = len & 0xFFU; + s_spi2_dma_tx_buf[3] = (len >> 8) & 0xFFU; + + /* 数据部分填充 0xFF(SPI 时钟驱动用) */ + memset(&s_spi2_dma_tx_buf[4], 0xFFU, len); + + /* CS 拉低 -> DMA 全双工传输(发命令头 + 收数据) -> CS 拉高 */ + ch395f_spi_begin(); + ch395f_spi_dma_transfer(total); ch395f_spi_end(); + + /* 从 DMA 接收缓冲区偏移 4 字节处拷贝有效数据到用户缓冲区 */ + memcpy(p_data, &s_spi2_dma_rx_buf[4], len); } /* @@ -910,6 +1276,24 @@ uint8_t ch395f_get_glob_int_status(void) return status; } +/* + * 函数功能:获取全局中断状态(2 字节版,支持 Socket 0~7) + * 返回值:中断状态 uint16_t(低字节=Socket 0~3 + PHY/DHCP,高字节=Socket 4~7 + DHCPv6) + * 限定条件:芯片已初始化 + */ +uint16_t ch395f_get_glob_int_status_all(void) +{ + uint16_t status; + + ch395f_spi_begin(); + ch395f_write_cmd(CH395F_CMD_GET_GLOB_INT_STATUS_ALL); + status = (uint16_t)ch395f_read_data(); + status |= ((uint16_t)ch395f_read_data() << 8U); + ch395f_spi_end(); + + return status; +} + /* * 函数功能:获取 Socket 中断状态 * 入口参数:sock - Socket 索引 uint8_t 0 - 3 @@ -937,3 +1321,68 @@ uint8_t ch395f_get_sock_int_status(uint8_t sock) return status; } + +/* + * 函数功能:获取 Socket 状态(2 字节输出) + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * status - 2 字节输出缓冲区 [0]=Socket状态, [1]=TCP状态 + * 限定条件:芯片已初始化 + */ +void ch395f_get_socket_status(uint8_t sock, uint8_t *status) +{ + ch395f_spi_begin(); + ch395f_write_cmd(CH395F_CMD_GET_SOCKET_STATUS_SN); + ch395f_write_data(sock); + status[0] = ch395f_read_data(); + status[1] = ch395f_read_data(); + ch395f_spi_end(); +} + +/* + * 函数功能:设置 ARP 重传参数 + * 入口参数:period_100ms - ARP 重传周期(单位 100ms,0=默认值=10即1秒) uint8_t + * cnt - ARP 重传次数(0=默认值=3) uint8_t + * 限定条件:应在 ch395f_init() 之后调用 + * 函数说明:周期最大值约 25.5 秒,255 表示无限重传 + */ +void ch395f_set_arp(uint8_t period_100ms, uint8_t cnt) +{ + ch395f_spi_begin(); + ch395f_write_cmd(CH395F_CMD_SET_ARP); + ch395f_write_data(period_100ms); + ch395f_write_data(cnt); + ch395f_spi_end(); +} + +/* + * 函数功能:设置 Socket TTL + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * ttl - TTL 值 uint8_t 最大 128 + * 限定条件:Socket 已打开,TCP Server 模式下不生效 + */ +void ch395f_set_ttl(uint8_t sock, uint8_t ttl) +{ + ch395f_spi_begin(); + ch395f_write_cmd(CH395F_CMD_SET_TTL); + ch395f_write_data(sock); + ch395f_write_data(ttl); + ch395f_spi_end(); +} + +/* + * 函数功能:获取不可达信息(用于调试连接失败原因) + * 出口参数:p_buf - 8 字节输出缓冲区(类型+协议码+端口+IP) + * 限定条件:收到 GINT_STAT_UNREACH 中断后调用 + */ +void ch395f_get_unreach_info(uint8_t *p_buf) +{ + uint8_t i; + + ch395f_spi_begin(); + ch395f_write_cmd(CH395F_CMD_GET_UNREACH_IPPT); + for (i = 0; i < 8; i++) + { + p_buf[i] = ch395f_read_data(); + } + ch395f_spi_end(); +} diff --git a/Drivers/BSP/CH395F/ch395f.h b/Drivers/BSP/CH395F/ch395f.h index f965ad6..c3df638 100644 --- a/Drivers/BSP/CH395F/ch395f.h +++ b/Drivers/BSP/CH395F/ch395f.h @@ -35,6 +35,7 @@ extern "C" { #define CH395F_CMD_GET_CMD_STATUS 0x2CU #define CH395F_CMD_GET_GLOB_INT_STATUS 0x29U #define CH395F_CMD_GET_GLOB_INT_STATUS_ALL 0x19U +#define CH395F_CMD_GET_UNREACH_IPPT 0x28U #define CH395F_CMD_GET_PHY_STATUS 0x26U #define CH395F_CMD_GET_DHCP_STATUS 0x42U @@ -124,19 +125,26 @@ extern "C" { #define CH395F_CMD_WRITE_GPIO_REG 0xEDU /* - * CH395 错误码(来自手册 CH395INC.H 引用) + * CH395 命令执行状态码(CMD_GET_CMD_STATUS 返回值,与芯片手册一致) */ -#define CH395F_ERR_SUCCESS 0x00U -#define CH395F_ERR_BUSY 0x01U -#define CH395F_ERR_CMD 0x02U -#define CH395F_ERR_MAC 0x03U -#define CH395F_ERR_PHY 0x04U -#define CH395F_ERR_IP_CONFLI 0x05U -#define CH395F_ERR_SOCK 0x06U -#define CH395F_ERR_SOCK_BUSY 0x07U -#define CH395F_ERR_SOCK_CLOSED 0x08U -#define CH395F_ERR_SOCK_ERR 0x09U -#define CH395F_ERR_UNKNOW 0xFFU +#define CH395F_ERR_SUCCESS 0x00U /* 成功 */ +#define CH395F_ERR_BUSY 0x10U /* 忙,命令正在执行 */ +#define CH395F_ERR_MEM 0x11U /* 内存管理错误 */ +#define CH395F_ERR_BUF 0x12U /* 缓冲区错误 */ +#define CH395F_ERR_TIMEOUT 0x13U /* 超时 */ +#define CH395F_ERR_RTE 0x14U /* 路由错误 */ +#define CH395F_ERR_ABRT 0x15U /* 连接中止 */ +#define CH395F_ERR_RST 0x16U /* 连接复位 */ +#define CH395F_ERR_CLSD 0x17U /* 连接关闭 */ +#define CH395F_ERR_CONN 0x18U /* 无连接 */ +#define CH395F_ERR_VAL 0x19U /* 值错误 */ +#define CH395F_ERR_ARG 0x1AH /* 参数错误 */ +#define CH395F_ERR_USE 0x1BH /* 已被使用 */ +#define CH395F_ERR_IF 0x1CH /* MAC 错误 */ +#define CH395F_ERR_ISCONN 0x1DH /* 已连接 */ +#define CH395F_ERR_OPEN 0x20U /* 已打开 */ +#define CH395F_CMD_RET_ABORT 0x5FU /* 命令中止(来自官方库) */ +#define CH395F_ERR_UNKNOW 0xFAU /* 未知错误」 */ /* * PHY 状态码 @@ -148,6 +156,13 @@ extern "C" { #define CH395F_PHY_100M_HALF 0x10U /* 100M 半双工 */ #define CH395F_PHY_AUTO_NEGOTIATE 0x20U /* 自动协商 */ +/* + * 功能参数标志位(CMD_SET_FUN_PARA) + */ +#define CH395F_FUN_PARA_TCP_SERVER (1UL << 1) /* TCP Server 多连接模式 */ +#define CH395F_FUN_PARA_SOCKET_CLOSE (1UL << 3) /* Socket 关闭控制(0=芯片自动,1=外部控制) */ +#define CH395F_FUN_PARA_DISABLE_SEND_OK (1UL << 4) /* 禁用 SEND_OK 中断 */ + /* * 协议类型 */ @@ -178,7 +193,7 @@ extern "C" { #define CH395F_TCP_TIME_WAIT 0x0AU /* - * 全局中断状态位 + * 全局中断状态位(CMD_GET_GLOB_INT_STATUS 1字节版,仅Socket 0~3) */ #define CH395F_GINT_STAT_UNREACH 0x01U #define CH395F_GINT_STAT_IP_CONFLI 0x02U @@ -190,15 +205,25 @@ extern "C" { #define CH395F_GINT_STAT_SOCK3 0x80U /* - * Socket 中断状态位 + * 全局中断状态位(CMD_GET_GLOB_INT_STATUS_ALL 2字节版,支持Socket 0~7) + * 低字节高字节分别定义 */ -#define CH395F_SINT_STAT_CONNECT 0x01U -#define CH395F_SINT_STAT_DISCONNECT 0x02U -#define CH395F_SINT_STAT_SEND_OK 0x04U -#define CH395F_SINT_STAT_SENBUF_FREE 0x08U -#define CH395F_SINT_STAT_RECV_OK 0x10U -#define CH395F_SINT_STAT_DISCARD 0x20U -#define CH395F_SINT_STAT_SOCK_TIMEOUT 0x40U +#define CH395F_GINT_STAT_SOCK4 0x0100U +#define CH395F_GINT_STAT_SOCK5 0x0200U +#define CH395F_GINT_STAT_SOCK6 0x0400U +#define CH395F_GINT_STAT_SOCK7 0x0800U +#define CH395F_GINT_STAT_DHCPV6 0x8000U + +/* + * Socket 中断状态位(CMD_GET_INT_STATUS_SN 返回值,来自手册表5-x) + */ +#define CH395F_SINT_STAT_SENBUF_FREE 0x01U /* bit0: 发送缓冲区空闲 */ +#define CH395F_SINT_STAT_SEND_OK 0x02U /* bit1: 发送成功 */ +#define CH395F_SINT_STAT_RECV_OK 0x04U /* bit2: 接收缓冲区非空 */ +#define CH395F_SINT_STAT_CONNECT 0x08U /* bit3: TCP 连接成功 */ +#define CH395F_SINT_STAT_DISCONNECT 0x10U /* bit4: TCP 连接断开 */ +#define CH395F_SINT_STAT_DISCARD 0x20U /* bit5: 保留 */ +#define CH395F_SINT_STAT_SOCK_TIMEOUT 0x40U /* bit6: 超时 */ /* * 类型定义区 - 驱动返回码 @@ -277,6 +302,15 @@ ch395f_status_t ch395f_reset(void); */ uint8_t ch395f_get_cmd_status(void); +/* + * 函数功能:轮询等待命令执行完成 + * 入口参数:timeout_ms - 超时时间(毫秒) uint32_t + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 或 CH395F_CMD_RET_ABORT) + * 限定条件:在发送长执行命令(OPEN/TCP_LISTEN/CONNECT/DISCONNECT/CLOSE/DHCP)后调用 + * 函数说明:每 20ms 查询一次,直到命令完成或超时 + */ +uint8_t ch395f_poll_cmd_status(uint32_t timeout_ms); + /* * 函数功能:初始化 CH395(包含 MAC、PHY、TCP/IP 协议栈) * 返回值:CH395F_STATUS_OK - 成功,CH395F_STATUS_TIMEOUT - 超时失败 @@ -382,7 +416,7 @@ uint8_t ch395f_get_dhcp_status(void); /* * 函数功能:设置 Socket 协议类型 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * proto - 协议类型 uint8_t CH395F_PROTO_TYPE_xxx * 限定条件:Socket 未打开 */ @@ -390,7 +424,7 @@ void ch395f_set_proto_type(uint8_t sock, uint8_t proto); /* * 函数功能:设置 Socket 目标 IP 地址 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * p_ip - 4 字节目标 IP 指针 uint8_t* * 限定条件:指针非空 */ @@ -398,7 +432,7 @@ void ch395f_set_des_ip(uint8_t sock, uint8_t *p_ip); /* * 函数功能:设置 Socket 目标端口(小端序) - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * port - 目标端口 uint16_t * 限定条件:无 */ @@ -406,7 +440,7 @@ void ch395f_set_des_port(uint8_t sock, uint16_t port); /* * 函数功能:设置 Socket 源端口(小端序) - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * port - 源端口 uint16_t * 限定条件:无 */ @@ -414,38 +448,43 @@ void ch395f_set_sour_port(uint8_t sock, uint16_t port); /* * 函数功能:打开 Socket - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:协议类型、目标 IP、端口已设置 */ -void ch395f_open_socket(uint8_t sock); +uint8_t ch395f_open_socket(uint8_t sock); /* * 函数功能:关闭 Socket - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:Socket 已打开 */ -void ch395f_close_socket(uint8_t sock); +uint8_t ch395f_close_socket(uint8_t sock); /* * 函数功能:启动 TCP 监听模式 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:Socket 已打开且协议类型为 TCP */ -void ch395f_tcp_listen(uint8_t sock); +uint8_t ch395f_tcp_listen(uint8_t sock); /* * 函数功能:启动 TCP 连接 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:Socket 已打开且协议类型为 TCP */ -void ch395f_tcp_connect(uint8_t sock); +uint8_t ch395f_tcp_connect(uint8_t sock); /* * 函数功能:断开 TCP 连接 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 返回值:命令执行状态码(CH395F_ERR_SUCCESS 表示成功) uint8_t * 限定条件:TCP 已建立连接 */ -void ch395f_tcp_disconnect(uint8_t sock); +uint8_t ch395f_tcp_disconnect(uint8_t sock); /* * 函数声明区 - Socket 数据传输 @@ -453,7 +492,7 @@ void ch395f_tcp_disconnect(uint8_t sock); /* * 函数功能:向 Socket 发送缓冲区写入数据 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * p_data - 数据指针 uint8_t* * len - 数据长度 uint16_t * 限定条件:指针非空,长度大于 0,TCP 已连接或 UDP 已打开 @@ -462,7 +501,7 @@ void ch395f_write_send_buf(uint8_t sock, uint8_t *p_data, uint16_t len); /* * 函数功能:从 Socket 接收缓冲区读取数据 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * p_data - 输出缓冲区指针 uint8_t* * len - 待读取数据长度 uint16_t * 限定条件:指针非空,长度大于 0,接收缓冲区有数据 @@ -471,7 +510,7 @@ void ch395f_read_recv_buf(uint8_t sock, uint8_t *p_data, uint16_t len); /* * 函数功能:获取 Socket 接收缓冲区数据长度 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * 返回值:接收数据长度 uint16_t * 限定条件:Socket 已打开 */ @@ -488,14 +527,174 @@ uint16_t ch395f_get_recv_len(uint8_t sock); */ uint8_t ch395f_get_glob_int_status(void); +/* + * 函数功能:获取全局中断状态(2 字节版,支持 Socket 0~7) + * 返回值:中断状态 uint16_t(低字节=Socket 0~3 + PHY/DHCP,高字节=Socket 4~7 + DHCPv6) + * 限定条件:芯片已初始化 + */ +uint16_t ch395f_get_glob_int_status_all(void); + /* * 函数功能:获取 Socket 中断状态 - * 入口参数:sock - Socket 索引 uint8_t 0 - 3 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 * 返回值:Socket 中断状态字节 uint8_t * 限定条件:芯片已初始化 */ uint8_t ch395f_get_sock_int_status(uint8_t sock); +/* + * 函数功能:获取 Socket 状态(2 字节输出) + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * status - 2 字节输出缓冲区 [0]=Socket状态, [1]=TCP状态 + * 限定条件:芯片已初始化 + * 函数说明:Socket 状态: 00H=CLOSED, 05H=OPEN + * TCP 状态: 00H=CLOSED, 01H=LISTEN, 02H=SYN_SENT, 03H=SYN_RCVD, 04H=ESTABLISHED + */ +void ch395f_get_socket_status(uint8_t sock, uint8_t *status); + +/* + * 函数声明区 - Socket 远端信息 + */ + +/* + * 函数功能:获取远端 IP 和端口 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * p_buf - 6 字节输出缓冲区(4字节IP + 2字节端口) + * 限定条件:TCP Server 模式下连接建立后调用 + * 函数说明:IP 低字节在前,端口低字节在前 + */ +void ch395f_get_remot_ipp(uint8_t sock, uint8_t *p_buf); + +/* + * 函数声明区 - 功能参数设置 + */ + +/* + * 函数功能:设置 CH395 功能参数 + * 入口参数:para - 4 字节功能参数 uint32_t + * bit0: 未使用,必须为 0 + * bit1: TCP Server 多连接模式使能 + * bit2: 低功耗模式 + * bit3: Socket 关闭控制(0=芯片自动关闭,1=外部关闭) + * bit4: 禁用 SEND_OK 中断 + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_fun_para(uint32_t para); + +/* + * 函数声明区 - TCP MSS 设置 + */ + +/* + * 函数功能:设置 TCP MSS + * 入口参数:mss - MSS 值 uint16_t 60~1460 + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_tcp_mss(uint16_t mss); + +/* + * 函数声明区 - Socket 缓冲区设置 + */ + +/* + * 函数功能:设置 Socket 接收缓冲区 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * start_block - 起始块地址 uint8_t + * block_count - 块个数 uint8_t + * 限定条件:必须在 OPEN_SOCKET_SN 之前调用 + */ +void ch395f_set_recv_buf(uint8_t sock, uint8_t start_block, uint8_t block_count); + +/* + * 函数功能:设置 Socket 发送缓冲区 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * start_block - 起始块地址 uint8_t + * block_count - 块个数 uint8_t + * 限定条件:必须在 OPEN_SOCKET_SN 之前调用 + */ +void ch395f_set_send_buf(uint8_t sock, uint8_t start_block, uint8_t block_count); + +/* + * 函数功能:清空 Socket 接收缓冲区 + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * 限定条件:Socket 已打开 + */ +void ch395f_clear_recv_buf(uint8_t sock); + +/* + * 函数功能:设置 TCP KeepAlive 空闲时间 + * 入口参数:time - 空闲时间(毫秒,必须为 500 的倍数) uint32_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_keepalive_idle(uint32_t time); + +/* + * 函数功能:设置 TCP KeepAlive 探测间隔 + * 入口参数:time - 间隔时间(毫秒,必须为 500 的倍数) uint32_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_keepalive_intvl(uint32_t time); + +/* + * 函数功能:设置 TCP KeepAlive 探测次数 + * 入口参数:cnt - 探测次数 uint8_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_keepalive_cnt(uint8_t cnt); + +/* + * 函数功能:启用/禁用指定 Socket 的 KeepAlive + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * enable - 1 启用,0 禁用 uint8_t + * 限定条件:Socket 已打开 + */ +void ch395f_set_keepalive_enable(uint8_t sock, uint8_t enable); + +/* + * 函数声明区 - TCP 重传参数设置 + */ + +/* + * 函数功能:设置 TCP 重传次数 + * 入口参数:cnt - 重传次数 uint8_t(0=使用默认值5) + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_retrans_count(uint8_t cnt); + +/* + * 函数功能:设置 TCP 重传周期 + * 入口参数:period - 重传周期(毫秒) uint16_t + * 限定条件:必须在 ch395f_init() 之前调用 + */ +void ch395f_set_retrans_period(uint16_t period); + +/* + * 函数声明区 - ARP / TTL / 不可达信息 + */ + +/* + * 函数功能:设置 ARP 重传参数 + * 入口参数:period_100ms - ARP 重传周期(单位 100ms,0=默认值=10即1秒) uint8_t + * cnt - ARP 重传次数(0=默认值=3,255=无限重传) uint8_t + * 限定条件:应在 ch395f_init() 之后调用 + */ +void ch395f_set_arp(uint8_t period_100ms, uint8_t cnt); + +/* + * 函数功能:设置 Socket TTL + * 入口参数:sock - Socket 索引 uint8_t 0 - 7 + * ttl - TTL 值 uint8_t 最大 128 + * 限定条件:Socket 已打开,TCP Server 模式下不生效 + */ +void ch395f_set_ttl(uint8_t sock, uint8_t ttl); + +/* + * 函数功能:获取不可达信息(用于调试连接失败原因) + * 出口参数:p_buf - 8 字节输出缓冲区(类型+协议码+端口+IP) + * 限定条件:收到 GINT_STAT_UNREACH 中断后调用 + */ +void ch395f_get_unreach_info(uint8_t *p_buf); + #ifdef __cplusplus } #endif diff --git a/Drivers/BSP/NET/net_config.h b/Drivers/BSP/NET/net_config.h new file mode 100644 index 0000000..b306ed3 --- /dev/null +++ b/Drivers/BSP/NET/net_config.h @@ -0,0 +1,105 @@ +/* + * 模块名称:Network Configuration + * 模块功能:网络子系统配置宏定义,包括最大Socket数量、缓冲区大小、 + * 超时时间等参数配置 + * 适用平台:STM32F4 系列(CH395F 以太网芯片) + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + */ + +#ifndef __NET_CONFIG_H +#define __NET_CONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * 最大支持的 Socket 数量(CH395F 支持 0~7) + */ +#ifndef NET_MAX_SOCKETS +#define NET_MAX_SOCKETS 8 +#endif + +/* + * TCP Server 多连接模式配置 + * 监听 Socket 索引和数据 Socket 起始索引 + */ +#ifndef NET_TCP_SERVER_LISTEN_SOCK +#define NET_TCP_SERVER_LISTEN_SOCK 0 /* 监听 Socket 索引 */ +#endif + +#ifndef NET_TCP_SERVER_DATA_SOCK_START +#define NET_TCP_SERVER_DATA_SOCK_START 1 /* 数据 Socket 起始索引 */ +#endif + +#ifndef NET_TCP_SERVER_MAX_CLIENTS +#define NET_TCP_SERVER_MAX_CLIENTS 7 /* 最大客户端数(1~7) */ +#endif + +/* + * 缓冲区大小配置 + */ +#ifndef NET_SEND_BUF_SIZE +#define NET_SEND_BUF_SIZE 4096 /* 发送缓冲区大小(字节) */ +#endif + +#ifndef NET_RECV_BUF_SIZE +#define NET_RECV_BUF_SIZE 4096 /* 接收缓冲区大小(字节) */ +#endif + +/* + * 超时时间配置(毫秒) + * 0 = 无限等待(阻塞模式) + */ +#ifndef NET_CONNECT_TIMEOUT_MS +#define NET_CONNECT_TIMEOUT_MS 5000 /* 连接超时 */ +#endif + +#ifndef NET_ACCEPT_TIMEOUT_MS +#define NET_ACCEPT_TIMEOUT_MS 0 /* 接受连接超时(0=无限等待) */ +#endif + +#ifndef NET_SEND_TIMEOUT_MS +#define NET_SEND_TIMEOUT_MS 1000 /* 发送超时 */ +#endif + +#ifndef NET_RECV_TIMEOUT_MS +#define NET_RECV_TIMEOUT_MS 0 /* 接收超时(0=无限等待) */ +#endif + +/* + * TCP KeepAlive 配置 + */ +#ifndef NET_KEEPALIVE_IDLE_MS +#define NET_KEEPALIVE_IDLE_MS 7200000 /* KeepAlive 空闲时间(默认2小时) */ +#endif + +#ifndef NET_KEEPALIVE_INTVL_MS +#define NET_KEEPALIVE_INTVL_MS 75000 /* KeepAlive 探测间隔(默认75秒) */ +#endif + +#ifndef NET_KEEPALIVE_CNT +#define NET_KEEPALIVE_CNT 9 /* KeepAlive 探测次数(默认9次) */ +#endif + +/* + * select/poll 最大 fd 数量 + */ +#ifndef NET_SELECT_MAX_FDS +#define NET_SELECT_MAX_FDS NET_MAX_SOCKETS +#endif + +/* + * 轮询延时配置(毫秒) + */ +#ifndef NET_POLL_DELAY_MS +#define NET_POLL_DELAY_MS 1 /* 非阻塞轮询延时 */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* __NET_CONFIG_H */ diff --git a/Drivers/BSP/NET/net_select.c b/Drivers/BSP/NET/net_select.c new file mode 100644 index 0000000..3de27ee --- /dev/null +++ b/Drivers/BSP/NET/net_select.c @@ -0,0 +1,319 @@ +/* + * 模块名称:Network Select Implementation + * 模块功能:I/O 多路复用实现,包括 select 和 poll 模式, + * 用于同时监控多个 Socket 的读写事件 + * 适用平台:STM32F4 系列(CH395F 以太网芯片) + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + */ + +#include "net_select.h" +#include "net_socket.h" +#include "ch395f.h" + +/* + * 私有函数声明区 + */ +static int check_socket_readable(int sockfd); +static int check_socket_writable(int sockfd); +static int check_socket_error(int sockfd); + +/* + * 公共函数实现区 + */ + +/* + * 函数功能:I/O 多路复用(select 模式) + */ +int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, + net_fd_set *exceptfds, net_timeval *timeout) +{ + uint32_t start_tick; + uint32_t timeout_ms = 0; + net_fd_set result_read = {0}; + net_fd_set result_write = {0}; + net_fd_set result_except = {0}; + int ready_count = 0; + int i; + + /* 计算超时时间 */ + if (timeout != NULL) + { + timeout_ms = (uint32_t)(timeout->tv_sec * 1000) + + (uint32_t)(timeout->tv_usec / 1000); + } + + start_tick = HAL_GetTick(); + + while (1) + { + /* 轮询所有 Socket */ + net_poll(); + + ready_count = 0; + + /* 检查哪些 Socket 有事件 */ + for (i = 0; i < nfds && i < NET_MAX_SOCKETS; i++) + { + net_sock_t *sock = net_get_sock(i); + + if (sock == NULL) + { + continue; + } + + /* 检查可读事件 */ + if (readfds != NULL && NET_FD_ISSET(i, readfds)) + { + if (check_socket_readable(i)) + { + NET_FD_SET(i, &result_read); + ready_count++; + } + } + + /* 检查可写事件 */ + if (writefds != NULL && NET_FD_ISSET(i, writefds)) + { + if (check_socket_writable(i)) + { + NET_FD_SET(i, &result_write); + ready_count++; + } + } + + /* 检查异常事件 */ + if (exceptfds != NULL && NET_FD_ISSET(i, exceptfds)) + { + if (check_socket_error(i)) + { + NET_FD_SET(i, &result_except); + ready_count++; + } + } + } + + /* 有事件就绪,返回 */ + if (ready_count > 0) + { + break; + } + + /* 超时检查 */ + if (timeout != NULL) + { + if (timeout_ms > 0 && + (HAL_GetTick() - start_tick) >= timeout_ms) + { + break; /* 超时返回 0 */ + } + } + + /* 短暂延时避免忙等 */ + HAL_Delay(NET_POLL_DELAY_MS); + } + + /* 复制结果 */ + if (readfds != NULL) + { + readfds->fd_bits = result_read.fd_bits; + } + if (writefds != NULL) + { + writefds->fd_bits = result_write.fd_bits; + } + if (exceptfds != NULL) + { + exceptfds->fd_bits = result_except.fd_bits; + } + + return ready_count; +} + +/* + * 函数功能:I/O 多路复用(poll 模式) + */ +int net_poll_events(net_pollfd *fds, int nfds, int timeout) +{ + uint32_t start_tick; + int ready = 0; + int i; + + if (fds == NULL || nfds <= 0) + { + return -1; + } + + start_tick = HAL_GetTick(); + + while (1) + { + /* 轮询 */ + net_poll(); + + ready = 0; + + for (i = 0; i < nfds; i++) + { + fds[i].revents = 0; + + /* 检查 fd 有效性 */ + if (fds[i].fd < 0 || fds[i].fd >= NET_MAX_SOCKETS) + { + fds[i].revents |= NET_POLLNVAL; + ready++; + continue; + } + + net_sock_t *sock = net_get_sock(fds[i].fd); + + if (sock == NULL) + { + fds[i].revents |= NET_POLLNVAL; + ready++; + continue; + } + + /* 检查可读 */ + if (fds[i].events & NET_POLLIN) + { + if (check_socket_readable(fds[i].fd)) + { + fds[i].revents |= NET_POLLIN; + ready++; + } + } + + /* 检查可写 */ + if (fds[i].events & NET_POLLOUT) + { + if (check_socket_writable(fds[i].fd)) + { + fds[i].revents |= NET_POLLOUT; + ready++; + } + } + + /* 检查错误 */ + if (check_socket_error(fds[i].fd)) + { + fds[i].revents |= NET_POLLERR; + ready++; + } + + /* 检查挂起(连接断开) */ + if (sock->state == NET_SOCK_STATE_TCP_ACCEPT || + sock->state == NET_SOCK_STATE_CLOSED) + { + fds[i].revents |= NET_POLLHUP; + ready++; + } + } + + if (ready > 0) + { + break; + } + + /* 超时检查 */ + if (timeout >= 0) + { + if ((HAL_GetTick() - start_tick) >= (uint32_t)timeout) + { + break; + } + } + + HAL_Delay(NET_POLL_DELAY_MS); + } + + return ready; +} + +/* + * 私有函数实现区 + */ + +/* + * 函数功能:检查 Socket 是否可读 + * 返回值:1 可读,0 不可读 + */ +static int check_socket_readable(int sockfd) +{ + net_sock_t *sock; + uint16_t recv_len; + + sock = net_get_sock(sockfd); + if (sock == NULL) + { + return 0; + } + + /* 监听 Socket 有新连接时可读 */ + if (sock->state == NET_SOCK_STATE_LISTENING) + { + return 1; + } + + /* 已建立连接的 Socket 有数据可读 */ + if (sock->state == NET_SOCK_STATE_ESTABLISHED) + { + recv_len = ch395f_get_recv_len(sockfd); + if (recv_len > 0) + { + return 1; + } + } + + return 0; +} + +/* + * 函数功能:检查 Socket 是否可写 + * 返回值:1 可写,0 不可写 + */ +static int check_socket_writable(int sockfd) +{ + net_sock_t *sock; + + sock = net_get_sock(sockfd); + if (sock == NULL) + { + return 0; + } + + /* 已建立连接的 Socket 发送缓冲区空闲时可写 */ + if (sock->state == NET_SOCK_STATE_ESTABLISHED) + { + if (sock->send_ready) + { + return 1; + } + } + + return 0; +} + +/* + * 函数功能:检查 Socket 是否有错误 + * 返回值:1 有错误,0 无错误 + */ +static int check_socket_error(int sockfd) +{ + net_sock_t *sock; + + /* 无效 fd 视为错误 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + return 1; + } + + sock = net_get_sock(sockfd); + if (sock == NULL) + { + return 1; /* 未使用的 Socket 视为错误 */ + } + + return 0; +} diff --git a/Drivers/BSP/NET/net_select.h b/Drivers/BSP/NET/net_select.h new file mode 100644 index 0000000..494396c --- /dev/null +++ b/Drivers/BSP/NET/net_select.h @@ -0,0 +1,114 @@ +/* + * 模块名称:Network Select API + * 模块功能:I/O 多路复用接口,支持 select 和 poll 模式, + * 用于同时监控多个 Socket 的读写事件 + * 适用平台:STM32F4 系列(CH395F 以太网芯片) + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + */ + +#ifndef __NET_SELECT_H +#define __NET_SELECT_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "net_types.h" +#include "net_config.h" + +/* + * 文件描述符集合 + * 每个位对应一个 socket 描述符 (0~7) + */ +typedef struct { + uint32_t fd_bits; /* 位掩码 */ +} net_fd_set; + +/* + * 时间结构体 + */ +typedef struct { + long tv_sec; /* 秒 */ + long tv_usec; /* 微秒 */ +} net_timeval; + +/* + * poll 事件结构体 + */ +typedef struct { + int fd; /* socket 描述符 */ + short events; /* 请求的事件 */ + short revents; /* 实际发生的事件 */ +} net_pollfd; + +/* + * poll 事件掩码 + */ +#define NET_POLLIN 0x0001 /* 可读 */ +#define NET_POLLOUT 0x0004 /* 可写 */ +#define NET_POLLERR 0x0008 /* 错误 */ +#define NET_POLLHUP 0x0010 /* 挂起 */ +#define NET_POLLNVAL 0x0020 /* 无效请求 */ + +/* + * fd_set 操作宏 + */ + +/* + * 函数功能:清空 fd_set + */ +#define NET_FD_ZERO(fdset) ((fdset)->fd_bits = 0) + +/* + * 函数功能:将 fd 加入 fd_set + */ +#define NET_FD_SET(fd, fdset) ((fdset)->fd_bits |= (1U << (fd))) + +/* + * 函数功能:将 fd 从 fd_set 移除 + */ +#define NET_FD_CLR(fd, fdset) ((fdset)->fd_bits &= ~(1U << (fd))) + +/* + * 函数功能:检查 fd 是否在 fd_set 中 + */ +#define NET_FD_ISSET(fd, fdset) (((fdset)->fd_bits & (1U << (fd))) != 0) + +/* + * I/O 多路复用 API + */ + +/* + * 函数功能:I/O 多路复用(select 模式) + * 入口参数:nfds - 最大 fd + 1(通常为 NET_MAX_SOCKETS) + * readfds - 可读事件集合,NULL 表示不关心 + * writefds - 可写事件集合,NULL 表示不关心 + * exceptfds - 异常事件集合,NULL 表示不关心 + * timeout - 超时时间,NULL 表示无限等待 + * 返回值:就绪的 fd 数量,0=超时,-1=错误 + * 限定条件:net_init() 已调用 + * 函数说明:此函数会调用 net_poll() 轮询所有 Socket, + * 并检查哪些 Socket 有指定的事件发生。 + */ +int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, + net_fd_set *exceptfds, net_timeval *timeout); + +/* + * 函数功能:I/O 多路复用(poll 模式) + * 入口参数:fds - net_pollfd 数组 + * nfds - 数组元素数量 + * timeout - 超时时间(毫秒),-1 表示无限等待 + * 返回值:就绪的 fd 数量,0=超时,-1=错误 + * 限定条件:net_init() 已调用 + * 函数说明:与 select 类似,但使用 pollfd 结构体数组, + * 更灵活,支持更多事件类型。 + */ +int net_poll_events(net_pollfd *fds, int nfds, int timeout); + +#ifdef __cplusplus +} +#endif + +#endif /* __NET_SELECT_H */ diff --git a/Drivers/BSP/NET/net_socket.c b/Drivers/BSP/NET/net_socket.c new file mode 100644 index 0000000..79d203a --- /dev/null +++ b/Drivers/BSP/NET/net_socket.c @@ -0,0 +1,1219 @@ +/* + * 模块名称:Network Socket Core + * 模块功能:BSD Socket API 核心实现,包括 Socket 管理、状态机、 + * 事件处理、数据收发等功能,底层调用 CH395F 驱动 + * 适用平台:STM32F4 系列(CH395F 以太网芯片) + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + */ + +#include "net_socket.h" +#include "ch395f.h" +#include "main.h" +#include +#include + +/* + * 私有宏定义区 + */ +#define TS_FMT() do { \ + uint32_t _t = HAL_GetTick(); \ + printf("[%lu.%03lu] ", _t / 1000UL, _t % 1000UL); \ +} while(0) + +/* + * 私有变量区 + */ +static net_sock_t g_net_socks[NET_MAX_SOCKETS]; /* Socket 控制块数组 */ +static int s_net_errno = 0; /* 最后错误码 */ +static uint8_t s_net_initialized = 0; /* 初始化标志 */ + + +/* + * 私有宏定义区 - 补充 + */ +#define NET_PHY_CHECK_INTERVAL_MS 5000U /* PHY 状态定期检查间隔(ms) */ + +/* + * 私有函数声明区 + */ +static int alloc_socket(void); +static void handle_connect_event(net_sock_t *sock); +static void handle_disconnect_event(net_sock_t *sock); +static void handle_recv_event(net_sock_t *sock); +static void handle_send_event(net_sock_t *sock); +static void handle_timeout_event(net_sock_t *sock); +static void fire_event(net_sock_t *sock, net_event_t event); +static void parse_ip_string(const char *str, uint8_t *ip_arr); + +/* + * 公共函数实现区 + */ + +/* + * 函数功能:网络子系统初始化 + */ +int net_init(const char *ip, const char *mask, const char *gateway) +{ + ch395f_status_t ret; + + /* 清零 Socket 控制块 */ + memset(g_net_socks, 0, sizeof(g_net_socks)); + s_net_errno = 0; + s_net_initialized = 0; + + /* 检测 CH395F 芯片 */ + if (ch395f_check_exist() != CH395F_STATUS_OK) + { + s_net_errno = NET_ERR; + return -1; + } + + /* 复位芯片 */ + ch395f_reset(); + + /* 启用 TCP Server 多连接模式(bit1=1) + * Socket 0 专职监听,Socket 1~7 自动分配给新连接 */ + ch395f_set_fun_para(0x02); + + /* 设置 TCP MSS */ + ch395f_set_tcp_mss(1460); + + /* 设置 TCP 重传参数(必须在 INIT_CH395 之前) */ + ch395f_set_retrans_count(8); /* 重传 8 次 */ + ch395f_set_retrans_period(500); /* 每次间隔 500ms,总计 4s 重传窗口 */ + + /* 设置 TCP KeepAlive 参数(必须在 INIT_CH395 之前,单位 ms,必须为 500 的倍数,IDLE > INTVL) */ + ch395f_set_keepalive_idle(60000); /* 空闲 60 秒后开始探测 */ + ch395f_set_keepalive_intvl(5000); /* 每 5 秒探测一次 */ + ch395f_set_keepalive_cnt(3); /* 最多探测 3 次 */ + + /* 设置网络参数(必须在 INIT_CH395 之前) */ + if (ip != NULL) + { + uint8_t ip_arr[4]; + uint8_t mask_arr[4]; + uint8_t gw_arr[4]; + + parse_ip_string(ip, ip_arr); + parse_ip_string(mask, mask_arr); + parse_ip_string(gateway, gw_arr); + + ch395f_set_ip_addr(ip_arr); + ch395f_set_gwip_addr(gw_arr); + ch395f_set_mask_addr(mask_arr); + } + else + { + /* 启用 DHCP */ + ch395f_set_dhcp(1); + } + + /* 初始化 CH395F 协议栈(先不配置 buffer,测试默认值是否工作) */ + ret = ch395f_init(); + if (ret != CH395F_STATUS_OK) + { + s_net_errno = NET_ERR; + return -1; + } + + /* 使能 PING 响应 */ + ch395f_ping_enable(1); + + s_net_initialized = 1; + + return 0; +} + +/* + * 函数功能:轮询所有 Socket 状态 + */ +int net_poll(void) +{ + int event_count = 0; + int i; + uint16_t int_status; + + if (!s_net_initialized) + { + return 0; + } + + /* + * GPIO 轮询模式:仅在 INT# 有效(低电平)时读取中断状态 + * 读取 GET_GLOB_INT_STATUS_ALL 后 CH395F 拉高 INT#,若仍有中断则再次拉低 + */ + do + { + if (HAL_GPIO_ReadPin(CH395_INT_GPIO_Port, CH395_INT_Pin) == GPIO_PIN_SET) + { + break; + } + + int_status = ch395f_get_glob_int_status_all(); + + if (int_status == 0) + { + break; + } + + if (int_status & CH395F_GINT_STAT_PHY_CHANGE) + { + uint8_t phy = ch395f_get_phy_status(); + printf("[NET] PHY_CHANGE: 0x%02X\r\n", phy); + if (phy == CH395F_PHY_DISCONN) + { + /* PHY 断开:所有活动 Socket 可能需要恢复 */ + printf("[NET] PHY disconnected, sockets may need recovery\r\n"); + } + } + + /* 处理每个 Socket */ + for (i = 0; i < NET_MAX_SOCKETS; i++) + { + net_sock_t *sock = &g_net_socks[i]; + + /* 检查该 Socket 是否有中断(全局中断状态 bit4~bit11 对应 socket0~7) */ + if (!(int_status & (1U << (i + 4)))) + { + continue; + } + + /* 读取 Socket 中断状态 */ + uint8_t sock_int = ch395f_get_sock_int_status(i); + + /* 连接事件 */ + if (sock_int & CH395F_SINT_STAT_CONNECT) + { + handle_connect_event(sock); + event_count++; + } + + /* 断开事件 */ + if (sock_int & CH395F_SINT_STAT_DISCONNECT) + { + handle_disconnect_event(sock); + event_count++; + } + + /* 数据接收事件 */ + if (sock_int & CH395F_SINT_STAT_RECV_OK) + { + handle_recv_event(sock); + event_count++; + } + + /* 发送完成事件 */ + if (sock_int & CH395F_SINT_STAT_SEND_OK) + { + handle_send_event(sock); + event_count++; + } + + /* 发送缓冲区空闲 */ + if (sock_int & CH395F_SINT_STAT_SENBUF_FREE) + { + sock->send_ready = 1; + } + + /* 超时事件 */ + if (sock_int & CH395F_SINT_STAT_SOCK_TIMEOUT) + { + handle_timeout_event(sock); + event_count++; + } + } + + /* + * 处理完毕后检查 INT# 是否仍为低电平 + * 如果是,说明在处理期间有新中断产生,需要再次读取 + * + * 注意:CH395F 的 RECV 中断是电平触发的(只要接收缓冲区有数据就保持低电平), + * 如果不跳出循环,net_poll() 会无限循环处理 RECV 中断, + * 导致主循环中的应用代码(如 echo)永远无法执行。 + * 因此每次 net_poll() 调用只处理一批中断,由主循环负责读取数据。 + */ + } while (0); + + /* 每 2 秒打印一次诊断信息(调试用) */ + { + static uint32_t s_last_dbg = 0; + uint32_t now = HAL_GetTick(); + if (now - s_last_dbg >= 2000) + { + s_last_dbg = now; + uint8_t int_level = (HAL_GPIO_ReadPin(CH395_INT_GPIO_Port, CH395_INT_Pin) == GPIO_PIN_RESET) ? 1 : 0; + printf("[NET] INT#=%d", int_level); + for (i = 0; i < NET_MAX_SOCKETS; i++) + { + if (g_net_socks[i].in_use) + { + uint8_t st[2]; + ch395f_get_socket_status((uint8_t)i, st); + uint16_t rl = ch395f_get_recv_len((uint8_t)i); + printf(" s%d:sw%d/tcp%d rl%d", i, st[0], st[1], rl); + } + } + printf("\r\n"); + } + } + + return event_count; +} + +/* + * 函数功能:创建 socket + */ +int net_socket(int domain, int type, int protocol) +{ + int sockfd; + net_sock_t *sock; + + /* 参数检查 */ + if (domain != NET_AF_INET) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + /* 分配空闲 Socket */ + sockfd = alloc_socket(); + if (sockfd < 0) + { + s_net_errno = NET_ERR_NOMEM; + return -1; + } + + sock = &g_net_socks[sockfd]; + sock->ch395_sock = (uint8_t)sockfd; + sock->type = (uint8_t)type; + sock->state = NET_SOCK_STATE_CREATED; + sock->in_use = 1; + sock->send_ready = 1; + + return sockfd; +} + +/* + * 函数功能:绑定本地地址和端口 + */ +int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen) +{ + net_sock_t *sock; + const struct net_sockaddr_in *addr_in; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + addr_in = (const struct net_sockaddr_in *)addr; + + /* 设置本地端口(转换为主机序) */ + sock->local_port = net_ntohs(addr_in->sin_port); + + sock->state = NET_SOCK_STATE_BOUND; + + return 0; +} + +/* + * 函数功能:TCP Server 监听 + */ +int net_listen(int sockfd, int backlog) +{ + net_sock_t *sock; + uint8_t status; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use || sock->type != NET_SOCK_STREAM) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + if (sock->state != NET_SOCK_STATE_BOUND && + sock->state != NET_SOCK_STATE_CREATED) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + /* 配置 CH395F Socket */ + ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP); + ch395f_set_sour_port(sockfd, sock->local_port); + status = ch395f_open_socket(sockfd); + + if (status != CH395F_ERR_SUCCESS) + { + s_net_errno = NET_ERR_BUSY; + return -1; + } + + /* 启动 TCP 监听 */ + ch395f_tcp_listen(sockfd); + + /* 多连接模式:为数据 Socket 设置协议类型和源端口(不 OPEN) + * CH395F 根据这些信息在客户端连接时自动分配空闲 Socket */ + for (int ds = NET_TCP_SERVER_DATA_SOCK_START; ds < NET_MAX_SOCKETS; ds++) + { + ch395f_set_proto_type((uint8_t)ds, CH395F_PROTO_TYPE_TCP); + ch395f_set_sour_port((uint8_t)ds, sock->local_port); + } + + sock->state = NET_SOCK_STATE_LISTENING; + + return 0; +} + +/* + * 函数功能:TCP Server 接受连接 + */ +int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen) +{ + net_sock_t *listen_sock; + int i; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + listen_sock = &g_net_socks[sockfd]; + if (!listen_sock->in_use || listen_sock->state != NET_SOCK_STATE_LISTENING) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + /* 轮询查找已建立连接的数据 Socket */ + net_poll(); + + for (i = 0; i < listen_sock->data_sock_count; i++) + { + int data_sock = listen_sock->data_socks[i]; + net_sock_t *data_cb = &g_net_socks[data_sock]; + + if (data_cb->state == NET_SOCK_STATE_ESTABLISHED) + { + /* 找到已建立连接的 Socket */ + if (addr != NULL && addrlen != NULL) + { + struct net_sockaddr_in *addr_in = (struct net_sockaddr_in *)addr; + addr_in->sin_family = NET_AF_INET; + addr_in->sin_port = net_htons(data_cb->remote_port); + addr_in->sin_addr.s_addr = data_cb->remote_ip; + *addrlen = sizeof(struct net_sockaddr_in); + } + + return data_sock; + } + } + + /* 没有新连接 */ + s_net_errno = NET_ERR_WOULDBLOCK; + return -1; +} + +/* + * 函数功能:TCP Client 发起连接 + */ +int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen) +{ + net_sock_t *sock; + const struct net_sockaddr_in *addr_in; + uint8_t status; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use || sock->type != NET_SOCK_STREAM) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + if (addr == NULL || addrlen < (int)sizeof(struct net_sockaddr_in)) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + addr_in = (const struct net_sockaddr_in *)addr; + + /* 保存远端地址 */ + sock->remote_ip = addr_in->sin_addr.s_addr; + sock->remote_port = net_ntohs(addr_in->sin_port); + + /* 转换 IP 为数组格式(低字节在前) */ + sock->remote_ip_arr[0] = (uint8_t)(sock->remote_ip); + sock->remote_ip_arr[1] = (uint8_t)(sock->remote_ip >> 8); + sock->remote_ip_arr[2] = (uint8_t)(sock->remote_ip >> 16); + sock->remote_ip_arr[3] = (uint8_t)(sock->remote_ip >> 24); + + /* 配置 CH395F Socket */ + ch395f_set_proto_type(sockfd, CH395F_PROTO_TYPE_TCP); + ch395f_set_sour_port(sockfd, sock->local_port); + ch395f_set_des_ip(sockfd, sock->remote_ip_arr); + ch395f_set_des_port(sockfd, sock->remote_port); + status = ch395f_open_socket(sockfd); + + if (status != CH395F_ERR_SUCCESS) + { + s_net_errno = NET_ERR_BUSY; + return -1; + } + + /* 发起 TCP 连接 */ + ch395f_tcp_connect(sockfd); + sock->state = NET_SOCK_STATE_CONNECTING; + + return 0; +} + +/* + * 函数功能:发送数据(TCP) + */ +int net_send(int sockfd, const void *buf, int len, int flags) +{ + net_sock_t *sock; + int sent = 0; + uint32_t tick_start; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use || sock->state != NET_SOCK_STATE_ESTABLISHED) + { + s_net_errno = NET_ERR_NOTCONN; + return -1; + } + + if (buf == NULL || len <= 0) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + tick_start = HAL_GetTick(); + + /* 分块发送 */ + while (sent < len) + { + int chunk = len - sent; + if (chunk > NET_SEND_BUF_SIZE) + { + chunk = NET_SEND_BUF_SIZE; + } + + /* 检查发送缓冲区是否空闲 */ + if (!sock->send_ready) + { + /* 超时检查 */ + if (NET_SEND_TIMEOUT_MS > 0 && + (HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) + { + s_net_errno = NET_ERR_TIMEDOUT; + break; + } + /* 继续轮询 */ + net_poll(); + continue; + } + + ch395f_write_send_buf(sockfd, (uint8_t *)buf + sent, (uint16_t)chunk); + sock->send_ready = 0; + sent += chunk; + sock->send_bytes += (uint32_t)chunk; + + /* 短暂延时等待发送完成 */ + tick_start = HAL_GetTick(); + while (!sock->send_ready) + { + net_poll(); + if (NET_SEND_TIMEOUT_MS > 0 && + (HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) + { + break; + } + } + } + + return sent; +} + +/* + * 函数功能:接收数据(TCP) + */ +int net_recv(int sockfd, void *buf, int len, int flags) +{ + net_sock_t *sock; + uint16_t recv_len; + uint32_t tick_start; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + /* 检查连接状态 */ + if (sock->type == NET_SOCK_STREAM && + sock->state != NET_SOCK_STATE_ESTABLISHED) + { + s_net_errno = NET_ERR_NOTCONN; + return -1; + } + + if (buf == NULL || len <= 0) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + tick_start = HAL_GetTick(); + + while (1) + { + net_poll(); + + recv_len = ch395f_get_recv_len(sockfd); + if (recv_len > 0) + { + /* 有数据可读 */ + int read_len = (recv_len > (uint16_t)len) ? len : (int)recv_len; + ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len); + sock->recv_bytes += (uint32_t)read_len; + return read_len; + } + + /* 非阻塞模式检查 */ + if (flags & NET_MSG_DONTWAIT) + { + s_net_errno = NET_ERR_WOULDBLOCK; + return -1; + } + + /* 超时检查 */ + if (NET_RECV_TIMEOUT_MS > 0 && + (HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) + { + s_net_errno = NET_ERR_TIMEDOUT; + return -1; + } + + /* 连接已断开检查 */ + if (sock->state == NET_SOCK_STATE_TCP_ACCEPT) + { + return 0; /* 对端关闭 */ + } + } +} + +/* + * 函数功能:发送 UDP 数据 + */ +int net_sendto(int sockfd, const void *buf, int len, int flags, + const struct net_sockaddr *dest_addr, int addrlen) +{ + net_sock_t *sock; + const struct net_sockaddr_in *addr_in; + int sent = 0; + uint32_t tick_start; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + if (buf == NULL || len <= 0) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + /* UDP 模式:设置目标地址 */ + if (dest_addr != NULL && addrlen >= (int)sizeof(struct net_sockaddr_in)) + { + addr_in = (const struct net_sockaddr_in *)dest_addr; + + /* 转换 IP 为数组格式 */ + uint8_t ip_arr[4]; + uint32_t ip = addr_in->sin_addr.s_addr; + ip_arr[0] = (uint8_t)(ip); + ip_arr[1] = (uint8_t)(ip >> 8); + ip_arr[2] = (uint8_t)(ip >> 16); + ip_arr[3] = (uint8_t)(ip >> 24); + + ch395f_set_des_ip(sockfd, ip_arr); + ch395f_set_des_port(sockfd, net_ntohs(addr_in->sin_port)); + } + + tick_start = HAL_GetTick(); + + /* 发送数据 */ + while (sent < len) + { + int chunk = len - sent; + if (chunk > NET_SEND_BUF_SIZE) + { + chunk = NET_SEND_BUF_SIZE; + } + + /* 检查发送缓冲区 */ + if (!sock->send_ready) + { + if (NET_SEND_TIMEOUT_MS > 0 && + (HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) + { + s_net_errno = NET_ERR_TIMEDOUT; + break; + } + net_poll(); + continue; + } + + ch395f_write_send_buf(sockfd, (uint8_t *)buf + sent, (uint16_t)chunk); + sock->send_ready = 0; + sent += chunk; + sock->send_bytes += (uint32_t)chunk; + + /* 等待发送完成 */ + tick_start = HAL_GetTick(); + while (!sock->send_ready) + { + net_poll(); + if (NET_SEND_TIMEOUT_MS > 0 && + (HAL_GetTick() - tick_start) >= NET_SEND_TIMEOUT_MS) + { + break; + } + } + } + + return sent; +} + +/* + * 函数功能:接收 UDP 数据 + */ +int net_recvfrom(int sockfd, void *buf, int len, int flags, + struct net_sockaddr *src_addr, int *addrlen) +{ + net_sock_t *sock; + uint16_t recv_len; + uint32_t tick_start; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + if (buf == NULL || len <= 0) + { + s_net_errno = NET_ERR_INVAL; + return -1; + } + + tick_start = HAL_GetTick(); + + while (1) + { + net_poll(); + + recv_len = ch395f_get_recv_len(sockfd); + if (recv_len > 0) + { + /* UDP Server 模式:前 8 字节为固定格式(IP + 端口 + 长度) */ + if (sock->type == NET_SOCK_DGRAM && recv_len >= 8) + { + uint8_t header[8]; + ch395f_read_recv_buf(sockfd, header, 8); + recv_len -= 8; + + /* 填充来源地址 */ + if (src_addr != NULL && addrlen != NULL && + *addrlen >= (int)sizeof(struct net_sockaddr_in)) + { + struct net_sockaddr_in *addr_in = (struct net_sockaddr_in *)src_addr; + addr_in->sin_family = NET_AF_INET; + addr_in->sin_port = net_htons((uint16_t)(header[4] | (header[5] << 8))); + addr_in->sin_addr.s_addr = ((uint32_t)header[0]) | + ((uint32_t)header[1] << 8) | + ((uint32_t)header[2] << 16) | + ((uint32_t)header[3] << 24); + *addrlen = sizeof(struct net_sockaddr_in); + } + } + + /* 读取数据 */ + if (recv_len > 0) + { + int read_len = (recv_len > (uint16_t)len) ? len : (int)recv_len; + ch395f_read_recv_buf(sockfd, (uint8_t *)buf, (uint16_t)read_len); + sock->recv_bytes += (uint32_t)read_len; + return read_len; + } + } + + /* 非阻塞模式检查 */ + if (flags & NET_MSG_DONTWAIT) + { + s_net_errno = NET_ERR_WOULDBLOCK; + return -1; + } + + /* 超时检查 */ + if (NET_RECV_TIMEOUT_MS > 0 && + (HAL_GetTick() - tick_start) >= NET_RECV_TIMEOUT_MS) + { + s_net_errno = NET_ERR_TIMEDOUT; + return -1; + } + } +} + +/* + * 函数功能:关闭 socket + */ +int net_close(int sockfd) +{ + net_sock_t *sock; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + /* 关闭 CH395F Socket */ + ch395f_close_socket(sockfd); + + /* 清零控制块 */ + memset(sock, 0, sizeof(net_sock_t)); + + return 0; +} + +/* + * 函数功能:注册事件回调 + */ +int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg) +{ + net_sock_t *sock; + + /* 参数检查 */ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock = &g_net_socks[sockfd]; + if (!sock->in_use) + { + s_net_errno = NET_ERR_BADF; + return -1; + } + + sock->event_cb = cb; + sock->event_arg = arg; + + return 0; +} + +/* + * 函数功能:获取 socket 最后错误码 + */ +int net_get_errno(void) +{ + return s_net_errno; +} + +/* + * 函数功能:将 IP 地址字符串转为网络字节序 + */ +uint32_t net_inet_addr(const char *cp) +{ + uint32_t addr = 0; + uint32_t val; + int i; + + if (cp == NULL) + { + return 0; + } + + for (i = 0; i < 4; i++) + { + val = 0; + while (*cp >= '0' && *cp <= '9') + { + val = val * 10 + (*cp - '0'); + cp++; + } + addr |= (val << (i * 8)); + if (*cp == '.') + { + cp++; + } + } + + return NET_htonl(addr); +} + +/* + * 函数功能:将网络字节序 IP 转为字符串 + */ +char *net_inet_ntoa(uint32_t addr, char *buf) +{ + uint32_t naddr = NET_ntohl(addr); + + if (buf == NULL) + { + return NULL; + } + + sprintf(buf, "%lu.%lu.%lu.%lu", + (unsigned long)(naddr & 0xFF), + (unsigned long)((naddr >> 8) & 0xFF), + (unsigned long)((naddr >> 16) & 0xFF), + (unsigned long)((naddr >> 24) & 0xFF)); + + return buf; +} + +/* + * 函数功能:端口字节序转换(主机序 -> 网络序) + */ +uint16_t net_htons(uint16_t hostshort) +{ + return NET_htons(hostshort); +} + +/* + * 函数功能:端口字节序转换(网络序 -> 主机序) + */ +uint16_t net_ntohs(uint16_t netshort) +{ + return NET_ntohs(netshort); +} + +/* + * 函数功能:IP 地址字节序转换(主机序 -> 网络序) + */ +uint32_t net_htonl(uint32_t hostlong) +{ + return NET_htonl(hostlong); +} + +/* + * 函数功能:IP 地址字节序转换(网络序 -> 主机序) + */ +uint32_t net_ntohl(uint32_t netlong) +{ + return NET_ntohl(netlong); +} + +/* + * 私有函数实现区 + */ + +/* + * 函数功能:分配空闲 Socket + * 返回值:Socket 索引,失败返回 -1 + */ +static int alloc_socket(void) +{ + int i; + + for (i = 0; i < NET_MAX_SOCKETS; i++) + { + if (!g_net_socks[i].in_use) + { + return i; + } + } + + return -1; +} + +/* + * 函数功能:处理连接事件 + */ +static void handle_connect_event(net_sock_t *sock) +{ + if (sock->state == NET_SOCK_STATE_LISTENING) + { + /* 多连接模式:监听 Socket 收到 CONNECT 中断 = 有新连接被分配到数据 Socket + * Socket 0 自身保持 LISTENING,不需要改变状态 */ + fire_event(sock, NET_EVENT_CONNECTED); + } + else if (!sock->in_use) + { + /* CH395F 自动打开的数据 Socket(多连接模式),首次收到 CONNECT 中断 */ + int sock_idx = (int)(sock - g_net_socks); + sock->ch395_sock = (uint8_t)sock_idx; + TS_FMT(); printf("[NET] sock%d AUTO-OPENED, CONNECTED\r\n", sock->ch395_sock); + uint8_t remote[6]; + ch395f_get_remot_ipp(sock->ch395_sock, remote); + memcpy(sock->remote_ip_arr, remote, 4); + sock->remote_ip = ((uint32_t)remote[0]) | + ((uint32_t)remote[1] << 8) | + ((uint32_t)remote[2] << 16) | + ((uint32_t)remote[3] << 24); + sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); + sock->in_use = 1; + sock->state = NET_SOCK_STATE_ESTABLISHED; + sock->send_ready = 1; + + ch395f_set_keepalive_enable(sock->ch395_sock, 1); + + fire_event(sock, NET_EVENT_CONNECTED); + } + else if (sock->state == NET_SOCK_STATE_CONNECTING) + { + /* TCP Client 连接成功 */ + uint8_t remote[6]; + ch395f_get_remot_ipp(sock->ch395_sock, remote); + memcpy(sock->remote_ip_arr, remote, 4); + sock->remote_ip = ((uint32_t)remote[0]) | + ((uint32_t)remote[1] << 8) | + ((uint32_t)remote[2] << 16) | + ((uint32_t)remote[3] << 24); + sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); + sock->state = NET_SOCK_STATE_ESTABLISHED; + + ch395f_set_keepalive_enable(sock->ch395_sock, 1); + + fire_event(sock, NET_EVENT_CONNECTED); + } + else if (sock->state == NET_SOCK_STATE_TCP_ACCEPT) + { + /* 数据 Socket 被分配了连接 */ + TS_FMT(); printf("[NET] data sock%d TCP_ACCEPT -> CONNECTED\r\n", sock->ch395_sock); + uint8_t remote[6]; + ch395f_get_remot_ipp(sock->ch395_sock, remote); + memcpy(sock->remote_ip_arr, remote, 4); + sock->remote_ip = ((uint32_t)remote[0]) | + ((uint32_t)remote[1] << 8) | + ((uint32_t)remote[2] << 16) | + ((uint32_t)remote[3] << 24); + sock->remote_port = (uint16_t)(remote[4] | (remote[5] << 8)); + sock->state = NET_SOCK_STATE_ESTABLISHED; + + ch395f_set_keepalive_enable(sock->ch395_sock, 1); + + fire_event(sock, NET_EVENT_CONNECTED); + } +} + +/* + * 函数功能:处理断开事件 + * 函数说明:CH395F 在 DISCONNECT 中断后自动关闭 Socket(默认 FUN_PARA_FLAG_SOCKET_CLOSE=0), + * 必须重新 OPEN_SOCKET + TCP_LISTEN 才能接受新连接 + */ +static void handle_disconnect_event(net_sock_t *sock) +{ + if (sock->state == NET_SOCK_STATE_LISTENING) + { + /* 监听 Socket 收到断开通知(不应发生) */ + printf("[NET] WARNING: listen sock%d got disconnect\r\n", sock->ch395_sock); + } + else if (sock->state == NET_SOCK_STATE_ESTABLISHED) + { + /* CH395F 自动关闭了 Socket,恢复为未使用状态 */ + TS_FMT(); printf("[NET] sock%d DISCONNECTED, back to idle\r\n", sock->ch395_sock); + memset(sock->remote_ip_arr, 0, 4); + sock->remote_port = 0; + sock->send_ready = 1; + sock->in_use = 0; + sock->state = NET_SOCK_STATE_CREATED; + fire_event(sock, NET_EVENT_DISCONNECTED); + } + else + { + /* 未预期的断开 */ + printf("[NET] sock%d disconnect in state %d\r\n", sock->ch395_sock, sock->state); + fire_event(sock, NET_EVENT_DISCONNECTED); + } +} + +/* + * 函数功能:处理数据接收事件 + */ +static void handle_recv_event(net_sock_t *sock) +{ + uint16_t recv_len = ch395f_get_recv_len(sock->ch395_sock); + + if (recv_len > 0) + { + fire_event(sock, NET_EVENT_DATA_RECEIVED); + } +} + +/* + * 函数功能:处理发送完成事件 + */ +static void handle_send_event(net_sock_t *sock) +{ + fire_event(sock, NET_EVENT_SEND_COMPLETE); +} + +/* + * 函数功能:处理超时事件 + * 函数说明:CH395F 在 TIMEOUT 中断后同样自动关闭 Socket, + * 需要重新打开并恢复监听 + */ +static void handle_timeout_event(net_sock_t *sock) +{ + if (sock->state == NET_SOCK_STATE_CONNECTING) + { + /* TCP Client 连接超时 */ + TS_FMT(); printf("[NET] sock%d CONNECT TIMEOUT\r\n", sock->ch395_sock); + sock->state = NET_SOCK_STATE_CREATED; + } + else if (sock->state == NET_SOCK_STATE_ESTABLISHED) + { + /* 多连接模式:数据 Socket 超时,恢复为 TCP_ACCEPT 等待新连接 */ + TS_FMT(); printf("[NET] sock%d TIMEOUT, back to ACCEPT\r\n", sock->ch395_sock); + memset(sock->remote_ip_arr, 0, 4); + sock->remote_port = 0; + sock->send_ready = 1; + sock->state = NET_SOCK_STATE_TCP_ACCEPT; + } + else if (sock->state == NET_SOCK_STATE_LISTENING) + { + /* 监听超时(异常情况) */ + printf("[NET] sock%d LISTEN TIMEOUT\r\n", sock->ch395_sock); + } + + fire_event(sock, NET_EVENT_TIMEOUT); +} + +/* + * 函数功能:触发事件回调 + */ +static void fire_event(net_sock_t *sock, net_event_t event) +{ + if (sock->event_cb != NULL) + { + sock->event_cb(sock->ch395_sock, event, sock->event_arg); + } +} + +/* + * 函数功能:解析 IP 地址字符串为数组 + */ +static void parse_ip_string(const char *str, uint8_t *ip_arr) +{ + uint32_t val; + int i; + + if (str == NULL || ip_arr == NULL) + { + return; + } + + for (i = 0; i < 4; i++) + { + val = 0; + while (*str >= '0' && *str <= '9') + { + val = val * 10 + (*str - '0'); + str++; + } + ip_arr[i] = (uint8_t)val; + if (*str == '.') + { + str++; + } + } +} + +/* + * 函数功能:获取 Socket 控制块指针(内部使用) + */ +net_sock_t *net_get_sock(int sockfd) +{ + if (sockfd < 0 || sockfd >= NET_MAX_SOCKETS) + { + return NULL; + } + + if (!g_net_socks[sockfd].in_use) + { + return NULL; + } + + return &g_net_socks[sockfd]; +} diff --git a/Drivers/BSP/NET/net_socket.h b/Drivers/BSP/NET/net_socket.h new file mode 100644 index 0000000..b63688e --- /dev/null +++ b/Drivers/BSP/NET/net_socket.h @@ -0,0 +1,227 @@ +/* + * 模块名称:Network Socket API + * 模块功能:BSD Socket API 兼容接口,提供 TCP/UDP 网络通信功能, + * 底层基于 CH395F 以太网协议栈芯片 + * 适用平台:STM32F4 系列(CH395F 以太网芯片) + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + */ + +#ifndef __NET_SOCKET_H +#define __NET_SOCKET_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include "net_types.h" + +/* + * 网络初始化接口 + */ + +/* + * 函数功能:网络子系统初始化(配置 CH395F + 启用多连接模式) + * 入口参数:ip - 本地 IP 地址字符串 "192.168.1.100",NULL 使用 DHCP + * mask - 子网掩码字符串 "255.255.255.0",NULL 使用默认 + * gateway - 网关地址字符串 "192.168.1.1",NULL 使用默认 + * 返回值:0 成功,-1 失败 + * 限定条件:SPI2 已正确初始化 + * 函数说明:必须在所有网络操作之前调用 + */ +int net_init(const char *ip, const char *mask, const char *gateway); + +/* + * 函数功能:轮询所有 Socket 状态(必须在主循环中调用) + * 返回值:有事件发生的 socket 数量 + * 限定条件:net_init() 已调用 + * 函数说明:此函数处理所有 Socket 的中断事件,更新状态, + * 并触发事件回调。在非阻塞模式下必须定期调用。 + */ +int net_poll(void); + +/* + * 核心 Socket API + */ + +/* + * 函数功能:创建 socket + * 入口参数:domain - 地址族,仅支持 AF_INET + * type - SOCK_STREAM(TCP) 或 SOCK_DGRAM(UDP) + * protocol - 通常为 0 + * 返回值:socket 描述符 (0~7),失败返回 -1 + * 限定条件:net_init() 已调用 + */ +int net_socket(int domain, int type, int protocol); + +/* + * 函数功能:绑定本地地址和端口 + * 入口参数:sockfd - socket 描述符 + * addr - 本地地址结构体指针 + * addrlen - 地址结构体长度 + * 返回值:0 成功,-1 失败 + */ +int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen); + +/* + * 函数功能:TCP Server 监听 + * 入口参数:sockfd - socket 描述符 + * backlog - 最大等待连接数(多连接模式下为数据 Socket 数量) + * 返回值:0 成功,-1 失败 + * 限定条件:socket 已绑定 + */ +int net_listen(int sockfd, int backlog); + +/* + * 函数功能:TCP Server 接受连接 + * 入口参数:sockfd - 监听 socket 描述符 + * addr - 输出:客户端地址 + * addrlen - 输入输出:地址长度 + * 返回值:新 socket 描述符,失败返回 -1 + * 限定条件:socket 正在监听 + * 函数说明:在非阻塞模式下,如果没有新连接,返回 -1 并设置 + * net_errno 为 NET_ERR_WOULDBLOCK + */ +int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen); + +/* + * 函数功能:TCP Client 发起连接 + * 入口参数:sockfd - socket 描述符 + * addr - 服务器地址 + * addrlen - 地址长度 + * 返回值:0 成功,-1 失败 + * 限定条件:socket 已创建 + * 函数说明:连接过程是异步的,需要调用 net_poll() 等待连接完成, + * 或使用 select() 等待可写事件 + */ +int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen); + +/* + * 函数功能:发送数据(TCP) + * 入口参数:sockfd - socket 描述符 + * buf - 数据缓冲区 + * len - 数据长度 + * flags - 通常为 0 + * 返回值:实际发送字节数,失败返回 -1 + * 限定条件:TCP 连接已建立 + */ +int net_send(int sockfd, const void *buf, int len, int flags); + +/* + * 函数功能:接收数据(TCP) + * 入口参数:sockfd - socket 描述符 + * buf - 接收缓冲区 + * len - 缓冲区大小 + * flags - 通常为 0,可设置 NET_MSG_DONTWAIT 非阻塞 + * 返回值:实际接收字节数,0=对端关闭,-1=错误 + * 限定条件:TCP 连接已建立 + * 函数说明:在阻塞模式下,如果没有数据,函数会等待; + * 在非阻塞模式下,如果没有数据,返回 -1 并设置 + * net_errno 为 NET_ERR_WOULDBLOCK + */ +int net_recv(int sockfd, void *buf, int len, int flags); + +/* + * 函数功能:发送 UDP 数据 + * 入口参数:sockfd - socket 描述符 + * buf - 数据缓冲区 + * len - 数据长度 + * flags - 通常为 0 + * dest_addr - 目标地址 + * addrlen - 地址长度 + * 返回值:实际发送字节数,失败返回 -1 + */ +int net_sendto(int sockfd, const void *buf, int len, int flags, + const struct net_sockaddr *dest_addr, int addrlen); + +/* + * 函数功能:接收 UDP 数据 + * 入口参数:sockfd - socket 描述符 + * buf - 接收缓冲区 + * len - 缓冲区大小 + * flags - 通常为 0 + * src_addr - 输出:发送方地址 + * addrlen - 输入输出:地址长度 + * 返回值:实际接收字节数,失败返回 -1 + */ +int net_recvfrom(int sockfd, void *buf, int len, int flags, + struct net_sockaddr *src_addr, int *addrlen); + +/* + * 函数功能:关闭 socket + * 入口参数:sockfd - socket 描述符 + * 返回值:0 成功,-1 失败 + */ +int net_close(int sockfd); + +/* + * 事件回调 API + */ + +/* + * 函数功能:注册事件回调 + * 入口参数:sockfd - socket 描述符 + * cb - 回调函数 + * arg - 用户参数 + * 返回值:0 成功,-1 失败 + */ +int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg); + +/* + * 辅助函数 + */ + +/* + * 函数功能:获取 socket 最后错误码 + * 返回值:错误码(NET_ERR_xxx) + */ +int net_get_errno(void); + +/* + * 函数功能:将 IP 地址字符串转为网络字节序 + * 入口参数:cp - 点分十进制字符串 "192.168.1.100" + * 返回值:网络字节序 IP + */ +uint32_t net_inet_addr(const char *cp); + +/* + * 函数功能:将网络字节序 IP 转为字符串 + * 入口参数:addr - 网络字节序 IP + * buf - 输出缓冲区(至少 16 字节) + * 返回值:buf 指针 + */ +char *net_inet_ntoa(uint32_t addr, char *buf); + +/* + * 函数功能:端口字节序转换(主机序 -> 网络序) + */ +uint16_t net_htons(uint16_t hostshort); + +/* + * 函数功能:端口字节序转换(网络序 -> 主机序) + */ +uint16_t net_ntohs(uint16_t netshort); + +/* + * 函数功能:IP 地址字节序转换(主机序 -> 网络序) + */ +uint32_t net_htonl(uint32_t hostlong); + +/* + * 函数功能:IP 地址字节序转换(网络序 -> 主机序) + */ +uint32_t net_ntohl(uint32_t netlong); + +/* + * 函数功能:获取 Socket 控制块指针(内部使用) + * 入口参数:sockfd - socket 描述符 + * 返回值:Socket 控制块指针,失败返回 NULL + */ +net_sock_t *net_get_sock(int sockfd); + +#ifdef __cplusplus +} +#endif + +#endif /* __NET_SOCKET_H */ diff --git a/Drivers/BSP/NET/net_types.h b/Drivers/BSP/NET/net_types.h new file mode 100644 index 0000000..e3ebf4b --- /dev/null +++ b/Drivers/BSP/NET/net_types.h @@ -0,0 +1,175 @@ +/* + * 模块名称:Network Types + * 模块功能:网络子系统类型定义,包括地址结构体、Socket状态、 + * 事件类型、Socket控制块等 + * 适用平台:STM32F4 系列(CH395F 以太网芯片) + * 作者:王建锋 + * 创建日期:2026-07-18 + * 修改记录: + */ + +#ifndef __NET_TYPES_H +#define __NET_TYPES_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include "net_config.h" + +/* + * 地址族定义 + */ +#define NET_AF_INET 2 /* IPv4 */ +#define NET_AF_INET6 10 /* IPv6(未支持) */ +#define NET_AF_UNSPEC 0 /* 未指定 */ + +/* + * Socket 类型定义 + */ +#define NET_SOCK_STREAM 1 /* TCP */ +#define NET_SOCK_DGRAM 2 /* UDP */ + +/* + * 协议号定义 + */ +#define NET_IPPROTO_TCP 6 +#define NET_IPPROTO_UDP 17 +#define NET_IPPROTO_ICMP 1 + +/* + * 特殊地址定义 + */ +#define NET_INADDR_ANY 0 /* 任意地址 */ +#define NET_INADDR_NONE 0xFFFFFFFF /* 无效地址 */ + +/* + * 接收标志定义 + */ +#define NET_MSG_DONTWAIT 0x01 /* 非阻塞模式 */ + +/* + * 网络字节序转换宏 + */ +#ifdef __BIG_ENDIAN +#define NET_htonl(x) (x) +#define NET_htons(x) (x) +#define NET_ntohl(x) (x) +#define NET_ntohs(x) (x) +#else +#define NET_htonl(x) (((uint32_t)(x) << 24) | \ + (((uint32_t)(x) & 0xFF00) << 8) | \ + (((uint32_t)(x) & 0xFF0000) >> 8) | \ + ((uint32_t)(x) >> 24)) +#define NET_htons(x) (((uint16_t)(x) << 8) | ((uint16_t)(x) >> 8)) +#define NET_ntohl(x) NET_htonl(x) +#define NET_ntohs(x) NET_htons(x) +#endif + +/* + * 套接字地址结构体 + */ +struct net_sockaddr { + uint16_t sa_family; /* 地址族 */ + char sa_data[14]; /* 地址数据 */ +}; + +/* + * IPv4 套接字地址结构体 + */ +struct net_sockaddr_in { + uint16_t sin_family; /* 地址族,必须为 AF_INET */ + uint16_t sin_port; /* 端口号(网络字节序) */ + struct { + uint32_t s_addr; /* IP 地址(网络字节序) */ + } sin_addr; + char sin_zero[8]; /* 填充字节 */ +}; + +/* + * Socket 错误码定义 + */ +#define NET_OK 0 /* 成功 */ +#define NET_ERR -1 /* 通用错误 */ +#define NET_ERR_BADF -2 /* 无效 socket 描述符 */ +#define NET_ERR_INVAL -3 /* 无效参数 */ +#define NET_ERR_NOMEM -4 /* 内存不足 */ +#define NET_ERR_NOTCONN -5 /* 未连接 */ +#define NET_ERR_ISCONN -6 /* 已连接 */ +#define NET_ERR_WOULDBLOCK -7 /* 非阻塞模式下操作未完成 */ +#define NET_ERR_CONNRESET -8 /* 连接被重置 */ +#define NET_ERR_TIMEDOUT -9 /* 操作超时 */ +#define NET_ERR_NOSPACE -10 /* 发送缓冲区无空间 */ +#define NET_ERR_BUSY -11 /* 操作未完成(CH395F BUSY) */ + +/* + * Socket 状态枚举 + */ +typedef enum { + NET_SOCK_STATE_CLOSED = 0, /* 未使用 */ + NET_SOCK_STATE_CREATED, /* 已创建,未绑定 */ + NET_SOCK_STATE_BOUND, /* 已绑定 */ + NET_SOCK_STATE_LISTENING, /* TCP Server 监听中(仅监听 Socket) */ + NET_SOCK_STATE_CONNECTING, /* TCP Client 连接中 */ + NET_SOCK_STATE_ESTABLISHED, /* TCP 已连接 */ + NET_SOCK_STATE_UDP_OPEN, /* UDP 已打开 */ + NET_SOCK_STATE_TCP_ACCEPT, /* TCP Server 数据 Socket,等待分配连接 */ + NET_SOCK_STATE_TCP_CLIENT_RECONNECTING, /* TCP Client 重连中 */ +} net_sock_state_t; + +/* + * 网络事件类型枚举 + */ +typedef enum { + NET_EVENT_CONNECTED = 0, /* 连接建立 */ + NET_EVENT_DISCONNECTED, /* 连接断开 */ + NET_EVENT_DATA_RECEIVED, /* 收到数据 */ + NET_EVENT_SEND_COMPLETE, /* 发送完成 */ + NET_EVENT_TIMEOUT, /* 操作超时 */ + NET_EVENT_ERROR, /* 错误发生 */ +} net_event_t; + +/* + * 事件回调函数类型定义 + * 参数:sockfd - socket 描述符 + * event - 事件类型 + * arg - 用户参数 + */ +typedef void (*net_event_cb_t)(int sockfd, net_event_t event, void *arg); + +/* + * Socket 控制块结构体 + * 每个 socket 对应一个控制块,管理其状态和参数 + */ +typedef struct { + uint8_t ch395_sock; /* CH395F 物理 Socket 索引 (0~7) */ + net_sock_state_t state; /* Socket 状态 */ + uint8_t type; /* SOCK_STREAM / SOCK_DGRAM */ + uint8_t in_use; /* 是否在用标志 */ + uint16_t local_port; /* 本地端口(主机序) */ + uint16_t remote_port; /* 远端端口(主机序) */ + uint32_t remote_ip; /* 远端 IP(网络字节序) */ + uint8_t remote_ip_arr[4]; /* 远端 IP 数组(低字节在前) */ + + /* 事件回调(可选) */ + net_event_cb_t event_cb; + void *event_arg; + + /* TCP Server 多连接模式:监听 Socket 的数据 Socket 列表 */ + int data_socks[NET_TCP_SERVER_MAX_CLIENTS]; + int data_sock_count; + + /* 发送就绪标志 */ + uint8_t send_ready; + + /* 统计信息 */ + uint32_t recv_bytes; /* 累计接收字节数 */ + uint32_t send_bytes; /* 累计发送字节数 */ +} net_sock_t; + +#ifdef __cplusplus +} +#endif + +#endif /* __NET_TYPES_H */ diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.c b/Drivers/BSP/TPAFE5160/tpafe5160.c index 08e94a9..53061a7 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.c +++ b/Drivers/BSP/TPAFE5160/tpafe5160.c @@ -11,6 +11,7 @@ /* 头文件包含区 */ #include "tpafe5160.h" +#include "main.h" /* ======================== 私有宏定义 ======================== */ @@ -325,13 +326,13 @@ const int16_t* tpafe5160_get_buf(void) } /* - * 函数功能:BUSY 下降沿 EXTI 回调(由 HAL_GPIO_EXTI_IRQHandler 调用) + * 函数功能:BUSY 下降沿 EXTI 处理(由 HAL_GPIO_EXTI_Callback 调用) * 入口参数:GPIO_Pin - 触发中断的引脚号 * 返回值:无 * 限定条件:BUSY EXTI 已使能 * 函数说明:转换完成后自动读取 8 通道数据到缓冲区,耗时约 0.5µs (168MHz) */ -void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) +void tpafe5160_exti_handler(uint16_t GPIO_Pin) { if (GPIO_Pin != TP_BUSY_Pin) { return; diff --git a/Drivers/BSP/TPAFE5160/tpafe5160.h b/Drivers/BSP/TPAFE5160/tpafe5160.h index 8fc391b..90d1569 100644 --- a/Drivers/BSP/TPAFE5160/tpafe5160.h +++ b/Drivers/BSP/TPAFE5160/tpafe5160.h @@ -217,6 +217,15 @@ void tpafe5160_clear_ready(void); */ const int16_t* tpafe5160_get_buf(void); +/* + * 函数功能:EXTI 中断处理(由 HAL_GPIO_EXTI_Callback 调用) + * 入口参数:GPIO_Pin - 触发的引脚号 + * 返回值:无 + * 限定条件:BUSY EXTI 已使能 + * 函数说明:BUSY 下降沿时自动读取 8 通道数据到缓冲区 + */ +void tpafe5160_exti_handler(uint16_t GPIO_Pin); + #ifdef __cplusplus } #endif diff --git a/Inc/main.h b/Inc/main.h index 66ba597..efbacc9 100644 --- a/Inc/main.h +++ b/Inc/main.h @@ -57,6 +57,8 @@ void Error_Handler(void); /* USER CODE END EFP */ /* Private defines -----------------------------------------------------------*/ +#define CH395_INT_Pin GPIO_PIN_5 +#define CH395_INT_GPIO_Port GPIOE #define LED1_Pin GPIO_PIN_4 #define LED1_GPIO_Port GPIOC #define LED2_Pin GPIO_PIN_5 diff --git a/Inc/stm32f4xx_it.h b/Inc/stm32f4xx_it.h index 7e5f406..9718a22 100644 --- a/Inc/stm32f4xx_it.h +++ b/Inc/stm32f4xx_it.h @@ -55,7 +55,10 @@ void SVC_Handler(void); void DebugMon_Handler(void); void PendSV_Handler(void); void SysTick_Handler(void); +void DMA1_Stream3_IRQHandler(void); +void DMA1_Stream4_IRQHandler(void); void EXTI9_5_IRQHandler(void); +void SPI2_IRQHandler(void); void USART1_IRQHandler(void); void USART2_IRQHandler(void); void USART3_IRQHandler(void); diff --git a/MDK-ARM/STM32F407-Demo.uvprojx b/MDK-ARM/STM32F407-Demo.uvprojx index ac39bd6..76db6a9 100644 --- a/MDK-ARM/STM32F407-Demo.uvprojx +++ b/MDK-ARM/STM32F407-Demo.uvprojx @@ -1,7 +1,10 @@ - - + + + 2.1 +
### uVision Project, (C) Keil Software
+ STM32F407-Demo @@ -16,28 +19,28 @@ Keil.STM32F4xx_DFP.3.1.1 https://www.keil.com/pack/ IRAM(0x20000000-0x2001BFFF) IRAM2(0x2001C000-0x2001FFFF) IROM(0x8000000-0x80FFFFF) CLOCK(25000000) FPU2 CPUTYPE("Cortex-M4") TZ - - - + + + 0 - - - - - - - - - - + + + + + + + + + + $$Device:STM32F407ZGTx$CMSIS\SVD\STM32F407.svd 0 0 - - - - - + + + + + 0 0 @@ -52,15 +55,15 @@ 1 1 1 - + 1 0 0 0 0 - - + + 0 0 0 @@ -69,8 +72,8 @@ 0 0 - - + + 0 0 0 @@ -79,15 +82,15 @@ 0 1 - - + + 0 0 0 0 1 - + 0 @@ -101,8 +104,8 @@ 0 0 3 - - + + 0 @@ -136,10 +139,10 @@ 1 BIN\UL2CM3.DLL "" () - - - - + + + + 0 @@ -172,7 +175,7 @@ 0 0 "Cortex-M4" - + 0 0 0 @@ -307,7 +310,7 @@ 0x4000 - + 1 @@ -334,10 +337,10 @@ 0 0 - + USE_HAL_DRIVER,STM32F407xx - - ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Lib/FlashDB/port/fal/inc;../Lib/FlashDB/inc;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485 + + ../Inc;../Drivers/STM32F4xx_HAL_Driver/Inc;../Drivers/STM32F4xx_HAL_Driver/Inc/Legacy;../Drivers/CMSIS/Device/ST/STM32F4xx/Include;../Drivers/CMSIS/Include;../Drivers/BSP/CH395F;../Drivers/BSP/GD5F2GQ5UE;../Lib/FlashDB/port/fal/inc;../Lib/FlashDB/inc;../Drivers/BSP/TPAFE5160;../Drivers/BSP/SD2506;../Drivers/BSP/RS485;../Drivers/BSP/NET @@ -352,10 +355,10 @@ 0 1 - - - - + + + + @@ -365,15 +368,15 @@ 0 1 0 - - - - - - - - - + + + + + + + + + @@ -418,6 +421,8 @@ 2 2 11 + + 1 @@ -445,19 +450,13 @@ 2 2 2 + + + + + + - - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 2 - 1 - @@ -478,6 +477,8 @@ 2 2 11 + + 1 @@ -505,6 +506,12 @@ 2 2 2 + + + + + + @@ -526,6 +533,8 @@ 2 2 11 + + 1 @@ -553,6 +562,12 @@ 2 2 2 + + + + + + @@ -574,6 +589,8 @@ 2 2 11 + + 1 @@ -601,6 +618,12 @@ 2 2 2 + + + + + + @@ -637,6 +660,8 @@ 2 2 11 + + 1 @@ -664,6 +689,12 @@ 2 2 2 + + + + + + @@ -685,6 +716,8 @@ 2 2 11 + + 1 @@ -712,6 +745,12 @@ 2 2 2 + + + + + + @@ -798,6 +837,8 @@ 2 2 11 + + 1 @@ -825,6 +866,12 @@ 2 2 2 + + + + + + @@ -846,6 +893,8 @@ 2 2 11 + + 1 @@ -873,6 +922,12 @@ 2 2 2 + + + + + + @@ -922,6 +977,16 @@ 1 ..\Drivers\BSP\RS485\rs485.c + + net_select.c + 1 + ..\Drivers\BSP\NET\net_select.c + + + net_socket.c + 1 + ..\Drivers\BSP\NET\net_socket.c + @@ -970,18 +1035,20 @@ + - + - + - + - + + @@ -990,5 +1057,5 @@ -
+
diff --git a/MDK-ARM/build.bat b/MDK-ARM/build.bat new file mode 100644 index 0000000..9448881 --- /dev/null +++ b/MDK-ARM/build.bat @@ -0,0 +1,19 @@ +@echo off +setlocal + +set UV4=C:\Keil_v5\UV4\UV4.exe +set PROJ=%~dp0STM32F407-Demo.uvprojx +set LOG=%~dp0build_log.txt +set TARGET=STM32F407-Demo + +"%UV4%" -r "%PROJ%" -t%TARGET% -j0 -o "%LOG%" +set RET=%ERRORLEVEL% + +if %RET% equ 0 ( + echo BUILD_OK: 0 Error(s), 0 Warning(s) +) else ( + echo BUILD_FAIL: exit code %RET% + if exist "%LOG%" type "%LOG%" +) + +exit /b %RET% diff --git a/MDK-ARM/build_log.txt b/MDK-ARM/build_log.txt new file mode 100644 index 0000000..d228e1e --- /dev/null +++ b/MDK-ARM/build_log.txt @@ -0,0 +1,49 @@ +*** Using Compiler 'V5.06 update 7 (build 960)', folder: 'C:\Keil_v5\ARM\ARMCC\Bin' +Rebuild target 'STM32F407-Demo' +assembling startup_stm32f407xx.s... +compiling gpio.c... +compiling dma.c... +compiling stm32f4xx_hal_i2c_ex.c... +compiling stm32f4xx_hal_flash_ramfunc.c... +compiling stm32f4xx_it.c... +compiling stm32f4xx_hal_pwr_ex.c... +compiling stm32f4xx_hal_gpio.c... +compiling spi.c... +compiling stm32f4xx_hal_flash_ex.c... +compiling i2c.c... +compiling stm32f4xx_hal.c... +compiling stm32f4xx_hal_rcc_ex.c... +compiling stm32f4xx_hal_pwr.c... +compiling stm32f4xx_hal_cortex.c... +compiling stm32f4xx_hal_flash.c... +compiling main.c... +compiling stm32f4xx_hal_exti.c... +compiling usart.c... +compiling stm32f4xx_hal_msp.c... +compiling stm32f4xx_hal_dma.c... +compiling stm32f4xx_hal_rcc.c... +compiling stm32f4xx_hal_dma_ex.c... +compiling stm32f4xx_hal_i2c.c... +compiling fdb.c... +compiling fal.c... +compiling fal_flash.c... +compiling fdb_utils.c... +compiling fal_partition.c... +compiling fdb_tsdb.c... +compiling fdb_kvdb.c... +compiling system_stm32f4xx.c... +compiling stm32f4xx_hal_spi.c... +compiling fal_flash_gd5f2gq5ue.c... +compiling ch395f.c... +compiling stm32f4xx_hal_uart.c... +compiling gd5f2gq5ue.c... +compiling rs485.c... +compiling sd2506.c... +compiling tpafe5160.c... +compiling net_select.c... +compiling net_socket.c... +linking... +Program Size: Code=20964 RO-data=624 RW-data=40 ZI-data=7344 +FromELF: creating hex file... +".\STM32F407-Demo\STM32F407-Demo.axf" - 0 Error(s), 0 Warning(s). +Build Time Elapsed: 00:00:12 diff --git a/STM32F407-Demo.ioc b/STM32F407-Demo.ioc index b388d77..12f7985 100644 --- a/STM32F407-Demo.ioc +++ b/STM32F407-Demo.ioc @@ -1,10 +1,32 @@ #MicroXplorer Configuration settings - do not modify -CAD.formats= -CAD.pinconfig= -CAD.provider= +CAD.formats=[] +CAD.pinconfig=Dual +CAD.provider=Component Search Engine Dma.Request0=USART1_TX Dma.Request1=USART1_RX -Dma.RequestsNb=2 +Dma.Request2=SPI2_RX +Dma.Request3=SPI2_TX +Dma.RequestsNb=4 +Dma.SPI2_RX.2.Direction=DMA_PERIPH_TO_MEMORY +Dma.SPI2_RX.2.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.SPI2_RX.2.Instance=DMA1_Stream3 +Dma.SPI2_RX.2.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.SPI2_RX.2.MemInc=DMA_MINC_ENABLE +Dma.SPI2_RX.2.Mode=DMA_NORMAL +Dma.SPI2_RX.2.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.SPI2_RX.2.PeriphInc=DMA_PINC_DISABLE +Dma.SPI2_RX.2.Priority=DMA_PRIORITY_VERY_HIGH +Dma.SPI2_RX.2.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode +Dma.SPI2_TX.3.Direction=DMA_MEMORY_TO_PERIPH +Dma.SPI2_TX.3.FIFOMode=DMA_FIFOMODE_DISABLE +Dma.SPI2_TX.3.Instance=DMA1_Stream4 +Dma.SPI2_TX.3.MemDataAlignment=DMA_MDATAALIGN_BYTE +Dma.SPI2_TX.3.MemInc=DMA_MINC_ENABLE +Dma.SPI2_TX.3.Mode=DMA_NORMAL +Dma.SPI2_TX.3.PeriphDataAlignment=DMA_PDATAALIGN_BYTE +Dma.SPI2_TX.3.PeriphInc=DMA_PINC_DISABLE +Dma.SPI2_TX.3.Priority=DMA_PRIORITY_VERY_HIGH +Dma.SPI2_TX.3.RequestParameters=Instance,Direction,PeriphInc,MemInc,PeriphDataAlignment,MemDataAlignment,Mode,Priority,FIFOMode Dma.USART1_RX.1.Direction=DMA_PERIPH_TO_MEMORY Dma.USART1_RX.1.FIFOMode=DMA_FIFOMODE_DISABLE Dma.USART1_RX.1.Instance=DMA2_Stream2 @@ -45,70 +67,73 @@ Mcu.IP9=USART1 Mcu.IPNb=12 Mcu.Name=STM32F407Z(E-G)Tx Mcu.Package=LQFP144 -Mcu.Pin0=PH0-OSC_IN -Mcu.Pin1=PH1-OSC_OUT -Mcu.Pin10=PF15 -Mcu.Pin11=PG0 -Mcu.Pin12=PG1 -Mcu.Pin13=PB10 -Mcu.Pin14=PB11 -Mcu.Pin15=PB12 -Mcu.Pin16=PB13 -Mcu.Pin17=PB14 -Mcu.Pin18=PB15 -Mcu.Pin19=PG2 -Mcu.Pin2=PC4 -Mcu.Pin20=PG3 -Mcu.Pin21=PG4 -Mcu.Pin22=PG5 -Mcu.Pin23=PG6 -Mcu.Pin24=PG7 -Mcu.Pin25=PG8 -Mcu.Pin26=PA9 -Mcu.Pin27=PA10 -Mcu.Pin28=PA13 -Mcu.Pin29=PA14 -Mcu.Pin3=PC5 -Mcu.Pin30=PC10 -Mcu.Pin31=PC11 -Mcu.Pin32=PC12 -Mcu.Pin33=PD0 -Mcu.Pin34=PD1 -Mcu.Pin35=PD2 -Mcu.Pin36=PD3 -Mcu.Pin37=PD4 -Mcu.Pin38=PD5 -Mcu.Pin39=PD6 -Mcu.Pin4=PB1 -Mcu.Pin40=PD7 -Mcu.Pin41=PG9 -Mcu.Pin42=PG10 -Mcu.Pin43=PG11 -Mcu.Pin44=PG12 -Mcu.Pin45=PG13 -Mcu.Pin46=PG14 -Mcu.Pin47=PG15 -Mcu.Pin48=PB3 -Mcu.Pin49=PB4 -Mcu.Pin5=PB2 -Mcu.Pin50=PB5 -Mcu.Pin51=PB6 -Mcu.Pin52=PB7 -Mcu.Pin53=PB8 -Mcu.Pin54=PE0 -Mcu.Pin55=PE1 -Mcu.Pin56=VP_SYS_VS_Systick -Mcu.Pin6=PF11 -Mcu.Pin7=PF12 -Mcu.Pin8=PF13 -Mcu.Pin9=PF14 -Mcu.PinsNb=57 +Mcu.Pin0=PE5 +Mcu.Pin1=PH0-OSC_IN +Mcu.Pin10=PF14 +Mcu.Pin11=PF15 +Mcu.Pin12=PG0 +Mcu.Pin13=PG1 +Mcu.Pin14=PB10 +Mcu.Pin15=PB11 +Mcu.Pin16=PB12 +Mcu.Pin17=PB13 +Mcu.Pin18=PB14 +Mcu.Pin19=PB15 +Mcu.Pin2=PH1-OSC_OUT +Mcu.Pin20=PG2 +Mcu.Pin21=PG3 +Mcu.Pin22=PG4 +Mcu.Pin23=PG5 +Mcu.Pin24=PG6 +Mcu.Pin25=PG7 +Mcu.Pin26=PG8 +Mcu.Pin27=PA9 +Mcu.Pin28=PA10 +Mcu.Pin29=PA13 +Mcu.Pin3=PC4 +Mcu.Pin30=PA14 +Mcu.Pin31=PC10 +Mcu.Pin32=PC11 +Mcu.Pin33=PC12 +Mcu.Pin34=PD0 +Mcu.Pin35=PD1 +Mcu.Pin36=PD2 +Mcu.Pin37=PD3 +Mcu.Pin38=PD4 +Mcu.Pin39=PD5 +Mcu.Pin4=PC5 +Mcu.Pin40=PD6 +Mcu.Pin41=PD7 +Mcu.Pin42=PG9 +Mcu.Pin43=PG10 +Mcu.Pin44=PG11 +Mcu.Pin45=PG12 +Mcu.Pin46=PG13 +Mcu.Pin47=PG14 +Mcu.Pin48=PG15 +Mcu.Pin49=PB3 +Mcu.Pin5=PB1 +Mcu.Pin50=PB4 +Mcu.Pin51=PB5 +Mcu.Pin52=PB6 +Mcu.Pin53=PB7 +Mcu.Pin54=PB8 +Mcu.Pin55=PE0 +Mcu.Pin56=PE1 +Mcu.Pin57=VP_SYS_VS_Systick +Mcu.Pin6=PB2 +Mcu.Pin7=PF11 +Mcu.Pin8=PF12 +Mcu.Pin9=PF13 +Mcu.PinsNb=58 Mcu.ThirdPartyNb=0 Mcu.UserConstants= Mcu.UserName=STM32F407ZGTx MxCube.Version=6.17.0 MxDb.Version=DB.6.0.170 NVIC.BusFault_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false +NVIC.DMA1_Stream3_IRQn=true\:1\:0\:true\:false\:true\:false\:true\:true +NVIC.DMA1_Stream4_IRQn=true\:1\:0\:true\:false\:true\:false\:true\:true NVIC.DMA2_Stream2_IRQn=true\:3\:0\:true\:false\:true\:false\:true\:true NVIC.DMA2_Stream7_IRQn=true\:3\:0\:true\:false\:true\:false\:true\:true NVIC.DebugMonitor_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false @@ -119,6 +144,7 @@ NVIC.MemoryManagement_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.NonMaskableInt_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.PendSV_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.PriorityGroup=NVIC_PRIORITYGROUP_4 +NVIC.SPI2_IRQn=true\:1\:0\:true\:false\:true\:true\:true\:true NVIC.SVCall_IRQn=true\:0\:0\:false\:false\:true\:false\:false\:false NVIC.SysTick_IRQn=true\:15\:0\:false\:false\:true\:false\:true\:false NVIC.UART4_IRQn=true\:5\:0\:true\:false\:true\:true\:true\:true @@ -315,6 +341,10 @@ PE1.GPIO_Speed=GPIO_SPEED_FREQ_VERY_HIGH PE1.Locked=true PE1.PinState=GPIO_PIN_SET PE1.Signal=GPIO_Output +PE5.GPIOParameters=GPIO_Label +PE5.GPIO_Label=CH395_INT +PE5.Locked=true +PE5.Signal=GPIO_Input PF11.GPIOParameters=GPIO_Speed,PinState,GPIO_PuPd,GPIO_Label PF11.GPIO_Label=LED5 PF11.GPIO_PuPd=GPIO_PULLUP @@ -481,14 +511,16 @@ RCC.VCOOutputFreq_Value=336000000 RCC.VcooutputI2S=192000000 SH.GPXTI7.0=GPIO_EXTI7 SH.GPXTI7.ConfNb=1 -SPI1.CalculateBaudRate=42.0 MBits/s +SPI1.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_8 +SPI1.CalculateBaudRate=10.5 MBits/s SPI1.Direction=SPI_DIRECTION_2LINES -SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate +SPI1.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler SPI1.Mode=SPI_MODE_MASTER SPI1.VirtualType=VM_MASTER -SPI2.CalculateBaudRate=21.0 MBits/s +SPI2.BaudRatePrescaler=SPI_BAUDRATEPRESCALER_8 +SPI2.CalculateBaudRate=5.25 MBits/s SPI2.Direction=SPI_DIRECTION_2LINES -SPI2.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate +SPI2.IPParameters=VirtualType,Mode,Direction,CalculateBaudRate,BaudRatePrescaler SPI2.Mode=SPI_MODE_MASTER SPI2.VirtualType=VM_MASTER UART4.IPParameters=VirtualMode diff --git a/Src/dma.c b/Src/dma.c index 67a3016..d343f2f 100644 --- a/Src/dma.c +++ b/Src/dma.c @@ -41,8 +41,15 @@ void MX_DMA_Init(void) /* DMA controller clock enable */ __HAL_RCC_DMA2_CLK_ENABLE(); + __HAL_RCC_DMA1_CLK_ENABLE(); /* DMA interrupt init */ + /* DMA1_Stream3_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Stream3_IRQn, 1, 0); + HAL_NVIC_EnableIRQ(DMA1_Stream3_IRQn); + /* DMA1_Stream4_IRQn interrupt configuration */ + HAL_NVIC_SetPriority(DMA1_Stream4_IRQn, 1, 0); + HAL_NVIC_EnableIRQ(DMA1_Stream4_IRQn); /* DMA2_Stream2_IRQn interrupt configuration */ HAL_NVIC_SetPriority(DMA2_Stream2_IRQn, 3, 0); HAL_NVIC_EnableIRQ(DMA2_Stream2_IRQn); diff --git a/Src/gpio.c b/Src/gpio.c index 2ba48eb..e98b16d 100644 --- a/Src/gpio.c +++ b/Src/gpio.c @@ -45,6 +45,7 @@ void MX_GPIO_Init(void) GPIO_InitTypeDef GPIO_InitStruct = {0}; /* GPIO Ports Clock Enable */ + __HAL_RCC_GPIOE_CLK_ENABLE(); __HAL_RCC_GPIOH_CLK_ENABLE(); __HAL_RCC_GPIOC_CLK_ENABLE(); __HAL_RCC_GPIOB_CLK_ENABLE(); @@ -52,7 +53,6 @@ void MX_GPIO_Init(void) __HAL_RCC_GPIOG_CLK_ENABLE(); __HAL_RCC_GPIOA_CLK_ENABLE(); __HAL_RCC_GPIOD_CLK_ENABLE(); - __HAL_RCC_GPIOE_CLK_ENABLE(); /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOC, LED1_Pin|LED2_Pin, GPIO_PIN_SET); @@ -75,6 +75,12 @@ void MX_GPIO_Init(void) /*Configure GPIO pin Output Level */ HAL_GPIO_WritePin(GPIOE, GD_CS_Pin|GD_HOLD_Pin, GPIO_PIN_SET); + /*Configure GPIO pin : CH395_INT_Pin */ + GPIO_InitStruct.Pin = CH395_INT_Pin; + GPIO_InitStruct.Mode = GPIO_MODE_INPUT; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(CH395_INT_GPIO_Port, &GPIO_InitStruct); + /*Configure GPIO pins : LED1_Pin LED2_Pin */ GPIO_InitStruct.Pin = LED1_Pin|LED2_Pin; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; diff --git a/Src/main.c b/Src/main.c index 07c8d5e..3add98a 100644 --- a/Src/main.c +++ b/Src/main.c @@ -33,6 +33,8 @@ #include "tpafe5160.h" #include "sd2506.h" #include "rs485.h" +#include "net_socket.h" +#include "net_select.h" /* USER CODE END Includes */ /* Private typedef -----------------------------------------------------------*/ @@ -47,7 +49,10 @@ /* Private macro -------------------------------------------------------------*/ /* USER CODE BEGIN PM */ - +#define TS() do { \ + uint32_t _t = HAL_GetTick(); \ + printf("[%lu.%03lu] ", _t / 1000UL, _t % 1000UL); \ +} while(0) /* USER CODE END PM */ /* Private variables ---------------------------------------------------------*/ @@ -57,6 +62,9 @@ rs485_handle_t g_rs485; #define RS485_RX_BUF_SIZE 256 uint8_t g_rs485_rx_buf[RS485_RX_BUF_SIZE]; +/* TCP Server Echo 测试 */ +static int s_listen_sock = -1; +static uint8_t s_echo_buf[1500]; /* USER CODE END PV */ /* Private function prototypes -----------------------------------------------*/ @@ -110,49 +118,29 @@ int main(void) /* USER CODE BEGIN 2 */ HAL_Delay(100); - /* CH395F 硬件检测 */ - ch395f_status_t ch395f_ret = ch395f_check_exist(); - printf("[CH395F] SPI: %s\r\n", - (ch395f_ret == CH395F_STATUS_OK) ? "OK" : "FAIL"); + /* BSD Socket API 初始化(包含 CH395F 复位/配置/协议栈初始化/多连接模式) */ + printf("[NET] init: %s\r\n", + (net_init("192.168.1.100", "255.255.255.0", "192.168.1.1") == 0) ? "OK" : "FAIL"); - printf("[CH395F] version: 0x%02X\r\n", ch395f_get_version()); + /* 读取 MAC */ + uint8_t mac[6]; + ch395f_get_mac_addr(mac); + printf("[CH395F] MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - if (ch395f_ret == CH395F_STATUS_OK) { - /* 1. 复位 */ - ch395f_reset(); - - /* 2. 配置网络参数(必须在 INIT 之前,INIT 会锁定协议栈参数) */ - uint8_t ip[4] = {192, 168, 1, 100}; - uint8_t gw[4] = {192, 168, 1, 1}; - uint8_t mask[4] = {255, 255, 255, 0}; - ch395f_set_ip_addr(ip); - ch395f_set_gwip_addr(gw); - ch395f_set_mask_addr(mask); - - /* 3. 初始化协议栈(锁定 IP/GW/MASK 等参数) */ - printf("[CH395F] init: %s\r\n", - (ch395f_init() == CH395F_STATUS_OK) ? "OK" : "FAIL"); - - /* 4. 读取 MAC */ - uint8_t mac[6]; - ch395f_get_mac_addr(mac); - printf("[CH395F] MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n", - mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - - /* 5. 强制 100M 全双工,建立 PHY 链路 */ - ch395f_set_phy(CH395F_PHY_100M_FULL); - HAL_Delay(3000); - uint8_t phy = ch395f_get_phy_status(); - const char *phy_str = "UNKNOWN"; - switch (phy) { - case CH395F_PHY_DISCONN: phy_str = "DISCONNECT"; break; - case CH395F_PHY_10M_FULL: phy_str = "10M FULL"; break; - case CH395F_PHY_10M_HALF: phy_str = "10M HALF"; break; - case CH395F_PHY_100M_FULL: phy_str = "100M FULL"; break; - case CH395F_PHY_100M_HALF: phy_str = "100M HALF"; break; - } - printf("[CH395F] PHY: %s\r\n", phy_str); + /* 强制 100M 全双工,建立 PHY 链路 */ + ch395f_set_phy(CH395F_PHY_100M_FULL); + HAL_Delay(3000); + uint8_t phy = ch395f_get_phy_status(); + const char *phy_str = "UNKNOWN"; + switch (phy) { + case CH395F_PHY_DISCONN: phy_str = "DISCONNECT"; break; + case CH395F_PHY_10M_FULL: phy_str = "10M FULL"; break; + case CH395F_PHY_10M_HALF: phy_str = "10M HALF"; break; + case CH395F_PHY_100M_FULL: phy_str = "100M FULL"; break; + case CH395F_PHY_100M_HALF: phy_str = "100M HALF"; break; } + printf("[CH395F] PHY: %s\r\n", phy_str); /* GD5F 初始化测试 */ int ret = gd5f2gq5ue_init(); @@ -213,6 +201,30 @@ int main(void) /* 启动第一次 RS-485 接收 */ rs485_receive_start(&g_rs485, g_rs485_rx_buf, RS485_RX_BUF_SIZE); + /* 创建 TCP Server Echo 测试 */ + { + s_listen_sock = net_socket(NET_AF_INET, NET_SOCK_STREAM, 0); + printf("[NET] TCP listen socket: %d\r\n", s_listen_sock); + + struct net_sockaddr_in serv_addr; + memset(&serv_addr, 0, sizeof(serv_addr)); + serv_addr.sin_family = NET_AF_INET; + serv_addr.sin_port = net_htons(8080); + serv_addr.sin_addr.s_addr = NET_INADDR_ANY; + + if (net_bind(s_listen_sock, (struct net_sockaddr *)&serv_addr, sizeof(serv_addr)) == 0) { + printf("[NET] TCP bind: OK (port 8080)\r\n"); + } else { + printf("[NET] TCP bind: FAIL\r\n"); + } + + if (net_listen(s_listen_sock, NET_TCP_SERVER_MAX_CLIENTS) == 0) { + printf("[NET] TCP listen: OK (max_clients=%d)\r\n", NET_TCP_SERVER_MAX_CLIENTS); + } else { + printf("[NET] TCP listen: FAIL\r\n"); + } + } + /* 启动第一次转换(CubeMX 已配置 BUSY EXTI 中断) */ tpafe5160_start_conv_irq(); /* USER CODE END 2 */ @@ -224,22 +236,47 @@ int main(void) /* USER CODE END WHILE */ /* USER CODE BEGIN 3 */ - /* TPAFE5160 中断模式读取 */ - if (tpafe5160_data_ready()) { - const int16_t *buf = tpafe5160_get_buf(); - printf("[TPAFE5160] CH:"); - for (int i = 0; i < 8; i++) { - printf(" %d", buf[i]); + /* 网络轮询(必须在主循环中调用) */ + net_poll(); + + /* TCP Server Echo 处理 —— 多连接模式,socket 0 监听,socket 1~7 收发 */ + { + int i; + for (i = NET_TCP_SERVER_DATA_SOCK_START; i < NET_MAX_SOCKETS; i++) + { + net_sock_t *sk = net_get_sock(i); + if (sk == NULL || !sk->in_use || sk->state != NET_SOCK_STATE_ESTABLISHED) + { + continue; + } + + uint16_t rlen = ch395f_get_recv_len((uint8_t)i); + if (rlen > 0) { + uint16_t read_len = (rlen > sizeof(s_echo_buf)) ? sizeof(s_echo_buf) : rlen; + ch395f_read_recv_buf((uint8_t)i, s_echo_buf, read_len); + TS(); printf("[NET] recv sock%d: %d bytes\r\n", i, read_len); + ch395f_write_send_buf((uint8_t)i, s_echo_buf, read_len); + TS(); printf("[NET] send sock%d: %d bytes\r\n", i, read_len); + } } - printf("\r\n"); - tpafe5160_clear_ready(); - /* 启动下一次转换 */ - tpafe5160_start_conv_irq(); } +// /* TPAFE5160 中断模式读取 */ +// if (tpafe5160_data_ready()) { +// const int16_t *buf = tpafe5160_get_buf(); +// printf("[TPAFE5160] CH:"); +// for (int i = 0; i < 8; i++) { +// printf(" %d", buf[i]); +// } +// printf("\r\n"); +// tpafe5160_clear_ready(); +// /* 启动下一次转换 */ +// tpafe5160_start_conv_irq(); +// } + /* 其他任务 */ - HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); - HAL_Delay(500); +// HAL_GPIO_TogglePin(LED1_GPIO_Port, LED1_Pin); +// HAL_Delay(10); } /* USER CODE END 3 */ } diff --git a/Src/spi.c b/Src/spi.c index ded36d7..43acdb6 100644 --- a/Src/spi.c +++ b/Src/spi.c @@ -26,6 +26,8 @@ SPI_HandleTypeDef hspi1; SPI_HandleTypeDef hspi2; +DMA_HandleTypeDef hdma_spi2_rx; +DMA_HandleTypeDef hdma_spi2_tx; /* SPI1 init function */ void MX_SPI1_Init(void) @@ -45,7 +47,7 @@ void MX_SPI1_Init(void) hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; - hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; + hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; @@ -77,7 +79,7 @@ void MX_SPI2_Init(void) hspi2.Init.CLKPolarity = SPI_POLARITY_LOW; hspi2.Init.CLKPhase = SPI_PHASE_1EDGE; hspi2.Init.NSS = SPI_NSS_SOFT; - hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2; + hspi2.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; hspi2.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi2.Init.TIMode = SPI_TIMODE_DISABLE; hspi2.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; @@ -149,6 +151,46 @@ void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle) GPIO_InitStruct.Alternate = GPIO_AF5_SPI2; HAL_GPIO_Init(CH395F_SDO_GPIO_Port, &GPIO_InitStruct); + /* SPI2 DMA Init */ + /* SPI2_RX Init */ + hdma_spi2_rx.Instance = DMA1_Stream3; + hdma_spi2_rx.Init.Channel = DMA_CHANNEL_0; + hdma_spi2_rx.Init.Direction = DMA_PERIPH_TO_MEMORY; + hdma_spi2_rx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_spi2_rx.Init.MemInc = DMA_MINC_ENABLE; + hdma_spi2_rx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_spi2_rx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + hdma_spi2_rx.Init.Mode = DMA_NORMAL; + hdma_spi2_rx.Init.Priority = DMA_PRIORITY_VERY_HIGH; + hdma_spi2_rx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_spi2_rx) != HAL_OK) + { + Error_Handler(); + } + + __HAL_LINKDMA(spiHandle,hdmarx,hdma_spi2_rx); + + /* SPI2_TX Init */ + hdma_spi2_tx.Instance = DMA1_Stream4; + hdma_spi2_tx.Init.Channel = DMA_CHANNEL_0; + hdma_spi2_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; + hdma_spi2_tx.Init.PeriphInc = DMA_PINC_DISABLE; + hdma_spi2_tx.Init.MemInc = DMA_MINC_ENABLE; + hdma_spi2_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; + hdma_spi2_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; + hdma_spi2_tx.Init.Mode = DMA_NORMAL; + hdma_spi2_tx.Init.Priority = DMA_PRIORITY_VERY_HIGH; + hdma_spi2_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + if (HAL_DMA_Init(&hdma_spi2_tx) != HAL_OK) + { + Error_Handler(); + } + + __HAL_LINKDMA(spiHandle,hdmatx,hdma_spi2_tx); + + /* SPI2 interrupt Init */ + HAL_NVIC_SetPriority(SPI2_IRQn, 1, 0); + HAL_NVIC_EnableIRQ(SPI2_IRQn); /* USER CODE BEGIN SPI2_MspInit 1 */ /* USER CODE END SPI2_MspInit 1 */ @@ -192,6 +234,12 @@ void HAL_SPI_MspDeInit(SPI_HandleTypeDef* spiHandle) */ HAL_GPIO_DeInit(GPIOB, CH395F_SCK_Pin|CH395F_SDO_Pin|CH395F_SDOB15_Pin); + /* SPI2 DMA DeInit */ + HAL_DMA_DeInit(spiHandle->hdmarx); + HAL_DMA_DeInit(spiHandle->hdmatx); + + /* SPI2 interrupt Deinit */ + HAL_NVIC_DisableIRQ(SPI2_IRQn); /* USER CODE BEGIN SPI2_MspDeInit 1 */ /* USER CODE END SPI2_MspDeInit 1 */ diff --git a/Src/stm32f4xx_it.c b/Src/stm32f4xx_it.c index a61978f..0a81eb4 100644 --- a/Src/stm32f4xx_it.c +++ b/Src/stm32f4xx_it.c @@ -55,6 +55,9 @@ /* USER CODE END 0 */ /* External variables --------------------------------------------------------*/ +extern DMA_HandleTypeDef hdma_spi2_rx; +extern DMA_HandleTypeDef hdma_spi2_tx; +extern SPI_HandleTypeDef hspi2; extern DMA_HandleTypeDef hdma_usart1_tx; extern DMA_HandleTypeDef hdma_usart1_rx; extern UART_HandleTypeDef huart4; @@ -204,6 +207,34 @@ void SysTick_Handler(void) /* please refer to the startup file (startup_stm32f4xx.s). */ /******************************************************************************/ +/** + * @brief This function handles DMA1 stream3 global interrupt. + */ +void DMA1_Stream3_IRQHandler(void) +{ + /* USER CODE BEGIN DMA1_Stream3_IRQn 0 */ + + /* USER CODE END DMA1_Stream3_IRQn 0 */ + HAL_DMA_IRQHandler(&hdma_spi2_rx); + /* USER CODE BEGIN DMA1_Stream3_IRQn 1 */ + + /* USER CODE END DMA1_Stream3_IRQn 1 */ +} + +/** + * @brief This function handles DMA1 stream4 global interrupt. + */ +void DMA1_Stream4_IRQHandler(void) +{ + /* USER CODE BEGIN DMA1_Stream4_IRQn 0 */ + + /* USER CODE END DMA1_Stream4_IRQn 0 */ + HAL_DMA_IRQHandler(&hdma_spi2_tx); + /* USER CODE BEGIN DMA1_Stream4_IRQn 1 */ + + /* USER CODE END DMA1_Stream4_IRQn 1 */ +} + /** * @brief This function handles EXTI line[9:5] interrupts. */ @@ -218,6 +249,20 @@ void EXTI9_5_IRQHandler(void) /* USER CODE END EXTI9_5_IRQn 1 */ } +/** + * @brief This function handles SPI2 global interrupt. + */ +void SPI2_IRQHandler(void) +{ + /* USER CODE BEGIN SPI2_IRQn 0 */ + + /* USER CODE END SPI2_IRQn 0 */ + HAL_SPI_IRQHandler(&hspi2); + /* USER CODE BEGIN SPI2_IRQn 1 */ + + /* USER CODE END SPI2_IRQn 1 */ +} + /** * @brief This function handles USART1 global interrupt. */ diff --git a/docs/BSD_Socket_API_使用指南.md b/docs/BSD_Socket_API_使用指南.md new file mode 100644 index 0000000..9b92a54 --- /dev/null +++ b/docs/BSD_Socket_API_使用指南.md @@ -0,0 +1,1268 @@ +# BSD Socket API 使用指南 + +## 目录 + +1. [概述](#1-概述) +2. [架构说明](#2-架构说明) +3. [快速开始](#3-快速开始) +4. [TCP 编程](#4-tcp-编程) +5. [UDP 编程](#5-udp-编程) +6. [I/O 多路复用](#6-io-多路复用) +7. [事件回调机制](#7-事件回调机制) +8. [字节序处理](#8-字节序处理) +9. [API 参考](#9-api-参考) +10. [完整示例](#10-完整示例) +11. [常见问题](#11-常见问题) + +--- + +## 1. 概述 + +本项目基于 CH395F 以太网协议栈芯片,提供了一套兼容 Linux BSD Socket API 的网络编程接口。这套接口屏蔽了底层硬件细节,让嵌入式网络编程与标准 Linux 网络编程保持一致。 + +### 1.1 支持的功能 + +| 功能 | 说明 | +|------|------| +| TCP Client | 主动连接远程服务器 | +| TCP Server(单连接) | 支持 1 个客户端连接 | +| TCP Server(多连接) | 支持最多 7 个并发连接 | +| UDP Client | 发送 UDP 数据报 | +| UDP Server | 接收 UDP 数据报 | +| select/poll | I/O 多路复用 | +| 事件回调 | 异步事件通知 | + +### 1.2 与 Linux Socket 的差异 + +| 特性 | Linux Socket | 本实现 | +|------|--------------|--------| +| Socket 数量 | 无限制 | 最多 8 个 | +| TCP Server 多连接 | 无限制 | 最多 7 个并发连接 | +| 非阻塞模式 | 支持 | 通过 `net_poll()` 实现 | +| 错误码 | errno | `net_get_errno()` | +| 头文件 | `` | `"net_socket.h"` | + +--- + +## 2. 架构说明 + +``` +┌─────────────────────────────────────────────────────────┐ +│ 应用层 (App) │ +├─────────────────────────────────────────────────────────┤ +│ net_socket.h (BSD Socket API) │ ← 用户接口 +├─────────────────────────────────────────────────────────┤ +│ net_socket.c + net_select.c (状态机+select) │ ← 核心逻辑 +├─────────────────────────────────────────────────────────┤ +│ ch395f.c/h (底层驱动) │ ← 硬件驱动 +├─────────────────────────────────────────────────────────┤ +│ CH395F 硬件 │ +└─────────────────────────────────────────────────────────┘ +``` + +### 2.1 文件说明 + +| 文件 | 说明 | +|------|------| +| `net_config.h` | 配置宏定义(最大 Socket 数、超时时间等) | +| `net_types.h` | 类型定义(地址结构、Socket 控制块等) | +| `net_socket.h` | BSD Socket API 头文件 | +| `net_socket.c` | API 核心实现 | +| `net_select.h` | select/poll API 头文件 | +| `net_select.c` | select/poll 实现 | + +### 2.1a 底层驱动改进(v2.1) + +ch395f.c/h 已完成以下优化: + +| 改进项 | 说明 | +|--------|------| +| 命令执行等待 | `open/close/listen/connect/disconnect` 所有长命令内部调用 `poll_cmd_status` 等待完成(20ms × 200 次 = 4s 超时) | +| 全局中断 | 使用 `GET_GLOB_INT_STATUS_ALL`(2字节版,命令码 0x19)支持全部 8 个 Socket | +| 错误码 | 与芯片手册完全对齐的 17 个状态码定义 | +| FUN_PARA 标志 | `CH395F_FUN_PARA_TCP_SERVER` / `SOCKET_CLOSE` / `DISABLE_SEND_OK` 宏 | +| 新增函数 | `ch395f_set_arp()` / `ch395f_set_ttl()` / `ch395f_get_unreach_info()` / `ch395f_poll_cmd_status()` | +| KeepAlive | 参数单位为毫秒(非秒),必须为 500 的倍数 | + +### 2.2 Socket 状态机 + +``` + net_socket() + │ + ▼ + ┌──────────── CREATED ────────────┐ + │ │ │ + │ net_bind() │ + │ │ │ + │ ▼ │ + │ BOUND ─────────────────┤ + │ │ │ │ + │ net_listen() net_connect() │ + │ │ │ │ + │ ▼ ▼ │ + │ LISTENING CONNECTING │ + │ │ │ │ + │ net_accept() │ (连接成功) │ + │ │ │ │ + │ ▼ ▼ │ + │ TCP_ACCEPT ESTABLISHED ◄──┤ + │ │ │ │ + │ (分配连接)│ net_send/recv() │ + │ │ │ │ + │ ▼ ▼ │ + │ ESTABLISHED │ + │ │ │ + │ net_close() │ + │ │ │ + │ ▼ │ + └────── CLOSED ──────────────────┘ +``` + +--- + +## 3. 快速开始 + +### 3.1 包含头文件 + +```c +#include "net_socket.h" +#include "net_select.h" +``` + +### 3.2 初始化网络 + +```c +int main(void) +{ + /* 初始化网络子系统 + * 参数:IP地址, 子网掩码, 网关 + * 传入 NULL 使用 DHCP 自动获取 + */ + int ret = net_init("192.168.1.100", "255.255.255.0", "192.168.1.1"); + if (ret != 0) { + printf("网络初始化失败!\r\n"); + return -1; + } + + /* 其他初始化... */ + + while (1) + { + net_poll(); /* 必须在主循环中调用 */ + /* 其他任务... */ + } +} +``` + +### 3.3 主循环要求 + +**重要**:`net_poll()` 必须在主循环中定期调用,它负责: +- 轮询 CH395F INT# 引脚电平(GPIO 轮询,无需 EXTI 中断) +- 通过 `GET_GLOB_INT_STATUS_ALL`(2字节版)读取所有 8 个 Socket 的中断状态 +- 更新所有 Socket 的连接状态(CONNECT / DISCONNECT / RECV / TIMEOUT) +- 触发事件回调 +- 处理数据接收 + +```c +while (1) +{ + net_poll(); /* 建议至少每 10ms 调用一次 */ + + /* 其他应用代码... */ +} +``` + +--- + +## 4. TCP 编程 + +### 4.1 TCP Server(单连接) + +单连接模式下,1 个 Socket 既做监听又做数据通信: + +```c +#include "net_socket.h" + +void tcp_server_single(void) +{ + int listen_sock; + struct net_sockaddr_in addr; + struct net_sockaddr_in client_addr; + int client_addr_len; + char buf[256]; + int len; + + /* 1. 创建 Socket */ + listen_sock = net_socket(AF_INET, SOCK_STREAM, 0); + if (listen_sock < 0) { + printf("创建 Socket 失败\r\n"); + return; + } + + /* 2. 绑定地址和端口 */ + addr.sin_family = AF_INET; + addr.sin_port = net_htons(8080); /* 端口号 8080 */ + addr.sin_addr.s_addr = net_inet_addr("0.0.0.0"); /* 监听所有网卡 */ + + if (net_bind(listen_sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) { + printf("绑定失败\r\n"); + net_close(listen_sock); + return; + } + + /* 3. 开始监听(单连接模式 backlog=0) */ + if (net_listen(listen_sock, 0) != 0) { + printf("监听失败\r\n"); + net_close(listen_sock); + return; + } + + printf("TCP Server 启动,监听端口 8080\r\n"); + + /* 4. 主循环 */ + while (1) + { + net_poll(); + + /* 尝试接受新连接 */ + client_addr_len = sizeof(client_addr); + int client_sock = net_accept(listen_sock, + (struct net_sockaddr *)&client_addr, &client_addr_len); + + if (client_sock >= 0) + { + char ip_str[16]; + printf("新连接: %s:%d\r\n", + net_inet_ntoa(client_addr.sin_addr.s_addr, ip_str), + net_ntohs(client_addr.sin_port)); + + /* 接收和发送数据 */ + while (1) + { + len = net_recv(client_sock, buf, sizeof(buf) - 1, 0); + if (len <= 0) { + printf("连接断开\r\n"); + break; + } + + buf[len] = '\0'; + printf("收到: %s\r\n", buf); + + /* 回显数据 */ + net_send(client_sock, buf, len, 0); + } + + net_close(client_sock); + } + } +} +``` + +### 4.2 TCP Server(多连接) + +多连接模式使用 1 个监听 Socket + N 个数据 Socket,支持并发连接: + +```c +#include "net_socket.h" +#include "net_select.h" + +#define MAX_CLIENTS 4 + +void tcp_server_multi(void) +{ + int listen_sock; + struct net_sockaddr_in addr; + int i; + + /* 1. 创建监听 Socket */ + listen_sock = net_socket(AF_INET, SOCK_STREAM, 0); + if (listen_sock < 0) { + printf("创建 Socket 失败\r\n"); + return; + } + + /* 2. 绑定 */ + addr.sin_family = AF_INET; + addr.sin_port = net_htons(8080); + addr.sin_addr.s_addr = net_inet_addr("0.0.0.0"); + + if (net_bind(listen_sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) { + printf("绑定失败\r\n"); + return; + } + + /* 3. 启动监听(backlog 设为并发连接数) */ + if (net_listen(listen_sock, MAX_CLIENTS) != 0) { + printf("监听失败\r\n"); + return; + } + + printf("TCP Server 启动,支持 %d 个并发连接\r\n", MAX_CLIENTS); + + /* 4. 使用 select 等待事件 */ + while (1) + { + net_fd_set readfds; + struct net_timeval timeout; + + NET_FD_ZERO(&readfds); + + /* 监听 Socket 加入读集合 */ + NET_FD_SET(listen_sock, &readfds); + + /* 数据 Socket(1~4)加入读集合 */ + for (i = 1; i <= MAX_CLIENTS; i++) { + NET_FD_SET(i, &readfds); + } + + timeout.tv_sec = 0; + timeout.tv_usec = 100000; /* 100ms 超时 */ + + int ret = net_select(listen_sock + MAX_CLIENTS + 1, + &readfds, NULL, NULL, &timeout); + + if (ret > 0) + { + /* 检查数据 Socket */ + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (NET_FD_ISSET(i, &readfds)) + { + net_sock_t *sock = net_get_sock(i); + if (sock != NULL && sock->state == NET_SOCK_STATE_ESTABLISHED) + { + char buf[256]; + int len = net_recv(i, buf, sizeof(buf) - 1, 0); + if (len > 0) { + buf[len] = '\0'; + printf("Socket %d: %s\r\n", i, buf); + net_send(i, buf, len, 0); /* 回显 */ + } else { + printf("Socket %d 断开\r\n", i); + } + } + } + } + } + } +} +``` + +### 4.3 TCP Client + +```c +#include "net_socket.h" + +void tcp_client(void) +{ + int sock; + struct net_sockaddr_in addr; + char buf[256]; + int len; + + /* 1. 创建 Socket */ + sock = net_socket(AF_INET, SOCK_STREAM, 0); + if (sock < 0) { + printf("创建 Socket 失败\r\n"); + return; + } + + /* 2. 设置服务器地址 */ + addr.sin_family = AF_INET; + addr.sin_port = net_htons(8080); + addr.sin_addr.s_addr = net_inet_addr("192.168.1.200"); + + /* 3. 连接服务器 */ + printf("正在连接服务器...\r\n"); + if (net_connect(sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) { + printf("连接失败\r\n"); + net_close(sock); + return; + } + + /* 4. 等待连接建立 */ + while (1) + { + net_poll(); + + net_sock_t *sock_info = net_get_sock(sock); + if (sock_info != NULL && sock_info->state == NET_SOCK_STATE_ESTABLISHED) { + printf("连接成功!\r\n"); + break; + } + + HAL_Delay(10); + } + + /* 5. 发送数据 */ + const char *msg = "Hello, CH395F!"; + net_send(sock, msg, strlen(msg), 0); + printf("发送: %s\r\n", msg); + + /* 6. 接收数据 */ + len = net_recv(sock, buf, sizeof(buf) - 1, 0); + if (len > 0) { + buf[len] = '\0'; + printf("收到: %s\r\n", buf); + } + + /* 7. 关闭连接 */ + net_close(sock); +} +``` + +--- + +## 5. UDP 编程 + +### 5.1 UDP Client + +```c +#include "net_socket.h" + +void udp_client(void) +{ + int sock; + struct net_sockaddr_in local_addr; + struct net_sockaddr_in dest_addr; + char buf[256]; + int len; + + /* 1. 创建 UDP Socket */ + sock = net_socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + printf("创建 Socket 失败\r\n"); + return; + } + + /* 2. 绑定本地端口(可选) */ + local_addr.sin_family = AF_INET; + local_addr.sin_port = net_htons(12345); + local_addr.sin_addr.s_addr = net_inet_addr("0.0.0.0"); + + net_bind(sock, (struct net_sockaddr *)&local_addr, sizeof(local_addr)); + + /* 3. 设置目标地址 */ + dest_addr.sin_family = AF_INET; + dest_addr.sin_port = net_htons(8888); + dest_addr.sin_addr.s_addr = net_inet_addr("192.168.1.200"); + + /* 4. 发送数据 */ + const char *msg = "UDP Hello!"; + net_sendto(sock, msg, strlen(msg), 0, + (struct net_sockaddr *)&dest_addr, sizeof(dest_addr)); + printf("发送: %s\r\n", msg); + + /* 5. 接收数据 */ + struct net_sockaddr_in src_addr; + int src_addr_len = sizeof(src_addr); + + len = net_recvfrom(sock, buf, sizeof(buf) - 1, 0, + (struct net_sockaddr *)&src_addr, &src_addr_len); + + if (len > 0) { + buf[len] = '\0'; + char ip_str[16]; + printf("收到: %s (from %s:%d)\r\n", buf, + net_inet_ntoa(src_addr.sin_addr.s_addr, ip_str), + net_ntohs(src_addr.sin_port)); + } + + net_close(sock); +} +``` + +### 5.2 UDP Server + +```c +#include "net_socket.h" + +void udp_server(void) +{ + int sock; + struct net_sockaddr_in addr; + char buf[256]; + + /* 1. 创建 UDP Socket */ + sock = net_socket(AF_INET, SOCK_DGRAM, 0); + if (sock < 0) { + printf("创建 Socket 失败\r\n"); + return; + } + + /* 2. 绑定本地端口 */ + addr.sin_family = AF_INET; + addr.sin_port = net_htons(8888); + addr.sin_addr.s_addr = net_inet_addr("0.0.0.0"); + + if (net_bind(sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) { + printf("绑定失败\r\n"); + net_close(sock); + return; + } + + printf("UDP Server 启动,监听端口 8888\r\n"); + + /* 3. 循环接收数据 */ + while (1) + { + net_poll(); + + struct net_sockaddr_in src_addr; + int src_addr_len = sizeof(src_addr); + + int len = net_recvfrom(sock, buf, sizeof(buf) - 1, 0, + (struct net_sockaddr *)&src_addr, &src_addr_len); + + if (len > 0) { + buf[len] = '\0'; + char ip_str[16]; + printf("收到: %s (from %s:%d)\r\n", buf, + net_inet_ntoa(src_addr.sin_addr.s_addr, ip_str), + net_ntohs(src_addr.sin_port)); + + /* 回显数据 */ + net_sendto(sock, buf, len, 0, + (struct net_sockaddr *)&src_addr, src_addr_len); + } + } +} +``` + +--- + +## 6. I/O 多路复用 + +### 6.1 select 使用 + +`net_select()` 可以同时监控多个 Socket 的读写事件: + +```c +#include "net_select.h" + +void select_example(void) +{ + int sock1, sock2; + net_fd_set readfds; + struct net_timeval timeout; + + /* 创建并连接两个 Socket ... */ + + while (1) + { + NET_FD_ZERO(&readfds); + NET_FD_SET(sock1, &readfds); + NET_FD_SET(sock2, &readfds); + + timeout.tv_sec = 0; + timeout.tv_usec = 500000; /* 500ms 超时 */ + + int ret = net_select(sock2 + 1, &readfds, NULL, NULL, &timeout); + + if (ret > 0) { + if (NET_FD_ISSET(sock1, &readfds)) { + char buf[128]; + int len = net_recv(sock1, buf, sizeof(buf), 0); + /* 处理数据... */ + } + + if (NET_FD_ISSET(sock2, &readfds)) { + char buf[128]; + int len = net_recv(sock2, buf, sizeof(buf), 0); + /* 处理数据... */ + } + } else if (ret == 0) { + /* 超时 */ + } + } +} +``` + +### 6.2 poll 使用 + +`net_poll_events()` 使用 `pollfd` 结构体数组: + +```c +#include "net_select.h" + +void poll_example(void) +{ + net_pollfd fds[2]; + + fds[0].fd = sock1; + fds[0].events = NET_POLLIN; + + fds[1].fd = sock2; + fds[1].events = NET_POLLIN; + + while (1) + { + int ret = net_poll_events(fds, 2, 500); /* 500ms 超时 */ + + if (ret > 0) { + if (fds[0].revents & NET_POLLIN) { + char buf[128]; + int len = net_recv(fds[0].fd, buf, sizeof(buf), 0); + /* 处理数据... */ + } + + if (fds[1].revents & NET_POLLIN) { + char buf[128]; + int len = net_recv(fds[1].fd, buf, sizeof(buf), 0); + /* 处理数据... */ + } + } + } +} +``` + +### 6.3 事件掩码说明 + +| 事件 | 说明 | +|------|------| +| `NET_POLLIN` | 有数据可读,或有新连接到达(监听 Socket) | +| `NET_POLLOUT` | 发送缓冲区有空间,可以写入数据 | +| `NET_POLLERR` | 发生错误 | +| `NET_POLLHUP` | 连接挂起(对端关闭) | +| `NET_POLLNVAL` | 无效的文件描述符 | + +--- + +## 7. 事件回调机制 + +可以通过注册回调函数,异步获取 Socket 事件通知: + +### 7.1 定义回调函数 + +```c +#include "net_socket.h" + +void my_event_callback(int sockfd, net_event_t event, void *arg) +{ + switch (event) + { + case NET_EVENT_CONNECTED: + printf("Socket %d: 连接建立\r\n", sockfd); + break; + + case NET_EVENT_DISCONNECTED: + printf("Socket %d: 连接断开\r\n", sockfd); + break; + + case NET_EVENT_DATA_RECEIVED: + printf("Socket %d: 收到数据\r\n", sockfd); + /* 注意:这里只是通知,需要调用 net_recv() 读取 */ + break; + + case NET_EVENT_SEND_COMPLETE: + printf("Socket %d: 发送完成\r\n", sockfd); + break; + + case NET_EVENT_TIMEOUT: + printf("Socket %d: 操作超时\r\n", sockfd); + break; + + case NET_EVENT_ERROR: + printf("Socket %d: 发生错误\r\n", sockfd); + break; + } +} +``` + +### 7.2 注册回调 + +```c +void event_callback_example(void) +{ + int sock; + + /* 创建 Socket ... */ + + /* 注册事件回调 */ + net_set_event_cb(sock, my_event_callback, NULL); + + /* 主循环 */ + while (1) + { + net_poll(); + } +} +``` + +--- + +## 8. 字节序处理 + +网络协议使用**大端序**(网络字节序),而 STM32 使用**小端序**(主机字节序)。发送和接收数据时需要进行字节序转换。 + +### 8.1 端口号转换 + +```c +uint16_t port = 8080; + +/* 主机序 -> 网络序(发送前转换) */ +uint16_t net_port = net_htons(port); + +/* 网络序 -> 主机序(接收后转换) */ +uint16_t host_port = net_ntohs(net_port); +``` + +### 8.2 IP 地址转换 + +```c +/* 字符串转网络序 IP */ +uint32_t ip = net_inet_addr("192.168.1.100"); + +/* 网络序 IP 转字符串 */ +char ip_str[16]; +net_inet_ntoa(ip, ip_str); /* ip_str = "192.168.1.100" */ +``` + +### 8.3 填充地址结构体 + +```c +struct net_sockaddr_in addr; + +addr.sin_family = AF_INET; +addr.sin_port = net_htons(8080); /* 端口转换 */ +addr.sin_addr.s_addr = net_inet_addr("192.168.1.100"); /* IP 转换 */ + +/* 如果要绑定任意地址 */ +addr.sin_addr.s_addr = net_inet_addr("0.0.0.0"); +/* 或者直接 */ +addr.sin_addr.s_addr = INADDR_ANY; +``` + +### 8.4 读取地址信息 + +```c +struct net_sockaddr_in client_addr; +int addr_len = sizeof(client_addr); + +int client_sock = net_accept(listen_sock, + (struct net_sockaddr *)&client_addr, &addr_len); + +if (client_sock >= 0) +{ + /* 转换端口到主机序 */ + uint16_t port = net_ntohs(client_addr.sin_port); + + /* 转换 IP 到字符串 */ + char ip_str[16]; + net_inet_ntoa(client_addr.sin_addr.s_addr, ip_str); + + printf("客户端: %s:%d\r\n", ip_str, port); +} +``` + +--- + +## 9. API 参考 + +### 9.1 核心 API + +#### net_init - 网络初始化 + +```c +int net_init(const char *ip, const char *mask, const char *gateway); +``` + +| 参数 | 说明 | +|------|------| +| `ip` | IP 地址字符串,NULL 使用 DHCP | +| `mask` | 子网掩码字符串,NULL 使用默认 | +| `gateway` | 网关地址字符串,NULL 使用默认 | +| 返回值 | 0 成功,-1 失败 | + +#### net_poll - 状态轮询 + +```c +int net_poll(void); +``` + +| 参数 | 说明 | +|------|------| +| 返回值 | 有事件发生的 socket 数量 | + +**注意**:必须在主循环中定期调用。 + +#### net_socket - 创建 Socket + +```c +int net_socket(int domain, int type, int protocol); +``` + +| 参数 | 说明 | +|------|------| +| `domain` | 地址族,仅支持 `AF_INET` | +| `type` | `SOCK_STREAM`(TCP) 或 `SOCK_DGRAM`(UDP) | +| `protocol` | 通常为 0 | +| 返回值 | socket 描述符 (0~7),失败返回 -1 | + +#### net_bind - 绑定地址 + +```c +int net_bind(int sockfd, const struct net_sockaddr *addr, int addrlen); +``` + +| 参数 | 说明 | +|------|------| +| `sockfd` | socket 描述符 | +| `addr` | 地址结构体指针 | +| `addrlen` | 地址结构体长度 | +| 返回值 | 0 成功,-1 失败 | + +#### net_listen - 开始监听 + +```c +int net_listen(int sockfd, int backlog); +``` + +| 参数 | 说明 | +|------|------| +| `sockfd` | socket 描述符 | +| `backlog` | 最大并发连接数(0=单连接) | +| 返回值 | 0 成功,-1 失败 | + +#### net_accept - 接受连接 + +```c +int net_accept(int sockfd, struct net_sockaddr *addr, int *addrlen); +``` + +| 参数 | 说明 | +|------|------| +| `sockfd` | 监听 socket 描述符 | +| `addr` | 输出:客户端地址 | +| `addrlen` | 输入输出:地址长度 | +| 返回值 | 新 socket 描述符,失败返回 -1 | + +#### net_connect - 连接服务器 + +```c +int net_connect(int sockfd, const struct net_sockaddr *addr, int addrlen); +``` + +| 参数 | 说明 | +|------|------| +| `sockfd` | socket 描述符 | +| `addr` | 服务器地址 | +| `addrlen` | 地址长度 | +| 返回值 | 0 成功,-1 失败 | + +#### net_send - 发送数据 + +```c +int net_send(int sockfd, const void *buf, int len, int flags); +``` + +| 参数 | 说明 | +|------|------| +| `sockfd` | socket 描述符 | +| `buf` | 数据缓冲区 | +| `len` | 数据长度 | +| `flags` | 通常为 0 | +| 返回值 | 实际发送字节数,失败返回 -1 | + +#### net_recv - 接收数据 + +```c +int net_recv(int sockfd, void *buf, int len, int flags); +``` + +| 参数 | 说明 | +|------|------| +| `sockfd` | socket 描述符 | +| `buf` | 接收缓冲区 | +| `len` | 缓冲区大小 | +| `flags` | 0=阻塞,`NET_MSG_DONTWAIT`=非阻塞 | +| 返回值 | 实际接收字节数,0=对端关闭,-1=错误 | + +#### net_sendto - 发送 UDP 数据 + +```c +int net_sendto(int sockfd, const void *buf, int len, int flags, + const struct net_sockaddr *dest_addr, int addrlen); +``` + +#### net_recvfrom - 接收 UDP 数据 + +```c +int net_recvfrom(int sockfd, void *buf, int len, int flags, + struct net_sockaddr *src_addr, int *addrlen); +``` + +#### net_close - 关闭 Socket + +```c +int net_close(int sockfd); +``` + +### 9.2 辅助 API + +#### net_set_event_cb - 注册事件回调 + +```c +int net_set_event_cb(int sockfd, net_event_cb_t cb, void *arg); +``` + +#### net_get_sock - 获取 Socket 控制块 + +```c +net_sock_t *net_get_sock(int sockfd); +``` + +#### net_get_errno - 获取错误码 + +```c +int net_get_errno(void); +``` + +### 9.3 字节序转换 + +```c +uint16_t net_htons(uint16_t hostshort); /* 主机序 -> 网络序 */ +uint16_t net_ntohs(uint16_t netshort); /* 网络序 -> 主机序 */ +uint32_t net_htonl(uint32_t hostlong); /* 主机序 -> 网络序 */ +uint32_t net_ntohl(uint32_t netlong); /* 网络序 -> 主机序 */ +uint32_t net_inet_addr(const char *cp); /* 字符串 -> 网络序 IP */ +char *net_inet_ntoa(uint32_t addr, char *buf); /* 网络序 IP -> 字符串 */ +``` + +### 9.4 select/poll + +```c +int net_select(int nfds, net_fd_set *readfds, net_fd_set *writefds, + net_fd_set *exceptfds, net_timeval *timeout); + +int net_poll_events(net_pollfd *fds, int nfds, int timeout); +``` + +### 9.5 fd_set 操作宏 + +```c +NET_FD_ZERO(fdset) /* 清空集合 */ +NET_FD_SET(fd, fdset) /* 添加 fd */ +NET_FD_CLR(fd, fdset) /* 移除 fd */ +NET_FD_ISSET(fd, fdset) /* 检查 fd 是否在集合中 */ +``` + +--- + +## 10. 完整示例 + +### 10.1 TCP Echo Server(多连接) + +```c +#include "net_socket.h" +#include "net_select.h" +#include +#include + +#define SERVER_PORT 8080 +#define MAX_CLIENTS 4 + +void tcp_echo_server(void) +{ + int listen_sock; + struct net_sockaddr_in addr; + int i; + + /* 初始化网络 */ + net_init("192.168.1.100", "255.255.255.0", "192.168.1.1"); + + /* 创建监听 Socket */ + listen_sock = net_socket(AF_INET, SOCK_STREAM, 0); + if (listen_sock < 0) { + printf("创建 Socket 失败\r\n"); + while(1); + } + + /* 绑定 */ + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = net_htons(SERVER_PORT); + addr.sin_addr.s_addr = net_inet_addr("0.0.0.0"); + + if (net_bind(listen_sock, (struct net_sockaddr *)&addr, sizeof(addr)) != 0) { + printf("绑定失败\r\n"); + while(1); + } + + /* 监听 */ + if (net_listen(listen_sock, MAX_CLIENTS) != 0) { + printf("监听失败\r\n"); + while(1); + } + + printf("TCP Echo Server 启动,端口 %d\r\n", SERVER_PORT); + + /* 主循环 */ + while (1) + { + net_fd_set readfds; + struct net_timeval timeout; + + NET_FD_ZERO(&readfds); + NET_FD_SET(listen_sock, &readfds); + + for (i = 1; i <= MAX_CLIENTS; i++) { + NET_FD_SET(i, &readfds); + } + + timeout.tv_sec = 0; + timeout.tv_usec = 100000; + + int ret = net_select(listen_sock + MAX_CLIENTS + 1, + &readfds, NULL, NULL, &timeout); + + if (ret > 0) + { + for (i = 1; i <= MAX_CLIENTS; i++) + { + if (NET_FD_ISSET(i, &readfds)) + { + net_sock_t *sock = net_get_sock(i); + + if (sock == NULL) continue; + + if (sock->state == NET_SOCK_STATE_ESTABLISHED) + { + char buf[256]; + int len = net_recv(i, buf, sizeof(buf) - 1, 0); + + if (len > 0) { + buf[len] = '\0'; + printf("客户端 %d: %s\r\n", i, buf); + net_send(i, buf, len, 0); + } else { + printf("客户端 %d 断开\r\n", i); + net_close(i); + } + } + } + } + } + } +} +``` + +### 10.2 TCP Client 自动重连 + +```c +#include "net_socket.h" +#include +#include + +#define SERVER_IP "192.168.1.200" +#define SERVER_PORT 8080 + +void tcp_client_reconnect(void) +{ + int sock = -1; + struct net_sockaddr_in addr; + uint32_t last_try = 0; + uint32_t reconnect_interval = 5000; /* 5 秒重连间隔 */ + + /* 初始化网络 */ + net_init("192.168.1.100", "255.255.255.0", "192.168.1.1"); + + while (1) + { + net_poll(); + + /* 尝试重连 */ + if (sock < 0 && (HAL_GetTick() - last_try) >= reconnect_interval) + { + last_try = HAL_GetTick(); + + sock = net_socket(AF_INET, SOCK_STREAM, 0); + if (sock >= 0) { + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = net_htons(SERVER_PORT); + addr.sin_addr.s_addr = net_inet_addr(SERVER_IP); + + if (net_connect(sock, (struct net_sockaddr *)&addr, sizeof(addr)) == 0) { + printf("正在连接服务器...\r\n"); + } else { + net_close(sock); + sock = -1; + } + } + } + + /* 检查连接状态 */ + if (sock >= 0) + { + net_sock_t *sock_info = net_get_sock(sock); + + if (sock_info != NULL && sock_info->state == NET_SOCK_STATE_ESTABLISHED) + { + /* 已连接,发送数据 */ + static uint32_t last_send = 0; + if ((HAL_GetTick() - last_send) >= 1000) { + last_send = HAL_GetTick(); + net_send(sock, "heartbeat", 9, 0); + } + + /* 检查接收 */ + char buf[128]; + int len = net_recv(sock, buf, sizeof(buf) - 1, NET_MSG_DONTWAIT); + if (len > 0) { + buf[len] = '\0'; + printf("收到: %s\r\n", buf); + } else if (len == 0) { + /* 连接断开 */ + printf("连接断开\r\n"); + net_close(sock); + sock = -1; + } + } + else if (sock_info != NULL && + (sock_info->state == NET_SOCK_STATE_TCP_ACCEPT || + sock_info->state == NET_SOCK_STATE_CLOSED)) + { + /* 连接失败或断开 */ + printf("连接失败,准备重连...\r\n"); + net_close(sock); + sock = -1; + } + } + } +} +``` + +--- + +## 11. 常见问题 + +### 11.1 net_poll() 必须调用吗? + +**是的**。`net_poll()` 负责轮询 CH395F 的中断状态并更新所有 Socket 的状态。如果不调用,连接状态不会更新,数据也无法接收。 + +建议至少每 10ms 调用一次。 + +### 11.2 Socket 数量有限制吗? + +是的,CH395F 最多支持 8 个 Socket(索引 0~7)。其中: +- TCP Server 多连接模式:1 个监听 Socket + 最多 7 个数据 Socket +- TCP Client:最多 8 个 +- UDP:最多 8 个 + +### 11.3 TCP Server 多连接模式有什么要求? + +**模式工作原理:** +- CH395F 多连接模式下,Socket 0 专职监听,Socket 1~7 由芯片自动分配 +- 客户端连接时,CH395F 自动查找协议类型为 TCP、源端口与监听端口相同、处于关闭状态的 Socket +- 找到后自动打开该 Socket 并分配连接,触发 CONNECT 中断 + +**硬件要求:** +- CH395F 固件版本 >= 0x44(支持 Socket 4~7) + +### 11.4 发送数据有什么限制? + +- 每次发送的数据长度不能超过 `NET_SEND_BUF_SIZE`(默认 4096 字节) +- TCP 需要等待发送缓冲区空闲(`send_ready` 标志) +- UDP 单次发送最大 1460 字节 + +### 11.5 如何处理连接断开? + +**方式一:阻塞接收** +```c +int len = net_recv(sock, buf, sizeof(buf), 0); +if (len <= 0) { + /* 连接断开或错误 */ + net_close(sock); +} +``` + +**方式二:非阻塞接收** +```c +int len = net_recv(sock, buf, sizeof(buf), NET_MSG_DONTWAIT); +if (len < 0 && net_get_errno() == NET_ERR_WOULDBLOCK) { + /* 没有数据 */ +} else if (len <= 0) { + /* 连接断开 */ +} +``` + +**方式三:事件回调** +```c +void callback(int sockfd, net_event_t event, void *arg) +{ + if (event == NET_EVENT_DISCONNECTED) { + /* 连接断开 */ + } +} +``` + +### 11.6 select 和 poll 的区别? + +| 特性 | select | poll | +|------|--------|------| +| 描述符集合 | fd_set(位掩码) | pollfd 数组 | +| 最大 fd 数 | 32 | 无限制 | +| 使用方式 | NET_FD_SET 宏 | 直接操作结构体 | + +两者功能相同,推荐使用 `net_select()`,更接近 Linux 编程习惯。 + +### 11.7 错误码说明 + +| 错误码 | 宏定义 | 说明 | +|--------|--------|------| +| 0 | NET_OK | 成功 | +| -1 | NET_ERR | 通用错误 | +| -2 | NET_ERR_BADF | 无效的 socket 描述符 | +| -3 | NET_ERR_INVAL | 无效参数 | +| -4 | NET_ERR_NOMEM | 内存不足 | +| -5 | NET_ERR_NOTCONN | 未连接 | +| -6 | NET_ERR_ISCONN | 已连接 | +| -7 | NET_ERR_WOULDBLOCK | 非阻塞模式下操作未完成 | +| -8 | NET_ERR_CONNRESET | 连接被重置 | +| -9 | NET_ERR_TIMEDOUT | 操作超时 | +| -10 | NET_ERR_NOSPACE | 发送缓冲区无空间 | +| -11 | NET_ERR_BUSY | 操作未完成(BUSY) | + +### 11.8 CH395F 命令执行状态码 + +CH395F 底层驱动返回的状态码(`ch395f_get_cmd_status()` 或 `ch395f_poll_cmd_status()`): + +| 代码 | 宏定义 | 说明 | +|------|--------|------| +| 0x00 | CH395F_ERR_SUCCESS | 成功 | +| 0x10 | CH395F_ERR_BUSY | 忙,命令正在执行 | +| 0x11 | CH395F_ERR_MEM | 内存管理错误 | +| 0x12 | CH395F_ERR_BUF | 缓冲区错误 | +| 0x13 | CH395F_ERR_TIMEOUT | 超时 | +| 0x14 | CH395F_ERR_RTE | 路由错误 | +| 0x15 | CH395F_ERR_ABRT | 连接中止 | +| 0x16 | CH395F_ERR_RST | 连接复位 | +| 0x17 | CH395F_ERR_CLSD | 连接关闭 | +| 0x18 | CH395F_ERR_CONN | 无连接 | +| 0x19 | CH395F_ERR_VAL | 值错误 | +| 0x1A | CH395F_ERR_ARG | 参数错误 | +| 0x1B | CH395F_ERR_USE | 已被使用(常见于重复 OPEN 或冲突配置) | +| 0x1C | CH395F_ERR_IF | MAC 错误 | +| 0x1D | CH395F_ERR_ISCONN | 已连接 | +| 0x20 | CH395F_ERR_OPEN | 已打开 | +| 0x5F | CH395F_CMD_RET_ABORT | 命令中止 | +| 0xFA | CH395F_ERR_UNKNOW | 未知错误 | + +### 11.9 故障排查 + +**问题:TCP Client 连接超时,无 CONNECT 中断** +- 检查多连接模式下数据 Socket(1~7)是否设置了 `SET_PROTO_TCP` + `SET_SOUR_PORT`(与监听端口相同) +- 如果不设置,CH395F 不知道哪些 Socket 可用于自动分配,TCP SYN 被静默丢弃 + +**问题:数据 Socket OPEN 返回 0x1B(已被使用)** +- CH395F 多连接模式下,数据 Socket 由芯片自动管理,软件不应手动 `OPEN_SOCKET` +- 只需 `SET_PROTO_TCP` + `SET_SOUR_PORT`,CH395F 在有连接时自动打开 + +**问题:Socket 4~7 的 CONNECT/RECV 中断不触发** +- 需使用 `GET_GLOB_INT_STATUS_ALL`(命令码 0x19,返回 2 字节)才能检测 Socket 4~7 的中断 +- 1 字节版 `GET_GLOB_INT_STATUS`(命令码 0x29)仅支持 Socket 0~3 + +**问题:CH395F 初始化超时** +- 芯片 INIT 需要约 200ms,轮询间隔应为 20ms(过短的查询可能干扰内部处理) +- 超时值建议设为 4 秒以上 diff --git a/test/ch395f_socket_test.py b/test/ch395f_socket_test.py new file mode 100644 index 0000000..7743e4a --- /dev/null +++ b/test/ch395f_socket_test.py @@ -0,0 +1,694 @@ +#!/usr/bin/env python3 +""" +CH395F BSD Socket API 测试程序 +用于测试 STM32F4 + CH395F 的网络功能 + +使用方法: + python ch395f_socket_test.py [模式] [选项] + +模式: + tcp_server - 作为 TCP 服务器(测试 CH395F TCP Client) + tcp_client - 作为 TCP 客户端(测试 CH395F TCP Server) + udp_server - 作为 UDP 服务器(测试 CH395F UDP Client) + udp_client - 作为 UDP 客户端(测试 CH395F UDP Server) + tcp_echo - TCP 回显服务器(测试双向通信) + udp_echo - UDP 回显服务器(测试双向通信) + stress - 压力测试(多连接并发测试) + +选项: + --ip 目标 IP 地址(默认: 192.168.1.100) + --port 端口号(默认: 8080) + --count 测试次数(默认: 10) + --size 数据包大小(默认: 1024) + +示例: + # 测试 CH395F TCP Server + python ch395f_socket_test.py tcp_client --ip 192.168.1.100 --port 8080 + + # 测试 CH395F TCP Client + python ch395f_socket_test.py tcp_server --port 8080 + + # 测试 CH395F UDP Client + python ch395f_socket_test.py udp_server --port 8888 + + # 测试 CH395F UDP Server + python ch395f_socket_test.py udp_client --ip 192.168.1.100 --port 8888 + + # 压力测试(多连接) + python ch395f_socket_test.py stress --ip 192.168.1.100 --port 8080 --count 7 +""" + +import socket +import sys +import time +import argparse +import threading +import struct +import random +import string +from datetime import datetime + + +class Colors: + """终端颜色""" + HEADER = '\033[95m' + BLUE = '\033[94m' + CYAN = '\033[96m' + GREEN = '\033[92m' + YELLOW = '\033[93m' + RED = '\033[91m' + END = '\033[0m' + BOLD = '\033[1m' + + +def log_msg(msg, color=Colors.END): + """带时间戳的日志输出""" + timestamp = datetime.now().strftime("%H:%M:%S.%f")[:-3] + print(f"{Colors.CYAN}[{timestamp}]{Colors.END} {color}{msg}{Colors.END}") + + +def log_success(msg): + log_msg(f"✓ {msg}", Colors.GREEN) + + +def log_error(msg): + log_msg(f"✗ {msg}", Colors.RED) + + +def log_info(msg): + log_msg(f"ℹ {msg}", Colors.BLUE) + + +def log_warn(msg): + log_msg(f"⚠ {msg}", Colors.YELLOW) + + +def generate_data(size): + """生成测试数据""" + return ''.join(random.choices(string.ascii_letters + string.digits, k=size)).encode() + + +def verify_data(data, expected_len): + """验证数据""" + if len(data) != expected_len: + return False, f"长度不匹配: 期望 {expected_len}, 实际 {len(data)}" + return True, "OK" + + +# ==================== TCP 测试函数 ==================== + +def tcp_server_test(args): + """TCP 服务器测试(测试 CH395F TCP Client)""" + log_info("=" * 60) + log_info("TCP 服务器测试 - 测试 CH395F TCP Client 功能") + log_info("=" * 60) + + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.settimeout(30) + + try: + server_socket.bind(('0.0.0.0', args.port)) + server_socket.listen(1) + log_success(f"服务器启动,监听端口 {args.port}") + log_info("等待 CH395F TCP Client 连接...") + + client_socket, client_addr = server_socket.accept() + log_success(f"连接来自: {client_addr[0]}:{client_addr[1]}") + client_socket.settimeout(5) + + # 测试接收数据 + log_info("-" * 40) + log_info("测试 1: 接收数据") + for i in range(args.count): + try: + data = client_socket.recv(args.size) + if data: + log_success(f"收到数据 ({len(data)} bytes): {data[:50]}...") + else: + log_error("收到空数据") + break + except socket.timeout: + log_error("接收超时") + break + except Exception as e: + log_error(f"接收错误: {e}") + break + + # 测试发送数据 + log_info("-" * 40) + log_info("测试 2: 发送数据") + for i in range(args.count): + try: + test_data = generate_data(min(100, args.size)) + client_socket.send(test_data) + log_success(f"发送数据 ({len(test_data)} bytes)") + time.sleep(0.1) + except Exception as e: + log_error(f"发送错误: {e}") + break + + # 测试双向通信 + log_info("-" * 40) + log_info("测试 3: 双向通信") + for i in range(min(5, args.count)): + try: + # 发送 + test_data = f"PING_{i}".encode() + client_socket.send(test_data) + log_info(f"发送: {test_data.decode()}") + + # 接收回显 + data = client_socket.recv(args.size) + if data: + log_info(f"收到: {data.decode()}") + if data == test_data: + log_success("回显数据匹配!") + else: + log_error("回显数据不匹配!") + except Exception as e: + log_error(f"通信错误: {e}") + break + + client_socket.close() + log_success("测试完成,连接已关闭") + + except socket.timeout: + log_error("等待连接超时") + except Exception as e: + log_error(f"服务器错误: {e}") + finally: + server_socket.close() + + +def tcp_client_test(args): + """TCP 客户端测试(测试 CH395F TCP Server)""" + log_info("=" * 60) + log_info("TCP 客户端测试 - 测试 CH395F TCP Server 功能") + log_info("=" * 60) + + client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client_socket.settimeout(10) + + try: + # 连接服务器 + log_info(f"正在连接 CH395F TCP Server: {args.ip}:{args.port}") + client_socket.connect((args.ip, args.port)) + log_success("连接成功!") + + # 测试发送数据 + log_info("-" * 40) + log_info("测试 1: 发送数据") + for i in range(args.count): + try: + test_data = generate_data(min(100, args.size)) + client_socket.send(test_data) + log_success(f"发送数据 ({len(test_data)} bytes)") + log_info(f"发送 hex: {' '.join(f'{b:02X}' for b in test_data[:16])}") + + # 等待回显 + data = client_socket.recv(args.size) + if data: + log_info(f"接收 hex: {' '.join(f'{b:02X}' for b in data[:16])}") + log_info(f"接收长度: {len(data)}, 期望长度: {len(test_data)}") + if data == test_data: + log_success("回显数据匹配!") + else: + log_error("回显数据不匹配!") + # 找出第一个不同的字节 + for j in range(min(len(data), len(test_data))): + if data[j] != test_data[j]: + log_error(f"首个差异: 位置 {j}, 发送={test_data[j]:02X}, 接收={data[j]:02X}") + break + else: + log_error("收到空数据") + break + + time.sleep(0.1) + except Exception as e: + log_error(f"发送/接收错误: {e}") + break + + # 测试大数据量 + log_info("-" * 40) + log_info("测试 2: 大数据量传输") + try: + large_data = generate_data(min(1024, args.size)) + client_socket.send(large_data) + log_success(f"发送大数据 ({len(large_data)} bytes)") + + received = b"" + while len(received) < len(large_data): + chunk = client_socket.recv(min(1024, len(large_data) - len(received))) + if not chunk: + break + received += chunk + + if received == large_data: + log_success("大数据传输完整!") + else: + log_error(f"大数据传输不完整: 发送 {len(large_data)}, 接收 {len(received)}") + except Exception as e: + log_error(f"大数据传输错误: {e}") + + # 测试快速收发 + log_info("-" * 40) + log_info("测试 3: 快速收发") + success_count = 0 + fail_count = 0 + start_time = time.time() + + for i in range(args.count): + try: + test_data = f"FAST_{i:04d}".encode() + client_socket.send(test_data) + data = client_socket.recv(100) + if data and data == test_data: + success_count += 1 + else: + fail_count += 1 + except Exception as e: + fail_count += 1 + + elapsed = time.time() - start_time + log_info(f"快速收发结果: 成功 {success_count}, 失败 {fail_count}, 耗时 {elapsed:.2f}s") + + client_socket.close() + log_success("测试完成,连接已关闭") + + except ConnectionRefusedError: + log_error("连接被拒绝 - CH395F TCP Server 未运行") + except socket.timeout: + log_error("连接超时") + except Exception as e: + log_error(f"客户端错误: {e}") + finally: + client_socket.close() + + +# ==================== UDP 测试函数 ==================== + +def udp_server_test(args): + """UDP 服务器测试(测试 CH395F UDP Client)""" + log_info("=" * 60) + log_info("UDP 服务器测试 - 测试 CH395F UDP Client 功能") + log_info("=" * 60) + + server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.settimeout(30) + + try: + server_socket.bind(('0.0.0.0', args.port)) + log_success(f"服务器启动,监听端口 {args.port}") + log_info("等待 CH395F UDP Client 数据...") + + # 接收数据 + for i in range(args.count): + try: + data, client_addr = server_socket.recvfrom(args.size) + log_success(f"收到来自 {client_addr[0]}:{client_addr[1]} ({len(data)} bytes)") + log_info(f"数据: {data[:50]}...") + + # 回显数据 + server_socket.sendto(data, client_addr) + log_info(f"回显数据到 {client_addr[0]}:{client_addr[1]}") + except socket.timeout: + log_warn("接收超时") + break + except Exception as e: + log_error(f"接收错误: {e}") + break + + log_success("测试完成") + + except Exception as e: + log_error(f"服务器错误: {e}") + finally: + server_socket.close() + + +def udp_client_test(args): + """UDP 客户端测试(测试 CH395F UDP Server)""" + log_info("=" * 60) + log_info("UDP 客户端测试 - 测试 CH395F UDP Server 功能") + log_info("=" * 60) + + client_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + client_socket.settimeout(5) + + try: + dest_addr = (args.ip, args.port) + + # 发送数据 + log_info("-" * 40) + log_info("测试 1: 发送 UDP 数据") + for i in range(args.count): + try: + test_data = f"UDP_MSG_{i:04d}".encode() + client_socket.sendto(test_data, dest_addr) + log_success(f"发送数据到 {args.ip}:{args.port} ({len(test_data)} bytes)") + + # 等待回显 + data, addr = client_socket.recvfrom(args.size) + if data: + if data == test_data: + log_success(f"回显数据匹配! (from {addr[0]}:{addr[1]})") + else: + log_error("回显数据不匹配!") + else: + log_error("收到空数据") + break + + time.sleep(0.2) + except socket.timeout: + log_warn("接收超时") + except Exception as e: + log_error(f"发送/接收错误: {e}") + break + + # 测试大数据 + log_info("-" * 40) + log_info("测试 2: 大数据 UDP 传输") + try: + large_data = generate_data(min(512, args.size)) + client_socket.sendto(large_data, dest_addr) + log_success(f"发送大数据 ({len(large_data)} bytes)") + + data, addr = client_socket.recvfrom(args.size) + if data and data == large_data: + log_success("大数据传输完整!") + else: + log_error("大数据传输失败") + except Exception as e: + log_error(f"大数据传输错误: {e}") + + log_success("测试完成") + + except Exception as e: + log_error(f"客户端错误: {e}") + finally: + client_socket.close() + + +# ==================== 回显测试 ==================== + +def tcp_echo_server_test(args): + """TCP 回显服务器测试""" + log_info("=" * 60) + log_info("TCP 回显服务器测试 - 测试双向通信功能") + log_info("=" * 60) + + server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.settimeout(60) + + try: + server_socket.bind(('0.0.0.0', args.port)) + server_socket.listen(1) + log_success(f"回显服务器启动,端口 {args.port}") + log_info("等待连接...") + + client_socket, client_addr = server_socket.accept() + log_success(f"连接来自: {client_addr[0]}:{client_addr[1]}") + client_socket.settimeout(5) + + # 循环回显 + echo_count = 0 + start_time = time.time() + + log_info("开始回显测试 (按 Ctrl+C 停止)...") + while echo_count < args.count: + try: + data = client_socket.recv(args.size) + if not data: + log_warn("客户端断开连接") + break + + echo_count += 1 + client_socket.send(data) + + if echo_count % 10 == 0: + elapsed = time.time() - start_time + rate = echo_count / elapsed if elapsed > 0 else 0 + log_info(f"回显 {echo_count} 次, 速率: {rate:.1f} msg/s") + + except socket.timeout: + continue + except Exception as e: + log_error(f"回显错误: {e}") + break + + elapsed = time.time() - start_time + log_success(f"回显测试完成: {echo_count} 次, 耗时 {elapsed:.2f}s") + + client_socket.close() + + except Exception as e: + log_error(f"服务器错误: {e}") + finally: + server_socket.close() + + +def udp_echo_server_test(args): + """UDP 回显服务器测试""" + log_info("=" * 60) + log_info("UDP 回显服务器测试 - 测试双向通信功能") + log_info("=" * 60) + + server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_socket.settimeout(60) + + try: + server_socket.bind(('0.0.0.0', args.port)) + log_success(f"UDP 回显服务器启动,端口 {args.port}") + log_info("等待数据...") + + echo_count = 0 + start_time = time.time() + clients = set() + + while echo_count < args.count: + try: + data, client_addr = server_socket.recvfrom(args.size) + if not data: + continue + + clients.add(client_addr) + echo_count += 1 + + # 回显数据 + server_socket.sendto(data, client_addr) + + if echo_count % 10 == 0: + elapsed = time.time() - start_time + rate = echo_count / elapsed if elapsed > 0 else 0 + log_info(f"回显 {echo_count} 次, 客户端数: {len(clients)}, 速率: {rate:.1f} msg/s") + + except socket.timeout: + continue + except Exception as e: + log_error(f"回显错误: {e}") + break + + elapsed = time.time() - start_time + log_success(f"回显测试完成: {echo_count} 次, 耗时 {elapsed:.2f}s") + + except Exception as e: + log_error(f"服务器错误: {e}") + finally: + server_socket.close() + + +# ==================== 压力测试 ==================== + +def stress_test_thread(client_id, args, results): + """压力测试线程""" + thread_name = f"Client-{client_id}" + sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(10) + + try: + sock.connect((args.ip, args.port)) + log_success(f"[{thread_name}] 连接成功") + + success_count = 0 + fail_count = 0 + + for i in range(args.count): + try: + # 发送 + test_data = f"T{client_id:02d}_{i:04d}".encode() + sock.send(test_data) + + # 接收 + data = sock.recv(100) + if data and data == test_data: + success_count += 1 + else: + fail_count += 1 + + except Exception as e: + fail_count += 1 + + results[client_id] = (success_count, fail_count) + log_info(f"[{thread_name}] 完成: 成功 {success_count}, 失败 {fail_count}") + + except Exception as e: + log_error(f"[{thread_name}] 错误: {e}") + results[client_id] = (0, args.count) + finally: + sock.close() + + +def stress_test(args): + """压力测试(多连接并发)""" + log_info("=" * 60) + log_info("压力测试 - 多连接并发") + log_info("=" * 60) + + num_clients = min(args.count, 7) # CH395F 最多支持 7 个并发连接 + log_info(f"创建 {num_clients} 个并发连接...") + + results = {} + threads = [] + + # 创建并启动线程 + for i in range(num_clients): + thread = threading.Thread( + target=stress_test_thread, + args=(i, args, results) + ) + threads.append(thread) + thread.start() + time.sleep(0.1) # 错开连接时间 + + # 等待所有线程完成 + for thread in threads: + thread.join() + + # 汇总结果 + total_success = sum(r[0] for r in results.values()) + total_fail = sum(r[1] for r in results.values()) + + log_info("-" * 40) + log_info("压力测试结果:") + log_info(f" 并发连接数: {num_clients}") + log_info(f" 每连接测试次数: {args.count}") + log_info(f" 总成功: {total_success}") + log_info(f" 总失败: {total_fail}") + log_info(f" 成功率: {total_success/(total_success+total_fail)*100:.1f}%") + + if total_fail == 0: + log_success("压力测试通过!") + else: + log_error("压力测试有失败!") + + +# ==================== 数据包大小测试 ==================== + +def packet_size_test(args): + """不同数据包大小测试""" + log_info("=" * 60) + log_info("数据包大小测试") + log_info("=" * 60) + + client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + client_socket.settimeout(5) + + try: + client_socket.connect((args.ip, args.port)) + log_success("连接成功") + + # 测试不同的数据包大小 + test_sizes = [1, 10, 100, 512, 1024, 2048, 4096] + + for size in test_sizes: + try: + test_data = generate_data(size) + start_time = time.time() + client_socket.send(test_data) + + received = b"" + while len(received) < size: + chunk = client_socket.recv(size - len(received)) + if not chunk: + break + received += chunk + + elapsed = time.time() - start_time + + if received == test_data: + rate = size / elapsed / 1024 if elapsed > 0 else 0 + log_success(f"大小 {size:5d} bytes: 成功, 速率 {rate:.1f} KB/s") + else: + log_error(f"大小 {size:5d} bytes: 数据不匹配") + except Exception as e: + log_error(f"大小 {size:5d} bytes: 错误 - {e}") + + time.sleep(0.1) + + except Exception as e: + log_error(f"测试错误: {e}") + finally: + client_socket.close() + + +# ==================== 主程序 ==================== + +def main(): + parser = argparse.ArgumentParser( + description='CH395F BSD Socket API 测试程序', + formatter_class=argparse.RawDescriptionHelpFormatter, + epilog=""" +示例: + %(prog)s tcp_client --ip 192.168.1.100 --port 8080 + %(prog)s tcp_server --port 8080 + %(prog)s stress --ip 192.168.1.100 --port 8080 --count 5 + """ + ) + + parser.add_argument('mode', choices=['tcp_server', 'tcp_client', 'udp_server', 'udp_client', + 'tcp_echo', 'udp_echo', 'stress', 'packet_size'], + help='测试模式') + parser.add_argument('--ip', default='192.168.1.100', help='目标 IP 地址') + parser.add_argument('--port', type=int, default=8080, help='端口号') + parser.add_argument('--count', type=int, default=10, help='测试次数') + parser.add_argument('--size', type=int, default=1024, help='数据包大小') + + args = parser.parse_args() + + print() + print(Colors.BOLD + "=" * 60 + Colors.END) + print(Colors.BOLD + " CH395F BSD Socket API 测试程序" + Colors.END) + print(Colors.BOLD + "=" * 60 + Colors.END) + print() + + try: + if args.mode == 'tcp_server': + tcp_server_test(args) + elif args.mode == 'tcp_client': + tcp_client_test(args) + elif args.mode == 'udp_server': + udp_server_test(args) + elif args.mode == 'udp_client': + udp_client_test(args) + elif args.mode == 'tcp_echo': + tcp_echo_server_test(args) + elif args.mode == 'udp_echo': + udp_echo_server_test(args) + elif args.mode == 'stress': + stress_test(args) + elif args.mode == 'packet_size': + packet_size_test(args) + except KeyboardInterrupt: + log_warn("用户中断") + except Exception as e: + log_error(f"未预期的错误: {e}") + import traceback + traceback.print_exc() + + +if __name__ == '__main__': + main()