Merge pull request #6373 from s-hadinger/serial_115200

TasmotaSerial: Reduce IRAM usage by 280 bytes and improve reliability at 115200 bauds
This commit is contained in:
Theo Arends 2019-09-08 12:50:48 +02:00 committed by GitHub
commit a59dea5a74
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 70 additions and 28 deletions

View File

@ -27,6 +27,9 @@ extern "C" {
#include <TasmotaSerial.h>
// for STAGE and pre-2.6, we can have a single wrapper using attachInterruptArg()
void ICACHE_RAM_ATTR callRxRead(void *self) { ((TasmotaSerial*)self)->rxRead(); };
// As the Arduino attachInterrupt has no parameter, lists of objects
// and callbacks corresponding to each possible GPIO pins have to be defined
TasmotaSerial *tms_obj_list[16];
@ -103,9 +106,14 @@ TasmotaSerial::TasmotaSerial(int receive_pin, int transmit_pin, int hardware_fal
if (m_buffer == NULL) return;
// Use getCycleCount() loop to get as exact timing as possible
m_bit_time = ESP.getCpuFreqMHz() * 1000000 / TM_SERIAL_BAUDRATE;
m_bit_start_time = m_bit_time + m_bit_time/3 - 500; // pre-compute first wait
pinMode(m_rx_pin, INPUT);
tms_obj_list[m_rx_pin] = this;
#if defined(ARDUINO_ESP8266_RELEASE_2_3_0) || defined(ARDUINO_ESP8266_RELEASE_2_4_2) || defined(ARDUINO_ESP8266_RELEASE_2_5_2)
attachInterrupt(m_rx_pin, ISRList[m_rx_pin], (m_nwmode) ? CHANGE : FALLING);
#else
attachInterruptArg(m_rx_pin, callRxRead, this, (m_nwmode) ? CHANGE : FALLING);
#endif
}
if (m_tx_pin > -1) {
pinMode(m_tx_pin, OUTPUT);
@ -148,7 +156,9 @@ bool TasmotaSerial::begin(long speed, int stop_bits) {
} else {
// Use getCycleCount() loop to get as exact timing as possible
m_bit_time = ESP.getCpuFreqMHz() * 1000000 / speed;
m_bit_start_time = m_bit_time + m_bit_time/3 - (ESP.getCpuFreqMHz() > 120 ? 700 : 500); // pre-compute first wait
m_high_speed = (speed >= 9600);
m_very_high_speed = (speed >= 100000);
}
return m_valid;
}
@ -202,9 +212,12 @@ int TasmotaSerial::available()
}
#ifdef TM_SERIAL_USE_IRAM
#define TM_SERIAL_WAIT { while (ESP.getCycleCount()-start < wait) if (!m_high_speed) optimistic_yield(1); wait += m_bit_time; } // Watchdog timeouts
#define TM_SERIAL_WAIT_SND { while (ESP.getCycleCount() < wait + start) if (!m_high_speed) optimistic_yield(1); wait += m_bit_time; } // Watchdog timeouts
#define TM_SERIAL_WAIT_RCV { while (ESP.getCycleCount() < wait + start); wait += m_bit_time; }
#define TM_SERIAL_WAIT_RCV_LOOP { while (ESP.getCycleCount() < wait + start); }
#else
#define TM_SERIAL_WAIT { while (ESP.getCycleCount()-start < wait); wait += m_bit_time; }
#define TM_SERIAL_WAIT_SND { while (ESP.getCycleCount() < wait + start); wait += m_bit_time; }
#define TM_SERIAL_WAIT_RCV { while (ESP.getCycleCount() < wait + start); wait += m_bit_time; }
#endif
size_t TasmotaSerial::write(uint8_t b)
@ -215,22 +228,23 @@ size_t TasmotaSerial::write(uint8_t b)
if (-1 == m_tx_pin) return 0;
if (m_high_speed) cli(); // Disable interrupts in order to get a clean transmit
uint32_t wait = m_bit_time;
digitalWrite(m_tx_pin, HIGH);
//digitalWrite(m_tx_pin, HIGH); // already in HIGH mode
uint32_t start = ESP.getCycleCount();
// Start bit;
digitalWrite(m_tx_pin, LOW);
TM_SERIAL_WAIT;
TM_SERIAL_WAIT_SND;
for (uint32_t i = 0; i < 8; i++) {
digitalWrite(m_tx_pin, (b & 1) ? HIGH : LOW);
TM_SERIAL_WAIT;
TM_SERIAL_WAIT_SND;
b >>= 1;
}
// Stop bit(s)
for (uint32_t i = 0; i < m_stop_bits; i++) {
digitalWrite(m_tx_pin, HIGH);
TM_SERIAL_WAIT;
}
digitalWrite(m_tx_pin, HIGH);
// re-enable interrupts during stop bits, it's not an issue if they are longer than expected
if (m_high_speed) sei();
for (uint32_t i = 0; i < m_stop_bits; i++) {
TM_SERIAL_WAIT_SND;
}
return 1;
}
}
@ -243,27 +257,49 @@ void TasmotaSerial::rxRead()
{
#endif
if (!m_nwmode) {
int32_t loop_read = serial_buffer_size;
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
uint32_t wait = m_bit_time + m_bit_time/3 - 500;
uint32_t wait = m_bit_start_time;
uint32_t start = ESP.getCycleCount();
uint8_t rec = 0;
for (uint32_t i = 0; i < 8; i++) {
TM_SERIAL_WAIT;
rec >>= 1;
if (digitalRead(m_rx_pin)) rec |= 0x80;
}
// Stop bit(s)
TM_SERIAL_WAIT;
if (2 == m_stop_bits) {
digitalRead(m_rx_pin);
TM_SERIAL_WAIT;
}
// Store the received value in the buffer unless we have an overflow
uint32_t next = (m_in_pos+1) % serial_buffer_size;
if (next != (int)m_out_pos) {
m_buffer[m_in_pos] = rec;
m_in_pos = next;
while (loop_read-- > 0) { // try to receveive all consecutive bytes in a raw
uint32_t rec = 0;
for (uint32_t i = 0; i < 8; i++) {
TM_SERIAL_WAIT_RCV;
rec >>= 1;
if (digitalRead(m_rx_pin)) rec |= 0x80;
}
// Store the received value in the buffer unless we have an overflow
uint32_t next = (m_in_pos+1) % serial_buffer_size;
if (next != (int)m_out_pos) {
m_buffer[m_in_pos] = rec;
m_in_pos = next;
}
// Stop bit(s) -- actually we don't need to wait for stop bits
// so we free some time for other interrupt handlers to do their work
bool start_of_next_byte = false;
for (uint32_t i = 0; i < 14; i++) {
TM_SERIAL_WAIT_RCV_LOOP; // wait for 1/4 bits
wait += m_bit_time / 4;
if (!digitalRead(m_rx_pin)) {
// this is the start bit of the next byte
wait += m_bit_time; // we have advanced in the first 1/4 of bit, and already added 1/4 of bit so we're roughly centered. Just skip start bit.
start_of_next_byte = true;
m_bit_follow_metric++;
break; // exit loop
}
}
if (!start_of_next_byte) {
loop_read = 0; // exit loop
}
// TM_SERIAL_WAIT_RCV;
// if (2 == m_stop_bits) {
// digitalRead(m_rx_pin);
// TM_SERIAL_WAIT_RCV;
// }
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled

View File

@ -53,6 +53,8 @@ class TasmotaSerial : public Stream {
void rxRead();
uint32_t getLoopReadMetric(void) const { return m_bit_follow_metric; }
using Print::write;
private:
@ -67,6 +69,8 @@ class TasmotaSerial : public Stream {
uint32_t ss_bstart;
uint32_t ss_index;
uint32_t m_bit_time;
uint32_t m_bit_start_time;
uint32_t m_bit_follow_metric = 0;
uint32_t m_in_pos;
uint32_t m_out_pos;
uint32_t serial_buffer_size;
@ -74,7 +78,8 @@ class TasmotaSerial : public Stream {
bool m_nwmode;
bool m_hardserial;
bool m_hardswap;
bool m_high_speed;
bool m_high_speed = false;
bool m_very_high_speed = false; // above 100000 bauds
uint8_t *m_buffer;
};

View File

@ -2,6 +2,7 @@
* 6.6.0.11 20190907
* Change Settings crc calculation allowing short term backward compatibility
* Add support for up to 4 INA226 Voltage and Current sensors by Steve Rogers (#6342)
* Change Improve reliability of TasmotaSerial at 115200 bauds and reduce IRAM usage for Stage/pre-2.6
*
* 6.6.0.10 20190905
* Redesign Tuya support by Shantur Rathore (#6353)