From e147314f97ce929cade4c678b23484ba082587bc Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Mon, 28 Dec 2015 12:17:18 -0500 Subject: [PATCH 01/20] Re-enable interrupts before directly enqueuing characters in the UART FIFO. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not sure why, but this reduces the occurrence rate of an occasional ~3.25 or ~7μs intercharacter delay, which was interfering with use of the UART to generate precise timing pulses (such as driving WS2812 LEDs). --- cores/esp8266/HardwareSerial.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cores/esp8266/HardwareSerial.cpp b/cores/esp8266/HardwareSerial.cpp index 3527b4a86..0cddc2ad2 100644 --- a/cores/esp8266/HardwareSerial.cpp +++ b/cores/esp8266/HardwareSerial.cpp @@ -627,12 +627,13 @@ size_t HardwareSerial::write(uint8_t c) { return 0; _written = true; + bool tx_now = false; while(true) { { InterruptLock il; if(_tx_buffer->empty()) { if(uart_get_tx_fifo_room(_uart) > 0) { - uart_transmit_char(_uart, c); + tx_now = true; } else { _tx_buffer->write(c); uart_arm_tx_interrupt(_uart); @@ -644,6 +645,9 @@ size_t HardwareSerial::write(uint8_t c) { } yield(); } + if (tx_now) { + uart_transmit_char(_uart, c); + } return 1; } From e83dd4d2415f153dc9b06ed8ec0bf4555bcd2aa5 Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Mon, 28 Dec 2015 17:53:10 -0500 Subject: [PATCH 02/20] Use lightweight tests when we only care if the buffer is empty or full. --- cores/esp8266/cbuf.h | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/cores/esp8266/cbuf.h b/cores/esp8266/cbuf.h index 02b612c5c..4b5157e1a 100644 --- a/cores/esp8266/cbuf.h +++ b/cores/esp8266/cbuf.h @@ -45,18 +45,22 @@ class cbuf { return _begin - _end - 1; } - bool empty() const { + inline bool empty() const { return _begin == _end; } + inline bool full() const { + return wrap_if_bufend(_end + 1) == _begin; + } + int peek() { - if(_end == _begin) return -1; + if(empty()) return -1; return static_cast(*_begin); } int read() { - if(getSize() == 0) return -1; + if(empty()) return -1; char result = *_begin; _begin = wrap_if_bufend(_begin + 1); @@ -80,7 +84,7 @@ class cbuf { } size_t write(char c) { - if(room() == 0) return 0; + if(full()) return 0; *_end = c; _end = wrap_if_bufend(_end + 1); @@ -109,7 +113,7 @@ class cbuf { } private: - inline char* wrap_if_bufend(char* ptr) { + inline char* wrap_if_bufend(char* ptr) const { return (ptr == _bufend) ? _buf : ptr; } From 83398f60119bec102ecc7823b2a367106cddd501 Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Mon, 28 Dec 2015 16:21:22 -0500 Subject: [PATCH 03/20] Don't bother testing isRxEnabled() on UART1 - it is always false. --- cores/esp8266/HardwareSerial.cpp | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/cores/esp8266/HardwareSerial.cpp b/cores/esp8266/HardwareSerial.cpp index 0cddc2ad2..800d021fe 100644 --- a/cores/esp8266/HardwareSerial.cpp +++ b/cores/esp8266/HardwareSerial.cpp @@ -133,13 +133,7 @@ void ICACHE_RAM_ATTR uart_interrupt_handler(uart_t* uart) { } // -------------- UART 1 -------------- - - if(Serial1.isRxEnabled()) { - while(U1IS & (1 << UIFF)) { - Serial1._rx_complete_irq((char) (U1F & 0xff)); - U1IC = (1 << UIFF); - } - } + // Note: only TX is supported on UART 1. if(Serial1.isTxEnabled()) { if(U1IS & (1 << UIFE)) { U1IC = (1 << UIFE); @@ -294,6 +288,7 @@ uart_t* uart_init(int uart_nr, int baudrate, byte config, byte mode) { IOSWAP &= ~(1 << IOSWAPU0); break; case UART1: + // Note: uart_interrupt_handler does not support RX on UART 1. uart->rxEnabled = false; uart->txEnabled = (mode != SERIAL_RX_ONLY); uart->rxPin = 255; From cfe7ae1118f817f42c020464e2444e51e37da559 Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Mon, 28 Dec 2015 18:35:27 -0500 Subject: [PATCH 04/20] Put HardwareSerial and cbuf methods called from interrupt context in RAM. This is required per the non-OS SDK doc, which states: "Using non-OS SDK, please do not call any function defined with ICACHE_FLASH_ATTR in the interrupt handler." This avoids an "Illegal instruction" exception with epc1 pointing at a valid instruction (in flash) for one of the moved methods. --- cores/esp8266/HardwareSerial.cpp | 40 ++++++++++++++++----------- cores/esp8266/cbuf.cpp | 46 ++++++++++++++++++++++++++++++++ cores/esp8266/cbuf.h | 25 +++++------------ 3 files changed, 77 insertions(+), 34 deletions(-) create mode 100644 cores/esp8266/cbuf.cpp diff --git a/cores/esp8266/HardwareSerial.cpp b/cores/esp8266/HardwareSerial.cpp index 800d021fe..16094aa66 100644 --- a/cores/esp8266/HardwareSerial.cpp +++ b/cores/esp8266/HardwareSerial.cpp @@ -116,6 +116,13 @@ int uart_get_debug(); // #################################################################################################### // #################################################################################################### +// These function internals can be used from interrupt handlers to ensure they +// are in instruction RAM, or anywhere that the uart_nr has been validated. +#define UART_GET_TX_FIFO_ROOM(uart_nr) (UART_TX_FIFO_SIZE - ((USS(uart_nr) >> USTXC) & 0xff)) +#define UART_TRANSMIT_CHAR(uart_nr, c) do { USF(uart_nr) = (c); } while(0) +#define UART_ARM_TX_INTERRUPT(uart_nr) do { USIE(uart_nr) |= (1 << UIFE); } while(0) +#define UART_DISARM_TX_INTERRUPT(uart_nr) do { USIE(uart_nr) &= ~(1 << UIFE); } while(0) + void ICACHE_RAM_ATTR uart_interrupt_handler(uart_t* uart) { // -------------- UART 0 -------------- @@ -160,7 +167,7 @@ size_t uart_get_tx_fifo_room(uart_t* uart) { if(uart == 0) return 0; if(uart->txEnabled) { - return UART_TX_FIFO_SIZE - ((USS(uart->uart_nr) >> USTXC) & 0xff); + return UART_GET_TX_FIFO_ROOM(uart->uart_nr); } return 0; } @@ -177,7 +184,7 @@ void uart_transmit_char(uart_t* uart, char c) { if(uart == 0) return; if(uart->txEnabled) { - USF(uart->uart_nr) = c; + UART_TRANSMIT_CHAR(uart->uart_nr, c); } } @@ -241,7 +248,7 @@ void uart_arm_tx_interrupt(uart_t* uart) { if(uart == 0) return; if(uart->txEnabled) { - USIE(uart->uart_nr) |= (1 << UIFE); + UART_ARM_TX_INTERRUPT(uart->uart_nr); } } @@ -249,7 +256,7 @@ void uart_disarm_tx_interrupt(uart_t* uart) { if(uart == 0) return; if(uart->txEnabled) { - USIE(uart->uart_nr) &= ~(1 << UIFE); + UART_DISARM_TX_INTERRUPT(uart->uart_nr); } } @@ -536,13 +543,13 @@ void HardwareSerial::setDebugOutput(bool en) { } } -bool HardwareSerial::isTxEnabled(void) { +bool ICACHE_RAM_ATTR HardwareSerial::isTxEnabled(void) { if(_uart == 0) return false; return _uart->txEnabled; } -bool HardwareSerial::isRxEnabled(void) { +bool ICACHE_RAM_ATTR HardwareSerial::isRxEnabled(void) { if(_uart == 0) return false; return _uart->rxEnabled; @@ -604,11 +611,12 @@ void HardwareSerial::flush() { if(!_written) return; + const int uart_nr = _uart->uart_nr; while(true) { { InterruptLock il; if(_tx_buffer->getSize() == 0 && - uart_get_tx_fifo_room(_uart) >= UART_TX_FIFO_SIZE) { + UART_GET_TX_FIFO_ROOM(uart_nr) >= UART_TX_FIFO_SIZE) { break; } } @@ -623,15 +631,16 @@ size_t HardwareSerial::write(uint8_t c) { _written = true; bool tx_now = false; + const int uart_nr = _uart->uart_nr; while(true) { { InterruptLock il; if(_tx_buffer->empty()) { - if(uart_get_tx_fifo_room(_uart) > 0) { + if(UART_GET_TX_FIFO_ROOM(uart_nr) > 0) { tx_now = true; } else { _tx_buffer->write(c); - uart_arm_tx_interrupt(_uart); + UART_ARM_TX_INTERRUPT(uart_nr); } break; } else if(_tx_buffer->write(c)) { @@ -641,7 +650,7 @@ size_t HardwareSerial::write(uint8_t c) { yield(); } if (tx_now) { - uart_transmit_char(_uart, c); + UART_TRANSMIT_CHAR(uart_nr, c); } return 1; } @@ -650,26 +659,27 @@ HardwareSerial::operator bool() const { return _uart != 0; } -void HardwareSerial::_rx_complete_irq(char c) { +void ICACHE_RAM_ATTR HardwareSerial::_rx_complete_irq(char c) { if(_rx_buffer) { _rx_buffer->write(c); } } -void HardwareSerial::_tx_empty_irq(void) { +void ICACHE_RAM_ATTR HardwareSerial::_tx_empty_irq(void) { if(_uart == 0) return; if(_tx_buffer == 0) return; + const int uart_nr = _uart->uart_nr; size_t queued = _tx_buffer->getSize(); if(!queued) { - uart_disarm_tx_interrupt(_uart); + UART_DISARM_TX_INTERRUPT(uart_nr); return; } - size_t room = uart_get_tx_fifo_room(_uart); + size_t room = UART_GET_TX_FIFO_ROOM(uart_nr); int n = static_cast((queued < room) ? queued : room); while(n--) { - uart_transmit_char(_uart, _tx_buffer->read()); + UART_TRANSMIT_CHAR(uart_nr, _tx_buffer->read()); } } diff --git a/cores/esp8266/cbuf.cpp b/cores/esp8266/cbuf.cpp new file mode 100644 index 000000000..963ac638f --- /dev/null +++ b/cores/esp8266/cbuf.cpp @@ -0,0 +1,46 @@ +/* + cbuf.cpp - Circular buffer implementation + Copyright (c) 2014 Ivan Grokhotkov. All rights reserved. + This file is part of the esp8266 core for Arduino environment. + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "cbuf.h" +#include "c_types.h" + +size_t ICACHE_RAM_ATTR cbuf::getSize() const { + if(_end >= _begin) { + return _end - _begin; + } + return _size - (_begin - _end); +} + +int ICACHE_RAM_ATTR cbuf::read() { + if(empty()) return -1; + + char result = *_begin; + _begin = wrap_if_bufend(_begin + 1); + return result; + return static_cast(result); +} + +size_t ICACHE_RAM_ATTR cbuf::write(char c) { + if(full()) return 0; + + *_end = c; + _end = wrap_if_bufend(_end + 1); + return 1; +} diff --git a/cores/esp8266/cbuf.h b/cores/esp8266/cbuf.h index 4b5157e1a..46f886c31 100644 --- a/cores/esp8266/cbuf.h +++ b/cores/esp8266/cbuf.h @@ -21,7 +21,10 @@ #ifndef __cbuf_h #define __cbuf_h +#include #include +#include + class cbuf { public: cbuf(size_t size) : @@ -32,11 +35,7 @@ class cbuf { delete[] _buf; } - size_t getSize() const { - if(_end >= _begin) return _end - _begin; - - return _size - (_begin - _end); - } + size_t getSize() const; size_t room() const { if(_end >= _begin) { @@ -59,13 +58,7 @@ class cbuf { return static_cast(*_begin); } - int read() { - if(empty()) return -1; - - char result = *_begin; - _begin = wrap_if_bufend(_begin + 1); - return static_cast(result); - } + int read(); size_t read(char* dst, size_t size) { size_t bytes_available = getSize(); @@ -83,13 +76,7 @@ class cbuf { return size_read; } - size_t write(char c) { - if(full()) return 0; - - *_end = c; - _end = wrap_if_bufend(_end + 1); - return 1; - } + size_t write(char c); size_t write(const char* src, size_t size) { size_t bytes_available = room(); From c8772cfcd04630d8c2457096ab6d1f137db7a141 Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Mon, 28 Dec 2015 19:11:12 -0500 Subject: [PATCH 05/20] Make HardwareSerial::begin() and end() interrupt safe and disable TX/RX if we can't allocate a buffer for them. Prior to this change, the interrupt could fire during initialisation, necessitating a deep check that the HardwareSerial structure had valid _tx_buffer or _rx_buffer each time an interrupt occurred. By keeping uart_t's and HardwareSerial's (txEnabled, _tx_buffer) and (rxEnabled, _rx_buffer) in sync, we can remove this extra check, as well as fixing a null pointer dereference if e.g. _tx_buffer allocation failed and write() was subsequently called. --- cores/esp8266/HardwareSerial.cpp | 50 ++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/cores/esp8266/HardwareSerial.cpp b/cores/esp8266/HardwareSerial.cpp index 16094aa66..3fd5af48b 100644 --- a/cores/esp8266/HardwareSerial.cpp +++ b/cores/esp8266/HardwareSerial.cpp @@ -101,7 +101,8 @@ void uart_disarm_tx_interrupt(uart_t* uart); void uart_set_baudrate(uart_t* uart, int baud_rate); int uart_get_baudrate(uart_t* uart); -uart_t* uart_init(int uart_nr, int baudrate, byte config); +uart_t* uart_start_init(int uart_nr, int baudrate, byte config); +void uart_finish_init(uart_t* uart); void uart_uninit(uart_t* uart); void uart_swap(uart_t* uart); @@ -273,9 +274,8 @@ int uart_get_baudrate(uart_t* uart) { return uart->baud_rate; } -uart_t* uart_init(int uart_nr, int baudrate, byte config, byte mode) { +uart_t* uart_start_init(int uart_nr, int baudrate, byte config, byte mode) { - uint32_t conf1 = 0x00000000; uart_t* uart = (uart_t*) os_malloc(sizeof(uart_t)); if(uart == 0) { @@ -311,6 +311,12 @@ uart_t* uart_init(int uart_nr, int baudrate, byte config, byte mode) { uart_set_baudrate(uart, baudrate); USC0(uart->uart_nr) = config; + return uart; +} + +void uart_finish_init(uart_t* uart) { + uint32_t conf1 = 0x00000000; + uart_flush(uart); uart_interrupt_enable(uart); @@ -323,8 +329,6 @@ uart_t* uart_init(int uart_nr, int baudrate, byte config, byte mode) { } USC1(uart->uart_nr) = conf1; - - return uart; } void uart_uninit(uart_t* uart) { @@ -485,31 +489,45 @@ HardwareSerial::HardwareSerial(int uart_nr) : } void HardwareSerial::begin(unsigned long baud, byte config, byte mode) { + InterruptLock il; // disable debug for this interface if(uart_get_debug() == _uart_nr) { uart_set_debug(UART_NO); } - _uart = uart_init(_uart_nr, baud, config, mode); + if (_uart) { + os_free(_uart); + } + _uart = uart_start_init(_uart_nr, baud, config, mode); if(_uart == 0) { return; } - if(_uart->rxEnabled) { - if(!_rx_buffer) - _rx_buffer = new cbuf(SERIAL_RX_BUFFER_SIZE); + // Disable the RX and/or TX functions if we fail to allocate circular buffers. + // The user can confirm they are enabled with isRxEnabled() and isTxEnabled(). + if(_uart->rxEnabled && !_rx_buffer) { + _rx_buffer = new cbuf(SERIAL_RX_BUFFER_SIZE); + if(!_rx_buffer) { + _uart->rxEnabled = false; + } } - if(_uart->txEnabled) { - if(!_tx_buffer) - _tx_buffer = new cbuf(SERIAL_TX_BUFFER_SIZE); + if(_uart->txEnabled && !_tx_buffer) { + _tx_buffer = new cbuf(SERIAL_TX_BUFFER_SIZE); + if(!_tx_buffer) { + _uart->txEnabled = false; + } } _written = false; delay(1); + + uart_finish_init(_uart); } void HardwareSerial::end() { + InterruptLock il; + if(uart_get_debug() == _uart_nr) { uart_set_debug(UART_NO); } @@ -660,16 +678,10 @@ HardwareSerial::operator bool() const { } void ICACHE_RAM_ATTR HardwareSerial::_rx_complete_irq(char c) { - if(_rx_buffer) { - _rx_buffer->write(c); - } + _rx_buffer->write(c); } void ICACHE_RAM_ATTR HardwareSerial::_tx_empty_irq(void) { - if(_uart == 0) - return; - if(_tx_buffer == 0) - return; const int uart_nr = _uart->uart_nr; size_t queued = _tx_buffer->getSize(); if(!queued) { From 4ef0466578f84f6ba3e67204e450606d65ef88d0 Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Tue, 29 Dec 2015 12:05:15 -0500 Subject: [PATCH 06/20] Don't trip the WDT if interrupts are disabled during write() or flush(). Prior to this change, if interrupts were disabled during a call to HardwareSerial::write() when the circular buffer was full, or HardwareSerial::flush() when the circular buffer was non-empty, we would loop forever and trip a watchdog timeout. --- cores/esp8266/HardwareSerial.cpp | 6 ++++++ cores/esp8266/interrupts.h | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/cores/esp8266/HardwareSerial.cpp b/cores/esp8266/HardwareSerial.cpp index 3fd5af48b..b0f6861d2 100644 --- a/cores/esp8266/HardwareSerial.cpp +++ b/cores/esp8266/HardwareSerial.cpp @@ -636,6 +636,9 @@ void HardwareSerial::flush() { if(_tx_buffer->getSize() == 0 && UART_GET_TX_FIFO_ROOM(uart_nr) >= UART_TX_FIFO_SIZE) { break; + } else if(il.savedInterruptLevel() > 0) { + _tx_empty_irq(); + continue; } } yield(); @@ -663,6 +666,9 @@ size_t HardwareSerial::write(uint8_t c) { break; } else if(_tx_buffer->write(c)) { break; + } else if(il.savedInterruptLevel() > 0) { + _tx_empty_irq(); + continue; } } yield(); diff --git a/cores/esp8266/interrupts.h b/cores/esp8266/interrupts.h index 813997447..b4a2ed44b 100644 --- a/cores/esp8266/interrupts.h +++ b/cores/esp8266/interrupts.h @@ -31,6 +31,10 @@ public: xt_wsr_ps(_state); } + uint32_t savedInterruptLevel() const { + return _state & 0x0f; + } + protected: uint32_t _state; }; From 2375fb0f865dd16895a02cfcaa574f8fb0d79771 Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Mon, 14 Dec 2015 21:37:29 -0800 Subject: [PATCH 07/20] Cleanup: remove unused includes of cbuf.h. --- libraries/ESP8266WiFi/src/WiFiClient.cpp | 1 - libraries/ESP8266WiFi/src/WiFiClientSecure.cpp | 1 - libraries/ESP8266WiFi/src/WiFiServer.cpp | 1 - 3 files changed, 3 deletions(-) diff --git a/libraries/ESP8266WiFi/src/WiFiClient.cpp b/libraries/ESP8266WiFi/src/WiFiClient.cpp index 672fff4b2..c2c6966f2 100644 --- a/libraries/ESP8266WiFi/src/WiFiClient.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClient.cpp @@ -38,7 +38,6 @@ extern "C" #include "lwip/tcp.h" #include "lwip/inet.h" #include "lwip/netif.h" -#include "cbuf.h" #include "include/ClientContext.h" #include "c_types.h" diff --git a/libraries/ESP8266WiFi/src/WiFiClientSecure.cpp b/libraries/ESP8266WiFi/src/WiFiClientSecure.cpp index 484a23fc0..bf4252275 100644 --- a/libraries/ESP8266WiFi/src/WiFiClientSecure.cpp +++ b/libraries/ESP8266WiFi/src/WiFiClientSecure.cpp @@ -37,7 +37,6 @@ extern "C" #include "lwip/tcp.h" #include "lwip/inet.h" #include "lwip/netif.h" -#include "cbuf.h" #include "include/ClientContext.h" #include "c_types.h" diff --git a/libraries/ESP8266WiFi/src/WiFiServer.cpp b/libraries/ESP8266WiFi/src/WiFiServer.cpp index 462e7d7de..c58a91476 100644 --- a/libraries/ESP8266WiFi/src/WiFiServer.cpp +++ b/libraries/ESP8266WiFi/src/WiFiServer.cpp @@ -35,7 +35,6 @@ extern "C" { #include "lwip/opt.h" #include "lwip/tcp.h" #include "lwip/inet.h" -#include "cbuf.h" #include "include/ClientContext.h" WiFiServer::WiFiServer(IPAddress addr, uint16_t port) From bc9493e690c5483b268e38ff291bc5a5db0279fb Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Tue, 29 Dec 2015 12:46:25 -0500 Subject: [PATCH 08/20] Remove tracking of whether write() was called. Tracking _written was required on AVR devices to work around a hardware limitation when implementing flush(). The ESP8266 doesn't have the same issue, so we can remove the tracking and make write() more lightweight. The cost is a minor increase in work done in flush() when write() was not previously called, but this should be much rarer than individual character writes. --- cores/esp8266/HardwareSerial.cpp | 7 +------ cores/esp8266/HardwareSerial.h | 1 - 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/cores/esp8266/HardwareSerial.cpp b/cores/esp8266/HardwareSerial.cpp index b0f6861d2..1939bbcbb 100644 --- a/cores/esp8266/HardwareSerial.cpp +++ b/cores/esp8266/HardwareSerial.cpp @@ -485,7 +485,7 @@ int uart_get_debug() { // #################################################################################################### HardwareSerial::HardwareSerial(int uart_nr) : - _uart_nr(uart_nr), _uart(0), _tx_buffer(0), _rx_buffer(0), _written(false) { + _uart_nr(uart_nr), _uart(0), _tx_buffer(0), _rx_buffer(0) { } void HardwareSerial::begin(unsigned long baud, byte config, byte mode) { @@ -519,7 +519,6 @@ void HardwareSerial::begin(unsigned long baud, byte config, byte mode) { _uart->txEnabled = false; } } - _written = false; delay(1); uart_finish_init(_uart); @@ -626,8 +625,6 @@ void HardwareSerial::flush() { return; if(!_uart->txEnabled) return; - if(!_written) - return; const int uart_nr = _uart->uart_nr; while(true) { @@ -643,13 +640,11 @@ void HardwareSerial::flush() { } yield(); } - _written = false; } size_t HardwareSerial::write(uint8_t c) { if(_uart == 0 || !_uart->txEnabled) return 0; - _written = true; bool tx_now = false; const int uart_nr = _uart->uart_nr; diff --git a/cores/esp8266/HardwareSerial.h b/cores/esp8266/HardwareSerial.h index 123aa7adc..f2a108a5e 100644 --- a/cores/esp8266/HardwareSerial.h +++ b/cores/esp8266/HardwareSerial.h @@ -117,7 +117,6 @@ class HardwareSerial: public Stream { uart_t* _uart; cbuf* _tx_buffer; cbuf* _rx_buffer; - bool _written; }; extern HardwareSerial Serial; From 9d7c2fd5be4c734d8915f91b821fdcd7602de055 Mon Sep 17 00:00:00 2001 From: Christopher Pascoe Date: Tue, 29 Dec 2015 17:04:15 -0500 Subject: [PATCH 09/20] Remove duplicate 'return' (copy-and-paste error). --- cores/esp8266/cbuf.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cores/esp8266/cbuf.cpp b/cores/esp8266/cbuf.cpp index 963ac638f..8f36cf8a2 100644 --- a/cores/esp8266/cbuf.cpp +++ b/cores/esp8266/cbuf.cpp @@ -33,7 +33,6 @@ int ICACHE_RAM_ATTR cbuf::read() { char result = *_begin; _begin = wrap_if_bufend(_begin + 1); - return result; return static_cast(result); } From f3c0ded2d83302030b9a5b5431cfdb253468500a Mon Sep 17 00:00:00 2001 From: Charles Date: Sun, 3 Jan 2016 02:43:57 +0100 Subject: [PATCH 10/20] Added default debug Mode to WifInfo boards --- boards.txt | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/boards.txt b/boards.txt index 9377b4f71..c0d16f5e4 100644 --- a/boards.txt +++ b/boards.txt @@ -944,8 +944,20 @@ wifinfo.build.variant=wifinfo wifinfo.build.flash_mode=qio wifinfo.build.board=ESP8266_ESP12 wifinfo.build.spiffs_pagesize=256 -wifinfo.build.debug_port= -wifinfo.build.debug_level= +wifinfo.build.debug_port=Serial1 +wifinfo.build.debug_level=Wifinfo + +wifinfo.menu.Debug.Disabled=Disabled +wifinfo.menu.Debug.Disabled.build.debug_port= +wifinfo.menu.Debug.Serial=Serial +wifinfo.menu.Debug.Serial.build.debug_port=-DDEBUG_ESP_PORT=Serial +wifinfo.menu.Debug.Serial1=Serial1 +wifinfo.menu.Debug.Serial1.build.debug_port=-DDEBUG_ESP_PORT=Serial1 + +wifinfo.menu.DebugLevel.None=None +wifinfo.menu.DebugLevel.None.build.debug_level= +wifinfo.menu.DebugLevel.Wifinfo=Wifinfo +wifinfo.menu.DebugLevel.Wifinfo.build.debug_level=-DDEBUG_ESP_WIFINFO #wifinfo.menu.ESPModule.ESP07512=ESP07 (1M/512K SPIFFS) #wifinfo.menu.ESPModule.ESP07512.build.board=ESP8266_ESP07 From 6cf20e0a71ccd87f123a3ca6aa5eea119661e217 Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Sun, 3 Jan 2016 15:27:36 +0100 Subject: [PATCH 11/20] fix WiFi Multi debug compile error --- libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp b/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp index 23b82e648..ea33352a4 100644 --- a/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp +++ b/libraries/ESP8266WiFi/src/ESP8266WiFiMulti.cpp @@ -127,7 +127,7 @@ wl_status_t ESP8266WiFiMulti::run(void) { ip = WiFi.localIP(); mac = WiFi.BSSID(); DEBUG_WIFI_MULTI("[WIFI] Connecting done.\n"); - DEBUG_WIFI_MULTI("[WIFI] SSID: %s\n", WiFi.SSID()); + DEBUG_WIFI_MULTI("[WIFI] SSID: %s\n", WiFi.SSID().c_str()); DEBUG_WIFI_MULTI("[WIFI] IP: %d.%d.%d.%d\n", ip[0], ip[1], ip[2], ip[3]); DEBUG_WIFI_MULTI("[WIFI] MAC: %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); DEBUG_WIFI_MULTI("[WIFI] Channel: %d\n", WiFi.channel()); From d2a357e6933a31e9d4f6a397c3d9163f117be1c1 Mon Sep 17 00:00:00 2001 From: Matthew O'Gorman Date: Sun, 3 Jan 2016 23:33:48 -0500 Subject: [PATCH 12/20] add support for manually selecting ip and port for host side --- tools/espota.py | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tools/espota.py b/tools/espota.py index 9913f0045..d3b125492 100755 --- a/tools/espota.py +++ b/tools/espota.py @@ -5,11 +5,12 @@ # # Modified since 2015-09-18 from Pascal Gollor (https://github.com/pgollor) # Modified since 2015-11-09 from Hristo Gochkov (https://github.com/me-no-dev) +# Modified since 2016-01-03 from Matthew O'Gorman (https://githumb.com/mogorman) # # This script will push an OTA update to the ESP -# use it like: python espota.py -i -p [-a password] -f +# use it like: python espota.py -i -I -p -P [-a password] -f # Or to upload SPIFFS image: -# python espota.py -i -p [-a password] -s -f +# python espota.py -i -I -p -P [-a password] -s -f # # Changes # 2015-09-18: @@ -22,6 +23,10 @@ # - Added digest authentication # - Enchanced error tracking and reporting # +# Changes +# 2016-01-03: +# - Added more options to parser. +# from __future__ import print_function import socket @@ -64,11 +69,10 @@ def update_progress(progress): sys.stderr.write('.') sys.stderr.flush() -def serve(remoteAddr, remotePort, password, filename, command = FLASH): +def serve(remoteAddr, localAddr, remotePort, localPort, password, filename, command = FLASH): # Create a TCP/IP socket sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - serverPort = random.randint(10000,60000) - server_address = ('0.0.0.0', serverPort) + server_address = (localAddr, localPort) logging.info('Starting on %s:%s', str(server_address[0]), str(server_address[1])) try: sock.bind(server_address) @@ -82,7 +86,7 @@ def serve(remoteAddr, remotePort, password, filename, command = FLASH): file_md5 = hashlib.md5(f.read()).hexdigest() f.close() logging.info('Upload size: %d', content_size) - message = '%d %d %d %s\n' % (command, serverPort, content_size, file_md5) + message = '%d %d %d %s\n' % (command, localPort, content_size, file_md5) # Wait for a connection logging.info('Sending invitation to: %s', remoteAddr) @@ -209,12 +213,24 @@ def parser(): help = "ESP8266 IP Address.", default = False ) + group.add_option("-I", "--host_ip", + dest = "host_ip", + action = "store", + help = "Host IP Address.", + default = "0.0.0.0" + ) group.add_option("-p", "--port", dest = "esp_port", type = "int", help = "ESP8266 ota Port. Default 8266", default = 8266 ) + group.add_option("-P", "--host_port", + dest = "host_port", + type = "int", + help = "Host server ota Port. Default random 10000-60000", + default = random.randint(10000,60000) + ) parser.add_option_group(group) # auth @@ -294,7 +310,7 @@ def main(args): command = SPIFFS # end if - return serve(options.esp_ip, options.esp_port, options.auth, options.image, command) + return serve(options.esp_ip, options.host_ip, options.esp_port, options.host_port, options.auth, options.image, command) # end main From 43a55bd404b369691fdc50a837cd586eac3198ab Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 4 Jan 2016 17:25:23 +0300 Subject: [PATCH 13/20] Update changelog --- doc/changes.md | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/doc/changes.md b/doc/changes.md index 7d19d6166..763371c3c 100644 --- a/doc/changes.md +++ b/doc/changes.md @@ -6,10 +6,44 @@ title: Change Log ### Core +- Allow control of enabling debug and debug level from IDE +- Make HardwareSerial::begin() and end() interrupt safe +- Put HardwareSerial and cbuf methods called from interrupt context in RAM +- Re-enable interrupts before directly enqueuing characters in the UART FIFO +- Add espduino board +- Rework StreamString::write to use String internal buffer directly (#1289) +- Add function to measure stack high water mark +- Update SDK to esp_iot_sdk_v1.5.0_15_12_15_p1 +- Fix RAM corruption caused by our hook of register_chipv6_phy(init_data*). +- Optimize PWM interrupt handler for better precision +- Add warning levels configurable through Preferences +- Protect HardwareSerial's cbuf usage with InterruptLock +- SPIFFS: check if path length is valid (#1089) +- Set CPU frequency before running setup +- Add core_esp8266_features.h to be able to detect the features and libraries included in the ESP core +- Added ESPino to supported boards + ### Libraries +- ESP8266HTTPClient: add CHUNKED encoding support (#1324) +- Fixed crash bug with mDNS where a string buffer could be used uninitialized +- Add WiFi TX power control +- Add WiFi sleep management +- Allow to hook into WiFi events from sketch +- Allow setting TCP timeout +- Add setSleepMode + getSleepMode and setPhyMode + getPhyMode to WiFi +- Update GDBStub library with the source of esp-gdbstub +- Servo: fix detach and attach +- ESP8266mDNS: refactoring, add TXT support +- Add HTTP Basic Auth to WebServer and libb64 (base64) to core +- Fix link-time dependency of ESP8266WebServer on SPIFFS (#862) +- Allow setting client side TLS key and certificate +- Replace chain of UDP pbufs with a single pbuf before sending (#1009) + ### Tools +- espota.py: add support for manually selecting ip and port for host side + --- ## 2.0.0 November 30, 2015 From a995643e7d5a78c751caec93a0061d80e3eedfa5 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 4 Jan 2016 18:36:03 +0300 Subject: [PATCH 14/20] Update Readme --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fdb199842..c12cde778 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ESP8266 Arduino core comes with libraries to communicate over WiFi using TCP and - [Using git version](#using-git-version-) - [Using stable version with PlatformIO](#using-stable-version-with-platformio) - [Documentation](#documentation) -- [Issues and support](#issues-and-support) +- [Issues and support](#issues-and-support) - [Contributing](#contributing) - [License and credits](#license-and-credits) @@ -25,7 +25,7 @@ Starting with 1.6.4, Arduino allows installation of third-party platform package - Open Boards Manager from Tools > Board menu and install *esp8266* platform (and don't forget to select your ESP8266 board from Tools > Board menu after installation). The best place to ask questions related to this core is ESP8266 community forum: http://www.esp8266.com/arduino. -If you find this ESP8266 board manager useful, please consider supporting it with a donation. The ESP8266 Community Forum and IGRR have made this wonderful port available. +If you find this forum or the ESP8266 Boards Manager package useful, please consider supporting it with a donation. [![Donate](https://img.shields.io/badge/paypal-donate-yellow.svg)](https://www.paypal.com/webscr?cmd=_s-xclick&hosted_button_id=4M56YCWV6PX66) #### Available versions @@ -38,11 +38,11 @@ Documentation: [http://esp8266.github.io/Arduino/versions/2.0.0/](http://esp8266 ##### Staging version ![](http://arduino.esp8266.com/staging/badge.svg) Boards manager link: `http://arduino.esp8266.com/staging/package_esp8266com_index.json` -Documentation: [http://esp8266.github.io/Arduino/versions/2.0.0-rc2/](http://esp8266.github.io/Arduino/versions/2.0.0-rc2/) +Documentation: [http://esp8266.github.io/Arduino/versions/2.1.0-rc1/](http://esp8266.github.io/Arduino/versions/2.1.0-rc1/) ### Using git version [![Linux build status](https://travis-ci.org/esp8266/Arduino.svg)](https://travis-ci.org/esp8266/Arduino) -- Install Arduino 1.6.5 +- Install Arduino 1.6.7 - Go to Arduino directory - Clone this repository into hardware/esp8266com/esp8266 directory (or clone it elsewhere and create a symlink) ```bash From 971bba13dd9ae2a2ec9b26f016174511ecdcaac5 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Mon, 4 Jan 2016 18:36:28 +0300 Subject: [PATCH 15/20] Update release tools --- package/build_boards_manager_package.sh | 2 +- package/esp8266-arudino-doc.bash | 9 +++++---- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/package/build_boards_manager_package.sh b/package/build_boards_manager_package.sh index 261dd2afd..9adf03f00 100755 --- a/package/build_boards_manager_package.sh +++ b/package/build_boards_manager_package.sh @@ -39,7 +39,7 @@ cat << EOF > exclude.txt package EOF # Also include all files which are ignored by git -git ls-files --other --ignored --exclude-standard --directory >> exclude.txt +git ls-files --other --directory >> exclude.txt # Now copy files to $outdir rsync -a --exclude-from 'exclude.txt' $srcdir/ $outdir/ rm exclude.txt diff --git a/package/esp8266-arudino-doc.bash b/package/esp8266-arudino-doc.bash index 25e3dc036..90914b000 100755 --- a/package/esp8266-arudino-doc.bash +++ b/package/esp8266-arudino-doc.bash @@ -24,8 +24,9 @@ set -e # some variable definitions tmp_path=$1 -arduinoESP_src="$tmp_path/arduino" -version="$(git --work-tree=$arduinoESP_src describe --tags --always)" +doc_src_path=$2 +arduinoESP_src=$(cd $PWD/..; pwd) +version="$(git --work-tree=$arduinoESP_src --git-dir=$arduinoESP_src/.git describe --tags --always)" release_date=$(date "+%b_%d,_%Y") # format for badge link build_date=$(date "+%b %d, %Y") destination_path="$tmp_path/doc" @@ -62,7 +63,8 @@ mkdir -p $destination_path/$version cp -R $arduinoESP_src/doc/* $destination_path/src # download doc template -git clone $doc_template_url $destination_path/build +rsync -av $doc_src_path/ $destination_path/build/ +# git clone $doc_template_url $destination_path/build # create versions.html file @@ -113,4 +115,3 @@ popd # grab badge wget -q -O $destination_path/$version/badge.svg "https://img.shields.io/badge/updated-$release_date-blue.svg" - From 1c0a5537b1ee5731246e60e44b7caa35fb0fd626 Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Wed, 6 Jan 2016 13:35:45 +0100 Subject: [PATCH 16/20] add debug level HTTPClient + SSL --- boards.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/boards.txt b/boards.txt index 9377b4f71..f1be0f47a 100644 --- a/boards.txt +++ b/boards.txt @@ -189,6 +189,8 @@ generic.menu.DebugLevel.WiFi=WiFi generic.menu.DebugLevel.WiFi.build.debug_level=-DDEBUG_ESP_WIFI generic.menu.DebugLevel.HTTPClient=HTTPClient generic.menu.DebugLevel.HTTPClient.build.debug_level=-DDEBUG_ESP_HTTP_CLIENT +generic.menu.DebugLevel.HTTPClient2=HTTPClient + SSL +generic.menu.DebugLevel.HTTPClient2.build.debug_level=-DDEBUG_ESP_HTTP_CLIENT -DDEBUG_ESP_SSL generic.menu.DebugLevel.HTTPUpdate=HTTPUpdate generic.menu.DebugLevel.HTTPUpdate.build.debug_level=-DDEBUG_ESP_HTTP_UPDATE generic.menu.DebugLevel.HTTPUpdate2=HTTPClient + HTTPUpdate From 5a85b4708e6a5270afd23a3555ec1ebcb36bb9a7 Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Wed, 6 Jan 2016 15:42:37 +0100 Subject: [PATCH 17/20] add some docu for debug menu --- doc/Troubleshooting/debug_level.png | Bin 0 -> 37293 bytes doc/Troubleshooting/debug_port.png | Bin 0 -> 34602 bytes doc/Troubleshooting/debugging.md | 102 ++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 doc/Troubleshooting/debug_level.png create mode 100644 doc/Troubleshooting/debug_port.png create mode 100644 doc/Troubleshooting/debugging.md diff --git a/doc/Troubleshooting/debug_level.png b/doc/Troubleshooting/debug_level.png new file mode 100644 index 0000000000000000000000000000000000000000..3d35321832d9d2b927a1bbeea0a4ba48964562d2 GIT binary patch literal 37293 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
g<vJko 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 UYo*U_S&Vz=fW0l==Jd6H19tb&D*ylh literal 0 HcmV?d00001 diff --git a/doc/Troubleshooting/debugging.md b/doc/Troubleshooting/debugging.md new file mode 100644 index 000000000..47a3cbebb --- /dev/null +++ b/doc/Troubleshooting/debugging.md @@ -0,0 +1,102 @@ +--- +title: Debugging +--- + +## Table of Contents + * [Introduction](#introduction) + * [Requirements](#requirements) + * [Usage](#Usage) + * [Informations](#Informations) + * [For Developers](#for-developers) + +## Introduction + +Since 2.1.0-rc1 the core includes a Debugging feature that is controllable over the IDE menu. + +The new menu points manage the real-time Debug messages. + +### Requirements + +For usage of the debugging a Serial connection is required (Serial or Serial1). + +The Serial Interface need to be initialized in the ```setup()```. + +Set the Serial baud rate as high as possible for your Hardware setup. + +Minimum sketch to use debugging: +```cpp +void setup() { + Serial.begin(115200); +} + +void loop() { +} +``` + +### Usage + +1. Select the Serial interface for the Debugging messages: +![Debug-Port](debug_port.png) + +2. Select which type / level you want debug messages for: +![Debug-Level](debug_level.png) + +3. Check if the Serial interface is initialized in ```setup()``` (see [Requirements](#requirements)) + +4. Flash sketch + +5. Check the Serial Output + + + +## Informations + +It work with every sketch that enables the Serial interface that is selected as debug port. + +The Serial interface can still be used normal in the Sketch. + +The debug output is additional and will not disable any interface from usage in the sketch. + +### For Developers + +For the debug handling uses defines. + +The defined are set by command line. + +#### Debug Port + +The port has the define ```DEBUG_ESP_PORT``` possible value: + - Disabled: define not existing + - Serial: Serial + - Serial1: Serial1 + +#### Debug Level + +All defines for the different levels starts with ```DEBUG_ESP_``` + +a full list can be found here in the [boards.txt](https://github.com/esp8266/Arduino/blob/master/boards.txt#L180) + +#### Example for own debug messages + +The debug messages will be only shown when the Debug Port in the IDE menu is set. + +```cpp +#ifdef DEBUG_ESP_PORT +#define DEBUG_MSG(...) DEBUG_ESP_PORT.printf( __VA_ARGS__ ) +#else +#define DEBUG_MSG(...) +#endif + +void setup() { + Serial.begin(115200); + + delay(3000); + DEBUG_MSG("bootup...\n"); +} + +void loop() { + DEBUG_MSG("loop %d\n", millis()); + delay(1000); +} +``` + From dee768b08cfc706ca2bebb8f5e8edff2e3690c08 Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Thu, 7 Jan 2016 13:32:19 +0100 Subject: [PATCH 18/20] add arduinoVNC to compatible librarys --- doc/libraries.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/libraries.md b/doc/libraries.md index c9c714e2f..86e7d75d3 100644 --- a/doc/libraries.md +++ b/doc/libraries.md @@ -141,6 +141,7 @@ While many RC servo motors will accept the 3.3V IO data pin from a ESP8266, most Libraries that don't rely on low-level access to AVR registers should work well. Here are a few libraries that were verified to work: - [Adafruit_ILI9341](https://github.com/Links2004/Adafruit_ILI9341) - Port of the Adafruit ILI9341 for the ESP8266 +- [arduinoVNC](https://github.com/Links2004/arduinoVNC) - VNC Client for Arduino - [arduinoWebSockets](https://github.com/Links2004/arduinoWebSockets) - WebSocket Server and Client compatible with ESP8266 (RFC6455) - [aREST](https://github.com/marcoschwartz/aREST) - REST API handler library. - [Blynk](https://github.com/blynkkk/blynk-library) - easy IoT framework for Makers (check out the [Kickstarter page](http://tiny.cc/blynk-kick)). From dec6739e3f526d761ce35a7c3e59cdc9785651a2 Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Fri, 8 Jan 2016 19:06:28 +0100 Subject: [PATCH 19/20] add more debug to WiFi --- libraries/ESP8266WiFi/src/ESP8266WiFi.h | 11 +++++++++++ libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp | 11 ++++++++++- libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp | 4 ++-- 3 files changed, 23 insertions(+), 3 deletions(-) diff --git a/libraries/ESP8266WiFi/src/ESP8266WiFi.h b/libraries/ESP8266WiFi/src/ESP8266WiFi.h index 4e6ce8e04..d7b56ca92 100644 --- a/libraries/ESP8266WiFi/src/ESP8266WiFi.h +++ b/libraries/ESP8266WiFi/src/ESP8266WiFi.h @@ -40,6 +40,17 @@ extern "C" { #include "WiFiServer.h" #include "WiFiClientSecure.h" +#ifdef DEBUG_ESP_WIFI +#ifdef DEBUG_ESP_PORT +#define DEBUG_WIFI(...) DEBUG_ESP_PORT.printf( __VA_ARGS__ ) +#endif +#endif + +#ifndef DEBUG_WIFI +#define DEBUG_WIFI(...) +#endif + + class ESP8266WiFiClass : public ESP8266WiFiGenericClass, public ESP8266WiFiSTAClass, public ESP8266WiFiScanClass, public ESP8266WiFiAPClass { public: diff --git a/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp b/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp index 6c911263a..fec121ad9 100644 --- a/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp +++ b/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp @@ -85,16 +85,19 @@ bool ESP8266WiFiAPClass::softAP(const char* ssid, const char* passphrase, int ch if(!WiFi.enableAP(true)) { // enable AP failed + DEBUG_WIFI("[AP] enableAP failed!"); return false; } if(!ssid || *ssid == 0 || strlen(ssid) > 31) { // fail SSID too long or missing! + DEBUG_WIFI("[AP] SSID too long or missing!"); return false; } if(passphrase && (strlen(passphrase) > 63 || strlen(passphrase) < 8)) { // fail passphrase to long or short! + DEBUG_WIFI("[AP] fail passphrase to long or short!"); return false; } @@ -117,7 +120,7 @@ bool ESP8266WiFiAPClass::softAP(const char* ssid, const char* passphrase, int ch struct softap_config conf_current; wifi_softap_get_config(&conf_current); if(softap_config_equal(conf, conf_current)) { - DEBUGV("softap config unchanged"); + DEBUG_WIFI("[AP] softap config unchanged"); return true; } @@ -131,6 +134,9 @@ bool ESP8266WiFiAPClass::softAP(const char* ssid, const char* passphrase, int ch } ETS_UART_INTR_ENABLE(); + if(!ret) { + DEBUG_WIFI("[AP] set_config faild!"); + } return ret; } @@ -145,6 +151,7 @@ bool ESP8266WiFiAPClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPA if(!WiFi.enableAP(true)) { // enable AP failed + DEBUG_WIFI("[AP] enableAP failed!"); return false; } @@ -155,6 +162,8 @@ bool ESP8266WiFiAPClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPA wifi_softap_dhcps_stop(); if(wifi_set_ip_info(SOFTAP_IF, &info)) { return wifi_softap_dhcps_start(); + } else { + DEBUG_WIFI("[AP] wifi_set_ip_info failed!"); } return false; } diff --git a/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp b/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp index 86bbb7091..0a06fce85 100644 --- a/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp +++ b/libraries/ESP8266WiFi/src/ESP8266WiFiGeneric.cpp @@ -103,10 +103,10 @@ void ESP8266WiFiGenericClass::removeEvent(WiFiEventCb cbEvent, WiFiEvent_t event */ void ESP8266WiFiGenericClass::_eventCallback(void* arg) { System_Event_t* event = reinterpret_cast(arg); - DEBUGV("wifi evt: %d\n", event->event); + DEBUG_WIFI("wifi evt: %d\n", event->event); if(event->event == EVENT_STAMODE_DISCONNECTED) { - DEBUGV("STA disconnect: %d\n", event->event_info.disconnected.reason); + DEBUG_WIFI("STA disconnect: %d\n", event->event_info.disconnected.reason); WiFiClient::stopAll(); } From 29bb74beabca5e1b9db7d21f85b3bb63fdfbfb16 Mon Sep 17 00:00:00 2001 From: Markus Sattler Date: Fri, 8 Jan 2016 19:30:21 +0100 Subject: [PATCH 20/20] rework AP config to get DHCP in best mode if SDK config got invalid some how. --- libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp | 131 ++++++++++++++++---- 1 file changed, 108 insertions(+), 23 deletions(-) diff --git a/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp b/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp index fec121ad9..bf2bfb195 100644 --- a/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp +++ b/libraries/ESP8266WiFi/src/ESP8266WiFiAP.cpp @@ -85,22 +85,24 @@ bool ESP8266WiFiAPClass::softAP(const char* ssid, const char* passphrase, int ch if(!WiFi.enableAP(true)) { // enable AP failed - DEBUG_WIFI("[AP] enableAP failed!"); + DEBUG_WIFI("[AP] enableAP failed!\n"); return false; } if(!ssid || *ssid == 0 || strlen(ssid) > 31) { // fail SSID too long or missing! - DEBUG_WIFI("[AP] SSID too long or missing!"); + DEBUG_WIFI("[AP] SSID too long or missing!\n"); return false; } if(passphrase && (strlen(passphrase) > 63 || strlen(passphrase) < 8)) { // fail passphrase to long or short! - DEBUG_WIFI("[AP] fail passphrase to long or short!"); + DEBUG_WIFI("[AP] fail passphrase to long or short!\n"); return false; } + bool ret = false; + struct softap_config conf; strcpy(reinterpret_cast(conf.ssid), ssid); conf.channel = channel; @@ -119,24 +121,51 @@ bool ESP8266WiFiAPClass::softAP(const char* ssid, const char* passphrase, int ch struct softap_config conf_current; wifi_softap_get_config(&conf_current); - if(softap_config_equal(conf, conf_current)) { - DEBUG_WIFI("[AP] softap config unchanged"); - return true; - } + if(!softap_config_equal(conf, conf_current)) { - bool ret; + ETS_UART_INTR_DISABLE(); + if(WiFi._persistent) { + ret = wifi_softap_set_config(&conf); + } else { + ret = wifi_softap_set_config_current(&conf); + } + ETS_UART_INTR_ENABLE(); + + if(!ret) { + DEBUG_WIFI("[AP] set_config failed!\n"); + return false; + } - ETS_UART_INTR_DISABLE(); - if(WiFi._persistent) { - ret = wifi_softap_set_config(&conf); } else { - ret = wifi_softap_set_config_current(&conf); + DEBUG_WIFI("[AP] softap config unchanged\n"); } - ETS_UART_INTR_ENABLE(); - if(!ret) { - DEBUG_WIFI("[AP] set_config faild!"); + if(wifi_softap_dhcps_status() != DHCP_STARTED) { + DEBUG_WIFI("[AP] DHCP not started, starting...\n"); + if(!wifi_softap_dhcps_start()) { + DEBUG_WIFI("[AP] wifi_softap_dhcps_start failed!\n"); + ret = false; + } } + + // check IP config + struct ip_info ip; + if(wifi_get_ip_info(SOFTAP_IF, &ip)) { + if(ip.ip.addr == 0x00000000) { + // Invalid config + DEBUG_WIFI("[AP] IP config Invalid resetting...\n"); + //192.168.244.1 , 192.168.244.1 , 255.255.255.0 + ret = softAPConfig(0x01F4A8C0, 0x01F4A8C0, 0x00FFFFFF); + if(!ret) { + DEBUG_WIFI("[AP] softAPConfig failed!\n"); + ret = false; + } + } + } else { + DEBUG_WIFI("[AP] wifi_get_ip_info failed!\n"); + ret = false; + } + return ret; } @@ -148,24 +177,76 @@ bool ESP8266WiFiAPClass::softAP(const char* ssid, const char* passphrase, int ch * @param subnet subnet mask */ bool ESP8266WiFiAPClass::softAPConfig(IPAddress local_ip, IPAddress gateway, IPAddress subnet) { - + DEBUG_WIFI("[APConfig] local_ip: %s gateway: %s subnet: %s\n", local_ip.toString().c_str(), gateway.toString().c_str(), subnet.toString().c_str()); if(!WiFi.enableAP(true)) { // enable AP failed - DEBUG_WIFI("[AP] enableAP failed!"); + DEBUG_WIFI("[APConfig] enableAP failed!\n"); return false; } + bool ret = true; struct ip_info info; info.ip.addr = static_cast(local_ip); info.gw.addr = static_cast(gateway); info.netmask.addr = static_cast(subnet); - wifi_softap_dhcps_stop(); - if(wifi_set_ip_info(SOFTAP_IF, &info)) { - return wifi_softap_dhcps_start(); - } else { - DEBUG_WIFI("[AP] wifi_set_ip_info failed!"); + + if(!wifi_softap_dhcps_stop()) { + DEBUG_WIFI("[APConfig] wifi_softap_dhcps_stop failed!\n"); } - return false; + + if(!wifi_set_ip_info(SOFTAP_IF, &info)) { + DEBUG_WIFI("[APConfig] wifi_set_ip_info failed!\n"); + ret = false; + } + + struct dhcps_lease dhcp_lease; + IPAddress ip = local_ip; + ip[3] += 99; + dhcp_lease.start_ip.addr = static_cast(ip); + DEBUG_WIFI("[APConfig] DHCP IP start: %s\n", ip.toString().c_str()); + + ip[3] += 100; + dhcp_lease.end_ip.addr = static_cast(ip); + DEBUG_WIFI("[APConfig] DHCP IP end: %s\n", ip.toString().c_str()); + + if(!wifi_softap_set_dhcps_lease(&dhcp_lease)) { + DEBUG_WIFI("[APConfig] wifi_set_ip_info failed!\n"); + ret = false; + } + + // set lease time to 720min --> 12h + if(!wifi_softap_set_dhcps_lease_time(720)) { + DEBUG_WIFI("[APConfig] wifi_softap_set_dhcps_lease_time failed!\n"); + ret = false; + } + + uint8 mode = 1; + if(!wifi_softap_set_dhcps_offer_option(OFFER_ROUTER, &mode)) { + DEBUG_WIFI("[APConfig] wifi_softap_set_dhcps_offer_option failed!\n"); + ret = false; + } + + if(!wifi_softap_dhcps_start()) { + DEBUG_WIFI("[APConfig] wifi_softap_dhcps_start failed!\n"); + ret = false; + } + + // check config + if(wifi_get_ip_info(SOFTAP_IF, &info)) { + if(info.ip.addr == 0x00000000) { + DEBUG_WIFI("[AP] IP config Invalid?!\n"); + ret = false; + } else if(local_ip != info.ip.addr) { + ip = info.ip.addr; + DEBUG_WIFI("[AP] IP config not set correct?! new IP: %s\n", ip.toString().c_str()); + ret = false; + } + } else { + DEBUG_WIFI("[AP] wifi_get_ip_info failed!\n"); + ret = false; + } + + return ret; } @@ -188,6 +269,10 @@ bool ESP8266WiFiAPClass::softAPdisconnect(bool wifioff) { } ETS_UART_INTR_ENABLE(); + if(!ret) { + DEBUG_WIFI("[APdisconnect] set_config failed!\n"); + } + if(wifioff) { ret = WiFi.enableAP(false); }