#include "tcp_debug.h" #include "pico/cyw43_arch.h" #include "lwip/tcp.h" #include "pico/stdio/driver.h" #include "pico/sync.h" #include #include #define TCP_PORT 4242 #define DEBUG_BUF_SIZE 2048 #define TCP_IN_BUF_SIZE 20000 // Increased to hold full image + commands // Circular buffer for debug output static char debug_buf[DEBUG_BUF_SIZE]; static volatile int write_idx = 0; static volatile int read_idx = 0; // Circular buffer for TCP input static char tcp_in_buf[TCP_IN_BUF_SIZE]; static volatile int in_write_idx = 0; static volatile int in_read_idx = 0; static critical_section_t tcp_in_crit; static struct tcp_pcb *server_pcb = NULL; static struct tcp_pcb *client_pcb = NULL; // Helper to write to buffer static void buffer_write(const char *data, int len) { for (int i = 0; i < len; i++) { int next_write = (write_idx + 1) % DEBUG_BUF_SIZE; if (next_write != read_idx) { // Only write if not full debug_buf[write_idx] = data[i]; write_idx = next_write; } } } // stdio driver callback static void tcp_out_chars(const char *buf, int len) { // Handle CRLF conversion manually if needed, or just write raw // The issue is likely that \n is sent without \r, causing "staircase" effect in raw terminals for (int i = 0; i < len; i++) { if (buf[i] == '\n') { char cr = '\r'; buffer_write(&cr, 1); } buffer_write(&buf[i], 1); } } static int tcp_in_chars(char *buf, int len) { int count = 0; critical_section_enter_blocking(&tcp_in_crit); while (count < len && in_read_idx != in_write_idx) { buf[count++] = tcp_in_buf[in_read_idx]; in_read_idx = (in_read_idx + 1) % TCP_IN_BUF_SIZE; } critical_section_exit(&tcp_in_crit); return count > 0 ? count : PICO_ERROR_NO_DATA; } static stdio_driver_t tcp_driver = { .out_chars = tcp_out_chars, .out_flush = NULL, .in_chars = tcp_in_chars, #if PICO_STDIO_ENABLE_CRLF_SUPPORT .crlf_enabled = false #endif }; // TCP callbacks static err_t tcp_server_poll(void *arg, struct tcp_pcb *tpcb) { if (tpcb != client_pcb) return ERR_OK; // Check if there is data in the ring buffer to send if (write_idx != read_idx) { int len_to_send = 0; char send_buf[256]; // Temporary buffer for one TCP write chunk // Copy from ring buffer to linear buffer while (write_idx != read_idx && len_to_send < sizeof(send_buf)) { send_buf[len_to_send++] = debug_buf[read_idx]; read_idx = (read_idx + 1) % DEBUG_BUF_SIZE; } if (len_to_send > 0) { err_t err = tcp_write(tpcb, send_buf, len_to_send, TCP_WRITE_FLAG_COPY); if (err == ERR_OK) { tcp_output(tpcb); } else { // If write failed (e.g. buffer full), rewind read_idx (simple retry logic) // Note: This is a simplification. In a real robust system we might drop data or handle partial writes. // For now, we just lose the data if TCP is full to avoid blocking the system. } } } return ERR_OK; } static tcp_connection_callback_t g_conn_callback = NULL; void tcp_set_connection_callback(tcp_connection_callback_t callback) { g_conn_callback = callback; } static err_t tcp_server_recv(void *arg, struct tcp_pcb *tpcb, struct pbuf *p, err_t err) { if (!p) { tcp_close(tpcb); client_pcb = NULL; printf("TCP debug client disconnected\n"); if (g_conn_callback) g_conn_callback(false); return ERR_OK; } // Process received data into input buffer if (p->tot_len > 0) { critical_section_enter_blocking(&tcp_in_crit); struct pbuf *q = p; while (q != NULL) { char *data = (char *)q->payload; for (int i = 0; i < q->len; i++) { int next_write = (in_write_idx + 1) % TCP_IN_BUF_SIZE; if (next_write != in_read_idx) { tcp_in_buf[in_write_idx] = data[i]; in_write_idx = next_write; } } q = q->next; } critical_section_exit(&tcp_in_crit); tcp_recved(tpcb, p->tot_len); } pbuf_free(p); return ERR_OK; } static err_t tcp_server_accept(void *arg, struct tcp_pcb *newpcb, err_t err) { if (client_pcb) { // Already have a client, reject or close old? // Let's close the old one and accept the new one (last one wins) tcp_abort(client_pcb); if (g_conn_callback) g_conn_callback(false); } client_pcb = newpcb; tcp_recv(newpcb, tcp_server_recv); tcp_poll(newpcb, tcp_server_poll, 1); // Poll every 500ms (coarse grain) -> actually arg is interval in 500ms steps? // Wait, poll interval is passed to tcp_poll. 1 means every 500ms. // We might want faster polling for debug output. // But we can't poll faster than the lwIP timer. // However, we can also trigger writes from the main loop if we wanted, but polling is safer for threading. printf("TCP debug client connected\n"); if (g_conn_callback) g_conn_callback(true); return ERR_OK; } bool tcp_debug_init(void) { if (server_pcb) return true; critical_section_init(&tcp_in_crit); // Register stdio driver stdio_set_driver_enabled(&tcp_driver, true); cyw43_arch_lwip_begin(); server_pcb = tcp_new_ip_type(IPADDR_TYPE_ANY); if (!server_pcb) { cyw43_arch_lwip_end(); return false; } err_t err = tcp_bind(server_pcb, IP_ANY_TYPE, TCP_PORT); if (err != ERR_OK) { cyw43_arch_lwip_end(); return false; } server_pcb = tcp_listen(server_pcb); tcp_accept(server_pcb, tcp_server_accept); cyw43_arch_lwip_end(); printf("TCP debug server listening on port %d\n", TCP_PORT); return true; }