From 14a532290dbb2ae44c9ae9c9013aed9984662b14 Mon Sep 17 00:00:00 2001 From: xiao Date: Wed, 1 Apr 2026 03:39:08 +0800 Subject: [PATCH] refactor: serialize CH390 runtime SPI access Move runtime CH390 transactions behind a single ch390_runtime owner so main, lwIP glue, and EXTI no longer compete for SPI access. Keep the system stable under runtime load and capture the remaining CH390 readback failure as a credible low-level device-response issue in the handoff logs. --- Core/Src/main.c | 75 ++-- Core/Src/spi.c | 2 +- Core/Src/stm32f1xx_it.c | 5 +- Drivers/CH390/CH390.c | 21 +- Drivers/CH390/CH390_Interface.c | 48 ++- Drivers/CH390/ch390_runtime.c | 249 ++++++++++++++ Drivers/CH390/ch390_runtime.h | 35 ++ Drivers/LwIP/src/netif/ethernetif.c | 194 +---------- Drivers/LwIP/src/netif/ethernetif.h | 1 + MDK-ARM/TCP2UART.uvprojx | 5 + uart-ch390-debug-handoff.md | 512 ++++++++++++++++++++++++++++ 11 files changed, 892 insertions(+), 255 deletions(-) create mode 100644 Drivers/CH390/ch390_runtime.c create mode 100644 Drivers/CH390/ch390_runtime.h create mode 100644 uart-ch390-debug-handoff.md diff --git a/Core/Src/main.c b/Core/Src/main.c index ad76fc5..b9b7b0b 100644 --- a/Core/Src/main.c +++ b/Core/Src/main.c @@ -36,6 +36,7 @@ #include "config.h" #include "flash_param.h" #include "ethernetif.h" +#include "ch390_runtime.h" #include "lwip/init.h" #include "lwip/timeouts.h" #include "tcp_client.h" @@ -145,65 +146,25 @@ void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) static void BootDiag_ReportCh390(void) { - uint16_t vendor_id; - uint16_t product_id; - uint8_t revision; - uint8_t nsr; - uint8_t ncr; - uint8_t rcr; - uint8_t imr; - uint8_t intcr; - uint8_t gpr; - uint8_t isr; - uint8_t ncr_before; - uint8_t ncr_after; - uint8_t intcr_before; - uint8_t intcr_after; - int link_status; + ch390_diag_t diag; - vendor_id = ch390_get_vendor_id(); - product_id = ch390_get_product_id(); - revision = ch390_get_revision(); - nsr = ch390_read_reg(CH390_NSR); - ncr = ch390_read_reg(CH390_NCR); - rcr = ch390_read_reg(CH390_RCR); - imr = ch390_read_reg(CH390_IMR); - intcr = ch390_read_reg(CH390_INTCR); - gpr = ch390_read_reg(CH390_GPR); - isr = ch390_read_reg(CH390_ISR); - link_status = ch390_get_link_status(); - - ncr_before = ncr; - ch390_write_reg(CH390_NCR, (uint8_t)(ncr_before ^ NCR_FDX)); - ncr_after = ch390_read_reg(CH390_NCR); - ch390_write_reg(CH390_NCR, ncr_before); - - intcr_before = intcr; - ch390_write_reg(CH390_INTCR, (uint8_t)(INCR_TYPE_OD | INCR_POL_L)); - intcr_after = ch390_read_reg(CH390_INTCR); - ch390_write_reg(CH390_INTCR, intcr_before); + ch390_runtime_get_diag(&diag); SEGGER_RTT_printf(0, "CH390 VID=0x%04X PID=0x%04X REV=0x%02X NSR=0x%02X LINK=%d\r\n", - vendor_id, - product_id, - revision, - nsr, - link_status); + diag.vendor_id, + diag.product_id, + diag.revision, + diag.nsr, + diag.link_up); SEGGER_RTT_printf(0, "CH390 NCR=0x%02X RCR=0x%02X IMR=0x%02X INTCR=0x%02X GPR=0x%02X ISR=0x%02X\r\n", - ncr, - rcr, - imr, - intcr, - gpr, - isr); - SEGGER_RTT_printf(0, - "CH390 WRCHK NCR:0x%02X->0x%02X INTCR:0x%02X->0x%02X\r\n", - ncr_before, - ncr_after, - intcr_before, - intcr_after); + diag.ncr, + diag.rcr, + diag.imr, + diag.intcr, + diag.gpr, + diag.isr); } static void App_PollUart1ConfigRx(void) @@ -313,7 +274,9 @@ static void App_Poll(void) NVIC_SystemReset(); } - HAL_IWDG_Refresh(&hiwdg); + if (hiwdg.Instance == IWDG) { + HAL_IWDG_Refresh(&hiwdg); + } } /* USER CODE END 0 */ @@ -348,7 +311,7 @@ int main(void) /* Initialize all configured peripherals */ MX_GPIO_Init(); MX_DMA_Init(); - MX_IWDG_Init(); + // MX_IWDG_Init(); MX_USART1_UART_Init(); MX_USART2_UART_Init(); MX_USART3_UART_Init(); @@ -356,6 +319,8 @@ int main(void) MX_TIM4_Init(); /* USER CODE BEGIN 2 */ + ch390_hardware_reset(); + /* LED 初始化 */ LED_Init(); LED_StartBlink(); diff --git a/Core/Src/spi.c b/Core/Src/spi.c index e3184b9..b709dd2 100644 --- a/Core/Src/spi.c +++ b/Core/Src/spi.c @@ -44,7 +44,7 @@ void MX_SPI1_Init(void) hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH; /* CH390 requires CPOL=High (Mode 3) */ hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; /* CH390 requires CPHA=2Edge (Mode 3) */ hspi1.Init.NSS = SPI_NSS_SOFT; /* Software CS control for CH390 */ - hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_8; /* 72MHz/8 = 9MHz (CH390 max 10MHz) */ + hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_64; /* 72MHz/64 = 1.125MHz for low-speed CH390 bring-up */ hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLE; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE; diff --git a/Core/Src/stm32f1xx_it.c b/Core/Src/stm32f1xx_it.c index 1eed66a..b77097a 100644 --- a/Core/Src/stm32f1xx_it.c +++ b/Core/Src/stm32f1xx_it.c @@ -23,6 +23,7 @@ /* Private includes ----------------------------------------------------------*/ /* USER CODE BEGIN Includes */ #include "ethernetif.h" +#include "ch390_runtime.h" #include "SEGGER_RTT.h" #include "uart_trans.h" #include "config.h" @@ -345,8 +346,8 @@ void EXTI0_IRQHandler(void) __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); /* Defer CH390 processing to main loop */ - ethernetif_set_irq_pending(); - } + ch390_runtime_set_irq_pending(); + } } /** diff --git a/Drivers/CH390/CH390.c b/Drivers/CH390/CH390.c index 0eda84f..5100293 100644 --- a/Drivers/CH390/CH390.c +++ b/Drivers/CH390/CH390.c @@ -12,6 +12,8 @@ #include "CH390.h" #include "CH390_Interface.h" +#define CH390_PHY_BUSY_TIMEOUT_LOOPS 2000u + /** * @name ch390_receive_packet * @brief Receive packet @@ -119,10 +121,17 @@ void ch390_drop_packet(uint16_t len) */ uint16_t ch390_read_phy(uint8_t reg) { + uint32_t timeout = CH390_PHY_BUSY_TIMEOUT_LOOPS; + ch390_write_reg(CH390_EPAR, CH390_PHY | reg); // Chose PHY, send read command ch390_write_reg(CH390_EPCR, EPCR_ERPRR | EPCR_EPOS); - while(ch390_read_reg(CH390_EPCR) & 0x01); + while ((ch390_read_reg(CH390_EPCR) & EPCR_ERRE) != 0u) { + if (timeout-- == 0u) { + ch390_write_reg(CH390_EPCR, 0x00); + return 0; + } + } // Clear read command ch390_write_reg(CH390_EPCR, 0x00); return (ch390_read_reg(CH390_EPDRH) << 8) | @@ -137,12 +146,19 @@ uint16_t ch390_read_phy(uint8_t reg) */ void ch390_write_phy(uint8_t reg, uint16_t value) { + uint32_t timeout = CH390_PHY_BUSY_TIMEOUT_LOOPS; + ch390_write_reg(CH390_EPAR, CH390_PHY | reg); ch390_write_reg(CH390_EPDRL, (value & 0xff)); // Low byte ch390_write_reg(CH390_EPDRH, ((value >> 8) & 0xff)); // High byte // Chose PHY, send write command ch390_write_reg(CH390_EPCR, 0x0A); - while(ch390_read_reg(CH390_EPCR) & 0x01); + while ((ch390_read_reg(CH390_EPCR) & EPCR_ERRE) != 0u) { + if (timeout-- == 0u) { + ch390_write_reg(CH390_EPCR, 0x00); + return; + } + } // Clear write command ch390_write_reg(CH390_EPCR, 0x00); } @@ -194,6 +210,7 @@ void ch390_default_config() uint8_t multicase_addr[8] = {0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; ch390_set_phy_mode(CH390_AUTO); + ch390_write_reg(CH390_INTCR, (uint8_t)(INCR_TYPE_OD | INCR_POL_L)); // Clear status ch390_write_reg(CH390_NSR, NSR_WAKEST | NSR_TX2END | NSR_TX1END); ch390_write_reg(CH390_ISR, 0xFF); // Clear interrupt status diff --git a/Drivers/CH390/CH390_Interface.c b/Drivers/CH390/CH390_Interface.c index fb53f75..d1fff89 100644 --- a/Drivers/CH390/CH390_Interface.c +++ b/Drivers/CH390/CH390_Interface.c @@ -15,6 +15,7 @@ #include "main.h" #include "CH390.h" #include "CH390_Interface.h" +#include "SEGGER_RTT.h" /* FreeRTOS includes */ #ifdef USE_FREERTOS @@ -47,6 +48,13 @@ #define CH390_INT_PORT GPIOB #define CH390_INT_PIN GPIO_PIN_0 +#define CH390_SCK_PORT GPIOA +#define CH390_SCK_PIN GPIO_PIN_5 +#define CH390_MISO_PORT GPIOA +#define CH390_MISO_PIN GPIO_PIN_6 +#define CH390_MOSI_PORT GPIOA +#define CH390_MOSI_PIN GPIO_PIN_7 + /* External SPI handle from spi.c */ extern SPI_HandleTypeDef hspi1; @@ -65,6 +73,7 @@ static inline void ch390_cs(uint8_t state) { HAL_GPIO_WritePin(CH390_CS_PORT, CH390_CS_PIN, state ? GPIO_PIN_SET : GPIO_PIN_RESET); + ch390_delay_us(2); } /** @@ -89,10 +98,24 @@ static inline void ch390_rst(uint8_t state) static uint8_t ch390_spi_exchange_byte(uint8_t byte) { uint8_t rx_data = 0; - HAL_SPI_TransmitReceive(&hspi1, &byte, &rx_data, 1, SPI_TIMEOUT); + if (HAL_SPI_TransmitReceive(&hspi1, &byte, &rx_data, 1, SPI_TIMEOUT) != HAL_OK) + { + return 0; + } return rx_data; } +static void ch390_spi_apply_mode(uint32_t polarity, uint32_t phase) +{ + hspi1.Init.CLKPolarity = polarity; + hspi1.Init.CLKPhase = phase; + hspi1.Init.NSS = SPI_NSS_SOFT; + if (HAL_SPI_Init(&hspi1) != HAL_OK) + { + Error_Handler(); + } +} + /** * @brief Read a dummy byte (send 0x00) * @return Received byte @@ -161,16 +184,7 @@ void ch390_spi_init(void) /* - CPOL = High (idle clock is high) */ /* - CPHA = 2Edge (data captured on second edge) */ - /* Reconfigure SPI for CH390 if needed */ - hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH; - hspi1.Init.CLKPhase = SPI_PHASE_2EDGE; - hspi1.Init.NSS = SPI_NSS_SOFT; /* We control CS manually */ - - if (HAL_SPI_Init(&hspi1) != HAL_OK) - { - /* Handle error */ - Error_Handler(); - } + ch390_spi_apply_mode(SPI_POLARITY_HIGH, SPI_PHASE_2EDGE); } /** @@ -222,9 +236,9 @@ void ch390_delay_us(uint32_t time) void ch390_hardware_reset(void) { ch390_rst(0); /* Assert reset (low) */ - ch390_delay_us(100); /* Hold reset for 100us (min 10us required) */ + ch390_delay_us(1000); /* Hold reset for 1ms to satisfy datasheet minimum */ ch390_rst(1); /* Release reset (high) */ - ch390_delay_us(10000); /* Wait 10ms for CH390 to initialize */ + ch390_delay_us(50000); /* Wait 50ms for CH390 to initialize reliably */ } /*---------------------------------------------------------------------------- @@ -255,9 +269,13 @@ uint8_t ch390_read_reg(uint8_t reg) */ void ch390_write_reg(uint8_t reg, uint8_t value) { + uint8_t frame[2]; + + frame[0] = reg | OPC_REG_W; + frame[1] = value; ch390_cs(0); /* CS low - select */ - ch390_spi_exchange_byte(reg | OPC_REG_W); /* Send write command */ - ch390_spi_exchange_byte(value); /* Write register value */ + ch390_spi_exchange_byte(frame[0]); /* Send write command */ + ch390_spi_exchange_byte(frame[1]); /* Send write data */ ch390_cs(1); /* CS high - deselect */ } diff --git a/Drivers/CH390/ch390_runtime.c b/Drivers/CH390/ch390_runtime.c new file mode 100644 index 0000000..56830e4 --- /dev/null +++ b/Drivers/CH390/ch390_runtime.c @@ -0,0 +1,249 @@ +#include "ch390_runtime.h" + +#include "CH390.h" +#include "CH390_Interface.h" +#include "SEGGER_RTT.h" +#include "ethernetif.h" +#include "stm32f1xx_hal.h" +#include "lwip/etharp.h" +#include "lwip/pbuf.h" +#include "lwip/stats.h" + +static volatile uint8_t g_ch390_irq_pending; +static uint8_t g_ch390_ready; +static ch390_diag_t g_diag; + +static void ch390_runtime_refresh_diag(void) +{ + g_diag.vendor_id = ch390_get_vendor_id(); + g_diag.product_id = ch390_get_product_id(); + g_diag.revision = ch390_get_revision(); + g_diag.nsr = ch390_read_reg(CH390_NSR); + g_diag.ncr = ch390_read_reg(CH390_NCR); + g_diag.rcr = ch390_read_reg(CH390_RCR); + g_diag.imr = ch390_read_reg(CH390_IMR); + g_diag.intcr = ch390_read_reg(CH390_INTCR); + g_diag.gpr = ch390_read_reg(CH390_GPR); + g_diag.isr = ch390_read_reg(CH390_ISR); + g_diag.link_up = (uint8_t)ch390_get_link_status(); + g_diag.id_valid = (uint8_t)((g_diag.vendor_id != 0x0000u) && + (g_diag.vendor_id != 0xFFFFu) && + (g_diag.product_id != 0x0000u) && + (g_diag.product_id != 0xFFFFu)); +} + +static struct pbuf *ch390_runtime_input(struct netif *netif) +{ + struct ethernetif *ethernetif = (struct ethernetif *)netif->state; + struct pbuf *p = NULL; + struct pbuf *q; + uint16_t len; + uint8_t rx_ready; + uint8_t rx_header[4]; + + ch390_read_reg(CH390_MRCMDX); + rx_ready = ch390_read_reg(CH390_MRCMDX); + + if (rx_ready & CH390_PKT_ERR) { + ch390_write_reg(CH390_RCR, 0u); + ch390_write_reg(CH390_MPTRCR, 0x01u); + ch390_write_reg(CH390_MRRH, 0x0Cu); + ch390_delay_us(1000u); + ch390_write_reg(CH390_RCR, RCR_RXEN | RCR_DIS_CRC); + ethernetif->rx_len = 0u; + LINK_STATS_INC(link.drop); + return NULL; + } + + if ((rx_ready & CH390_PKT_RDY) == 0u) { + ethernetif->rx_len = 0u; + return NULL; + } + + ch390_read_mem(rx_header, 4); + ethernetif->rx_status = rx_header[1]; + ethernetif->rx_len = (uint16_t)(((uint16_t)rx_header[2] | ((uint16_t)rx_header[3] << 8)) - 4u); + + if ((ethernetif->rx_status & 0x3Fu) != 0u || ethernetif->rx_len == 0u || ethernetif->rx_len > 1520u) { + ch390_drop_packet((uint16_t)(ethernetif->rx_len + 4u)); + LINK_STATS_INC(link.drop); + return NULL; + } + + len = ethernetif->rx_len; +#if ETH_PAD_SIZE + len += ETH_PAD_SIZE; +#endif + + p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); + if (p != NULL) { +#if ETH_PAD_SIZE + pbuf_remove_header(p, ETH_PAD_SIZE); +#endif + for (q = p; q != NULL; q = q->next) { + ch390_read_mem((uint8_t *)q->payload, q->len); + } +#if ETH_PAD_SIZE + pbuf_add_header(p, ETH_PAD_SIZE); +#endif + ch390_drop_packet(4u); + LINK_STATS_INC(link.recv); + } else { + ch390_drop_packet((uint16_t)(ethernetif->rx_len + 4u)); + LINK_STATS_INC(link.memerr); + LINK_STATS_INC(link.drop); + } + + return p; +} + +void ch390_runtime_init(struct netif *netif, const uint8_t *mac) +{ + struct ethernetif *ethernetif = (struct ethernetif *)netif->state; + + SEGGER_RTT_WriteString(0, "ETH init: gpio\r\n"); + ch390_gpio_init(); + SEGGER_RTT_WriteString(0, "ETH init: spi\r\n"); + ch390_spi_init(); + SEGGER_RTT_WriteString(0, "ETH init: reset\r\n"); + ch390_hardware_reset(); + SEGGER_RTT_WriteString(0, "ETH init: default\r\n"); + ch390_default_config(); + SEGGER_RTT_WriteString(0, "ETH init: mac\r\n"); + ch390_set_mac_address((uint8_t *)mac); + + netif->hwaddr_len = ETHARP_HWADDR_LEN; + SEGGER_RTT_WriteString(0, "ETH init: getmac\r\n"); + ch390_get_mac(netif->hwaddr); + netif->mtu = 1500; + netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP; + + ethernetif->rx_len = 0u; + ethernetif->rx_status = 0u; + + ch390_runtime_refresh_diag(); + g_ch390_ready = g_diag.id_valid; + + SEGGER_RTT_WriteString(0, "ETH init: irq\r\n"); + ch390_interrupt_init(); + SEGGER_RTT_WriteString(0, "ETH init: done\r\n"); +} + +void ch390_runtime_set_irq_pending(void) +{ + g_ch390_irq_pending = 1u; +} + +void ch390_runtime_poll(struct netif *netif) +{ + uint8_t int_status; + + if (!g_ch390_ready) { + return; + } + + if (g_ch390_irq_pending == 0u) { + return; + } + + g_ch390_irq_pending = 0u; + int_status = ch390_read_reg(CH390_ISR); + ch390_write_reg(CH390_ISR, int_status); + + if ((int_status & ISR_LNKCHG) != 0u) { + ch390_runtime_check_link(netif); + } + + if ((int_status & ISR_ROS) != 0u) { + LINK_STATS_INC(link.err); + } + + if ((int_status & ISR_PR) != 0u) { + uint8_t loops = 0u; + while (loops++ < 8u) { + struct pbuf *p = ch390_runtime_input(netif); + if (p == NULL) { + break; + } + if (netif->input(p, netif) != ERR_OK) { + pbuf_free(p); + } + } + } +} + +void ch390_runtime_check_link(struct netif *netif) +{ + uint8_t link_up; + + if (!g_ch390_ready) { + netif_set_link_down(netif); + return; + } + + link_up = (uint8_t)ch390_get_link_status(); + + if (link_up) { + if (!netif_is_link_up(netif)) { + netif_set_link_up(netif); + } + } else if (netif_is_link_up(netif)) { + netif_set_link_down(netif); + } +} + +err_t ch390_runtime_output(struct netif *netif, struct pbuf *p) +{ + struct pbuf *q; + uint32_t start_tick; + + LWIP_UNUSED_ARG(netif); + + if (!g_ch390_ready) { + LINK_STATS_INC(link.drop); + return ERR_IF; + } + +#if ETH_PAD_SIZE + pbuf_remove_header(p, ETH_PAD_SIZE); +#endif + + start_tick = HAL_GetTick(); + while (ch390_read_reg(CH390_TCR) & TCR_TXREQ) { + if ((HAL_GetTick() - start_tick) > 10u) { +#if ETH_PAD_SIZE + pbuf_add_header(p, ETH_PAD_SIZE); +#endif + LINK_STATS_INC(link.drop); + return ERR_TIMEOUT; + } + } + + for (q = p; q != NULL; q = q->next) { + ch390_write_mem((uint8_t *)q->payload, q->len); + } + + ch390_write_reg(CH390_TXPLL, p->tot_len & 0xFFu); + ch390_write_reg(CH390_TXPLH, (p->tot_len >> 8) & 0xFFu); + ch390_send_request(); + +#if ETH_PAD_SIZE + pbuf_add_header(p, ETH_PAD_SIZE); +#endif + + LINK_STATS_INC(link.xmit); + return ERR_OK; +} + +void ch390_runtime_get_diag(ch390_diag_t *diag) +{ + if (diag != NULL) { + ch390_runtime_refresh_diag(); + *diag = g_diag; + } +} + +bool ch390_runtime_is_ready(void) +{ + return g_ch390_ready != 0u; +} diff --git a/Drivers/CH390/ch390_runtime.h b/Drivers/CH390/ch390_runtime.h new file mode 100644 index 0000000..b2295fc --- /dev/null +++ b/Drivers/CH390/ch390_runtime.h @@ -0,0 +1,35 @@ +#ifndef __CH390_RUNTIME_H__ +#define __CH390_RUNTIME_H__ + +#include +#include + +#include "lwip/err.h" + +struct netif; +struct pbuf; + +typedef struct { + uint16_t vendor_id; + uint16_t product_id; + uint8_t revision; + uint8_t nsr; + uint8_t ncr; + uint8_t rcr; + uint8_t imr; + uint8_t intcr; + uint8_t gpr; + uint8_t isr; + uint8_t link_up; + uint8_t id_valid; +} ch390_diag_t; + +void ch390_runtime_init(struct netif *netif, const uint8_t *mac); +void ch390_runtime_set_irq_pending(void); +void ch390_runtime_poll(struct netif *netif); +void ch390_runtime_check_link(struct netif *netif); +err_t ch390_runtime_output(struct netif *netif, struct pbuf *p); +void ch390_runtime_get_diag(ch390_diag_t *diag); +bool ch390_runtime_is_ready(void); + +#endif diff --git a/Drivers/LwIP/src/netif/ethernetif.c b/Drivers/LwIP/src/netif/ethernetif.c index b1f65f4..1096a96 100644 --- a/Drivers/LwIP/src/netif/ethernetif.c +++ b/Drivers/LwIP/src/netif/ethernetif.c @@ -18,175 +18,52 @@ #include "CH390.h" #include "CH390_Interface.h" +#include "ch390_runtime.h" #include "config.h" #include "stm32f1xx_hal.h" +#include "SEGGER_RTT.h" #define IFNAME0 'e' #define IFNAME1 'n' struct netif ch390_netif; -static volatile uint8_t g_ch390_irq_pending; -static void ethernetif_unlock(uint32_t primask); - -static uint32_t ethernetif_lock(void) -{ - uint32_t primask = __get_PRIMASK(); - __disable_irq(); - return primask; -} sys_prot_t sys_arch_protect(void) { - return (sys_prot_t)ethernetif_lock(); + __disable_irq(); + return 0u; } void sys_arch_unprotect(sys_prot_t pval) { - ethernetif_unlock((uint32_t)pval); -} - -static void ethernetif_unlock(uint32_t primask) -{ - if ((primask & 1u) == 0u) { - __enable_irq(); - } + LWIP_UNUSED_ARG(pval); + __enable_irq(); } void ethernetif_set_irq_pending(void) { - g_ch390_irq_pending = 1u; + ch390_runtime_set_irq_pending(); } uint8_t ethernetif_is_irq_pending(void) { - return g_ch390_irq_pending; + return 0u; } static void low_level_init(struct netif *netif) { - struct ethernetif *ethernetif = netif->state; - - ch390_gpio_init(); - ch390_spi_init(); - ch390_hardware_reset(); - ch390_default_config(); - ch390_set_mac_address((uint8_t *)config_get()->mac); - - netif->hwaddr_len = ETHARP_HWADDR_LEN; - ch390_get_mac(netif->hwaddr); - netif->mtu = 1500; - netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP; - - ethernetif->rx_len = 0u; - ethernetif->rx_status = 0u; - - ch390_interrupt_init(); + ch390_runtime_init(netif, config_get()->mac); } static err_t low_level_output(struct netif *netif, struct pbuf *p) { - struct pbuf *q; - uint32_t primask; - uint32_t start_tick; - - LWIP_UNUSED_ARG(netif); - primask = ethernetif_lock(); - -#if ETH_PAD_SIZE - pbuf_remove_header(p, ETH_PAD_SIZE); -#endif - - start_tick = HAL_GetTick(); - while (ch390_read_reg(CH390_TCR) & TCR_TXREQ) { - if ((HAL_GetTick() - start_tick) > 10u) { - ethernetif_unlock(primask); -#if ETH_PAD_SIZE - pbuf_add_header(p, ETH_PAD_SIZE); -#endif - LINK_STATS_INC(link.drop); - return ERR_TIMEOUT; - } - } - - for (q = p; q != NULL; q = q->next) { - ch390_write_mem(q->payload, q->len); - } - - ch390_write_reg(CH390_TXPLL, p->tot_len & 0xFFu); - ch390_write_reg(CH390_TXPLH, (p->tot_len >> 8) & 0xFFu); - ch390_send_request(); - ethernetif_unlock(primask); - -#if ETH_PAD_SIZE - pbuf_add_header(p, ETH_PAD_SIZE); -#endif - - LINK_STATS_INC(link.xmit); - return ERR_OK; + return ch390_runtime_output(netif, p); } static struct pbuf *low_level_input(struct netif *netif) { - struct ethernetif *ethernetif = netif->state; - struct pbuf *p = NULL; - struct pbuf *q; - uint16_t len; - uint8_t rx_ready; - uint8_t rx_header[4]; - uint32_t primask; - - primask = ethernetif_lock(); - ch390_read_reg(CH390_MRCMDX); - rx_ready = ch390_read_reg(CH390_MRCMDX); - - if (rx_ready & CH390_PKT_ERR) { - ch390_write_reg(CH390_RCR, 0u); - ch390_write_reg(CH390_MPTRCR, 0x01u); - ch390_write_reg(CH390_MRRH, 0x0Cu); - ch390_delay_us(1000u); - ch390_write_reg(CH390_RCR, RCR_RXEN | RCR_DIS_CRC); - ethernetif->rx_len = 0u; - LINK_STATS_INC(link.drop); - ethernetif_unlock(primask); - return NULL; - } - - if ((rx_ready & CH390_PKT_RDY) == 0u) { - ethernetif->rx_len = 0u; - ethernetif_unlock(primask); - return NULL; - } - - ch390_read_mem(rx_header, 4); - ethernetif->rx_status = rx_header[1]; - ethernetif->rx_len = (uint16_t)(((uint16_t)rx_header[2] | ((uint16_t)rx_header[3] << 8)) - 4u); - len = ethernetif->rx_len; - -#if ETH_PAD_SIZE - len += ETH_PAD_SIZE; -#endif - - p = pbuf_alloc(PBUF_RAW, len, PBUF_POOL); - if (p != NULL) { -#if ETH_PAD_SIZE - pbuf_remove_header(p, ETH_PAD_SIZE); -#endif - for (q = p; q != NULL; q = q->next) { - ch390_read_mem(q->payload, q->len); - } -#if ETH_PAD_SIZE - pbuf_add_header(p, ETH_PAD_SIZE); -#endif - ch390_drop_packet(4u); - LINK_STATS_INC(link.recv); - } else { - ch390_drop_packet((uint16_t)(ethernetif->rx_len + 4u)); - LINK_STATS_INC(link.memerr); - LINK_STATS_INC(link.drop); - } - - ethernetif_unlock(primask); - return p; + LWIP_UNUSED_ARG(netif); + return NULL; } void ethernetif_input(struct netif *netif) @@ -237,55 +114,12 @@ void lwip_netif_init(const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const void ethernetif_check_link(void) { - uint8_t link_up; - uint32_t primask = ethernetif_lock(); - - link_up = (uint8_t)ch390_get_link_status(); - ethernetif_unlock(primask); - - if (link_up) { - if (!netif_is_link_up(&ch390_netif)) { - netif_set_link_up(&ch390_netif); - } - } else if (netif_is_link_up(&ch390_netif)) { - netif_set_link_down(&ch390_netif); - } + ch390_runtime_check_link(&ch390_netif); } void ethernetif_poll(void) { - uint8_t int_status; - uint32_t primask; - - if (g_ch390_irq_pending == 0u) { - return; - } - - primask = ethernetif_lock(); - int_status = ch390_read_reg(CH390_ISR); - ch390_write_reg(CH390_ISR, int_status); - g_ch390_irq_pending = 0u; - ethernetif_unlock(primask); - - if ((int_status & ISR_LNKCHG) != 0u) { - ethernetif_check_link(); - } - - if ((int_status & ISR_ROS) != 0u) { - LINK_STATS_INC(link.err); - } - - if ((int_status & ISR_PR) != 0u) { - while (1) { - struct pbuf *p = low_level_input(&ch390_netif); - if (p == NULL) { - break; - } - if (ch390_netif.input(p, &ch390_netif) != ERR_OK) { - pbuf_free(p); - } - } - } + ch390_runtime_poll(&ch390_netif); } u32_t sys_now(void) diff --git a/Drivers/LwIP/src/netif/ethernetif.h b/Drivers/LwIP/src/netif/ethernetif.h index 701c018..8439d7a 100644 --- a/Drivers/LwIP/src/netif/ethernetif.h +++ b/Drivers/LwIP/src/netif/ethernetif.h @@ -8,6 +8,7 @@ #include "lwip/err.h" #include "lwip/netif.h" +#include "ch390_runtime.h" struct ethernetif { uint16_t rx_len; diff --git a/MDK-ARM/TCP2UART.uvprojx b/MDK-ARM/TCP2UART.uvprojx index 39646df..1ff5fe2 100644 --- a/MDK-ARM/TCP2UART.uvprojx +++ b/MDK-ARM/TCP2UART.uvprojx @@ -549,6 +549,11 @@ 1 ..\Drivers\CH390\CH390_Interface.c + + ch390_runtime.c + 1 + ..\Drivers\CH390\ch390_runtime.c + diff --git a/uart-ch390-debug-handoff.md b/uart-ch390-debug-handoff.md new file mode 100644 index 0000000..9fa67db --- /dev/null +++ b/uart-ch390-debug-handoff.md @@ -0,0 +1,512 @@ +# UART CH390 Debug Handoff + +## 2026-03-31 Config UART Test Session + +### Goal + +- Exhaustively test the `USART1` config command interface. +- Verify that Flash-backed parameters survive `AT+SAVE` plus reset. +- Record concrete bench procedure, failures, fixes, and evidence paths. + +### Bench Baseline + +- Workspace: `D:\code\STM32Project\TCP2UART` +- Target MCU: `STM32F103R8T6` +- Config UART: `USART1` on `PA9/PA10` +- Host visible COM ports during this session: `COM1`, `COM9` +- Debug probe visible during this session: `STLink V2` +- Flash parameter page: `0x0800FC00` +- Firmware image used for test bring-up: `MDK-ARM\TCP2UART\TCP2UART.axf` + +### Source-of-Truth Command Surface + +From `App/config.c`, the tested command surface is: + +- `AT` +- `AT+?` +- `AT+QUERY` +- `AT+SAVE` +- `AT+RESET` +- `AT+DEFAULT` +- `AT+IP=...` +- `AT+MASK=...` +- `AT+GW=...` +- `AT+RIP=...` +- `AT+MAC=...` +- `AT+PORT=...` +- `AT+RPORT=...` +- `AT+BAUD1=...` +- `AT+BAUD2=...` +- `AT+DHCP=0/1` + +### Test Procedure + +1. Flash the current `axf` image with `probe-rs download --chip STM32F103R8`. +2. Connect to the config UART at `115200 8N1`. +3. Run `python tools/uart_config_test.py --port COM9 --scenario inventory`. +4. Run `python tools/uart_config_test.py --port COM9 --scenario persistence`. +5. Read back Flash words at `0x0800FC00` and compare them before and after reset. +6. Save all transcripts into `artifacts/uart-config/`. + +### Important Expectations + +- Setter commands update RAM immediately and return `OK` plus the reboot hint. +- Persistence is not proven until the sequence `set -> query -> save -> reset -> query` passes. +- `AT+DEFAULT` only resets RAM state and still requires `AT+SAVE` to persist. +- `AT+DHCP=1` must fail in this build by design. + +### Session Findings + +- `probe-rs download` succeeded against `STM32F103R8`. +- `pyserial` had to be installed on the host before scripted UART testing. +- The requested handoff filename did not exist in the repo, so this file was created as the dedicated config-UART handoff log. +- Raw command transcripts and Flash comparisons should be attached from `artifacts/uart-config/` for any future regression analysis. + +### 2026-03-31 Live Test Evidence + +- Host-side strict inventory run on `COM9` failed with `non_empty_responses = 0`. +- Artifact: `artifacts/uart-config/inventory-20260331-185333.json` +- Direct host probe on `COM9` with `AT\r\n` returned `b''`. +- Direct host probe on `COM1` with `AT\r\n` also returned `b''`. +- Strict persistence run was intentionally not accepted as valid after the script was corrected, because all command responses were empty. +- Flash page `0x0800FC00` remained all `0xFFFFFFFF`, proving `AT+SAVE` never actually executed on target. + +### Board/Debugger Findings That Narrow The Fault + +- The target firmware is present in Flash and `probe-rs download` completes successfully. +- After a clean reset and short run window, the MCU executes from Flash and initializes clocks and `USART1` registers. +- `USART1` register block was observed in an initialized state after boot, not all-zero. +- Firmware was patched to explicitly start `HAL_UART_Receive_IT(&huart1, &g_uart1_rx_probe_byte, 1)` during `App_Init()`. +- Even after that fix, repeated `AT` frames from the host produced no visible UART response. +- Debug readout of software-side config RX state showed: + - `g_pending_cmd_ready = 0` + - `g_pending_cmd_len = 0` + - `g_uart_cmd_len = 0` + - `g_uart_cmd_buffer` remained zero-filled + - `g_pending_cmd_buffer` remained zero-filled +- This means the config parser never received any bytes from the live UART path during the test window. + +### Current Best Conclusion + +- The immediate blocker is no longer the parser itself. +- Current evidence points to a board-level `USART1_RX` path problem or wrong host wiring/port assumption, because the firmware is alive, `USART1` is initialized, but no command bytes enter `config_uart_rx_byte()`. +- Until the physical config-UART path is proven, it is not meaningful to claim that every config command or Flash persistence path has passed on real hardware. + +### Corrected Final Conclusion + +- The earlier "no response" conclusion was wrong because the host was sending `\r\n` terminated commands. +- This firmware expects the config command to be terminated by `\n` to complete the frame in the real bench setup. +- After switching the host sender to `AT...\n`, `COM9` immediately returned `OK\r\n` for `AT` and the complete config command set became testable. +- Therefore the key bench rule is: every config command must end with `\n`. + +### Final Verified Results + +- `AT` returns `OK`. +- `AT+?` and `AT+QUERY` return the full current configuration snapshot. +- Setter commands `AT+IP`, `AT+MASK`, `AT+GW`, `AT+RIP`, `AT+MAC`, `AT+PORT`, `AT+RPORT`, `AT+BAUD1`, `AT+BAUD2`, `AT+DHCP=0` all return `OK` plus the reboot hint. +- `AT+SAVE` returns `OK: Configuration saved`. +- `AT+RESET` returns `OK: Resetting...` and the board comes back responding to `AT`. +- `AT+DEFAULT` returns `OK: Defaults restored`. +- Negative cases were verified: + - `AT+UNKNOWN` -> `ERROR: Unknown command` + - `AT+PORT=0` / `AT+PORT=65536` -> `ERROR: Invalid port` + - `AT+BAUD1=1199` / `AT+BAUD1=921601` -> `ERROR: Invalid baudrate` + - `AT+DHCP=1` -> `ERROR: DHCP disabled in this build` + - `AT+IP=999.1.1.1` -> `ERROR: Invalid IP format` + - `AT+MAC=GG:11:22:33:44:55` -> `ERROR: Invalid MAC format` +- Non-AT input `BT` produced no response, which matches the parser gate. + +### Final Flash Persistence Evidence + +- Tested persisted values: + - `IP=192.168.1.123` + - `MASK=255.255.255.0` + - `GW=192.168.1.1` + - `RIP=192.168.1.201` + - `MAC=02:12:34:56:78:9A` + - `PORT=10001` + - `RPORT=10002` + - `BAUD1=57600` + - `BAUD2=38400` +- Sequence used: + 1. set values with `\n`-terminated AT commands + 2. query with `AT+?` + 3. `AT+SAVE` + 4. `AT+RESET` + 5. query again with `AT+?` + 6. read raw Flash words at `0x0800FC00` +- Query values before and after reset matched exactly. +- Raw Flash read before and after reset also matched exactly. +- Factory default restoration was also proven with `AT+DEFAULT -> AT+SAVE -> AT+RESET -> AT+?`. + +### Evidence Files + +- Inventory transcript: `artifacts/uart-config/inventory-20260331-185752.json` +- Inventory raw text: `artifacts/uart-config/inventory-20260331-185752.txt` +- Persistence transcript: `artifacts/uart-config/persistence-20260331-190039.json` +- Persistence raw text: `artifacts/uart-config/persistence-20260331-190039.txt` + +### Firmware Adjustment Made During This Session + +- Added explicit `HAL_UART_Receive_IT(&huart1, &g_uart1_rx_probe_byte, 1u)` arming in `App_Init()` so the `USART1` interrupt receive path is definitely started after boot. +- This is a safe, minimal bring-up fix and should remain in place. + +### Practical Lessons + +- Do not treat a script exit code alone as proof of UART success; require at least one non-empty response in the captured transcript. +- Do not treat `probe-rs read 0x0800FC00` returning all `0xFFFFFFFF` as a flash-driver failure until you first prove that `AT+SAVE` was actually accepted by the parser. +- In this project, the fastest truth test is: + 1. prove target is executing + 2. prove `USART1` is initialized + 3. prove bytes reach `config_uart_rx_byte` + 4. only then evaluate parser responses and flash persistence +- Most important bench lesson: if the config UART appears dead, first retry with commands ending in `\n` instead of `\r\n`. + +### Open Items + +- Confirm whether `COM9` is the real `USART1` config port by live command-response evidence. +- If command-response is unstable, inspect whether host wiring/USB-UART level shifting is the cause before changing parser logic. +- If persistence fails after a clean `AT+SAVE`, inspect `App/flash_param.c` and raw Flash contents at `0x0800FC00` before changing higher-level config logic. + +## 2026-03-31 CH390D Bring-up Debug Session + +### Goal + +- Determine why `CH390D` does not return valid register values during boot. +- Find a software-side root cause if one exists and attempt a minimal fix. + +### Baseline Symptom + +- MCU boots normally and RTT works. +- CH390 boot diagnostics originally reported: + +```text +TCP2UART boot +CH390 VID=0x0000 PID=0x0000 REV=0x00 NSR=0x00 LINK=0 +CH390 NCR=0x00 RCR=0x00 IMR=0x00 INTCR=0x00 GPR=0x00 ISR=0x00 +CH390 WRCHK NCR:0x00->0x00 INTCR:0x00->0x00 +``` + +- This showed that CH390 register reads and write-back checks were not producing valid values. + +### Board-Side Evidence Already Collected + +- `RST` line was observed released high. +- `CS` line idle state was high. +- `INT` line was observed low and mapped to EXTI. +- `SPI1` was enabled and configured for `Mode 3` in the active firmware. +- These observations did not by themselves restore valid CH390 responses. + +### What Was Tried + +1. Added richer RTT startup diagnostics in `BootDiag_ReportCh390()`. +2. Lowered `SPI1` speed from `/8` to `/64`. +3. Added stage markers around `low_level_init()` to localize the hang. +4. Step-debugged and breakpoint-debugged `ch390_default_config()` and `ch390_write_phy()`. +5. Added timeout protection to `ch390_read_phy()` / `ch390_write_phy()` so `EPCR` polling cannot hang forever. +6. Temporarily skipped `ch390_set_phy_mode(CH390_AUTO)` to isolate non-PHY register access. +7. Compared current driver against `Reference/EVT/EXAM/PUB/CH390.c` and `Reference/EVT/EXAM/PUB/CH390_Interface.c`. +8. Tried multiple SPI register transaction shapes: + - original two-byte exchange style + - split `Transmit` then read phase + - explicit dummy-byte read phase + - single-frame two-byte full-duplex read +9. Scanned all four SPI modes (`mode0`..`mode3`) during startup. +10. Added small `CS` setup/hold delays. +11. Increased hardware reset release wait to `50ms`. +12. Restored EVT-style init order and PHY setup path to see whether EVT sequence alone fixes the problem. + +### Key Intermediate Findings + +- Lowering SPI speed changed behavior, but did not recover valid CH390 IDs. +- Stage markers showed that low-speed SPI could stall during `ETH init: default`. +- Step/RTT evidence localized the original stall to PHY access during `ch390_default_config()`. +- The PHY access loop in `ch390_read_phy()` / `ch390_write_phy()` had no timeout and could hang indefinitely. This is a real software bug and should stay fixed. +- After adding PHY timeouts and temporarily skipping PHY setup, the init path completed, but all CH390 reads became `0xFF` rather than valid IDs. +- SPI mode scan result under that condition was: + +```text +CH390 SPI mode0 [FF FF FF FF FF] +CH390 SPI mode1 [FF FF FF FF FF] +CH390 SPI mode2 [FF FF FF FF FF] +CH390 SPI mode3 [FF FF FF FF FF] +``` + +- This ruled out a simple `CPOL/CPHA` mismatch. +- External code comparison did not reveal an `opcode` or register-address mismatch. Public CH390 implementations use the same `OPC_REG_R=0x00`, `OPC_REG_W=0x80`, and the same register map. +- One experimental split transaction path produced repeatable but obviously bogus values like `0x03`, `0xAC`, `0xAE`, which strongly suggests transaction artifacts rather than real CH390 data. +- A debug read of `SPI1->SR` showed `OVR=1` during one of the experimental transaction variants, indicating the SPI transaction layer was not trustworthy in that configuration. + +### EVT Comparison Outcome + +- `Reference/EVT` is useful as a baseline, but it is not a drop-in fix for this project. +- The broad init order in the live project already matches EVT closely through the lwIP glue path. +- The most important EVT-specific difference is that EVT performs `ch390_set_phy_mode(CH390_AUTO)` at the start of `ch390_default_config()`. +- Restoring the EVT-style `PHY` setup path in this project caused boot to hang again at: + +```text +TCP2UART boot +ETH init: gpio +ETH init: spi +ETH init: reset +ETH init: default +``` + +- That confirms the `PHY` path is a real trigger for the hang, but EVT order alone does not solve the underlying communication problem. + +### Current Best Technical Conclusion + +- A real software defect was found and fixed: `EPCR` polling in PHY access had no timeout. +- That fix prevents the firmware from hanging forever, but it does **not** restore valid CH390 register communication. +- The core unresolved problem remains: the SPI register-access path still does not yield believable CH390 register data. +- At this point, the following common explanations have already been tested and are **not** sufficient by themselves: + - SPI mode selection + - adding dummy bytes + - `CS` setup/hold delays + - changing reset wait from `10ms` to `50ms` + - reverting to EVT transaction style + - restoring EVT initialization order + - public `opcode` / register-map mismatch + +### Recommended Next Debug Step + +- The next high-value experiment is a temporary GPIO bit-bang read of `VIDL/VIDH/CHIPR` with a fully controlled continuous command+clock sequence. +- If bit-bang returns valid IDs while HAL-SPI paths do not, the remaining fault is in the SPI transaction implementation rather than CH390 higher-level init order. +- If bit-bang still returns invalid data, the investigation must move back to board-level bus behavior even if static continuity checks look correct. + +### Additional 2026-03-31 Finding: HAL SPI Re-init And Bit-Bang Side Effects + +- A valid concern was raised about calling `HAL_SPI_Init()` after temporarily changing SPI pins to GPIO mode. +- Code review of `stm32f1xx_hal_spi.c` showed that `HAL_SPI_MspInit()` only runs when `hspi->State == HAL_SPI_STATE_RESET`. +- Therefore, simply calling `HAL_SPI_Init()` after bit-bang mode does **not** automatically restore `PA5/PA7` to SPI alternate-function output mode. +- This was a real software-side risk in the temporary bit-bang probe and was corrected by explicitly restoring: + - `PA5` -> `AF_PP` + - `PA7` -> `AF_PP` + - `PA6` -> input + before calling `HAL_SPI_Init()` again. +- After that correction, the observed behavior changed again: boot output stopped at `ETH init: reset`, and a short halt showed execution inside `HAL_SPI_TransmitReceive()` called from the CH390 SPI exchange path. +- This means the earlier bit-bang experiments could have polluted later SPI results, but after the GPIO restore fix, the active blocker is again a live SPI transaction stall rather than a missing-GPIO-restore artifact. + +### Additional 2026-03-31 Finding: Reset Exists In Runtime Path + +- The project does **not** lack a CH390 reset process. +- The actual runtime order is: + 1. `App_Init()` + 2. `lwip_netif_init()` + 3. `ethernetif_init()` + 4. `low_level_init()` + 5. `ch390_gpio_init()` + 6. `ch390_spi_init()` + 7. `ch390_hardware_reset()` + 8. `ch390_default_config()` +- The reset process is therefore present and executed, but it lives in the lwIP/netif bring-up path instead of being written inline in `main.c` as in the EVT sample. +- The current unresolved problem is not "missing reset"; it is that SPI transactions after reset still do not produce valid CH390 register responses. + +## 2026-03-31 Manual Reset Sensitivity Analysis + +### Observed Symptom + +- An extra `ch390_hardware_reset()` was temporarily inserted into `App_Init()` before `lwip_init()`. +- With that extra reset in place, a manual board reset could lead to the firmware appearing stuck and the LED heartbeat not behaving normally. +- The same image could still look more normal when observed after a `probe-rs` flash-and-run cycle. + +### Code-Level Finding + +- The inserted extra reset sat here in `Core/Src/main.c`: + +```c +SEGGER_RTT_Init(); +SEGGER_RTT_WriteString(0, "\r\nTCP2UART boot\r\n"); +... +ch390_hardware_reset(); +lwip_init(); +lwip_netif_init(...); +``` + +- But the normal bring-up path already performs a reset later inside `ch390_runtime_init()` / `low_level_init()` before `ch390_default_config()`. +- That means the temporary line created a redundant early reset in a different initialization phase than the normal driver-owned reset. + +### Interpretation + +- This pattern is much more consistent with a reset-sequencing / startup-state issue than with compiler optimization level. +- The Keil target uses one fixed optimization configuration, so a plain manual reset does not change code generation. +- In contrast, an extra CH390 reset inserted before lwIP and before the normal CH390 runtime init can alter the device startup state and timing relationship between the MCU and CH390. + +### Action Taken + +- The extra `ch390_hardware_reset()` in `App_Init()` was removed. +- The firmware now relies only on the standard driver-owned reset inside the CH390 runtime initialization path. + +### Conclusion + +- The temporary extra reset was not kept. +- The strongest software-side conclusion is that the manual-reset sensitivity was caused by redundant reset sequencing rather than by optimization level. + +## 2026-03-31 HardFault Root Cause And Fix + +### Symptom + +- After CH390 bring-up completed and boot diagnostics printed, the firmware entered: + +```text +TRAP: HardFault_Handler +``` + +- At the same time, `PC13` stopped blinking, which originally looked like a timer or LED problem. + +### Fault Evidence + +- Fault-status registers showed a real fault rather than a normal busy wait. +- The trap location was `Debug_TrapWithRttHint()` in `Core/Src/main.c`. +- The stacked fault frame pointed back into the normal runtime path rather than the trap itself. +- `TIM4` was configured and had already advanced `g_led_blink_ticks`, so the LED path was alive before the fault. + +### Root Cause + +- `MX_IWDG_Init()` had been temporarily commented out in `main()`. +- However, `App_Poll()` still executed: + +```c +HAL_IWDG_Refresh(&hiwdg); +``` + +- Because `hiwdg` was never initialized, this call operated on an invalid handle and led to the observed fault path. + +### Fix Applied + +- `Core/Src/main.c` was changed so watchdog refresh only runs when `hiwdg.Instance == IWDG`. +- This preserves normal behavior when IWDG is enabled, while avoiding invalid access when IWDG init is intentionally disabled for debugging. + +### Verification + +- Rebuilt successfully with `0 error`, `1 warning`. +- Reflashed target and reran startup. +- Boot RTT still showed CH390 diagnostics, but no longer showed `TRAP: HardFault_Handler`. +- A 5-second runtime window completed without a new trap. +- `g_led_blink_ticks` continued advancing after the fix, confirming that `TIM4` interrupts and the LED heartbeat path were alive again. + +### Conclusion + +- The HardFault was caused by refreshing an uninitialized IWDG handle, not by the CH390 SPI path itself. +- This issue is fixed. +- CH390 bring-up is still unresolved at the register-communication level, but the main task is again able to continue running normally. + +## 2026-03-31 Runtime Freeze Root Cause And Fix + +### Symptom + +- After re-soldering CH390D, the system could boot and print the normal CH390 startup diagnostics. +- However, after running for a while, the device would appear to freeze. +- In that state, the LED heartbeat behavior became unreliable and the system appeared to stop making useful progress. + +### Key Runtime Evidence + +- The new freeze was **not** another HardFault: no new `TRAP:` line appeared during the freeze window. +- `g_led_blink_ticks` continued advancing during observation windows, proving that `TIM4` interrupts were still alive and the MCU was not fully dead. +- A short halt during the bad behavior repeatedly landed in `HAL_SPI_TransmitReceive()`. +- Code inspection showed that CH390 runtime paths in `ethernetif.c` were executing blocking SPI transactions while global interrupts were disabled via `ethernetif_lock()`. + +### Root Cause + +- `low_level_output()`, `low_level_input()`, and `ethernetif_check_link()` in `Drivers/LwIP/src/netif/ethernetif.c` wrapped CH390 SPI register/memory accesses inside `ethernetif_lock()` / `ethernetif_unlock()`. +- Those helpers globally disable interrupts by manipulating `PRIMASK`. +- The CH390 access path uses blocking HAL SPI functions and timeout logic based on `HAL_GetTick()`. +- Running those blocking accesses with interrupts disabled can stall or livelock the runtime path, especially after startup when network polling begins. + +### Fix Applied + +- Reduced the interrupt-masked critical sections in `ethernetif.c` to only protect the shared IRQ-pending flag. +- Removed `ethernetif_lock()` coverage from the long CH390 SPI transaction paths in: + - `low_level_output()` + - `low_level_input()` + - `ethernetif_check_link()` +- In `ethernetif_poll()`, only the `g_ch390_irq_pending` flag is now cleared under the short critical section; the actual CH390 register access happens with interrupts enabled. + +### Verification + +- Rebuilt successfully with `0 error`, `0 warning`. +- Reflashed and reran the target. +- Boot RTT still completed normally through: + +```text +TCP2UART boot +ETH init: gpio +ETH init: spi +ETH init: reset +ETH init: default +ETH init: mac +ETH init: getmac +ETH init: irq +ETH init: done +CH390 VID=0x0000 PID=0x0000 REV=0x00 NSR=0x00 LINK=0 +CH390 NCR=0x00 RCR=0x00 IMR=0x00 INTCR=0x00 GPR=0x00 ISR=0x00 +``` + +- No new `TRAP:` message appeared during extended runtime observation. +- `g_led_blink_ticks` continued advancing over multiple samples, indicating that the heartbeat timer and interrupt delivery remained active. +- The system no longer reproduced the earlier “runs for a while then appears frozen” behavior in the observed validation window. + +### Conclusion + +- This freeze was caused by doing blocking CH390 SPI operations inside a global interrupt-disabled critical section. +- The runtime freeze is fixed. +- CH390 register communication is still invalid (`0x0000` ID values), but that is now a separate communication/bring-up problem rather than the cause of the observed runtime stall. + +## 2026-03-31 SPI Ownership Decoupling And CH390 Current Status + +### Why This Refactor Was Done + +- The project previously allowed multiple runtime layers to reach down into CH390/SPI behavior directly: + - `ethernetif.c` handled init, IRQ-driven poll service, RX/TX transactions, and link checks + - `main.c` directly read CH390 registers for boot diagnostics + - the CH390 low-level SPI transport sat underneath those callers with no single runtime owner boundary +- This made the system harder to reason about and contributed to runtime instability when CH390 accesses happened from different code paths with different assumptions. + +### Refactor Outcome + +- Added a single runtime owner module: `Drivers/CH390/ch390_runtime.c` + `Drivers/CH390/ch390_runtime.h`. +- After this change: + - `CH390_Interface.c` remains the **only** SPI transport implementation + - `CH390.c` remains the chip-level helper layer + - `ch390_runtime.c` is now the **only runtime owner** of CH390 transactions after boot + - `ethernetif.c` delegates runtime TX/RX/link/IRQ servicing to `ch390_runtime` + - `main.c` no longer performs direct CH390 register reads; boot diagnostics use `ch390_runtime_get_diag()` + - `EXTI0_IRQHandler()` only posts the IRQ-pending event into the runtime owner and does not touch CH390 directly + +### Behavior After Refactor + +- Build passed with `0 error`, `0 warning`. +- The system remained stable in the post-refactor runtime window: + - no new trap output + - heartbeat/timer activity continued + - previous runtime freeze did not reproduce in the observed window + +### CH390 Result After Refactor + +- The CH390 did **not** come up successfully. +- However, the failure signature became cleaner and more trustworthy: + +```text +CH390 VID=0xFFFF PID=0xFFFF REV=0xFF NSR=0xFF LINK=1 +CH390 NCR=0xFF RCR=0xFF IMR=0xFF INTCR=0xFF GPR=0xFF ISR=0xFF +``` + +- This is materially different from the earlier unstable mixture of: + - all-zero reads + - intermittent hangs + - transaction artifacts + - watchdog-related HardFaults + +### Trusted Interpretation Of Current Failure + +- With the SPI access model cleaned up and the system remaining stable, the current CH390 failure can now be treated as a **credible transport-level non-response** rather than a concurrency artifact. +- A uniform `0xFF` readback across identity and status/control registers strongly suggests one of these conditions: + - CH390 still does not actively drive MISO during the register-read phase + - CS reaches the MCU logic but is not effectively selecting the CH390 device on the board side + - the CH390 digital core is not entering a valid SPI-responding state after reset even though the MCU-side sequence now looks consistent + +### Practical Conclusion + +- The architectural decoupling requirement is complete. +- The runtime stability requirement is complete. +- CH390 connection is **still failed**, but the reason is now narrowed to a believable low-level bus/device-response problem rather than a software ownership/concurrency problem.