From 176da0f21716fa8b92f0e4bdb96240105cfee515 Mon Sep 17 00:00:00 2001 From: Bernd Busse Date: Fri, 5 May 2023 00:17:11 +0200 Subject: [PATCH] Workaround for hang when receiving data while waiting for transmission --- src/EthernetUdp.cpp | 15 ++++++++++++++- src/socket.cpp | 18 ++++++++++++++++++ src/utility/w5100.cpp | 32 +++++++++++++++++++++++++++++++- src/utility/w5100.h | 15 +++++++++++++-- 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/EthernetUdp.cpp b/src/EthernetUdp.cpp index e28791f6..5fe810f2 100644 --- a/src/EthernetUdp.cpp +++ b/src/EthernetUdp.cpp @@ -78,9 +78,22 @@ int EthernetUDP::beginPacket(IPAddress ip, uint16_t port) return Ethernet.socketStartUDP(sockindex, rawIPAddress(ip), port); } +// Variable defined and set in socket.cpp +extern uint8_t udp_send_error; + int EthernetUDP::endPacket() { - return Ethernet.socketSendUDP(sockindex); + bool result = Ethernet.socketSendUDP(sockindex); + if (!result && udp_send_error) { + // XXX While waiting for SEND_OK or TIMEOUT, a RECV is returned because something + // arrived while trying to send — and SEND_OK or TIMEOUT is never received. + // After this happens a few times, the W5100 will lock up. Try to compensate by + // closing, reseting, and calling begin() again when this happens. + stop(); + W5100.reset(); + begin(_port); + } + return result; } size_t EthernetUDP::write(uint8_t byte) diff --git a/src/socket.cpp b/src/socket.cpp index 7dc83feb..bd3aad33 100644 --- a/src/socket.cpp +++ b/src/socket.cpp @@ -509,11 +509,18 @@ bool EthernetClass::socketStartUDP(uint8_t s, uint8_t* addr, uint16_t port) return true; } +// Keep track of UDP send failures when loosing SEND_OK response. +uint8_t udp_send_error = 0; + bool EthernetClass::socketSendUDP(uint8_t s) { + udp_send_error = 0; SPI.beginTransaction(SPI_ETHERNET_SETTINGS); W5100.execCmdSn(s, Sock_SEND); + uint16_t timeoutMax = W5100.getRetransmissionTimeoutMs() + 1; + uint16_t start = millis(); + /* +2008.01 bj */ while ( (W5100.readSnIR(s) & SnIR::SEND_OK) != SnIR::SEND_OK ) { if (W5100.readSnIR(s) & SnIR::TIMEOUT) { @@ -523,6 +530,17 @@ bool EthernetClass::socketSendUDP(uint8_t s) //Serial.printf("sendUDP timeout\n"); return false; } + + // XXX While waiting for SEND_OK or TIMEOUT, a RECV is returned because something + // arrived while trying to send — and SEND_OK or TIMEOUT is never received. + // Forcefully break the loop after the expected timeout period has passed. + if (millis() - start > timeoutMax) { + udp_send_error = 1; + W5100.writeSnIR(s, SnIR::SEND_OK); + SPI.endTransaction(); + return false; + } + SPI.endTransaction(); yield(); SPI.beginTransaction(SPI_ETHERNET_SETTINGS); diff --git a/src/utility/w5100.cpp b/src/utility/w5100.cpp index 8b7c3f6c..79fae1ff 100644 --- a/src/utility/w5100.cpp +++ b/src/utility/w5100.cpp @@ -53,6 +53,7 @@ uint8_t W5100Class::chip = 0; uint8_t W5100Class::CH_BASE_MSB; uint8_t W5100Class::ss_pin = SS_PIN_DEFAULT; +bool W5100Class::initialized = false; #ifdef ETHERNET_LARGE_BUFFERS uint16_t W5100Class::SSIZE = 2048; uint16_t W5100Class::SMASK = 0x07FF; @@ -82,10 +83,39 @@ W5100Class W5100; uint32_t W5100Class::ss_pin_mask; #endif +uint8_t W5100Class::reset(void) +{ + // Get current configuration + uint8_t gw[4], sn[4], mac[8], ip[4]; + W5100.getGatewayIp(gw); + W5100.getSubnetMask(sn); + W5100.getMACAddress(mac); + W5100.getIPAddress(ip); + + uint16_t rtr = W5100.getRetransmissionTime(); + uint8_t rcr = W5100.getRetransmissionCount(); + + // Clear the init flag, perform a soft-reset, and init() again + initialized = false; + softReset(); + bool success = init(); + + // Re-apply the previous configuration + if (success) { + W5100.setGatewayIp(gw); + W5100.setSubnetMask(sn); + W5100.setMACAddress(mac); + W5100.setIPAddress(ip); + + W5100.setRetransmissionTime(rtr); + W5100.setRetransmissionCount(rcr); + } + + return success; +} uint8_t W5100Class::init(void) { - static bool initialized = false; uint8_t i; if (initialized) return 1; diff --git a/src/utility/w5100.h b/src/utility/w5100.h index f49d5b35..11f3d67d 100644 --- a/src/utility/w5100.h +++ b/src/utility/w5100.h @@ -128,6 +128,7 @@ enum W5100Linkstatus { class W5100Class { public: + static uint8_t reset(void); static uint8_t init(void); inline void setGatewayIp(const uint8_t * addr) { writeGAR(addr); } @@ -142,11 +143,20 @@ class W5100Class { inline void setIPAddress(const uint8_t * addr) { writeSIPR(addr); } inline void getIPAddress(uint8_t * addr) { readSIPR(addr); } - inline void setRetransmissionTime(uint16_t timeout) { if (chip == 55) writeRTR_W5500(timeout); else writeRTR(timeout); } - inline void setRetransmissionCount(uint8_t retry) { if (chip == 55) writeRCR_W5500(retry); else writeRCR(retry); } + inline void setRetransmissionTime(uint16_t timeout) { retransmissionTime = timeout; if (chip == 55) writeRTR_W5500(timeout); else writeRTR(timeout); } + inline uint16_t getRetransmissionTime(void) { if (chip == 55) return readRTR_W5500(); else return readRTR(); } + + inline void setRetransmissionCount(uint8_t retry) { retransmissionCount = retry; if (chip == 55) writeRCR_W5500(retry); else writeRCR(retry); } + inline uint8_t getRetransmissionCount(void) { if (chip == 55) return readRCR_W5500(); else return readRCR(); } static void execCmdSn(SOCKET s, SockCMD _cmd); +private: + uint16_t retransmissionTime = 2000; + uint8_t retransmissionCount = 8; + +public: + inline uint16_t getRetransmissionTimeoutMs() { return retransmissionTime / 10 * (retransmissionCount + 1); } // W5100 Registers // --------------- @@ -321,6 +331,7 @@ class W5100Class { private: static uint8_t chip; static uint8_t ss_pin; + static bool initialized; static uint8_t softReset(void); static uint8_t isW5100(void); static uint8_t isW5200(void);