Provide SoftwareSerial without iram usage

This commit is contained in:
arendst 2017-12-05 17:42:54 +01:00
parent 6331ee7c8f
commit a5afc3d227
7 changed files with 429 additions and 0 deletions

View File

@ -0,0 +1,11 @@
# EspSoftwareSerial
Implementation of the Arduino software serial library for the ESP8266
Same functionality as the corresponding AVR library but several instances can be active at the same time.
Speed up to 115200 baud is supported. The constructor also has an optional input buffer size.
Please note that due to the fact that the ESP always have other activities ongoing, there will be some inexactness in interrupt
timings. This may lead to bit errors when having heavy data traffic in high baud rates.

View File

@ -0,0 +1,248 @@
/*
SoftwareSerialNoIram.cpp - Implementation of the Arduino software serial for ESP8266.
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
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
*/
#define TASMOTA_NO_ICACHE_RAM // To solve compile errors due to lack off iram
#include <Arduino.h>
// The Arduino standard GPIO routines are not enough,
// must use some from the Espressif SDK as well
extern "C" {
#include "gpio.h"
}
#include <SoftwareSerialNoIram.h>
#define MAX_PIN 15
// As the Arduino attachInterrupt has no parameter, lists of objects
// and callbacks corresponding to each possible GPIO pins have to be defined
SoftwareSerialNoIram *ObjList[MAX_PIN+1];
#ifdef TASMOTA_NO_ICACHE_RAM
void sws_isr_0() { ObjList[0]->rxRead(); };
void sws_isr_1() { ObjList[1]->rxRead(); };
void sws_isr_2() { ObjList[2]->rxRead(); };
void sws_isr_3() { ObjList[3]->rxRead(); };
void sws_isr_4() { ObjList[4]->rxRead(); };
void sws_isr_5() { ObjList[5]->rxRead(); };
// Pin 6 to 11 can not be used
void sws_isr_12() { ObjList[12]->rxRead(); };
void sws_isr_13() { ObjList[13]->rxRead(); };
void sws_isr_14() { ObjList[14]->rxRead(); };
void sws_isr_15() { ObjList[15]->rxRead(); };
#else
void ICACHE_RAM_ATTR sws_isr_0() { ObjList[0]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_1() { ObjList[1]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_2() { ObjList[2]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_3() { ObjList[3]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_4() { ObjList[4]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_5() { ObjList[5]->rxRead(); };
// Pin 6 to 11 can not be used
void ICACHE_RAM_ATTR sws_isr_12() { ObjList[12]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_13() { ObjList[13]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_14() { ObjList[14]->rxRead(); };
void ICACHE_RAM_ATTR sws_isr_15() { ObjList[15]->rxRead(); };
#endif
static void (*ISRList[MAX_PIN+1])() = {
sws_isr_0,
sws_isr_1,
sws_isr_2,
sws_isr_3,
sws_isr_4,
sws_isr_5,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
sws_isr_12,
sws_isr_13,
sws_isr_14,
sws_isr_15
};
SoftwareSerialNoIram::SoftwareSerialNoIram(int receivePin, int transmitPin, bool inverse_logic, unsigned int buffSize) {
m_rxValid = m_txValid = m_txEnableValid = false;
m_buffer = NULL;
m_invert = inverse_logic;
m_overflow = false;
m_rxEnabled = false;
if (isValidGPIOpin(receivePin)) {
m_rxPin = receivePin;
m_buffSize = buffSize;
m_buffer = (uint8_t*)malloc(m_buffSize);
if (m_buffer != NULL) {
m_rxValid = true;
m_inPos = m_outPos = 0;
pinMode(m_rxPin, INPUT);
ObjList[m_rxPin] = this;
enableRx(true);
}
}
if (isValidGPIOpin(transmitPin) || transmitPin == 16) {
m_txValid = true;
m_txPin = transmitPin;
pinMode(m_txPin, OUTPUT);
digitalWrite(m_txPin, !m_invert);
}
// Default speed
begin(9600);
}
SoftwareSerialNoIram::~SoftwareSerialNoIram() {
enableRx(false);
if (m_rxValid)
ObjList[m_rxPin] = NULL;
if (m_buffer)
free(m_buffer);
}
bool SoftwareSerialNoIram::isValidGPIOpin(int pin) {
return (pin >= 0 && pin <= 5) || (pin >= 12 && pin <= MAX_PIN);
}
void SoftwareSerialNoIram::begin(long speed) {
// Use getCycleCount() loop to get as exact timing as possible
m_bitTime = ESP.getCpuFreqMHz()*1000000/speed;
m_highSpeed = speed > 9600;
if (!m_rxEnabled)
enableRx(true);
}
long SoftwareSerialNoIram::baudRate() {
return ESP.getCpuFreqMHz()*1000000/m_bitTime;
}
void SoftwareSerialNoIram::setTransmitEnablePin(int transmitEnablePin) {
if (isValidGPIOpin(transmitEnablePin)) {
m_txEnableValid = true;
m_txEnablePin = transmitEnablePin;
pinMode(m_txEnablePin, OUTPUT);
digitalWrite(m_txEnablePin, LOW);
} else {
m_txEnableValid = false;
}
}
void SoftwareSerialNoIram::enableRx(bool on) {
if (m_rxValid) {
if (on)
attachInterrupt(m_rxPin, ISRList[m_rxPin], m_invert ? RISING : FALLING);
else
detachInterrupt(m_rxPin);
m_rxEnabled = on;
}
}
int SoftwareSerialNoIram::read() {
if (!m_rxValid || (m_inPos == m_outPos)) return -1;
uint8_t ch = m_buffer[m_outPos];
m_outPos = (m_outPos+1) % m_buffSize;
return ch;
}
int SoftwareSerialNoIram::available() {
if (!m_rxValid) return 0;
int avail = m_inPos - m_outPos;
if (avail < 0) avail += m_buffSize;
return avail;
}
#define WAIT { while (ESP.getCycleCount()-start < wait) if (!m_highSpeed) optimistic_yield(1); wait += m_bitTime; }
size_t SoftwareSerialNoIram::write(uint8_t b) {
if (!m_txValid) return 0;
if (m_invert) b = ~b;
if (m_highSpeed)
// Disable interrupts in order to get a clean transmit
cli();
if (m_txEnableValid) digitalWrite(m_txEnablePin, HIGH);
unsigned long wait = m_bitTime;
digitalWrite(m_txPin, HIGH);
unsigned long start = ESP.getCycleCount();
// Start bit;
digitalWrite(m_txPin, LOW);
WAIT;
for (int i = 0; i < 8; i++) {
digitalWrite(m_txPin, (b & 1) ? HIGH : LOW);
WAIT;
b >>= 1;
}
// Stop bit
digitalWrite(m_txPin, HIGH);
WAIT;
if (m_txEnableValid) digitalWrite(m_txEnablePin, LOW);
if (m_highSpeed)
sei();
return 1;
}
void SoftwareSerialNoIram::flush() {
m_inPos = m_outPos = 0;
}
bool SoftwareSerialNoIram::overflow() {
bool res = m_overflow;
m_overflow = false;
return res;
}
int SoftwareSerialNoIram::peek() {
if (!m_rxValid || (m_inPos == m_outPos)) return -1;
return m_buffer[m_outPos];
}
#ifdef TASMOTA_NO_ICACHE_RAM
void SoftwareSerialNoIram::rxRead() {
#else
void ICACHE_RAM_ATTR SoftwareSerialNoIram::rxRead() {
#endif
// Advance the starting point for the samples but compensate for the
// initial delay which occurs before the interrupt is delivered
unsigned long wait = m_bitTime + m_bitTime/3 - 500;
unsigned long start = ESP.getCycleCount();
uint8_t rec = 0;
for (int i = 0; i < 8; i++) {
WAIT;
rec >>= 1;
if (digitalRead(m_rxPin))
rec |= 0x80;
}
if (m_invert) rec = ~rec;
// Stop bit
WAIT;
// Store the received value in the buffer unless we have an overflow
int next = (m_inPos+1) % m_buffSize;
if (next != m_outPos) {
m_buffer[m_inPos] = rec;
m_inPos = next;
} else {
m_overflow = true;
}
// Must clear this bit in the interrupt register,
// it gets set even when interrupts are disabled
GPIO_REG_WRITE(GPIO_STATUS_W1TC_ADDRESS, 1 << m_rxPin);
}

View File

@ -0,0 +1,88 @@
/*
SoftwareSerialNoIram.h
SoftwareSerialNoIram.cpp - Implementation of the Arduino software serial for ESP8266 without iram usage.
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
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
*/
#ifndef SoftwareSerialNoIram_h
#define SoftwareSerialNoIram_h
#include <inttypes.h>
#include <Stream.h>
// This class is compatible with the corresponding AVR one,
// the constructor however has an optional rx buffer size.
// Speed up to 115200 can be used.
class SoftwareSerialNoIram : public Stream
{
public:
SoftwareSerialNoIram(int receivePin, int transmitPin, bool inverse_logic = false, unsigned int buffSize = 64);
~SoftwareSerialNoIram();
void begin(long speed);
long baudRate();
void setTransmitEnablePin(int transmitEnablePin);
bool overflow();
int peek();
virtual size_t write(uint8_t byte);
virtual int read();
virtual int available();
virtual void flush();
operator bool() {return m_rxValid || m_txValid;}
// Disable or enable interrupts on the rx pin
void enableRx(bool on);
void rxRead();
// AVR compatibility methods
bool listen() { enableRx(true); return true; }
void end() { stopListening(); }
bool isListening() { return m_rxEnabled; }
bool stopListening() { enableRx(false); return true; }
using Print::write;
private:
bool isValidGPIOpin(int pin);
// Member variables
int m_rxPin, m_txPin, m_txEnablePin;
bool m_rxValid, m_rxEnabled;
bool m_txValid, m_txEnableValid;
bool m_invert;
bool m_overflow;
unsigned long m_bitTime;
bool m_highSpeed;
unsigned int m_inPos, m_outPos;
int m_buffSize;
uint8_t *m_buffer;
};
// If only one tx or rx wanted then use this as parameter for the unused pin
#define SW_SERIAL_UNUSED_PIN -1
#endif

View File

@ -0,0 +1,27 @@
#include <SoftwareSerial.h>
SoftwareSerial swSer(14, 12, false, 256);
void setup() {
Serial.begin(115200);
swSer.begin(115200);
Serial.println("\nSoftware serial test started");
for (char ch = ' '; ch <= 'z'; ch++) {
swSer.write(ch);
}
swSer.println("");
}
void loop() {
while (swSer.available() > 0) {
Serial.write(swSer.read());
}
while (Serial.available() > 0) {
swSer.write(Serial.read());
}
}

View File

@ -0,0 +1,31 @@
#######################################
# Syntax Coloring Map for SoftwareSerialNoIram
# (esp8266)
#######################################
#######################################
# Datatypes (KEYWORD1)
#######################################
SoftwareSerialNoIram KEYWORD1
#######################################
# Methods and Functions (KEYWORD2)
#######################################
begin KEYWORD2
read KEYWORD2
write KEYWORD2
available KEYWORD2
flush KEYWORD2
overflow KEYWORD2
peek KEYWORD2
listen KEYWORD2
end KEYWORD2
isListening KEYWORD2
stopListening KEYWORD2
#######################################
# Constants (LITERAL1)
#######################################

View File

@ -0,0 +1,15 @@
{
"name": "EspSoftwareSerialNoIram",
"version": "3.3.1",
"keywords": [
"serial", "io", "softwareserialnoiram"
],
"description": "Implementation of the Arduino software serial for ESP8266 without iram usage.",
"repository":
{
"type": "git",
"url": "https://github.com/plerup/espsoftwareserial"
},
"frameworks": "arduino",
"platforms": "espressif8266"
}

View File

@ -0,0 +1,9 @@
name=SoftwareSerialNoIram
version=1.0
author=Peter Lerup
maintainer=Peter Lerup <peter@lerup.com>
sentence=Implementation of the Arduino software serial for ESP8266 without iram usage.
paragraph=
category=Signal Input/Output
url=
architectures=esp8266