diff --git a/AT固件使用手册.md b/AT固件使用手册.md new file mode 100644 index 0000000..0dc52ca --- /dev/null +++ b/AT固件使用手册.md @@ -0,0 +1,645 @@ +# TCP2UART AT 固件使用手册 + +## 1. 文档说明 + +本文档用于说明 `TCP2UART` 固件的 `AT` 配置功能使用方法。 + +本手册适用于当前工程中的 STM32 固件版本,配置口通过 `USART1` 提供,主要用于: + +- 查询当前设备参数 +- 修改网络参数 +- 修改串口透传参数 +- 保存参数到 Flash +- 恢复默认参数 +- 软件复位设备 + +本文档内容基于以下信息整理: + +- 当前代码实现 `App/config.c`、`App/config.h` +- 当前默认配置定义 +- 实板测试结果 +- `uart-ch390-debug-handoff.md` 中已记录的 bench 经验 + +## 1.1 版本更新说明(2026-04-02) + +为避免引入额外时序噪声与误判,当前固件已移除 CH390 的 bit-bang 诊断读路径。 + +当前版本仅保留 CH390 硬件 SPI 访问路径,启动探测与运行期寄存器访问均通过统一 SPI 接口执行。 + +## 2. 适用硬件 + +- 主控:`STM32F103R8T6` +- 以太网芯片:`CH390D` +- 配置串口:`USART1` +- 透传串口:`USART2`、`USART3` + +当前引脚用途如下: + +- `PA9`:`USART1_TX`,配置口发送 +- `PA10`:`USART1_RX`,配置口接收 +- `PA2/PA3`:`USART2`,对应 TCP Server 透传串口 +- `PB10/PB11`:`USART3`,对应 TCP Client 透传串口 + +## 3. 配置口通信参数 + +连接配置口时,使用以下串口参数: + +- 波特率:`115200` +- 数据位:`8` +- 校验位:`None` +- 停止位:`1` + +## 4. 最重要的使用规则 + +### 4.1 推荐使用 `\n` 作为命令结尾 + +这是当前固件 bench 联调中最重要的一条经验。 + +在当前实板联调中,使用 `\n` 结尾是最稳妥的做法。例如: + +```text +AT\n +AT+?\n +AT+IP=192.168.1.123\n +``` + +代码中的帧接收逻辑会在收到 `\r` 或 `\n` 时结束当前命令,但当前 bench 记录表明,实际使用时优先采用 `\n` 结尾更可靠。如果上位机默认发送 `\r\n` 后看起来“设备没有响应”,优先改成只发送 `\n` 再测试。 + +### 4.2 修改参数后不会立刻写入 Flash + +大多数设置命令只会先修改 RAM 中的当前配置,不会自动写入 Flash。 + +如果要让参数在复位或重新上电后继续保持,必须执行: + +1. `AT+SAVE` +2. `AT+RESET` + +### 4.3 `AT+DEFAULT` 只恢复当前默认值,不自动保存 + +执行 `AT+DEFAULT` 后,当前参数会恢复为默认值,但如果要让默认值持久生效,仍然需要: + +1. `AT+SAVE` +2. `AT+RESET` + +## 5. 默认参数 + +当前固件默认值如下: + +| 参数 | 默认值 | +|------|--------| +| MAC | `02:00:00:00:00:01` | +| DHCP | `0` | +| IP | `192.168.1.100` | +| MASK | `255.255.255.0` | +| GW | `192.168.1.1` | +| PORT | `8080` | +| RIP | `192.168.1.200` | +| RPORT | `9000` | +| BAUD1 | `115200` | +| BAUD2 | `115200` | + +说明: + +- `BAUD1` 对应 `USART2`,即 TCP Server 对应的透传串口 +- `BAUD2` 对应 `USART3`,即 TCP Client 对应的透传串口 + +## 6. AT 命令总览 + +当前固件支持以下命令: + +- `AT` +- `AT+?` +- `AT+QUERY` +- `AT+IP=...` +- `AT+MASK=...` +- `AT+GW=...` +- `AT+RIP=...` +- `AT+MAC=...` +- `AT+PORT=...` +- `AT+RPORT=...` +- `AT+BAUD1=...` +- `AT+BAUD2=...` +- `AT+DHCP=0` +- `AT+SAVE` +- `AT+RESET` +- `AT+DEFAULT` + +## 7. 命令详细说明 + +### 7.1 测试设备在线 + +命令: + +```text +AT\n +``` + +返回: + +```text +OK +``` + +用途: + +- 用于确认配置口通信正常 + +### 7.2 查询当前参数 + +命令 1: + +```text +AT+?\n +``` + +命令 2: + +```text +AT+QUERY\n +``` + +返回示例: + +```text +MAC: 02:00:00:00:00:01 +DHCP: 0 +IP: 192.168.1.100 +MASK: 255.255.255.0 +GW: 192.168.1.1 +PORT: 8080 +RIP: 192.168.1.200 +RPORT: 9000 +BAUD1: 115200 +BAUD2: 115200 +``` + +用途: + +- 查询当前运行配置 +- 可用于保存前后、复位前后对比参数是否一致 + +### 7.3 设置设备 IP 地址 + +命令: + +```text +AT+IP=192.168.1.123\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid IP format +``` + +说明: + +- 推荐使用标准 IPv4 点分十进制格式 +- 当前参数会立即更新到 RAM +- 需要 `AT+SAVE` 和 `AT+RESET` 后才会以持久配置重新启动 + +### 7.4 设置子网掩码 + +命令: + +```text +AT+MASK=255.255.255.0\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid mask format +``` + +### 7.5 设置网关 + +命令: + +```text +AT+GW=192.168.1.1\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid gateway format +``` + +### 7.6 设置远端服务器 IP + +命令: + +```text +AT+RIP=192.168.1.201\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid remote IP format +``` + +说明: + +- 该参数用于 TCP Client 主动连接的目标地址 + +### 7.7 设置 MAC 地址 + +命令: + +```text +AT+MAC=02:12:34:56:78:9A\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid MAC format +``` + +说明: + +- 推荐使用 `:` 分隔的标准 MAC 字符串 +- 当前实现也兼容 `-` 分隔格式 + +### 7.8 设置 TCP Server 监听端口 + +命令: + +```text +AT+PORT=10001\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid port +``` + +说明: + +- 推荐使用 `1 ~ 65535` 范围内的十进制端口号 + +### 7.9 设置 TCP Client 远端端口 + +命令: + +```text +AT+RPORT=10002\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid port +``` + +说明: + +- 推荐使用 `1 ~ 65535` 范围内的十进制端口号 + +### 7.10 设置 USART2 波特率 + +命令: + +```text +AT+BAUD1=57600\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid baudrate +``` + +说明: + +- `BAUD1` 对应 `USART2` +- 推荐使用 `1200 ~ 921600` 范围内的标准波特率值 + +### 7.11 设置 USART3 波特率 + +命令: + +```text +AT+BAUD2=38400\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回: + +```text +ERROR: Invalid baudrate +``` + +说明: + +- `BAUD2` 对应 `USART3` +- 推荐使用 `1200 ~ 921600` 范围内的标准波特率值 + +### 7.12 设置 DHCP + +命令: + +```text +AT+DHCP=0\n +``` + +成功返回: + +```text +OK +Note: Use AT+SAVE then AT+RESET to apply changes +``` + +失败返回 1: + +```text +ERROR: Invalid value +``` + +失败返回 2: + +```text +ERROR: DHCP disabled in this build +``` + +说明: + +- 当前固件构建不支持 DHCP +- 因此 `AT+DHCP=1` 会明确返回失败 +- 当前版本建议固定使用静态 IP + +### 7.13 保存参数到 Flash + +命令: + +```text +AT+SAVE\n +``` + +成功返回: + +```text +OK: Configuration saved +``` + +失败返回: + +```text +ERROR: Save failed +``` + +用途: + +- 将当前 RAM 中的配置写入 Flash +- 只有执行成功后,参数才会在复位后保留 + +### 7.14 软件复位设备 + +命令: + +```text +AT+RESET\n +``` + +返回: + +```text +OK: Resetting... +``` + +用途: + +- 请求设备执行软件复位 +- 通常用于 `AT+SAVE` 之后让新配置重新生效 + +### 7.15 恢复默认参数 + +命令: + +```text +AT+DEFAULT\n +``` + +返回: + +```text +OK: Defaults restored +``` + +用途: + +- 将当前配置恢复为固件默认值 +- 如果要让默认值长期生效,仍然需要再执行 `AT+SAVE` 和 `AT+RESET` + +## 8. 常见错误返回 + +当前固件已验证的错误返回如下: + +| 场景 | 返回 | +|------|------| +| 未知命令 | `ERROR: Unknown command` | +| 非法端口 | `ERROR: Invalid port` | +| 非法波特率 | `ERROR: Invalid baudrate` | +| 非法 IP 地址 | `ERROR: Invalid IP format` | +| 非法掩码 | `ERROR: Invalid mask format` | +| 非法网关 | `ERROR: Invalid gateway format` | +| 非法远端 IP | `ERROR: Invalid remote IP format` | +| 非法 MAC | `ERROR: Invalid MAC format` | +| 非法 DHCP 参数 | `ERROR: Invalid value` | +| DHCP 在当前构建中不可用 | `ERROR: DHCP disabled in this build` | +| Flash 保存失败 | `ERROR: Save failed` | + +## 9. 推荐操作流程 + +### 9.1 查询当前参数 + +```text +AT\n +AT+?\n +``` + +### 9.2 修改参数并永久保存 + +例如修改设备 IP、监听端口和远端地址: + +```text +AT+IP=192.168.1.123\n +AT+PORT=10001\n +AT+RIP=192.168.1.201\n +AT+RPORT=10002\n +AT+SAVE\n +AT+RESET\n +``` + +设备复位后,再执行: + +```text +AT+?\n +``` + +确认参数是否与修改值一致。 + +### 9.3 恢复出厂默认参数 + +```text +AT+DEFAULT\n +AT+SAVE\n +AT+RESET\n +AT+?\n +``` + +## 10. Flash 持久化说明 + +当前固件已在实板上验证以下保存/复位路径: + +- 设置参数后执行 `AT+SAVE` +- 再执行 `AT+RESET` +- 设备重启后再次查询参数 +- 查询结果与保存前一致 +- 原始 Flash 参数页 `0x0800FC00` 在 reset 前后读回一致 + +这说明在上述测试路径下: + +- 配置确实被写入 Flash +- reset 后加载流程正常 +- 参数在 `设置 -> 保存 -> 复位 -> 查询` 这条路径下可以保持一致 + +## 11. 故障排查建议 + +### 11.1 发送 `AT` 没有返回 + +优先检查以下几项: + +1. 是否连接到了 `USART1` +2. 串口参数是否为 `115200 8N1` +3. 是否优先使用 `\n` 作为命令结尾 +4. USB 转串口模块接线是否正确 +5. 设备是否已经正常上电运行 + +### 11.2 设置成功但重启后参数丢失 + +检查是否漏掉了以下步骤: + +1. `AT+SAVE` +2. `AT+RESET` + +如果只执行设置命令但没有保存,参数只会停留在当前 RAM 中。 + +### 11.3 `AT+DHCP=1` 失败 + +这是当前固件设计行为,不是故障。 + +当前构建不支持 DHCP,因此: + +```text +AT+DHCP=1\n +``` + +返回: + +```text +ERROR: DHCP disabled in this build +``` + +## 12. 已实测通过的命令行为 + +本手册中的以下命令行为已经在实板上验证通过: + +- `AT` +- `AT+?` +- `AT+QUERY` +- `AT+IP=...` +- `AT+MASK=...` +- `AT+GW=...` +- `AT+RIP=...` +- `AT+MAC=...` +- `AT+PORT=...` +- `AT+RPORT=...` +- `AT+BAUD1=...` +- `AT+BAUD2=...` +- `AT+DHCP=0` +- `AT+SAVE` +- `AT+RESET` +- `AT+DEFAULT` + +已实测通过的错误路径包括: + +- `AT+UNKNOWN` +- `AT+PORT=0` +- `AT+PORT=65536` +- `AT+BAUD1=1199` +- `AT+BAUD1=921601` +- `AT+DHCP=1` +- `AT+IP=999.1.1.1` +- `AT+MAC=GG:11:22:33:44:55` + +## 13. 相关文件 + +- AT 命令实现:[config.c](/D:/code/STM32Project/TCP2UART/App/config.c) +- 配置结构与默认值:[config.h](/D:/code/STM32Project/TCP2UART/App/config.h) +- 调试与测试记录:[uart-ch390-debug-handoff.md](/D:/code/STM32Project/TCP2UART/uart-ch390-debug-handoff.md) diff --git a/CH390_最终结论报告.md b/CH390_最终结论报告.md index c83e993..138653a 100644 --- a/CH390_最终结论报告.md +++ b/CH390_最终结论报告.md @@ -19,7 +19,7 @@ 5. 在 `main()` 中移除重复 CH390 复位,避免启动阶段额外复位噪声。 6. 清理已确认 warning 来源,避免无效变量继续污染构建结果。 7. 增加 CH390 identity gate,避免在无效寄存器读回前继续执行默认配置和 PHY 初始化。 -8. 增加 bit-bang 诊断读,专门区分 STM32 硬件 SPI 实现问题和板级总线/器件无响应问题。 +8. 曾增加 bit-bang 诊断读用于快速隔离问题,该临时调试路径已在当前代码中移除。 ## 实机关键证据 @@ -47,15 +47,15 @@ CH390 VID=0xFFFF PID=0xFFFF REV=0xFF NSR=0xFF LINK=0 CH390 NCR=0xFF RCR=0xFF IMR=0xFF INTCR=0xFF GPR=0xFF ISR=0xFF ``` -### 3. bit-bang 读 CH390 仍为全 `0xFF` +### 3. 历史 bit-bang 对照结果(已归档) -在绕过 STM32 硬件 SPI 外设、直接用 GPIO 软件时序读取 `VIDL/VIDH/PIDL/PIDH/CHIPR` 后,RTT 输出为: +在早期调试中,曾绕过 STM32 硬件 SPI 外设、直接用 GPIO 软件时序读取 `VIDL/VIDH/PIDL/PIDH/CHIPR`,RTT 输出为: ```text CH390 bitbang VIDL=0xFF VIDH=0xFF PIDL=0xFF PIDH=0xFF CHIPR=0xFF ``` -这一点非常关键,因为它说明: +该历史证据用于定位阶段,当前仅保留结论,不再保留对应代码路径。它说明: 1. 问题不再像单纯的 `SPI1` 模式寄存器或 HAL 事务实现错误。 2. 即使 MCU 直接软件驱动 `CS/SCK/MOSI`,CH390 端仍未给出有效响应。 diff --git a/Drivers/CH390/CH390_Interface.c b/Drivers/CH390/CH390_Interface.c index e923fe3..4fe4cae 100644 --- a/Drivers/CH390/CH390_Interface.c +++ b/Drivers/CH390/CH390_Interface.c @@ -61,8 +61,6 @@ extern SPI_HandleTypeDef hspi1; /* Timeout for SPI operations (ms) */ #define SPI_TIMEOUT 100 -#define CH390_BITBANG_HALF_PERIOD_US 2u - /*---------------------------------------------------------------------------- * Low-level GPIO operations *---------------------------------------------------------------------------*/ @@ -105,63 +103,6 @@ static inline uint8_t ch390_miso(void) return (uint8_t)(HAL_GPIO_ReadPin(CH390_MISO_PORT, CH390_MISO_PIN) == GPIO_PIN_SET); } -static void ch390_spi_gpio_mode(void) -{ - GPIO_InitTypeDef GPIO_InitStruct = {0}; - - GPIO_InitStruct.Pin = CH390_SCK_PIN | CH390_MOSI_PIN; - GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - HAL_GPIO_Init(CH390_SCK_PORT, &GPIO_InitStruct); - - GPIO_InitStruct.Pin = CH390_MISO_PIN; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(CH390_MISO_PORT, &GPIO_InitStruct); - - ch390_cs(1u); - ch390_sck(1u); - ch390_mosi(1u); -} - -static void ch390_spi_restore_pins(void) -{ - GPIO_InitTypeDef GPIO_InitStruct = {0}; - - GPIO_InitStruct.Pin = CH390_SCK_PIN | CH390_MOSI_PIN; - GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; - GPIO_InitStruct.Pull = GPIO_NOPULL; - GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; - HAL_GPIO_Init(CH390_SCK_PORT, &GPIO_InitStruct); - - GPIO_InitStruct.Pin = CH390_MISO_PIN; - GPIO_InitStruct.Mode = GPIO_MODE_INPUT; - GPIO_InitStruct.Pull = GPIO_NOPULL; - HAL_GPIO_Init(CH390_MISO_PORT, &GPIO_InitStruct); - - ch390_spi_init(); -} - -static uint8_t ch390_bitbang_exchange_byte(uint8_t byte) -{ - uint8_t bit; - uint8_t rx_data = 0u; - - for (bit = 0u; bit < 8u; ++bit) { - ch390_mosi((uint8_t)((byte & 0x80u) != 0u)); - ch390_delay_us(CH390_BITBANG_HALF_PERIOD_US); - ch390_sck(0u); - ch390_delay_us(CH390_BITBANG_HALF_PERIOD_US); - rx_data = (uint8_t)((rx_data << 1) | ch390_miso()); - ch390_sck(1u); - byte <<= 1; - ch390_delay_us(CH390_BITBANG_HALF_PERIOD_US); - } - - return rx_data; -} - /*---------------------------------------------------------------------------- * SPI Communication *---------------------------------------------------------------------------*/ @@ -260,7 +201,7 @@ void ch390_spi_init(void) /* - CPOL = High (idle clock is high) */ /* - CPHA = 2Edge (data captured on second edge) */ - ch390_spi_apply_mode(SPI_POLARITY_HIGH, SPI_PHASE_2EDGE); + ch390_spi_apply_mode(SPI_POLARITY_LOW, SPI_PHASE_1EDGE); /* Start with Mode 0 */ } /** @@ -311,28 +252,13 @@ void ch390_delay_us(uint32_t time) */ void ch390_hardware_reset(void) { + ch390_delay_us(3000); /* Short delay before reset */ ch390_rst(0); /* Assert reset (low) */ - ch390_delay_us(1000); /* Hold reset for 1ms to satisfy datasheet minimum */ + ch390_delay_us(3000); /* Hold reset for 3ms to satisfy datasheet minimum */ ch390_rst(1); /* Release reset (high) */ ch390_delay_us(50000); /* Wait 50ms for CH390 to initialize reliably */ } -uint8_t ch390_bitbang_read_reg(uint8_t reg) -{ - uint8_t value; - - __HAL_SPI_DISABLE(&hspi1); - ch390_spi_gpio_mode(); - - ch390_cs(0u); - (void)ch390_bitbang_exchange_byte((uint8_t)(reg | OPC_REG_R)); - value = ch390_bitbang_exchange_byte(0x00u); - ch390_cs(1u); - - ch390_spi_restore_pins(); - return value; -} - /*---------------------------------------------------------------------------- * CH390 Register/Memory Access Functions (SPI Mode) *---------------------------------------------------------------------------*/ diff --git a/Drivers/CH390/CH390_Interface.h b/Drivers/CH390/CH390_Interface.h index fc62cef..657468e 100644 --- a/Drivers/CH390/CH390_Interface.h +++ b/Drivers/CH390/CH390_Interface.h @@ -19,7 +19,6 @@ void ch390_spi_init(void); uint16_t ch390_get_int_pin(void); void ch390_delay_us(uint32_t time); void ch390_hardware_reset(void); -uint8_t ch390_bitbang_read_reg(uint8_t reg); /** * @name ch390_read_reg diff --git a/Drivers/CH390/ch390_runtime.c b/Drivers/CH390/ch390_runtime.c index 5a39cf6..14bd464 100644 --- a/Drivers/CH390/ch390_runtime.c +++ b/Drivers/CH390/ch390_runtime.c @@ -123,13 +123,6 @@ void ch390_runtime_init(struct netif *netif, const uint8_t *mac) SEGGER_RTT_WriteString(0, "ETH init: probe\r\n"); g_ch390_ready = ch390_runtime_probe_identity(); if (g_ch390_ready == 0u) { - SEGGER_RTT_printf(0, - "CH390 bitbang VIDL=0x%02X VIDH=0x%02X PIDL=0x%02X PIDH=0x%02X CHIPR=0x%02X\r\n", - ch390_bitbang_read_reg(CH390_VIDL), - ch390_bitbang_read_reg(CH390_VIDH), - ch390_bitbang_read_reg(CH390_PIDL), - ch390_bitbang_read_reg(CH390_PIDH), - ch390_bitbang_read_reg(CH390_CHIPR)); netif->hwaddr_len = ETHARP_HWADDR_LEN; netif->mtu = 1500; netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP;