parent
37bade9cb3
commit
bec1f0e297
@ -0,0 +1,279 @@
|
|||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
// On ESP8266:
|
||||||
|
// Local EspSoftwareSerial loopback, connect D5 (rx) and D6 (tx).
|
||||||
|
// For local hardware loopback, connect D5 to D8 (tx), D6 to D7 (rx).
|
||||||
|
// For hardware send/sink, connect D7 (rx) and D8 (tx).
|
||||||
|
// Hint: The logger is run at 9600bps such that enableIntTx(true) can remain unchanged. Blocking
|
||||||
|
// interrupts severely impacts the ability of the EspSoftwareSerial devices to operate concurrently
|
||||||
|
// and/or in duplex mode.
|
||||||
|
// Operating in software serial full duplex mode, runs at 19200bps and few errors (~2.5%).
|
||||||
|
// Operating in software serial half duplex mode (both loopback and repeater),
|
||||||
|
// runs at 57600bps with nearly no errors.
|
||||||
|
// Operating loopback in full duplex, and repeater in half duplex, runs at 38400bps with nearly no errors.
|
||||||
|
// On ESP32:
|
||||||
|
// For EspSoftwareSerial or hardware send/sink, connect D5 (rx) and D6 (tx).
|
||||||
|
// Hardware Serial2 defaults to D4 (rx), D3 (tx).
|
||||||
|
// For local hardware loopback, connect D5 (rx) to D3 (tx), D6 (tx) to D4 (rx).
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D8 (5)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D7 (23)
|
||||||
|
#define D6 (19)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Pick only one of HWLOOPBACK, HWSOURCESWSINK, or HWSOURCESINK
|
||||||
|
//#define HWLOOPBACK 1
|
||||||
|
//#define HWSOURCESWSINK 1
|
||||||
|
//#define HWSOURCESINK 1
|
||||||
|
#define HALFDUPLEX 1
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
constexpr int IUTBITRATE = 19200;
|
||||||
|
#else
|
||||||
|
constexpr int IUTBITRATE = 19200;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
constexpr EspSoftwareSerial::Config swSerialConfig = EspSoftwareSerial::SWSERIAL_8E1;
|
||||||
|
constexpr SerialConfig hwSerialConfig = ::SERIAL_8E1;
|
||||||
|
#elif defined(ESP32)
|
||||||
|
constexpr EspSoftwareSerial::Config swSerialConfig = EspSoftwareSerial::SWSERIAL_8E1;
|
||||||
|
constexpr uint32_t hwSerialConfig = ::SERIAL_8E1;
|
||||||
|
#else
|
||||||
|
constexpr unsigned swSerialConfig = 3;
|
||||||
|
#endif
|
||||||
|
constexpr bool invert = false;
|
||||||
|
|
||||||
|
constexpr int BLOCKSIZE = 16; // use fractions of 256
|
||||||
|
|
||||||
|
unsigned long start;
|
||||||
|
const char effTxTxt[] PROGMEM = "eff. tx: ";
|
||||||
|
const char effRxTxt[] PROGMEM = "eff. rx: ";
|
||||||
|
int txCount;
|
||||||
|
int rxCount;
|
||||||
|
int expected;
|
||||||
|
int rxErrors;
|
||||||
|
int rxParityErrors;
|
||||||
|
constexpr int ReportInterval = IUTBITRATE / 8;
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#if defined(HWLOOPBACK) || defined(HWSOURCESWSINK)
|
||||||
|
HardwareSerial& hwSerial(Serial);
|
||||||
|
EspSoftwareSerial::UART serialIUT;
|
||||||
|
EspSoftwareSerial::UART logger;
|
||||||
|
#elif defined(HWSOURCESINK)
|
||||||
|
HardwareSerial& serialIUT(Serial);
|
||||||
|
EspSoftwareSerial::UART logger;
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART serialIUT;
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#endif
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#if defined(HWLOOPBACK) || defined (HWSOURCESWSINK)
|
||||||
|
HardwareSerial& hwSerial(Serial2);
|
||||||
|
EspSoftwareSerial::UART serialIUT;
|
||||||
|
#elif defined(HWSOURCESINK)
|
||||||
|
HardwareSerial& serialIUT(Serial2);
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART serialIUT;
|
||||||
|
#endif
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART serialIUT(14, 12);
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#if defined(HWLOOPBACK) || defined(HWSOURCESINK) || defined(HWSOURCESWSINK)
|
||||||
|
Serial.begin(IUTBITRATE, hwSerialConfig, ::SERIAL_FULL, 1, invert);
|
||||||
|
Serial.swap();
|
||||||
|
Serial.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
logger.begin(9600, EspSoftwareSerial::SWSERIAL_8N1, -1, TX);
|
||||||
|
#else
|
||||||
|
logger.begin(9600);
|
||||||
|
#endif
|
||||||
|
#if !defined(HWSOURCESINK)
|
||||||
|
serialIUT.begin(IUTBITRATE, swSerialConfig, D5, D6, invert, 2 * BLOCKSIZE);
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
serialIUT.enableIntTx(false);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#if defined(HWLOOPBACK) || defined(HWSOURCESWSINK)
|
||||||
|
Serial2.begin(IUTBITRATE, hwSerialConfig, D4, D3, invert);
|
||||||
|
Serial2.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
#elif defined(HWSOURCESINK)
|
||||||
|
serialIUT.begin(IUTBITRATE, hwSerialConfig, D5, D6, invert);
|
||||||
|
serialIUT.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
#endif
|
||||||
|
#if !defined(HWSOURCESINK)
|
||||||
|
serialIUT.begin(IUTBITRATE, swSerialConfig, D5, D6, invert, 2 * BLOCKSIZE);
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
serialIUT.enableIntTx(false);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
logger.begin(9600);
|
||||||
|
#else
|
||||||
|
#if !defined(HWSOURCESINK)
|
||||||
|
serialIUT.begin(IUTBITRATE);
|
||||||
|
#endif
|
||||||
|
logger.begin(9600);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
logger.println(PSTR("Loopback example for EspEspSoftwareSerial"));
|
||||||
|
|
||||||
|
start = micros();
|
||||||
|
txCount = 0;
|
||||||
|
rxCount = 0;
|
||||||
|
rxErrors = 0;
|
||||||
|
rxParityErrors = 0;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char c = 0;
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
char block[BLOCKSIZE];
|
||||||
|
#endif
|
||||||
|
char inBuf[BLOCKSIZE];
|
||||||
|
for (int i = 0; i < BLOCKSIZE; ++i) {
|
||||||
|
#ifndef HALFDUPLEX
|
||||||
|
#ifdef HWSOURCESWSINK
|
||||||
|
hwSerial.write(c);
|
||||||
|
#else
|
||||||
|
serialIUT.write(c);
|
||||||
|
#endif
|
||||||
|
#ifdef HWLOOPBACK
|
||||||
|
int avail = hwSerial.available();
|
||||||
|
while ((0 == (i % 8)) && avail > 0) {
|
||||||
|
int inCnt = hwSerial.read(inBuf, min(avail, min(BLOCKSIZE, hwSerial.availableForWrite())));
|
||||||
|
hwSerial.write(inBuf, inCnt);
|
||||||
|
avail -= inCnt;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
block[i] = c;
|
||||||
|
#endif
|
||||||
|
c = (c + 1) % 256;
|
||||||
|
++txCount;
|
||||||
|
}
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
#ifdef HWSOURCESWSINK
|
||||||
|
hwSerial.write(block, BLOCKSIZE);
|
||||||
|
#else
|
||||||
|
serialIUT.write(block, BLOCKSIZE);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifdef HWSOURCESINK
|
||||||
|
#if defined(ESP8266)
|
||||||
|
if (serialIUT.hasOverrun()) { logger.println(PSTR("serialIUT.overrun")); }
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
if (serialIUT.overflow()) { logger.println(PSTR("serialIUT.overflow")); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int inCnt;
|
||||||
|
uint32_t deadlineStart;
|
||||||
|
|
||||||
|
#ifdef HWLOOPBACK
|
||||||
|
// starting deadline for the first bytes to become readable
|
||||||
|
deadlineStart = ESP.getCycleCount();
|
||||||
|
inCnt = 0;
|
||||||
|
while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 24 * ESP.getCpuFreqMHz()) {
|
||||||
|
int avail = hwSerial.available();
|
||||||
|
inCnt += hwSerial.read(&inBuf[inCnt], min(avail, min(BLOCKSIZE - inCnt, hwSerial.availableForWrite())));
|
||||||
|
if (inCnt >= BLOCKSIZE) { break; }
|
||||||
|
// wait for more outstanding bytes to trickle in
|
||||||
|
if (avail) deadlineStart = ESP.getCycleCount();
|
||||||
|
}
|
||||||
|
hwSerial.write(inBuf, inCnt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// starting deadline for the first bytes to come in
|
||||||
|
deadlineStart = ESP.getCycleCount();
|
||||||
|
inCnt = 0;
|
||||||
|
while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 8 * ESP.getCpuFreqMHz()) {
|
||||||
|
int avail;
|
||||||
|
if (0 != (swSerialConfig & 070))
|
||||||
|
avail = serialIUT.available();
|
||||||
|
else
|
||||||
|
avail = serialIUT.read(inBuf, BLOCKSIZE);
|
||||||
|
for (int i = 0; i < avail; ++i)
|
||||||
|
{
|
||||||
|
unsigned char r;
|
||||||
|
if (0 != (swSerialConfig & 070))
|
||||||
|
r = serialIUT.read();
|
||||||
|
else
|
||||||
|
r = inBuf[i];
|
||||||
|
if (expected == -1) { expected = r; }
|
||||||
|
else {
|
||||||
|
expected = (expected + 1) % (1UL << (5 + swSerialConfig % 4));
|
||||||
|
}
|
||||||
|
if (r != expected) {
|
||||||
|
++rxErrors;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
#ifndef HWSOURCESINK
|
||||||
|
if (serialIUT.readParity() != (static_cast<bool>(swSerialConfig & 010) ? serialIUT.parityOdd(r) : serialIUT.parityEven(r)))
|
||||||
|
{
|
||||||
|
++rxParityErrors;
|
||||||
|
}
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
// current ESP8266 API does not flag parity errors separately
|
||||||
|
if (serialIUT.hasRxError())
|
||||||
|
{
|
||||||
|
++rxParityErrors;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
++rxCount;
|
||||||
|
++inCnt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (inCnt >= BLOCKSIZE) { break; }
|
||||||
|
// wait for more outstanding bytes to trickle in
|
||||||
|
if (avail) deadlineStart = ESP.getCycleCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint32_t interval = micros() - start;
|
||||||
|
if (txCount >= ReportInterval && interval) {
|
||||||
|
uint8_t wordBits = (5 + swSerialConfig % 4) + static_cast<bool>(swSerialConfig & 070) + 1 + ((swSerialConfig & 0300) ? 1 : 0);
|
||||||
|
logger.println(String(PSTR("tx/rx: ")) + txCount + PSTR("/") + rxCount);
|
||||||
|
const long txCps = txCount * (1000000.0 / interval);
|
||||||
|
const long rxCps = rxCount * (1000000.0 / interval);
|
||||||
|
logger.print(String(FPSTR(effTxTxt)) + wordBits * txCps + PSTR("bps, ")
|
||||||
|
+ effRxTxt + wordBits * rxCps + PSTR("bps, ")
|
||||||
|
+ rxErrors + PSTR(" errors (") + 100.0 * rxErrors / (!rxErrors ? 1 : rxCount) + PSTR("%)"));
|
||||||
|
if (0 != (swSerialConfig & 070))
|
||||||
|
{
|
||||||
|
logger.print(PSTR(" (")); logger.print(rxParityErrors); logger.println(PSTR(" parity errors)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
logger.println();
|
||||||
|
}
|
||||||
|
txCount = 0;
|
||||||
|
rxCount = 0;
|
||||||
|
rxErrors = 0;
|
||||||
|
rxParityErrors = 0;
|
||||||
|
expected = -1;
|
||||||
|
// resync
|
||||||
|
delay(1000UL * 12 * BLOCKSIZE / IUTBITRATE * 16);
|
||||||
|
serialIUT.flush();
|
||||||
|
start = micros();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,59 @@
|
|||||||
|
#include "SoftwareSerial.h"
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D6 (12)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D6 (19)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
EspSoftwareSerial::UART swSer1;
|
||||||
|
EspSoftwareSerial::UART swSer2;
|
||||||
|
|
||||||
|
void checkSwSerial(EspSoftwareSerial::UART* ss) {
|
||||||
|
byte ch;
|
||||||
|
while (!Serial.available());
|
||||||
|
ss->enableTx(true);
|
||||||
|
while (Serial.available()) {
|
||||||
|
ch = Serial.read();
|
||||||
|
ss->write(ch);
|
||||||
|
}
|
||||||
|
ss->enableTx(false);
|
||||||
|
// wait 1 second for the reply from EspSoftwareSerial if any
|
||||||
|
delay(1000);
|
||||||
|
if (ss->available()) {
|
||||||
|
Serial.print(PSTR("\nResult:"));
|
||||||
|
while (ss->available()) {
|
||||||
|
ch = (byte)ss->read();
|
||||||
|
Serial.print(ch < 0x10 ? PSTR(" 0") : PSTR(" "));
|
||||||
|
Serial.print(ch, HEX);
|
||||||
|
}
|
||||||
|
Serial.println();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
delay(2000);
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.println(PSTR("\nOne Wire Half Duplex Serial Tester"));
|
||||||
|
swSer1.begin(115200, EspSoftwareSerial::SWSERIAL_8N1, D6, D6, false, 256);
|
||||||
|
// high speed half duplex, turn off interrupts during tx
|
||||||
|
swSer1.enableIntTx(false);
|
||||||
|
swSer2.begin(115200, EspSoftwareSerial::SWSERIAL_8N1, D5, D5, false, 256);
|
||||||
|
// high speed half duplex, turn off interrupts during tx
|
||||||
|
swSer2.enableIntTx(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
Serial.println(PSTR("\n\nTesting on swSer1"));
|
||||||
|
Serial.print(PSTR("Enter something to send using swSer1."));
|
||||||
|
checkSwSerial(&swSer1);
|
||||||
|
|
||||||
|
Serial.println(PSTR("\n\nTesting on swSer2"));
|
||||||
|
Serial.print(PSTR("Enter something to send using swSer2."));
|
||||||
|
checkSwSerial(&swSer2);
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,80 @@
|
|||||||
|
// On ESP8266:
|
||||||
|
// Runs up to 115200bps at 80MHz, 250000bps at 160MHz, with nearly zero errors.
|
||||||
|
// This example is currently not ported to ESP32, which is based on FreeRTOS.
|
||||||
|
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define BAUD_RATE 115200
|
||||||
|
#define MAX_FRAMEBITS (1 + 8 + 1 + 2)
|
||||||
|
|
||||||
|
EspSoftwareSerial::UART testSerial;
|
||||||
|
|
||||||
|
// Becomes set from ISR / IRQ callback function.
|
||||||
|
std::atomic<bool> rxPending(false);
|
||||||
|
|
||||||
|
void IRAM_ATTR receiveHandler() {
|
||||||
|
rxPending.store(true);
|
||||||
|
esp_schedule();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
Serial.setDebugOutput(false);
|
||||||
|
Serial.swap();
|
||||||
|
testSerial.begin(BAUD_RATE, EspSoftwareSerial::SWSERIAL_8N1, RX, TX);
|
||||||
|
// Only half duplex this way, but reliable TX timings for high bps
|
||||||
|
testSerial.enableIntTx(false);
|
||||||
|
testSerial.onReceive(receiveHandler);
|
||||||
|
|
||||||
|
testSerial.println(PSTR("\nSoftware serial onReceive() event test started"));
|
||||||
|
|
||||||
|
for (char ch = ' '; ch <= 'z'; ch++) {
|
||||||
|
testSerial.write(ch);
|
||||||
|
}
|
||||||
|
testSerial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
#ifdef ESP8266
|
||||||
|
bool isRxPending = rxPending.load();
|
||||||
|
if (isRxPending) {
|
||||||
|
rxPending.store(false);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
bool isRxPending = m_isrOverflow.exchange(false);
|
||||||
|
#endif
|
||||||
|
auto avail = testSerial.available();
|
||||||
|
if (isRxPending && !avail) {
|
||||||
|
// event fired on start bit, wait until first stop bit of longest frame
|
||||||
|
delayMicroseconds(1 + MAX_FRAMEBITS * 1000000 / BAUD_RATE);
|
||||||
|
avail = testSerial.available();
|
||||||
|
}
|
||||||
|
if (!avail) {
|
||||||
|
// On development board, idle power draw at USB:
|
||||||
|
// with yield() 77mA, 385mW (160MHz: 82mA, 410mW)
|
||||||
|
// with esp_suspend() 20mA, 100mW (at 160MHz, too)
|
||||||
|
//yield();
|
||||||
|
esp_suspend();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// try to force to half-duplex
|
||||||
|
decltype(avail) prev_avail;
|
||||||
|
do {
|
||||||
|
delayMicroseconds(1 + MAX_FRAMEBITS * 1000000 / BAUD_RATE);
|
||||||
|
prev_avail = avail;
|
||||||
|
} while (prev_avail != (avail = testSerial.available()));
|
||||||
|
while (avail > 0) {
|
||||||
|
testSerial.write(testSerial.read());
|
||||||
|
avail = testSerial.available();
|
||||||
|
}
|
||||||
|
testSerial.println();
|
||||||
|
}
|
@ -0,0 +1,199 @@
|
|||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
// On ESP8266:
|
||||||
|
// EspSoftwareSerial loopback for remote source (loopback.ino), or hardware loopback.
|
||||||
|
// Connect source D5 (rx) to local D8 (tx), source D6 (tx) to local D7 (rx).
|
||||||
|
// Hint: The logger is run at 9600bps such that enableIntTx(true) can remain unchanged. Blocking
|
||||||
|
// interrupts severely impacts the ability of the EspSoftwareSerial devices to operate concurrently
|
||||||
|
// and/or in duplex mode.
|
||||||
|
// On ESP32:
|
||||||
|
// For software or hardware loopback, connect source rx to local D8 (tx), source tx to local D7 (rx).
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D8 (5)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D7 (23)
|
||||||
|
#define D6 (19)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define HWLOOPBACK 1
|
||||||
|
#define HALFDUPLEX 1
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
constexpr int IUTBITRATE = 19200;
|
||||||
|
#else
|
||||||
|
constexpr int IUTBITRATE = 19200;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
constexpr EspSoftwareSerial::Config swSerialConfig = EspSoftwareSerial::SWSERIAL_8E1;
|
||||||
|
constexpr SerialConfig hwSerialConfig = ::SERIAL_8E1;
|
||||||
|
#elif defined(ESP32)
|
||||||
|
constexpr EspSoftwareSerial::Config swSerialConfig = EspSoftwareSerial::SWSERIAL_8E1;
|
||||||
|
constexpr uint32_t hwSerialConfig = ::SERIAL_8E1;
|
||||||
|
#else
|
||||||
|
constexpr unsigned swSerialConfig = 3;
|
||||||
|
#endif
|
||||||
|
constexpr bool invert = false;
|
||||||
|
|
||||||
|
constexpr int BLOCKSIZE = 16; // use fractions of 256
|
||||||
|
|
||||||
|
unsigned long start;
|
||||||
|
const char bitRateTxt[] PROGMEM = "Effective data rate: ";
|
||||||
|
int rxCount;
|
||||||
|
int seqErrors;
|
||||||
|
int parityErrors;
|
||||||
|
int expected;
|
||||||
|
constexpr int ReportInterval = IUTBITRATE / 8;
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#if defined(HWLOOPBACK)
|
||||||
|
HardwareSerial& repeater(Serial);
|
||||||
|
EspSoftwareSerial::UART logger;
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART repeater;
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#endif
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#if defined(HWLOOPBACK)
|
||||||
|
HardwareSerial& repeater(Serial2);
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART repeater;
|
||||||
|
#endif
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART repeater(14, 12);
|
||||||
|
HardwareSerial& logger(Serial);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#if defined(HWLOOPBACK)
|
||||||
|
repeater.begin(IUTBITRATE, hwSerialConfig, ::SERIAL_FULL, 1, invert);
|
||||||
|
repeater.swap();
|
||||||
|
repeater.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
logger.begin(9600, EspSoftwareSerial::SWSERIAL_8N1, -1, TX);
|
||||||
|
#else
|
||||||
|
repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, invert, 4 * BLOCKSIZE);
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
repeater.enableIntTx(false);
|
||||||
|
#endif
|
||||||
|
logger.begin(9600);
|
||||||
|
#endif
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#if defined(HWLOOPBACK)
|
||||||
|
repeater.begin(IUTBITRATE, hwSerialConfig, D7, D8, invert);
|
||||||
|
repeater.setRxBufferSize(2 * BLOCKSIZE);
|
||||||
|
#else
|
||||||
|
repeater.begin(IUTBITRATE, swSerialConfig, D7, D8, invert, 4 * BLOCKSIZE);
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
repeater.enableIntTx(false);
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
logger.begin(9600);
|
||||||
|
#else
|
||||||
|
repeater.begin(IUTBITRATE);
|
||||||
|
logger.begin(9600);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
logger.println(PSTR("Repeater example for EspEspSoftwareSerial"));
|
||||||
|
start = micros();
|
||||||
|
rxCount = 0;
|
||||||
|
seqErrors = 0;
|
||||||
|
parityErrors = 0;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
#ifdef HWLOOPBACK
|
||||||
|
#if defined(ESP8266)
|
||||||
|
if (repeater.hasOverrun()) { logger.println(PSTR("repeater.overrun")); }
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
if (repeater.overflow()) { logger.println(PSTR("repeater.overflow")); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
char block[BLOCKSIZE];
|
||||||
|
#endif
|
||||||
|
// starting deadline for the first bytes to come in
|
||||||
|
uint32_t deadlineStart = ESP.getCycleCount();
|
||||||
|
int inCnt = 0;
|
||||||
|
while ((ESP.getCycleCount() - deadlineStart) < (1000000UL * 12 * BLOCKSIZE) / IUTBITRATE * 24 * ESP.getCpuFreqMHz()) {
|
||||||
|
int avail = repeater.available();
|
||||||
|
for (int i = 0; i < avail; ++i)
|
||||||
|
{
|
||||||
|
int r = repeater.read();
|
||||||
|
if (r == -1) { logger.println(PSTR("read() == -1")); }
|
||||||
|
if (expected == -1) { expected = r; }
|
||||||
|
else {
|
||||||
|
expected = (expected + 1) % (1UL << (5 + swSerialConfig % 4));
|
||||||
|
}
|
||||||
|
if (r != expected) {
|
||||||
|
++seqErrors;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
#ifndef HWLOOPBACK
|
||||||
|
if (repeater.readParity() != (static_cast<bool>(swSerialConfig & 010) ? repeater.parityOdd(r) : repeater.parityEven(r)))
|
||||||
|
{
|
||||||
|
++parityErrors;
|
||||||
|
}
|
||||||
|
#elif defined(ESP8266)
|
||||||
|
// current ESP8266 API does not flag parity errors separately
|
||||||
|
if (repeater.hasRxError())
|
||||||
|
{
|
||||||
|
++parityErrors;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
++rxCount;
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
block[inCnt] = r;
|
||||||
|
#else
|
||||||
|
repeater.write(r);
|
||||||
|
#endif
|
||||||
|
if (++inCnt >= BLOCKSIZE) { break; }
|
||||||
|
}
|
||||||
|
if (inCnt >= BLOCKSIZE) { break; }
|
||||||
|
// wait for more outstanding bytes to trickle in
|
||||||
|
if (avail) deadlineStart = ESP.getCycleCount();
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef HALFDUPLEX
|
||||||
|
repeater.write(block, inCnt);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (rxCount >= ReportInterval) {
|
||||||
|
auto end = micros();
|
||||||
|
unsigned long interval = end - start;
|
||||||
|
long cps = rxCount * (1000000.0 / interval);
|
||||||
|
long seqErrorsps = seqErrors * (1000000.0 / interval);
|
||||||
|
logger.print(String(FPSTR(bitRateTxt)) + 10 * cps + PSTR("bps, ")
|
||||||
|
+ seqErrorsps + PSTR("cps seq. errors (") + 100.0 * seqErrors / rxCount + PSTR("%)"));
|
||||||
|
#ifndef HWLOOPBACK
|
||||||
|
if (0 != (swSerialConfig & 070))
|
||||||
|
{
|
||||||
|
logger.print(PSTR(" (")); logger.print(parityErrors); logger.println(PSTR(" parity errors)"));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
logger.println();
|
||||||
|
}
|
||||||
|
start = end;
|
||||||
|
rxCount = 0;
|
||||||
|
seqErrors = 0;
|
||||||
|
parityErrors = 0;
|
||||||
|
expected = -1;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
// On ESP8266:
|
||||||
|
// At 80MHz runs up 57600bps, and at 160MHz CPU frequency up to 115200bps with only negligible errors.
|
||||||
|
// Connect pin 13 to 15.
|
||||||
|
// For verification and as a example for how to use SW serial on the USB to PC connection,
|
||||||
|
// which allows the use of HW Serial on GPIO13 and GPIO15 instead, #define SWAPSERIAL below.
|
||||||
|
// Notice how the bitrates are also swapped then between RX/TX and GPIO13/GPIO15.
|
||||||
|
// Builtin debug output etc. must be stopped on HW Serial in this case, as it would interfere with the
|
||||||
|
// external communication on GPIO13/GPIO15.
|
||||||
|
|
||||||
|
#include <SoftwareSerial.h>
|
||||||
|
|
||||||
|
#ifndef D5
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#define D8 (15)
|
||||||
|
#define D5 (14)
|
||||||
|
#define D7 (13)
|
||||||
|
#define D6 (12)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#elif defined(ESP32)
|
||||||
|
#define D8 (5)
|
||||||
|
#define D5 (18)
|
||||||
|
#define D7 (23)
|
||||||
|
#define D6 (19)
|
||||||
|
#define RX (3)
|
||||||
|
#define TX (1)
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef ESP32
|
||||||
|
#define BAUD_RATE 57600
|
||||||
|
#else
|
||||||
|
#define BAUD_RATE 57600
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#undef SWAPSERIAL
|
||||||
|
|
||||||
|
#ifndef SWAPSERIAL
|
||||||
|
auto& usbSerial = Serial;
|
||||||
|
EspSoftwareSerial::UART testSerial;
|
||||||
|
#else
|
||||||
|
EspSoftwareSerial::UART usbSerial;
|
||||||
|
auto& testSerial = Serial;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setup() {
|
||||||
|
#ifndef SWAPSERIAL
|
||||||
|
usbSerial.begin(115200);
|
||||||
|
// Important: the buffer size optimizations here, in particular the isrBufSize (11) that is only sufficiently
|
||||||
|
// large to hold a single word (up to start - 8 data - parity - stop), are on the basis that any char written
|
||||||
|
// to the loopback EspSoftwareSerial adapter gets read before another write is performed.
|
||||||
|
// Block writes with a size greater than 1 would usually fail. Do not copy this into your own project without
|
||||||
|
// reading the documentation.
|
||||||
|
testSerial.begin(BAUD_RATE, EspSoftwareSerial::SWSERIAL_8N1, D7, D8, false, 95, 11);
|
||||||
|
#else
|
||||||
|
testSerial.begin(115200);
|
||||||
|
testSerial.setDebugOutput(false);
|
||||||
|
testSerial.swap();
|
||||||
|
usbSerial.begin(BAUD_RATE, EspSoftwareSerial::SWSERIAL_8N1, RX, TX, false, 95);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
usbSerial.println(PSTR("\nSoftware serial test started"));
|
||||||
|
|
||||||
|
for (char ch = ' '; ch <= 'z'; ch++) {
|
||||||
|
testSerial.write(ch);
|
||||||
|
}
|
||||||
|
testSerial.println();
|
||||||
|
}
|
||||||
|
|
||||||
|
void loop() {
|
||||||
|
while (testSerial.available() > 0) {
|
||||||
|
usbSerial.write(testSerial.read());
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
while (usbSerial.available() > 0) {
|
||||||
|
testSerial.write(usbSerial.read());
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
#######################################
|
||||||
|
# Syntax Coloring Map for EspSoftwareSerial
|
||||||
|
# (esp8266)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Datatypes (KEYWORD1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
EspSoftwareSerial KEYWORD1
|
||||||
|
SoftwareSerial KEYWORD1
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Methods and Functions (KEYWORD2)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
begin KEYWORD2
|
||||||
|
baudRate KEYWORD2
|
||||||
|
setTransmitEnablePin KEYWORD2
|
||||||
|
enableIntTx KEYWORD2
|
||||||
|
overflow KEYWORD2
|
||||||
|
available KEYWORD2
|
||||||
|
peek KEYWORD2
|
||||||
|
read KEYWORD2
|
||||||
|
flush KEYWORD2
|
||||||
|
write KEYWORD2
|
||||||
|
enableRx KEYWORD2
|
||||||
|
enableTx KEYWORD2
|
||||||
|
listen KEYWORD2
|
||||||
|
end KEYWORD2
|
||||||
|
isListening KEYWORD2
|
||||||
|
stopListening KEYWORD2
|
||||||
|
onReceive KEYWORD2
|
||||||
|
|
||||||
|
#######################################
|
||||||
|
# Constants (LITERAL1)
|
||||||
|
#######################################
|
||||||
|
|
||||||
|
SW_SERIAL_UNUSED_PIN LITERAL1
|
||||||
|
SWSERIAL_5N1 LITERAL1
|
||||||
|
SWSERIAL_6N1 LITERAL1
|
||||||
|
SWSERIAL_7N1 LITERAL1
|
||||||
|
SWSERIAL_8N1 LITERAL1
|
@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "EspSoftwareSerial",
|
||||||
|
"version": "8.1.0",
|
||||||
|
"description": "Implementation of the Arduino software serial for ESP8266/ESP32.",
|
||||||
|
"keywords": [
|
||||||
|
"serial", "io", "softwareserial"
|
||||||
|
],
|
||||||
|
"repository":
|
||||||
|
{
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/plerup/espsoftwareserial"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Dirk Kaar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Peter Lerup"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "LGPL-2.1+",
|
||||||
|
"frameworks": "arduino",
|
||||||
|
"platforms": [
|
||||||
|
"espressif8266", "espressif32"
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,9 @@
|
|||||||
|
name=EspSoftwareSerial
|
||||||
|
version=8.1.0
|
||||||
|
author=Dirk Kaar, Peter Lerup
|
||||||
|
maintainer=Dirk Kaar <dok@dok-net.net>
|
||||||
|
sentence=Implementation of the Arduino software serial for ESP8266/ESP32.
|
||||||
|
paragraph=
|
||||||
|
category=Signal Input/Output
|
||||||
|
url=https://github.com/plerup/espsoftwareserial/
|
||||||
|
architectures=esp8266,esp32
|
@ -0,0 +1,621 @@
|
|||||||
|
/*
|
||||||
|
|
||||||
|
SoftwareSerial.cpp - Implementation of the Arduino software serial for ESP8266/ESP32.
|
||||||
|
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
|
||||||
|
Copyright (c) 2018-2019 Dirk O. Kaar. 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
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "SoftwareSerial.h"
|
||||||
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
using namespace EspSoftwareSerial;
|
||||||
|
|
||||||
|
#ifndef ESP32
|
||||||
|
uint32_t UARTBase::m_savedPS = 0;
|
||||||
|
#else
|
||||||
|
portMUX_TYPE UARTBase::m_interruptsMux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ALWAYS_INLINE_ATTR inline void IRAM_ATTR UARTBase::disableInterrupts()
|
||||||
|
{
|
||||||
|
#ifndef ESP32
|
||||||
|
m_savedPS = xt_rsil(15);
|
||||||
|
#else
|
||||||
|
taskENTER_CRITICAL(&m_interruptsMux);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
ALWAYS_INLINE_ATTR inline void IRAM_ATTR UARTBase::restoreInterrupts()
|
||||||
|
{
|
||||||
|
#ifndef ESP32
|
||||||
|
xt_wsr_ps(m_savedPS);
|
||||||
|
#else
|
||||||
|
taskEXIT_CRITICAL(&m_interruptsMux);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr uint8_t BYTE_ALL_BITS_SET = ~static_cast<uint8_t>(0);
|
||||||
|
|
||||||
|
UARTBase::UARTBase() {
|
||||||
|
}
|
||||||
|
|
||||||
|
UARTBase::UARTBase(int8_t rxPin, int8_t txPin, bool invert)
|
||||||
|
{
|
||||||
|
m_rxPin = rxPin;
|
||||||
|
m_txPin = txPin;
|
||||||
|
m_invert = invert;
|
||||||
|
}
|
||||||
|
|
||||||
|
UARTBase::~UARTBase() {
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::setRxGPIOPinMode() {
|
||||||
|
if (m_rxValid) {
|
||||||
|
pinMode(m_rxPin, m_rxGPIOHasPullUp && m_rxGPIOPullUpEnabled ? INPUT_PULLUP : INPUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::setTxGPIOPinMode() {
|
||||||
|
if (m_txValid) {
|
||||||
|
pinMode(m_txPin, m_txGPIOOpenDrain ? OUTPUT_OPEN_DRAIN : OUTPUT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin, int8_t txPin,
|
||||||
|
bool invert) {
|
||||||
|
if (-1 != rxPin) m_rxPin = rxPin;
|
||||||
|
if (-1 != txPin) m_txPin = txPin;
|
||||||
|
m_oneWire = (m_rxPin == m_txPin);
|
||||||
|
m_invert = invert;
|
||||||
|
m_dataBits = 5 + (config & 07);
|
||||||
|
m_parityMode = static_cast<Parity>(config & 070);
|
||||||
|
m_stopBits = 1 + ((config & 0300) ? 1 : 0);
|
||||||
|
m_pduBits = m_dataBits + static_cast<bool>(m_parityMode) + m_stopBits;
|
||||||
|
m_bitTicks = (microsToTicks(1000000UL) + baud / 2) / baud;
|
||||||
|
m_intTxEnabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::beginRx(bool hasPullUp, int bufCapacity, int isrBufCapacity) {
|
||||||
|
m_rxGPIOHasPullUp = hasPullUp;
|
||||||
|
m_rxReg = portInputRegister(digitalPinToPort(m_rxPin));
|
||||||
|
m_rxBitMask = digitalPinToBitMask(m_rxPin);
|
||||||
|
m_buffer.reset(new circular_queue<uint8_t>((bufCapacity > 0) ? bufCapacity : 64));
|
||||||
|
if (m_parityMode)
|
||||||
|
{
|
||||||
|
m_parityBuffer.reset(new circular_queue<uint8_t>((m_buffer->capacity() + 7) / 8));
|
||||||
|
m_parityInPos = m_parityOutPos = 1;
|
||||||
|
}
|
||||||
|
m_isrBuffer.reset(new circular_queue<uint32_t, UARTBase*>((isrBufCapacity > 0) ?
|
||||||
|
isrBufCapacity : m_buffer->capacity() * (2 + m_dataBits + static_cast<bool>(m_parityMode))));
|
||||||
|
if (m_buffer && (!m_parityMode || m_parityBuffer) && m_isrBuffer) {
|
||||||
|
m_rxValid = true;
|
||||||
|
setRxGPIOPinMode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::beginTx() {
|
||||||
|
#if !defined(ESP8266)
|
||||||
|
m_txReg = portOutputRegister(digitalPinToPort(m_txPin));
|
||||||
|
#endif
|
||||||
|
m_txBitMask = digitalPinToBitMask(m_txPin);
|
||||||
|
m_txValid = true;
|
||||||
|
if (!m_oneWire) {
|
||||||
|
setTxGPIOPinMode();
|
||||||
|
digitalWrite(m_txPin, !m_invert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::end()
|
||||||
|
{
|
||||||
|
enableRx(false);
|
||||||
|
m_txValid = false;
|
||||||
|
if (m_buffer) {
|
||||||
|
m_buffer.reset();
|
||||||
|
}
|
||||||
|
m_parityBuffer.reset();
|
||||||
|
if (m_isrBuffer) {
|
||||||
|
m_isrBuffer.reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t UARTBase::baudRate() {
|
||||||
|
return 1000000UL / ticksToMicros(m_bitTicks);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::setTransmitEnablePin(int8_t txEnablePin) {
|
||||||
|
if (-1 != txEnablePin) {
|
||||||
|
m_txEnableValid = true;
|
||||||
|
m_txEnablePin = txEnablePin;
|
||||||
|
pinMode(m_txEnablePin, OUTPUT);
|
||||||
|
digitalWrite(m_txEnablePin, LOW);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_txEnableValid = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableIntTx(bool on) {
|
||||||
|
m_intTxEnabled = on;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableRxGPIOPullUp(bool on) {
|
||||||
|
m_rxGPIOPullUpEnabled = on;
|
||||||
|
setRxGPIOPinMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableTxGPIOOpenDrain(bool on) {
|
||||||
|
m_txGPIOOpenDrain = on;
|
||||||
|
setTxGPIOPinMode();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableTx(bool on) {
|
||||||
|
if (m_txValid && m_oneWire) {
|
||||||
|
if (on) {
|
||||||
|
enableRx(false);
|
||||||
|
setTxGPIOPinMode();
|
||||||
|
digitalWrite(m_txPin, !m_invert);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setRxGPIOPinMode();
|
||||||
|
enableRx(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::enableRx(bool on) {
|
||||||
|
if (m_rxValid && on != m_rxEnabled) {
|
||||||
|
if (on) {
|
||||||
|
m_rxLastBit = m_pduBits - 1;
|
||||||
|
// Init to stop bit level and current tick
|
||||||
|
m_isrLastTick = (microsToTicks(micros()) | 1) ^ m_invert;
|
||||||
|
if (m_bitTicks >= microsToTicks(1000000UL / 74880UL))
|
||||||
|
attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast<void (*)(void*)>(rxBitISR), this, CHANGE);
|
||||||
|
else
|
||||||
|
attachInterruptArg(digitalPinToInterrupt(m_rxPin), reinterpret_cast<void (*)(void*)>(rxBitSyncISR), this, m_invert ? RISING : FALLING);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
detachInterrupt(digitalPinToInterrupt(m_rxPin));
|
||||||
|
}
|
||||||
|
m_rxEnabled = on;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int UARTBase::read() {
|
||||||
|
if (!m_rxValid) { return -1; }
|
||||||
|
if (!m_buffer->available()) {
|
||||||
|
rxBits();
|
||||||
|
if (!m_buffer->available()) { return -1; }
|
||||||
|
}
|
||||||
|
auto val = m_buffer->pop();
|
||||||
|
if (m_parityBuffer)
|
||||||
|
{
|
||||||
|
m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos;
|
||||||
|
m_parityOutPos <<= 1;
|
||||||
|
if (!m_parityOutPos)
|
||||||
|
{
|
||||||
|
m_parityOutPos = 1;
|
||||||
|
m_parityBuffer->pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UARTBase::read(uint8_t* buffer, size_t size) {
|
||||||
|
if (!m_rxValid) { return 0; }
|
||||||
|
int avail;
|
||||||
|
if (0 == (avail = m_buffer->pop_n(buffer, size))) {
|
||||||
|
rxBits();
|
||||||
|
avail = m_buffer->pop_n(buffer, size);
|
||||||
|
}
|
||||||
|
if (!avail) return 0;
|
||||||
|
if (m_parityBuffer) {
|
||||||
|
uint32_t parityBits = avail;
|
||||||
|
while (m_parityOutPos >>= 1) ++parityBits;
|
||||||
|
m_parityOutPos = (1 << (parityBits % 8));
|
||||||
|
m_parityBuffer->pop_n(nullptr, parityBits / 8);
|
||||||
|
}
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UARTBase::readBytes(uint8_t* buffer, size_t size) {
|
||||||
|
if (!m_rxValid || !size) { return 0; }
|
||||||
|
size_t count = 0;
|
||||||
|
auto start = millis();
|
||||||
|
do {
|
||||||
|
auto readCnt = read(&buffer[count], size - count);
|
||||||
|
count += readCnt;
|
||||||
|
if (count >= size) break;
|
||||||
|
if (readCnt) {
|
||||||
|
start = millis();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
optimistic_yield(1000UL);
|
||||||
|
}
|
||||||
|
} while (millis() - start < _timeout);
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UARTBase::available() {
|
||||||
|
if (!m_rxValid) { return 0; }
|
||||||
|
rxBits();
|
||||||
|
int avail = m_buffer->available();
|
||||||
|
if (!avail) {
|
||||||
|
optimistic_yield(10000UL);
|
||||||
|
}
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::lazyDelay() {
|
||||||
|
// Reenable interrupts while delaying to avoid other tasks piling up
|
||||||
|
if (!m_intTxEnabled) { restoreInterrupts(); }
|
||||||
|
const auto expired = microsToTicks(micros()) - m_periodStart;
|
||||||
|
const int32_t remaining = m_periodDuration - expired;
|
||||||
|
const uint32_t ms = remaining > 0 ? ticksToMicros(remaining) / 1000UL : 0;
|
||||||
|
if (ms > 0)
|
||||||
|
{
|
||||||
|
delay(ms);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
optimistic_yield(10000UL);
|
||||||
|
}
|
||||||
|
// Assure that below-ms part of delays are not elided
|
||||||
|
preciseDelay();
|
||||||
|
// Disable interrupts again if applicable
|
||||||
|
if (!m_intTxEnabled) { disableInterrupts(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR UARTBase::preciseDelay() {
|
||||||
|
uint32_t ticks;
|
||||||
|
do {
|
||||||
|
ticks = microsToTicks(micros());
|
||||||
|
} while ((ticks - m_periodStart) < m_periodDuration);
|
||||||
|
m_periodDuration = 0;
|
||||||
|
m_periodStart = ticks;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR UARTBase::writePeriod(
|
||||||
|
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit) {
|
||||||
|
preciseDelay();
|
||||||
|
if (dutyCycle)
|
||||||
|
{
|
||||||
|
#if defined(ESP8266)
|
||||||
|
if (16 == m_txPin) {
|
||||||
|
GP16O = 1;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOS = m_txBitMask;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
*m_txReg = *m_txReg | m_txBitMask;
|
||||||
|
#endif
|
||||||
|
m_periodDuration += dutyCycle;
|
||||||
|
if (offCycle || (withStopBit && !m_invert)) {
|
||||||
|
if (!withStopBit || m_invert) {
|
||||||
|
preciseDelay();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
lazyDelay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (offCycle)
|
||||||
|
{
|
||||||
|
#if defined(ESP8266)
|
||||||
|
if (16 == m_txPin) {
|
||||||
|
GP16O = 0;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
GPOC = m_txBitMask;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
*m_txReg = *m_txReg & ~m_txBitMask;
|
||||||
|
#endif
|
||||||
|
m_periodDuration += offCycle;
|
||||||
|
if (withStopBit && m_invert) lazyDelay();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UARTBase::write(uint8_t byte) {
|
||||||
|
return write(&byte, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UARTBase::write(uint8_t byte, Parity parity) {
|
||||||
|
return write(&byte, 1, parity);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t UARTBase::write(const uint8_t* buffer, size_t size) {
|
||||||
|
return write(buffer, size, m_parityMode);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IRAM_ATTR UARTBase::write(const uint8_t* buffer, size_t size, Parity parity) {
|
||||||
|
if (m_rxValid) { rxBits(); }
|
||||||
|
if (!m_txValid) { return -1; }
|
||||||
|
|
||||||
|
if (m_txEnableValid) {
|
||||||
|
digitalWrite(m_txEnablePin, HIGH);
|
||||||
|
}
|
||||||
|
// Stop bit: if inverted, LOW, otherwise HIGH
|
||||||
|
bool b = !m_invert;
|
||||||
|
uint32_t dutyCycle = 0;
|
||||||
|
uint32_t offCycle = 0;
|
||||||
|
if (!m_intTxEnabled) {
|
||||||
|
// Disable interrupts in order to get a clean transmit timing
|
||||||
|
disableInterrupts();
|
||||||
|
}
|
||||||
|
const uint32_t dataMask = ((1UL << m_dataBits) - 1);
|
||||||
|
bool withStopBit = true;
|
||||||
|
m_periodDuration = 0;
|
||||||
|
m_periodStart = microsToTicks(micros());
|
||||||
|
for (size_t cnt = 0; cnt < size; ++cnt) {
|
||||||
|
uint8_t byte = pgm_read_byte(buffer + cnt) & dataMask;
|
||||||
|
// push LSB start-data-parity-stop bit pattern into uint32_t
|
||||||
|
// Stop bits: HIGH
|
||||||
|
uint32_t word = ~0UL;
|
||||||
|
// inverted parity bit, performance tweak for xor all-bits-set word
|
||||||
|
if (parity && m_parityMode)
|
||||||
|
{
|
||||||
|
uint32_t parityBit;
|
||||||
|
switch (parity)
|
||||||
|
{
|
||||||
|
case PARITY_EVEN:
|
||||||
|
// from inverted, so use odd parity
|
||||||
|
parityBit = byte;
|
||||||
|
parityBit ^= parityBit >> 4;
|
||||||
|
parityBit &= 0xf;
|
||||||
|
parityBit = (0x9669 >> parityBit) & 1;
|
||||||
|
break;
|
||||||
|
case PARITY_ODD:
|
||||||
|
// from inverted, so use even parity
|
||||||
|
parityBit = byte;
|
||||||
|
parityBit ^= parityBit >> 4;
|
||||||
|
parityBit &= 0xf;
|
||||||
|
parityBit = (0x6996 >> parityBit) & 1;
|
||||||
|
break;
|
||||||
|
case PARITY_MARK:
|
||||||
|
parityBit = 0;
|
||||||
|
break;
|
||||||
|
case PARITY_SPACE:
|
||||||
|
// suppresses warning parityBit uninitialized
|
||||||
|
default:
|
||||||
|
parityBit = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
word ^= parityBit;
|
||||||
|
}
|
||||||
|
word <<= m_dataBits;
|
||||||
|
word |= byte;
|
||||||
|
// Start bit: LOW
|
||||||
|
word <<= 1;
|
||||||
|
if (m_invert) word = ~word;
|
||||||
|
for (int i = 0; i <= m_pduBits; ++i) {
|
||||||
|
bool pb = b;
|
||||||
|
b = word & (1UL << i);
|
||||||
|
if (!pb && b) {
|
||||||
|
writePeriod(dutyCycle, offCycle, withStopBit);
|
||||||
|
withStopBit = false;
|
||||||
|
dutyCycle = offCycle = 0;
|
||||||
|
}
|
||||||
|
if (b) {
|
||||||
|
dutyCycle += m_bitTicks;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
offCycle += m_bitTicks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
withStopBit = true;
|
||||||
|
}
|
||||||
|
writePeriod(dutyCycle, offCycle, true);
|
||||||
|
if (!m_intTxEnabled) {
|
||||||
|
// restore the interrupt state if applicable
|
||||||
|
restoreInterrupts();
|
||||||
|
}
|
||||||
|
if (m_txEnableValid) {
|
||||||
|
digitalWrite(m_txEnablePin, LOW);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::flush() {
|
||||||
|
if (!m_rxValid) { return; }
|
||||||
|
m_buffer->flush();
|
||||||
|
if (m_parityBuffer)
|
||||||
|
{
|
||||||
|
m_parityInPos = m_parityOutPos = 1;
|
||||||
|
m_parityBuffer->flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UARTBase::overflow() {
|
||||||
|
bool res = m_overflow;
|
||||||
|
m_overflow = false;
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
int UARTBase::peek() {
|
||||||
|
if (!m_rxValid) { return -1; }
|
||||||
|
if (!m_buffer->available()) {
|
||||||
|
rxBits();
|
||||||
|
if (!m_buffer->available()) return -1;
|
||||||
|
}
|
||||||
|
auto val = m_buffer->peek();
|
||||||
|
if (m_parityBuffer) m_lastReadParity = m_parityBuffer->peek() & m_parityOutPos;
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::rxBits() {
|
||||||
|
#ifdef ESP8266
|
||||||
|
if (m_isrOverflow.load()) {
|
||||||
|
m_overflow = true;
|
||||||
|
m_isrOverflow.store(false);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (m_isrOverflow.exchange(false)) {
|
||||||
|
m_overflow = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
m_isrBuffer->for_each(m_isrBufferForEachDel);
|
||||||
|
|
||||||
|
// A stop bit can go undetected if leading data bits are at same level
|
||||||
|
// and there was also no next start bit yet, so one word may be pending.
|
||||||
|
// Check that there was no new ISR data received in the meantime, inserting an
|
||||||
|
// extraneous stop level bit out of sequence breaks rx.
|
||||||
|
if (m_rxLastBit < m_pduBits - 1) {
|
||||||
|
const uint32_t detectionTicks = (m_pduBits - 1 - m_rxLastBit) * m_bitTicks;
|
||||||
|
if (!m_isrBuffer->available() && microsToTicks(micros()) - m_isrLastTick > detectionTicks) {
|
||||||
|
// Produce faux stop bit level, prevents start bit maldetection
|
||||||
|
// tick's LSB is repurposed for the level bit
|
||||||
|
rxBits(((m_isrLastTick + detectionTicks) | 1) ^ m_invert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::rxBits(const uint32_t isrTick) {
|
||||||
|
const bool level = (m_isrLastTick & 1) ^ m_invert;
|
||||||
|
|
||||||
|
// error introduced by edge value in LSB of isrTick is negligible
|
||||||
|
uint32_t ticks = isrTick - m_isrLastTick;
|
||||||
|
m_isrLastTick = isrTick;
|
||||||
|
|
||||||
|
uint32_t bits = ticks / m_bitTicks;
|
||||||
|
if (ticks % m_bitTicks > (m_bitTicks >> 1)) ++bits;
|
||||||
|
while (bits > 0) {
|
||||||
|
// start bit detection
|
||||||
|
if (m_rxLastBit >= (m_pduBits - 1)) {
|
||||||
|
// leading edge of start bit?
|
||||||
|
if (level) break;
|
||||||
|
m_rxLastBit = -1;
|
||||||
|
--bits;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// data bits
|
||||||
|
if (m_rxLastBit < (m_dataBits - 1)) {
|
||||||
|
uint8_t dataBits = min(bits, static_cast<uint32_t>(m_dataBits - 1 - m_rxLastBit));
|
||||||
|
m_rxLastBit += dataBits;
|
||||||
|
bits -= dataBits;
|
||||||
|
m_rxCurByte >>= dataBits;
|
||||||
|
if (level) { m_rxCurByte |= (BYTE_ALL_BITS_SET << (8 - dataBits)); }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// parity bit
|
||||||
|
if (m_parityMode && m_rxLastBit == (m_dataBits - 1)) {
|
||||||
|
++m_rxLastBit;
|
||||||
|
--bits;
|
||||||
|
m_rxCurParity = level;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// stop bits
|
||||||
|
// Store the received value in the buffer unless we have an overflow
|
||||||
|
// if not high stop bit level, discard word
|
||||||
|
if (bits >= static_cast<uint32_t>(m_pduBits - 1 - m_rxLastBit) && level) {
|
||||||
|
m_rxCurByte >>= (sizeof(uint8_t) * 8 - m_dataBits);
|
||||||
|
if (!m_buffer->push(m_rxCurByte)) {
|
||||||
|
m_overflow = true;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (m_parityBuffer)
|
||||||
|
{
|
||||||
|
if (m_rxCurParity) {
|
||||||
|
m_parityBuffer->pushpeek() |= m_parityInPos;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
m_parityBuffer->pushpeek() &= ~m_parityInPos;
|
||||||
|
}
|
||||||
|
m_parityInPos <<= 1;
|
||||||
|
if (!m_parityInPos)
|
||||||
|
{
|
||||||
|
m_parityBuffer->push();
|
||||||
|
m_parityInPos = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_rxLastBit = m_pduBits - 1;
|
||||||
|
// reset to 0 is important for masked bit logic
|
||||||
|
m_rxCurByte = 0;
|
||||||
|
m_rxCurParity = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR UARTBase::rxBitISR(UARTBase* self) {
|
||||||
|
const bool level = *self->m_rxReg & self->m_rxBitMask;
|
||||||
|
const uint32_t curTick = microsToTicks(micros());
|
||||||
|
const bool empty = !self->m_isrBuffer->available();
|
||||||
|
|
||||||
|
// Store level and tick in the buffer unless we have an overflow
|
||||||
|
// tick's LSB is repurposed for the level bit
|
||||||
|
if (!self->m_isrBuffer->push((curTick | 1U) ^ !level)) self->m_isrOverflow.store(true);
|
||||||
|
// Trigger rx callback only when receiver is starved
|
||||||
|
if (empty) self->m_rxHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR UARTBase::rxBitSyncISR(UARTBase* self) {
|
||||||
|
bool level = self->m_invert;
|
||||||
|
const uint32_t start = microsToTicks(micros());
|
||||||
|
uint32_t wait = self->m_bitTicks;
|
||||||
|
const bool empty = !self->m_isrBuffer->available();
|
||||||
|
|
||||||
|
// Store level and tick in the buffer unless we have an overflow
|
||||||
|
// tick's LSB is repurposed for the level bit
|
||||||
|
if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ !level)) self->m_isrOverflow.store(true);
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < self->m_pduBits; ++i) {
|
||||||
|
while (microsToTicks(micros()) - start < wait) {};
|
||||||
|
wait += self->m_bitTicks;
|
||||||
|
|
||||||
|
// Store level and tick in the buffer unless we have an overflow
|
||||||
|
// tick's LSB is repurposed for the level bit
|
||||||
|
if (static_cast<bool>(*self->m_rxReg & self->m_rxBitMask) != level)
|
||||||
|
{
|
||||||
|
if (!self->m_isrBuffer->push(((start + wait) | 1U) ^ level)) self->m_isrOverflow.store(true);
|
||||||
|
level = !level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Trigger rx callback only when receiver is starved
|
||||||
|
if (empty) self->m_rxHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::onReceive(const Delegate<void(), void*>& handler) {
|
||||||
|
disableInterrupts();
|
||||||
|
m_rxHandler = handler;
|
||||||
|
restoreInterrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
void UARTBase::onReceive(Delegate<void(), void*>&& handler) {
|
||||||
|
disableInterrupts();
|
||||||
|
m_rxHandler = std::move(handler);
|
||||||
|
restoreInterrupts();
|
||||||
|
}
|
||||||
|
|
||||||
|
#if __GNUC__ < 12
|
||||||
|
// The template member functions below must be in IRAM, but due to a bug GCC doesn't currently
|
||||||
|
// honor the attribute. Instead, it is possible to do explicit specialization and adorn
|
||||||
|
// these with the IRAM attribute:
|
||||||
|
// Delegate<>::operator (), circular_queue<>::available,
|
||||||
|
// circular_queue<>::available_for_push, circular_queue<>::push_peek, circular_queue<>::push
|
||||||
|
|
||||||
|
template void IRAM_ATTR delegate::detail::DelegateImpl<void*, void>::operator()() const;
|
||||||
|
template size_t IRAM_ATTR circular_queue<uint32_t, UARTBase*>::available() const;
|
||||||
|
template bool IRAM_ATTR circular_queue<uint32_t, UARTBase*>::push(uint32_t&&);
|
||||||
|
template bool IRAM_ATTR circular_queue<uint32_t, UARTBase*>::push(const uint32_t&);
|
||||||
|
#endif // __GNUC__ < 12
|
||||||
|
|
@ -0,0 +1,449 @@
|
|||||||
|
/*
|
||||||
|
SoftwareSerial.h - Implementation of the Arduino software serial for ESP8266/ESP32.
|
||||||
|
Copyright (c) 2015-2016 Peter Lerup. All rights reserved.
|
||||||
|
Copyright (c) 2018-2019 Dirk O. Kaar. 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 __SoftwareSerial_h
|
||||||
|
#define __SoftwareSerial_h
|
||||||
|
|
||||||
|
#include "circular_queue/circular_queue.h"
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
namespace EspSoftwareSerial {
|
||||||
|
|
||||||
|
// Interface definition for template argument of BasicUART
|
||||||
|
class IGpioCapabilities {
|
||||||
|
public:
|
||||||
|
static constexpr bool isValidPin(int8_t pin);
|
||||||
|
static constexpr bool isValidInputPin(int8_t pin);
|
||||||
|
static constexpr bool isValidOutputPin(int8_t pin);
|
||||||
|
// result is only defined for a valid Rx pin
|
||||||
|
static constexpr bool hasPullUp(int8_t pin);
|
||||||
|
};
|
||||||
|
|
||||||
|
class GpioCapabilities : private IGpioCapabilities {
|
||||||
|
public:
|
||||||
|
static constexpr bool isValidPin(int8_t pin) {
|
||||||
|
#if defined(ESP8266)
|
||||||
|
return (pin >= 0 && pin <= 16) && !isFlashInterfacePin(pin);
|
||||||
|
#elif defined(ESP32)
|
||||||
|
// Remove the strapping pins as defined in the datasheets, they affect bootup and other critical operations
|
||||||
|
// Remmove the flash memory pins on related devices, since using these causes memory access issues.
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32_datasheet_en.pdf,
|
||||||
|
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32/_images/esp32-devkitC-v4-pinout.jpg
|
||||||
|
return (pin == 1) || (pin >= 3 && pin <= 5) ||
|
||||||
|
(pin >= 12 && pin <= 15) ||
|
||||||
|
(!psramFound() && pin >= 16 && pin <= 17) ||
|
||||||
|
(pin >= 18 && pin <= 19) ||
|
||||||
|
(pin >= 21 && pin <= 23) || (pin >= 25 && pin <= 27) || (pin >= 32 && pin <= 39);
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-s2_datasheet_en.pdf,
|
||||||
|
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/_images/esp32-s2_saola1-pinout.jpg
|
||||||
|
return (pin >= 1 && pin <= 21) || (pin >= 33 && pin <= 44);
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
// Datasheet https://www.espressif.com/sites/default/files/documentation/esp32-c3_datasheet_en.pdf,
|
||||||
|
// Pinout https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/_images/esp32-c3-devkitm-1-v1-pinout.jpg
|
||||||
|
return (pin >= 0 && pin <= 1) || (pin >= 3 && pin <= 7) || (pin >= 18 && pin <= 21);
|
||||||
|
#else
|
||||||
|
return pin >= 0;
|
||||||
|
#endif
|
||||||
|
#else
|
||||||
|
return pin >= 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr bool isValidInputPin(int8_t pin) {
|
||||||
|
return isValidPin(pin)
|
||||||
|
#if defined(ESP8266)
|
||||||
|
&& (pin != 16)
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr bool isValidOutputPin(int8_t pin) {
|
||||||
|
return isValidPin(pin)
|
||||||
|
#if defined(ESP32)
|
||||||
|
#ifdef CONFIG_IDF_TARGET_ESP32
|
||||||
|
&& (pin < 34)
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
&& (pin <= 45)
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
// no restrictions
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
// result is only defined for a valid Rx pin
|
||||||
|
static constexpr bool hasPullUp(int8_t pin) {
|
||||||
|
#if defined(ESP32)
|
||||||
|
return !(pin >= 34 && pin <= 39);
|
||||||
|
#else
|
||||||
|
(void)pin;
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Parity : uint8_t {
|
||||||
|
PARITY_NONE = 000,
|
||||||
|
PARITY_EVEN = 020,
|
||||||
|
PARITY_ODD = 030,
|
||||||
|
PARITY_MARK = 040,
|
||||||
|
PARITY_SPACE = 070,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Config {
|
||||||
|
SWSERIAL_5N1 = PARITY_NONE,
|
||||||
|
SWSERIAL_6N1,
|
||||||
|
SWSERIAL_7N1,
|
||||||
|
SWSERIAL_8N1,
|
||||||
|
SWSERIAL_5E1 = PARITY_EVEN,
|
||||||
|
SWSERIAL_6E1,
|
||||||
|
SWSERIAL_7E1,
|
||||||
|
SWSERIAL_8E1,
|
||||||
|
SWSERIAL_5O1 = PARITY_ODD,
|
||||||
|
SWSERIAL_6O1,
|
||||||
|
SWSERIAL_7O1,
|
||||||
|
SWSERIAL_8O1,
|
||||||
|
SWSERIAL_5M1 = PARITY_MARK,
|
||||||
|
SWSERIAL_6M1,
|
||||||
|
SWSERIAL_7M1,
|
||||||
|
SWSERIAL_8M1,
|
||||||
|
SWSERIAL_5S1 = PARITY_SPACE,
|
||||||
|
SWSERIAL_6S1,
|
||||||
|
SWSERIAL_7S1,
|
||||||
|
SWSERIAL_8S1,
|
||||||
|
SWSERIAL_5N2 = 0200 | PARITY_NONE,
|
||||||
|
SWSERIAL_6N2,
|
||||||
|
SWSERIAL_7N2,
|
||||||
|
SWSERIAL_8N2,
|
||||||
|
SWSERIAL_5E2 = 0200 | PARITY_EVEN,
|
||||||
|
SWSERIAL_6E2,
|
||||||
|
SWSERIAL_7E2,
|
||||||
|
SWSERIAL_8E2,
|
||||||
|
SWSERIAL_5O2 = 0200 | PARITY_ODD,
|
||||||
|
SWSERIAL_6O2,
|
||||||
|
SWSERIAL_7O2,
|
||||||
|
SWSERIAL_8O2,
|
||||||
|
SWSERIAL_5M2 = 0200 | PARITY_MARK,
|
||||||
|
SWSERIAL_6M2,
|
||||||
|
SWSERIAL_7M2,
|
||||||
|
SWSERIAL_8M2,
|
||||||
|
SWSERIAL_5S2 = 0200 | PARITY_SPACE,
|
||||||
|
SWSERIAL_6S2,
|
||||||
|
SWSERIAL_7S2,
|
||||||
|
SWSERIAL_8S2,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// This class is compatible with the corresponding AVR one, however,
|
||||||
|
/// the constructor takes no arguments, for compatibility with the
|
||||||
|
/// HardwareSerial class.
|
||||||
|
/// Instead, the begin() function handles pin assignments and logic inversion.
|
||||||
|
/// It also has optional input buffer capacity arguments for byte buffer and ISR bit buffer.
|
||||||
|
/// Bitrates up to at least 115200 can be used.
|
||||||
|
class UARTBase : public Stream {
|
||||||
|
public:
|
||||||
|
UARTBase();
|
||||||
|
/// Ctor to set defaults for pins.
|
||||||
|
/// @param rxPin the GPIO pin used for RX
|
||||||
|
/// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX
|
||||||
|
UARTBase(int8_t rxPin, int8_t txPin = -1, bool invert = false);
|
||||||
|
UARTBase(const UARTBase&) = delete;
|
||||||
|
UARTBase& operator= (const UARTBase&) = delete;
|
||||||
|
virtual ~UARTBase();
|
||||||
|
/// Configure the UARTBase object for use.
|
||||||
|
/// @param baud the TX/RX bitrate
|
||||||
|
/// @param config sets databits, parity, and stop bit count
|
||||||
|
/// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor
|
||||||
|
/// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor
|
||||||
|
/// @param invert true: uses invert line level logic
|
||||||
|
/// @param bufCapacity the capacity for the received bytes buffer
|
||||||
|
/// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous
|
||||||
|
/// bit receive buffer, a suggested size is bufCapacity times the sum of
|
||||||
|
/// start, data, parity and stop bit count.
|
||||||
|
void begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin, int8_t txPin, bool invert);
|
||||||
|
|
||||||
|
uint32_t baudRate();
|
||||||
|
/// Transmit control pin.
|
||||||
|
void setTransmitEnablePin(int8_t txEnablePin);
|
||||||
|
/// Enable (default) or disable interrupts during tx.
|
||||||
|
void enableIntTx(bool on);
|
||||||
|
/// Enable (default) or disable internal rx GPIO pull-up.
|
||||||
|
void enableRxGPIOPullUp(bool on);
|
||||||
|
/// Enable or disable (default) tx GPIO output mode.
|
||||||
|
void enableTxGPIOOpenDrain(bool on);
|
||||||
|
|
||||||
|
bool overflow();
|
||||||
|
|
||||||
|
int available() override;
|
||||||
|
#if defined(ESP8266)
|
||||||
|
int availableForWrite() override {
|
||||||
|
#else
|
||||||
|
int availableForWrite() {
|
||||||
|
#endif
|
||||||
|
if (!m_txValid) return 0;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
int peek() override;
|
||||||
|
int read() override;
|
||||||
|
/// @returns The verbatim parity bit associated with the last successful read() or peek() call
|
||||||
|
bool readParity()
|
||||||
|
{
|
||||||
|
return m_lastReadParity;
|
||||||
|
}
|
||||||
|
/// @returns The calculated bit for even parity of the parameter byte
|
||||||
|
static bool parityEven(uint8_t byte) {
|
||||||
|
byte ^= byte >> 4;
|
||||||
|
byte &= 0xf;
|
||||||
|
return (0x6996 >> byte) & 1;
|
||||||
|
}
|
||||||
|
/// @returns The calculated bit for odd parity of the parameter byte
|
||||||
|
static bool parityOdd(uint8_t byte) {
|
||||||
|
byte ^= byte >> 4;
|
||||||
|
byte &= 0xf;
|
||||||
|
return (0x9669 >> byte) & 1;
|
||||||
|
}
|
||||||
|
/// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout
|
||||||
|
int read(uint8_t* buffer, size_t size)
|
||||||
|
#if defined(ESP8266)
|
||||||
|
override
|
||||||
|
#endif
|
||||||
|
;
|
||||||
|
/// The read(buffer, size) functions are non-blocking, the same as readBytes but without timeout
|
||||||
|
int read(char* buffer, size_t size) {
|
||||||
|
return read(reinterpret_cast<uint8_t*>(buffer), size);
|
||||||
|
}
|
||||||
|
/// @returns The number of bytes read into buffer, up to size. Times out if the limit set through
|
||||||
|
/// Stream::setTimeout() is reached.
|
||||||
|
size_t readBytes(uint8_t* buffer, size_t size) override;
|
||||||
|
/// @returns The number of bytes read into buffer, up to size. Times out if the limit set through
|
||||||
|
/// Stream::setTimeout() is reached.
|
||||||
|
size_t readBytes(char* buffer, size_t size) override {
|
||||||
|
return readBytes(reinterpret_cast<uint8_t*>(buffer), size);
|
||||||
|
}
|
||||||
|
void flush() override;
|
||||||
|
size_t write(uint8_t byte) override;
|
||||||
|
size_t write(uint8_t byte, Parity parity);
|
||||||
|
size_t write(const uint8_t* buffer, size_t size) override;
|
||||||
|
size_t write(const char* buffer, size_t size) {
|
||||||
|
return write(reinterpret_cast<const uint8_t*>(buffer), size);
|
||||||
|
}
|
||||||
|
size_t write(const uint8_t* buffer, size_t size, Parity parity);
|
||||||
|
size_t write(const char* buffer, size_t size, Parity parity) {
|
||||||
|
return write(reinterpret_cast<const uint8_t*>(buffer), size, parity);
|
||||||
|
}
|
||||||
|
operator bool() const {
|
||||||
|
return (-1 == m_rxPin || m_rxValid) && (-1 == m_txPin || m_txValid) && !(-1 == m_rxPin && m_oneWire);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Disable or enable interrupts on the rx pin.
|
||||||
|
void enableRx(bool on);
|
||||||
|
/// One wire control.
|
||||||
|
void enableTx(bool on);
|
||||||
|
|
||||||
|
// AVR compatibility methods.
|
||||||
|
bool listen() { enableRx(true); return true; }
|
||||||
|
void end();
|
||||||
|
bool isListening() { return m_rxEnabled; }
|
||||||
|
bool stopListening() { enableRx(false); return true; }
|
||||||
|
|
||||||
|
/// onReceive sets a callback that will be called in interrupt context
|
||||||
|
/// when data is received.
|
||||||
|
/// More precisely, the callback is triggered when UARTBase detects
|
||||||
|
/// a new reception, which may not yet have completed on invocation.
|
||||||
|
/// Reading - never from this interrupt context - should therefore be
|
||||||
|
/// delayed at least for the duration of one incoming word.
|
||||||
|
void onReceive(const Delegate<void(), void*>& handler);
|
||||||
|
/// onReceive sets a callback that will be called in interrupt context
|
||||||
|
/// when data is received.
|
||||||
|
/// More precisely, the callback is triggered when UARTBase detects
|
||||||
|
/// a new reception, which may not yet have completed on invocation.
|
||||||
|
/// Reading - never from this interrupt context - should therefore be
|
||||||
|
/// delayed at least for the duration of one incoming word.
|
||||||
|
void onReceive(Delegate<void(), void*>&& handler);
|
||||||
|
|
||||||
|
[[deprecated("function removed; semantics of onReceive() changed; check the header file.")]]
|
||||||
|
void perform_work();
|
||||||
|
|
||||||
|
using Print::write;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void beginRx(bool hasPullUp, int bufCapacity, int isrBufCapacity);
|
||||||
|
void beginTx();
|
||||||
|
// Member variables
|
||||||
|
int8_t m_rxPin = -1;
|
||||||
|
int8_t m_txPin = -1;
|
||||||
|
bool m_invert = false;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// It's legal to exceed the deadline, for instance,
|
||||||
|
// by enabling interrupts.
|
||||||
|
void lazyDelay();
|
||||||
|
// Synchronous precise delay
|
||||||
|
void preciseDelay();
|
||||||
|
// If withStopBit is set, either cycle contains a stop bit.
|
||||||
|
// If dutyCycle == 0, the level is not forced to HIGH.
|
||||||
|
// If offCycle == 0, the level remains unchanged from dutyCycle.
|
||||||
|
void writePeriod(
|
||||||
|
uint32_t dutyCycle, uint32_t offCycle, bool withStopBit);
|
||||||
|
// safely set the pin mode for the Rx GPIO pin
|
||||||
|
void setRxGPIOPinMode();
|
||||||
|
// safely set the pin mode for the Tx GPIO pin
|
||||||
|
void setTxGPIOPinMode();
|
||||||
|
/* check m_rxValid that calling is safe */
|
||||||
|
void rxBits();
|
||||||
|
void rxBits(const uint32_t isrTick);
|
||||||
|
static void disableInterrupts();
|
||||||
|
static void restoreInterrupts();
|
||||||
|
|
||||||
|
static void rxBitISR(UARTBase* self);
|
||||||
|
static void rxBitSyncISR(UARTBase* self);
|
||||||
|
|
||||||
|
static inline uint32_t IRAM_ATTR microsToTicks(uint32_t micros) ALWAYS_INLINE_ATTR {
|
||||||
|
return micros << 1;
|
||||||
|
}
|
||||||
|
static inline uint32_t ticksToMicros(uint32_t ticks) ALWAYS_INLINE_ATTR {
|
||||||
|
return ticks >> 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Member variables
|
||||||
|
volatile uint32_t* m_rxReg;
|
||||||
|
uint32_t m_rxBitMask;
|
||||||
|
#if !defined(ESP8266)
|
||||||
|
volatile uint32_t* m_txReg;
|
||||||
|
#endif
|
||||||
|
uint32_t m_txBitMask;
|
||||||
|
int8_t m_txEnablePin = -1;
|
||||||
|
uint8_t m_dataBits;
|
||||||
|
bool m_oneWire;
|
||||||
|
bool m_rxValid = false;
|
||||||
|
bool m_rxEnabled = false;
|
||||||
|
bool m_txValid = false;
|
||||||
|
bool m_txEnableValid = false;
|
||||||
|
/// PDU bits include data, parity and stop bits; the start bit is not counted.
|
||||||
|
uint8_t m_pduBits;
|
||||||
|
bool m_intTxEnabled;
|
||||||
|
bool m_rxGPIOHasPullUp = false;
|
||||||
|
bool m_rxGPIOPullUpEnabled = true;
|
||||||
|
bool m_txGPIOOpenDrain = false;
|
||||||
|
Parity m_parityMode;
|
||||||
|
uint8_t m_stopBits;
|
||||||
|
bool m_lastReadParity;
|
||||||
|
bool m_overflow = false;
|
||||||
|
uint32_t m_bitTicks;
|
||||||
|
uint8_t m_parityInPos;
|
||||||
|
uint8_t m_parityOutPos;
|
||||||
|
int8_t m_rxLastBit; // 0 thru (m_pduBits - m_stopBits - 1): data/parity bits. -1: start bit. (m_pduBits - 1): stop bit.
|
||||||
|
uint8_t m_rxCurByte = 0;
|
||||||
|
std::unique_ptr<circular_queue<uint8_t> > m_buffer;
|
||||||
|
std::unique_ptr<circular_queue<uint8_t> > m_parityBuffer;
|
||||||
|
uint32_t m_periodStart;
|
||||||
|
uint32_t m_periodDuration;
|
||||||
|
#ifndef ESP32
|
||||||
|
static uint32_t m_savedPS;
|
||||||
|
#else
|
||||||
|
static portMUX_TYPE m_interruptsMux;
|
||||||
|
#endif
|
||||||
|
// the ISR stores the relative bit times in the buffer. The inversion corrected level is used as sign bit (2's complement):
|
||||||
|
// 1 = positive including 0, 0 = negative.
|
||||||
|
std::unique_ptr<circular_queue<uint32_t, UARTBase*> > m_isrBuffer;
|
||||||
|
const Delegate<void(uint32_t&&), UARTBase*> m_isrBufferForEachDel { [](UARTBase* self, uint32_t&& isrTick) { self->rxBits(isrTick); }, this };
|
||||||
|
std::atomic<bool> m_isrOverflow { false };
|
||||||
|
uint32_t m_isrLastTick;
|
||||||
|
bool m_rxCurParity = false;
|
||||||
|
Delegate<void(), void*> m_rxHandler;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< class GpioCapabilities > class BasicUART : public UARTBase {
|
||||||
|
static_assert(std::is_base_of<IGpioCapabilities, GpioCapabilities>::value,
|
||||||
|
"template argument is not derived from IGpioCapabilities");
|
||||||
|
public:
|
||||||
|
BasicUART() : UARTBase() {
|
||||||
|
}
|
||||||
|
/// Ctor to set defaults for pins.
|
||||||
|
/// @param rxPin the GPIO pin used for RX
|
||||||
|
/// @param txPin -1 for onewire protocol, GPIO pin used for twowire TX
|
||||||
|
BasicUART(int8_t rxPin, int8_t txPin = -1, bool invert = false) :
|
||||||
|
UARTBase(rxPin, txPin, invert) {
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configure the BasicUART object for use.
|
||||||
|
/// @param baud the TX/RX bitrate
|
||||||
|
/// @param config sets databits, parity, and stop bit count
|
||||||
|
/// @param rxPin -1 or default: either no RX pin, or keeps the rxPin set in the ctor
|
||||||
|
/// @param txPin -1 or default: either no TX pin (onewire), or keeps the txPin set in the ctor
|
||||||
|
/// @param invert true: uses invert line level logic
|
||||||
|
/// @param bufCapacity the capacity for the received bytes buffer
|
||||||
|
/// @param isrBufCapacity 0: derived from bufCapacity. The capacity of the internal asynchronous
|
||||||
|
/// bit receive buffer, a suggested size is bufCapacity times the sum of
|
||||||
|
/// start, data, parity and stop bit count.
|
||||||
|
void begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin, int8_t txPin, bool invert,
|
||||||
|
int bufCapacity = 64, int isrBufCapacity = 0) {
|
||||||
|
UARTBase::begin(baud, config, rxPin, txPin, invert);
|
||||||
|
if (GpioCapabilities::isValidInputPin(rxPin)) {
|
||||||
|
beginRx(GpioCapabilities:: hasPullUp(rxPin), bufCapacity, isrBufCapacity);
|
||||||
|
}
|
||||||
|
if (GpioCapabilities::isValidOutputPin(txPin)) {
|
||||||
|
beginTx();
|
||||||
|
}
|
||||||
|
enableRx(true);
|
||||||
|
}
|
||||||
|
void begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin, int8_t txPin) {
|
||||||
|
begin(baud, config, rxPin, txPin, m_invert);
|
||||||
|
}
|
||||||
|
void begin(uint32_t baud, Config config,
|
||||||
|
int8_t rxPin) {
|
||||||
|
begin(baud, config, rxPin, m_txPin, m_invert);
|
||||||
|
}
|
||||||
|
void begin(uint32_t baud, Config config = SWSERIAL_8N1) {
|
||||||
|
begin(baud, config, m_rxPin, m_txPin, m_invert);
|
||||||
|
}
|
||||||
|
void setTransmitEnablePin(int8_t txEnablePin) {
|
||||||
|
UARTBase::setTransmitEnablePin(
|
||||||
|
GpioCapabilities::isValidOutputPin(txEnablePin) ? txEnablePin : -1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
using UART = BasicUART< GpioCapabilities >;
|
||||||
|
|
||||||
|
}; // namespace EspSoftwareSerial
|
||||||
|
|
||||||
|
using SoftwareSerial = EspSoftwareSerial::UART;
|
||||||
|
using namespace EspSoftwareSerial;
|
||||||
|
|
||||||
|
#if __GNUC__ < 12
|
||||||
|
// The template member functions below must be in IRAM, but due to a bug GCC doesn't currently
|
||||||
|
// honor the attribute. Instead, it is possible to do explicit specialization and adorn
|
||||||
|
// these with the IRAM attribute:
|
||||||
|
// Delegate<>::operator (), circular_queue<>::available,
|
||||||
|
// circular_queue<>::available_for_push, circular_queue<>::push_peek, circular_queue<>::push
|
||||||
|
|
||||||
|
extern template void delegate::detail::DelegateImpl<void*, void>::operator()() const;
|
||||||
|
extern template size_t circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::available() const;
|
||||||
|
extern template bool circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::push(uint32_t&&);
|
||||||
|
extern template bool circular_queue<uint32_t, EspSoftwareSerial::UARTBase*>::push(const uint32_t&);
|
||||||
|
#endif // __GNUC__ < 12
|
||||||
|
|
||||||
|
#endif // __SoftwareSerial_h
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,567 @@
|
|||||||
|
/*
|
||||||
|
MultiDelegate.h - A queue or event multiplexer based on the efficient Delegate
|
||||||
|
class
|
||||||
|
Copyright (c) 2019-2020 Dirk O. Kaar. 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 __MULTIDELEGATE_H
|
||||||
|
#define __MULTIDELEGATE_H
|
||||||
|
|
||||||
|
#include <iterator>
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
#include <atomic>
|
||||||
|
#else
|
||||||
|
#include "circular_queue/ghostl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#include <interrupts.h>
|
||||||
|
using esp8266::InterruptLock;
|
||||||
|
#elif defined(ARDUINO)
|
||||||
|
class InterruptLock {
|
||||||
|
public:
|
||||||
|
InterruptLock() {
|
||||||
|
noInterrupts();
|
||||||
|
}
|
||||||
|
~InterruptLock() {
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
#else
|
||||||
|
#include <mutex>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE = false, typename... P>
|
||||||
|
struct CallP
|
||||||
|
{
|
||||||
|
static R execute(Delegate& del, P... args)
|
||||||
|
{
|
||||||
|
return del(std::forward<P...>(args...));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, bool ISQUEUE, typename... P>
|
||||||
|
struct CallP<Delegate, void, ISQUEUE, P...>
|
||||||
|
{
|
||||||
|
static bool execute(Delegate& del, P... args)
|
||||||
|
{
|
||||||
|
del(std::forward<P...>(args...));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE = false>
|
||||||
|
struct Call
|
||||||
|
{
|
||||||
|
static R execute(Delegate& del)
|
||||||
|
{
|
||||||
|
return del();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, bool ISQUEUE>
|
||||||
|
struct Call<Delegate, void, ISQUEUE>
|
||||||
|
{
|
||||||
|
static bool execute(Delegate& del)
|
||||||
|
{
|
||||||
|
del();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace delegate
|
||||||
|
{
|
||||||
|
namespace detail
|
||||||
|
{
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32, typename... P>
|
||||||
|
class MultiDelegatePImpl
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
MultiDelegatePImpl() = default;
|
||||||
|
~MultiDelegatePImpl()
|
||||||
|
{
|
||||||
|
*this = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl(const MultiDelegatePImpl&) = delete;
|
||||||
|
MultiDelegatePImpl& operator=(const MultiDelegatePImpl&) = delete;
|
||||||
|
|
||||||
|
MultiDelegatePImpl(MultiDelegatePImpl&& md)
|
||||||
|
{
|
||||||
|
first = md.first;
|
||||||
|
last = md.last;
|
||||||
|
unused = md.unused;
|
||||||
|
nodeCount = md.nodeCount;
|
||||||
|
md.first = nullptr;
|
||||||
|
md.last = nullptr;
|
||||||
|
md.unused = nullptr;
|
||||||
|
md.nodeCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl(const Delegate& del)
|
||||||
|
{
|
||||||
|
add(del);
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl(Delegate&& del)
|
||||||
|
{
|
||||||
|
add(std::move(del));
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl& operator=(MultiDelegatePImpl&& md)
|
||||||
|
{
|
||||||
|
first = md.first;
|
||||||
|
last = md.last;
|
||||||
|
unused = md.unused;
|
||||||
|
nodeCount = md.nodeCount;
|
||||||
|
md.first = nullptr;
|
||||||
|
md.last = nullptr;
|
||||||
|
md.unused = nullptr;
|
||||||
|
md.nodeCount = 0;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl& operator=(std::nullptr_t)
|
||||||
|
{
|
||||||
|
if (last)
|
||||||
|
last->mNext = unused;
|
||||||
|
if (first)
|
||||||
|
unused = first;
|
||||||
|
while (unused)
|
||||||
|
{
|
||||||
|
auto to_delete = unused;
|
||||||
|
unused = unused->mNext;
|
||||||
|
delete(to_delete);
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl& operator+=(const Delegate& del)
|
||||||
|
{
|
||||||
|
add(del);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
MultiDelegatePImpl& operator+=(Delegate&& del)
|
||||||
|
{
|
||||||
|
add(std::move(del));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
struct Node_t
|
||||||
|
{
|
||||||
|
~Node_t()
|
||||||
|
{
|
||||||
|
mDelegate = nullptr; // special overload in Delegate
|
||||||
|
}
|
||||||
|
Node_t* mNext = nullptr;
|
||||||
|
Delegate mDelegate;
|
||||||
|
};
|
||||||
|
|
||||||
|
Node_t* first = nullptr;
|
||||||
|
Node_t* last = nullptr;
|
||||||
|
Node_t* unused = nullptr;
|
||||||
|
size_t nodeCount = 0;
|
||||||
|
|
||||||
|
// Returns a pointer to an unused Node_t,
|
||||||
|
// or if none are available allocates a new one,
|
||||||
|
// or nullptr if limit is reached
|
||||||
|
Node_t* IRAM_ATTR get_node_unsafe()
|
||||||
|
{
|
||||||
|
Node_t* result = nullptr;
|
||||||
|
// try to get an item from unused items list
|
||||||
|
if (unused)
|
||||||
|
{
|
||||||
|
result = unused;
|
||||||
|
unused = unused->mNext;
|
||||||
|
}
|
||||||
|
// if no unused items, and count not too high, allocate a new one
|
||||||
|
else if (nodeCount < QUEUE_CAPACITY)
|
||||||
|
{
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
result = new (std::nothrow) Node_t;
|
||||||
|
#else
|
||||||
|
result = new Node_t;
|
||||||
|
#endif
|
||||||
|
if (result)
|
||||||
|
++nodeCount;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void recycle_node_unsafe(Node_t* node)
|
||||||
|
{
|
||||||
|
node->mDelegate = nullptr; // special overload in Delegate
|
||||||
|
node->mNext = unused;
|
||||||
|
unused = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef ARDUINO
|
||||||
|
std::mutex mutex_unused;
|
||||||
|
#endif
|
||||||
|
public:
|
||||||
|
class iterator : public std::iterator<std::forward_iterator_tag, Delegate>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
Node_t* current = nullptr;
|
||||||
|
Node_t* prev = nullptr;
|
||||||
|
const Node_t* stop = nullptr;
|
||||||
|
|
||||||
|
iterator(MultiDelegatePImpl& md) : current(md.first), stop(md.last) {}
|
||||||
|
iterator() = default;
|
||||||
|
iterator(const iterator&) = default;
|
||||||
|
iterator& operator=(const iterator&) = default;
|
||||||
|
iterator& operator=(iterator&&) = default;
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return current && stop;
|
||||||
|
}
|
||||||
|
bool operator==(const iterator& rhs) const
|
||||||
|
{
|
||||||
|
return current == rhs.current;
|
||||||
|
}
|
||||||
|
bool operator!=(const iterator& rhs) const
|
||||||
|
{
|
||||||
|
return !operator==(rhs);
|
||||||
|
}
|
||||||
|
Delegate& operator*() const
|
||||||
|
{
|
||||||
|
return current->mDelegate;
|
||||||
|
}
|
||||||
|
Delegate* operator->() const
|
||||||
|
{
|
||||||
|
return ¤t->mDelegate;
|
||||||
|
}
|
||||||
|
iterator& operator++() // prefix
|
||||||
|
{
|
||||||
|
if (current && stop != current)
|
||||||
|
{
|
||||||
|
prev = current;
|
||||||
|
current = current->mNext;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
current = nullptr; // end
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
iterator& operator++(int) // postfix
|
||||||
|
{
|
||||||
|
iterator tmp(*this);
|
||||||
|
operator++();
|
||||||
|
return tmp;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
iterator begin()
|
||||||
|
{
|
||||||
|
return iterator(*this);
|
||||||
|
}
|
||||||
|
iterator end() const
|
||||||
|
{
|
||||||
|
return iterator();
|
||||||
|
}
|
||||||
|
|
||||||
|
const Delegate* add(const Delegate& del)
|
||||||
|
{
|
||||||
|
return add(Delegate(del));
|
||||||
|
}
|
||||||
|
|
||||||
|
const Delegate* add(Delegate&& del)
|
||||||
|
{
|
||||||
|
if (!del)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
#else
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_unused);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Node_t* item = ISQUEUE ? get_node_unsafe() :
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
new (std::nothrow) Node_t;
|
||||||
|
#else
|
||||||
|
new Node_t;
|
||||||
|
#endif
|
||||||
|
if (!item)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
item->mDelegate = std::move(del);
|
||||||
|
item->mNext = nullptr;
|
||||||
|
|
||||||
|
if (last)
|
||||||
|
last->mNext = item;
|
||||||
|
else
|
||||||
|
first = item;
|
||||||
|
last = item;
|
||||||
|
|
||||||
|
return &item->mDelegate;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator erase(iterator it)
|
||||||
|
{
|
||||||
|
if (!it)
|
||||||
|
return end();
|
||||||
|
#ifdef ARDUINO
|
||||||
|
InterruptLock lockAllInterruptsInThisScope;
|
||||||
|
#else
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_unused);
|
||||||
|
#endif
|
||||||
|
auto to_recycle = it.current;
|
||||||
|
|
||||||
|
if (last == it.current)
|
||||||
|
last = it.prev;
|
||||||
|
it.current = it.current->mNext;
|
||||||
|
if (it.prev)
|
||||||
|
{
|
||||||
|
it.prev->mNext = it.current;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
first = it.current;
|
||||||
|
}
|
||||||
|
if (ISQUEUE)
|
||||||
|
recycle_node_unsafe(to_recycle);
|
||||||
|
else
|
||||||
|
delete to_recycle;
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool erase(const Delegate* const del)
|
||||||
|
{
|
||||||
|
auto it = begin();
|
||||||
|
while (it)
|
||||||
|
{
|
||||||
|
if (del == &(*it))
|
||||||
|
{
|
||||||
|
erase(it);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
|
||||||
|
R operator()(P... args)
|
||||||
|
{
|
||||||
|
auto it = begin();
|
||||||
|
if (!it)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
static std::atomic<bool> fence(false);
|
||||||
|
// prevent recursive calls
|
||||||
|
#if defined(ARDUINO) && !defined(ESP32)
|
||||||
|
if (fence.load()) return {};
|
||||||
|
fence.store(true);
|
||||||
|
#else
|
||||||
|
if (fence.exchange(true)) return {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
R result;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
result = CallP<Delegate, R, ISQUEUE, P...>::execute(*it, args...);
|
||||||
|
if (result && ISQUEUE)
|
||||||
|
it = erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
// running callbacks might last too long for watchdog etc.
|
||||||
|
optimistic_yield(10000);
|
||||||
|
#endif
|
||||||
|
} while (it);
|
||||||
|
|
||||||
|
fence.store(false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, typename R = void, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32>
|
||||||
|
class MultiDelegateImpl : public MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>::MultiDelegatePImpl;
|
||||||
|
|
||||||
|
R operator()()
|
||||||
|
{
|
||||||
|
auto it = this->begin();
|
||||||
|
if (!it)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
static std::atomic<bool> fence(false);
|
||||||
|
// prevent recursive calls
|
||||||
|
#if defined(ARDUINO) && !defined(ESP32)
|
||||||
|
if (fence.load()) return {};
|
||||||
|
fence.store(true);
|
||||||
|
#else
|
||||||
|
if (fence.exchange(true)) return {};
|
||||||
|
#endif
|
||||||
|
|
||||||
|
R result;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
result = Call<Delegate, R, ISQUEUE>::execute(*it);
|
||||||
|
if (result && ISQUEUE)
|
||||||
|
it = this->erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
// running callbacks might last too long for watchdog etc.
|
||||||
|
optimistic_yield(10000);
|
||||||
|
#endif
|
||||||
|
} while (it);
|
||||||
|
|
||||||
|
fence.store(false);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P> class MultiDelegate;
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P>
|
||||||
|
class MultiDelegate<Delegate, R(P...), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY, P...>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegatePImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY, P...>::MultiDelegatePImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, typename R, bool ISQUEUE, size_t QUEUE_CAPACITY>
|
||||||
|
class MultiDelegate<Delegate, R(), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegateImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegateImpl<Delegate, R, ISQUEUE, QUEUE_CAPACITY>::MultiDelegateImpl;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY, typename... P>
|
||||||
|
class MultiDelegate<Delegate, void(P...), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegatePImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY, P...>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegatePImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY, P...>::MultiDelegatePImpl;
|
||||||
|
|
||||||
|
void operator()(P... args)
|
||||||
|
{
|
||||||
|
auto it = this->begin();
|
||||||
|
if (!it)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static std::atomic<bool> fence(false);
|
||||||
|
// prevent recursive calls
|
||||||
|
#if defined(ARDUINO) && !defined(ESP32)
|
||||||
|
if (fence.load()) return;
|
||||||
|
fence.store(true);
|
||||||
|
#else
|
||||||
|
if (fence.exchange(true)) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
CallP<Delegate, void, ISQUEUE, P...>::execute(*it, args...);
|
||||||
|
if (ISQUEUE)
|
||||||
|
it = this->erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
// running callbacks might last too long for watchdog etc.
|
||||||
|
optimistic_yield(10000);
|
||||||
|
#endif
|
||||||
|
} while (it);
|
||||||
|
|
||||||
|
fence.store(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename Delegate, bool ISQUEUE, size_t QUEUE_CAPACITY>
|
||||||
|
class MultiDelegate<Delegate, void(), ISQUEUE, QUEUE_CAPACITY> : public MultiDelegateImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using MultiDelegateImpl<Delegate, void, ISQUEUE, QUEUE_CAPACITY>::MultiDelegateImpl;
|
||||||
|
|
||||||
|
void operator()()
|
||||||
|
{
|
||||||
|
auto it = this->begin();
|
||||||
|
if (!it)
|
||||||
|
return;
|
||||||
|
|
||||||
|
static std::atomic<bool> fence(false);
|
||||||
|
// prevent recursive calls
|
||||||
|
#if defined(ARDUINO) && !defined(ESP32)
|
||||||
|
if (fence.load()) return;
|
||||||
|
fence.store(true);
|
||||||
|
#else
|
||||||
|
if (fence.exchange(true)) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
Call<Delegate, void, ISQUEUE>::execute(*it);
|
||||||
|
if (ISQUEUE)
|
||||||
|
it = this->erase(it);
|
||||||
|
else
|
||||||
|
++it;
|
||||||
|
#if defined(ESP8266) || defined(ESP32)
|
||||||
|
// running callbacks might last too long for watchdog etc.
|
||||||
|
optimistic_yield(10000);
|
||||||
|
#endif
|
||||||
|
} while (it);
|
||||||
|
|
||||||
|
fence.store(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
The MultiDelegate class template can be specialized to either a queue or an event multiplexer.
|
||||||
|
It is designed to be used with Delegate, the efficient runtime wrapper for C function ptr and C++ std::function.
|
||||||
|
@tparam Delegate specifies the concrete type that MultiDelegate bases the queue or event multiplexer on.
|
||||||
|
@tparam ISQUEUE modifies the generated MultiDelegate class in subtle ways. In queue mode (ISQUEUE == true),
|
||||||
|
the value of QUEUE_CAPACITY enforces the maximum number of simultaneous items the queue can contain.
|
||||||
|
This is exploited to minimize the use of new and delete by reusing already allocated items, thus
|
||||||
|
reducing heap fragmentation. In event multiplexer mode (ISQUEUE = false), new and delete are
|
||||||
|
used for allocation of the event handler items.
|
||||||
|
If the result type of the function call operator of Delegate is void, calling a MultiDelegate queue
|
||||||
|
removes each item after calling it; a Multidelegate event multiplexer keeps event handlers until
|
||||||
|
explicitly removed.
|
||||||
|
If the result type of the function call operator of Delegate is non-void, in a MultiDelegate queue
|
||||||
|
the type-conversion to bool of that result determines if the item is immediately removed or kept
|
||||||
|
after each call: if true is returned, the item is removed. A Multidelegate event multiplexer keeps event
|
||||||
|
handlers until they are explicitly removed.
|
||||||
|
@tparam QUEUE_CAPACITY is only used if ISQUEUE == true. Then, it sets the maximum capacity that the queue dynamically
|
||||||
|
allocates from the heap. Unused items are not returned to the heap, but are managed by the MultiDelegate
|
||||||
|
instance during its own lifetime for efficiency.
|
||||||
|
*/
|
||||||
|
template< typename Delegate, bool ISQUEUE = false, size_t QUEUE_CAPACITY = 32>
|
||||||
|
class MultiDelegate : public delegate::detail::MultiDelegate<Delegate, typename Delegate::target_type, ISQUEUE, QUEUE_CAPACITY>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using delegate::detail::MultiDelegate<Delegate, typename Delegate::target_type, ISQUEUE, QUEUE_CAPACITY>::MultiDelegate;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // __MULTIDELEGATE_H
|
@ -0,0 +1,384 @@
|
|||||||
|
/*
|
||||||
|
circular_queue.h - Implementation of a lock-free circular queue for EspSoftwareSerial.
|
||||||
|
Copyright (c) 2019 Dirk O. Kaar. 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 __circular_queue_h
|
||||||
|
#define __circular_queue_h
|
||||||
|
|
||||||
|
#ifdef ARDUINO
|
||||||
|
#include <Arduino.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
#include <atomic>
|
||||||
|
#include <memory>
|
||||||
|
#include <algorithm>
|
||||||
|
#include "Delegate.h"
|
||||||
|
using std::min;
|
||||||
|
#else
|
||||||
|
#include "ghostl.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !defined(ESP32) && !defined(ESP8266)
|
||||||
|
#define IRAM_ATTR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined(__GNUC__)
|
||||||
|
#undef ALWAYS_INLINE_ATTR
|
||||||
|
#define ALWAYS_INLINE_ATTR __attribute__((always_inline))
|
||||||
|
#else
|
||||||
|
#define ALWAYS_INLINE_ATTR
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Instance class for a single-producer, single-consumer circular queue / ring buffer (FIFO).
|
||||||
|
This implementation is lock-free between producer and consumer for the available(), peek(),
|
||||||
|
pop(), and push() type functions.
|
||||||
|
*/
|
||||||
|
template< typename T, typename ForEachArg = void >
|
||||||
|
class circular_queue
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/*!
|
||||||
|
@brief Constructs a valid, but zero-capacity dummy queue.
|
||||||
|
*/
|
||||||
|
circular_queue() : m_bufSize(1)
|
||||||
|
{
|
||||||
|
m_inPos.store(0);
|
||||||
|
m_outPos.store(0);
|
||||||
|
}
|
||||||
|
/*!
|
||||||
|
@brief Constructs a queue of the given maximum capacity.
|
||||||
|
*/
|
||||||
|
circular_queue(const size_t capacity) : m_bufSize(capacity + 1), m_buffer(new T[m_bufSize])
|
||||||
|
{
|
||||||
|
m_inPos.store(0);
|
||||||
|
m_outPos.store(0);
|
||||||
|
}
|
||||||
|
circular_queue(circular_queue&& cq) :
|
||||||
|
m_bufSize(cq.m_bufSize), m_buffer(cq.m_buffer), m_inPos(cq.m_inPos.load()), m_outPos(cq.m_outPos.load())
|
||||||
|
{}
|
||||||
|
~circular_queue()
|
||||||
|
{
|
||||||
|
m_buffer.reset();
|
||||||
|
}
|
||||||
|
circular_queue(const circular_queue&) = delete;
|
||||||
|
circular_queue& operator=(circular_queue&& cq)
|
||||||
|
{
|
||||||
|
m_bufSize = cq.m_bufSize;
|
||||||
|
m_buffer = cq.m_buffer;
|
||||||
|
m_inPos.store(cq.m_inPos.load());
|
||||||
|
m_outPos.store(cq.m_outPos.load());
|
||||||
|
}
|
||||||
|
circular_queue& operator=(const circular_queue&) = delete;
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Get the numer of elements the queue can hold at most.
|
||||||
|
*/
|
||||||
|
size_t capacity() const
|
||||||
|
{
|
||||||
|
return m_bufSize - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Resize the queue. The available elements in the queue are preserved.
|
||||||
|
This is not lock-free and concurrent producer or consumer access
|
||||||
|
will lead to corruption.
|
||||||
|
@return True if the new capacity could accommodate the present elements in
|
||||||
|
the queue, otherwise nothing is done and false is returned.
|
||||||
|
*/
|
||||||
|
bool capacity(const size_t cap);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Discard all data in the queue.
|
||||||
|
*/
|
||||||
|
void flush()
|
||||||
|
{
|
||||||
|
m_outPos.store(m_inPos.load());
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Get a snapshot number of elements that can be retrieved by pop.
|
||||||
|
*/
|
||||||
|
size_t IRAM_ATTR available() const
|
||||||
|
{
|
||||||
|
int avail = static_cast<int>(m_inPos.load() - m_outPos.load());
|
||||||
|
if (avail < 0) avail += m_bufSize;
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Get the remaining free elementes for pushing.
|
||||||
|
*/
|
||||||
|
size_t IRAM_ATTR available_for_push() const
|
||||||
|
{
|
||||||
|
int avail = static_cast<int>(m_outPos.load() - m_inPos.load()) - 1;
|
||||||
|
if (avail < 0) avail += m_bufSize;
|
||||||
|
return avail;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Peek at the next element pop will return without removing it from the queue.
|
||||||
|
@return An rvalue copy of the next element that can be popped. If the queue is empty,
|
||||||
|
return an rvalue copy of the element that is pending the next push.
|
||||||
|
*/
|
||||||
|
T peek() const
|
||||||
|
{
|
||||||
|
const auto outPos = m_outPos.load(std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
return m_buffer[outPos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Peek at the next pending input value.
|
||||||
|
@return A reference to the next element that can be pushed.
|
||||||
|
*/
|
||||||
|
T& IRAM_ATTR pushpeek()
|
||||||
|
{
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
return m_buffer[inPos];
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Release the next pending input value, accessible by pushpeek(), into the queue.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
bool IRAM_ATTR push()
|
||||||
|
{
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||||
|
const size_t next = (inPos + 1) % m_bufSize;
|
||||||
|
if (next == m_outPos.load(std::memory_order_relaxed)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
m_inPos.store(next, std::memory_order_release);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Move the rvalue parameter into the queue.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
bool IRAM_ATTR push(T&& val)
|
||||||
|
{
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||||
|
const size_t next = (inPos + 1) % m_bufSize;
|
||||||
|
if (next == m_outPos.load(std::memory_order_relaxed)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_buffer[inPos] = std::move(val);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
m_inPos.store(next, std::memory_order_release);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Push a copy of the parameter into the queue.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
inline bool IRAM_ATTR push(const T& val) ALWAYS_INLINE_ATTR
|
||||||
|
{
|
||||||
|
T v(val);
|
||||||
|
return push(std::move(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
/*!
|
||||||
|
@brief Push copies of multiple elements from a buffer into the queue,
|
||||||
|
in order, beginning at buffer's head.
|
||||||
|
@return The number of elements actually copied into the queue, counted
|
||||||
|
from the buffer head.
|
||||||
|
*/
|
||||||
|
size_t push_n(const T* buffer, size_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Pop the next available element from the queue.
|
||||||
|
@return An rvalue copy of the popped element, or a default
|
||||||
|
value of type T if the queue is empty.
|
||||||
|
*/
|
||||||
|
T pop();
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
/*!
|
||||||
|
@brief Pop multiple elements in ordered sequence from the queue to a buffer.
|
||||||
|
If buffer is nullptr, simply discards up to size elements from the queue.
|
||||||
|
@return The number of elements actually popped from the queue to
|
||||||
|
buffer.
|
||||||
|
*/
|
||||||
|
size_t pop_n(T* buffer, size_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Iterate over and remove each available element from queue,
|
||||||
|
calling back fun with an rvalue reference of every single element.
|
||||||
|
*/
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
void for_each(const Delegate<void(T&&), ForEachArg>& fun);
|
||||||
|
#else
|
||||||
|
void for_each(Delegate<void(T&&), ForEachArg> fun);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief In reverse order, iterate over, pop and optionally requeue each available element from the queue,
|
||||||
|
calling back fun with a reference of every single element.
|
||||||
|
Requeuing is dependent on the return boolean of the callback function. If it
|
||||||
|
returns true, the requeue occurs.
|
||||||
|
*/
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
bool for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun);
|
||||||
|
#else
|
||||||
|
bool for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
size_t m_bufSize;
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
std::unique_ptr<T[]> m_buffer;
|
||||||
|
#else
|
||||||
|
std::unique_ptr<T> m_buffer;
|
||||||
|
#endif
|
||||||
|
std::atomic<size_t> m_inPos;
|
||||||
|
std::atomic<size_t> m_outPos;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
bool circular_queue<T, ForEachArg>::capacity(const size_t cap)
|
||||||
|
{
|
||||||
|
if (cap + 1 == m_bufSize) return true;
|
||||||
|
else if (available() > cap) return false;
|
||||||
|
std::unique_ptr<T[] > buffer(new T[cap + 1]);
|
||||||
|
const auto available = pop_n(buffer, cap);
|
||||||
|
m_buffer.reset(buffer);
|
||||||
|
m_bufSize = cap + 1;
|
||||||
|
m_inPos.store(available, std::memory_order_relaxed);
|
||||||
|
m_outPos.store(0, std::memory_order_relaxed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
size_t circular_queue<T, ForEachArg>::push_n(const T* buffer, size_t size)
|
||||||
|
{
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_acquire);
|
||||||
|
const auto outPos = m_outPos.load(std::memory_order_relaxed);
|
||||||
|
|
||||||
|
size_t blockSize = (outPos > inPos) ? outPos - 1 - inPos : (outPos == 0) ? m_bufSize - 1 - inPos : m_bufSize - inPos;
|
||||||
|
blockSize = min(size, blockSize);
|
||||||
|
if (!blockSize) return 0;
|
||||||
|
int next = (inPos + blockSize) % m_bufSize;
|
||||||
|
|
||||||
|
auto dest = m_buffer.get() + inPos;
|
||||||
|
std::copy_n(std::make_move_iterator(buffer), blockSize, dest);
|
||||||
|
size = min(size - blockSize, outPos > 1 ? static_cast<size_t>(outPos - next - 1) : 0);
|
||||||
|
next += size;
|
||||||
|
dest = m_buffer.get();
|
||||||
|
std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest);
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
m_inPos.store(next, std::memory_order_release);
|
||||||
|
return blockSize + size;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
T circular_queue<T, ForEachArg>::pop()
|
||||||
|
{
|
||||||
|
const auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||||
|
if (m_inPos.load(std::memory_order_relaxed) == outPos) return {};
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
|
||||||
|
auto val = std::move(m_buffer[outPos]);
|
||||||
|
|
||||||
|
m_outPos.store((outPos + 1) % m_bufSize, std::memory_order_release);
|
||||||
|
return val;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
size_t circular_queue<T, ForEachArg>::pop_n(T* buffer, size_t size) {
|
||||||
|
size_t avail = size = min(size, available());
|
||||||
|
if (!avail) return 0;
|
||||||
|
const auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||||
|
size_t n = min(avail, static_cast<size_t>(m_bufSize - outPos));
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
|
||||||
|
if (buffer) {
|
||||||
|
buffer = std::copy_n(std::make_move_iterator(m_buffer.get() + outPos), n, buffer);
|
||||||
|
avail -= n;
|
||||||
|
std::copy_n(std::make_move_iterator(m_buffer.get()), avail, buffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_outPos.store((outPos + size) % m_bufSize, std::memory_order_release);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
void circular_queue<T, ForEachArg>::for_each(const Delegate<void(T&&), ForEachArg>& fun)
|
||||||
|
#else
|
||||||
|
void circular_queue<T, ForEachArg>::for_each(Delegate<void(T&&), ForEachArg> fun)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
auto outPos = m_outPos.load(std::memory_order_acquire);
|
||||||
|
const auto inPos = m_inPos.load(std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
while (outPos != inPos)
|
||||||
|
{
|
||||||
|
fun(std::move(m_buffer[outPos]));
|
||||||
|
outPos = (outPos + 1) % m_bufSize;
|
||||||
|
m_outPos.store(outPos, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
bool circular_queue<T, ForEachArg>::for_each_rev_requeue(const Delegate<bool(T&), ForEachArg>& fun)
|
||||||
|
#else
|
||||||
|
bool circular_queue<T, ForEachArg>::for_each_rev_requeue(Delegate<bool(T&), ForEachArg> fun)
|
||||||
|
#endif
|
||||||
|
{
|
||||||
|
auto inPos0 = circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_acquire);
|
||||||
|
auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed);
|
||||||
|
if (outPos == inPos0) return false;
|
||||||
|
auto pos = inPos0;
|
||||||
|
auto outPos1 = inPos0;
|
||||||
|
const auto posDecr = circular_queue<T, ForEachArg>::m_bufSize - 1;
|
||||||
|
std::atomic_thread_fence(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
pos = (pos + posDecr) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||||
|
T&& val = std::move(circular_queue<T, ForEachArg>::m_buffer[pos]);
|
||||||
|
if (fun(val))
|
||||||
|
{
|
||||||
|
outPos1 = (outPos1 + posDecr) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||||
|
if (outPos1 != pos) circular_queue<T, ForEachArg>::m_buffer[outPos1] = std::move(val);
|
||||||
|
}
|
||||||
|
} while (pos != outPos);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
circular_queue<T, ForEachArg>::m_outPos.store(outPos1, std::memory_order_release);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __circular_queue_h
|
@ -0,0 +1,310 @@
|
|||||||
|
/*
|
||||||
|
circular_queue_mp.h - Implementation of a lock-free circular queue for EspSoftwareSerial.
|
||||||
|
Copyright (c) 2019 Dirk O. Kaar. 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 __circular_queue_mp_h
|
||||||
|
#define __circular_queue_mp_h
|
||||||
|
|
||||||
|
#include "circular_queue.h"
|
||||||
|
|
||||||
|
#if defined(ESP8266)
|
||||||
|
#include <interrupts.h>
|
||||||
|
using esp8266::InterruptLock;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Instance class for a multi-producer, single-consumer circular queue / ring buffer (FIFO).
|
||||||
|
This implementation is lock-free between producers and consumer for the available(), peek(),
|
||||||
|
pop(), and push() type functions.
|
||||||
|
*/
|
||||||
|
template< typename T, typename ForEachArg = void >
|
||||||
|
class circular_queue_mp : protected circular_queue<T, ForEachArg>
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
circular_queue_mp() : circular_queue<T, ForEachArg>()
|
||||||
|
{
|
||||||
|
m_inPos_mp.store(0);
|
||||||
|
m_concurrent_mp.store(0);
|
||||||
|
}
|
||||||
|
circular_queue_mp(const size_t capacity) : circular_queue<T, ForEachArg>(capacity)
|
||||||
|
{
|
||||||
|
m_inPos_mp.store(0);
|
||||||
|
m_concurrent_mp.store(0);
|
||||||
|
}
|
||||||
|
circular_queue_mp(circular_queue_mp<T, ForEachArg>&& cq) : circular_queue<T, ForEachArg>(std::move(cq))
|
||||||
|
{
|
||||||
|
m_inPos_mp.store(cq.m_inPos_mp.load());
|
||||||
|
m_concurrent_mp.store(cq.m_concurrent_mp.load());
|
||||||
|
}
|
||||||
|
circular_queue_mp& operator=(circular_queue_mp&& cq)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::operator=(std::move(cq));
|
||||||
|
m_inPos_mp.store(cq.m_inPos_mp.load());
|
||||||
|
m_concurrent_mp.store(cq.m_concurrent_mp.load());
|
||||||
|
}
|
||||||
|
circular_queue_mp& operator=(const circular_queue_mp&) = delete;
|
||||||
|
|
||||||
|
using circular_queue<T, ForEachArg>::capacity;
|
||||||
|
using circular_queue<T, ForEachArg>::flush;
|
||||||
|
using circular_queue<T, ForEachArg>::peek;
|
||||||
|
using circular_queue<T, ForEachArg>::pop;
|
||||||
|
using circular_queue<T, ForEachArg>::pop_n;
|
||||||
|
using circular_queue<T, ForEachArg>::for_each;
|
||||||
|
using circular_queue<T, ForEachArg>::for_each_rev_requeue;
|
||||||
|
|
||||||
|
T& pushpeek() = delete;
|
||||||
|
bool push() = delete;
|
||||||
|
|
||||||
|
inline size_t IRAM_ATTR available() const ALWAYS_INLINE_ATTR
|
||||||
|
{
|
||||||
|
return circular_queue<T, ForEachArg>::available();
|
||||||
|
}
|
||||||
|
inline size_t IRAM_ATTR available_for_push() const ALWAYS_INLINE_ATTR
|
||||||
|
{
|
||||||
|
return circular_queue<T, ForEachArg>::available_for_push();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Resize the queue. The available elements in the queue are preserved.
|
||||||
|
This is not lock-free and concurrent producer or consumer access
|
||||||
|
will lead to corruption.
|
||||||
|
@return True if the new capacity could accommodate the present elements in
|
||||||
|
the queue, otherwise nothing is done and false is returned.
|
||||||
|
*/
|
||||||
|
bool capacity(const size_t cap);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Move the rvalue parameter into the queue, guarded
|
||||||
|
for multiple concurrent producers.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
bool push(T&& val);
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Push a copy of the parameter into the queue, guarded
|
||||||
|
for multiple concurrent producers.
|
||||||
|
@return true if the queue accepted the value, false if the queue
|
||||||
|
was full.
|
||||||
|
*/
|
||||||
|
inline bool IRAM_ATTR push(const T& val) ALWAYS_INLINE_ATTR
|
||||||
|
{
|
||||||
|
T v(val);
|
||||||
|
return push(std::move(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*!
|
||||||
|
@brief Push copies of multiple elements from a buffer into the queue,
|
||||||
|
in order, beginning at buffer's head. This is safe for
|
||||||
|
multiple producers.
|
||||||
|
@return The number of elements actually copied into the queue, counted
|
||||||
|
from the buffer head.
|
||||||
|
*/
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
size_t push_n(const T* buffer, size_t size);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
protected:
|
||||||
|
std::atomic<size_t> m_inPos_mp;
|
||||||
|
std::atomic<int> m_concurrent_mp;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
bool circular_queue_mp<T, ForEachArg>::capacity(const size_t cap)
|
||||||
|
{
|
||||||
|
if (cap + 1 == circular_queue<T, ForEachArg>::m_bufSize) return true;
|
||||||
|
else if (!circular_queue<T, ForEachArg>::capacity(cap)) return false;
|
||||||
|
m_inPos_mp.store(circular_queue<T, ForEachArg>::m_inPos.load(std::memory_order_relaxed),
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
m_concurrent_mp.store(0, std::memory_order_relaxed);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
bool IRAM_ATTR circular_queue_mp<T, ForEachArg>::push(T&& val)
|
||||||
|
{
|
||||||
|
size_t inPos_mp;
|
||||||
|
size_t next;
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
class InterruptLock {
|
||||||
|
public:
|
||||||
|
InterruptLock() {
|
||||||
|
noInterrupts();
|
||||||
|
}
|
||||||
|
~InterruptLock() {
|
||||||
|
interrupts();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
#else
|
||||||
|
++m_concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
inPos_mp = m_inPos_mp.load(std::memory_order_relaxed);
|
||||||
|
next = (inPos_mp + 1) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||||
|
if (next == circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed)) {
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_inPos_mp.store(next, std::memory_order_relaxed);
|
||||||
|
m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) + 1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load();
|
||||||
|
concurrent_mp = m_concurrent_mp.load();
|
||||||
|
if (1 == concurrent_mp)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_inPos_mp.compare_exchange_weak(inPos_mp, next));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
circular_queue<T, ForEachArg>::m_buffer[inPos_mp] = std::move(val);
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
if (1 == m_concurrent_mp.load(std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load(std::memory_order_relaxed);
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) - 1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load();
|
||||||
|
concurrent_mp = m_concurrent_mp.load();
|
||||||
|
if (1 == concurrent_mp)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ESP8266) || defined(ESP32) || !defined(ARDUINO)
|
||||||
|
template< typename T, typename ForEachArg >
|
||||||
|
size_t circular_queue_mp<T, ForEachArg>::push_n(const T* buffer, size_t size)
|
||||||
|
{
|
||||||
|
const auto outPos = circular_queue<T, ForEachArg>::m_outPos.load(std::memory_order_relaxed);
|
||||||
|
size_t inPos_mp;
|
||||||
|
size_t next;
|
||||||
|
size_t blockSize;
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
#else
|
||||||
|
++m_concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
#endif
|
||||||
|
inPos_mp = m_inPos_mp.load(std::memory_order_relaxed);
|
||||||
|
blockSize = (outPos > inPos_mp) ? outPos - 1 - inPos_mp : (outPos == 0) ? circular_queue<T, ForEachArg>::m_bufSize - 1 - inPos_mp : circular_queue<T, ForEachArg>::m_bufSize - inPos_mp;
|
||||||
|
blockSize = min(size, blockSize);
|
||||||
|
if (!blockSize)
|
||||||
|
{
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
next = (inPos_mp + blockSize) % circular_queue<T, ForEachArg>::m_bufSize;
|
||||||
|
m_inPos_mp.store(next, std::memory_order_relaxed);
|
||||||
|
m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) + 1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int concurrent_mp = m_concurrent_mp.load();
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load();
|
||||||
|
concurrent_mp = m_concurrent_mp.load();
|
||||||
|
if (1 == concurrent_mp)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_inPos_mp.compare_exchange_weak(inPos_mp, next));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
auto dest = circular_queue<T, ForEachArg>::m_buffer.get() + inPos_mp;
|
||||||
|
std::copy_n(std::make_move_iterator(buffer), blockSize, dest);
|
||||||
|
size = min(size - blockSize, outPos > 1 ? static_cast<size_t>(outPos - next - 1) : 0);
|
||||||
|
next += size;
|
||||||
|
dest = circular_queue<T, ForEachArg>::m_buffer.get();
|
||||||
|
std::copy_n(std::make_move_iterator(buffer + blockSize), size, dest);
|
||||||
|
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
|
||||||
|
#if !defined(ESP32) && defined(ARDUINO)
|
||||||
|
{
|
||||||
|
InterruptLock lock;
|
||||||
|
if (1 == m_concurrent_mp.load(std::memory_order_relaxed))
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load(std::memory_order_relaxed);
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
m_concurrent_mp.store(m_concurrent_mp.load(std::memory_order_relaxed) - 1,
|
||||||
|
std::memory_order_relaxed);
|
||||||
|
std::atomic_thread_fence(std::memory_order_release);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
int concurrent_mp;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
inPos_mp = m_inPos_mp.load();
|
||||||
|
concurrent_mp = m_concurrent_mp.load();
|
||||||
|
if (1 == concurrent_mp)
|
||||||
|
{
|
||||||
|
circular_queue<T, ForEachArg>::m_inPos.store(inPos_mp, std::memory_order_release);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while (!m_concurrent_mp.compare_exchange_weak(concurrent_mp, concurrent_mp - 1));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return blockSize + size;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // __circular_queue_mp_h
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
ghostl.h - Implementation of a bare-bones, mostly no-op, C++ STL shell
|
||||||
|
that allows building some Arduino ESP8266/ESP32
|
||||||
|
libraries on Aruduino AVR.
|
||||||
|
Copyright (c) 2019 Dirk O. Kaar. 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 __ghostl_h
|
||||||
|
#define __ghostl_h
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_SAMD)
|
||||||
|
#include <atomic>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
using size_t = decltype(sizeof(char));
|
||||||
|
|
||||||
|
namespace std
|
||||||
|
{
|
||||||
|
#if !defined(ARDUINO_ARCH_SAMD)
|
||||||
|
typedef enum memory_order {
|
||||||
|
memory_order_relaxed,
|
||||||
|
memory_order_acquire,
|
||||||
|
memory_order_release,
|
||||||
|
memory_order_seq_cst
|
||||||
|
} memory_order;
|
||||||
|
template< typename T > class atomic {
|
||||||
|
private:
|
||||||
|
T value;
|
||||||
|
public:
|
||||||
|
atomic() {}
|
||||||
|
atomic(T desired) { value = desired; }
|
||||||
|
void store(T desired, std::memory_order = std::memory_order_seq_cst) volatile noexcept { value = desired; }
|
||||||
|
T load(std::memory_order = std::memory_order_seq_cst) const volatile noexcept { return value; }
|
||||||
|
};
|
||||||
|
inline void atomic_thread_fence(std::memory_order order) noexcept {}
|
||||||
|
template< typename T > T&& move(T& t) noexcept { return static_cast<T&&>(t); }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
template< typename T, size_t long N > struct array
|
||||||
|
{
|
||||||
|
T _M_elems[N];
|
||||||
|
decltype(sizeof(0)) size() const { return N; }
|
||||||
|
T& operator[](decltype(sizeof(0)) i) { return _M_elems[i]; }
|
||||||
|
const T& operator[](decltype(sizeof(0)) i) const { return _M_elems[i]; }
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T > class unique_ptr
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using pointer = T*;
|
||||||
|
unique_ptr() noexcept : ptr(nullptr) {}
|
||||||
|
unique_ptr(pointer p) : ptr(p) {}
|
||||||
|
pointer operator->() const noexcept { return ptr; }
|
||||||
|
T& operator[](decltype(sizeof(0)) i) const { return ptr[i]; }
|
||||||
|
void reset(pointer p = pointer()) noexcept
|
||||||
|
{
|
||||||
|
delete ptr;
|
||||||
|
ptr = p;
|
||||||
|
}
|
||||||
|
T& operator*() const { return *ptr; }
|
||||||
|
private:
|
||||||
|
pointer ptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template< typename T > using function = T*;
|
||||||
|
using nullptr_t = decltype(nullptr);
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct identity {
|
||||||
|
typedef T type;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
inline T&& forward(typename identity<T>::type& t) noexcept
|
||||||
|
{
|
||||||
|
return static_cast<typename identity<T>::type&&>(t);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __ghostl_h
|
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,489 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title></title>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport" />
|
||||||
|
<style>
|
||||||
|
*{
|
||||||
|
margin:0px;
|
||||||
|
padding:0px;
|
||||||
|
}
|
||||||
|
#myMap{
|
||||||
|
position:absolute;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<!--
|
||||||
|
<style>
|
||||||
|
.bm_bottomLeftOverlay{
|
||||||
|
display:none !important
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
||||||
|
-->
|
||||||
|
|
||||||
|
<script type='text/javascript'>
|
||||||
|
var map;
|
||||||
|
var infobox;
|
||||||
|
var key;
|
||||||
|
var zoom = 15;
|
||||||
|
var center =[39.92, 116.40];
|
||||||
|
var disablePanning = false;
|
||||||
|
var disableZooming = false;
|
||||||
|
var maxZoom = 17;
|
||||||
|
var minZoom = 5;
|
||||||
|
var showLocateMeButton = true;
|
||||||
|
var showMapTypeSelector= true;
|
||||||
|
var showScalebar= true;
|
||||||
|
var showZoomButtons =true;
|
||||||
|
var mapType = "road";
|
||||||
|
|
||||||
|
function GetMap() {
|
||||||
|
map = new Microsoft.Maps.Map('#myMap', {
|
||||||
|
//credentials: 'AmxEDXs-Yhwj3Uv5WvVB4Q6YvISdMjqrC-pPOw0rNKMu_5rrksVmAJkpcv5HJwJS',
|
||||||
|
zoom: zoom,
|
||||||
|
showDashboard: true,
|
||||||
|
showLocateMeButton:showLocateMeButton,
|
||||||
|
showMapTypeSelector:showMapTypeSelector,
|
||||||
|
showTermsLink: false,
|
||||||
|
//enableHighDpi:true,
|
||||||
|
enableClickableLogo: false,
|
||||||
|
//mapTypeId: Microsoft.Maps.MapTypeId.aerial,
|
||||||
|
//mapTypeId: Microsoft.Maps.MapTypeId.road,
|
||||||
|
maxZoom: maxZoom,
|
||||||
|
minZoom: minZoom,
|
||||||
|
disablePanning:disablePanning,
|
||||||
|
disableZooming:disableZooming,
|
||||||
|
//customMapStyle: myStyle,
|
||||||
|
liteMode: true,
|
||||||
|
showScalebar: showScalebar,
|
||||||
|
showZoomButtons: showZoomButtons,
|
||||||
|
center:realLocation(center),
|
||||||
|
});
|
||||||
|
setMapType(mapType);
|
||||||
|
|
||||||
|
infobox = new Microsoft.Maps.Infobox(map.getCenter(), {
|
||||||
|
visible: false,
|
||||||
|
});
|
||||||
|
infobox.setMap(map);
|
||||||
|
|
||||||
|
Microsoft.Maps.Events.addHandler(map, 'click', function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.mapClicked(JSON.stringify([e.location.latitude, e.location.longitude]));
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify([e.location.latitude, e.location.longitude]));
|
||||||
|
});
|
||||||
|
|
||||||
|
Microsoft.Maps.Events.addHandler(map, 'viewchangeend', function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.mapViewChangedEnd(JSON.stringify([map.getCenter()
|
||||||
|
.latitude, map.getCenter()
|
||||||
|
.longitude
|
||||||
|
]), map.getZoom(), JSON.stringify(map.getBounds()
|
||||||
|
.bounds));
|
||||||
|
}
|
||||||
|
console.log(JSON.stringify([map.getCenter().latitude,
|
||||||
|
map.getCenter().longitude]),
|
||||||
|
map.getZoom(),
|
||||||
|
JSON.stringify(map.getBounds().bounds));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setMapType(type) {
|
||||||
|
var mapTypeId;
|
||||||
|
switch (type) {
|
||||||
|
case 'aerial':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.aerial;
|
||||||
|
break;
|
||||||
|
case 'canvasDark':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.canvasDark;
|
||||||
|
break;
|
||||||
|
case 'canvasLight':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.canvasLight;
|
||||||
|
break;
|
||||||
|
case 'birdseye':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.birdseye;
|
||||||
|
break;
|
||||||
|
case 'grayscale':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.grayscale;
|
||||||
|
break;
|
||||||
|
case 'streetside':
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.streetside;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
mapTypeId = Microsoft.Maps.MapTypeId.road;
|
||||||
|
}
|
||||||
|
map.setView({
|
||||||
|
mapTypeId: mapTypeId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function setZoom(z) {
|
||||||
|
map.setView({
|
||||||
|
zoom: z
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getZoom() {
|
||||||
|
return map.getZoom();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setCenter(location) {
|
||||||
|
map.setView({
|
||||||
|
center: realLocation(location)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getCenter() {
|
||||||
|
return [map.getCenter()
|
||||||
|
.latitude, map.getCenter()
|
||||||
|
.longitude
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
//视图的右上角和左下角纬度+经度
|
||||||
|
function getBounds() {
|
||||||
|
return map.getBounds()
|
||||||
|
.bounds;
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPushpin(location, id, options) {
|
||||||
|
//options = {title: title,subTitle: subtitle,text: text, color: 'blue',icon: 'poi-custom.png',anchor: new Microsoft.Maps.Point(12, 39), draggable: draggable, };
|
||||||
|
var pin = new Microsoft.Maps.Pushpin(realLocation(location), options);
|
||||||
|
pin.id = id;
|
||||||
|
map.entities.push(pin);
|
||||||
|
Microsoft.Maps.Events.addHandler(pin, 'click', function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.pushpinClicked(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
});
|
||||||
|
Microsoft.Maps.Events.addHandler(pin, 'dragend', function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.pushpinDragEnd(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
function realLocation(location) {
|
||||||
|
return new Microsoft.Maps.Location(location[0], location[1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPolyline(locations, id, options) {
|
||||||
|
//options = { strokeColor: 'red',strokeThickness: 3, strokeDashArray: [10,10] };
|
||||||
|
var bingLocations = [];
|
||||||
|
for (var i = 0; i < locations.length; i++) {
|
||||||
|
bingLocations.push(realLocation(locations[i]));
|
||||||
|
}
|
||||||
|
var polyline = new Microsoft.Maps.Polyline(bingLocations, options);
|
||||||
|
polyline.id = id;
|
||||||
|
map.entities.push(polyline);
|
||||||
|
Microsoft.Maps.Events.addHandler(polyline, 'click', function(e) {
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.polylineClicked(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setPolylineLocation(locations, id) {
|
||||||
|
var bingLocations = [];
|
||||||
|
for (var i = 0; i < locations.length; i++) {
|
||||||
|
bingLocations.push(realLocation(locations[i]));
|
||||||
|
}
|
||||||
|
for (var i = map.entities.getLength() - 1; i >= 0; i--) {
|
||||||
|
var line = map.entities.get(i);
|
||||||
|
if (line.id == id) {
|
||||||
|
line.setLocations(bingLocations);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPolygon(locations, id, options) {
|
||||||
|
//options = { fillColor: '#ff000022', strokeColor: '#00ff00', strokeThickness: 4, strokeDashArray: [10,5] };
|
||||||
|
|
||||||
|
var bingLocations = [];
|
||||||
|
for (var i = 0; i < locations.length; i++) {
|
||||||
|
bingLocations.push(realLocation(locations[i]));
|
||||||
|
}
|
||||||
|
var polygon = new Microsoft.Maps.Polygon(bingLocations, options);
|
||||||
|
polygon.id = id;
|
||||||
|
map.entities.push(polygon);
|
||||||
|
Microsoft.Maps.Events.addHandler(polygon, 'click', function(e) {
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.polygonClicked(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function newPolygonRegular(center, distance, sides, id, options, angle) {
|
||||||
|
//options = { fillColor: '#ff000022', strokeColor: '#00ff00', strokeThickness: 4, strokeDashArray: [10,5] };
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.SpatialMath', function() {
|
||||||
|
var locations = Microsoft.Maps.SpatialMath.getRegularPolygon(realLocation(center), distance, sides, Microsoft.Maps.SpatialMath.DistanceUnits.Kilometers,angle);
|
||||||
|
var polygon = new Microsoft.Maps.Polygon(locations, options);
|
||||||
|
polygon.id = id;
|
||||||
|
map.entities.push(polygon);
|
||||||
|
Microsoft.Maps.Events.addHandler(polygon, 'click', function(e) {
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.polygonClicked(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
}
|
||||||
|
console.log(e.target.id, JSON.stringify([e.location.latitude, e.location.longitude]), JSON.stringify([e.point.x, e.point.y]));
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function showInfobox(location, title, description, actions) {
|
||||||
|
|
||||||
|
var options = {
|
||||||
|
location: realLocation(location),
|
||||||
|
title: title,
|
||||||
|
description: description,
|
||||||
|
//maxWidth: 500,
|
||||||
|
showCloseButton:true,
|
||||||
|
actions: null,
|
||||||
|
visible: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (actions instanceof Array && actions.length > 0) {
|
||||||
|
var realActions = [];
|
||||||
|
for (var i = 0; i < actions.length; i++) {
|
||||||
|
var option = {};
|
||||||
|
option.label = actions[i];
|
||||||
|
option.eventHandler = function(e) {
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.actionClicked(e.srcElement.innerText);
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
console.log(e.srcElement.innerText);
|
||||||
|
//hideInfobox();
|
||||||
|
};
|
||||||
|
realActions.push(option);
|
||||||
|
|
||||||
|
}
|
||||||
|
options.actions = realActions;
|
||||||
|
}
|
||||||
|
|
||||||
|
infobox.setOptions(options);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hideInfobox() {
|
||||||
|
infobox.setOptions({
|
||||||
|
visible: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var directionsManager;
|
||||||
|
|
||||||
|
function calculateDirections(locations,mode) {
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.Directions', function() {
|
||||||
|
directionsManager = new Microsoft.Maps.Directions.DirectionsManager(map);
|
||||||
|
// Set Route Mode to driving
|
||||||
|
var routeMode;
|
||||||
|
if (mode == 'walking'){
|
||||||
|
routeMode = Microsoft.Maps.Directions.RouteMode.walking;
|
||||||
|
}else if (mode == 'transit'){
|
||||||
|
routeMode = Microsoft.Maps.Directions.RouteMode.transit;
|
||||||
|
}else if (mode == 'truck'){
|
||||||
|
routeMode = Microsoft.Maps.Directions.RouteMode.truck;
|
||||||
|
}else {
|
||||||
|
routeMode = Microsoft.Maps.Directions.RouteMode.driving;
|
||||||
|
}
|
||||||
|
|
||||||
|
directionsManager.setRequestOptions({
|
||||||
|
distanceUnit: Microsoft.Maps.Directions.DistanceUnit.km,
|
||||||
|
routeMode: routeMode,
|
||||||
|
//routeDraggable: false,
|
||||||
|
});
|
||||||
|
for (var i = 0; i < locations.length; i++) {
|
||||||
|
var waypoint1 = new Microsoft.Maps.Directions.Waypoint({
|
||||||
|
address: "",
|
||||||
|
location: realLocation(locations[i])
|
||||||
|
});
|
||||||
|
directionsManager.addWaypoint(waypoint1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the element in which the itinerary will be rendered
|
||||||
|
directionsManager.setRenderOptions({ itineraryContainer: document.getElementById('printoutPanel') });
|
||||||
|
|
||||||
|
directionsManager.setRenderOptions({
|
||||||
|
drivingPolylineOptions: {
|
||||||
|
strokeColor: 'lightblue',
|
||||||
|
strokeThickness: 1
|
||||||
|
},
|
||||||
|
waypointPushpinOptions: {
|
||||||
|
title: ''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Microsoft.Maps.Events.addHandler(directionsManager, 'directionsError', directionsError);
|
||||||
|
Microsoft.Maps.Events.addHandler(directionsManager, 'directionsUpdated', directionsUpdated);
|
||||||
|
|
||||||
|
directionsManager.calculateDirections();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function directionsUpdated(e) {
|
||||||
|
console.log(e);
|
||||||
|
//Get the current route index.
|
||||||
|
var routeIdx = directionsManager.getRequestOptions()
|
||||||
|
.routeIndex;
|
||||||
|
var total = e.routeSummary.length;
|
||||||
|
|
||||||
|
//var options = [];
|
||||||
|
//for (var i = 0; i < e.routeSummary.length; i++) {
|
||||||
|
//var option = {};
|
||||||
|
//option.distance = e.route[routeIdx].distance;
|
||||||
|
//option.time = e.routeSummary[routeIdx].time;
|
||||||
|
//option.timeWithTraffic = e.routeSummary[routeIdx].timeWithTraffic;
|
||||||
|
//options.push(option);
|
||||||
|
//}
|
||||||
|
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.directionsUpdated(total, routeIdx+1, JSON.stringify(e.route[routeIdx]));
|
||||||
|
}
|
||||||
|
console.log(total, routeIdx+1, e.route[routeIdx]);
|
||||||
|
}
|
||||||
|
|
||||||
|
function directionsError(e) {
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.directionsError(JSON.stringify(e));
|
||||||
|
}
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearRoute() {
|
||||||
|
directionsManager.clearAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(id) {
|
||||||
|
for (var i = map.entities.getLength() - 1; i >= 0; i--) {
|
||||||
|
var pin = map.entities.get(i);
|
||||||
|
if (pin.id == id) {
|
||||||
|
map.entities.removeAt(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeAll() {
|
||||||
|
map.entities.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getAddress(location) {
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.Search', function() {
|
||||||
|
var searchManager = new Microsoft.Maps.Search.SearchManager(map);
|
||||||
|
var reverseGeocodeRequestOptions = {
|
||||||
|
location: realLocation(location),
|
||||||
|
callback: function(answer, userData) {
|
||||||
|
var ad = answer.address.formattedAddress;
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.gotAddress(ad);
|
||||||
|
}
|
||||||
|
console.log(ad);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
searchManager.reverseGeocode(reverseGeocodeRequestOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLocation(address) {
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.Search', function() {
|
||||||
|
var searchManager = new Microsoft.Maps.Search.SearchManager(map);
|
||||||
|
var requestOptions = {
|
||||||
|
//bounds: map.getBounds(),
|
||||||
|
where: address,
|
||||||
|
callback: function(answer, userData) {
|
||||||
|
|
||||||
|
var result = [];
|
||||||
|
for (var i = 0; i < answer.results.length; i++) {
|
||||||
|
result.push([answer.results[i].name, [answer.results[i].location.latitude, answer.results[i].location.longitude]]);
|
||||||
|
}
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.gotLocation(JSON.stringify(result));
|
||||||
|
}
|
||||||
|
console.log(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
searchManager.geocode(requestOptions);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDistance(location1, location2) {
|
||||||
|
Microsoft.Maps.loadModule('Microsoft.Maps.SpatialMath', function() {
|
||||||
|
var dis = Microsoft.Maps.SpatialMath.getDistanceTo(realLocation(location1), realLocation(location2), Microsoft.Maps.SpatialMath.DistanceUnits.Kilometers);
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.gotDistance(dis);
|
||||||
|
}
|
||||||
|
console.log(dis);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadJS() {
|
||||||
|
|
||||||
|
var script = document.createElement('script');
|
||||||
|
script.type = 'text/javascript';
|
||||||
|
script.src = 'http://www.bing.com/api/maps/mapcontrol?callback=GetMap&key=' + key;
|
||||||
|
script.async = true;
|
||||||
|
script.defer = true;
|
||||||
|
document.getElementsByTagName('head')[0].appendChild(script);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getQueryVariable(variable) {
|
||||||
|
var query = window.location.search.substring(1);
|
||||||
|
var vars = query.split("&");
|
||||||
|
for (var i = 0; i < vars.length; i++) {
|
||||||
|
var pair = vars[i].split("=");
|
||||||
|
if (pair[0] == variable) {
|
||||||
|
return pair[1];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (false);
|
||||||
|
}
|
||||||
|
var watchId;
|
||||||
|
function startTracking() {
|
||||||
|
|
||||||
|
watchId = navigator.geolocation.watchPosition(function(position) {
|
||||||
|
var loc = [ position.coords.latitude, position.coords.longitude];
|
||||||
|
if (typeof(KevinkunBingMap) != "undefined") {
|
||||||
|
KevinkunBingMap.locationUpdated(JSON.stringify(loc));
|
||||||
|
}
|
||||||
|
console.log("locationUpdated", loc);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
function stopTracking() {
|
||||||
|
// Cancel the geolocation updates.
|
||||||
|
navigator.geolocation.clearWatch(watchId);
|
||||||
|
}
|
||||||
|
//loadJS(getQueryVariable('key'));
|
||||||
|
//loadJS('AmxEDXs-Yhwj3Uv5WvVB4Q6YvISdMjqrC-pPOw0rNKMu_5rrksVmAJkpcv5HJwJS');
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id='printoutPanel' style='display:none'></div>
|
||||||
|
<div id='myMap'></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
After Width: | Height: | Size: 894 B |
After Width: | Height: | Size: 889 B |
After Width: | Height: | Size: 839 B |
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
[{"permissions":["android.permission.ACCESS_FINE_LOCATION"],"conditionalPermissions":{},"conditionalBroadcastReceivers":{},"libraries":[],"native":[],"broadcastReceiver":[],"assets":["pin3.png","pin2.png","bingmap.html","pin1.png"],"activities":[],"broadcastReceivers":[],"type":"cn.kevinkun.BingMap.BingMap","androidMinSdk":["7"]}]
|
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
[{"categoryString":"EXTENSION","dateBuilt":"2023-09-21","nonVisible":"true","iconName":"https://res.cloudinary.com/andromedaviewflyvipul/image/upload/c_scale,h_20,w_20/v1571472765/ktvu4bapylsvnykoyhdm.png","methods":[{"deprecated":"false","name":"GetVoicesList","description":"Gets a list of voices in dictionary form","params":[]},{"deprecated":"false","name":"TextToSpeech","description":"Converts text to speech and saves audio file to output path","params":[{"name":"text","type":"text"},{"name":"outputhFile","type":"text"},{"name":"voiceName","type":"text"},{"name":"voiceGender","type":"text"},{"name":"voiceLang","type":"text"}]}],"blockProperties":[{"rw":"write-only","deprecated":"false","name":"ApiKey","description":"Set API key","type":"text"},{"rw":"write-only","deprecated":"false","name":"ResourceRegion","description":"Set resource region","type":"text"}],"helpUrl":"https://sunnythedeveloper.in/microsofttts-convert-text-to-audio-using-microsoft-texttospeech-api/","licenseName":"https://creativecommons.org/licenses/by-sa/4.0/","type":"com.sunny.tts.MicrosoftTTS","androidMinSdk":7,"versionName":"1","version":"4","external":"true","showOnPalette":"true","name":"MicrosoftTTS","helpString":"<p>Extension to convert Text to Speech using Microsoft Text-To-Speech API <br> Developed by Sunny Gupta<\/p>\n","events":[{"deprecated":"false","name":"GotVoicesList","description":"Event raised after gettting voices list. Each list element is a dictionary containing voice information.","params":[{"name":"reponseList","type":"list"}]},{"deprecated":"false","name":"GotError","description":"Event raised when error occurs","params":[{"name":"methodName","type":"text"},{"name":"errorMsg","type":"text"}]},{"deprecated":"false","name":"GotSpeechFile","description":"Event raised when text was successfully converted to audio and file was saved","params":[{"name":"outputFile","type":"text"}]}],"properties":[]}]
|
@ -0,0 +1,2 @@
|
|||||||
|
type=external
|
||||||
|
rush-version=1.2.4
|
Binary file not shown.
@ -0,0 +1 @@
|
|||||||
|
[{"assets":[],"activities":[],"permissions":["android.permission.INTERNET"],"type":"com.sunny.tts.MicrosoftTTS","androidMinSdk":[7]}]
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,10 @@
|
|||||||
|
<xml xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<block type="component_event" id="Nu],`$R_Zo+h]%t0u=E{" x="undefined" y="undefined">
|
||||||
|
<mutation component_type="Button" instance_name="按钮1" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮1</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_closeScreen" id="_5tFHD60tzn_~.}x1DrY"></block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<yacodeblocks ya-version="172" language-version="22"></yacodeblocks>
|
||||||
|
</xml>
|
@ -0,0 +1,41 @@
|
|||||||
|
<xml xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<block type="component_set_get" id="D7C,mwN}q=UqZ*a5_PK=" x="-411" y="32">
|
||||||
|
<mutation component_type="Button" set_or_get="get" property_name="Enabled" is_generic="false" instance_name="按钮_避障"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_避障</field>
|
||||||
|
<field name="PROP">Enabled</field>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="1E~cny;xyoBW`6/m=u_i" x="-415" y="87">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_实时定位" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_实时定位</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_openAnotherScreen" id="$]%v|uY7Rfv~rk!)@z(3">
|
||||||
|
<value name="SCREEN">
|
||||||
|
<block type="text" id=".gh[SARAdd)M$4]M~[y%">
|
||||||
|
<field name="TEXT">dingwei</field>
|
||||||
|
</block>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="20%u[R^-3+ouiy8=/CZF" x="-420" y="187">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_拨打电话" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_拨打电话</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_openAnotherScreen" id="JMzPot8U;!E~]~U4SNd+">
|
||||||
|
<value name="SCREEN">
|
||||||
|
<block type="text" id="rD6.uP~:nv;itN019[.z">
|
||||||
|
<field name="TEXT">calling</field>
|
||||||
|
</block>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="7H89A@N/?oES:zQX.*:v" x="-411" y="296">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_关闭" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_关闭</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_closeScreen" id="r1-697xyD#}ga{a..=0l"></block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<yacodeblocks ya-version="172" language-version="22"></yacodeblocks>
|
||||||
|
</xml>
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net","ai2.appinventor.mit.edu"],"YaVersion":"224","Source":"Form","Properties":{"$Name":"Screen1","$Type":"Form","$Version":"30","ActionBar":"True","AlignHorizontal":"3","AppName":"ruangong","ShowListsAsJson":"False","Theme":"AppTheme.Light.DarkActionBar","Title":"\u907f\u969c\u76d1\u6d4b","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c404","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","Height":"378","Width":"-2","Uuid":"-1446214067","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"7","Text":"\u907f\u969c","Uuid":"-644814633"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"7","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"-18939228"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"7","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"2062084651"},{"$Name":"\u5217\u8868\u9009\u62e9\u6846_\u84dd\u7259","$Type":"ListPicker","$Version":"9","Text":"\u8fde\u63a5\u8bbe\u5907","Uuid":"-1183153014"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c403","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"-2055779801","$Components":[{"$Name":"\u6807\u7b7e_\u8ddd\u79bb","$Type":"Label","$Version":"5","Text":"\u8ddd\u79bb\uff1a","Uuid":"1009622951"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ddd\u79bb","$Type":"TextBox","$Version":"6","ReadOnly":"True","Uuid":"-2128440146"},{"$Name":"\u64ad\u62a5","$Type":"CheckBox","$Version":"2","Text":"\u81ea\u52a8\u64ad\u62a5","Uuid":"405481945"}]},{"$Name":"\u5782\u76f4\u5e03\u5c401","$Type":"VerticalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"3","Height":"-1040","Width":"-2","Uuid":"2081953051","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"177806287","$Components":[{"$Name":"\u6807\u7b7e_\u8ba1\u65f6","$Type":"Label","$Version":"5","Text":"\u8ba1\u65f6\uff1a","Uuid":"-911991431"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ba1\u65f6","$Type":"TextBox","$Version":"6","NumbersOnly":"True","Text":"1","Uuid":"-152203493"},{"$Name":"\u6309\u94ae2","$Type":"Button","$Version":"7","Text":"\u786e\u5b9a","Uuid":"-444567807"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c405","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"2","AlignVertical":"3","Width":"-2","Uuid":"-1161251788","$Components":[{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"7","Text":"\u5173\u95ed","Uuid":"1124974886"}]}]},{"$Name":"\u84dd\u7259\u5ba2\u6237\u7aef1","$Type":"BluetoothClient","$Version":"8","Uuid":"-1413619977"},{"$Name":"\u8ba1\u65f6\u56681","$Type":"Clock","$Version":"4","Uuid":"530919694"},{"$Name":"\u6587\u672c\u8bed\u97f3\u8f6c\u6362\u56681","$Type":"TextToSpeech","$Version":"6","Uuid":"-1761536897"},{"$Name":"\u77ed\u4fe1\u6536\u53d1\u56681","$Type":"Texting","$Version":"5","Uuid":"-1268134884"}]}}
|
||||||
|
|#
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net"],"YaVersion":"172","Source":"Form","Properties":{"$Name":"Screen1","$Type":"Form","$Version":"23","AppName":"ruangong","Title":"Screen1","Uuid":"0","$Components":[{"$Name":"\u5217\u8868\u663e\u793a\u68461","$Type":"ListView","$Version":"5","Uuid":"1220671722"},{"$Name":"\u6309\u94ae1","$Type":"Button","$Version":"6","Text":"\u5173\u95ed","Uuid":"-644814633"}]}}
|
||||||
|
|#
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net"],"YaVersion":"172","Source":"Form","Properties":{"$Name":"Screen1","$Type":"Form","$Version":"23","AlignHorizontal":"3","AlignVertical":"2","AppName":"ruangong","Title":"Screen1","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c401","$Type":"HorizontalArrangement","$Version":"3","AlignHorizontal":"3","AlignVertical":"2","Uuid":"177113749","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"6","Text":"\u907f\u969c","Uuid":"-644814633"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"6","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"-18939228"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"6","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"2062084651"}]},{"$Name":"\u5782\u76f4\u5e03\u5c401","$Type":"VerticalArrangement","$Version":"3","AlignHorizontal":"2","AlignVertical":"2","Uuid":"-1007398740","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"3","Uuid":"177806287","$Components":[{"$Name":"\u6807\u7b7e_\u8ba1\u65f6","$Type":"Label","$Version":"4","Text":"\u8ba1\u65f6\uff1a","Uuid":"-911991431"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ba1\u65f6","$Type":"TextBox","$Version":"5","Uuid":"-152203493"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c403","$Type":"HorizontalArrangement","$Version":"3","Uuid":"-2055779801","$Components":[{"$Name":"\u6807\u7b7e_\u8ddd\u79bb","$Type":"Label","$Version":"4","Text":"\u8ddd\u79bb\uff1a","Uuid":"1009622951"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ddd\u79bb","$Type":"TextBox","$Version":"5","Uuid":"-2128440146"}]},{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"6","Text":"\u5173\u95ed","Uuid":"1124974886"}]}]}}
|
||||||
|
|#
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net","ai2.appinventor.mit.edu"],"YaVersion":"224","Source":"Form","Properties":{"$Name":"calling","$Type":"Form","$Version":"30","ActionBar":"True","AlignHorizontal":"3","AppName":"ruangong","ShowListsAsJson":"False","Theme":"AppTheme.Light.DarkActionBar","Title":"\u62e8\u6253\u7535\u8bdd","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c401","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","Height":"-1040","Width":"-2","Uuid":"497758912","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"7","Text":"\u907f\u969c","Uuid":"-1955423566"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"7","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"479692292"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"7","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"-1181431767"}]},{"$Name":"\u5782\u76f4\u5e03\u5c401","$Type":"VerticalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"-1614689866","$Components":[{"$Name":"\u5217\u8868\u9009\u62e9\u6846_\u7535\u8bdd\u5217\u8868","$Type":"ListPicker","$Version":"9","ElementsFromString":"123,19968538899","Text":"\u9009\u62e9\u7d27\u6025\u8054\u7cfb\u4eba","Uuid":"-1795360827"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"2","AlignVertical":"3","Height":"-1040","Width":"-2","Uuid":"923044023","$Components":[{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"7","Text":"\u5173\u95ed","Uuid":"2024754233"}]},{"$Name":"\u7535\u8bdd\u62e8\u53f7\u5668_\u62e8\u6253\u7535\u8bdd","$Type":"PhoneCall","$Version":"3","Uuid":"-1369827363"}]}}
|
||||||
|
|#
|
File diff suppressed because one or more lines are too long
@ -0,0 +1,41 @@
|
|||||||
|
<xml xmlns="http://www.w3.org/1999/xhtml">
|
||||||
|
<block type="component_event" id="l0r/-ENMfb_}qL~ed0!E" x="-472" y="-89">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_避障" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_避障</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_openAnotherScreen" id="=fZ9R$eQnx9W=T/0WSns">
|
||||||
|
<value name="SCREEN">
|
||||||
|
<block type="text" id="9b{~^NYOdkLaS]=}B?0n">
|
||||||
|
<field name="TEXT">Screen1</field>
|
||||||
|
</block>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<block type="component_set_get" id="]6?;)6zP7hTG8D.)LDl$" x="-468" y="8">
|
||||||
|
<mutation component_type="Button" set_or_get="get" property_name="Enabled" is_generic="false" instance_name="按钮_实时定位"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_实时定位</field>
|
||||||
|
<field name="PROP">Enabled</field>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="20%u[R^-3+ouiy8=/CZF" x="-475" y="56">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_拨打电话" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_拨打电话</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_openAnotherScreen" id="JMzPot8U;!E~]~U4SNd+">
|
||||||
|
<value name="SCREEN">
|
||||||
|
<block type="text" id="rD6.uP~:nv;itN019[.z">
|
||||||
|
<field name="TEXT">calling</field>
|
||||||
|
</block>
|
||||||
|
</value>
|
||||||
|
</block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<block type="component_event" id="7H89A@N/?oES:zQX.*:v" x="-472" y="152">
|
||||||
|
<mutation component_type="Button" instance_name="按钮_关闭" event_name="Click"></mutation>
|
||||||
|
<field name="COMPONENT_SELECTOR">按钮_关闭</field>
|
||||||
|
<statement name="DO">
|
||||||
|
<block type="controls_closeScreen" id="r1-697xyD#}ga{a..=0l"></block>
|
||||||
|
</statement>
|
||||||
|
</block>
|
||||||
|
<yacodeblocks ya-version="172" language-version="22"></yacodeblocks>
|
||||||
|
</xml>
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net","ai2.appinventor.mit.edu"],"YaVersion":"224","Source":"Form","Properties":{"$Name":"dingwei","$Type":"Form","$Version":"30","ActionBar":"True","AlignHorizontal":"3","AppName":"ruangong","ShowListsAsJson":"False","Theme":"AppTheme.Light.DarkActionBar","Title":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c401","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"-655869917","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"7","Text":"\u907f\u969c","Uuid":"1440681744"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"7","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"652290387"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"7","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"1956743508"},{"$Name":"\u5217\u8868\u9009\u62e9\u6846_\u84dd\u7259","$Type":"ListPicker","$Version":"9","Text":"\u8fde\u63a5\u8bbe\u5907","Uuid":"1464195969"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c403","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Height":"-1060","Width":"-2","Uuid":"1743176679","$Components":[{"$Name":"Web\u6d4f\u89c8\u68461","$Type":"WebViewer","$Version":"10","Uuid":"177751501"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"3","AlignVertical":"2","Uuid":"785425026","$Components":[{"$Name":"\u5237\u65b0","$Type":"Button","$Version":"7","Text":"\u5237\u65b0","Uuid":"-990679099"}]},{"$Name":"\u6c34\u5e73\u5e03\u5c404","$Type":"HorizontalArrangement","$Version":"4","AlignHorizontal":"2","AlignVertical":"3","Height":"-1020","Width":"-2","Uuid":"1670022820","$Components":[{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"7","Text":"\u5173\u95ed","Uuid":"-1949803666"}]},{"$Name":"\u84dd\u7259\u5ba2\u6237\u7aef1","$Type":"BluetoothClient","$Version":"8","Uuid":"985772490"},{"$Name":"\u8ba1\u65f6\u56681","$Type":"Clock","$Version":"4","TimerInterval":"40000","Uuid":"-415083639"},{"$Name":"\u4f4d\u7f6e\u4f20\u611f\u56681","$Type":"LocationSensor","$Version":"3","Uuid":"-939539378"},{"$Name":"Web\u5ba2\u6237\u7aef1","$Type":"Web","$Version":"8","Uuid":"-1721494346"},{"$Name":"\u77ed\u4fe1\u6536\u53d1\u56681","$Type":"Texting","$Version":"5","Uuid":"-459111995"}]}}
|
||||||
|
|#
|
@ -0,0 +1,4 @@
|
|||||||
|
#|
|
||||||
|
$JSON
|
||||||
|
{"authURL":["app.gzjkw.net"],"YaVersion":"172","Source":"Form","Properties":{"$Name":"dingwei","$Type":"Form","$Version":"23","AlignHorizontal":"3","AlignVertical":"2","AppName":"ruangong","Title":"dingwei","Uuid":"0","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c401","$Type":"HorizontalArrangement","$Version":"3","AlignHorizontal":"3","Uuid":"-655869917","$Components":[{"$Name":"\u6309\u94ae_\u907f\u969c","$Type":"Button","$Version":"6","Text":"\u907f\u969c","Uuid":"1440681744"},{"$Name":"\u6309\u94ae_\u5b9e\u65f6\u5b9a\u4f4d","$Type":"Button","$Version":"6","Text":"\u5b9e\u65f6\u5b9a\u4f4d","Uuid":"652290387"},{"$Name":"\u6309\u94ae_\u62e8\u6253\u7535\u8bdd","$Type":"Button","$Version":"6","Text":"\u62e8\u6253\u7535\u8bdd","Uuid":"1956743508"}]},{"$Name":"\u5782\u76f4\u5e03\u5c401","$Type":"VerticalArrangement","$Version":"3","AlignHorizontal":"2","Uuid":"525050555","$Components":[{"$Name":"\u6c34\u5e73\u5e03\u5c402","$Type":"HorizontalArrangement","$Version":"3","Uuid":"785425026","$Components":[{"$Name":"\u6807\u7b7e_\u8ba1\u65f6\u63d0\u793a","$Type":"Label","$Version":"4","Text":"\u8ba1\u65f6\uff1a","Uuid":"-1133475828"},{"$Name":"\u6587\u672c\u8f93\u5165\u6846_\u8ba1\u65f6","$Type":"TextBox","$Version":"5","Uuid":"2119337786"}]},{"$Name":"\u6309\u94ae_\u5173\u95ed","$Type":"Button","$Version":"6","Text":"\u5173\u95ed","Uuid":"139342436"}]}]}}
|
||||||
|
|#
|
@ -0,0 +1,19 @@
|
|||||||
|
#
|
||||||
|
#Tue Nov 14 13:34:00 UTC 2023
|
||||||
|
source=../src
|
||||||
|
name=ruangong
|
||||||
|
defaultfilescope=App
|
||||||
|
main=appinventor.ai_dustinzrm.ruangong.Screen1
|
||||||
|
color.accent=&HFFFF4081
|
||||||
|
sizing=Responsive
|
||||||
|
assets=../assets
|
||||||
|
theme=AppTheme.Light.DarkActionBar
|
||||||
|
showlistsasjson=False
|
||||||
|
useslocation=False
|
||||||
|
aname=ruangong
|
||||||
|
actionbar=True
|
||||||
|
color.primary=&HFF3F51B5
|
||||||
|
build=../build
|
||||||
|
versionname=1.0
|
||||||
|
versioncode=1
|
||||||
|
color.primary.dark=&HFF303F9F
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,225 @@
|
|||||||
|
/*
|
||||||
|
Arduino.h - Main include file for the Arduino SDK
|
||||||
|
Copyright (c) 2005-2013 Arduino Team. All right 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 Arduino_h
|
||||||
|
#define Arduino_h
|
||||||
|
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "esp_arduino_version.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "esp8266-compat.h"
|
||||||
|
#include "soc/gpio_reg.h"
|
||||||
|
|
||||||
|
#include "stdlib_noniso.h"
|
||||||
|
#include "binary.h"
|
||||||
|
|
||||||
|
#define PI 3.1415926535897932384626433832795
|
||||||
|
#define HALF_PI 1.5707963267948966192313216916398
|
||||||
|
#define TWO_PI 6.283185307179586476925286766559
|
||||||
|
#define DEG_TO_RAD 0.017453292519943295769236907684886
|
||||||
|
#define RAD_TO_DEG 57.295779513082320876798154814105
|
||||||
|
#define EULER 2.718281828459045235360287471352
|
||||||
|
|
||||||
|
#define SERIAL 0x0
|
||||||
|
#define DISPLAY 0x1
|
||||||
|
|
||||||
|
#define LSBFIRST 0
|
||||||
|
#define MSBFIRST 1
|
||||||
|
|
||||||
|
//Interrupt Modes
|
||||||
|
#define RISING 0x01
|
||||||
|
#define FALLING 0x02
|
||||||
|
#define CHANGE 0x03
|
||||||
|
#define ONLOW 0x04
|
||||||
|
#define ONHIGH 0x05
|
||||||
|
#define ONLOW_WE 0x0C
|
||||||
|
#define ONHIGH_WE 0x0D
|
||||||
|
|
||||||
|
#define DEFAULT 1
|
||||||
|
#define EXTERNAL 0
|
||||||
|
|
||||||
|
#ifndef __STRINGIFY
|
||||||
|
#define __STRINGIFY(a) #a
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// can't define max() / min() because of conflicts with C++
|
||||||
|
#define _min(a,b) ((a)<(b)?(a):(b))
|
||||||
|
#define _max(a,b) ((a)>(b)?(a):(b))
|
||||||
|
#define _abs(x) ((x)>0?(x):-(x)) // abs() comes from STL
|
||||||
|
#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))
|
||||||
|
#define _round(x) ((x)>=0?(long)((x)+0.5):(long)((x)-0.5)) // round() comes from STL
|
||||||
|
#define radians(deg) ((deg)*DEG_TO_RAD)
|
||||||
|
#define degrees(rad) ((rad)*RAD_TO_DEG)
|
||||||
|
#define sq(x) ((x)*(x))
|
||||||
|
|
||||||
|
// ESP32xx runs FreeRTOS... disabling interrupts can lead to issues, such as Watchdog Timeout
|
||||||
|
#define sei() portENABLE_INTERRUPTS()
|
||||||
|
#define cli() portDISABLE_INTERRUPTS()
|
||||||
|
#define interrupts() sei()
|
||||||
|
#define noInterrupts() cli()
|
||||||
|
|
||||||
|
#define clockCyclesPerMicrosecond() ( (long int)getCpuFrequencyMhz() )
|
||||||
|
#define clockCyclesToMicroseconds(a) ( (a) / clockCyclesPerMicrosecond() )
|
||||||
|
#define microsecondsToClockCycles(a) ( (a) * clockCyclesPerMicrosecond() )
|
||||||
|
|
||||||
|
#define lowByte(w) ((uint8_t) ((w) & 0xff))
|
||||||
|
#define highByte(w) ((uint8_t) ((w) >> 8))
|
||||||
|
|
||||||
|
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
|
||||||
|
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
|
||||||
|
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
|
||||||
|
#define bitToggle(value, bit) ((value) ^= (1UL << (bit)))
|
||||||
|
#define bitWrite(value, bit, bitvalue) ((bitvalue) ? bitSet(value, bit) : bitClear(value, bit))
|
||||||
|
|
||||||
|
// avr-libc defines _NOP() since 1.6.2
|
||||||
|
#ifndef _NOP
|
||||||
|
#define _NOP() do { __asm__ volatile ("nop"); } while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define bit(b) (1UL << (b))
|
||||||
|
#define _BV(b) (1UL << (b))
|
||||||
|
|
||||||
|
#define digitalPinToTimer(pin) (0)
|
||||||
|
#define analogInPinToBit(P) (P)
|
||||||
|
#if SOC_GPIO_PIN_COUNT <= 32
|
||||||
|
#define digitalPinToPort(pin) (0)
|
||||||
|
#define digitalPinToBitMask(pin) (1UL << digitalPinToGPIONumber(pin))
|
||||||
|
#define portOutputRegister(port) ((volatile uint32_t*)GPIO_OUT_REG)
|
||||||
|
#define portInputRegister(port) ((volatile uint32_t*)GPIO_IN_REG)
|
||||||
|
#define portModeRegister(port) ((volatile uint32_t*)GPIO_ENABLE_REG)
|
||||||
|
#elif SOC_GPIO_PIN_COUNT <= 64
|
||||||
|
#define digitalPinToPort(pin) ((digitalPinToGPIONumber(pin)>31)?1:0)
|
||||||
|
#define digitalPinToBitMask(pin) (1UL << (digitalPinToGPIONumber(pin)&31))
|
||||||
|
#define portOutputRegister(port) ((volatile uint32_t*)((port)?GPIO_OUT1_REG:GPIO_OUT_REG))
|
||||||
|
#define portInputRegister(port) ((volatile uint32_t*)((port)?GPIO_IN1_REG:GPIO_IN_REG))
|
||||||
|
#define portModeRegister(port) ((volatile uint32_t*)((port)?GPIO_ENABLE1_REG:GPIO_ENABLE_REG))
|
||||||
|
#else
|
||||||
|
#error SOC_GPIO_PIN_COUNT > 64 not implemented
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define NOT_A_PIN -1
|
||||||
|
#define NOT_A_PORT -1
|
||||||
|
#define NOT_AN_INTERRUPT -1
|
||||||
|
#define NOT_ON_TIMER 0
|
||||||
|
|
||||||
|
typedef bool boolean;
|
||||||
|
typedef uint8_t byte;
|
||||||
|
typedef unsigned int word;
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
void setup(void);
|
||||||
|
void loop(void);
|
||||||
|
|
||||||
|
// The default is using Real Hardware random number generator
|
||||||
|
// But when randomSeed() is called, it turns to Psedo random
|
||||||
|
// generator, exactly as done in Arduino mainstream
|
||||||
|
long random(long);
|
||||||
|
long random(long, long);
|
||||||
|
// Calling randomSeed() will make random()
|
||||||
|
// using pseudo random like in Arduino
|
||||||
|
void randomSeed(unsigned long);
|
||||||
|
// Allow the Application to decide if the random generator
|
||||||
|
// will use Real Hardware random generation (true - default)
|
||||||
|
// or Pseudo random generation (false) as in Arduino MainStream
|
||||||
|
void useRealRandomGenerator(bool useRandomHW);
|
||||||
|
#endif
|
||||||
|
long map(long, long, long, long, long);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void init(void);
|
||||||
|
void initVariant(void);
|
||||||
|
void initArduino(void);
|
||||||
|
|
||||||
|
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout);
|
||||||
|
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout);
|
||||||
|
|
||||||
|
uint8_t shiftIn(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder);
|
||||||
|
void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
#include "WCharacter.h"
|
||||||
|
#include "WString.h"
|
||||||
|
#include "Stream.h"
|
||||||
|
#include "Printable.h"
|
||||||
|
#include "Print.h"
|
||||||
|
#include "IPAddress.h"
|
||||||
|
#include "Client.h"
|
||||||
|
#include "Server.h"
|
||||||
|
#include "Udp.h"
|
||||||
|
#include "HardwareSerial.h"
|
||||||
|
#include "Esp.h"
|
||||||
|
#include "esp32/spiram.h"
|
||||||
|
|
||||||
|
// Use float-compatible stl abs() and round(), we don't use Arduino macros to avoid issues with the C++ libraries
|
||||||
|
using std::abs;
|
||||||
|
using std::isinf;
|
||||||
|
using std::isnan;
|
||||||
|
using std::max;
|
||||||
|
using std::min;
|
||||||
|
using std::round;
|
||||||
|
|
||||||
|
uint16_t makeWord(uint16_t w);
|
||||||
|
uint16_t makeWord(uint8_t h, uint8_t l);
|
||||||
|
|
||||||
|
#define word(...) makeWord(__VA_ARGS__)
|
||||||
|
|
||||||
|
size_t getArduinoLoopTaskStackSize(void);
|
||||||
|
#define SET_LOOP_TASK_STACK_SIZE(sz) size_t getArduinoLoopTaskStackSize() { return sz;}
|
||||||
|
|
||||||
|
// allows user to bypass esp_spiram_test()
|
||||||
|
#define BYPASS_SPIRAM_TEST(bypass) bool testSPIRAM(void) { if (bypass) return true; else return esp_spiram_test(); }
|
||||||
|
|
||||||
|
unsigned long pulseIn(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L);
|
||||||
|
unsigned long pulseInLong(uint8_t pin, uint8_t state, unsigned long timeout = 1000000L);
|
||||||
|
|
||||||
|
extern "C" bool getLocalTime(struct tm * info, uint32_t ms = 5000);
|
||||||
|
extern "C" void configTime(long gmtOffset_sec, int daylightOffset_sec,
|
||||||
|
const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);
|
||||||
|
extern "C" void configTzTime(const char* tz,
|
||||||
|
const char* server1, const char* server2 = nullptr, const char* server3 = nullptr);
|
||||||
|
|
||||||
|
void setToneChannel(uint8_t channel = 0);
|
||||||
|
void tone(uint8_t _pin, unsigned int frequency, unsigned long duration = 0);
|
||||||
|
void noTone(uint8_t _pin);
|
||||||
|
|
||||||
|
#endif /* __cplusplus */
|
||||||
|
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
#include "io_pin_remap.h"
|
||||||
|
|
||||||
|
#endif /* _ESP32_CORE_ARDUINO_H_ */
|
@ -0,0 +1,48 @@
|
|||||||
|
/*
|
||||||
|
Client.h - Base class that provides Client
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right 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 client_h
|
||||||
|
#define client_h
|
||||||
|
#include "Print.h"
|
||||||
|
#include "Stream.h"
|
||||||
|
#include "IPAddress.h"
|
||||||
|
|
||||||
|
class Client: public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual int connect(IPAddress ip, uint16_t port) =0;
|
||||||
|
virtual int connect(const char *host, uint16_t port) =0;
|
||||||
|
virtual size_t write(uint8_t) =0;
|
||||||
|
virtual size_t write(const uint8_t *buf, size_t size) =0;
|
||||||
|
virtual int available() = 0;
|
||||||
|
virtual int read() = 0;
|
||||||
|
virtual int read(uint8_t *buf, size_t size) = 0;
|
||||||
|
virtual int peek() = 0;
|
||||||
|
virtual void flush() = 0;
|
||||||
|
virtual void stop() = 0;
|
||||||
|
virtual uint8_t connected() = 0;
|
||||||
|
virtual operator bool() = 0;
|
||||||
|
protected:
|
||||||
|
uint8_t* rawIPAddress(IPAddress& addr)
|
||||||
|
{
|
||||||
|
return addr.raw_address();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,450 @@
|
|||||||
|
/*
|
||||||
|
Esp.cpp - ESP31B-specific APIs
|
||||||
|
Copyright (c) 2015 Ivan Grokhotkov. 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Esp.h"
|
||||||
|
#include "esp_sleep.h"
|
||||||
|
#include "esp_spi_flash.h"
|
||||||
|
#include <memory>
|
||||||
|
#include <soc/soc.h>
|
||||||
|
#include <esp_partition.h>
|
||||||
|
extern "C" {
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp_image_format.h"
|
||||||
|
}
|
||||||
|
#include <MD5Builder.h>
|
||||||
|
|
||||||
|
#include "soc/spi_reg.h"
|
||||||
|
#include "esp_system.h"
|
||||||
|
#ifdef ESP_IDF_VERSION_MAJOR // IDF 4+
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
|
||||||
|
#include "esp32/rom/spi_flash.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x1000 // Flash offset containing flash size and spi mode
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#include "esp32s2/rom/spi_flash.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x1000
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#include "esp32s3/rom/spi_flash.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x0000 // Esp32s3 is located at 0x0000
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#include "esp32c3/rom/spi_flash.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x0000 // Esp32c3 is located at 0x0000
|
||||||
|
#else
|
||||||
|
#error Target CONFIG_IDF_TARGET is not supported
|
||||||
|
#endif
|
||||||
|
#else // ESP32 Before IDF 4.0
|
||||||
|
#include "rom/spi_flash.h"
|
||||||
|
#define ESP_FLASH_IMAGE_BASE 0x1000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// REG_SPI_BASE is not defined for S3/C3 ??
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#ifndef REG_SPI_BASE
|
||||||
|
#define REG_SPI_BASE(i) (DR_REG_SPI1_BASE + (((i)>1) ? (((i)* 0x1000) + 0x20000) : (((~(i)) & 1)* 0x1000 )))
|
||||||
|
#endif // REG_SPI_BASE
|
||||||
|
#endif // TARGET
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User-defined Literals
|
||||||
|
* usage:
|
||||||
|
*
|
||||||
|
* uint32_t = test = 10_MHz; // --> 10000000
|
||||||
|
*/
|
||||||
|
|
||||||
|
unsigned long long operator"" _kHz(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _MHz(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1000 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _GHz(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1000 * 1000 * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _kBit(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _MBit(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _GBit(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _kB(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _MB(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned long long operator"" _GB(unsigned long long x)
|
||||||
|
{
|
||||||
|
return x * 1024 * 1024 * 1024;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
EspClass ESP;
|
||||||
|
|
||||||
|
void EspClass::deepSleep(uint32_t time_us)
|
||||||
|
{
|
||||||
|
esp_deep_sleep(time_us);
|
||||||
|
}
|
||||||
|
|
||||||
|
void EspClass::restart(void)
|
||||||
|
{
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getHeapSize(void)
|
||||||
|
{
|
||||||
|
multi_heap_info_t info;
|
||||||
|
heap_caps_get_info(&info, MALLOC_CAP_INTERNAL);
|
||||||
|
return info.total_free_bytes + info.total_allocated_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFreeHeap(void)
|
||||||
|
{
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getMinFreeHeap(void)
|
||||||
|
{
|
||||||
|
return heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getMaxAllocHeap(void)
|
||||||
|
{
|
||||||
|
return heap_caps_get_largest_free_block(MALLOC_CAP_INTERNAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getPsramSize(void)
|
||||||
|
{
|
||||||
|
if(psramFound()){
|
||||||
|
multi_heap_info_t info;
|
||||||
|
heap_caps_get_info(&info, MALLOC_CAP_SPIRAM);
|
||||||
|
return info.total_free_bytes + info.total_allocated_bytes;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFreePsram(void)
|
||||||
|
{
|
||||||
|
if(psramFound()){
|
||||||
|
return heap_caps_get_free_size(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getMinFreePsram(void)
|
||||||
|
{
|
||||||
|
if(psramFound()){
|
||||||
|
return heap_caps_get_minimum_free_size(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getMaxAllocPsram(void)
|
||||||
|
{
|
||||||
|
if(psramFound()){
|
||||||
|
return heap_caps_get_largest_free_block(MALLOC_CAP_SPIRAM);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t sketchSize(sketchSize_t response) {
|
||||||
|
esp_image_metadata_t data;
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
if (!running) return 0;
|
||||||
|
const esp_partition_pos_t running_pos = {
|
||||||
|
.offset = running->address,
|
||||||
|
.size = running->size,
|
||||||
|
};
|
||||||
|
data.start_addr = running_pos.offset;
|
||||||
|
esp_image_verify(ESP_IMAGE_VERIFY, &running_pos, &data);
|
||||||
|
if (response) {
|
||||||
|
return running_pos.size - data.image_len;
|
||||||
|
} else {
|
||||||
|
return data.image_len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getSketchSize () {
|
||||||
|
return sketchSize(SKETCH_SIZE_TOTAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
String EspClass::getSketchMD5()
|
||||||
|
{
|
||||||
|
static String result;
|
||||||
|
if (result.length()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
uint32_t lengthLeft = getSketchSize();
|
||||||
|
|
||||||
|
const esp_partition_t *running = esp_ota_get_running_partition();
|
||||||
|
if (!running) {
|
||||||
|
log_e("Partition could not be found");
|
||||||
|
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
const size_t bufSize = SPI_FLASH_SEC_SIZE;
|
||||||
|
std::unique_ptr<uint8_t[]> buf(new uint8_t[bufSize]);
|
||||||
|
uint32_t offset = 0;
|
||||||
|
if(!buf.get()) {
|
||||||
|
log_e("Not enough memory to allocate buffer");
|
||||||
|
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
MD5Builder md5;
|
||||||
|
md5.begin();
|
||||||
|
while( lengthLeft > 0) {
|
||||||
|
size_t readBytes = (lengthLeft < bufSize) ? lengthLeft : bufSize;
|
||||||
|
if (!ESP.flashRead(running->address + offset, reinterpret_cast<uint32_t*>(buf.get()), (readBytes + 3) & ~3)) {
|
||||||
|
log_e("Could not read buffer from flash");
|
||||||
|
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
md5.add(buf.get(), readBytes);
|
||||||
|
lengthLeft -= readBytes;
|
||||||
|
offset += readBytes;
|
||||||
|
}
|
||||||
|
md5.calculate();
|
||||||
|
result = md5.toString();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFreeSketchSpace () {
|
||||||
|
const esp_partition_t* _partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
if(!_partition){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _partition->size;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t EspClass::getChipRevision(void)
|
||||||
|
{
|
||||||
|
esp_chip_info_t chip_info;
|
||||||
|
esp_chip_info(&chip_info);
|
||||||
|
return chip_info.revision;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * EspClass::getChipModel(void)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
uint32_t chip_ver = REG_GET_FIELD(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_VER_PKG);
|
||||||
|
uint32_t pkg_ver = chip_ver & 0x7;
|
||||||
|
switch (pkg_ver) {
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDQ6 :
|
||||||
|
if (getChipRevision() == 3)
|
||||||
|
return "ESP32-D0WDQ6-V3";
|
||||||
|
else
|
||||||
|
return "ESP32-D0WDQ6";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDQ5 :
|
||||||
|
if (getChipRevision() == 3)
|
||||||
|
return "ESP32-D0WD-V3";
|
||||||
|
else
|
||||||
|
return "ESP32-D0WD";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32D2WDQ5 :
|
||||||
|
return "ESP32-D2WD";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD2 :
|
||||||
|
return "ESP32-PICO-D2";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32PICOD4 :
|
||||||
|
return "ESP32-PICO-D4";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32PICOV302 :
|
||||||
|
return "ESP32-PICO-V3-02";
|
||||||
|
case EFUSE_RD_CHIP_VER_PKG_ESP32D0WDR2V3 :
|
||||||
|
return "ESP32-D0WDR2-V3";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
uint32_t pkg_ver = REG_GET_FIELD(EFUSE_RD_MAC_SPI_SYS_3_REG, EFUSE_PKG_VERSION);
|
||||||
|
switch (pkg_ver) {
|
||||||
|
case 0:
|
||||||
|
return "ESP32-S2";
|
||||||
|
case 1:
|
||||||
|
return "ESP32-S2FH16";
|
||||||
|
case 2:
|
||||||
|
return "ESP32-S2FH32";
|
||||||
|
default:
|
||||||
|
return "ESP32-S2 (Unknown)";
|
||||||
|
}
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return "ESP32-S3";
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
return "ESP32-C3";
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t EspClass::getChipCores(void)
|
||||||
|
{
|
||||||
|
esp_chip_info_t chip_info;
|
||||||
|
esp_chip_info(&chip_info);
|
||||||
|
return chip_info.cores;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char * EspClass::getSdkVersion(void)
|
||||||
|
{
|
||||||
|
return esp_get_idf_version();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t ESP_getFlashChipId(void)
|
||||||
|
{
|
||||||
|
uint32_t id = g_rom_flashchip.device_id;
|
||||||
|
id = ((id & 0xff) << 16) | ((id >> 16) & 0xff) | (id & 0xff00);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFlashChipSize(void)
|
||||||
|
{
|
||||||
|
uint32_t id = (ESP_getFlashChipId() >> 16) & 0xFF;
|
||||||
|
return 2 << (id - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::getFlashChipSpeed(void)
|
||||||
|
{
|
||||||
|
esp_image_header_t fhdr;
|
||||||
|
if(flashRead(ESP_FLASH_IMAGE_BASE, (uint32_t*)&fhdr, sizeof(esp_image_header_t)) && fhdr.magic != ESP_IMAGE_HEADER_MAGIC) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return magicFlashChipSpeed(fhdr.spi_speed);
|
||||||
|
}
|
||||||
|
|
||||||
|
FlashMode_t EspClass::getFlashChipMode(void)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
uint32_t spi_ctrl = REG_READ(PERIPHS_SPI_FLASH_CTRL);
|
||||||
|
#else
|
||||||
|
uint32_t spi_ctrl = REG_READ(SPI_CTRL_REG(0));
|
||||||
|
#endif
|
||||||
|
/* Not all of the following constants are already defined in older versions of spi_reg.h, so do it manually for now*/
|
||||||
|
if (spi_ctrl & BIT(24)) { //SPI_FREAD_QIO
|
||||||
|
return (FM_QIO);
|
||||||
|
} else if (spi_ctrl & BIT(20)) { //SPI_FREAD_QUAD
|
||||||
|
return (FM_QOUT);
|
||||||
|
} else if (spi_ctrl & BIT(23)) { //SPI_FREAD_DIO
|
||||||
|
return (FM_DIO);
|
||||||
|
} else if (spi_ctrl & BIT(14)) { // SPI_FREAD_DUAL
|
||||||
|
return (FM_DOUT);
|
||||||
|
} else if (spi_ctrl & BIT(13)) { //SPI_FASTRD_MODE
|
||||||
|
return (FM_FAST_READ);
|
||||||
|
} else {
|
||||||
|
return (FM_SLOW_READ);
|
||||||
|
}
|
||||||
|
return (FM_DOUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::magicFlashChipSize(uint8_t byte)
|
||||||
|
{
|
||||||
|
switch(byte & 0x0F) {
|
||||||
|
case 0x0: // 8 MBit (1MB)
|
||||||
|
return (1_MB);
|
||||||
|
case 0x1: // 16 MBit (2MB)
|
||||||
|
return (2_MB);
|
||||||
|
case 0x2: // 32 MBit (4MB)
|
||||||
|
return (4_MB);
|
||||||
|
case 0x3: // 64 MBit (8MB)
|
||||||
|
return (8_MB);
|
||||||
|
case 0x4: // 128 MBit (16MB)
|
||||||
|
return (16_MB);
|
||||||
|
default: // fail?
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t EspClass::magicFlashChipSpeed(uint8_t byte)
|
||||||
|
{
|
||||||
|
switch(byte & 0x0F) {
|
||||||
|
case 0x0: // 40 MHz
|
||||||
|
return (40_MHz);
|
||||||
|
case 0x1: // 26 MHz
|
||||||
|
return (26_MHz);
|
||||||
|
case 0x2: // 20 MHz
|
||||||
|
return (20_MHz);
|
||||||
|
case 0xf: // 80 MHz
|
||||||
|
return (80_MHz);
|
||||||
|
default: // fail?
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FlashMode_t EspClass::magicFlashChipMode(uint8_t byte)
|
||||||
|
{
|
||||||
|
FlashMode_t mode = (FlashMode_t) byte;
|
||||||
|
if(mode > FM_SLOW_READ) {
|
||||||
|
mode = FM_UNKNOWN;
|
||||||
|
}
|
||||||
|
return mode;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashEraseSector(uint32_t sector)
|
||||||
|
{
|
||||||
|
return spi_flash_erase_sector(sector) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning: These functions do not work with encrypted flash
|
||||||
|
bool EspClass::flashWrite(uint32_t offset, uint32_t *data, size_t size)
|
||||||
|
{
|
||||||
|
return spi_flash_write(offset, (uint32_t*) data, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::flashRead(uint32_t offset, uint32_t *data, size_t size)
|
||||||
|
{
|
||||||
|
return spi_flash_read(offset, (uint32_t*) data, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::partitionEraseRange(const esp_partition_t *partition, uint32_t offset, size_t size)
|
||||||
|
{
|
||||||
|
return esp_partition_erase_range(partition, offset, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::partitionWrite(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size)
|
||||||
|
{
|
||||||
|
return esp_partition_write(partition, offset, data, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool EspClass::partitionRead(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size)
|
||||||
|
{
|
||||||
|
return esp_partition_read(partition, offset, data, size) == ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint64_t EspClass::getEfuseMac(void)
|
||||||
|
{
|
||||||
|
uint64_t _chipmacid = 0LL;
|
||||||
|
esp_efuse_mac_get_default((uint8_t*) (&_chipmacid));
|
||||||
|
return _chipmacid;
|
||||||
|
}
|
@ -0,0 +1,119 @@
|
|||||||
|
/*
|
||||||
|
Esp.h - ESP31B-specific APIs
|
||||||
|
Copyright (c) 2015 Ivan Grokhotkov. 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 ESP_H
|
||||||
|
#define ESP_H
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <esp_partition.h>
|
||||||
|
#include <hal/cpu_hal.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* AVR macros for WDT managment
|
||||||
|
*/
|
||||||
|
typedef enum {
|
||||||
|
WDTO_0MS = 0, //!< WDTO_0MS
|
||||||
|
WDTO_15MS = 15, //!< WDTO_15MS
|
||||||
|
WDTO_30MS = 30, //!< WDTO_30MS
|
||||||
|
WDTO_60MS = 60, //!< WDTO_60MS
|
||||||
|
WDTO_120MS = 120, //!< WDTO_120MS
|
||||||
|
WDTO_250MS = 250, //!< WDTO_250MS
|
||||||
|
WDTO_500MS = 500, //!< WDTO_500MS
|
||||||
|
WDTO_1S = 1000,//!< WDTO_1S
|
||||||
|
WDTO_2S = 2000,//!< WDTO_2S
|
||||||
|
WDTO_4S = 4000,//!< WDTO_4S
|
||||||
|
WDTO_8S = 8000 //!< WDTO_8S
|
||||||
|
} WDTO_t;
|
||||||
|
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
FM_QIO = 0x00,
|
||||||
|
FM_QOUT = 0x01,
|
||||||
|
FM_DIO = 0x02,
|
||||||
|
FM_DOUT = 0x03,
|
||||||
|
FM_FAST_READ = 0x04,
|
||||||
|
FM_SLOW_READ = 0x05,
|
||||||
|
FM_UNKNOWN = 0xff
|
||||||
|
} FlashMode_t;
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
SKETCH_SIZE_TOTAL = 0,
|
||||||
|
SKETCH_SIZE_FREE = 1
|
||||||
|
} sketchSize_t;
|
||||||
|
|
||||||
|
class EspClass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
EspClass() {}
|
||||||
|
~EspClass() {}
|
||||||
|
void restart();
|
||||||
|
|
||||||
|
//Internal RAM
|
||||||
|
uint32_t getHeapSize(); //total heap size
|
||||||
|
uint32_t getFreeHeap(); //available heap
|
||||||
|
uint32_t getMinFreeHeap(); //lowest level of free heap since boot
|
||||||
|
uint32_t getMaxAllocHeap(); //largest block of heap that can be allocated at once
|
||||||
|
|
||||||
|
//SPI RAM
|
||||||
|
uint32_t getPsramSize();
|
||||||
|
uint32_t getFreePsram();
|
||||||
|
uint32_t getMinFreePsram();
|
||||||
|
uint32_t getMaxAllocPsram();
|
||||||
|
|
||||||
|
uint8_t getChipRevision();
|
||||||
|
const char * getChipModel();
|
||||||
|
uint8_t getChipCores();
|
||||||
|
uint32_t getCpuFreqMHz(){ return getCpuFrequencyMhz(); }
|
||||||
|
inline uint32_t getCycleCount() __attribute__((always_inline));
|
||||||
|
const char * getSdkVersion();
|
||||||
|
|
||||||
|
void deepSleep(uint32_t time_us);
|
||||||
|
|
||||||
|
uint32_t getFlashChipSize();
|
||||||
|
uint32_t getFlashChipSpeed();
|
||||||
|
FlashMode_t getFlashChipMode();
|
||||||
|
|
||||||
|
uint32_t magicFlashChipSize(uint8_t byte);
|
||||||
|
uint32_t magicFlashChipSpeed(uint8_t byte);
|
||||||
|
FlashMode_t magicFlashChipMode(uint8_t byte);
|
||||||
|
|
||||||
|
uint32_t getSketchSize();
|
||||||
|
String getSketchMD5();
|
||||||
|
uint32_t getFreeSketchSpace();
|
||||||
|
|
||||||
|
bool flashEraseSector(uint32_t sector);
|
||||||
|
bool flashWrite(uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
bool flashRead(uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
|
||||||
|
bool partitionEraseRange(const esp_partition_t *partition, uint32_t offset, size_t size);
|
||||||
|
bool partitionWrite(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
bool partitionRead(const esp_partition_t *partition, uint32_t offset, uint32_t *data, size_t size);
|
||||||
|
|
||||||
|
uint64_t getEfuseMac();
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
uint32_t ARDUINO_ISR_ATTR EspClass::getCycleCount()
|
||||||
|
{
|
||||||
|
return cpu_hal_get_cycle_count();
|
||||||
|
}
|
||||||
|
|
||||||
|
extern EspClass ESP;
|
||||||
|
|
||||||
|
#endif //ESP_H
|
@ -0,0 +1,424 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#include "FirmwareMSC.h"
|
||||||
|
|
||||||
|
#if CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
#include "esp_partition.h"
|
||||||
|
#include "esp_ota_ops.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
#include "firmware_msc_fat.h"
|
||||||
|
|
||||||
|
#ifndef USB_FW_MSC_VENDOR_ID
|
||||||
|
#define USB_FW_MSC_VENDOR_ID "ESP32" //max 8 chars
|
||||||
|
#endif
|
||||||
|
#ifndef USB_FW_MSC_PRODUCT_ID
|
||||||
|
#define USB_FW_MSC_PRODUCT_ID "Firmware MSC"//max 16 chars
|
||||||
|
#endif
|
||||||
|
#ifndef USB_FW_MSC_PRODUCT_REVISION
|
||||||
|
#define USB_FW_MSC_PRODUCT_REVISION "1.0" //max 4 chars
|
||||||
|
#endif
|
||||||
|
#ifndef USB_FW_MSC_VOLUME_NAME
|
||||||
|
#define USB_FW_MSC_VOLUME_NAME "ESP32-FWMSC" //max 11 chars
|
||||||
|
#endif
|
||||||
|
#ifndef USB_FW_MSC_SERIAL_NUMBER
|
||||||
|
#define USB_FW_MSC_SERIAL_NUMBER 0x00000000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
ESP_EVENT_DEFINE_BASE(ARDUINO_FIRMWARE_MSC_EVENTS);
|
||||||
|
esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait);
|
||||||
|
esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg);
|
||||||
|
|
||||||
|
//General Variables
|
||||||
|
static uint8_t * msc_ram_disk = NULL;
|
||||||
|
static fat_boot_sector_t * msc_boot = NULL;
|
||||||
|
static uint8_t * msc_table = NULL;
|
||||||
|
static uint16_t msc_table_sectors = 0;
|
||||||
|
static uint16_t msc_total_sectors = 0;
|
||||||
|
static bool mcs_is_fat16 = false;
|
||||||
|
|
||||||
|
//Firmware Read
|
||||||
|
static const esp_partition_t* msc_run_partition = NULL;
|
||||||
|
static uint16_t fw_start_sector = 0;
|
||||||
|
static uint16_t fw_end_sector = 0;
|
||||||
|
static size_t fw_size = 0;
|
||||||
|
static fat_dir_entry_t * fw_entry = NULL;
|
||||||
|
|
||||||
|
//Firmware Write
|
||||||
|
typedef enum {
|
||||||
|
MSC_UPDATE_IDLE,
|
||||||
|
MSC_UPDATE_STARTING,
|
||||||
|
MSC_UPDATE_RUNNING,
|
||||||
|
MSC_UPDATE_END
|
||||||
|
} msc_update_state_t;
|
||||||
|
|
||||||
|
static const esp_partition_t* msc_ota_partition = NULL;
|
||||||
|
static msc_update_state_t msc_update_state = MSC_UPDATE_IDLE;
|
||||||
|
static uint16_t msc_update_start_sector = 0;
|
||||||
|
static uint32_t msc_update_bytes_written = 0;
|
||||||
|
static fat_dir_entry_t * msc_update_entry = NULL;
|
||||||
|
|
||||||
|
static uint32_t get_firmware_size(const esp_partition_t* partition){
|
||||||
|
esp_image_metadata_t data;
|
||||||
|
const esp_partition_pos_t running_pos = {
|
||||||
|
.offset = partition->address,
|
||||||
|
.size = partition->size,
|
||||||
|
};
|
||||||
|
data.start_addr = running_pos.offset;
|
||||||
|
esp_image_verify(ESP_IMAGE_VERIFY, &running_pos, &data);
|
||||||
|
return data.image_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Get number of sectors required based on the size of the firmware and OTA partition
|
||||||
|
static size_t msc_update_get_required_disk_sectors(){
|
||||||
|
size_t data_sectors = 16;
|
||||||
|
size_t total_sectors = 0;
|
||||||
|
msc_run_partition = esp_ota_get_running_partition();
|
||||||
|
msc_ota_partition = esp_ota_get_next_update_partition(NULL);
|
||||||
|
if(msc_run_partition){
|
||||||
|
fw_size = get_firmware_size(msc_run_partition);
|
||||||
|
data_sectors += FAT_SIZE_TO_SECTORS(fw_size);
|
||||||
|
log_d("APP size: %u (%u sectors)", fw_size, FAT_SIZE_TO_SECTORS(fw_size));
|
||||||
|
} else {
|
||||||
|
log_w("APP partition not found. Reading disabled");
|
||||||
|
}
|
||||||
|
if(msc_ota_partition){
|
||||||
|
data_sectors += FAT_SIZE_TO_SECTORS(msc_ota_partition->size);
|
||||||
|
log_d("OTA size: %u (%u sectors)", msc_ota_partition->size, FAT_SIZE_TO_SECTORS(msc_ota_partition->size));
|
||||||
|
} else {
|
||||||
|
log_w("OTA partition not found. Writing disabled");
|
||||||
|
}
|
||||||
|
msc_table_sectors = fat_sectors_per_alloc_table(data_sectors, false);
|
||||||
|
total_sectors = data_sectors + msc_table_sectors + 2;
|
||||||
|
if(total_sectors > 0xFF4){
|
||||||
|
log_d("USING FAT16");
|
||||||
|
mcs_is_fat16 = true;
|
||||||
|
total_sectors -= msc_table_sectors;
|
||||||
|
msc_table_sectors = fat_sectors_per_alloc_table(data_sectors, true);
|
||||||
|
total_sectors += msc_table_sectors;
|
||||||
|
} else {
|
||||||
|
log_d("USING FAT12");
|
||||||
|
mcs_is_fat16 = false;
|
||||||
|
}
|
||||||
|
log_d("FAT sector size: %u", DISK_SECTOR_SIZE);
|
||||||
|
log_d("FAT data sectors: %u", data_sectors);
|
||||||
|
log_d("FAT table sectors: %u", msc_table_sectors);
|
||||||
|
log_d("FAT total sectors: %u (%uKB)", total_sectors, (total_sectors * DISK_SECTOR_SIZE) / 1024);
|
||||||
|
return total_sectors;
|
||||||
|
}
|
||||||
|
|
||||||
|
//setup the ramdisk and add the firmware download file
|
||||||
|
static bool msc_update_setup_disk(const char * volume_label, uint32_t serial_number){
|
||||||
|
msc_total_sectors = msc_update_get_required_disk_sectors();
|
||||||
|
uint8_t ram_sectors = msc_table_sectors + 2;
|
||||||
|
msc_ram_disk = (uint8_t*)calloc(ram_sectors, DISK_SECTOR_SIZE);
|
||||||
|
if(!msc_ram_disk){
|
||||||
|
log_e("Failed to allocate RAM Disk: %u bytes", ram_sectors * DISK_SECTOR_SIZE);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
fw_start_sector = ram_sectors;
|
||||||
|
fw_end_sector = fw_start_sector;
|
||||||
|
msc_boot = fat_add_boot_sector(msc_ram_disk, msc_total_sectors, msc_table_sectors, fat_file_system_type(mcs_is_fat16), volume_label, serial_number);
|
||||||
|
msc_table = fat_add_table(msc_ram_disk, msc_boot, mcs_is_fat16);
|
||||||
|
//fat_dir_entry_t * label = fat_add_label(msc_ram_disk, volume_label);
|
||||||
|
if(msc_run_partition){
|
||||||
|
fw_entry = fat_add_root_file(msc_ram_disk, 0, "FIRMWARE", "BIN", fw_size, 2, mcs_is_fat16);
|
||||||
|
fw_end_sector = FAT_SIZE_TO_SECTORS(fw_size) + fw_start_sector;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void msc_update_delete_disk(){
|
||||||
|
fw_entry = NULL;
|
||||||
|
fw_size = 0;
|
||||||
|
fw_end_sector = 0;
|
||||||
|
fw_start_sector = 0;
|
||||||
|
msc_table = NULL;
|
||||||
|
msc_boot = NULL;
|
||||||
|
msc_table_sectors = 0;
|
||||||
|
msc_total_sectors = 0;
|
||||||
|
msc_run_partition = NULL;
|
||||||
|
msc_ota_partition = NULL;
|
||||||
|
msc_update_state = MSC_UPDATE_IDLE;
|
||||||
|
msc_update_start_sector = 0;
|
||||||
|
msc_update_bytes_written = 0;
|
||||||
|
msc_update_entry = NULL;
|
||||||
|
free(msc_ram_disk);
|
||||||
|
msc_ram_disk = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//filter out entries to only include BINs in the root folder
|
||||||
|
static fat_dir_entry_t * msc_update_get_root_bin_entry(uint8_t index){
|
||||||
|
fat_dir_entry_t * entry = (fat_dir_entry_t *)(msc_ram_disk + ((msc_boot->sectors_per_alloc_table+1) * DISK_SECTOR_SIZE) + (index * sizeof(fat_dir_entry_t)));
|
||||||
|
fat_lfn_entry_t * lfn = (fat_lfn_entry_t*)entry;
|
||||||
|
|
||||||
|
//empty entry
|
||||||
|
if(entry->file_magic == 0){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//long file name
|
||||||
|
if(lfn->attr == 0x0F && lfn->type == 0x00 && lfn->first_cluster == 0x0000){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//only files marked as archives
|
||||||
|
if(entry->file_attr != FAT_FILE_ATTR_ARCHIVE){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//deleted
|
||||||
|
if(entry->file_magic == 0xE5 || entry->file_magic == 0x05){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
//not bins
|
||||||
|
if(memcmp("BIN", entry->file_extension, 3)){
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get an empty bin (the host will add an entry for file about to be written with size of zero)
|
||||||
|
static fat_dir_entry_t * msc_update_find_new_bin(){
|
||||||
|
for(uint8_t i=16; i;){
|
||||||
|
i--;
|
||||||
|
fat_dir_entry_t * entry = msc_update_get_root_bin_entry(i);
|
||||||
|
if(entry && entry->file_size == 0){
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//get a bin starting from particular sector
|
||||||
|
static fat_dir_entry_t * msc_update_find_bin(uint16_t sector){
|
||||||
|
for(uint8_t i=16; i; ){
|
||||||
|
i--;
|
||||||
|
fat_dir_entry_t * entry = msc_update_get_root_bin_entry(i);
|
||||||
|
if(entry && entry->data_start_sector == (sector - msc_boot->sectors_per_alloc_table)){
|
||||||
|
return entry;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
//write the new data and erase the flash blocks when necessary
|
||||||
|
static esp_err_t msc_update_write(const esp_partition_t *partition, uint32_t offset, void *data, size_t size){
|
||||||
|
esp_err_t err = ESP_OK;
|
||||||
|
if((offset & (SPI_FLASH_SEC_SIZE-1)) == 0){
|
||||||
|
err = esp_partition_erase_range(partition, offset, SPI_FLASH_SEC_SIZE);
|
||||||
|
log_v("ERASE[0x%08X]: %s", offset, (err != ESP_OK)?"FAIL":"OK");
|
||||||
|
if(err != ESP_OK){
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return esp_partition_write(partition, offset, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
//called when error was encountered while updating
|
||||||
|
static void msc_update_error(){
|
||||||
|
log_e("UPDATE_ERROR: %u", msc_update_bytes_written);
|
||||||
|
arduino_firmware_msc_event_data_t p;
|
||||||
|
p.error.size = msc_update_bytes_written;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_ERROR_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
msc_update_state = MSC_UPDATE_IDLE;
|
||||||
|
msc_update_entry = NULL;
|
||||||
|
msc_update_bytes_written = 0;
|
||||||
|
msc_update_start_sector = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//called when all firmware bytes have been received
|
||||||
|
static void msc_update_end(){
|
||||||
|
log_d("UPDATE_END: %u", msc_update_entry->file_size);
|
||||||
|
msc_update_state = MSC_UPDATE_END;
|
||||||
|
size_t ota_size = get_firmware_size(msc_ota_partition);
|
||||||
|
if(ota_size != msc_update_entry->file_size){
|
||||||
|
log_e("OTA SIZE MISMATCH %u != %u", ota_size, msc_update_entry->file_size);
|
||||||
|
msc_update_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(!ota_size || esp_ota_set_boot_partition(msc_ota_partition) != ESP_OK){
|
||||||
|
log_e("ENABLING OTA PARTITION FAILED");
|
||||||
|
msc_update_error();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
arduino_firmware_msc_event_data_t p;
|
||||||
|
p.end.size = msc_update_entry->file_size;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_END_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t msc_write(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize){
|
||||||
|
//log_d("lba: %u, offset: %u, bufsize: %u", lba, offset, bufsize);
|
||||||
|
if(lba < fw_start_sector){
|
||||||
|
//write to sectors that are in RAM
|
||||||
|
memcpy(msc_ram_disk + (lba * DISK_SECTOR_SIZE) + offset, buffer, bufsize);
|
||||||
|
if(msc_ota_partition && lba == (fw_start_sector - 1)){
|
||||||
|
//monitor the root folder table
|
||||||
|
if(msc_update_state <= MSC_UPDATE_RUNNING){
|
||||||
|
fat_dir_entry_t * update_entry = msc_update_find_new_bin();
|
||||||
|
if(update_entry) {
|
||||||
|
if(msc_update_entry) {
|
||||||
|
log_v("REPLACING ENTRY");
|
||||||
|
} else {
|
||||||
|
log_v("ASSIGNING ENTRY");
|
||||||
|
}
|
||||||
|
if(msc_update_state <= MSC_UPDATE_STARTING){
|
||||||
|
msc_update_state = MSC_UPDATE_STARTING;
|
||||||
|
msc_update_bytes_written = 0;
|
||||||
|
msc_update_start_sector = 0;
|
||||||
|
}
|
||||||
|
msc_update_entry = update_entry;
|
||||||
|
} else if(msc_update_state == MSC_UPDATE_RUNNING){
|
||||||
|
if(!msc_update_entry && msc_update_start_sector){
|
||||||
|
msc_update_entry = msc_update_find_bin(msc_update_start_sector);
|
||||||
|
}
|
||||||
|
if(msc_update_entry && msc_update_bytes_written >= msc_update_entry->file_size){
|
||||||
|
msc_update_end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if(msc_ota_partition && lba >= msc_update_start_sector){
|
||||||
|
//handle writes to the region where the new firmware will be uploaded
|
||||||
|
arduino_firmware_msc_event_data_t p;
|
||||||
|
if(msc_update_state <= MSC_UPDATE_STARTING && buffer[0] == 0xE9){
|
||||||
|
msc_update_state = MSC_UPDATE_RUNNING;
|
||||||
|
msc_update_start_sector = lba;
|
||||||
|
msc_update_bytes_written = 0;
|
||||||
|
log_d("UPDATE_START: %u (0x%02X)", lba, lba - msc_boot->sectors_per_alloc_table);
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_START_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
if(msc_update_write(msc_ota_partition, ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset, buffer, bufsize) == ESP_OK){
|
||||||
|
log_v("UPDATE_WRITE: %u %u", ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset, bufsize);
|
||||||
|
msc_update_bytes_written = ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset + bufsize;
|
||||||
|
p.write.offset = ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset;
|
||||||
|
p.write.size = bufsize;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_WRITE_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
} else {
|
||||||
|
msc_update_error();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if(msc_update_state == MSC_UPDATE_RUNNING){
|
||||||
|
if(msc_update_entry && msc_update_entry->file_size && msc_update_bytes_written < msc_update_entry->file_size && (msc_update_bytes_written + bufsize) >= msc_update_entry->file_size){
|
||||||
|
bufsize = msc_update_entry->file_size - msc_update_bytes_written;
|
||||||
|
}
|
||||||
|
if(msc_update_write(msc_ota_partition, ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset, buffer, bufsize) == ESP_OK){
|
||||||
|
log_v("UPDATE_WRITE: %u %u", ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset, bufsize);
|
||||||
|
msc_update_bytes_written = ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset + bufsize;
|
||||||
|
p.write.offset = ((lba - msc_update_start_sector) * DISK_SECTOR_SIZE) + offset;
|
||||||
|
p.write.size = bufsize;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_WRITE_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
if(msc_update_entry && msc_update_entry->file_size && msc_update_bytes_written >= msc_update_entry->file_size){
|
||||||
|
msc_update_end();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
msc_update_error();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bufsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t msc_read(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize){
|
||||||
|
//log_d("lba: %u, offset: %u, bufsize: %u", lba, offset, bufsize);
|
||||||
|
if(lba < fw_start_sector){
|
||||||
|
memcpy(buffer, msc_ram_disk + (lba * DISK_SECTOR_SIZE) + offset, bufsize);
|
||||||
|
} else if(msc_run_partition && lba < fw_end_sector){
|
||||||
|
//read the currently running firmware
|
||||||
|
if(esp_partition_read(msc_run_partition, ((lba - fw_start_sector) * DISK_SECTOR_SIZE) + offset, buffer, bufsize) != ESP_OK){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
memset(buffer, 0, bufsize);
|
||||||
|
}
|
||||||
|
return bufsize;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool msc_start_stop(uint8_t power_condition, bool start, bool load_eject){
|
||||||
|
//log_d("power: %u, start: %u, eject: %u", power_condition, start, load_eject);
|
||||||
|
arduino_firmware_msc_event_data_t p;
|
||||||
|
p.power.power_condition = power_condition;
|
||||||
|
p.power.start = start;
|
||||||
|
p.power.load_eject = load_eject;
|
||||||
|
arduino_usb_event_post(ARDUINO_FIRMWARE_MSC_EVENTS, ARDUINO_FIRMWARE_MSC_POWER_EVENT, &p, sizeof(arduino_firmware_msc_event_data_t), portMAX_DELAY);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static volatile TaskHandle_t msc_task_handle = NULL;
|
||||||
|
static void msc_task(void *pvParameters){
|
||||||
|
for (;;) {
|
||||||
|
if(msc_update_state == MSC_UPDATE_END){
|
||||||
|
delay(100);
|
||||||
|
esp_restart();
|
||||||
|
}
|
||||||
|
delay(100);
|
||||||
|
}
|
||||||
|
msc_task_handle = NULL;
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
FirmwareMSC::FirmwareMSC():msc(){}
|
||||||
|
|
||||||
|
FirmwareMSC::~FirmwareMSC(){
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool FirmwareMSC::begin(){
|
||||||
|
if(msc_ram_disk){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!msc_update_setup_disk(USB_FW_MSC_VOLUME_NAME, USB_FW_MSC_SERIAL_NUMBER)){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!msc_task_handle){
|
||||||
|
xTaskCreateUniversal(msc_task, "msc_disk", 1024, NULL, 2, (TaskHandle_t*)&msc_task_handle, 0);
|
||||||
|
if(!msc_task_handle){
|
||||||
|
msc_update_delete_disk();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
msc.vendorID(USB_FW_MSC_VENDOR_ID);
|
||||||
|
msc.productID(USB_FW_MSC_PRODUCT_ID);
|
||||||
|
msc.productRevision(USB_FW_MSC_PRODUCT_REVISION);
|
||||||
|
msc.onStartStop(msc_start_stop);
|
||||||
|
msc.onRead(msc_read);
|
||||||
|
msc.onWrite(msc_write);
|
||||||
|
msc.mediaPresent(true);
|
||||||
|
msc.begin(msc_boot->fat12_sector_num, DISK_SECTOR_SIZE);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FirmwareMSC::end(){
|
||||||
|
msc.end();
|
||||||
|
if(msc_task_handle){
|
||||||
|
vTaskDelete(msc_task_handle);
|
||||||
|
msc_task_handle = NULL;
|
||||||
|
}
|
||||||
|
msc_update_delete_disk();
|
||||||
|
}
|
||||||
|
|
||||||
|
void FirmwareMSC::onEvent(esp_event_handler_t callback){
|
||||||
|
onEvent(ARDUINO_FIRMWARE_MSC_ANY_EVENT, callback);
|
||||||
|
}
|
||||||
|
void FirmwareMSC::onEvent(arduino_firmware_msc_event_t event, esp_event_handler_t callback){
|
||||||
|
arduino_usb_event_handler_register_with(ARDUINO_FIRMWARE_MSC_EVENTS, event, callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINO_USB_MSC_ON_BOOT
|
||||||
|
FirmwareMSC MSC_Update;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_USB_MSC_ENABLED */
|
@ -0,0 +1,70 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "USBMSC.h"
|
||||||
|
|
||||||
|
#if CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
|
||||||
|
#include "esp_event.h"
|
||||||
|
|
||||||
|
ESP_EVENT_DECLARE_BASE(ARDUINO_FIRMWARE_MSC_EVENTS);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARDUINO_FIRMWARE_MSC_ANY_EVENT = ESP_EVENT_ANY_ID,
|
||||||
|
ARDUINO_FIRMWARE_MSC_START_EVENT = 0,
|
||||||
|
ARDUINO_FIRMWARE_MSC_WRITE_EVENT,
|
||||||
|
ARDUINO_FIRMWARE_MSC_END_EVENT,
|
||||||
|
ARDUINO_FIRMWARE_MSC_ERROR_EVENT,
|
||||||
|
ARDUINO_FIRMWARE_MSC_POWER_EVENT,
|
||||||
|
ARDUINO_FIRMWARE_MSC_MAX_EVENT,
|
||||||
|
} arduino_firmware_msc_event_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
size_t offset;
|
||||||
|
size_t size;
|
||||||
|
} write;
|
||||||
|
struct {
|
||||||
|
uint8_t power_condition;
|
||||||
|
bool start;
|
||||||
|
bool load_eject;
|
||||||
|
} power;
|
||||||
|
struct {
|
||||||
|
size_t size;
|
||||||
|
} end;
|
||||||
|
struct {
|
||||||
|
size_t size;
|
||||||
|
} error;
|
||||||
|
} arduino_firmware_msc_event_data_t;
|
||||||
|
|
||||||
|
class FirmwareMSC {
|
||||||
|
private:
|
||||||
|
USBMSC msc;
|
||||||
|
|
||||||
|
public:
|
||||||
|
FirmwareMSC();
|
||||||
|
~FirmwareMSC();
|
||||||
|
bool begin();
|
||||||
|
void end();
|
||||||
|
void onEvent(esp_event_handler_t callback);
|
||||||
|
void onEvent(arduino_firmware_msc_event_t event, esp_event_handler_t callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
#if ARDUINO_USB_MSC_ON_BOOT
|
||||||
|
extern FirmwareMSC MSC_Update;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_MSC_ENABLED */
|
@ -0,0 +1,44 @@
|
|||||||
|
/*
|
||||||
|
* FunctionalInterrupt.cpp
|
||||||
|
*
|
||||||
|
* Created on: 8 jul. 2018
|
||||||
|
* Author: Herman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "FunctionalInterrupt.h"
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
typedef void (*voidFuncPtr)(void);
|
||||||
|
typedef void (*voidFuncPtrArg)(void*);
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARDUINO_ISR_ATTR interruptFunctional(void* arg)
|
||||||
|
{
|
||||||
|
InterruptArgStructure* localArg = (InterruptArgStructure*)arg;
|
||||||
|
if (localArg->interruptFunction)
|
||||||
|
{
|
||||||
|
localArg->interruptFunction();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void attachInterrupt(uint8_t pin, std::function<void(void)> intRoutine, int mode)
|
||||||
|
{
|
||||||
|
// use the local interrupt routine which takes the ArgStructure as argument
|
||||||
|
__attachInterruptFunctionalArg (digitalPinToGPIONumber(pin), (voidFuncPtrArg)interruptFunctional, new InterruptArgStructure{intRoutine}, mode, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
void cleanupFunctional(void* arg)
|
||||||
|
{
|
||||||
|
delete (InterruptArgStructure*)arg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
|||||||
|
/*
|
||||||
|
* FunctionalInterrupt.h
|
||||||
|
*
|
||||||
|
* Created on: 8 jul. 2018
|
||||||
|
* Author: Herman
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef CORE_CORE_FUNCTIONALINTERRUPT_H_
|
||||||
|
#define CORE_CORE_FUNCTIONALINTERRUPT_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct InterruptArgStructure {
|
||||||
|
std::function<void(void)> interruptFunction;
|
||||||
|
};
|
||||||
|
|
||||||
|
// The extra set of parentheses here prevents macros defined
|
||||||
|
// in io_pin_remap.h from applying to this declaration.
|
||||||
|
void (attachInterrupt)(uint8_t pin, std::function<void(void)> intRoutine, int mode);
|
||||||
|
|
||||||
|
#endif /* CORE_CORE_FUNCTIONALINTERRUPT_H_ */
|
@ -0,0 +1,411 @@
|
|||||||
|
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#include "USB.h"
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "HWCDC.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/ringbuf.h"
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
#include "soc/periph_defs.h"
|
||||||
|
#include "hal/usb_serial_jtag_ll.h"
|
||||||
|
|
||||||
|
ESP_EVENT_DEFINE_BASE(ARDUINO_HW_CDC_EVENTS);
|
||||||
|
|
||||||
|
static RingbufHandle_t tx_ring_buf = NULL;
|
||||||
|
static xQueueHandle rx_queue = NULL;
|
||||||
|
static uint8_t rx_data_buf[64] = {0};
|
||||||
|
static intr_handle_t intr_handle = NULL;
|
||||||
|
static volatile bool initial_empty = false;
|
||||||
|
static xSemaphoreHandle tx_lock = NULL;
|
||||||
|
|
||||||
|
// workaround for when USB CDC is not connected
|
||||||
|
static uint32_t tx_timeout_ms = 0;
|
||||||
|
static bool tx_timeout_change_request = false;
|
||||||
|
|
||||||
|
static esp_event_loop_handle_t arduino_hw_cdc_event_loop_handle = NULL;
|
||||||
|
|
||||||
|
static esp_err_t arduino_hw_cdc_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, BaseType_t *task_unblocked){
|
||||||
|
if(arduino_hw_cdc_event_loop_handle == NULL){
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return esp_event_isr_post_to(arduino_hw_cdc_event_loop_handle, event_base, event_id, event_data, event_data_size, task_unblocked);
|
||||||
|
}
|
||||||
|
|
||||||
|
static esp_err_t arduino_hw_cdc_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg){
|
||||||
|
if (!arduino_hw_cdc_event_loop_handle) {
|
||||||
|
esp_event_loop_args_t event_task_args = {
|
||||||
|
.queue_size = 5,
|
||||||
|
.task_name = "arduino_hw_cdc_events",
|
||||||
|
.task_priority = 5,
|
||||||
|
.task_stack_size = 2048,
|
||||||
|
.task_core_id = tskNO_AFFINITY
|
||||||
|
};
|
||||||
|
if (esp_event_loop_create(&event_task_args, &arduino_hw_cdc_event_loop_handle) != ESP_OK) {
|
||||||
|
log_e("esp_event_loop_create failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(arduino_hw_cdc_event_loop_handle == NULL){
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return esp_event_handler_register_with(arduino_hw_cdc_event_loop_handle, event_base, event_id, event_handler, event_handler_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void hw_cdc_isr_handler(void *arg) {
|
||||||
|
portBASE_TYPE xTaskWoken = 0;
|
||||||
|
uint32_t usbjtag_intr_status = 0;
|
||||||
|
arduino_hw_cdc_event_data_t event = {0};
|
||||||
|
usbjtag_intr_status = usb_serial_jtag_ll_get_intsts_mask();
|
||||||
|
|
||||||
|
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY) {
|
||||||
|
// Interrupt tells us the host picked up the data we sent.
|
||||||
|
if (usb_serial_jtag_ll_txfifo_writable() == 1) {
|
||||||
|
// We disable the interrupt here so that the interrupt won't be triggered if there is no data to send.
|
||||||
|
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
if(!initial_empty){
|
||||||
|
initial_empty = true;
|
||||||
|
// First time USB is plugged and the application has not explicitly set TX Timeout, set it to default 100ms.
|
||||||
|
// Otherwise, USB is still unplugged and the timeout will be kept as Zero in order to avoid any delay in the
|
||||||
|
// application whenever it uses write() and the TX Queue gets full.
|
||||||
|
if (!tx_timeout_change_request) {
|
||||||
|
tx_timeout_ms = 100;
|
||||||
|
}
|
||||||
|
//send event?
|
||||||
|
//ets_printf("CONNECTED\n");
|
||||||
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_CONNECTED_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
||||||
|
}
|
||||||
|
size_t queued_size;
|
||||||
|
uint8_t *queued_buff = (uint8_t *)xRingbufferReceiveUpToFromISR(tx_ring_buf, &queued_size, 64);
|
||||||
|
// If the hardware fifo is avaliable, write in it. Otherwise, do nothing.
|
||||||
|
if (queued_buff != NULL) { //Although tx_queued_bytes may be larger than 0. We may have interrupt before xRingbufferSend() was called.
|
||||||
|
//Copy the queued buffer into the TX FIFO
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
usb_serial_jtag_ll_write_txfifo(queued_buff, queued_size);
|
||||||
|
usb_serial_jtag_ll_txfifo_flush();
|
||||||
|
vRingbufferReturnItemFromISR(tx_ring_buf, queued_buff, &xTaskWoken);
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
//send event?
|
||||||
|
//ets_printf("TX:%u\n", queued_size);
|
||||||
|
event.tx.len = queued_size;
|
||||||
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_TX_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT) {
|
||||||
|
// read rx buffer(max length is 64), and send avaliable data to ringbuffer.
|
||||||
|
// Ensure the rx buffer size is larger than RX_MAX_SIZE.
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT);
|
||||||
|
uint32_t rx_fifo_len = usb_serial_jtag_ll_read_rxfifo(rx_data_buf, 64);
|
||||||
|
uint32_t i=0;
|
||||||
|
for(i=0; i<rx_fifo_len; i++){
|
||||||
|
if(rx_queue == NULL || !xQueueSendFromISR(rx_queue, rx_data_buf+i, &xTaskWoken)){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//send event?
|
||||||
|
//ets_printf("RX:%u/%u\n", i, rx_fifo_len);
|
||||||
|
event.rx.len = i;
|
||||||
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_RX_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (usbjtag_intr_status & USB_SERIAL_JTAG_INTR_BUS_RESET) {
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_INTR_BUS_RESET);
|
||||||
|
initial_empty = false;
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
//ets_printf("BUS_RESET\n");
|
||||||
|
arduino_hw_cdc_event_post(ARDUINO_HW_CDC_EVENTS, ARDUINO_HW_CDC_BUS_RESET_EVENT, &event, sizeof(arduino_hw_cdc_event_data_t), &xTaskWoken);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (xTaskWoken == pdTRUE) {
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ARDUINO_ISR_ATTR cdc0_write_char(char c) {
|
||||||
|
if(xPortInIsrContext()){
|
||||||
|
xRingbufferSendFromISR(tx_ring_buf, (void*) (&c), 1, NULL);
|
||||||
|
} else {
|
||||||
|
xRingbufferSend(tx_ring_buf, (void*) (&c), 1, tx_timeout_ms / portTICK_PERIOD_MS);
|
||||||
|
}
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
HWCDC::HWCDC() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
HWCDC::~HWCDC(){
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
HWCDC::operator bool() const
|
||||||
|
{
|
||||||
|
return initial_empty;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::onEvent(esp_event_handler_t callback){
|
||||||
|
onEvent(ARDUINO_HW_CDC_ANY_EVENT, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::onEvent(arduino_hw_cdc_event_t event, esp_event_handler_t callback){
|
||||||
|
arduino_hw_cdc_event_handler_register_with(ARDUINO_HW_CDC_EVENTS, event, callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::begin(unsigned long baud)
|
||||||
|
{
|
||||||
|
if(tx_lock == NULL) {
|
||||||
|
tx_lock = xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
|
//RX Buffer default has 256 bytes if not preset
|
||||||
|
if(rx_queue == NULL) {
|
||||||
|
if (!setRxBufferSize(256)) {
|
||||||
|
log_e("HW CDC RX Buffer error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//TX Buffer default has 256 bytes if not preset
|
||||||
|
if (tx_ring_buf == NULL) {
|
||||||
|
if (!setTxBufferSize(256)) {
|
||||||
|
log_e("HW CDC TX Buffer error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
|
||||||
|
usb_serial_jtag_ll_clr_intsts_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY | USB_SERIAL_JTAG_INTR_SERIAL_OUT_RECV_PKT | USB_SERIAL_JTAG_INTR_BUS_RESET);
|
||||||
|
if(!intr_handle && esp_intr_alloc(ETS_USB_SERIAL_JTAG_INTR_SOURCE, 0, hw_cdc_isr_handler, NULL, &intr_handle) != ESP_OK){
|
||||||
|
isr_log_e("HW USB CDC failed to init interrupts");
|
||||||
|
end();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
usb_serial_jtag_ll_txfifo_flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::end()
|
||||||
|
{
|
||||||
|
//Disable tx/rx interrupt.
|
||||||
|
usb_serial_jtag_ll_disable_intr_mask(USB_SERIAL_JTAG_LL_INTR_MASK);
|
||||||
|
esp_intr_free(intr_handle);
|
||||||
|
intr_handle = NULL;
|
||||||
|
if(tx_lock != NULL) {
|
||||||
|
vSemaphoreDelete(tx_lock);
|
||||||
|
tx_lock = NULL;
|
||||||
|
}
|
||||||
|
setRxBufferSize(0);
|
||||||
|
setTxBufferSize(0);
|
||||||
|
if (arduino_hw_cdc_event_loop_handle) {
|
||||||
|
esp_event_loop_delete(arduino_hw_cdc_event_loop_handle);
|
||||||
|
arduino_hw_cdc_event_loop_handle = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::setTxTimeoutMs(uint32_t timeout){
|
||||||
|
tx_timeout_ms = timeout;
|
||||||
|
// it registers that the user has explicitly requested to use a value as TX timeout
|
||||||
|
// used for the workaround with unplugged USB and TX Queue Full that causes a delay on every write()
|
||||||
|
tx_timeout_change_request = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* WRITING
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t HWCDC::setTxBufferSize(size_t tx_queue_len){
|
||||||
|
if(tx_ring_buf){
|
||||||
|
vRingbufferDelete(tx_ring_buf);
|
||||||
|
tx_ring_buf = NULL;
|
||||||
|
}
|
||||||
|
if(!tx_queue_len){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
tx_ring_buf = xRingbufferCreate(tx_queue_len, RINGBUF_TYPE_BYTEBUF);
|
||||||
|
if(!tx_ring_buf){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return tx_queue_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HWCDC::availableForWrite(void)
|
||||||
|
{
|
||||||
|
if(tx_ring_buf == NULL || tx_lock == NULL){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t a = xRingbufferGetCurFreeSize(tx_ring_buf);
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HWCDC::write(const uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
if(buffer == NULL || size == 0 || tx_ring_buf == NULL || tx_lock == NULL){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t max_size = xRingbufferGetMaxItemSize(tx_ring_buf);
|
||||||
|
size_t space = xRingbufferGetCurFreeSize(tx_ring_buf);
|
||||||
|
size_t to_send = size, so_far = 0;
|
||||||
|
|
||||||
|
if(space > size){
|
||||||
|
space = size;
|
||||||
|
}
|
||||||
|
// Non-Blocking method, Sending data to ringbuffer, and handle the data in ISR.
|
||||||
|
if(xRingbufferSend(tx_ring_buf, (void*) (buffer), space, 0) != pdTRUE){
|
||||||
|
size = 0;
|
||||||
|
} else {
|
||||||
|
to_send -= space;
|
||||||
|
so_far += space;
|
||||||
|
// Now trigger the ISR to read data from the ring buffer.
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
|
||||||
|
while(to_send){
|
||||||
|
if(max_size > to_send){
|
||||||
|
max_size = to_send;
|
||||||
|
}
|
||||||
|
// Blocking method, Sending data to ringbuffer, and handle the data in ISR.
|
||||||
|
if(xRingbufferSend(tx_ring_buf, (void*) (buffer+so_far), max_size, tx_timeout_ms / portTICK_PERIOD_MS) != pdTRUE){
|
||||||
|
size = so_far;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
so_far += max_size;
|
||||||
|
to_send -= max_size;
|
||||||
|
// Now trigger the ISR to read data from the ring buffer.
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HWCDC::write(uint8_t c)
|
||||||
|
{
|
||||||
|
return write(&c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HWCDC::flush(void)
|
||||||
|
{
|
||||||
|
if(tx_ring_buf == NULL || tx_lock == NULL){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
UBaseType_t uxItemsWaiting = 0;
|
||||||
|
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
|
||||||
|
if(uxItemsWaiting){
|
||||||
|
// Now trigger the ISR to read data from the ring buffer.
|
||||||
|
usb_serial_jtag_ll_ena_intr_mask(USB_SERIAL_JTAG_INTR_SERIAL_IN_EMPTY);
|
||||||
|
}
|
||||||
|
while(uxItemsWaiting){
|
||||||
|
delay(5);
|
||||||
|
vRingbufferGetInfo(tx_ring_buf, NULL, NULL, NULL, NULL, &uxItemsWaiting);
|
||||||
|
}
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* READING
|
||||||
|
*/
|
||||||
|
|
||||||
|
size_t HWCDC::setRxBufferSize(size_t rx_queue_len){
|
||||||
|
if(rx_queue){
|
||||||
|
vQueueDelete(rx_queue);
|
||||||
|
rx_queue = NULL;
|
||||||
|
}
|
||||||
|
if(!rx_queue_len){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
rx_queue = xQueueCreate(rx_queue_len, sizeof(uint8_t));
|
||||||
|
if(!rx_queue){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return rx_queue_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HWCDC::available(void)
|
||||||
|
{
|
||||||
|
if(rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return uxQueueMessagesWaiting(rx_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HWCDC::peek(void)
|
||||||
|
{
|
||||||
|
if(rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c;
|
||||||
|
if(xQueuePeek(rx_queue, &c, 0)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HWCDC::read(void)
|
||||||
|
{
|
||||||
|
if(rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c = 0;
|
||||||
|
if(xQueueReceive(rx_queue, &c, 0)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HWCDC::read(uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
if(rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c = 0;
|
||||||
|
size_t count = 0;
|
||||||
|
while(count < size && xQueueReceive(rx_queue, &c, 0)){
|
||||||
|
buffer[count++] = c;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* DEBUG
|
||||||
|
*/
|
||||||
|
|
||||||
|
void HWCDC::setDebugOutput(bool en)
|
||||||
|
{
|
||||||
|
if(en) {
|
||||||
|
uartSetDebug(NULL);
|
||||||
|
ets_install_putc1((void (*)(char)) &cdc0_write_char);
|
||||||
|
} else {
|
||||||
|
ets_install_putc1(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINO_USB_MODE
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT//Serial used for USB CDC
|
||||||
|
HWCDC Serial;
|
||||||
|
#else
|
||||||
|
HWCDC USBSerial;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_CDC_ENABLED */
|
@ -0,0 +1,109 @@
|
|||||||
|
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "Stream.h"
|
||||||
|
|
||||||
|
ESP_EVENT_DECLARE_BASE(ARDUINO_HW_CDC_EVENTS);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARDUINO_HW_CDC_ANY_EVENT = ESP_EVENT_ANY_ID,
|
||||||
|
ARDUINO_HW_CDC_CONNECTED_EVENT = 0,
|
||||||
|
ARDUINO_HW_CDC_BUS_RESET_EVENT,
|
||||||
|
ARDUINO_HW_CDC_RX_EVENT,
|
||||||
|
ARDUINO_HW_CDC_TX_EVENT,
|
||||||
|
ARDUINO_HW_CDC_MAX_EVENT,
|
||||||
|
} arduino_hw_cdc_event_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
size_t len;
|
||||||
|
} rx;
|
||||||
|
struct {
|
||||||
|
size_t len;
|
||||||
|
} tx;
|
||||||
|
} arduino_hw_cdc_event_data_t;
|
||||||
|
|
||||||
|
class HWCDC: public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HWCDC();
|
||||||
|
~HWCDC();
|
||||||
|
|
||||||
|
void onEvent(esp_event_handler_t callback);
|
||||||
|
void onEvent(arduino_hw_cdc_event_t event, esp_event_handler_t callback);
|
||||||
|
|
||||||
|
size_t setRxBufferSize(size_t);
|
||||||
|
size_t setTxBufferSize(size_t);
|
||||||
|
void setTxTimeoutMs(uint32_t timeout);
|
||||||
|
void begin(unsigned long baud=0);
|
||||||
|
void end();
|
||||||
|
|
||||||
|
int available(void);
|
||||||
|
int availableForWrite(void);
|
||||||
|
int peek(void);
|
||||||
|
int read(void);
|
||||||
|
size_t read(uint8_t *buffer, size_t size);
|
||||||
|
size_t write(uint8_t);
|
||||||
|
size_t write(const uint8_t *buffer, size_t size);
|
||||||
|
void flush(void);
|
||||||
|
|
||||||
|
inline size_t read(char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return read((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
inline size_t write(const char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
inline size_t write(const char * s)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) s, strlen(s));
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
operator bool() const;
|
||||||
|
void setDebugOutput(bool);
|
||||||
|
uint32_t baudRate(){return 115200;}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#if ARDUINO_USB_MODE
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT//Serial used for USB CDC
|
||||||
|
extern HWCDC Serial;
|
||||||
|
#else
|
||||||
|
extern HWCDC USBSerial;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_IDF_TARGET_ESP32C3 */
|
@ -0,0 +1,623 @@
|
|||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
#include "io_pin_remap.h"
|
||||||
|
#include "HardwareSerial.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "driver/uart.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
|
||||||
|
#ifndef ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE
|
||||||
|
#define ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE 2048
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINO_SERIAL_EVENT_TASK_PRIORITY
|
||||||
|
#define ARDUINO_SERIAL_EVENT_TASK_PRIORITY (configMAX_PRIORITIES-1)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE
|
||||||
|
#define ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE -1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SOC_RX0
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define SOC_RX0 3
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define SOC_RX0 44
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#define SOC_RX0 20
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef SOC_TX0
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define SOC_TX0 1
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define SOC_TX0 43
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#define SOC_TX0 21
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void serialEvent(void) __attribute__((weak));
|
||||||
|
void serialEvent(void) {}
|
||||||
|
|
||||||
|
#if SOC_UART_NUM > 1
|
||||||
|
|
||||||
|
#ifndef RX1
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define RX1 9
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#define RX1 18
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#define RX1 18
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define RX1 15
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TX1
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define TX1 10
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#define TX1 17
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#define TX1 19
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define TX1 16
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void serialEvent1(void) __attribute__((weak));
|
||||||
|
void serialEvent1(void) {}
|
||||||
|
#endif /* SOC_UART_NUM > 1 */
|
||||||
|
|
||||||
|
#if SOC_UART_NUM > 2
|
||||||
|
#ifndef RX2
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define RX2 16
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define RX2 19
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef TX2
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define TX2 17
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define TX2 20
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void serialEvent2(void) __attribute__((weak));
|
||||||
|
void serialEvent2(void) {}
|
||||||
|
#endif /* SOC_UART_NUM > 2 */
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT //Serial used for USB CDC
|
||||||
|
HardwareSerial Serial0(0);
|
||||||
|
#else
|
||||||
|
HardwareSerial Serial(0);
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 1
|
||||||
|
HardwareSerial Serial1(1);
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 2
|
||||||
|
HardwareSerial Serial2(2);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void serialEventRun(void)
|
||||||
|
{
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT //Serial used for USB CDC
|
||||||
|
if(Serial0.available()) serialEvent();
|
||||||
|
#else
|
||||||
|
if(Serial.available()) serialEvent();
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 1
|
||||||
|
if(Serial1.available()) serialEvent1();
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 2
|
||||||
|
if(Serial2.available()) serialEvent2();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
#define HSERIAL_MUTEX_LOCK() do {} while (xSemaphoreTake(_lock, portMAX_DELAY) != pdPASS)
|
||||||
|
#define HSERIAL_MUTEX_UNLOCK() xSemaphoreGive(_lock)
|
||||||
|
#else
|
||||||
|
#define HSERIAL_MUTEX_LOCK()
|
||||||
|
#define HSERIAL_MUTEX_UNLOCK()
|
||||||
|
#endif
|
||||||
|
|
||||||
|
HardwareSerial::HardwareSerial(int uart_nr) :
|
||||||
|
_uart_nr(uart_nr),
|
||||||
|
_uart(NULL),
|
||||||
|
_rxBufferSize(256),
|
||||||
|
_txBufferSize(0),
|
||||||
|
_onReceiveCB(NULL),
|
||||||
|
_onReceiveErrorCB(NULL),
|
||||||
|
_onReceiveTimeout(false),
|
||||||
|
_rxTimeout(2),
|
||||||
|
_rxFIFOFull(0),
|
||||||
|
_eventTask(NULL)
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
,_lock(NULL)
|
||||||
|
#endif
|
||||||
|
,_rxPin(-1)
|
||||||
|
,_txPin(-1)
|
||||||
|
,_ctsPin(-1)
|
||||||
|
,_rtsPin(-1)
|
||||||
|
{
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(_lock == NULL){
|
||||||
|
_lock = xSemaphoreCreateMutex();
|
||||||
|
if(_lock == NULL){
|
||||||
|
log_e("xSemaphoreCreateMutex failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
HardwareSerial::~HardwareSerial()
|
||||||
|
{
|
||||||
|
end();
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(_lock != NULL){
|
||||||
|
vSemaphoreDelete(_lock);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void HardwareSerial::_createEventTask(void *args)
|
||||||
|
{
|
||||||
|
// Creating UART event Task
|
||||||
|
xTaskCreateUniversal(_uartEventTask, "uart_event_task", ARDUINO_SERIAL_EVENT_TASK_STACK_SIZE, this, ARDUINO_SERIAL_EVENT_TASK_PRIORITY, &_eventTask, ARDUINO_SERIAL_EVENT_TASK_RUNNING_CORE);
|
||||||
|
if (_eventTask == NULL) {
|
||||||
|
log_e(" -- UART%d Event Task not Created!", _uart_nr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::_destroyEventTask(void)
|
||||||
|
{
|
||||||
|
if (_eventTask != NULL) {
|
||||||
|
vTaskDelete(_eventTask);
|
||||||
|
_eventTask = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::onReceiveError(OnReceiveErrorCb function)
|
||||||
|
{
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
// function may be NULL to cancel onReceive() from its respective task
|
||||||
|
_onReceiveErrorCB = function;
|
||||||
|
// this can be called after Serial.begin(), therefore it shall create the event task
|
||||||
|
if (function != NULL && _uart != NULL && _eventTask == NULL) {
|
||||||
|
_createEventTask(this);
|
||||||
|
}
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::onReceive(OnReceiveCb function, bool onlyOnTimeout)
|
||||||
|
{
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
// function may be NULL to cancel onReceive() from its respective task
|
||||||
|
_onReceiveCB = function;
|
||||||
|
|
||||||
|
// setting the callback to NULL will just disable it
|
||||||
|
if (_onReceiveCB != NULL) {
|
||||||
|
// When Rx timeout is Zero (disabled), there is only one possible option that is callback when FIFO reaches 120 bytes
|
||||||
|
_onReceiveTimeout = _rxTimeout > 0 ? onlyOnTimeout : false;
|
||||||
|
|
||||||
|
// in case that onReceive() shall work only with RX Timeout, FIFO shall be high
|
||||||
|
// this is a work around for an IDF issue with events and low FIFO Full value (< 3)
|
||||||
|
if (_onReceiveTimeout) {
|
||||||
|
uartSetRxFIFOFull(_uart, 120);
|
||||||
|
log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// this method can be called after Serial.begin(), therefore it shall create the event task
|
||||||
|
if (_uart != NULL && _eventTask == NULL) {
|
||||||
|
_createEventTask(this); // Create event task
|
||||||
|
}
|
||||||
|
}
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
// This function allow the user to define how many bytes will trigger an Interrupt that will copy RX FIFO to the internal RX Ringbuffer
|
||||||
|
// ISR will also move data from FIFO to RX Ringbuffer after a RX Timeout defined in HardwareSerial::setRxTimeout(uint8_t symbols_timeout)
|
||||||
|
// A low value of FIFO Full bytes will consume more CPU time within the ISR
|
||||||
|
// A high value of FIFO Full bytes will make the application wait longer to have byte available for the Stkech in a streaming scenario
|
||||||
|
// Both RX FIFO Full and RX Timeout may affect when onReceive() will be called
|
||||||
|
bool HardwareSerial::setRxFIFOFull(uint8_t fifoBytes)
|
||||||
|
{
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
// in case that onReceive() shall work only with RX Timeout, FIFO shall be high
|
||||||
|
// this is a work around for an IDF issue with events and low FIFO Full value (< 3)
|
||||||
|
if (_onReceiveCB != NULL && _onReceiveTimeout) {
|
||||||
|
fifoBytes = 120;
|
||||||
|
log_w("OnReceive is set to Timeout only, thus FIFO Full is now 120 bytes.");
|
||||||
|
}
|
||||||
|
bool retCode = uartSetRxFIFOFull(_uart, fifoBytes); // Set new timeout
|
||||||
|
if (fifoBytes > 0 && fifoBytes < SOC_UART_FIFO_LEN - 1) _rxFIFOFull = fifoBytes;
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
return retCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// timout is calculates in time to receive UART symbols at the UART baudrate.
|
||||||
|
// the estimation is about 11 bits per symbol (SERIAL_8N1)
|
||||||
|
bool HardwareSerial::setRxTimeout(uint8_t symbols_timeout)
|
||||||
|
{
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
|
||||||
|
// Zero disables timeout, thus, onReceive callback will only be called when RX FIFO reaches 120 bytes
|
||||||
|
// Any non-zero value will activate onReceive callback based on UART baudrate with about 11 bits per symbol
|
||||||
|
_rxTimeout = symbols_timeout;
|
||||||
|
if (!symbols_timeout) _onReceiveTimeout = false; // only when RX timeout is disabled, we also must disable this flag
|
||||||
|
|
||||||
|
bool retCode = uartSetRxTimeout(_uart, _rxTimeout); // Set new timeout
|
||||||
|
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
return retCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::eventQueueReset()
|
||||||
|
{
|
||||||
|
QueueHandle_t uartEventQueue = NULL;
|
||||||
|
if (_uart == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
uartGetEventQueue(_uart, &uartEventQueue);
|
||||||
|
if (uartEventQueue != NULL) {
|
||||||
|
xQueueReset(uartEventQueue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::_uartEventTask(void *args)
|
||||||
|
{
|
||||||
|
HardwareSerial *uart = (HardwareSerial *)args;
|
||||||
|
uart_event_t event;
|
||||||
|
QueueHandle_t uartEventQueue = NULL;
|
||||||
|
uartGetEventQueue(uart->_uart, &uartEventQueue);
|
||||||
|
if (uartEventQueue != NULL) {
|
||||||
|
for(;;) {
|
||||||
|
//Waiting for UART event.
|
||||||
|
if(xQueueReceive(uartEventQueue, (void * )&event, (portTickType)portMAX_DELAY)) {
|
||||||
|
hardwareSerial_error_t currentErr = UART_NO_ERROR;
|
||||||
|
switch(event.type) {
|
||||||
|
case UART_DATA:
|
||||||
|
if(uart->_onReceiveCB && uart->available() > 0 &&
|
||||||
|
((uart->_onReceiveTimeout && event.timeout_flag) || !uart->_onReceiveTimeout) )
|
||||||
|
uart->_onReceiveCB();
|
||||||
|
break;
|
||||||
|
case UART_FIFO_OVF:
|
||||||
|
log_w("UART%d FIFO Overflow. Consider adding Hardware Flow Control to your Application.", uart->_uart_nr);
|
||||||
|
currentErr = UART_FIFO_OVF_ERROR;
|
||||||
|
break;
|
||||||
|
case UART_BUFFER_FULL:
|
||||||
|
log_w("UART%d Buffer Full. Consider increasing your buffer size of your Application.", uart->_uart_nr);
|
||||||
|
currentErr = UART_BUFFER_FULL_ERROR;
|
||||||
|
break;
|
||||||
|
case UART_BREAK:
|
||||||
|
log_w("UART%d RX break.", uart->_uart_nr);
|
||||||
|
currentErr = UART_BREAK_ERROR;
|
||||||
|
break;
|
||||||
|
case UART_PARITY_ERR:
|
||||||
|
log_w("UART%d parity error.", uart->_uart_nr);
|
||||||
|
currentErr = UART_PARITY_ERROR;
|
||||||
|
break;
|
||||||
|
case UART_FRAME_ERR:
|
||||||
|
log_w("UART%d frame error.", uart->_uart_nr);
|
||||||
|
currentErr = UART_FRAME_ERROR;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
log_w("UART%d unknown event type %d.", uart->_uart_nr, event.type);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (currentErr != UART_NO_ERROR) {
|
||||||
|
if(uart->_onReceiveErrorCB) uart->_onReceiveErrorCB(currentErr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert, unsigned long timeout_ms, uint8_t rxfifo_full_thrhd)
|
||||||
|
{
|
||||||
|
if(0 > _uart_nr || _uart_nr >= SOC_UART_NUM) {
|
||||||
|
log_e("Serial number is invalid, please use numers from 0 to %u", SOC_UART_NUM - 1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(_lock == NULL){
|
||||||
|
log_e("MUTEX Lock failed. Can't begin.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
HSERIAL_MUTEX_LOCK();
|
||||||
|
// First Time or after end() --> set default Pins
|
||||||
|
if (!uartIsDriverInstalled(_uart)) {
|
||||||
|
switch (_uart_nr) {
|
||||||
|
case UART_NUM_0:
|
||||||
|
if (rxPin < 0 && txPin < 0) {
|
||||||
|
rxPin = SOC_RX0;
|
||||||
|
txPin = SOC_TX0;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#if SOC_UART_NUM > 1 // may save some flash bytes...
|
||||||
|
case UART_NUM_1:
|
||||||
|
if (rxPin < 0 && txPin < 0) {
|
||||||
|
rxPin = RX1;
|
||||||
|
txPin = TX1;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 2 // may save some flash bytes...
|
||||||
|
case UART_NUM_2:
|
||||||
|
if (rxPin < 0 && txPin < 0) {
|
||||||
|
rxPin = RX2;
|
||||||
|
txPin = TX2;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// map logical pins to GPIO numbers
|
||||||
|
rxPin = digitalPinToGPIONumber(rxPin);
|
||||||
|
txPin = digitalPinToGPIONumber(txPin);
|
||||||
|
|
||||||
|
if(_uart) {
|
||||||
|
// in this case it is a begin() over a previous begin() - maybe to change baud rate
|
||||||
|
// thus do not disable debug output
|
||||||
|
end(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// IDF UART driver keeps Pin setting on restarting. Negative Pin number will keep it unmodified.
|
||||||
|
_uart = uartBegin(_uart_nr, baud ? baud : 9600, config, rxPin, txPin, _rxBufferSize, _txBufferSize, invert, rxfifo_full_thrhd);
|
||||||
|
if (!baud) {
|
||||||
|
// using baud rate as zero, forces it to try to detect the current baud rate in place
|
||||||
|
uartStartDetectBaudrate(_uart);
|
||||||
|
time_t startMillis = millis();
|
||||||
|
unsigned long detectedBaudRate = 0;
|
||||||
|
while(millis() - startMillis < timeout_ms && !(detectedBaudRate = uartDetectBaudrate(_uart))) {
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
|
||||||
|
end(false);
|
||||||
|
|
||||||
|
if(detectedBaudRate) {
|
||||||
|
delay(100); // Give some time...
|
||||||
|
_uart = uartBegin(_uart_nr, detectedBaudRate, config, rxPin, txPin, _rxBufferSize, _txBufferSize, invert, rxfifo_full_thrhd);
|
||||||
|
} else {
|
||||||
|
log_e("Could not detect baudrate. Serial data at the port must be present within the timeout for detection to be possible");
|
||||||
|
_uart = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// create a task to deal with Serial Events when, for example, calling begin() twice to change the baudrate,
|
||||||
|
// or when setting the callback before calling begin()
|
||||||
|
if (_uart != NULL && (_onReceiveCB != NULL || _onReceiveErrorCB != NULL) && _eventTask == NULL) {
|
||||||
|
_createEventTask(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set UART RX timeout
|
||||||
|
uartSetRxTimeout(_uart, _rxTimeout);
|
||||||
|
|
||||||
|
// Set UART FIFO Full depending on the baud rate.
|
||||||
|
// Lower baud rates will force to emulate byte-by-byte reading
|
||||||
|
// Higher baud rates will keep IDF default of 120 bytes for FIFO FULL Interrupt
|
||||||
|
// It can also be changed by the application at any time
|
||||||
|
if (!_rxFIFOFull) { // it has not being changed before calling begin()
|
||||||
|
// set a default FIFO Full value for the IDF driver
|
||||||
|
uint8_t fifoFull = 1;
|
||||||
|
if (baud > 57600 || (_onReceiveCB != NULL && _onReceiveTimeout)) {
|
||||||
|
fifoFull = 120;
|
||||||
|
}
|
||||||
|
uartSetRxFIFOFull(_uart, fifoFull);
|
||||||
|
_rxFIFOFull = fifoFull;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rxPin = rxPin;
|
||||||
|
_txPin = txPin;
|
||||||
|
|
||||||
|
HSERIAL_MUTEX_UNLOCK();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::updateBaudRate(unsigned long baud)
|
||||||
|
{
|
||||||
|
uartSetBaudRate(_uart, baud);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::end(bool fullyTerminate)
|
||||||
|
{
|
||||||
|
// default Serial.end() will completely disable HardwareSerial,
|
||||||
|
// including any tasks or debug message channel (log_x()) - but not for IDF log messages!
|
||||||
|
if(fullyTerminate) {
|
||||||
|
_onReceiveCB = NULL;
|
||||||
|
_onReceiveErrorCB = NULL;
|
||||||
|
if (uartGetDebug() == _uart_nr) {
|
||||||
|
uartSetDebug(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
_rxFIFOFull = 0;
|
||||||
|
|
||||||
|
uartDetachPins(_uart, _rxPin, _txPin, _ctsPin, _rtsPin);
|
||||||
|
_rxPin = _txPin = _ctsPin = _rtsPin = -1;
|
||||||
|
|
||||||
|
}
|
||||||
|
delay(10);
|
||||||
|
uartEnd(_uart);
|
||||||
|
_uart = 0;
|
||||||
|
_destroyEventTask();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::setDebugOutput(bool en)
|
||||||
|
{
|
||||||
|
if(_uart == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(en) {
|
||||||
|
uartSetDebug(_uart);
|
||||||
|
} else {
|
||||||
|
if(uartGetDebug() == _uart_nr) {
|
||||||
|
uartSetDebug(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int HardwareSerial::available(void)
|
||||||
|
{
|
||||||
|
return uartAvailable(_uart);
|
||||||
|
}
|
||||||
|
int HardwareSerial::availableForWrite(void)
|
||||||
|
{
|
||||||
|
return uartAvailableForWrite(_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
int HardwareSerial::peek(void)
|
||||||
|
{
|
||||||
|
if (available()) {
|
||||||
|
return uartPeek(_uart);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int HardwareSerial::read(void)
|
||||||
|
{
|
||||||
|
uint8_t c = 0;
|
||||||
|
if (uartReadBytes(_uart, &c, 1, 0) == 1) {
|
||||||
|
return c;
|
||||||
|
} else {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read characters into buffer
|
||||||
|
// terminates if size characters have been read, or no further are pending
|
||||||
|
// returns the number of characters placed in the buffer
|
||||||
|
// the buffer is NOT null terminated.
|
||||||
|
size_t HardwareSerial::read(uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
return uartReadBytes(_uart, buffer, size, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overrides Stream::readBytes() to be faster using IDF
|
||||||
|
size_t HardwareSerial::readBytes(uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
return uartReadBytes(_uart, buffer, length, (uint32_t)getTimeout());
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::flush(void)
|
||||||
|
{
|
||||||
|
uartFlush(_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::flush(bool txOnly)
|
||||||
|
{
|
||||||
|
uartFlushTxOnly(_uart, txOnly);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HardwareSerial::write(uint8_t c)
|
||||||
|
{
|
||||||
|
uartWrite(_uart, c);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HardwareSerial::write(const uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
uartWriteBuf(_uart, buffer, size);
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
uint32_t HardwareSerial::baudRate()
|
||||||
|
|
||||||
|
{
|
||||||
|
return uartGetBaudRate(_uart);
|
||||||
|
}
|
||||||
|
HardwareSerial::operator bool() const
|
||||||
|
{
|
||||||
|
return uartIsDriverInstalled(_uart);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HardwareSerial::setRxInvert(bool invert)
|
||||||
|
{
|
||||||
|
uartSetRxInvert(_uart, invert);
|
||||||
|
}
|
||||||
|
|
||||||
|
// negative Pin value will keep it unmodified
|
||||||
|
bool HardwareSerial::setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin, int8_t rtsPin)
|
||||||
|
{
|
||||||
|
if(_uart == NULL) {
|
||||||
|
log_e("setPins() shall be called after begin() - nothing done\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// map logical pins to GPIO numbers
|
||||||
|
rxPin = digitalPinToGPIONumber(rxPin);
|
||||||
|
txPin = digitalPinToGPIONumber(txPin);
|
||||||
|
ctsPin = digitalPinToGPIONumber(ctsPin);
|
||||||
|
rtsPin = digitalPinToGPIONumber(rtsPin);
|
||||||
|
|
||||||
|
// uartSetPins() checks if pins are valid for each function and for the SoC
|
||||||
|
bool retCode = uartSetPins(_uart, rxPin, txPin, ctsPin, rtsPin);
|
||||||
|
if (retCode) {
|
||||||
|
_txPin = _txPin >= 0 ? txPin : _txPin;
|
||||||
|
_rxPin = _rxPin >= 0 ? rxPin : _rxPin;
|
||||||
|
_rtsPin = _rtsPin >= 0 ? rtsPin : _rtsPin;
|
||||||
|
_ctsPin = _ctsPin >= 0 ? ctsPin : _ctsPin;
|
||||||
|
} else {
|
||||||
|
log_e("Error when setting Serial port Pins. Invalid Pin.\n");
|
||||||
|
}
|
||||||
|
return retCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enables or disables Hardware Flow Control using RTS and/or CTS pins (must use setAllPins() before)
|
||||||
|
bool HardwareSerial::setHwFlowCtrlMode(uint8_t mode, uint8_t threshold)
|
||||||
|
{
|
||||||
|
return uartSetHwFlowCtrlMode(_uart, mode, threshold);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sets the uart mode in the esp32 uart for use with RS485 modes (HwFlowCtrl must be disabled and RTS pin set)
|
||||||
|
bool HardwareSerial::setMode(uint8_t mode)
|
||||||
|
{
|
||||||
|
return uartSetMode(_uart, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HardwareSerial::setRxBufferSize(size_t new_size) {
|
||||||
|
|
||||||
|
if (_uart) {
|
||||||
|
log_e("RX Buffer can't be resized when Serial is already running.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_size <= SOC_UART_FIFO_LEN) {
|
||||||
|
log_e("RX Buffer must be higher than %d.\n", SOC_UART_FIFO_LEN); // ESP32, S2, S3 and C3 means higher than 128
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_rxBufferSize = new_size;
|
||||||
|
return _rxBufferSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t HardwareSerial::setTxBufferSize(size_t new_size) {
|
||||||
|
|
||||||
|
if (_uart) {
|
||||||
|
log_e("TX Buffer can't be resized when Serial is already running.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_size <= SOC_UART_FIFO_LEN) {
|
||||||
|
log_e("TX Buffer must be higher than %d.\n", SOC_UART_FIFO_LEN); // ESP32, S2, S3 and C3 means higher than 128
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
_txBufferSize = new_size;
|
||||||
|
return _txBufferSize;
|
||||||
|
}
|
@ -0,0 +1,216 @@
|
|||||||
|
/*
|
||||||
|
HardwareSerial.h - Hardware serial library for Wiring
|
||||||
|
Copyright (c) 2006 Nicholas Zambetti. All right 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
|
||||||
|
|
||||||
|
Modified 28 September 2010 by Mark Sproul
|
||||||
|
Modified 14 August 2012 by Alarus
|
||||||
|
Modified 3 December 2013 by Matthijs Kooijman
|
||||||
|
Modified 18 December 2014 by Ivan Grokhotkov (esp8266 platform support)
|
||||||
|
Modified 31 March 2015 by Markus Sattler (rewrite the code for UART0 + UART1 support in ESP8266)
|
||||||
|
Modified 25 April 2015 by Thomas Flayols (add configuration different from 8N1 in ESP8266)
|
||||||
|
Modified 13 October 2018 by Jeroen Döll (add baudrate detection)
|
||||||
|
Baudrate detection example usage (detection on Serial1):
|
||||||
|
void setup() {
|
||||||
|
Serial.begin(115200);
|
||||||
|
delay(100);
|
||||||
|
Serial.println();
|
||||||
|
|
||||||
|
Serial1.begin(0, SERIAL_8N1, -1, -1, true, 11000UL); // Passing 0 for baudrate to detect it, the last parameter is a timeout in ms
|
||||||
|
|
||||||
|
unsigned long detectedBaudRate = Serial1.baudRate();
|
||||||
|
if(detectedBaudRate) {
|
||||||
|
Serial.printf("Detected baudrate is %lu\n", detectedBaudRate);
|
||||||
|
} else {
|
||||||
|
Serial.println("No baudrate detected, Serial1 will not work!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pay attention: the baudrate returned by baudRate() may be rounded, eg 115200 returns 115201
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef HardwareSerial_h
|
||||||
|
#define HardwareSerial_h
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <functional>
|
||||||
|
#include "Stream.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "HWCDC.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
UART_NO_ERROR,
|
||||||
|
UART_BREAK_ERROR,
|
||||||
|
UART_BUFFER_FULL_ERROR,
|
||||||
|
UART_FIFO_OVF_ERROR,
|
||||||
|
UART_FRAME_ERROR,
|
||||||
|
UART_PARITY_ERROR
|
||||||
|
} hardwareSerial_error_t;
|
||||||
|
|
||||||
|
typedef std::function<void(void)> OnReceiveCb;
|
||||||
|
typedef std::function<void(hardwareSerial_error_t)> OnReceiveErrorCb;
|
||||||
|
|
||||||
|
class HardwareSerial: public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
HardwareSerial(int uart_nr);
|
||||||
|
~HardwareSerial();
|
||||||
|
|
||||||
|
// setRxTimeout sets the timeout after which onReceive callback will be called (after receiving data, it waits for this time of UART rx inactivity to call the callback fnc)
|
||||||
|
// param symbols_timeout defines a timeout threshold in uart symbol periods. Setting 0 symbol timeout disables the callback call by timeout.
|
||||||
|
// Maximum timeout setting is calculacted automatically by IDF. If set above the maximum, it is ignored and an error is printed on Serial0 (check console).
|
||||||
|
// Examples: Maximum for 11 bits symbol is 92 (SERIAL_8N2, SERIAL_8E1, SERIAL_8O1, etc), Maximum for 10 bits symbol is 101 (SERIAL_8N1).
|
||||||
|
// For example symbols_timeout=1 defines a timeout equal to transmission time of one symbol (~11 bit) on current baudrate.
|
||||||
|
// For a baudrate of 9600, SERIAL_8N1 (10 bit symbol) and symbols_timeout = 3, the timeout would be 3 / (9600 / 10) = 3.125 ms
|
||||||
|
bool setRxTimeout(uint8_t symbols_timeout);
|
||||||
|
|
||||||
|
// setRxFIFOFull(uint8_t fifoBytes) will set the number of bytes that will trigger UART_INTR_RXFIFO_FULL interrupt and fill up RxRingBuffer
|
||||||
|
// This affects some functions such as Serial::available() and Serial.read() because, in a UART flow of receiving data, Serial internal
|
||||||
|
// RxRingBuffer will be filled only after these number of bytes arrive or a RX Timeout happens.
|
||||||
|
// This parameter can be set to 1 in order to receive byte by byte, but it will also consume more CPU time as the ISR will be activates often.
|
||||||
|
bool setRxFIFOFull(uint8_t fifoBytes);
|
||||||
|
|
||||||
|
// onReceive will setup a callback that will be called whenever an UART interruption occurs (UART_INTR_RXFIFO_FULL or UART_INTR_RXFIFO_TOUT)
|
||||||
|
// UART_INTR_RXFIFO_FULL interrupt triggers at UART_FULL_THRESH_DEFAULT bytes received (defined as 120 bytes by default in IDF)
|
||||||
|
// UART_INTR_RXFIFO_TOUT interrupt triggers at UART_TOUT_THRESH_DEFAULT symbols passed without any reception (defined as 10 symbos by default in IDF)
|
||||||
|
// onlyOnTimeout parameter will define how onReceive will behave:
|
||||||
|
// Default: true -- The callback will only be called when RX Timeout happens.
|
||||||
|
// Whole stream of bytes will be ready for being read on the callback function at once.
|
||||||
|
// This option may lead to Rx Overflow depending on the Rx Buffer Size and number of bytes received in the streaming
|
||||||
|
// false -- The callback will be called when FIFO reaches 120 bytes and also on RX Timeout.
|
||||||
|
// The stream of incommig bytes will be "split" into blocks of 120 bytes on each callback.
|
||||||
|
// This option avoid any sort of Rx Overflow, but leaves the UART packet reassembling work to the Application.
|
||||||
|
void onReceive(OnReceiveCb function, bool onlyOnTimeout = false);
|
||||||
|
|
||||||
|
// onReceive will be called on error events (see hardwareSerial_error_t)
|
||||||
|
void onReceiveError(OnReceiveErrorCb function);
|
||||||
|
|
||||||
|
// eventQueueReset clears all events in the queue (the events that trigger onReceive and onReceiveError) - maybe usefull in some use cases
|
||||||
|
void eventQueueReset();
|
||||||
|
|
||||||
|
void begin(unsigned long baud, uint32_t config=SERIAL_8N1, int8_t rxPin=-1, int8_t txPin=-1, bool invert=false, unsigned long timeout_ms = 20000UL, uint8_t rxfifo_full_thrhd = 112);
|
||||||
|
void end(bool fullyTerminate = true);
|
||||||
|
void updateBaudRate(unsigned long baud);
|
||||||
|
int available(void);
|
||||||
|
int availableForWrite(void);
|
||||||
|
int peek(void);
|
||||||
|
int read(void);
|
||||||
|
size_t read(uint8_t *buffer, size_t size);
|
||||||
|
inline size_t read(char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return read((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
// Overrides Stream::readBytes() to be faster using IDF
|
||||||
|
size_t readBytes(uint8_t *buffer, size_t length);
|
||||||
|
size_t readBytes(char *buffer, size_t length)
|
||||||
|
{
|
||||||
|
return readBytes((uint8_t *) buffer, length);
|
||||||
|
}
|
||||||
|
void flush(void);
|
||||||
|
void flush( bool txOnly);
|
||||||
|
size_t write(uint8_t);
|
||||||
|
size_t write(const uint8_t *buffer, size_t size);
|
||||||
|
inline size_t write(const char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
inline size_t write(const char * s)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) s, strlen(s));
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
uint32_t baudRate();
|
||||||
|
operator bool() const;
|
||||||
|
|
||||||
|
void setDebugOutput(bool);
|
||||||
|
|
||||||
|
void setRxInvert(bool);
|
||||||
|
|
||||||
|
// Negative Pin Number will keep it unmodified, thus this function can set individual pins
|
||||||
|
// SetPins shall be called after Serial begin()
|
||||||
|
bool setPins(int8_t rxPin, int8_t txPin, int8_t ctsPin = -1, int8_t rtsPin = -1);
|
||||||
|
// Enables or disables Hardware Flow Control using RTS and/or CTS pins (must use setAllPins() before)
|
||||||
|
bool setHwFlowCtrlMode(uint8_t mode = HW_FLOWCTRL_CTS_RTS, uint8_t threshold = 64); // 64 is half FIFO Length
|
||||||
|
// Used to set RS485 modes such as UART_MODE_RS485_HALF_DUPLEX for Auto RTS function on ESP32
|
||||||
|
bool setMode(uint8_t mode);
|
||||||
|
size_t setRxBufferSize(size_t new_size);
|
||||||
|
size_t setTxBufferSize(size_t new_size);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
int _uart_nr;
|
||||||
|
uart_t* _uart;
|
||||||
|
size_t _rxBufferSize;
|
||||||
|
size_t _txBufferSize;
|
||||||
|
OnReceiveCb _onReceiveCB;
|
||||||
|
OnReceiveErrorCb _onReceiveErrorCB;
|
||||||
|
// _onReceive and _rxTimeout have be consistent when timeout is disabled
|
||||||
|
bool _onReceiveTimeout;
|
||||||
|
uint8_t _rxTimeout, _rxFIFOFull;
|
||||||
|
TaskHandle_t _eventTask;
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
SemaphoreHandle_t _lock;
|
||||||
|
#endif
|
||||||
|
int8_t _rxPin, _txPin, _ctsPin, _rtsPin;
|
||||||
|
|
||||||
|
void _createEventTask(void *args);
|
||||||
|
void _destroyEventTask(void);
|
||||||
|
static void _uartEventTask(void *args);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern void serialEventRun(void) __attribute__((weak));
|
||||||
|
|
||||||
|
#if !defined(NO_GLOBAL_INSTANCES) && !defined(NO_GLOBAL_SERIAL)
|
||||||
|
#ifndef ARDUINO_USB_CDC_ON_BOOT
|
||||||
|
#define ARDUINO_USB_CDC_ON_BOOT 0
|
||||||
|
#endif
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT //Serial used for USB CDC
|
||||||
|
#if !ARDUINO_USB_MODE
|
||||||
|
#include "USB.h"
|
||||||
|
#include "USBCDC.h"
|
||||||
|
#endif
|
||||||
|
extern HardwareSerial Serial0;
|
||||||
|
#else
|
||||||
|
extern HardwareSerial Serial;
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 1
|
||||||
|
extern HardwareSerial Serial1;
|
||||||
|
#endif
|
||||||
|
#if SOC_UART_NUM > 2
|
||||||
|
extern HardwareSerial Serial2;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // HardwareSerial_h
|
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
IPAddress.cpp - Base class that provides IPAddress
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <IPAddress.h>
|
||||||
|
#include <Print.h>
|
||||||
|
|
||||||
|
IPAddress::IPAddress()
|
||||||
|
{
|
||||||
|
_address.dword = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress::IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet)
|
||||||
|
{
|
||||||
|
_address.bytes[0] = first_octet;
|
||||||
|
_address.bytes[1] = second_octet;
|
||||||
|
_address.bytes[2] = third_octet;
|
||||||
|
_address.bytes[3] = fourth_octet;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress::IPAddress(uint32_t address)
|
||||||
|
{
|
||||||
|
_address.dword = address;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress::IPAddress(const uint8_t *address)
|
||||||
|
{
|
||||||
|
memcpy(_address.bytes, address, sizeof(_address.bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress& IPAddress::operator=(const uint8_t *address)
|
||||||
|
{
|
||||||
|
memcpy(_address.bytes, address, sizeof(_address.bytes));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
IPAddress& IPAddress::operator=(uint32_t address)
|
||||||
|
{
|
||||||
|
_address.dword = address;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPAddress::operator==(const uint8_t* addr) const
|
||||||
|
{
|
||||||
|
return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IPAddress::printTo(Print& p) const
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
for(int i = 0; i < 3; i++) {
|
||||||
|
n += p.print(_address.bytes[i], DEC);
|
||||||
|
n += p.print('.');
|
||||||
|
}
|
||||||
|
n += p.print(_address.bytes[3], DEC);
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
String IPAddress::toString() const
|
||||||
|
{
|
||||||
|
char szRet[16];
|
||||||
|
sprintf(szRet,"%u.%u.%u.%u", _address.bytes[0], _address.bytes[1], _address.bytes[2], _address.bytes[3]);
|
||||||
|
return String(szRet);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPAddress::fromString(const char *address)
|
||||||
|
{
|
||||||
|
// TODO: add support for "a", "a.b", "a.b.c" formats
|
||||||
|
|
||||||
|
uint16_t acc = 0; // Accumulator
|
||||||
|
uint8_t dots = 0;
|
||||||
|
|
||||||
|
while (*address)
|
||||||
|
{
|
||||||
|
char c = *address++;
|
||||||
|
if (c >= '0' && c <= '9')
|
||||||
|
{
|
||||||
|
acc = acc * 10 + (c - '0');
|
||||||
|
if (acc > 255) {
|
||||||
|
// Value out of [0..255] range
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (c == '.')
|
||||||
|
{
|
||||||
|
if (dots == 3) {
|
||||||
|
// Too much dots (there must be 3 dots)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_address.bytes[dots++] = acc;
|
||||||
|
acc = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Invalid char
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dots != 3) {
|
||||||
|
// Too few dots (there must be 3 dots)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
_address.bytes[3] = acc;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// declared one time - as external in IPAddress.h
|
||||||
|
IPAddress INADDR_NONE(0, 0, 0, 0);
|
@ -0,0 +1,96 @@
|
|||||||
|
/*
|
||||||
|
IPAddress.h - Base class that provides IPAddress
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right 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 IPAddress_h
|
||||||
|
#define IPAddress_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <WString.h>
|
||||||
|
#include <Printable.h>
|
||||||
|
|
||||||
|
// A class to make it easier to handle and pass around IP addresses
|
||||||
|
|
||||||
|
class IPAddress: public Printable
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
union {
|
||||||
|
uint8_t bytes[4]; // IPv4 address
|
||||||
|
uint32_t dword;
|
||||||
|
} _address;
|
||||||
|
|
||||||
|
// Access the raw byte array containing the address. Because this returns a pointer
|
||||||
|
// to the internal structure rather than a copy of the address this function should only
|
||||||
|
// be used when you know that the usage of the returned uint8_t* will be transient and not
|
||||||
|
// stored.
|
||||||
|
uint8_t* raw_address()
|
||||||
|
{
|
||||||
|
return _address.bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructors
|
||||||
|
IPAddress();
|
||||||
|
IPAddress(uint8_t first_octet, uint8_t second_octet, uint8_t third_octet, uint8_t fourth_octet);
|
||||||
|
IPAddress(uint32_t address);
|
||||||
|
IPAddress(const uint8_t *address);
|
||||||
|
virtual ~IPAddress() {}
|
||||||
|
|
||||||
|
bool fromString(const char *address);
|
||||||
|
bool fromString(const String &address) { return fromString(address.c_str()); }
|
||||||
|
|
||||||
|
// Overloaded cast operator to allow IPAddress objects to be used where a pointer
|
||||||
|
// to a four-byte uint8_t array is expected
|
||||||
|
operator uint32_t() const
|
||||||
|
{
|
||||||
|
return _address.dword;
|
||||||
|
}
|
||||||
|
bool operator==(const IPAddress& addr) const
|
||||||
|
{
|
||||||
|
return _address.dword == addr._address.dword;
|
||||||
|
}
|
||||||
|
bool operator==(const uint8_t* addr) const;
|
||||||
|
|
||||||
|
// Overloaded index operator to allow getting and setting individual octets of the address
|
||||||
|
uint8_t operator[](int index) const
|
||||||
|
{
|
||||||
|
return _address.bytes[index];
|
||||||
|
}
|
||||||
|
uint8_t& operator[](int index)
|
||||||
|
{
|
||||||
|
return _address.bytes[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overloaded copy operators to allow initialisation of IPAddress objects from other types
|
||||||
|
IPAddress& operator=(const uint8_t *address);
|
||||||
|
IPAddress& operator=(uint32_t address);
|
||||||
|
|
||||||
|
virtual size_t printTo(Print& p) const;
|
||||||
|
String toString() const;
|
||||||
|
|
||||||
|
friend class EthernetClass;
|
||||||
|
friend class UDP;
|
||||||
|
friend class Client;
|
||||||
|
friend class Server;
|
||||||
|
friend class DhcpClass;
|
||||||
|
friend class DNSClient;
|
||||||
|
};
|
||||||
|
|
||||||
|
// changed to extern because const declaration creates copies in BSS of INADDR_NONE for each CPP unit that includes it
|
||||||
|
extern IPAddress INADDR_NONE;
|
||||||
|
#endif
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
IPv6Address.cpp - Base class that provides IPv6Address
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include <IPv6Address.h>
|
||||||
|
#include <Print.h>
|
||||||
|
|
||||||
|
IPv6Address::IPv6Address()
|
||||||
|
{
|
||||||
|
memset(_address.bytes, 0, sizeof(_address.bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPv6Address::IPv6Address(const uint8_t *address)
|
||||||
|
{
|
||||||
|
memcpy(_address.bytes, address, sizeof(_address.bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPv6Address::IPv6Address(const uint32_t *address)
|
||||||
|
{
|
||||||
|
memcpy(_address.bytes, (const uint8_t *)address, sizeof(_address.bytes));
|
||||||
|
}
|
||||||
|
|
||||||
|
IPv6Address& IPv6Address::operator=(const uint8_t *address)
|
||||||
|
{
|
||||||
|
memcpy(_address.bytes, address, sizeof(_address.bytes));
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPv6Address::operator==(const uint8_t* addr) const
|
||||||
|
{
|
||||||
|
return memcmp(addr, _address.bytes, sizeof(_address.bytes)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t IPv6Address::printTo(Print& p) const
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
for(int i = 0; i < 16; i+=2) {
|
||||||
|
if(i){
|
||||||
|
n += p.print(':');
|
||||||
|
}
|
||||||
|
n += p.printf("%02x", _address.bytes[i]);
|
||||||
|
n += p.printf("%02x", _address.bytes[i+1]);
|
||||||
|
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
String IPv6Address::toString() const
|
||||||
|
{
|
||||||
|
char szRet[40];
|
||||||
|
sprintf(szRet,"%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x",
|
||||||
|
_address.bytes[0], _address.bytes[1], _address.bytes[2], _address.bytes[3],
|
||||||
|
_address.bytes[4], _address.bytes[5], _address.bytes[6], _address.bytes[7],
|
||||||
|
_address.bytes[8], _address.bytes[9], _address.bytes[10], _address.bytes[11],
|
||||||
|
_address.bytes[12], _address.bytes[13], _address.bytes[14], _address.bytes[15]);
|
||||||
|
return String(szRet);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IPv6Address::fromString(const char *address)
|
||||||
|
{
|
||||||
|
//format 0011:2233:4455:6677:8899:aabb:ccdd:eeff
|
||||||
|
if(strlen(address) != 39){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
char * pos = (char *)address;
|
||||||
|
size_t i = 0;
|
||||||
|
for(i = 0; i < 16; i+=2) {
|
||||||
|
if(!sscanf(pos, "%2hhx", &_address.bytes[i]) || !sscanf(pos+2, "%2hhx", &_address.bytes[i+1])){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pos += 5;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
IPv6Address.h - Base class that provides IPv6Address
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right 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 IPv6Address_h
|
||||||
|
#define IPv6Address_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <WString.h>
|
||||||
|
#include <Printable.h>
|
||||||
|
|
||||||
|
// A class to make it easier to handle and pass around IP addresses
|
||||||
|
|
||||||
|
class IPv6Address: public Printable
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
union {
|
||||||
|
uint8_t bytes[16]; // IPv4 address
|
||||||
|
uint32_t dword[4];
|
||||||
|
} _address;
|
||||||
|
|
||||||
|
// Access the raw byte array containing the address. Because this returns a pointer
|
||||||
|
// to the internal structure rather than a copy of the address this function should only
|
||||||
|
// be used when you know that the usage of the returned uint8_t* will be transient and not
|
||||||
|
// stored.
|
||||||
|
uint8_t* raw_address()
|
||||||
|
{
|
||||||
|
return _address.bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Constructors
|
||||||
|
IPv6Address();
|
||||||
|
IPv6Address(const uint8_t *address);
|
||||||
|
IPv6Address(const uint32_t *address);
|
||||||
|
virtual ~IPv6Address() {}
|
||||||
|
|
||||||
|
bool fromString(const char *address);
|
||||||
|
bool fromString(const String &address) { return fromString(address.c_str()); }
|
||||||
|
|
||||||
|
operator const uint8_t*() const
|
||||||
|
{
|
||||||
|
return _address.bytes;
|
||||||
|
}
|
||||||
|
operator const uint32_t*() const
|
||||||
|
{
|
||||||
|
return _address.dword;
|
||||||
|
}
|
||||||
|
bool operator==(const IPv6Address& addr) const
|
||||||
|
{
|
||||||
|
return (_address.dword[0] == addr._address.dword[0])
|
||||||
|
&& (_address.dword[1] == addr._address.dword[1])
|
||||||
|
&& (_address.dword[2] == addr._address.dword[2])
|
||||||
|
&& (_address.dword[3] == addr._address.dword[3]);
|
||||||
|
}
|
||||||
|
bool operator==(const uint8_t* addr) const;
|
||||||
|
|
||||||
|
// Overloaded index operator to allow getting and setting individual octets of the address
|
||||||
|
uint8_t operator[](int index) const
|
||||||
|
{
|
||||||
|
return _address.bytes[index];
|
||||||
|
}
|
||||||
|
uint8_t& operator[](int index)
|
||||||
|
{
|
||||||
|
return _address.bytes[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overloaded copy operators to allow initialisation of IPv6Address objects from other types
|
||||||
|
IPv6Address& operator=(const uint8_t *address);
|
||||||
|
|
||||||
|
virtual size_t printTo(Print& p) const;
|
||||||
|
String toString() const;
|
||||||
|
|
||||||
|
friend class UDP;
|
||||||
|
friend class Client;
|
||||||
|
friend class Server;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2015 Hristo Gochkov. 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 <Arduino.h>
|
||||||
|
#include <MD5Builder.h>
|
||||||
|
|
||||||
|
static uint8_t hex_char_to_byte(uint8_t c)
|
||||||
|
{
|
||||||
|
return (c >= 'a' && c <= 'f') ? (c - ((uint8_t)'a' - 0xa)) :
|
||||||
|
(c >= 'A' && c <= 'F') ? (c - ((uint8_t)'A' - 0xA)) :
|
||||||
|
(c >= '0' && c<= '9') ? (c - (uint8_t)'0') : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MD5Builder::begin(void)
|
||||||
|
{
|
||||||
|
memset(_buf, 0x00, ESP_ROM_MD5_DIGEST_LEN);
|
||||||
|
esp_rom_md5_init(&_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MD5Builder::add(uint8_t * data, uint16_t len)
|
||||||
|
{
|
||||||
|
esp_rom_md5_update(&_ctx, data, len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MD5Builder::addHexString(const char * data)
|
||||||
|
{
|
||||||
|
uint16_t i, len = strlen(data);
|
||||||
|
uint8_t * tmp = (uint8_t*)malloc(len/2);
|
||||||
|
if(tmp == NULL) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for(i=0; i<len; i+=2) {
|
||||||
|
uint8_t high = hex_char_to_byte(data[i]);
|
||||||
|
uint8_t low = hex_char_to_byte(data[i+1]);
|
||||||
|
tmp[i/2] = (high & 0x0F) << 4 | (low & 0x0F);
|
||||||
|
}
|
||||||
|
add(tmp, len/2);
|
||||||
|
free(tmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MD5Builder::addStream(Stream & stream, const size_t maxLen)
|
||||||
|
{
|
||||||
|
const int buf_size = 512;
|
||||||
|
int maxLengthLeft = maxLen;
|
||||||
|
uint8_t * buf = (uint8_t*) malloc(buf_size);
|
||||||
|
|
||||||
|
if(!buf) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int bytesAvailable = stream.available();
|
||||||
|
while((bytesAvailable > 0) && (maxLengthLeft > 0)) {
|
||||||
|
|
||||||
|
// determine number of bytes to read
|
||||||
|
int readBytes = bytesAvailable;
|
||||||
|
if(readBytes > maxLengthLeft) {
|
||||||
|
readBytes = maxLengthLeft ; // read only until max_len
|
||||||
|
}
|
||||||
|
if(readBytes > buf_size) {
|
||||||
|
readBytes = buf_size; // not read more the buffer can handle
|
||||||
|
}
|
||||||
|
|
||||||
|
// read data and check if we got something
|
||||||
|
int numBytesRead = stream.readBytes(buf, readBytes);
|
||||||
|
if(numBytesRead< 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update MD5 with buffer payload
|
||||||
|
esp_rom_md5_update(&_ctx, buf, numBytesRead);
|
||||||
|
|
||||||
|
// update available number of bytes
|
||||||
|
maxLengthLeft -= numBytesRead;
|
||||||
|
bytesAvailable = stream.available();
|
||||||
|
}
|
||||||
|
free(buf);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MD5Builder::calculate(void)
|
||||||
|
{
|
||||||
|
esp_rom_md5_final(_buf, &_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MD5Builder::getBytes(uint8_t * output)
|
||||||
|
{
|
||||||
|
memcpy(output, _buf, ESP_ROM_MD5_DIGEST_LEN);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MD5Builder::getChars(char * output)
|
||||||
|
{
|
||||||
|
for(uint8_t i = 0; i < ESP_ROM_MD5_DIGEST_LEN; i++) {
|
||||||
|
sprintf(output + (i * 2), "%02x", _buf[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String MD5Builder::toString(void)
|
||||||
|
{
|
||||||
|
char out[(ESP_ROM_MD5_DIGEST_LEN * 2) + 1];
|
||||||
|
getChars(out);
|
||||||
|
return String(out);
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
Copyright (c) 2015 Hristo Gochkov. 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
|
||||||
|
*/
|
||||||
|
#ifndef __ESP8266_MD5_BUILDER__
|
||||||
|
#define __ESP8266_MD5_BUILDER__
|
||||||
|
|
||||||
|
#include <WString.h>
|
||||||
|
#include <Stream.h>
|
||||||
|
|
||||||
|
#include "esp_system.h"
|
||||||
|
#include "esp_rom_md5.h"
|
||||||
|
|
||||||
|
class MD5Builder
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
md5_context_t _ctx;
|
||||||
|
uint8_t _buf[ESP_ROM_MD5_DIGEST_LEN];
|
||||||
|
public:
|
||||||
|
void begin(void);
|
||||||
|
void add(uint8_t * data, uint16_t len);
|
||||||
|
void add(const char * data)
|
||||||
|
{
|
||||||
|
add((uint8_t*)data, strlen(data));
|
||||||
|
}
|
||||||
|
void add(char * data)
|
||||||
|
{
|
||||||
|
add((const char*)data);
|
||||||
|
}
|
||||||
|
void add(String data)
|
||||||
|
{
|
||||||
|
add(data.c_str());
|
||||||
|
}
|
||||||
|
void addHexString(const char * data);
|
||||||
|
void addHexString(char * data)
|
||||||
|
{
|
||||||
|
addHexString((const char*)data);
|
||||||
|
}
|
||||||
|
void addHexString(String data)
|
||||||
|
{
|
||||||
|
addHexString(data.c_str());
|
||||||
|
}
|
||||||
|
bool addStream(Stream & stream, const size_t maxLen);
|
||||||
|
void calculate(void);
|
||||||
|
void getBytes(uint8_t * output);
|
||||||
|
void getChars(char * output);
|
||||||
|
String toString(void);
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,362 @@
|
|||||||
|
/*
|
||||||
|
Print.cpp - Base class that provides print() and println()
|
||||||
|
Copyright (c) 2008 David A. Mellis. All right 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
|
||||||
|
|
||||||
|
Modified 23 November 2006 by David A. Mellis
|
||||||
|
Modified December 2014 by Ivan Grokhotkov
|
||||||
|
Modified May 2015 by Michael C. Miller - ESP31B progmem support
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
#include "Arduino.h"
|
||||||
|
|
||||||
|
#include "Print.h"
|
||||||
|
extern "C" {
|
||||||
|
#include "time.h"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public Methods //////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
/* default implementation: may be overridden */
|
||||||
|
size_t Print::write(const uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
while(size--) {
|
||||||
|
n += write(*buffer++);
|
||||||
|
}
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::printf(const char *format, ...)
|
||||||
|
{
|
||||||
|
char loc_buf[64];
|
||||||
|
char * temp = loc_buf;
|
||||||
|
va_list arg;
|
||||||
|
va_list copy;
|
||||||
|
va_start(arg, format);
|
||||||
|
va_copy(copy, arg);
|
||||||
|
int len = vsnprintf(temp, sizeof(loc_buf), format, copy);
|
||||||
|
va_end(copy);
|
||||||
|
if(len < 0) {
|
||||||
|
va_end(arg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(len >= (int)sizeof(loc_buf)){ // comparation of same sign type for the compiler
|
||||||
|
temp = (char*) malloc(len+1);
|
||||||
|
if(temp == NULL) {
|
||||||
|
va_end(arg);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
len = vsnprintf(temp, len+1, format, arg);
|
||||||
|
}
|
||||||
|
va_end(arg);
|
||||||
|
len = write((uint8_t*)temp, len);
|
||||||
|
if(temp != loc_buf){
|
||||||
|
free(temp);
|
||||||
|
}
|
||||||
|
return len;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(const String &s)
|
||||||
|
{
|
||||||
|
return write(s.c_str(), s.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(const char str[])
|
||||||
|
{
|
||||||
|
return write(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(char c)
|
||||||
|
{
|
||||||
|
return write(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(unsigned char b, int base)
|
||||||
|
{
|
||||||
|
return print((unsigned long) b, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(int n, int base)
|
||||||
|
{
|
||||||
|
return print((long) n, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(unsigned int n, int base)
|
||||||
|
{
|
||||||
|
return print((unsigned long) n, base);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(long n, int base)
|
||||||
|
{
|
||||||
|
int t = 0;
|
||||||
|
if (base == 10 && n < 0) {
|
||||||
|
t = print('-');
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
return printNumber(static_cast<unsigned long>(n), base) + t;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(unsigned long n, int base)
|
||||||
|
{
|
||||||
|
if(base == 0) {
|
||||||
|
return write(n);
|
||||||
|
} else {
|
||||||
|
return printNumber(n, base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(long long n, int base)
|
||||||
|
{
|
||||||
|
int t = 0;
|
||||||
|
if (base == 10 && n < 0) {
|
||||||
|
t = print('-');
|
||||||
|
n = -n;
|
||||||
|
}
|
||||||
|
return printNumber(static_cast<unsigned long long>(n), base) + t;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(unsigned long long n, int base)
|
||||||
|
{
|
||||||
|
if (base == 0) {
|
||||||
|
return write(n);
|
||||||
|
} else {
|
||||||
|
return printNumber(n, base);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(double n, int digits)
|
||||||
|
{
|
||||||
|
return printFloat(n, digits);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(const Printable& x)
|
||||||
|
{
|
||||||
|
return x.printTo(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::print(struct tm * timeinfo, const char * format)
|
||||||
|
{
|
||||||
|
const char * f = format;
|
||||||
|
if(!f){
|
||||||
|
f = "%c";
|
||||||
|
}
|
||||||
|
char buf[64];
|
||||||
|
size_t written = strftime(buf, 64, f, timeinfo);
|
||||||
|
if(written == 0){
|
||||||
|
return written;
|
||||||
|
}
|
||||||
|
return print(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(void)
|
||||||
|
{
|
||||||
|
return print("\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(const String &s)
|
||||||
|
{
|
||||||
|
size_t n = print(s);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(const char c[])
|
||||||
|
{
|
||||||
|
size_t n = print(c);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(char c)
|
||||||
|
{
|
||||||
|
size_t n = print(c);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(unsigned char b, int base)
|
||||||
|
{
|
||||||
|
size_t n = print(b, base);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(int num, int base)
|
||||||
|
{
|
||||||
|
size_t n = print(num, base);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(unsigned int num, int base)
|
||||||
|
{
|
||||||
|
size_t n = print(num, base);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(long num, int base)
|
||||||
|
{
|
||||||
|
size_t n = print(num, base);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(unsigned long num, int base)
|
||||||
|
{
|
||||||
|
size_t n = print(num, base);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(long long num, int base)
|
||||||
|
{
|
||||||
|
size_t n = print(num, base);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(unsigned long long num, int base)
|
||||||
|
{
|
||||||
|
size_t n = print(num, base);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(double num, int digits)
|
||||||
|
{
|
||||||
|
size_t n = print(num, digits);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(const Printable& x)
|
||||||
|
{
|
||||||
|
size_t n = print(x);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::println(struct tm * timeinfo, const char * format)
|
||||||
|
{
|
||||||
|
size_t n = print(timeinfo, format);
|
||||||
|
n += println();
|
||||||
|
return n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Private Methods /////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
size_t Print::printNumber(unsigned long n, uint8_t base)
|
||||||
|
{
|
||||||
|
char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
|
||||||
|
char *str = &buf[sizeof(buf) - 1];
|
||||||
|
|
||||||
|
*str = '\0';
|
||||||
|
|
||||||
|
// prevent crash if called with base == 1
|
||||||
|
if(base < 2) {
|
||||||
|
base = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
char c = n % base;
|
||||||
|
n /= base;
|
||||||
|
|
||||||
|
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||||
|
} while (n);
|
||||||
|
|
||||||
|
return write(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::printNumber(unsigned long long n, uint8_t base)
|
||||||
|
{
|
||||||
|
char buf[8 * sizeof(n) + 1]; // Assumes 8-bit chars plus zero byte.
|
||||||
|
char* str = &buf[sizeof(buf) - 1];
|
||||||
|
|
||||||
|
*str = '\0';
|
||||||
|
|
||||||
|
// prevent crash if called with base == 1
|
||||||
|
if (base < 2) {
|
||||||
|
base = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
auto m = n;
|
||||||
|
n /= base;
|
||||||
|
char c = m - base * n;
|
||||||
|
|
||||||
|
*--str = c < 10 ? c + '0' : c + 'A' - 10;
|
||||||
|
} while (n);
|
||||||
|
|
||||||
|
return write(str);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t Print::printFloat(double number, uint8_t digits)
|
||||||
|
{
|
||||||
|
size_t n = 0;
|
||||||
|
|
||||||
|
if(isnan(number)) {
|
||||||
|
return print("nan");
|
||||||
|
}
|
||||||
|
if(isinf(number)) {
|
||||||
|
return print("inf");
|
||||||
|
}
|
||||||
|
if(number > 4294967040.0) {
|
||||||
|
return print("ovf"); // constant determined empirically
|
||||||
|
}
|
||||||
|
if(number < -4294967040.0) {
|
||||||
|
return print("ovf"); // constant determined empirically
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle negative numbers
|
||||||
|
if(number < 0.0) {
|
||||||
|
n += print('-');
|
||||||
|
number = -number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Round correctly so that print(1.999, 2) prints as "2.00"
|
||||||
|
double rounding = 0.5;
|
||||||
|
for(uint8_t i = 0; i < digits; ++i) {
|
||||||
|
rounding /= 10.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
number += rounding;
|
||||||
|
|
||||||
|
// Extract the integer part of the number and print it
|
||||||
|
unsigned long int_part = (unsigned long) number;
|
||||||
|
double remainder = number - (double) int_part;
|
||||||
|
n += print(int_part);
|
||||||
|
|
||||||
|
// Print the decimal point, but only if there are digits beyond
|
||||||
|
if(digits > 0) {
|
||||||
|
n += print(".");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract digits from the remainder one at a time
|
||||||
|
while(digits-- > 0) {
|
||||||
|
remainder *= 10.0;
|
||||||
|
int toPrint = int(remainder);
|
||||||
|
n += print(toPrint);
|
||||||
|
remainder -= toPrint;
|
||||||
|
}
|
||||||
|
|
||||||
|
return n;
|
||||||
|
}
|
@ -0,0 +1,116 @@
|
|||||||
|
/*
|
||||||
|
Print.h - Base class that provides print() and println()
|
||||||
|
Copyright (c) 2008 David A. Mellis. All right 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 Print_h
|
||||||
|
#define Print_h
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
#include "WString.h"
|
||||||
|
#include "Printable.h"
|
||||||
|
|
||||||
|
#define DEC 10
|
||||||
|
#define HEX 16
|
||||||
|
#define OCT 8
|
||||||
|
#define BIN 2
|
||||||
|
|
||||||
|
class Print
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int write_error;
|
||||||
|
size_t printNumber(unsigned long, uint8_t);
|
||||||
|
size_t printNumber(unsigned long long, uint8_t);
|
||||||
|
size_t printFloat(double, uint8_t);
|
||||||
|
protected:
|
||||||
|
void setWriteError(int err = 1)
|
||||||
|
{
|
||||||
|
write_error = err;
|
||||||
|
}
|
||||||
|
public:
|
||||||
|
Print() :
|
||||||
|
write_error(0)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
virtual ~Print() {}
|
||||||
|
int getWriteError()
|
||||||
|
{
|
||||||
|
return write_error;
|
||||||
|
}
|
||||||
|
void clearWriteError()
|
||||||
|
{
|
||||||
|
setWriteError(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual size_t write(uint8_t) = 0;
|
||||||
|
size_t write(const char *str)
|
||||||
|
{
|
||||||
|
if(str == NULL) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return write((const uint8_t *) str, strlen(str));
|
||||||
|
}
|
||||||
|
virtual size_t write(const uint8_t *buffer, size_t size);
|
||||||
|
size_t write(const char *buffer, size_t size)
|
||||||
|
{
|
||||||
|
return write((const uint8_t *) buffer, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t printf(const char * format, ...) __attribute__ ((format (printf, 2, 3)));
|
||||||
|
|
||||||
|
// add availableForWrite to make compatible with Arduino Print.h
|
||||||
|
// default to zero, meaning "a single write may block"
|
||||||
|
// should be overriden by subclasses with buffering
|
||||||
|
virtual int availableForWrite() { return 0; }
|
||||||
|
size_t print(const __FlashStringHelper *ifsh) { return print(reinterpret_cast<const char *>(ifsh)); }
|
||||||
|
size_t print(const String &);
|
||||||
|
size_t print(const char[]);
|
||||||
|
size_t print(char);
|
||||||
|
size_t print(unsigned char, int = DEC);
|
||||||
|
size_t print(int, int = DEC);
|
||||||
|
size_t print(unsigned int, int = DEC);
|
||||||
|
size_t print(long, int = DEC);
|
||||||
|
size_t print(unsigned long, int = DEC);
|
||||||
|
size_t print(long long, int = DEC);
|
||||||
|
size_t print(unsigned long long, int = DEC);
|
||||||
|
size_t print(double, int = 2);
|
||||||
|
size_t print(const Printable&);
|
||||||
|
size_t print(struct tm * timeinfo, const char * format = NULL);
|
||||||
|
|
||||||
|
size_t println(const __FlashStringHelper *ifsh) { return println(reinterpret_cast<const char *>(ifsh)); }
|
||||||
|
size_t println(const String &s);
|
||||||
|
size_t println(const char[]);
|
||||||
|
size_t println(char);
|
||||||
|
size_t println(unsigned char, int = DEC);
|
||||||
|
size_t println(int, int = DEC);
|
||||||
|
size_t println(unsigned int, int = DEC);
|
||||||
|
size_t println(long, int = DEC);
|
||||||
|
size_t println(unsigned long, int = DEC);
|
||||||
|
size_t println(long long, int = DEC);
|
||||||
|
size_t println(unsigned long long, int = DEC);
|
||||||
|
size_t println(double, int = 2);
|
||||||
|
size_t println(const Printable&);
|
||||||
|
size_t println(struct tm * timeinfo, const char * format = NULL);
|
||||||
|
size_t println(void);
|
||||||
|
|
||||||
|
virtual void flush() { /* Empty implementation for backward compatibility */ }
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
Printable.h - Interface class that allows printing of complex types
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right 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 Printable_h
|
||||||
|
#define Printable_h
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
class Print;
|
||||||
|
|
||||||
|
/** The Printable class provides a way for new classes to allow themselves to be printed.
|
||||||
|
By deriving from Printable and implementing the printTo method, it will then be possible
|
||||||
|
for users to print out instances of this class by passing them into the usual
|
||||||
|
Print::print and Print::println methods.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Printable
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual ~Printable() {}
|
||||||
|
virtual size_t printTo(Print& p) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
Server.h - Base class that provides Server
|
||||||
|
Copyright (c) 2011 Adrian McEwen. All right 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 server_h
|
||||||
|
#define server_h
|
||||||
|
|
||||||
|
#include "Print.h"
|
||||||
|
|
||||||
|
class Server: public Print
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual void begin(uint16_t port=0) =0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,337 @@
|
|||||||
|
/*
|
||||||
|
Stream.cpp - adds parsing methods to Stream class
|
||||||
|
Copyright (c) 2008 David A. Mellis. All right 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
|
||||||
|
|
||||||
|
Created July 2011
|
||||||
|
parsing functions based on TextFinder library by Michael Margolis
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "Arduino.h"
|
||||||
|
#include "Stream.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
|
||||||
|
#define PARSE_TIMEOUT 1000 // default number of milli-seconds to wait
|
||||||
|
#define NO_SKIP_CHAR 1 // a magic char not found in a valid ASCII numeric field
|
||||||
|
|
||||||
|
// private method to read stream with timeout
|
||||||
|
int Stream::timedRead()
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
_startMillis = millis();
|
||||||
|
do {
|
||||||
|
c = read();
|
||||||
|
if(c >= 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
} while(millis() - _startMillis < _timeout);
|
||||||
|
return -1; // -1 indicates timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// private method to peek stream with timeout
|
||||||
|
int Stream::timedPeek()
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
_startMillis = millis();
|
||||||
|
do {
|
||||||
|
c = peek();
|
||||||
|
if(c >= 0) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
} while(millis() - _startMillis < _timeout);
|
||||||
|
return -1; // -1 indicates timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns peek of the next digit in the stream or -1 if timeout
|
||||||
|
// discards non-numeric characters
|
||||||
|
int Stream::peekNextDigit()
|
||||||
|
{
|
||||||
|
int c;
|
||||||
|
while(1) {
|
||||||
|
c = timedPeek();
|
||||||
|
if(c < 0) {
|
||||||
|
return c; // timeout
|
||||||
|
}
|
||||||
|
if(c == '-') {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
if(c >= '0' && c <= '9') {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
read(); // discard non-numeric
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public Methods
|
||||||
|
//////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
void Stream::setTimeout(unsigned long timeout) // sets the maximum number of milliseconds to wait
|
||||||
|
{
|
||||||
|
_timeout = timeout;
|
||||||
|
}
|
||||||
|
unsigned long Stream::getTimeout(void) {
|
||||||
|
return _timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
// find returns true if the target string is found
|
||||||
|
bool Stream::find(const char *target)
|
||||||
|
{
|
||||||
|
return findUntil(target, strlen(target), NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// reads data from the stream until the target string of given length is found
|
||||||
|
// returns true if target string is found, false if timed out
|
||||||
|
bool Stream::find(const char *target, size_t length)
|
||||||
|
{
|
||||||
|
return findUntil(target, length, NULL, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// as find but search ends if the terminator string is found
|
||||||
|
bool Stream::findUntil(const char *target, const char *terminator)
|
||||||
|
{
|
||||||
|
return findUntil(target, strlen(target), terminator, strlen(terminator));
|
||||||
|
}
|
||||||
|
|
||||||
|
// reads data from the stream until the target string of the given length is found
|
||||||
|
// search terminated if the terminator string is found
|
||||||
|
// returns true if target string is found, false if terminated or timed out
|
||||||
|
bool Stream::findUntil(const char *target, size_t targetLen, const char *terminator, size_t termLen)
|
||||||
|
{
|
||||||
|
if (terminator == NULL) {
|
||||||
|
MultiTarget t[1] = {{target, targetLen, 0}};
|
||||||
|
return findMulti(t, 1) == 0 ? true : false;
|
||||||
|
} else {
|
||||||
|
MultiTarget t[2] = {{target, targetLen, 0}, {terminator, termLen, 0}};
|
||||||
|
return findMulti(t, 2) == 0 ? true : false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int Stream::findMulti( struct Stream::MultiTarget *targets, int tCount) {
|
||||||
|
// any zero length target string automatically matches and would make
|
||||||
|
// a mess of the rest of the algorithm.
|
||||||
|
for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
|
||||||
|
if (t->len <= 0)
|
||||||
|
return t - targets;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
int c = timedRead();
|
||||||
|
if (c < 0)
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
|
||||||
|
// the simple case is if we match, deal with that first.
|
||||||
|
if (c == t->str[t->index]) {
|
||||||
|
if (++t->index == t->len)
|
||||||
|
return t - targets;
|
||||||
|
else
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if not we need to walk back and see if we could have matched further
|
||||||
|
// down the stream (ie '1112' doesn't match the first position in '11112'
|
||||||
|
// but it will match the second position so we can't just reset the current
|
||||||
|
// index to 0 when we find a mismatch.
|
||||||
|
if (t->index == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
int origIndex = t->index;
|
||||||
|
do {
|
||||||
|
--t->index;
|
||||||
|
// first check if current char works against the new current index
|
||||||
|
if (c != t->str[t->index])
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// if it's the only char then we're good, nothing more to check
|
||||||
|
if (t->index == 0) {
|
||||||
|
t->index++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise we need to check the rest of the found string
|
||||||
|
int diff = origIndex - t->index;
|
||||||
|
size_t i;
|
||||||
|
for (i = 0; i < t->index; ++i) {
|
||||||
|
if (t->str[i] != t->str[i + diff])
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if we successfully got through the previous loop then our current
|
||||||
|
// index is good.
|
||||||
|
if (i == t->index) {
|
||||||
|
t->index++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// otherwise we just try the next index
|
||||||
|
} while (t->index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// unreachable
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// returns the first valid (long) integer value from the current position.
|
||||||
|
// initial characters that are not digits (or the minus sign) are skipped
|
||||||
|
// function is terminated by the first character that is not a digit.
|
||||||
|
long Stream::parseInt()
|
||||||
|
{
|
||||||
|
return parseInt(NO_SKIP_CHAR); // terminate on first non-digit character (or timeout)
|
||||||
|
}
|
||||||
|
|
||||||
|
// as above but a given skipChar is ignored
|
||||||
|
// this allows format characters (typically commas) in values to be ignored
|
||||||
|
long Stream::parseInt(char skipChar)
|
||||||
|
{
|
||||||
|
boolean isNegative = false;
|
||||||
|
long value = 0;
|
||||||
|
int c;
|
||||||
|
|
||||||
|
c = peekNextDigit();
|
||||||
|
// ignore non numeric leading characters
|
||||||
|
if(c < 0) {
|
||||||
|
return 0; // zero returned if timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(c == skipChar) {
|
||||||
|
} // ignore this charactor
|
||||||
|
else if(c == '-') {
|
||||||
|
isNegative = true;
|
||||||
|
} else if(c >= '0' && c <= '9') { // is c a digit?
|
||||||
|
value = value * 10 + c - '0';
|
||||||
|
}
|
||||||
|
read(); // consume the character we got with peek
|
||||||
|
c = timedPeek();
|
||||||
|
} while((c >= '0' && c <= '9') || c == skipChar);
|
||||||
|
|
||||||
|
if(isNegative) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
// as parseInt but returns a floating point value
|
||||||
|
float Stream::parseFloat()
|
||||||
|
{
|
||||||
|
return parseFloat(NO_SKIP_CHAR);
|
||||||
|
}
|
||||||
|
|
||||||
|
// as above but the given skipChar is ignored
|
||||||
|
// this allows format characters (typically commas) in values to be ignored
|
||||||
|
float Stream::parseFloat(char skipChar)
|
||||||
|
{
|
||||||
|
boolean isNegative = false;
|
||||||
|
boolean isFraction = false;
|
||||||
|
long value = 0;
|
||||||
|
int c;
|
||||||
|
float fraction = 1.0;
|
||||||
|
|
||||||
|
c = peekNextDigit();
|
||||||
|
// ignore non numeric leading characters
|
||||||
|
if(c < 0) {
|
||||||
|
return 0; // zero returned if timeout
|
||||||
|
}
|
||||||
|
|
||||||
|
do {
|
||||||
|
if(c == skipChar) {
|
||||||
|
} // ignore
|
||||||
|
else if(c == '-') {
|
||||||
|
isNegative = true;
|
||||||
|
} else if(c == '.') {
|
||||||
|
isFraction = true;
|
||||||
|
} else if(c >= '0' && c <= '9') { // is c a digit?
|
||||||
|
value = value * 10 + c - '0';
|
||||||
|
if(isFraction) {
|
||||||
|
fraction *= 0.1f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
read(); // consume the character we got with peek
|
||||||
|
c = timedPeek();
|
||||||
|
} while((c >= '0' && c <= '9') || c == '.' || c == skipChar);
|
||||||
|
|
||||||
|
if(isNegative) {
|
||||||
|
value = -value;
|
||||||
|
}
|
||||||
|
if(isFraction) {
|
||||||
|
return value * fraction;
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// read characters from stream into buffer
|
||||||
|
// terminates if length characters have been read, or timeout (see setTimeout)
|
||||||
|
// returns the number of characters placed in the buffer
|
||||||
|
// the buffer is NOT null terminated.
|
||||||
|
//
|
||||||
|
size_t Stream::readBytes(char *buffer, size_t length)
|
||||||
|
{
|
||||||
|
size_t count = 0;
|
||||||
|
while(count < length) {
|
||||||
|
int c = timedRead();
|
||||||
|
if(c < 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*buffer++ = (char) c;
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
// as readBytes with terminator character
|
||||||
|
// terminates if length characters have been read, timeout, or if the terminator character detected
|
||||||
|
// returns the number of characters placed in the buffer (0 means no valid data found)
|
||||||
|
|
||||||
|
size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length)
|
||||||
|
{
|
||||||
|
if(length < 1) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t index = 0;
|
||||||
|
while(index < length) {
|
||||||
|
int c = timedRead();
|
||||||
|
if(c < 0 || c == terminator) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
*buffer++ = (char) c;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
return index; // return number of characters, not including null terminator
|
||||||
|
}
|
||||||
|
|
||||||
|
String Stream::readString()
|
||||||
|
{
|
||||||
|
String ret;
|
||||||
|
int c = timedRead();
|
||||||
|
while(c >= 0) {
|
||||||
|
ret += (char) c;
|
||||||
|
c = timedRead();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Stream::readStringUntil(char terminator)
|
||||||
|
{
|
||||||
|
String ret;
|
||||||
|
int c = timedRead();
|
||||||
|
while(c >= 0 && c != terminator) {
|
||||||
|
ret += (char) c;
|
||||||
|
c = timedRead();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,139 @@
|
|||||||
|
/*
|
||||||
|
Stream.h - base class for character-based streams.
|
||||||
|
Copyright (c) 2010 David A. Mellis. All right 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
|
||||||
|
|
||||||
|
parsing functions based on TextFinder library by Michael Margolis
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef Stream_h
|
||||||
|
#define Stream_h
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "Print.h"
|
||||||
|
|
||||||
|
// compatability macros for testing
|
||||||
|
/*
|
||||||
|
#define getInt() parseInt()
|
||||||
|
#define getInt(skipChar) parseInt(skipchar)
|
||||||
|
#define getFloat() parseFloat()
|
||||||
|
#define getFloat(skipChar) parseFloat(skipChar)
|
||||||
|
#define getString( pre_string, post_string, buffer, length)
|
||||||
|
readBytesBetween( pre_string, terminator, buffer, length)
|
||||||
|
*/
|
||||||
|
|
||||||
|
class Stream: public Print
|
||||||
|
{
|
||||||
|
protected:
|
||||||
|
unsigned long _timeout; // number of milliseconds to wait for the next char before aborting timed read
|
||||||
|
unsigned long _startMillis; // used for timeout measurement
|
||||||
|
int timedRead(); // private method to read stream with timeout
|
||||||
|
int timedPeek(); // private method to peek stream with timeout
|
||||||
|
int peekNextDigit(); // returns the next numeric digit in the stream or -1 if timeout
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual int available() = 0;
|
||||||
|
virtual int read() = 0;
|
||||||
|
virtual int peek() = 0;
|
||||||
|
|
||||||
|
Stream():_startMillis(0)
|
||||||
|
{
|
||||||
|
_timeout = 1000;
|
||||||
|
}
|
||||||
|
virtual ~Stream() {}
|
||||||
|
|
||||||
|
// parsing methods
|
||||||
|
|
||||||
|
void setTimeout(unsigned long timeout); // sets maximum milliseconds to wait for stream data, default is 1 second
|
||||||
|
unsigned long getTimeout(void);
|
||||||
|
|
||||||
|
bool find(const char *target); // reads data from the stream until the target string is found
|
||||||
|
bool find(uint8_t *target)
|
||||||
|
{
|
||||||
|
return find((char *) target);
|
||||||
|
}
|
||||||
|
// returns true if target string is found, false if timed out (see setTimeout)
|
||||||
|
|
||||||
|
bool find(const char *target, size_t length); // reads data from the stream until the target string of given length is found
|
||||||
|
bool find(const uint8_t *target, size_t length)
|
||||||
|
{
|
||||||
|
return find((char *) target, length);
|
||||||
|
}
|
||||||
|
// returns true if target string is found, false if timed out
|
||||||
|
|
||||||
|
bool find(char target)
|
||||||
|
{
|
||||||
|
return find (&target, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool findUntil(const char *target, const char *terminator); // as find but search ends if the terminator string is found
|
||||||
|
bool findUntil(const uint8_t *target, const char *terminator)
|
||||||
|
{
|
||||||
|
return findUntil((char *) target, terminator);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool findUntil(const char *target, size_t targetLen, const char *terminate, size_t termLen); // as above but search ends if the terminate string is found
|
||||||
|
bool findUntil(const uint8_t *target, size_t targetLen, const char *terminate, size_t termLen)
|
||||||
|
{
|
||||||
|
return findUntil((char *) target, targetLen, terminate, termLen);
|
||||||
|
}
|
||||||
|
|
||||||
|
long parseInt(); // returns the first valid (long) integer value from the current position.
|
||||||
|
// initial characters that are not digits (or the minus sign) are skipped
|
||||||
|
// integer is terminated by the first character that is not a digit.
|
||||||
|
|
||||||
|
float parseFloat(); // float version of parseInt
|
||||||
|
|
||||||
|
virtual size_t readBytes(char *buffer, size_t length); // read chars from stream into buffer
|
||||||
|
virtual size_t readBytes(uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
return readBytes((char *) buffer, length);
|
||||||
|
}
|
||||||
|
// terminates if length characters have been read or timeout (see setTimeout)
|
||||||
|
// returns the number of characters placed in the buffer (0 means no valid data found)
|
||||||
|
|
||||||
|
size_t readBytesUntil(char terminator, char *buffer, size_t length); // as readBytes with terminator character
|
||||||
|
size_t readBytesUntil(char terminator, uint8_t *buffer, size_t length)
|
||||||
|
{
|
||||||
|
return readBytesUntil(terminator, (char *) buffer, length);
|
||||||
|
}
|
||||||
|
// terminates if length characters have been read, timeout, or if the terminator character detected
|
||||||
|
// returns the number of characters placed in the buffer (0 means no valid data found)
|
||||||
|
|
||||||
|
// Arduino String functions to be added here
|
||||||
|
virtual String readString();
|
||||||
|
String readStringUntil(char terminator);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
long parseInt(char skipChar); // as above but the given skipChar is ignored
|
||||||
|
// as above but the given skipChar is ignored
|
||||||
|
// this allows format characters (typically commas) in values to be ignored
|
||||||
|
|
||||||
|
float parseFloat(char skipChar); // as above but the given skipChar is ignored
|
||||||
|
|
||||||
|
struct MultiTarget {
|
||||||
|
const char *str; // string you're searching for
|
||||||
|
size_t len; // length of string you're searching for
|
||||||
|
size_t index; // index used by the search routine.
|
||||||
|
};
|
||||||
|
|
||||||
|
// This allows you to search for an arbitrary number of strings.
|
||||||
|
// Returns index of the target that is found first or -1 if timeout occurs.
|
||||||
|
int findMulti(struct MultiTarget *targets, int tCount);
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,67 @@
|
|||||||
|
/**
|
||||||
|
StreamString.cpp
|
||||||
|
|
||||||
|
Copyright (c) 2015 Markus Sattler. 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 <Arduino.h>
|
||||||
|
#include "StreamString.h"
|
||||||
|
|
||||||
|
size_t StreamString::write(const uint8_t *data, size_t size) {
|
||||||
|
if(size && data) {
|
||||||
|
const unsigned int newlen = length() + size;
|
||||||
|
if(reserve(newlen + 1)) {
|
||||||
|
memcpy((void *) (wbuffer() + len()), (const void *) data, size);
|
||||||
|
setLen(newlen);
|
||||||
|
*(wbuffer() + newlen) = 0x00; // add null for string end
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t StreamString::write(uint8_t data) {
|
||||||
|
return concat((char) data);
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamString::available() {
|
||||||
|
return length();
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamString::read() {
|
||||||
|
if(length()) {
|
||||||
|
char c = charAt(0);
|
||||||
|
remove(0, 1);
|
||||||
|
return c;
|
||||||
|
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int StreamString::peek() {
|
||||||
|
if(length()) {
|
||||||
|
char c = charAt(0);
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StreamString::flush() {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
StreamString.h
|
||||||
|
|
||||||
|
Copyright (c) 2015 Markus Sattler. 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 STREAMSTRING_H_
|
||||||
|
#define STREAMSTRING_H_
|
||||||
|
|
||||||
|
|
||||||
|
class StreamString: public Stream, public String
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
size_t write(const uint8_t *buffer, size_t size) override;
|
||||||
|
size_t write(uint8_t data) override;
|
||||||
|
|
||||||
|
int available() override;
|
||||||
|
int read() override;
|
||||||
|
int peek() override;
|
||||||
|
void flush() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* STREAMSTRING_H_ */
|
@ -0,0 +1,135 @@
|
|||||||
|
#include <Arduino.h>
|
||||||
|
#include "esp32-hal-ledc.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
|
||||||
|
static TaskHandle_t _tone_task = NULL;
|
||||||
|
static QueueHandle_t _tone_queue = NULL;
|
||||||
|
static uint8_t _channel = 0;
|
||||||
|
|
||||||
|
typedef enum{
|
||||||
|
TONE_START,
|
||||||
|
TONE_END,
|
||||||
|
TONE_SET_CHANNEL
|
||||||
|
} tone_cmd_t;
|
||||||
|
|
||||||
|
typedef struct{
|
||||||
|
tone_cmd_t tone_cmd;
|
||||||
|
uint8_t pin;
|
||||||
|
unsigned int frequency;
|
||||||
|
unsigned long duration;
|
||||||
|
uint8_t channel;
|
||||||
|
} tone_msg_t;
|
||||||
|
|
||||||
|
static void tone_task(void*){
|
||||||
|
tone_msg_t tone_msg;
|
||||||
|
while(1){
|
||||||
|
xQueueReceive(_tone_queue, &tone_msg, portMAX_DELAY);
|
||||||
|
switch(tone_msg.tone_cmd){
|
||||||
|
case TONE_START:
|
||||||
|
log_d("Task received from queue TONE_START: _pin=%d, frequency=%u Hz, duration=%lu ms", tone_msg.pin, tone_msg.frequency, tone_msg.duration);
|
||||||
|
|
||||||
|
log_d("Setup LED controll on channel %d", _channel);
|
||||||
|
ledcAttachPin(tone_msg.pin, _channel);
|
||||||
|
ledcWriteTone(_channel, tone_msg.frequency);
|
||||||
|
|
||||||
|
if(tone_msg.duration){
|
||||||
|
delay(tone_msg.duration);
|
||||||
|
ledcDetachPin(tone_msg.pin);
|
||||||
|
ledcWriteTone(_channel, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TONE_END:
|
||||||
|
log_d("Task received from queue TONE_END: pin=%d", tone_msg.pin);
|
||||||
|
ledcDetachPin(tone_msg.pin);
|
||||||
|
ledcWriteTone(_channel, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TONE_SET_CHANNEL:
|
||||||
|
log_d("Task received from queue TONE_SET_CHANNEL: channel=%d", tone_msg.channel);
|
||||||
|
_channel = tone_msg.channel;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: ; // do nothing
|
||||||
|
} // switch
|
||||||
|
} // infinite loop
|
||||||
|
}
|
||||||
|
|
||||||
|
static int tone_init(){
|
||||||
|
if(_tone_queue == NULL){
|
||||||
|
log_v("Creating tone queue");
|
||||||
|
_tone_queue = xQueueCreate(128, sizeof(tone_msg_t));
|
||||||
|
if(_tone_queue == NULL){
|
||||||
|
log_e("Could not create tone queue");
|
||||||
|
return 0; // ERR
|
||||||
|
}
|
||||||
|
log_v("Tone queue created");
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_tone_task == NULL){
|
||||||
|
log_v("Creating tone task");
|
||||||
|
xTaskCreate(
|
||||||
|
tone_task, // Function to implement the task
|
||||||
|
"toneTask", // Name of the task
|
||||||
|
3500, // Stack size in words
|
||||||
|
NULL, // Task input parameter
|
||||||
|
1, // Priority of the task
|
||||||
|
&_tone_task // Task handle.
|
||||||
|
);
|
||||||
|
if(_tone_task == NULL){
|
||||||
|
log_e("Could not create tone task");
|
||||||
|
return 0; // ERR
|
||||||
|
}
|
||||||
|
log_v("Tone task created");
|
||||||
|
}
|
||||||
|
return 1; // OK
|
||||||
|
}
|
||||||
|
|
||||||
|
void setToneChannel(uint8_t channel){
|
||||||
|
log_d("channel=%d", channel);
|
||||||
|
if(tone_init()){
|
||||||
|
tone_msg_t tone_msg = {
|
||||||
|
.tone_cmd = TONE_SET_CHANNEL,
|
||||||
|
.pin = 0, // Ignored
|
||||||
|
.frequency = 0, // Ignored
|
||||||
|
.duration = 0, // Ignored
|
||||||
|
.channel = channel
|
||||||
|
};
|
||||||
|
xQueueSend(_tone_queue, &tone_msg, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void noTone(uint8_t _pin){
|
||||||
|
log_d("noTone was called");
|
||||||
|
if(tone_init()){
|
||||||
|
tone_msg_t tone_msg = {
|
||||||
|
.tone_cmd = TONE_END,
|
||||||
|
.pin = _pin,
|
||||||
|
.frequency = 0, // Ignored
|
||||||
|
.duration = 0, // Ignored
|
||||||
|
.channel = 0 // Ignored
|
||||||
|
};
|
||||||
|
xQueueSend(_tone_queue, &tone_msg, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// parameters:
|
||||||
|
// _pin - pin number which will output the signal
|
||||||
|
// frequency - PWM frequency in Hz
|
||||||
|
// duration - time in ms - how long will the signal be outputted.
|
||||||
|
// If not provided, or 0 you must manually call noTone to end output
|
||||||
|
void tone(uint8_t _pin, unsigned int frequency, unsigned long duration){
|
||||||
|
log_d("_pin=%d, frequency=%u Hz, duration=%lu ms", _pin, frequency, duration);
|
||||||
|
if(tone_init()){
|
||||||
|
tone_msg_t tone_msg = {
|
||||||
|
.tone_cmd = TONE_START,
|
||||||
|
.pin = _pin,
|
||||||
|
.frequency = frequency,
|
||||||
|
.duration = duration,
|
||||||
|
.channel = 0 // Ignored
|
||||||
|
};
|
||||||
|
xQueueSend(_tone_queue, &tone_msg, portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,365 @@
|
|||||||
|
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#include "USB.h"
|
||||||
|
|
||||||
|
#if CONFIG_TINYUSB_ENABLED
|
||||||
|
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "esp32-hal-tinyusb.h"
|
||||||
|
#include "common/tusb_common.h"
|
||||||
|
#include "StreamString.h"
|
||||||
|
|
||||||
|
#ifndef USB_VID
|
||||||
|
#define USB_VID USB_ESPRESSIF_VID
|
||||||
|
#endif
|
||||||
|
#ifndef USB_PID
|
||||||
|
#define USB_PID 0x0002
|
||||||
|
#endif
|
||||||
|
#ifndef USB_MANUFACTURER
|
||||||
|
#define USB_MANUFACTURER "Espressif Systems"
|
||||||
|
#endif
|
||||||
|
#ifndef USB_PRODUCT
|
||||||
|
#define USB_PRODUCT ARDUINO_BOARD
|
||||||
|
#endif
|
||||||
|
#ifndef USB_SERIAL
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#define USB_SERIAL "__MAC__"
|
||||||
|
#else
|
||||||
|
#define USB_SERIAL "0"
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#ifndef USB_WEBUSB_ENABLED
|
||||||
|
#define USB_WEBUSB_ENABLED false
|
||||||
|
#endif
|
||||||
|
#ifndef USB_WEBUSB_URL
|
||||||
|
#define USB_WEBUSB_URL "https://espressif.github.io/arduino-esp32/webusb.html"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CFG_TUD_DFU
|
||||||
|
__attribute__((weak, unused)) uint16_t load_dfu_ota_descriptor(uint8_t * dst, uint8_t * itf) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif /* CFG_TUD_DFU */
|
||||||
|
|
||||||
|
#if CFG_TUD_DFU_RUNTIME
|
||||||
|
__attribute__((unused)) static uint16_t load_dfu_descriptor(uint8_t * dst, uint8_t * itf)
|
||||||
|
{
|
||||||
|
#define DFU_ATTRS (DFU_ATTR_CAN_DOWNLOAD | DFU_ATTR_CAN_UPLOAD | DFU_ATTR_MANIFESTATION_TOLERANT)
|
||||||
|
|
||||||
|
uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB DFU_RT");
|
||||||
|
uint8_t descriptor[TUD_DFU_RT_DESC_LEN] = {
|
||||||
|
// Interface number, string index, attributes, detach timeout, transfer size */
|
||||||
|
TUD_DFU_RT_DESCRIPTOR(*itf, str_index, DFU_ATTRS, 700, 64)
|
||||||
|
};
|
||||||
|
*itf+=1;
|
||||||
|
memcpy(dst, descriptor, TUD_DFU_RT_DESC_LEN);
|
||||||
|
return TUD_DFU_RT_DESC_LEN;
|
||||||
|
}
|
||||||
|
// Invoked on DFU_DETACH request to reboot to the bootloader
|
||||||
|
void tud_dfu_runtime_reboot_to_dfu_cb(void)
|
||||||
|
{
|
||||||
|
usb_persist_restart(RESTART_BOOTLOADER_DFU);
|
||||||
|
}
|
||||||
|
#endif /* CFG_TUD_DFU_RUNTIME */
|
||||||
|
|
||||||
|
ESP_EVENT_DEFINE_BASE(ARDUINO_USB_EVENTS);
|
||||||
|
|
||||||
|
static esp_event_loop_handle_t arduino_usb_event_loop_handle = NULL;
|
||||||
|
|
||||||
|
esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait){
|
||||||
|
if(arduino_usb_event_loop_handle == NULL){
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return esp_event_post_to(arduino_usb_event_loop_handle, event_base, event_id, event_data, event_data_size, ticks_to_wait);
|
||||||
|
}
|
||||||
|
esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg){
|
||||||
|
if(arduino_usb_event_loop_handle == NULL){
|
||||||
|
return ESP_FAIL;
|
||||||
|
}
|
||||||
|
return esp_event_handler_register_with(arduino_usb_event_loop_handle, event_base, event_id, event_handler, event_handler_arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool tinyusb_device_mounted = false;
|
||||||
|
static bool tinyusb_device_suspended = false;
|
||||||
|
|
||||||
|
// Invoked when device is mounted (configured)
|
||||||
|
void tud_mount_cb(void){
|
||||||
|
tinyusb_device_mounted = true;
|
||||||
|
arduino_usb_event_data_t p;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_STARTED_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when device is unmounted
|
||||||
|
void tud_umount_cb(void){
|
||||||
|
tinyusb_device_mounted = false;
|
||||||
|
arduino_usb_event_data_t p;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_STOPPED_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when usb bus is suspended
|
||||||
|
// Within 7ms, device must draw an average of current less than 2.5 mA from bus
|
||||||
|
void tud_suspend_cb(bool remote_wakeup_en){
|
||||||
|
tinyusb_device_suspended = true;
|
||||||
|
arduino_usb_event_data_t p;
|
||||||
|
p.suspend.remote_wakeup_en = remote_wakeup_en;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_SUSPEND_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when usb bus is resumed
|
||||||
|
void tud_resume_cb(void){
|
||||||
|
tinyusb_device_suspended = false;
|
||||||
|
arduino_usb_event_data_t p;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_EVENTS, ARDUINO_USB_RESUME_EVENT, &p, sizeof(arduino_usb_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPUSB::ESPUSB(size_t task_stack_size, uint8_t event_task_priority)
|
||||||
|
:vid(USB_VID)
|
||||||
|
,pid(USB_PID)
|
||||||
|
,product_name(USB_PRODUCT)
|
||||||
|
,manufacturer_name(USB_MANUFACTURER)
|
||||||
|
,serial_number(USB_SERIAL)
|
||||||
|
,fw_version(0x0100)
|
||||||
|
,usb_version(0x0200)// at least 2.1 or 3.x for BOS & webUSB
|
||||||
|
,usb_class(TUSB_CLASS_MISC)
|
||||||
|
,usb_subclass(MISC_SUBCLASS_COMMON)
|
||||||
|
,usb_protocol(MISC_PROTOCOL_IAD)
|
||||||
|
,usb_attributes(TUSB_DESC_CONFIG_ATT_SELF_POWERED)
|
||||||
|
,usb_power_ma(500)
|
||||||
|
,webusb_enabled(USB_WEBUSB_ENABLED)
|
||||||
|
,webusb_url(USB_WEBUSB_URL)
|
||||||
|
,_started(false)
|
||||||
|
,_task_stack_size(task_stack_size)
|
||||||
|
,_event_task_priority(event_task_priority)
|
||||||
|
{
|
||||||
|
if (!arduino_usb_event_loop_handle) {
|
||||||
|
esp_event_loop_args_t event_task_args = {
|
||||||
|
.queue_size = 5,
|
||||||
|
.task_name = "arduino_usb_events",
|
||||||
|
.task_priority = _event_task_priority,
|
||||||
|
.task_stack_size = _task_stack_size,
|
||||||
|
.task_core_id = tskNO_AFFINITY
|
||||||
|
};
|
||||||
|
if (esp_event_loop_create(&event_task_args, &arduino_usb_event_loop_handle) != ESP_OK) {
|
||||||
|
log_e("esp_event_loop_create failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPUSB::~ESPUSB(){
|
||||||
|
if (arduino_usb_event_loop_handle) {
|
||||||
|
esp_event_loop_delete(arduino_usb_event_loop_handle);
|
||||||
|
arduino_usb_event_loop_handle = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::begin(){
|
||||||
|
if(!_started){
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
if(serial_number == "__MAC__"){
|
||||||
|
StreamString s;
|
||||||
|
uint8_t m[6];
|
||||||
|
esp_efuse_mac_get_default(m);
|
||||||
|
s.printf("%02X%02X%02X%02X%02X%02X", m[0], m[1], m[2], m[3], m[4], m[5]);
|
||||||
|
serial_number = s;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
tinyusb_device_config_t tinyusb_device_config = {
|
||||||
|
.vid = vid,
|
||||||
|
.pid = pid,
|
||||||
|
.product_name = product_name.c_str(),
|
||||||
|
.manufacturer_name = manufacturer_name.c_str(),
|
||||||
|
.serial_number = serial_number.c_str(),
|
||||||
|
.fw_version = fw_version,
|
||||||
|
.usb_version = usb_version,
|
||||||
|
.usb_class = usb_class,
|
||||||
|
.usb_subclass = usb_subclass,
|
||||||
|
.usb_protocol = usb_protocol,
|
||||||
|
.usb_attributes = usb_attributes,
|
||||||
|
.usb_power_ma = usb_power_ma,
|
||||||
|
.webusb_enabled = webusb_enabled,
|
||||||
|
.webusb_url = webusb_url.c_str()
|
||||||
|
};
|
||||||
|
_started = tinyusb_init(&tinyusb_device_config) == ESP_OK;
|
||||||
|
}
|
||||||
|
return _started;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ESPUSB::onEvent(esp_event_handler_t callback){
|
||||||
|
onEvent(ARDUINO_USB_ANY_EVENT, callback);
|
||||||
|
}
|
||||||
|
void ESPUSB::onEvent(arduino_usb_event_t event, esp_event_handler_t callback){
|
||||||
|
arduino_usb_event_handler_register_with(ARDUINO_USB_EVENTS, event, callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPUSB::operator bool() const
|
||||||
|
{
|
||||||
|
return _started && tinyusb_device_mounted;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::enableDFU(){
|
||||||
|
#if CFG_TUD_DFU
|
||||||
|
return tinyusb_enable_interface(USB_INTERFACE_DFU, TUD_DFU_DESC_LEN(1), load_dfu_ota_descriptor) == ESP_OK;
|
||||||
|
#elif CFG_TUD_DFU_RUNTIME
|
||||||
|
return tinyusb_enable_interface(USB_INTERFACE_DFU, TUD_DFU_RT_DESC_LEN, load_dfu_descriptor) == ESP_OK;
|
||||||
|
#endif /* CFG_TUD_DFU_RUNTIME */
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::VID(uint16_t v){
|
||||||
|
if(!_started){
|
||||||
|
vid = v;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint16_t ESPUSB::VID(void){
|
||||||
|
return vid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::PID(uint16_t p){
|
||||||
|
if(!_started){
|
||||||
|
pid = p;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint16_t ESPUSB::PID(void){
|
||||||
|
return pid;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::firmwareVersion(uint16_t version){
|
||||||
|
if(!_started){
|
||||||
|
fw_version = version;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint16_t ESPUSB::firmwareVersion(void){
|
||||||
|
return fw_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::usbVersion(uint16_t version){
|
||||||
|
if(!_started){
|
||||||
|
usb_version = version;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint16_t ESPUSB::usbVersion(void){
|
||||||
|
return usb_version;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::usbPower(uint16_t mA){
|
||||||
|
if(!_started){
|
||||||
|
usb_power_ma = mA;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint16_t ESPUSB::usbPower(void){
|
||||||
|
return usb_power_ma;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::usbClass(uint8_t _class){
|
||||||
|
if(!_started){
|
||||||
|
usb_class = _class;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint8_t ESPUSB::usbClass(void){
|
||||||
|
return usb_class;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::usbSubClass(uint8_t subClass){
|
||||||
|
if(!_started){
|
||||||
|
usb_subclass = subClass;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint8_t ESPUSB::usbSubClass(void){
|
||||||
|
return usb_subclass;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::usbProtocol(uint8_t protocol){
|
||||||
|
if(!_started){
|
||||||
|
usb_protocol = protocol;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint8_t ESPUSB::usbProtocol(void){
|
||||||
|
return usb_protocol;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::usbAttributes(uint8_t attr){
|
||||||
|
if(!_started){
|
||||||
|
usb_attributes = attr;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
uint8_t ESPUSB::usbAttributes(void){
|
||||||
|
return usb_attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::webUSB(bool enabled){
|
||||||
|
if(!_started){
|
||||||
|
webusb_enabled = enabled;
|
||||||
|
if(enabled && usb_version < 0x0210){
|
||||||
|
usb_version = 0x0210;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
bool ESPUSB::webUSB(void){
|
||||||
|
return webusb_enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::productName(const char * name){
|
||||||
|
if(!_started){
|
||||||
|
product_name = name;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
const char * ESPUSB::productName(void){
|
||||||
|
return product_name.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::manufacturerName(const char * name){
|
||||||
|
if(!_started){
|
||||||
|
manufacturer_name = name;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
const char * ESPUSB::manufacturerName(void){
|
||||||
|
return manufacturer_name.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::serialNumber(const char * name){
|
||||||
|
if(!_started){
|
||||||
|
serial_number = name;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
const char * ESPUSB::serialNumber(void){
|
||||||
|
return serial_number.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ESPUSB::webUSBURL(const char * name){
|
||||||
|
if(!_started){
|
||||||
|
webusb_url = name;
|
||||||
|
}
|
||||||
|
return !_started;
|
||||||
|
}
|
||||||
|
const char * ESPUSB::webUSBURL(void){
|
||||||
|
return webusb_url.c_str();
|
||||||
|
}
|
||||||
|
|
||||||
|
ESPUSB USB;
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_ENABLED */
|
@ -0,0 +1,119 @@
|
|||||||
|
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#if CONFIG_TINYUSB_ENABLED
|
||||||
|
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "USBCDC.h"
|
||||||
|
|
||||||
|
#define ARDUINO_USB_ON_BOOT (ARDUINO_USB_CDC_ON_BOOT|ARDUINO_USB_MSC_ON_BOOT|ARDUINO_USB_DFU_ON_BOOT)
|
||||||
|
|
||||||
|
ESP_EVENT_DECLARE_BASE(ARDUINO_USB_EVENTS);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARDUINO_USB_ANY_EVENT = ESP_EVENT_ANY_ID,
|
||||||
|
ARDUINO_USB_STARTED_EVENT = 0,
|
||||||
|
ARDUINO_USB_STOPPED_EVENT,
|
||||||
|
ARDUINO_USB_SUSPEND_EVENT,
|
||||||
|
ARDUINO_USB_RESUME_EVENT,
|
||||||
|
ARDUINO_USB_MAX_EVENT,
|
||||||
|
} arduino_usb_event_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
bool remote_wakeup_en;
|
||||||
|
} suspend;
|
||||||
|
} arduino_usb_event_data_t;
|
||||||
|
|
||||||
|
class ESPUSB {
|
||||||
|
public:
|
||||||
|
ESPUSB(size_t event_task_stack_size=2048, uint8_t event_task_priority=5);
|
||||||
|
~ESPUSB();
|
||||||
|
|
||||||
|
void onEvent(esp_event_handler_t callback);
|
||||||
|
void onEvent(arduino_usb_event_t event, esp_event_handler_t callback);
|
||||||
|
|
||||||
|
bool VID(uint16_t v);
|
||||||
|
uint16_t VID(void);
|
||||||
|
|
||||||
|
bool PID(uint16_t p);
|
||||||
|
uint16_t PID(void);
|
||||||
|
|
||||||
|
bool firmwareVersion(uint16_t version);
|
||||||
|
uint16_t firmwareVersion(void);
|
||||||
|
|
||||||
|
bool usbVersion(uint16_t version);
|
||||||
|
uint16_t usbVersion(void);
|
||||||
|
|
||||||
|
bool usbPower(uint16_t mA);
|
||||||
|
uint16_t usbPower(void);
|
||||||
|
|
||||||
|
bool usbClass(uint8_t _class);
|
||||||
|
uint8_t usbClass(void);
|
||||||
|
|
||||||
|
bool usbSubClass(uint8_t subClass);
|
||||||
|
uint8_t usbSubClass(void);
|
||||||
|
|
||||||
|
bool usbProtocol(uint8_t protocol);
|
||||||
|
uint8_t usbProtocol(void);
|
||||||
|
|
||||||
|
bool usbAttributes(uint8_t attr);
|
||||||
|
uint8_t usbAttributes(void);
|
||||||
|
|
||||||
|
bool webUSB(bool enabled);
|
||||||
|
bool webUSB(void);
|
||||||
|
|
||||||
|
bool productName(const char * name);
|
||||||
|
const char * productName(void);
|
||||||
|
|
||||||
|
bool manufacturerName(const char * name);
|
||||||
|
const char * manufacturerName(void);
|
||||||
|
|
||||||
|
bool serialNumber(const char * name);
|
||||||
|
const char * serialNumber(void);
|
||||||
|
|
||||||
|
bool webUSBURL(const char * name);
|
||||||
|
const char * webUSBURL(void);
|
||||||
|
|
||||||
|
bool enableDFU();
|
||||||
|
bool begin();
|
||||||
|
operator bool() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
uint16_t vid;
|
||||||
|
uint16_t pid;
|
||||||
|
String product_name;
|
||||||
|
String manufacturer_name;
|
||||||
|
String serial_number;
|
||||||
|
uint16_t fw_version;
|
||||||
|
uint16_t usb_version;
|
||||||
|
uint8_t usb_class;
|
||||||
|
uint8_t usb_subclass;
|
||||||
|
uint8_t usb_protocol;
|
||||||
|
uint8_t usb_attributes;
|
||||||
|
uint16_t usb_power_ma;
|
||||||
|
bool webusb_enabled;
|
||||||
|
String webusb_url;
|
||||||
|
|
||||||
|
bool _started;
|
||||||
|
size_t _task_stack_size;
|
||||||
|
uint8_t _event_task_priority;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern ESPUSB USB;
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_ENABLED */
|
@ -0,0 +1,453 @@
|
|||||||
|
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#include "USB.h"
|
||||||
|
#if CONFIG_TINYUSB_CDC_ENABLED
|
||||||
|
|
||||||
|
#include "USBCDC.h"
|
||||||
|
#include "esp32-hal-tinyusb.h"
|
||||||
|
|
||||||
|
ESP_EVENT_DEFINE_BASE(ARDUINO_USB_CDC_EVENTS);
|
||||||
|
esp_err_t arduino_usb_event_post(esp_event_base_t event_base, int32_t event_id, void *event_data, size_t event_data_size, TickType_t ticks_to_wait);
|
||||||
|
esp_err_t arduino_usb_event_handler_register_with(esp_event_base_t event_base, int32_t event_id, esp_event_handler_t event_handler, void *event_handler_arg);
|
||||||
|
|
||||||
|
#define MAX_USB_CDC_DEVICES 2
|
||||||
|
USBCDC * devices[MAX_USB_CDC_DEVICES] = {NULL, NULL};
|
||||||
|
|
||||||
|
static uint16_t load_cdc_descriptor(uint8_t * dst, uint8_t * itf)
|
||||||
|
{
|
||||||
|
uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB CDC");
|
||||||
|
uint8_t descriptor[TUD_CDC_DESC_LEN] = {
|
||||||
|
// Interface number, string index, EP notification address and size, EP data address (out, in) and size.
|
||||||
|
TUD_CDC_DESCRIPTOR(*itf, str_index, 0x85, 64, 0x03, 0x84, 64)
|
||||||
|
};
|
||||||
|
*itf+=2;
|
||||||
|
memcpy(dst, descriptor, TUD_CDC_DESC_LEN);
|
||||||
|
return TUD_CDC_DESC_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when line state DTR & RTS are changed via SET_CONTROL_LINE_STATE
|
||||||
|
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
|
||||||
|
{
|
||||||
|
if(itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL){
|
||||||
|
devices[itf]->_onLineState(dtr, rts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when line coding is change via SET_LINE_CODING
|
||||||
|
void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* p_line_coding)
|
||||||
|
{
|
||||||
|
if(itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL){
|
||||||
|
devices[itf]->_onLineCoding(p_line_coding->bit_rate, p_line_coding->stop_bits, p_line_coding->parity, p_line_coding->data_bits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received new data
|
||||||
|
void tud_cdc_rx_cb(uint8_t itf)
|
||||||
|
{
|
||||||
|
if(itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL){
|
||||||
|
devices[itf]->_onRX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received send break
|
||||||
|
void tud_cdc_send_break_cb(uint8_t itf, uint16_t duration_ms){
|
||||||
|
//log_v("itf: %u, duration_ms: %u", itf, duration_ms);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when space becomes available in TX buffer
|
||||||
|
void tud_cdc_tx_complete_cb(uint8_t itf){
|
||||||
|
if(itf < MAX_USB_CDC_DEVICES && devices[itf] != NULL){
|
||||||
|
devices[itf]->_onTX();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ARDUINO_ISR_ATTR cdc0_write_char(char c){
|
||||||
|
if(devices[0] != NULL){
|
||||||
|
devices[0]->write(c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void usb_unplugged_cb(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data){
|
||||||
|
((USBCDC*)arg)->_onUnplugged();
|
||||||
|
}
|
||||||
|
|
||||||
|
USBCDC::USBCDC(uint8_t itfn)
|
||||||
|
: itf(itfn)
|
||||||
|
, bit_rate(0)
|
||||||
|
, stop_bits(0)
|
||||||
|
, parity(0)
|
||||||
|
, data_bits(0)
|
||||||
|
, dtr(false)
|
||||||
|
, rts(false)
|
||||||
|
, connected(false)
|
||||||
|
, reboot_enable(true)
|
||||||
|
, rx_queue(NULL)
|
||||||
|
, tx_lock(NULL)
|
||||||
|
, tx_timeout_ms(250)
|
||||||
|
{
|
||||||
|
tinyusb_enable_interface(USB_INTERFACE_CDC, TUD_CDC_DESC_LEN, load_cdc_descriptor);
|
||||||
|
if(itf < MAX_USB_CDC_DEVICES){
|
||||||
|
arduino_usb_event_handler_register_with(ARDUINO_USB_EVENTS, ARDUINO_USB_STOPPED_EVENT, usb_unplugged_cb, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
USBCDC::~USBCDC(){
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::onEvent(esp_event_handler_t callback){
|
||||||
|
onEvent(ARDUINO_USB_CDC_ANY_EVENT, callback);
|
||||||
|
}
|
||||||
|
void USBCDC::onEvent(arduino_usb_cdc_event_t event, esp_event_handler_t callback){
|
||||||
|
arduino_usb_event_handler_register_with(ARDUINO_USB_CDC_EVENTS, event, callback, this);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t USBCDC::setRxBufferSize(size_t rx_queue_len){
|
||||||
|
size_t currentQueueSize = rx_queue ?
|
||||||
|
uxQueueSpacesAvailable(rx_queue) + uxQueueMessagesWaiting(rx_queue) : 0;
|
||||||
|
|
||||||
|
if (rx_queue_len != currentQueueSize) {
|
||||||
|
xQueueHandle new_rx_queue = NULL;
|
||||||
|
if (rx_queue_len) {
|
||||||
|
new_rx_queue = xQueueCreate(rx_queue_len, sizeof(uint8_t));
|
||||||
|
if(!new_rx_queue){
|
||||||
|
log_e("CDC Queue creation failed.");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (rx_queue) {
|
||||||
|
size_t copySize = uxQueueMessagesWaiting(rx_queue);
|
||||||
|
if (copySize > 0) {
|
||||||
|
for(size_t i = 0; i < copySize; i++) {
|
||||||
|
uint8_t ch = 0;
|
||||||
|
xQueueReceive(rx_queue, &ch, 0);
|
||||||
|
if (!xQueueSend(new_rx_queue, &ch, 0)) {
|
||||||
|
arduino_usb_cdc_event_data_t p;
|
||||||
|
p.rx_overflow.dropped_bytes = copySize - i;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_RX_OVERFLOW_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
log_e("CDC RX Overflow.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vQueueDelete(rx_queue);
|
||||||
|
}
|
||||||
|
rx_queue = new_rx_queue;
|
||||||
|
return rx_queue_len;
|
||||||
|
} else {
|
||||||
|
if (rx_queue) {
|
||||||
|
vQueueDelete(rx_queue);
|
||||||
|
rx_queue = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rx_queue_len;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::begin(unsigned long baud)
|
||||||
|
{
|
||||||
|
if(tx_lock == NULL) {
|
||||||
|
tx_lock = xSemaphoreCreateMutex();
|
||||||
|
}
|
||||||
|
// if rx_queue was set before begin(), keep it
|
||||||
|
if (!rx_queue) setRxBufferSize(256); //default if not preset
|
||||||
|
devices[itf] = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::end()
|
||||||
|
{
|
||||||
|
connected = false;
|
||||||
|
devices[itf] = NULL;
|
||||||
|
setRxBufferSize(0);
|
||||||
|
if(tx_lock != NULL) {
|
||||||
|
vSemaphoreDelete(tx_lock);
|
||||||
|
tx_lock = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::setTxTimeoutMs(uint32_t timeout){
|
||||||
|
tx_timeout_ms = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::_onUnplugged(void){
|
||||||
|
if(connected){
|
||||||
|
connected = false;
|
||||||
|
dtr = false;
|
||||||
|
rts = false;
|
||||||
|
arduino_usb_cdc_event_data_t p;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_DISCONNECTED_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum { CDC_LINE_IDLE, CDC_LINE_1, CDC_LINE_2, CDC_LINE_3 };
|
||||||
|
void USBCDC::_onLineState(bool _dtr, bool _rts){
|
||||||
|
static uint8_t lineState = CDC_LINE_IDLE;
|
||||||
|
|
||||||
|
if(dtr == _dtr && rts == _rts){
|
||||||
|
return; // Skip duplicate events
|
||||||
|
}
|
||||||
|
|
||||||
|
dtr = _dtr;
|
||||||
|
rts = _rts;
|
||||||
|
|
||||||
|
if(reboot_enable){
|
||||||
|
if(!dtr && rts){
|
||||||
|
if(lineState == CDC_LINE_IDLE){
|
||||||
|
lineState++;
|
||||||
|
if(connected){
|
||||||
|
connected = false;
|
||||||
|
arduino_usb_cdc_event_data_t p;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_DISCONNECTED_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
lineState = CDC_LINE_IDLE;
|
||||||
|
}
|
||||||
|
} else if(dtr && rts){
|
||||||
|
if(lineState == CDC_LINE_1){
|
||||||
|
lineState++;
|
||||||
|
} else {
|
||||||
|
lineState = CDC_LINE_IDLE;
|
||||||
|
}
|
||||||
|
} else if(dtr && !rts){
|
||||||
|
if(lineState == CDC_LINE_2){
|
||||||
|
lineState++;
|
||||||
|
} else {
|
||||||
|
lineState = CDC_LINE_IDLE;
|
||||||
|
}
|
||||||
|
} else if(!dtr && !rts){
|
||||||
|
if(lineState == CDC_LINE_3){
|
||||||
|
usb_persist_restart(RESTART_BOOTLOADER);
|
||||||
|
} else {
|
||||||
|
lineState = CDC_LINE_IDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(lineState == CDC_LINE_IDLE){
|
||||||
|
if(dtr && rts && !connected){
|
||||||
|
connected = true;
|
||||||
|
arduino_usb_cdc_event_data_t p;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_CONNECTED_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
} else if(!dtr && connected){
|
||||||
|
connected = false;
|
||||||
|
arduino_usb_cdc_event_data_t p;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_DISCONNECTED_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
arduino_usb_cdc_event_data_t l;
|
||||||
|
l.line_state.dtr = dtr;
|
||||||
|
l.line_state.rts = rts;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_LINE_STATE_EVENT, &l, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::_onLineCoding(uint32_t _bit_rate, uint8_t _stop_bits, uint8_t _parity, uint8_t _data_bits){
|
||||||
|
if(bit_rate != _bit_rate || data_bits != _data_bits || stop_bits != _stop_bits || parity != _parity){
|
||||||
|
// ArduinoIDE sends LineCoding with 1200bps baud to reset the device
|
||||||
|
if(reboot_enable && _bit_rate == 1200){
|
||||||
|
usb_persist_restart(RESTART_BOOTLOADER);
|
||||||
|
} else {
|
||||||
|
bit_rate = _bit_rate;
|
||||||
|
data_bits = _data_bits;
|
||||||
|
stop_bits = _stop_bits;
|
||||||
|
parity = _parity;
|
||||||
|
arduino_usb_cdc_event_data_t p;
|
||||||
|
p.line_coding.bit_rate = bit_rate;
|
||||||
|
p.line_coding.data_bits = data_bits;
|
||||||
|
p.line_coding.stop_bits = stop_bits;
|
||||||
|
p.line_coding.parity = parity;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_LINE_CODING_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::_onRX(){
|
||||||
|
arduino_usb_cdc_event_data_t p;
|
||||||
|
uint8_t buf[CONFIG_TINYUSB_CDC_RX_BUFSIZE+1];
|
||||||
|
uint32_t count = tud_cdc_n_read(itf, buf, CONFIG_TINYUSB_CDC_RX_BUFSIZE);
|
||||||
|
for(uint32_t i=0; i<count; i++){
|
||||||
|
if(rx_queue == NULL || !xQueueSend(rx_queue, buf+i, 10)) {
|
||||||
|
p.rx_overflow.dropped_bytes = count - i;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_RX_OVERFLOW_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
log_e("CDC RX Overflow.");
|
||||||
|
count = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (count) {
|
||||||
|
p.rx.len = count;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_RX_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::_onTX(){
|
||||||
|
arduino_usb_cdc_event_data_t p;
|
||||||
|
arduino_usb_event_post(ARDUINO_USB_CDC_EVENTS, ARDUINO_USB_CDC_TX_EVENT, &p, sizeof(arduino_usb_cdc_event_data_t), portMAX_DELAY);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::enableReboot(bool enable){
|
||||||
|
reboot_enable = enable;
|
||||||
|
}
|
||||||
|
bool USBCDC::rebootEnabled(void){
|
||||||
|
return reboot_enable;
|
||||||
|
}
|
||||||
|
|
||||||
|
int USBCDC::available(void)
|
||||||
|
{
|
||||||
|
if(itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
return uxQueueMessagesWaiting(rx_queue);
|
||||||
|
}
|
||||||
|
|
||||||
|
int USBCDC::peek(void)
|
||||||
|
{
|
||||||
|
if(itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c;
|
||||||
|
if(xQueuePeek(rx_queue, &c, 0)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int USBCDC::read(void)
|
||||||
|
{
|
||||||
|
if(itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c = 0;
|
||||||
|
if(xQueueReceive(rx_queue, &c, 0)) {
|
||||||
|
return c;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t USBCDC::read(uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
if(itf >= MAX_USB_CDC_DEVICES || rx_queue == NULL){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t c = 0;
|
||||||
|
size_t count = 0;
|
||||||
|
while(count < size && xQueueReceive(rx_queue, &c, 0)){
|
||||||
|
buffer[count++] = c;
|
||||||
|
}
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::flush(void)
|
||||||
|
{
|
||||||
|
if(itf >= MAX_USB_CDC_DEVICES || tx_lock == NULL || !tud_cdc_n_connected(itf)){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tud_cdc_n_write_flush(itf);
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
int USBCDC::availableForWrite(void)
|
||||||
|
{
|
||||||
|
if(itf >= MAX_USB_CDC_DEVICES || tx_lock == NULL || !tud_cdc_n_connected(itf)){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t a = tud_cdc_n_write_available(itf);
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t USBCDC::write(const uint8_t *buffer, size_t size)
|
||||||
|
{
|
||||||
|
if(itf >= MAX_USB_CDC_DEVICES || tx_lock == NULL || buffer == NULL || size == 0 || !tud_cdc_n_connected(itf)){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(xPortInIsrContext()){
|
||||||
|
BaseType_t taskWoken = false;
|
||||||
|
if(xSemaphoreTakeFromISR(tx_lock, &taskWoken) != pdPASS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if(xSemaphoreTake(tx_lock, tx_timeout_ms / portTICK_PERIOD_MS) != pdPASS){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t to_send = size, so_far = 0;
|
||||||
|
while(to_send){
|
||||||
|
if(!tud_cdc_n_connected(itf)){
|
||||||
|
size = so_far;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
size_t space = tud_cdc_n_write_available(itf);
|
||||||
|
if(!space){
|
||||||
|
tud_cdc_n_write_flush(itf);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(space > to_send){
|
||||||
|
space = to_send;
|
||||||
|
}
|
||||||
|
size_t sent = tud_cdc_n_write(itf, buffer+so_far, space);
|
||||||
|
if(sent){
|
||||||
|
so_far += sent;
|
||||||
|
to_send -= sent;
|
||||||
|
tud_cdc_n_write_flush(itf);
|
||||||
|
} else {
|
||||||
|
size = so_far;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(xPortInIsrContext()){
|
||||||
|
BaseType_t taskWoken = false;
|
||||||
|
xSemaphoreGiveFromISR(tx_lock, &taskWoken);
|
||||||
|
} else {
|
||||||
|
xSemaphoreGive(tx_lock);
|
||||||
|
}
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t USBCDC::write(uint8_t c)
|
||||||
|
{
|
||||||
|
return write(&c, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t USBCDC::baudRate()
|
||||||
|
{
|
||||||
|
return bit_rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBCDC::setDebugOutput(bool en)
|
||||||
|
{
|
||||||
|
if(en) {
|
||||||
|
uartSetDebug(NULL);
|
||||||
|
ets_install_putc1((void (*)(char)) &cdc0_write_char);
|
||||||
|
} else {
|
||||||
|
ets_install_putc1(NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
USBCDC::operator bool() const
|
||||||
|
{
|
||||||
|
if(itf >= MAX_USB_CDC_DEVICES){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return connected;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE //Serial used for USB CDC
|
||||||
|
USBCDC Serial(0);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_CDC_ENABLED */
|
@ -0,0 +1,145 @@
|
|||||||
|
// Copyright 2015-2020 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#if CONFIG_TINYUSB_CDC_ENABLED
|
||||||
|
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include "esp_event.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "Stream.h"
|
||||||
|
|
||||||
|
ESP_EVENT_DECLARE_BASE(ARDUINO_USB_CDC_EVENTS);
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ARDUINO_USB_CDC_ANY_EVENT = ESP_EVENT_ANY_ID,
|
||||||
|
ARDUINO_USB_CDC_CONNECTED_EVENT = 0,
|
||||||
|
ARDUINO_USB_CDC_DISCONNECTED_EVENT,
|
||||||
|
ARDUINO_USB_CDC_LINE_STATE_EVENT,
|
||||||
|
ARDUINO_USB_CDC_LINE_CODING_EVENT,
|
||||||
|
ARDUINO_USB_CDC_RX_EVENT,
|
||||||
|
ARDUINO_USB_CDC_TX_EVENT,
|
||||||
|
ARDUINO_USB_CDC_RX_OVERFLOW_EVENT,
|
||||||
|
ARDUINO_USB_CDC_MAX_EVENT,
|
||||||
|
} arduino_usb_cdc_event_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
bool dtr;
|
||||||
|
bool rts;
|
||||||
|
} line_state;
|
||||||
|
struct {
|
||||||
|
uint32_t bit_rate;
|
||||||
|
uint8_t stop_bits; ///< 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits
|
||||||
|
uint8_t parity; ///< 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space
|
||||||
|
uint8_t data_bits; ///< can be 5, 6, 7, 8 or 16
|
||||||
|
} line_coding;
|
||||||
|
struct {
|
||||||
|
size_t len;
|
||||||
|
} rx;
|
||||||
|
struct {
|
||||||
|
size_t dropped_bytes;
|
||||||
|
} rx_overflow;
|
||||||
|
} arduino_usb_cdc_event_data_t;
|
||||||
|
|
||||||
|
class USBCDC: public Stream
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
USBCDC(uint8_t itf=0);
|
||||||
|
~USBCDC();
|
||||||
|
|
||||||
|
void onEvent(esp_event_handler_t callback);
|
||||||
|
void onEvent(arduino_usb_cdc_event_t event, esp_event_handler_t callback);
|
||||||
|
|
||||||
|
size_t setRxBufferSize(size_t size);
|
||||||
|
void setTxTimeoutMs(uint32_t timeout);
|
||||||
|
void begin(unsigned long baud=0);
|
||||||
|
void end();
|
||||||
|
|
||||||
|
int available(void);
|
||||||
|
int availableForWrite(void);
|
||||||
|
int peek(void);
|
||||||
|
int read(void);
|
||||||
|
size_t read(uint8_t *buffer, size_t size);
|
||||||
|
size_t write(uint8_t);
|
||||||
|
size_t write(const uint8_t *buffer, size_t size);
|
||||||
|
void flush(void);
|
||||||
|
|
||||||
|
inline size_t read(char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return read((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
inline size_t write(const char * buffer, size_t size)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) buffer, size);
|
||||||
|
}
|
||||||
|
inline size_t write(const char * s)
|
||||||
|
{
|
||||||
|
return write((uint8_t*) s, strlen(s));
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(long n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(unsigned int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
inline size_t write(int n)
|
||||||
|
{
|
||||||
|
return write((uint8_t) n);
|
||||||
|
}
|
||||||
|
uint32_t baudRate();
|
||||||
|
void setDebugOutput(bool);
|
||||||
|
operator bool() const;
|
||||||
|
|
||||||
|
void enableReboot(bool enable);
|
||||||
|
bool rebootEnabled(void);
|
||||||
|
|
||||||
|
//internal methods
|
||||||
|
void _onDFU(void);
|
||||||
|
void _onLineState(bool _dtr, bool _rts);
|
||||||
|
void _onLineCoding(uint32_t _bit_rate, uint8_t _stop_bits, uint8_t _parity, uint8_t _data_bits);
|
||||||
|
void _onRX(void);
|
||||||
|
void _onTX(void);
|
||||||
|
void _onUnplugged(void);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
uint8_t itf;
|
||||||
|
uint32_t bit_rate;
|
||||||
|
uint8_t stop_bits; ///< 0: 1 stop bit - 1: 1.5 stop bits - 2: 2 stop bits
|
||||||
|
uint8_t parity; ///< 0: None - 1: Odd - 2: Even - 3: Mark - 4: Space
|
||||||
|
uint8_t data_bits; ///< can be 5, 6, 7, 8 or 16
|
||||||
|
bool dtr;
|
||||||
|
bool rts;
|
||||||
|
bool connected;
|
||||||
|
bool reboot_enable;
|
||||||
|
xQueueHandle rx_queue;
|
||||||
|
xSemaphoreHandle tx_lock;
|
||||||
|
uint32_t tx_timeout_ms;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#if ARDUINO_USB_CDC_ON_BOOT && !ARDUINO_USB_MODE //Serial used for USB CDC
|
||||||
|
extern USBCDC Serial;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_CDC_ENABLED */
|
@ -0,0 +1,260 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
#include "USBMSC.h"
|
||||||
|
|
||||||
|
#if CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
|
||||||
|
#include "esp32-hal-tinyusb.h"
|
||||||
|
|
||||||
|
extern "C" uint16_t tusb_msc_load_descriptor(uint8_t * dst, uint8_t * itf)
|
||||||
|
{
|
||||||
|
uint8_t str_index = tinyusb_add_string_descriptor("TinyUSB MSC");
|
||||||
|
uint8_t ep_num = tinyusb_get_free_duplex_endpoint();
|
||||||
|
TU_VERIFY (ep_num != 0);
|
||||||
|
uint8_t descriptor[TUD_MSC_DESC_LEN] = {
|
||||||
|
// Interface number, string index, EP Out & EP In address, EP size
|
||||||
|
TUD_MSC_DESCRIPTOR(*itf, str_index, ep_num, (uint8_t)(0x80 | ep_num), 64)
|
||||||
|
};
|
||||||
|
*itf+=1;
|
||||||
|
memcpy(dst, descriptor, TUD_MSC_DESC_LEN);
|
||||||
|
return TUD_MSC_DESC_LEN;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
bool media_present;
|
||||||
|
uint8_t vendor_id[8];
|
||||||
|
uint8_t product_id[16];
|
||||||
|
uint8_t product_rev[4];
|
||||||
|
uint16_t block_size;
|
||||||
|
uint32_t block_count;
|
||||||
|
bool (*start_stop)(uint8_t power_condition, bool start, bool load_eject);
|
||||||
|
int32_t (*read)(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
|
||||||
|
int32_t (*write)(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize);
|
||||||
|
} msc_lun_t;
|
||||||
|
|
||||||
|
static const uint8_t MSC_MAX_LUN = 3;
|
||||||
|
static uint8_t MSC_ACTIVE_LUN = 0;
|
||||||
|
static msc_lun_t msc_luns[MSC_MAX_LUN];
|
||||||
|
|
||||||
|
static void cplstr(void *dst, const void * src, size_t max_len){
|
||||||
|
if(!src || !dst || !max_len){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
size_t l = strlen((const char *)src);
|
||||||
|
if(l > max_len){
|
||||||
|
l = max_len;
|
||||||
|
}
|
||||||
|
memcpy(dst, src, l);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received GET_MAX_LUN request, required for multiple LUNs implementation
|
||||||
|
uint8_t tud_msc_get_maxlun_cb(void)
|
||||||
|
{
|
||||||
|
log_v("%u", MSC_ACTIVE_LUN);
|
||||||
|
return MSC_ACTIVE_LUN;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received SCSI_CMD_INQUIRY
|
||||||
|
// Application fill vendor id, product id and revision with string up to 8, 16, 4 characters respectively
|
||||||
|
void tud_msc_inquiry_cb(uint8_t lun, uint8_t vendor_id[8], uint8_t product_id[16], uint8_t product_rev[4])
|
||||||
|
{
|
||||||
|
log_v("[%u]", lun);
|
||||||
|
cplstr(vendor_id , msc_luns[lun].vendor_id, 8);
|
||||||
|
cplstr(product_id , msc_luns[lun].product_id, 16);
|
||||||
|
cplstr(product_rev, msc_luns[lun].product_rev, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received Test Unit Ready command.
|
||||||
|
// return true allowing host to read/write this LUN e.g SD card inserted
|
||||||
|
bool tud_msc_test_unit_ready_cb(uint8_t lun)
|
||||||
|
{
|
||||||
|
log_v("[%u]: %u", lun, msc_luns[lun].media_present);
|
||||||
|
return msc_luns[lun].media_present; // RAM disk is always ready
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received SCSI_CMD_READ_CAPACITY_10 and SCSI_CMD_READ_FORMAT_CAPACITY to determine the disk size
|
||||||
|
// Application update block count and block size
|
||||||
|
void tud_msc_capacity_cb(uint8_t lun, uint32_t* block_count, uint16_t* block_size)
|
||||||
|
{
|
||||||
|
log_v("[%u]", lun);
|
||||||
|
if(!msc_luns[lun].media_present){
|
||||||
|
*block_count = 0;
|
||||||
|
*block_size = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
*block_count = msc_luns[lun].block_count;
|
||||||
|
*block_size = msc_luns[lun].block_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoked when received Start Stop Unit command
|
||||||
|
// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
|
||||||
|
// - Start = 1 : active mode, if load_eject = 1 : load disk storage
|
||||||
|
bool tud_msc_start_stop_cb(uint8_t lun, uint8_t power_condition, bool start, bool load_eject)
|
||||||
|
{
|
||||||
|
log_v("[%u] power: %u, start: %u, eject: %u", lun, power_condition, start, load_eject);
|
||||||
|
if(msc_luns[lun].start_stop){
|
||||||
|
return msc_luns[lun].start_stop(power_condition, start, load_eject);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback invoked when received READ10 command.
|
||||||
|
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
|
||||||
|
int32_t tud_msc_read10_cb(uint8_t lun, uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize)
|
||||||
|
{
|
||||||
|
log_v("[%u], lba: %u, offset: %u, bufsize: %u", lun, lba, offset, bufsize);
|
||||||
|
if(!msc_luns[lun].media_present){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(msc_luns[lun].read){
|
||||||
|
return msc_luns[lun].read(lba, offset, buffer, bufsize);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback invoked when received WRITE10 command.
|
||||||
|
// Process data in buffer to disk's storage and return number of written bytes
|
||||||
|
int32_t tud_msc_write10_cb(uint8_t lun, uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize)
|
||||||
|
{
|
||||||
|
log_v("[%u], lba: %u, offset: %u, bufsize: %u", lun, lba, offset, bufsize);
|
||||||
|
if(!msc_luns[lun].media_present){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if(msc_luns[lun].write){
|
||||||
|
return msc_luns[lun].write(lba, offset, buffer, bufsize);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback invoked when received an SCSI command not in built-in list below
|
||||||
|
// - READ_CAPACITY10, READ_FORMAT_CAPACITY, INQUIRY, MODE_SENSE6, REQUEST_SENSE
|
||||||
|
// - READ10 and WRITE10 has their own callbacks
|
||||||
|
int32_t tud_msc_scsi_cb (uint8_t lun, uint8_t const scsi_cmd[16], void* buffer, uint16_t bufsize)
|
||||||
|
{
|
||||||
|
// read10 & write10 has their own callback and MUST not be handled here
|
||||||
|
log_v("[%u] cmd: %u, bufsize: %u", lun, scsi_cmd[0], bufsize);
|
||||||
|
|
||||||
|
void const* response = NULL;
|
||||||
|
uint16_t resplen = 0;
|
||||||
|
|
||||||
|
// most scsi handled is input
|
||||||
|
bool in_xfer = true;
|
||||||
|
|
||||||
|
if(!msc_luns[lun].media_present){
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (scsi_cmd[0]) {
|
||||||
|
case SCSI_CMD_PREVENT_ALLOW_MEDIUM_REMOVAL:
|
||||||
|
// Host is about to read/write etc ... better not to disconnect disk
|
||||||
|
resplen = 0;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
// Set Sense = Invalid Command Operation
|
||||||
|
tud_msc_set_sense(lun, SCSI_SENSE_ILLEGAL_REQUEST, 0x20, 0x00);
|
||||||
|
|
||||||
|
// negative means error -> tinyusb could stall and/or response with failed status
|
||||||
|
resplen = -1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// return resplen must not larger than bufsize
|
||||||
|
if (resplen > bufsize) resplen = bufsize;
|
||||||
|
|
||||||
|
if (response && (resplen > 0)) {
|
||||||
|
if (in_xfer) {
|
||||||
|
memcpy(buffer, response, resplen);
|
||||||
|
} else {
|
||||||
|
// SCSI output
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resplen;
|
||||||
|
}
|
||||||
|
|
||||||
|
USBMSC::USBMSC(){
|
||||||
|
if(MSC_ACTIVE_LUN < MSC_MAX_LUN){
|
||||||
|
_lun = MSC_ACTIVE_LUN;
|
||||||
|
MSC_ACTIVE_LUN++;
|
||||||
|
msc_luns[_lun].media_present = false;
|
||||||
|
msc_luns[_lun].vendor_id[0] = 0;
|
||||||
|
msc_luns[_lun].product_id[0] = 0;
|
||||||
|
msc_luns[_lun].product_rev[0] = 0;
|
||||||
|
msc_luns[_lun].block_size = 0;
|
||||||
|
msc_luns[_lun].block_count = 0;
|
||||||
|
msc_luns[_lun].start_stop = NULL;
|
||||||
|
msc_luns[_lun].read = NULL;
|
||||||
|
msc_luns[_lun].write = NULL;
|
||||||
|
}
|
||||||
|
if(_lun == 0){
|
||||||
|
tinyusb_enable_interface(USB_INTERFACE_MSC, TUD_MSC_DESC_LEN, tusb_msc_load_descriptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
USBMSC::~USBMSC(){
|
||||||
|
end();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool USBMSC::begin(uint32_t block_count, uint16_t block_size){
|
||||||
|
msc_luns[_lun].block_size = block_size;
|
||||||
|
msc_luns[_lun].block_count = block_count;
|
||||||
|
if(!msc_luns[_lun].block_size || !msc_luns[_lun].block_count || !msc_luns[_lun].read || !msc_luns[_lun].write){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBMSC::end(){
|
||||||
|
msc_luns[_lun].media_present = false;
|
||||||
|
msc_luns[_lun].vendor_id[0] = 0;
|
||||||
|
msc_luns[_lun].product_id[0] = 0;
|
||||||
|
msc_luns[_lun].product_rev[0] = 0;
|
||||||
|
msc_luns[_lun].block_size = 0;
|
||||||
|
msc_luns[_lun].block_count = 0;
|
||||||
|
msc_luns[_lun].start_stop = NULL;
|
||||||
|
msc_luns[_lun].read = NULL;
|
||||||
|
msc_luns[_lun].write = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBMSC::vendorID(const char * vid){
|
||||||
|
cplstr(msc_luns[_lun].vendor_id, vid, 8);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBMSC::productID(const char * pid){
|
||||||
|
cplstr(msc_luns[_lun].product_id, pid, 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBMSC::productRevision(const char * rev){
|
||||||
|
cplstr(msc_luns[_lun].product_rev, rev, 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBMSC::onStartStop(msc_start_stop_cb cb){
|
||||||
|
msc_luns[_lun].start_stop = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBMSC::onRead(msc_read_cb cb){
|
||||||
|
msc_luns[_lun].read = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBMSC::onWrite(msc_write_cb cb){
|
||||||
|
msc_luns[_lun].write = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void USBMSC::mediaPresent(bool media_present){
|
||||||
|
msc_luns[_lun].media_present = media_present;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_MSC_ENABLED */
|
@ -0,0 +1,51 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
|
||||||
|
#if CONFIG_TINYUSB_MSC_ENABLED
|
||||||
|
|
||||||
|
// Invoked when received Start Stop Unit command
|
||||||
|
// - Start = 0 : stopped power mode, if load_eject = 1 : unload disk storage
|
||||||
|
// - Start = 1 : active mode, if load_eject = 1 : load disk storage
|
||||||
|
typedef bool (*msc_start_stop_cb)(uint8_t power_condition, bool start, bool load_eject);
|
||||||
|
|
||||||
|
// Copy disk's data to buffer (up to bufsize) and return number of copied bytes.
|
||||||
|
typedef int32_t (*msc_read_cb)(uint32_t lba, uint32_t offset, void* buffer, uint32_t bufsize);
|
||||||
|
|
||||||
|
// Process data in buffer to disk's storage and return number of written bytes
|
||||||
|
typedef int32_t (*msc_write_cb)(uint32_t lba, uint32_t offset, uint8_t* buffer, uint32_t bufsize);
|
||||||
|
|
||||||
|
class USBMSC
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
USBMSC();
|
||||||
|
~USBMSC();
|
||||||
|
bool begin(uint32_t block_count, uint16_t block_size);
|
||||||
|
void end();
|
||||||
|
void vendorID(const char * vid);//max 8 chars
|
||||||
|
void productID(const char * pid);//max 16 chars
|
||||||
|
void productRevision(const char * ver);//max 4 chars
|
||||||
|
void mediaPresent(bool media_present);
|
||||||
|
void onStartStop(msc_start_stop_cb cb);
|
||||||
|
void onRead(msc_read_cb cb);
|
||||||
|
void onWrite(msc_write_cb cb);
|
||||||
|
private:
|
||||||
|
uint8_t _lun;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif /* CONFIG_TINYUSB_MSC_ENABLED */
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Udp.cpp: Library to send/receive UDP packets.
|
||||||
|
*
|
||||||
|
* NOTE: UDP is fast, but has some important limitations (thanks to Warren Gray for mentioning these)
|
||||||
|
* 1) UDP does not guarantee the order in which assembled UDP packets are received. This
|
||||||
|
* might not happen often in practice, but in larger network topologies, a UDP
|
||||||
|
* packet can be received out of sequence.
|
||||||
|
* 2) UDP does not guard against lost packets - so packets *can* disappear without the sender being
|
||||||
|
* aware of it. Again, this may not be a concern in practice on small local networks.
|
||||||
|
* For more information, see http://www.cafeaulait.org/course/week12/35.html
|
||||||
|
*
|
||||||
|
* MIT License:
|
||||||
|
* Copyright (c) 2008 Bjoern Hartmann
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* bjoern@cs.stanford.edu 12/30/2008
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef udp_h
|
||||||
|
#define udp_h
|
||||||
|
|
||||||
|
#include <Stream.h>
|
||||||
|
#include <IPAddress.h>
|
||||||
|
|
||||||
|
class UDP: public Stream
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
virtual uint8_t begin(uint16_t) =0; // initialize, start listening on specified port. Returns 1 if successful, 0 if there are no sockets available to use
|
||||||
|
virtual uint8_t beginMulticast(IPAddress, uint16_t) { return 0; } // initialize, start listening on specified multicast IP address and port. Returns 1 if successful, 0 on failure
|
||||||
|
virtual void stop() =0; // Finish with the UDP socket
|
||||||
|
|
||||||
|
// Sending UDP packets
|
||||||
|
|
||||||
|
// Start building up a packet to send to the remote host specific in ip and port
|
||||||
|
// Returns 1 if successful, 0 if there was a problem with the supplied IP address or port
|
||||||
|
virtual int beginPacket(IPAddress ip, uint16_t port) =0;
|
||||||
|
// Start building up a packet to send to the remote host specific in host and port
|
||||||
|
// Returns 1 if successful, 0 if there was a problem resolving the hostname or port
|
||||||
|
virtual int beginPacket(const char *host, uint16_t port) =0;
|
||||||
|
// Finish off this packet and send it
|
||||||
|
// Returns 1 if the packet was sent successfully, 0 if there was an error
|
||||||
|
virtual int endPacket() =0;
|
||||||
|
// Write a single byte into the packet
|
||||||
|
virtual size_t write(uint8_t) =0;
|
||||||
|
// Write size bytes from buffer into the packet
|
||||||
|
virtual size_t write(const uint8_t *buffer, size_t size) =0;
|
||||||
|
|
||||||
|
// Start processing the next available incoming packet
|
||||||
|
// Returns the size of the packet in bytes, or 0 if no packets are available
|
||||||
|
virtual int parsePacket() =0;
|
||||||
|
// Number of bytes remaining in the current packet
|
||||||
|
virtual int available() =0;
|
||||||
|
// Read a single byte from the current packet
|
||||||
|
virtual int read() =0;
|
||||||
|
// Read up to len bytes from the current packet and place them into buffer
|
||||||
|
// Returns the number of bytes read, or 0 if none are available
|
||||||
|
virtual int read(unsigned char* buffer, size_t len) =0;
|
||||||
|
// Read up to len characters from the current packet and place them into buffer
|
||||||
|
// Returns the number of characters read, or 0 if none are available
|
||||||
|
virtual int read(char* buffer, size_t len) =0;
|
||||||
|
// Return the next byte from the current packet without moving on to the next byte
|
||||||
|
virtual int peek() =0;
|
||||||
|
virtual void flush() =0; // Finish reading the current packet
|
||||||
|
|
||||||
|
// Return the IP address of the host who sent the current incoming packet
|
||||||
|
virtual IPAddress remoteIP() =0;
|
||||||
|
// Return the port of the host who sent the current incoming packet
|
||||||
|
virtual uint16_t remotePort() =0;
|
||||||
|
protected:
|
||||||
|
uint8_t* rawIPAddress(IPAddress& addr)
|
||||||
|
{
|
||||||
|
return addr.raw_address();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,154 @@
|
|||||||
|
/*
|
||||||
|
WCharacter.h - Character utility functions for Wiring & Arduino
|
||||||
|
Copyright (c) 2010 Hernando Barragan. All right 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 Character_h
|
||||||
|
#define Character_h
|
||||||
|
|
||||||
|
#include <ctype.h>
|
||||||
|
#define isascii(__c) ((unsigned)(__c)<=0177)
|
||||||
|
#define toascii(__c) ((__c)&0177)
|
||||||
|
|
||||||
|
// WCharacter.h prototypes
|
||||||
|
inline boolean isAlphaNumeric(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isAlpha(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isAscii(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isWhitespace(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isControl(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isDigit(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isGraph(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isLowerCase(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isPrintable(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isPunct(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isSpace(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isUpperCase(int c) __attribute__((always_inline));
|
||||||
|
inline boolean isHexadecimalDigit(int c) __attribute__((always_inline));
|
||||||
|
inline int toAscii(int c) __attribute__((always_inline));
|
||||||
|
inline int toLowerCase(int c) __attribute__((always_inline));
|
||||||
|
inline int toUpperCase(int c) __attribute__((always_inline));
|
||||||
|
|
||||||
|
// Checks for an alphanumeric character.
|
||||||
|
// It is equivalent to (isalpha(c) || isdigit(c)).
|
||||||
|
inline boolean isAlphaNumeric(int c)
|
||||||
|
{
|
||||||
|
return (isalnum(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for an alphabetic character.
|
||||||
|
// It is equivalent to (isupper(c) || islower(c)).
|
||||||
|
inline boolean isAlpha(int c)
|
||||||
|
{
|
||||||
|
return (isalpha(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks whether c is a 7-bit unsigned char value
|
||||||
|
// that fits into the ASCII character set.
|
||||||
|
inline boolean isAscii(int c)
|
||||||
|
{
|
||||||
|
return ( isascii (c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for a blank character, that is, a space or a tab.
|
||||||
|
inline boolean isWhitespace(int c)
|
||||||
|
{
|
||||||
|
return (isblank(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for a control character.
|
||||||
|
inline boolean isControl(int c)
|
||||||
|
{
|
||||||
|
return (iscntrl(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for a digit (0 through 9).
|
||||||
|
inline boolean isDigit(int c)
|
||||||
|
{
|
||||||
|
return (isdigit(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for any printable character except space.
|
||||||
|
inline boolean isGraph(int c)
|
||||||
|
{
|
||||||
|
return (isgraph(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for a lower-case character.
|
||||||
|
inline boolean isLowerCase(int c)
|
||||||
|
{
|
||||||
|
return (islower(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for any printable character including space.
|
||||||
|
inline boolean isPrintable(int c)
|
||||||
|
{
|
||||||
|
return (isprint(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for any printable character which is not a space
|
||||||
|
// or an alphanumeric character.
|
||||||
|
inline boolean isPunct(int c)
|
||||||
|
{
|
||||||
|
return (ispunct(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for white-space characters. For the avr-libc library,
|
||||||
|
// these are: space, formfeed ('\f'), newline ('\n'), carriage
|
||||||
|
// return ('\r'), horizontal tab ('\t'), and vertical tab ('\v').
|
||||||
|
inline boolean isSpace(int c)
|
||||||
|
{
|
||||||
|
return (isspace(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for an uppercase letter.
|
||||||
|
inline boolean isUpperCase(int c)
|
||||||
|
{
|
||||||
|
return (isupper(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Checks for a hexadecimal digits, i.e. one of 0 1 2 3 4 5 6 7
|
||||||
|
// 8 9 a b c d e f A B C D E F.
|
||||||
|
inline boolean isHexadecimalDigit(int c)
|
||||||
|
{
|
||||||
|
return (isxdigit(c) == 0 ? false : true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts c to a 7-bit unsigned char value that fits into the
|
||||||
|
// ASCII character set, by clearing the high-order bits.
|
||||||
|
inline int toAscii(int c)
|
||||||
|
{
|
||||||
|
return toascii(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning:
|
||||||
|
// Many people will be unhappy if you use this function.
|
||||||
|
// This function will convert accented letters into random
|
||||||
|
// characters.
|
||||||
|
|
||||||
|
// Converts the letter c to lower case, if possible.
|
||||||
|
inline int toLowerCase(int c)
|
||||||
|
{
|
||||||
|
return tolower(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Converts the letter c to upper case, if possible.
|
||||||
|
inline int toUpperCase(int c)
|
||||||
|
{
|
||||||
|
return toupper(c);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,94 @@
|
|||||||
|
/* -*- mode: jde; c-basic-offset: 2; indent-tabs-mode: nil -*- */
|
||||||
|
|
||||||
|
/*
|
||||||
|
Part of the Wiring project - http://wiring.org.co
|
||||||
|
Copyright (c) 2004-06 Hernando Barragan
|
||||||
|
Modified 13 August 2006, David A. Mellis for Arduino - http://www.arduino.cc/
|
||||||
|
|
||||||
|
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., 59 Temple Place, Suite 330,
|
||||||
|
Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
|
$Id$
|
||||||
|
*/
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include "esp_system.h"
|
||||||
|
}
|
||||||
|
#include "esp32-hal-log.h"
|
||||||
|
|
||||||
|
// Allows the user to choose between Real Hardware
|
||||||
|
// or Software Pseudo random generators for the
|
||||||
|
// Arduino random() functions
|
||||||
|
static bool s_useRandomHW = true;
|
||||||
|
void useRealRandomGenerator(bool useRandomHW) {
|
||||||
|
s_useRandomHW = useRandomHW;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calling randomSeed() will force the
|
||||||
|
// Pseudo Random generator like in
|
||||||
|
// Arduino mainstream API
|
||||||
|
void randomSeed(unsigned long seed)
|
||||||
|
{
|
||||||
|
if(seed != 0) {
|
||||||
|
srand(seed);
|
||||||
|
s_useRandomHW = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
long random( long howsmall, long howbig );
|
||||||
|
long random( long howbig )
|
||||||
|
{
|
||||||
|
if ( howbig == 0 )
|
||||||
|
{
|
||||||
|
return 0 ;
|
||||||
|
}
|
||||||
|
if (howbig < 0) {
|
||||||
|
return (random(0, -howbig));
|
||||||
|
}
|
||||||
|
// if randomSeed was called, fall back to software PRNG
|
||||||
|
uint32_t val = (s_useRandomHW) ? esp_random() : rand();
|
||||||
|
return val % howbig;
|
||||||
|
}
|
||||||
|
|
||||||
|
long random(long howsmall, long howbig)
|
||||||
|
{
|
||||||
|
if(howsmall >= howbig) {
|
||||||
|
return howsmall;
|
||||||
|
}
|
||||||
|
long diff = howbig - howsmall;
|
||||||
|
return random(diff) + howsmall;
|
||||||
|
}
|
||||||
|
|
||||||
|
long map(long x, long in_min, long in_max, long out_min, long out_max) {
|
||||||
|
const long run = in_max - in_min;
|
||||||
|
if(run == 0){
|
||||||
|
log_e("map(): Invalid input range, min == max");
|
||||||
|
return -1; // AVR returns -1, SAM returns 0
|
||||||
|
}
|
||||||
|
const long rise = out_max - out_min;
|
||||||
|
const long delta = x - in_min;
|
||||||
|
return (delta * rise) / run + out_min;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t makeWord(uint16_t w)
|
||||||
|
{
|
||||||
|
return w;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t makeWord(uint8_t h, uint8_t l)
|
||||||
|
{
|
||||||
|
return (h << 8) | l;
|
||||||
|
}
|
@ -0,0 +1,861 @@
|
|||||||
|
/*
|
||||||
|
WString.cpp - String library for Wiring & Arduino
|
||||||
|
...mostly rewritten by Paul Stoffregen...
|
||||||
|
Copyright (c) 2009-10 Hernando Barragan. All rights reserved.
|
||||||
|
Copyright 2011, Paul Stoffregen, paul@pjrc.com
|
||||||
|
Modified by Ivan Grokhotkov, 2014 - esp8266 support
|
||||||
|
Modified by Michael C. Miller, 2015 - esp8266 progmem support
|
||||||
|
|
||||||
|
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 "Arduino.h"
|
||||||
|
#include "WString.h"
|
||||||
|
#include "stdlib_noniso.h"
|
||||||
|
#include "esp32-hal-log.h"
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Constructors */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
String::String(const char *cstr) {
|
||||||
|
init();
|
||||||
|
if (cstr)
|
||||||
|
copy(cstr, strlen(cstr));
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(const char *cstr, unsigned int length) {
|
||||||
|
init();
|
||||||
|
if (cstr)
|
||||||
|
copy(cstr, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(const String &value) {
|
||||||
|
init();
|
||||||
|
*this = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
||||||
|
String::String(String &&rval) {
|
||||||
|
init();
|
||||||
|
move(rval);
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(StringSumHelper &&rval) {
|
||||||
|
init();
|
||||||
|
move(rval);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
String::String(char c) {
|
||||||
|
init();
|
||||||
|
char buf[] = { c, '\0' };
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(unsigned char value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[1 + 8 * sizeof(unsigned char)];
|
||||||
|
utoa(value, buf, base);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(int value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[2 + 8 * sizeof(int)];
|
||||||
|
itoa(value, buf, base);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(unsigned int value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[1 + 8 * sizeof(unsigned int)];
|
||||||
|
utoa(value, buf, base);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(long value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[2 + 8 * sizeof(long)];
|
||||||
|
ltoa(value, buf, base);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(unsigned long value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[1 + 8 * sizeof(unsigned long)];
|
||||||
|
ultoa(value, buf, base);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(float value, unsigned int decimalPlaces) {
|
||||||
|
init();
|
||||||
|
char *buf = (char*)malloc(decimalPlaces + 42);
|
||||||
|
if (buf) {
|
||||||
|
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
||||||
|
free(buf);
|
||||||
|
} else {
|
||||||
|
*this = "nan";
|
||||||
|
log_e("No enought memory for the operation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(double value, unsigned int decimalPlaces) {
|
||||||
|
init();
|
||||||
|
char *buf = (char*)malloc(decimalPlaces + 312);
|
||||||
|
if (buf) {
|
||||||
|
*this = dtostrf(value, (decimalPlaces + 2), decimalPlaces, buf);
|
||||||
|
free(buf);
|
||||||
|
} else {
|
||||||
|
*this = "nan";
|
||||||
|
log_e("No enought memory for the operation.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(long long value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[2 + 8 * sizeof(long long)];
|
||||||
|
lltoa(value, buf, base);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::String(unsigned long long value, unsigned char base) {
|
||||||
|
init();
|
||||||
|
char buf[1 + 8 * sizeof(unsigned long long)];
|
||||||
|
ulltoa(value, buf, base);
|
||||||
|
*this = buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
String::~String() {
|
||||||
|
invalidate();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Memory Management */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
inline void String::init(void) {
|
||||||
|
setSSO(false);
|
||||||
|
setBuffer(nullptr);
|
||||||
|
setCapacity(0);
|
||||||
|
setLen(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::invalidate(void) {
|
||||||
|
if(!isSSO() && wbuffer())
|
||||||
|
free(wbuffer());
|
||||||
|
init();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::reserve(unsigned int size) {
|
||||||
|
if(buffer() && capacity() >= size)
|
||||||
|
return true;
|
||||||
|
if(changeBuffer(size)) {
|
||||||
|
if(len() == 0)
|
||||||
|
wbuffer()[0] = 0;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::changeBuffer(unsigned int maxStrLen) {
|
||||||
|
// Can we use SSO here to avoid allocation?
|
||||||
|
if (maxStrLen < sizeof(sso.buff) - 1) {
|
||||||
|
if (isSSO() || !buffer()) {
|
||||||
|
// Already using SSO, nothing to do
|
||||||
|
uint16_t oldLen = len();
|
||||||
|
setSSO(true);
|
||||||
|
setLen(oldLen);
|
||||||
|
} else { // if bufptr && !isSSO()
|
||||||
|
// Using bufptr, need to shrink into sso.buff
|
||||||
|
char temp[sizeof(sso.buff)];
|
||||||
|
memcpy(temp, buffer(), maxStrLen);
|
||||||
|
free(wbuffer());
|
||||||
|
uint16_t oldLen = len();
|
||||||
|
setSSO(true);
|
||||||
|
memcpy(wbuffer(), temp, maxStrLen);
|
||||||
|
setLen(oldLen);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Fallthrough to normal allocator
|
||||||
|
size_t newSize = (maxStrLen + 16) & (~0xf);
|
||||||
|
// Make sure we can fit newsize in the buffer
|
||||||
|
if (newSize > CAPACITY_MAX) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
uint16_t oldLen = len();
|
||||||
|
char *newbuffer = (char *) realloc(isSSO() ? nullptr : wbuffer(), newSize);
|
||||||
|
if (newbuffer) {
|
||||||
|
size_t oldSize = capacity() + 1; // include NULL.
|
||||||
|
if (isSSO()) {
|
||||||
|
// Copy the SSO buffer into allocated space
|
||||||
|
memmove(newbuffer, sso.buff, sizeof(sso.buff));
|
||||||
|
}
|
||||||
|
if (newSize > oldSize) {
|
||||||
|
memset(newbuffer + oldSize, 0, newSize - oldSize);
|
||||||
|
}
|
||||||
|
setSSO(false);
|
||||||
|
setCapacity(newSize - 1);
|
||||||
|
setBuffer(newbuffer);
|
||||||
|
setLen(oldLen); // Needed in case of SSO where len() never existed
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Copy and Move */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
String & String::copy(const char *cstr, unsigned int length) {
|
||||||
|
if(!reserve(length)) {
|
||||||
|
invalidate();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
memmove(wbuffer(), cstr, length + 1);
|
||||||
|
setLen(length);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
||||||
|
void String::move(String &rhs) {
|
||||||
|
if(buffer()) {
|
||||||
|
if(capacity() >= rhs.len()) {
|
||||||
|
memmove(wbuffer(), rhs.buffer(), rhs.length() + 1);
|
||||||
|
setLen(rhs.len());
|
||||||
|
rhs.invalidate();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
if (!isSSO()) {
|
||||||
|
free(wbuffer());
|
||||||
|
setBuffer(nullptr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (rhs.isSSO()) {
|
||||||
|
setSSO(true);
|
||||||
|
memmove(sso.buff, rhs.sso.buff, sizeof(sso.buff));
|
||||||
|
} else {
|
||||||
|
setSSO(false);
|
||||||
|
setBuffer(rhs.wbuffer());
|
||||||
|
}
|
||||||
|
setCapacity(rhs.capacity());
|
||||||
|
setLen(rhs.len());
|
||||||
|
rhs.setSSO(false);
|
||||||
|
rhs.setCapacity(0);
|
||||||
|
rhs.setBuffer(nullptr);
|
||||||
|
rhs.setLen(0);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
String & String::operator =(const String &rhs) {
|
||||||
|
if(this == &rhs)
|
||||||
|
return *this;
|
||||||
|
if(rhs.buffer())
|
||||||
|
copy(rhs.buffer(), rhs.len());
|
||||||
|
else
|
||||||
|
invalidate();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
||||||
|
String & String::operator =(String &&rval) {
|
||||||
|
if(this != &rval)
|
||||||
|
move(rval);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
String & String::operator =(StringSumHelper &&rval) {
|
||||||
|
if(this != &rval)
|
||||||
|
move(rval);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
String & String::operator =(const char *cstr) {
|
||||||
|
if(cstr)
|
||||||
|
copy(cstr, strlen(cstr));
|
||||||
|
else
|
||||||
|
invalidate();
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* concat */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
bool String::concat(const String &s) {
|
||||||
|
// Special case if we're concatting ourself (s += s;) since we may end up
|
||||||
|
// realloc'ing the buffer and moving s.buffer in the method called
|
||||||
|
if (&s == this) {
|
||||||
|
unsigned int newlen = 2 * len();
|
||||||
|
if (!s.buffer())
|
||||||
|
return false;
|
||||||
|
if (s.len() == 0)
|
||||||
|
return true;
|
||||||
|
if (!reserve(newlen))
|
||||||
|
return false;
|
||||||
|
memmove(wbuffer() + len(), buffer(), len());
|
||||||
|
setLen(newlen);
|
||||||
|
wbuffer()[len()] = 0;
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return concat(s.buffer(), s.len());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(const char *cstr, unsigned int length) {
|
||||||
|
unsigned int newlen = len() + length;
|
||||||
|
if(!cstr)
|
||||||
|
return false;
|
||||||
|
if(length == 0)
|
||||||
|
return true;
|
||||||
|
if(!reserve(newlen))
|
||||||
|
return false;
|
||||||
|
if (cstr >= wbuffer() && cstr < wbuffer() + len())
|
||||||
|
// compatible with SSO in ram #6155 (case "x += x.c_str()")
|
||||||
|
memmove(wbuffer() + len(), cstr, length + 1);
|
||||||
|
else
|
||||||
|
// compatible with source in flash #6367
|
||||||
|
memcpy_P(wbuffer() + len(), cstr, length + 1);
|
||||||
|
setLen(newlen);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(const char *cstr) {
|
||||||
|
if(!cstr)
|
||||||
|
return false;
|
||||||
|
return concat(cstr, strlen(cstr));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(char c) {
|
||||||
|
char buf[] = { c, '\0' };
|
||||||
|
return concat(buf, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(unsigned char num) {
|
||||||
|
char buf[1 + 3 * sizeof(unsigned char)];
|
||||||
|
utoa(num, buf, 10);
|
||||||
|
return concat(buf, strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(int num) {
|
||||||
|
char buf[2 + 3 * sizeof(int)];
|
||||||
|
itoa(num, buf, 10);
|
||||||
|
return concat(buf, strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(unsigned int num) {
|
||||||
|
char buf[1 + 3 * sizeof(unsigned int)];
|
||||||
|
utoa(num, buf, 10);
|
||||||
|
return concat(buf, strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(long num) {
|
||||||
|
char buf[2 + 3 * sizeof(long)];
|
||||||
|
ltoa(num, buf, 10);
|
||||||
|
return concat(buf, strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(unsigned long num) {
|
||||||
|
char buf[1 + 3 * sizeof(unsigned long)];
|
||||||
|
ultoa(num, buf, 10);
|
||||||
|
return concat(buf, strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(long long num) {
|
||||||
|
char buf[2 + 3 * sizeof(long long)];
|
||||||
|
lltoa(num, buf, 10);
|
||||||
|
return concat(buf, strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(unsigned long long num) {
|
||||||
|
char buf[1 + 3 * sizeof(unsigned long long)];
|
||||||
|
ulltoa(num, buf, 10);
|
||||||
|
return concat(buf, strlen(buf));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(float num) {
|
||||||
|
char buf[20];
|
||||||
|
char* string = dtostrf(num, 4, 2, buf);
|
||||||
|
return concat(string, strlen(string));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::concat(double num) {
|
||||||
|
char buf[20];
|
||||||
|
char* string = dtostrf(num, 4, 2, buf);
|
||||||
|
return concat(string, strlen(string));
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Concatenate */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(rhs.buffer(), rhs.len()))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!cstr || !a.concat(cstr, strlen(cstr)))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, char c) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(c))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, int num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, long num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, float num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, double num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, long long num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long long num) {
|
||||||
|
StringSumHelper &a = const_cast<StringSumHelper&>(lhs);
|
||||||
|
if(!a.concat(num))
|
||||||
|
a.invalidate();
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Comparison */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
int String::compareTo(const String &s) const {
|
||||||
|
if(!buffer() || !s.buffer()) {
|
||||||
|
if(s.buffer() && s.len() > 0)
|
||||||
|
return 0 - *(unsigned char *) s.buffer();
|
||||||
|
if(buffer() && len() > 0)
|
||||||
|
return *(unsigned char *) buffer();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return strcmp(buffer(), s.buffer());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::equals(const String &s2) const {
|
||||||
|
return (len() == s2.len() && compareTo(s2) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::equals(const char *cstr) const {
|
||||||
|
if(len() == 0)
|
||||||
|
return (cstr == NULL || *cstr == 0);
|
||||||
|
if(cstr == NULL)
|
||||||
|
return buffer()[0] == 0;
|
||||||
|
return strcmp(buffer(), cstr) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::operator<(const String &rhs) const {
|
||||||
|
return compareTo(rhs) < 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::operator>(const String &rhs) const {
|
||||||
|
return compareTo(rhs) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::operator<=(const String &rhs) const {
|
||||||
|
return compareTo(rhs) <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::operator>=(const String &rhs) const {
|
||||||
|
return compareTo(rhs) >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::equalsIgnoreCase(const String &s2) const {
|
||||||
|
if(this == &s2)
|
||||||
|
return true;
|
||||||
|
if(len() != s2.len())
|
||||||
|
return false;
|
||||||
|
if(len() == 0)
|
||||||
|
return true;
|
||||||
|
const char *p1 = buffer();
|
||||||
|
const char *p2 = s2.buffer();
|
||||||
|
while(*p1) {
|
||||||
|
if(tolower(*p1++) != tolower(*p2++))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
unsigned char String::equalsConstantTime(const String &s2) const {
|
||||||
|
// To avoid possible time-based attacks present function
|
||||||
|
// compares given strings in a constant time.
|
||||||
|
if(len() != s2.len())
|
||||||
|
return 0;
|
||||||
|
//at this point lengths are the same
|
||||||
|
if(len() == 0)
|
||||||
|
return 1;
|
||||||
|
//at this point lengths are the same and non-zero
|
||||||
|
const char *p1 = buffer();
|
||||||
|
const char *p2 = s2.buffer();
|
||||||
|
unsigned int equalchars = 0;
|
||||||
|
unsigned int diffchars = 0;
|
||||||
|
while(*p1) {
|
||||||
|
if(*p1 == *p2)
|
||||||
|
++equalchars;
|
||||||
|
else
|
||||||
|
++diffchars;
|
||||||
|
++p1;
|
||||||
|
++p2;
|
||||||
|
}
|
||||||
|
//the following should force a constant time eval of the condition without a compiler "logical shortcut"
|
||||||
|
unsigned char equalcond = (equalchars == len());
|
||||||
|
unsigned char diffcond = (diffchars == 0);
|
||||||
|
return (equalcond & diffcond); //bitwise AND
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::startsWith(const String &s2) const {
|
||||||
|
if(len() < s2.len())
|
||||||
|
return false;
|
||||||
|
return startsWith(s2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::startsWith(const String &s2, unsigned int offset) const {
|
||||||
|
if(offset > (unsigned)(len() - s2.len()) || !buffer() || !s2.buffer())
|
||||||
|
return false;
|
||||||
|
return strncmp(&buffer()[offset], s2.buffer(), s2.len()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool String::endsWith(const String &s2) const {
|
||||||
|
if(len() < s2.len() || !buffer() || !s2.buffer())
|
||||||
|
return false;
|
||||||
|
return strcmp(&buffer()[len() - s2.len()], s2.buffer()) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Character Access */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
char String::charAt(unsigned int loc) const {
|
||||||
|
return operator[](loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::setCharAt(unsigned int loc, char c) {
|
||||||
|
if(loc < len())
|
||||||
|
wbuffer()[loc] = c;
|
||||||
|
}
|
||||||
|
|
||||||
|
char & String::operator[](unsigned int index) {
|
||||||
|
static char dummy_writable_char;
|
||||||
|
if(index >= len() || !buffer()) {
|
||||||
|
dummy_writable_char = 0;
|
||||||
|
return dummy_writable_char;
|
||||||
|
}
|
||||||
|
return wbuffer()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
char String::operator[](unsigned int index) const {
|
||||||
|
if(index >= len() || !buffer())
|
||||||
|
return 0;
|
||||||
|
return buffer()[index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index) const {
|
||||||
|
if(!bufsize || !buf)
|
||||||
|
return;
|
||||||
|
if(index >= len()) {
|
||||||
|
buf[0] = 0;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
unsigned int n = bufsize - 1;
|
||||||
|
if(n > len() - index)
|
||||||
|
n = len() - index;
|
||||||
|
strncpy((char *) buf, buffer() + index, n);
|
||||||
|
buf[n] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Search */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
int String::indexOf(char c) const {
|
||||||
|
return indexOf(c, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::indexOf(char ch, unsigned int fromIndex) const {
|
||||||
|
if(fromIndex >= len())
|
||||||
|
return -1;
|
||||||
|
const char *temp = strchr(buffer() + fromIndex, ch);
|
||||||
|
if(temp == NULL)
|
||||||
|
return -1;
|
||||||
|
return temp - buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::indexOf(const String &s2) const {
|
||||||
|
return indexOf(s2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::indexOf(const String &s2, unsigned int fromIndex) const {
|
||||||
|
if(fromIndex >= len())
|
||||||
|
return -1;
|
||||||
|
const char *found = strstr(buffer() + fromIndex, s2.buffer());
|
||||||
|
if(found == NULL)
|
||||||
|
return -1;
|
||||||
|
return found - buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::lastIndexOf(char theChar) const {
|
||||||
|
return lastIndexOf(theChar, len() - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::lastIndexOf(char ch, unsigned int fromIndex) const {
|
||||||
|
if(fromIndex >= len())
|
||||||
|
return -1;
|
||||||
|
char tempchar = buffer()[fromIndex + 1];
|
||||||
|
wbuffer()[fromIndex + 1] = '\0';
|
||||||
|
char* temp = strrchr(wbuffer(), ch);
|
||||||
|
wbuffer()[fromIndex + 1] = tempchar;
|
||||||
|
if(temp == NULL)
|
||||||
|
return -1;
|
||||||
|
return temp - buffer();
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::lastIndexOf(const String &s2) const {
|
||||||
|
return lastIndexOf(s2, len() - s2.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
int String::lastIndexOf(const String &s2, unsigned int fromIndex) const {
|
||||||
|
if(s2.len() == 0 || len() == 0 || s2.len() > len())
|
||||||
|
return -1;
|
||||||
|
if(fromIndex >= len())
|
||||||
|
fromIndex = len() - 1;
|
||||||
|
int found = -1;
|
||||||
|
for(char *p = wbuffer(); p <= wbuffer() + fromIndex; p++) {
|
||||||
|
p = strstr(p, s2.buffer());
|
||||||
|
if(!p)
|
||||||
|
break;
|
||||||
|
if((unsigned int) (p - wbuffer()) <= fromIndex)
|
||||||
|
found = p - buffer();
|
||||||
|
}
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
String String::substring(unsigned int left, unsigned int right) const {
|
||||||
|
if(left > right) {
|
||||||
|
unsigned int temp = right;
|
||||||
|
right = left;
|
||||||
|
left = temp;
|
||||||
|
}
|
||||||
|
String out;
|
||||||
|
if(left >= len())
|
||||||
|
return out;
|
||||||
|
if(right > len())
|
||||||
|
right = len();
|
||||||
|
out.copy(buffer() + left, right - left);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Modification */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
void String::replace(char find, char replace) {
|
||||||
|
if(!buffer())
|
||||||
|
return;
|
||||||
|
for(char *p = wbuffer(); *p; p++) {
|
||||||
|
if(*p == find)
|
||||||
|
*p = replace;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::replace(const String &find, const String &replace) {
|
||||||
|
if(len() == 0 || find.len() == 0)
|
||||||
|
return;
|
||||||
|
int diff = replace.len() - find.len();
|
||||||
|
char *readFrom = wbuffer();
|
||||||
|
char *foundAt;
|
||||||
|
if(diff == 0) {
|
||||||
|
while((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
||||||
|
memmove(foundAt, replace.buffer(), replace.len());
|
||||||
|
readFrom = foundAt + replace.len();
|
||||||
|
}
|
||||||
|
} else if(diff < 0) {
|
||||||
|
char *writeTo = wbuffer();
|
||||||
|
unsigned int l = len();
|
||||||
|
while((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
||||||
|
unsigned int n = foundAt - readFrom;
|
||||||
|
memmove(writeTo, readFrom, n);
|
||||||
|
writeTo += n;
|
||||||
|
memmove(writeTo, replace.buffer(), replace.len());
|
||||||
|
writeTo += replace.len();
|
||||||
|
readFrom = foundAt + find.len();
|
||||||
|
l += diff;
|
||||||
|
}
|
||||||
|
memmove(writeTo, readFrom, strlen(readFrom)+1);
|
||||||
|
setLen(l);
|
||||||
|
} else {
|
||||||
|
unsigned int size = len(); // compute size needed for result
|
||||||
|
while((foundAt = strstr(readFrom, find.buffer())) != NULL) {
|
||||||
|
readFrom = foundAt + find.len();
|
||||||
|
size += diff;
|
||||||
|
}
|
||||||
|
if(size == len())
|
||||||
|
return;
|
||||||
|
if(size > capacity() && !changeBuffer(size)) {
|
||||||
|
log_w("String.Replace() Insufficient space to replace string");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int index = len() - 1;
|
||||||
|
while(index >= 0 && (index = lastIndexOf(find, index)) >= 0) {
|
||||||
|
readFrom = wbuffer() + index + find.len();
|
||||||
|
memmove(readFrom + diff, readFrom, len() - (readFrom - buffer()));
|
||||||
|
int newLen = len() + diff;
|
||||||
|
memmove(wbuffer() + index, replace.buffer(), replace.len());
|
||||||
|
setLen(newLen);
|
||||||
|
wbuffer()[newLen] = 0;
|
||||||
|
index--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::remove(unsigned int index) {
|
||||||
|
// Pass the biggest integer as the count. The remove method
|
||||||
|
// below will take care of truncating it at the end of the
|
||||||
|
// string.
|
||||||
|
remove(index, (unsigned int) -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::remove(unsigned int index, unsigned int count) {
|
||||||
|
if(index >= len()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(count <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if(count > len() - index) {
|
||||||
|
count = len() - index;
|
||||||
|
}
|
||||||
|
char *writeTo = wbuffer() + index;
|
||||||
|
unsigned int newlen = len() - count;
|
||||||
|
memmove(writeTo, wbuffer() + index + count, newlen - index);
|
||||||
|
setLen(newlen);
|
||||||
|
wbuffer()[newlen] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::toLowerCase(void) {
|
||||||
|
if(!buffer())
|
||||||
|
return;
|
||||||
|
for(char *p = wbuffer(); *p; p++) {
|
||||||
|
*p = tolower(*p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::toUpperCase(void) {
|
||||||
|
if(!buffer())
|
||||||
|
return;
|
||||||
|
for(char *p = wbuffer(); *p; p++) {
|
||||||
|
*p = toupper(*p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void String::trim(void) {
|
||||||
|
if(!buffer() || len() == 0)
|
||||||
|
return;
|
||||||
|
char *begin = wbuffer();
|
||||||
|
while(isspace(*begin))
|
||||||
|
begin++;
|
||||||
|
char *end = wbuffer() + len() - 1;
|
||||||
|
while(isspace(*end) && end >= begin)
|
||||||
|
end--;
|
||||||
|
unsigned int newlen = end + 1 - begin;
|
||||||
|
if(begin > buffer())
|
||||||
|
memmove(wbuffer(), begin, newlen);
|
||||||
|
setLen(newlen);
|
||||||
|
wbuffer()[newlen] = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*********************************************/
|
||||||
|
/* Parsing / Conversion */
|
||||||
|
/*********************************************/
|
||||||
|
|
||||||
|
long String::toInt(void) const {
|
||||||
|
if (buffer())
|
||||||
|
return atol(buffer());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
float String::toFloat(void) const {
|
||||||
|
if (buffer())
|
||||||
|
return atof(buffer());
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
double String::toDouble(void) const {
|
||||||
|
if (buffer())
|
||||||
|
return atof(buffer());
|
||||||
|
return 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// global empty string to allow returning const String& with nothing
|
||||||
|
|
||||||
|
const String emptyString;
|
@ -0,0 +1,404 @@
|
|||||||
|
/*
|
||||||
|
WString.h - String library for Wiring & Arduino
|
||||||
|
...mostly rewritten by Paul Stoffregen...
|
||||||
|
Copyright (c) 2009-10 Hernando Barragan. All right reserved.
|
||||||
|
Copyright 2011, Paul Stoffregen, paul@pjrc.com
|
||||||
|
|
||||||
|
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 String_class_h
|
||||||
|
#define String_class_h
|
||||||
|
#ifdef __cplusplus
|
||||||
|
|
||||||
|
#include <pgmspace.h>
|
||||||
|
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
|
|
||||||
|
// A pure abstract class forward used as a means to proide a unique pointer type
|
||||||
|
// but really is never defined.
|
||||||
|
class __FlashStringHelper;
|
||||||
|
#define FPSTR(str_pointer) (reinterpret_cast<const __FlashStringHelper *>(str_pointer))
|
||||||
|
#define F(string_literal) (FPSTR(PSTR(string_literal)))
|
||||||
|
|
||||||
|
// An inherited class for holding the result of a concatenation. These
|
||||||
|
// result objects are assumed to be writable by subsequent concatenations.
|
||||||
|
class StringSumHelper;
|
||||||
|
|
||||||
|
// The string class
|
||||||
|
class String {
|
||||||
|
// use a function pointer to allow for "if (s)" without the
|
||||||
|
// complications of an operator bool(). for more information, see:
|
||||||
|
// http://www.artima.com/cppsource/safebool.html
|
||||||
|
typedef void (String::*StringIfHelperType)() const;
|
||||||
|
void StringIfHelper() const {
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
// constructors
|
||||||
|
// creates a copy of the initial value.
|
||||||
|
// if the initial value is null or invalid, or if memory allocation
|
||||||
|
// fails, the string will be marked as invalid (i.e. "if (s)" will
|
||||||
|
// be false).
|
||||||
|
String(const char *cstr = "");
|
||||||
|
String(const char *cstr, unsigned int length);
|
||||||
|
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
||||||
|
String(const uint8_t *cstr, unsigned int length) : String(reinterpret_cast<const char*>(cstr), length) {}
|
||||||
|
#endif
|
||||||
|
String(const String &str);
|
||||||
|
String(const __FlashStringHelper *str) : String(reinterpret_cast<const char*>(str)) {}
|
||||||
|
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
||||||
|
String(String &&rval);
|
||||||
|
String(StringSumHelper &&rval);
|
||||||
|
#endif
|
||||||
|
explicit String(char c);
|
||||||
|
explicit String(unsigned char, unsigned char base = 10);
|
||||||
|
explicit String(int, unsigned char base = 10);
|
||||||
|
explicit String(unsigned int, unsigned char base = 10);
|
||||||
|
explicit String(long, unsigned char base = 10);
|
||||||
|
explicit String(unsigned long, unsigned char base = 10);
|
||||||
|
explicit String(float, unsigned int decimalPlaces = 2);
|
||||||
|
explicit String(double, unsigned int decimalPlaces = 2);
|
||||||
|
explicit String(long long, unsigned char base = 10);
|
||||||
|
explicit String(unsigned long long, unsigned char base = 10);
|
||||||
|
~String(void);
|
||||||
|
|
||||||
|
// memory management
|
||||||
|
// return true on success, false on failure (in which case, the string
|
||||||
|
// is left unchanged). reserve(0), if successful, will validate an
|
||||||
|
// invalid string (i.e., "if (s)" will be true afterwards)
|
||||||
|
bool reserve(unsigned int size);
|
||||||
|
inline unsigned int length(void) const {
|
||||||
|
if(buffer()) {
|
||||||
|
return len();
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void clear(void) {
|
||||||
|
setLen(0);
|
||||||
|
}
|
||||||
|
inline bool isEmpty(void) const {
|
||||||
|
return length() == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// creates a copy of the assigned value. if the value is null or
|
||||||
|
// invalid, or if the memory allocation fails, the string will be
|
||||||
|
// marked as invalid ("if (s)" will be false).
|
||||||
|
String & operator =(const String &rhs);
|
||||||
|
String & operator =(const char *cstr);
|
||||||
|
String & operator = (const __FlashStringHelper *str) {return *this = reinterpret_cast<const char*>(str);}
|
||||||
|
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
||||||
|
String & operator =(String &&rval);
|
||||||
|
String & operator =(StringSumHelper &&rval);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// concatenate (works w/ built-in types, same as assignment)
|
||||||
|
|
||||||
|
// returns true on success, false on failure (in which case, the string
|
||||||
|
// is left unchanged). if the argument is null or invalid, the
|
||||||
|
// concatenation is considered unsuccessful.
|
||||||
|
bool concat(const String &str);
|
||||||
|
bool concat(const char *cstr);
|
||||||
|
bool concat(const char *cstr, unsigned int length);
|
||||||
|
bool concat(const uint8_t *cstr, unsigned int length) {return concat(reinterpret_cast<const char*>(cstr), length);}
|
||||||
|
bool concat(char c);
|
||||||
|
bool concat(unsigned char c);
|
||||||
|
bool concat(int num);
|
||||||
|
bool concat(unsigned int num);
|
||||||
|
bool concat(long num);
|
||||||
|
bool concat(unsigned long num);
|
||||||
|
bool concat(float num);
|
||||||
|
bool concat(double num);
|
||||||
|
bool concat(long long num);
|
||||||
|
bool concat(unsigned long long num);
|
||||||
|
bool concat(const __FlashStringHelper * str) {return concat(reinterpret_cast<const char*>(str));}
|
||||||
|
|
||||||
|
// if there's not enough memory for the concatenated value, the string
|
||||||
|
// will be left unchanged (but this isn't signalled in any way)
|
||||||
|
String & operator +=(const String &rhs) {
|
||||||
|
concat(rhs);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(const char *cstr) {
|
||||||
|
concat(cstr);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(char c) {
|
||||||
|
concat(c);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(unsigned char num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(int num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(unsigned int num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(long num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(unsigned long num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(float num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(double num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(long long num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator +=(unsigned long long num) {
|
||||||
|
concat(num);
|
||||||
|
return (*this);
|
||||||
|
}
|
||||||
|
String & operator += (const __FlashStringHelper *str) {return *this += reinterpret_cast<const char*>(str);}
|
||||||
|
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, const String &rhs);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, const char *cstr);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, char c);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned char num);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, int num);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned int num);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, long num);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long num);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, float num);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, double num);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, long long num);
|
||||||
|
friend StringSumHelper & operator +(const StringSumHelper &lhs, unsigned long long num);
|
||||||
|
|
||||||
|
// comparison (only works w/ Strings and "strings")
|
||||||
|
operator StringIfHelperType() const {
|
||||||
|
return buffer() ? &String::StringIfHelper : 0;
|
||||||
|
}
|
||||||
|
int compareTo(const String &s) const;
|
||||||
|
bool equals(const String &s) const;
|
||||||
|
bool equals(const char *cstr) const;
|
||||||
|
bool operator ==(const String &rhs) const {
|
||||||
|
return equals(rhs);
|
||||||
|
}
|
||||||
|
bool operator ==(const char *cstr) const {
|
||||||
|
return equals(cstr);
|
||||||
|
}
|
||||||
|
bool operator !=(const String &rhs) const {
|
||||||
|
return !equals(rhs);
|
||||||
|
}
|
||||||
|
bool operator !=(const char *cstr) const {
|
||||||
|
return !equals(cstr);
|
||||||
|
}
|
||||||
|
bool operator <(const String &rhs) const;
|
||||||
|
bool operator >(const String &rhs) const;
|
||||||
|
bool operator <=(const String &rhs) const;
|
||||||
|
bool operator >=(const String &rhs) const;
|
||||||
|
bool equalsIgnoreCase(const String &s) const;
|
||||||
|
unsigned char equalsConstantTime(const String &s) const;
|
||||||
|
bool startsWith(const String &prefix) const;
|
||||||
|
bool startsWith(const char *prefix) const {
|
||||||
|
return this->startsWith(String(prefix));
|
||||||
|
}
|
||||||
|
bool startsWith(const __FlashStringHelper *prefix) const {
|
||||||
|
return this->startsWith(reinterpret_cast<const char*>(prefix));
|
||||||
|
}
|
||||||
|
bool startsWith(const String &prefix, unsigned int offset) const;
|
||||||
|
bool endsWith(const String &suffix) const;
|
||||||
|
bool endsWith(const char *suffix) const {
|
||||||
|
return this->endsWith(String(suffix));
|
||||||
|
}
|
||||||
|
bool endsWith(const __FlashStringHelper * suffix) const {
|
||||||
|
return this->endsWith(reinterpret_cast<const char*>(suffix));
|
||||||
|
}
|
||||||
|
|
||||||
|
// character access
|
||||||
|
char charAt(unsigned int index) const;
|
||||||
|
void setCharAt(unsigned int index, char c);
|
||||||
|
char operator [](unsigned int index) const;
|
||||||
|
char& operator [](unsigned int index);
|
||||||
|
void getBytes(unsigned char *buf, unsigned int bufsize, unsigned int index = 0) const;
|
||||||
|
void toCharArray(char *buf, unsigned int bufsize, unsigned int index = 0) const {
|
||||||
|
getBytes((unsigned char *) buf, bufsize, index);
|
||||||
|
}
|
||||||
|
const char* c_str() const { return buffer(); }
|
||||||
|
char* begin() { return wbuffer(); }
|
||||||
|
char* end() { return wbuffer() + length(); }
|
||||||
|
const char* begin() const { return c_str(); }
|
||||||
|
const char* end() const { return c_str() + length(); }
|
||||||
|
|
||||||
|
// search
|
||||||
|
int indexOf(char ch) const;
|
||||||
|
int indexOf(char ch, unsigned int fromIndex) const;
|
||||||
|
int indexOf(const String &str) const;
|
||||||
|
int indexOf(const String &str, unsigned int fromIndex) const;
|
||||||
|
int lastIndexOf(char ch) const;
|
||||||
|
int lastIndexOf(char ch, unsigned int fromIndex) const;
|
||||||
|
int lastIndexOf(const String &str) const;
|
||||||
|
int lastIndexOf(const String &str, unsigned int fromIndex) const;
|
||||||
|
String substring(unsigned int beginIndex) const {
|
||||||
|
return substring(beginIndex, len());
|
||||||
|
}
|
||||||
|
String substring(unsigned int beginIndex, unsigned int endIndex) const;
|
||||||
|
|
||||||
|
// modification
|
||||||
|
void replace(char find, char replace);
|
||||||
|
void replace(const String &find, const String &replace);
|
||||||
|
void replace(const char *find, const String &replace) {
|
||||||
|
this->replace(String(find), replace);
|
||||||
|
}
|
||||||
|
void replace(const __FlashStringHelper *find, const String &replace) {
|
||||||
|
this->replace(reinterpret_cast<const char*>(find), replace);
|
||||||
|
}
|
||||||
|
void replace(const char *find, const char *replace) {
|
||||||
|
this->replace(String(find), String(replace));
|
||||||
|
}
|
||||||
|
void replace(const __FlashStringHelper *find, const char *replace) {
|
||||||
|
this->replace(reinterpret_cast<const char*>(find), String(replace));
|
||||||
|
}
|
||||||
|
void replace(const __FlashStringHelper *find, const __FlashStringHelper *replace) {
|
||||||
|
this->replace(reinterpret_cast<const char*>(find), reinterpret_cast<const char*>(replace));
|
||||||
|
}
|
||||||
|
void remove(unsigned int index);
|
||||||
|
void remove(unsigned int index, unsigned int count);
|
||||||
|
void toLowerCase(void);
|
||||||
|
void toUpperCase(void);
|
||||||
|
void trim(void);
|
||||||
|
|
||||||
|
// parsing/conversion
|
||||||
|
long toInt(void) const;
|
||||||
|
float toFloat(void) const;
|
||||||
|
double toDouble(void) const;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// Contains the string info when we're not in SSO mode
|
||||||
|
struct _ptr {
|
||||||
|
char * buff;
|
||||||
|
uint32_t cap;
|
||||||
|
uint32_t len;
|
||||||
|
};
|
||||||
|
// This allows strings up up to 11 (10 + \0 termination) without any extra space.
|
||||||
|
enum { SSOSIZE = sizeof(struct _ptr) + 4 - 1 }; // Characters to allocate space for SSO, must be 12 or more
|
||||||
|
struct _sso {
|
||||||
|
char buff[SSOSIZE];
|
||||||
|
unsigned char len : 7; // Ensure only one byte is allocated by GCC for the bitfields
|
||||||
|
unsigned char isSSO : 1;
|
||||||
|
} __attribute__((packed)); // Ensure that GCC doesn't expand the flag byte to a 32-bit word for alignment issues
|
||||||
|
#ifdef BOARD_HAS_PSRAM
|
||||||
|
enum { CAPACITY_MAX = 3145728 };
|
||||||
|
#else
|
||||||
|
enum { CAPACITY_MAX = 65535 };
|
||||||
|
#endif
|
||||||
|
union {
|
||||||
|
struct _ptr ptr;
|
||||||
|
struct _sso sso;
|
||||||
|
};
|
||||||
|
// Accessor functions
|
||||||
|
inline bool isSSO() const { return sso.isSSO; }
|
||||||
|
inline unsigned int len() const { return isSSO() ? sso.len : ptr.len; }
|
||||||
|
inline unsigned int capacity() const { return isSSO() ? (unsigned int)SSOSIZE - 1 : ptr.cap; } // Size of max string not including terminal NUL
|
||||||
|
inline void setSSO(bool set) { sso.isSSO = set; }
|
||||||
|
inline void setLen(int len) {
|
||||||
|
if (isSSO()) {
|
||||||
|
sso.len = len;
|
||||||
|
sso.buff[len] = 0;
|
||||||
|
} else {
|
||||||
|
ptr.len = len;
|
||||||
|
if (ptr.buff) {
|
||||||
|
ptr.buff[len] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline void setCapacity(int cap) { if (!isSSO()) ptr.cap = cap; }
|
||||||
|
inline void setBuffer(char *buff) { if (!isSSO()) ptr.buff = buff; }
|
||||||
|
// Buffer accessor functions
|
||||||
|
inline const char *buffer() const { return reinterpret_cast<const char *>(isSSO() ? sso.buff : ptr.buff); }
|
||||||
|
inline char *wbuffer() const { return isSSO() ? const_cast<char *>(sso.buff) : ptr.buff; } // Writable version of buffer
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void init(void);
|
||||||
|
void invalidate(void);
|
||||||
|
bool changeBuffer(unsigned int maxStrLen);
|
||||||
|
|
||||||
|
// copy and move
|
||||||
|
String & copy(const char *cstr, unsigned int length);
|
||||||
|
String & copy(const __FlashStringHelper *pstr, unsigned int length) {
|
||||||
|
return copy(reinterpret_cast<const char*>(pstr), length);
|
||||||
|
}
|
||||||
|
#ifdef __GXX_EXPERIMENTAL_CXX0X__
|
||||||
|
void move(String &rhs);
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
class StringSumHelper: public String {
|
||||||
|
public:
|
||||||
|
StringSumHelper(const String &s) :
|
||||||
|
String(s) {
|
||||||
|
}
|
||||||
|
StringSumHelper(const char *p) :
|
||||||
|
String(p) {
|
||||||
|
}
|
||||||
|
StringSumHelper(char c) :
|
||||||
|
String(c) {
|
||||||
|
}
|
||||||
|
StringSumHelper(unsigned char num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(int num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(unsigned int num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(long num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(unsigned long num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(float num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(double num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(long long num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
StringSumHelper(unsigned long long num) :
|
||||||
|
String(num) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
inline StringSumHelper & operator +(const StringSumHelper &lhs, const __FlashStringHelper *rhs) {
|
||||||
|
return lhs + reinterpret_cast<const char*>(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern const String emptyString;
|
||||||
|
|
||||||
|
#endif // __cplusplus
|
||||||
|
#endif // String_class_h
|
@ -0,0 +1 @@
|
|||||||
|
#include "lwip/apps/sntp.h"
|
@ -0,0 +1,64 @@
|
|||||||
|
/**
|
||||||
|
* base64.cpp
|
||||||
|
*
|
||||||
|
* Created on: 09.12.2015
|
||||||
|
*
|
||||||
|
* Copyright (c) 2015 Markus Sattler. All rights reserved.
|
||||||
|
* This file is part of the ESP31B core for Arduino.
|
||||||
|
*
|
||||||
|
* 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 "Arduino.h"
|
||||||
|
extern "C" {
|
||||||
|
#include "libb64/cdecode.h"
|
||||||
|
#include "libb64/cencode.h"
|
||||||
|
}
|
||||||
|
#include "base64.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert input data to base64
|
||||||
|
* @param data const uint8_t *
|
||||||
|
* @param length size_t
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String base64::encode(const uint8_t * data, size_t length)
|
||||||
|
{
|
||||||
|
size_t size = base64_encode_expected_len(length) + 1;
|
||||||
|
char * buffer = (char *) malloc(size);
|
||||||
|
if(buffer) {
|
||||||
|
base64_encodestate _state;
|
||||||
|
base64_init_encodestate(&_state);
|
||||||
|
int len = base64_encode_block((const char *) &data[0], length, &buffer[0], &_state);
|
||||||
|
len = base64_encode_blockend((buffer + len), &_state);
|
||||||
|
|
||||||
|
String base64 = String(buffer);
|
||||||
|
free(buffer);
|
||||||
|
return base64;
|
||||||
|
}
|
||||||
|
return String("-FAIL-");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* convert input data to base64
|
||||||
|
* @param text const String&
|
||||||
|
* @return String
|
||||||
|
*/
|
||||||
|
String base64::encode(const String& text)
|
||||||
|
{
|
||||||
|
return base64::encode((uint8_t *) text.c_str(), text.length());
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,13 @@
|
|||||||
|
#ifndef CORE_BASE64_H_
|
||||||
|
#define CORE_BASE64_H_
|
||||||
|
|
||||||
|
class base64
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
static String encode(const uint8_t * data, size_t length);
|
||||||
|
static String encode(const String& text);
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif /* CORE_BASE64_H_ */
|
@ -0,0 +1,534 @@
|
|||||||
|
/*
|
||||||
|
binary.h - Definitions for binary constants
|
||||||
|
Copyright (c) 2006 David A. Mellis. All right 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 Binary_h
|
||||||
|
#define Binary_h
|
||||||
|
|
||||||
|
#define B0 0
|
||||||
|
#define B00 0
|
||||||
|
#define B000 0
|
||||||
|
#define B0000 0
|
||||||
|
#define B00000 0
|
||||||
|
#define B000000 0
|
||||||
|
#define B0000000 0
|
||||||
|
#define B00000000 0
|
||||||
|
#define B1 1
|
||||||
|
#define B01 1
|
||||||
|
#define B001 1
|
||||||
|
#define B0001 1
|
||||||
|
#define B00001 1
|
||||||
|
#define B000001 1
|
||||||
|
#define B0000001 1
|
||||||
|
#define B00000001 1
|
||||||
|
#define B10 2
|
||||||
|
#define B010 2
|
||||||
|
#define B0010 2
|
||||||
|
#define B00010 2
|
||||||
|
#define B000010 2
|
||||||
|
#define B0000010 2
|
||||||
|
#define B00000010 2
|
||||||
|
#define B11 3
|
||||||
|
#define B011 3
|
||||||
|
#define B0011 3
|
||||||
|
#define B00011 3
|
||||||
|
#define B000011 3
|
||||||
|
#define B0000011 3
|
||||||
|
#define B00000011 3
|
||||||
|
#define B100 4
|
||||||
|
#define B0100 4
|
||||||
|
#define B00100 4
|
||||||
|
#define B000100 4
|
||||||
|
#define B0000100 4
|
||||||
|
#define B00000100 4
|
||||||
|
#define B101 5
|
||||||
|
#define B0101 5
|
||||||
|
#define B00101 5
|
||||||
|
#define B000101 5
|
||||||
|
#define B0000101 5
|
||||||
|
#define B00000101 5
|
||||||
|
#define B110 6
|
||||||
|
#define B0110 6
|
||||||
|
#define B00110 6
|
||||||
|
#define B000110 6
|
||||||
|
#define B0000110 6
|
||||||
|
#define B00000110 6
|
||||||
|
#define B111 7
|
||||||
|
#define B0111 7
|
||||||
|
#define B00111 7
|
||||||
|
#define B000111 7
|
||||||
|
#define B0000111 7
|
||||||
|
#define B00000111 7
|
||||||
|
#define B1000 8
|
||||||
|
#define B01000 8
|
||||||
|
#define B001000 8
|
||||||
|
#define B0001000 8
|
||||||
|
#define B00001000 8
|
||||||
|
#define B1001 9
|
||||||
|
#define B01001 9
|
||||||
|
#define B001001 9
|
||||||
|
#define B0001001 9
|
||||||
|
#define B00001001 9
|
||||||
|
#define B1010 10
|
||||||
|
#define B01010 10
|
||||||
|
#define B001010 10
|
||||||
|
#define B0001010 10
|
||||||
|
#define B00001010 10
|
||||||
|
#define B1011 11
|
||||||
|
#define B01011 11
|
||||||
|
#define B001011 11
|
||||||
|
#define B0001011 11
|
||||||
|
#define B00001011 11
|
||||||
|
#define B1100 12
|
||||||
|
#define B01100 12
|
||||||
|
#define B001100 12
|
||||||
|
#define B0001100 12
|
||||||
|
#define B00001100 12
|
||||||
|
#define B1101 13
|
||||||
|
#define B01101 13
|
||||||
|
#define B001101 13
|
||||||
|
#define B0001101 13
|
||||||
|
#define B00001101 13
|
||||||
|
#define B1110 14
|
||||||
|
#define B01110 14
|
||||||
|
#define B001110 14
|
||||||
|
#define B0001110 14
|
||||||
|
#define B00001110 14
|
||||||
|
#define B1111 15
|
||||||
|
#define B01111 15
|
||||||
|
#define B001111 15
|
||||||
|
#define B0001111 15
|
||||||
|
#define B00001111 15
|
||||||
|
#define B10000 16
|
||||||
|
#define B010000 16
|
||||||
|
#define B0010000 16
|
||||||
|
#define B00010000 16
|
||||||
|
#define B10001 17
|
||||||
|
#define B010001 17
|
||||||
|
#define B0010001 17
|
||||||
|
#define B00010001 17
|
||||||
|
#define B10010 18
|
||||||
|
#define B010010 18
|
||||||
|
#define B0010010 18
|
||||||
|
#define B00010010 18
|
||||||
|
#define B10011 19
|
||||||
|
#define B010011 19
|
||||||
|
#define B0010011 19
|
||||||
|
#define B00010011 19
|
||||||
|
#define B10100 20
|
||||||
|
#define B010100 20
|
||||||
|
#define B0010100 20
|
||||||
|
#define B00010100 20
|
||||||
|
#define B10101 21
|
||||||
|
#define B010101 21
|
||||||
|
#define B0010101 21
|
||||||
|
#define B00010101 21
|
||||||
|
#define B10110 22
|
||||||
|
#define B010110 22
|
||||||
|
#define B0010110 22
|
||||||
|
#define B00010110 22
|
||||||
|
#define B10111 23
|
||||||
|
#define B010111 23
|
||||||
|
#define B0010111 23
|
||||||
|
#define B00010111 23
|
||||||
|
#define B11000 24
|
||||||
|
#define B011000 24
|
||||||
|
#define B0011000 24
|
||||||
|
#define B00011000 24
|
||||||
|
#define B11001 25
|
||||||
|
#define B011001 25
|
||||||
|
#define B0011001 25
|
||||||
|
#define B00011001 25
|
||||||
|
#define B11010 26
|
||||||
|
#define B011010 26
|
||||||
|
#define B0011010 26
|
||||||
|
#define B00011010 26
|
||||||
|
#define B11011 27
|
||||||
|
#define B011011 27
|
||||||
|
#define B0011011 27
|
||||||
|
#define B00011011 27
|
||||||
|
#define B11100 28
|
||||||
|
#define B011100 28
|
||||||
|
#define B0011100 28
|
||||||
|
#define B00011100 28
|
||||||
|
#define B11101 29
|
||||||
|
#define B011101 29
|
||||||
|
#define B0011101 29
|
||||||
|
#define B00011101 29
|
||||||
|
#define B11110 30
|
||||||
|
#define B011110 30
|
||||||
|
#define B0011110 30
|
||||||
|
#define B00011110 30
|
||||||
|
#define B11111 31
|
||||||
|
#define B011111 31
|
||||||
|
#define B0011111 31
|
||||||
|
#define B00011111 31
|
||||||
|
#define B100000 32
|
||||||
|
#define B0100000 32
|
||||||
|
#define B00100000 32
|
||||||
|
#define B100001 33
|
||||||
|
#define B0100001 33
|
||||||
|
#define B00100001 33
|
||||||
|
#define B100010 34
|
||||||
|
#define B0100010 34
|
||||||
|
#define B00100010 34
|
||||||
|
#define B100011 35
|
||||||
|
#define B0100011 35
|
||||||
|
#define B00100011 35
|
||||||
|
#define B100100 36
|
||||||
|
#define B0100100 36
|
||||||
|
#define B00100100 36
|
||||||
|
#define B100101 37
|
||||||
|
#define B0100101 37
|
||||||
|
#define B00100101 37
|
||||||
|
#define B100110 38
|
||||||
|
#define B0100110 38
|
||||||
|
#define B00100110 38
|
||||||
|
#define B100111 39
|
||||||
|
#define B0100111 39
|
||||||
|
#define B00100111 39
|
||||||
|
#define B101000 40
|
||||||
|
#define B0101000 40
|
||||||
|
#define B00101000 40
|
||||||
|
#define B101001 41
|
||||||
|
#define B0101001 41
|
||||||
|
#define B00101001 41
|
||||||
|
#define B101010 42
|
||||||
|
#define B0101010 42
|
||||||
|
#define B00101010 42
|
||||||
|
#define B101011 43
|
||||||
|
#define B0101011 43
|
||||||
|
#define B00101011 43
|
||||||
|
#define B101100 44
|
||||||
|
#define B0101100 44
|
||||||
|
#define B00101100 44
|
||||||
|
#define B101101 45
|
||||||
|
#define B0101101 45
|
||||||
|
#define B00101101 45
|
||||||
|
#define B101110 46
|
||||||
|
#define B0101110 46
|
||||||
|
#define B00101110 46
|
||||||
|
#define B101111 47
|
||||||
|
#define B0101111 47
|
||||||
|
#define B00101111 47
|
||||||
|
#define B110000 48
|
||||||
|
#define B0110000 48
|
||||||
|
#define B00110000 48
|
||||||
|
#define B110001 49
|
||||||
|
#define B0110001 49
|
||||||
|
#define B00110001 49
|
||||||
|
#define B110010 50
|
||||||
|
#define B0110010 50
|
||||||
|
#define B00110010 50
|
||||||
|
#define B110011 51
|
||||||
|
#define B0110011 51
|
||||||
|
#define B00110011 51
|
||||||
|
#define B110100 52
|
||||||
|
#define B0110100 52
|
||||||
|
#define B00110100 52
|
||||||
|
#define B110101 53
|
||||||
|
#define B0110101 53
|
||||||
|
#define B00110101 53
|
||||||
|
#define B110110 54
|
||||||
|
#define B0110110 54
|
||||||
|
#define B00110110 54
|
||||||
|
#define B110111 55
|
||||||
|
#define B0110111 55
|
||||||
|
#define B00110111 55
|
||||||
|
#define B111000 56
|
||||||
|
#define B0111000 56
|
||||||
|
#define B00111000 56
|
||||||
|
#define B111001 57
|
||||||
|
#define B0111001 57
|
||||||
|
#define B00111001 57
|
||||||
|
#define B111010 58
|
||||||
|
#define B0111010 58
|
||||||
|
#define B00111010 58
|
||||||
|
#define B111011 59
|
||||||
|
#define B0111011 59
|
||||||
|
#define B00111011 59
|
||||||
|
#define B111100 60
|
||||||
|
#define B0111100 60
|
||||||
|
#define B00111100 60
|
||||||
|
#define B111101 61
|
||||||
|
#define B0111101 61
|
||||||
|
#define B00111101 61
|
||||||
|
#define B111110 62
|
||||||
|
#define B0111110 62
|
||||||
|
#define B00111110 62
|
||||||
|
#define B111111 63
|
||||||
|
#define B0111111 63
|
||||||
|
#define B00111111 63
|
||||||
|
#define B1000000 64
|
||||||
|
#define B01000000 64
|
||||||
|
#define B1000001 65
|
||||||
|
#define B01000001 65
|
||||||
|
#define B1000010 66
|
||||||
|
#define B01000010 66
|
||||||
|
#define B1000011 67
|
||||||
|
#define B01000011 67
|
||||||
|
#define B1000100 68
|
||||||
|
#define B01000100 68
|
||||||
|
#define B1000101 69
|
||||||
|
#define B01000101 69
|
||||||
|
#define B1000110 70
|
||||||
|
#define B01000110 70
|
||||||
|
#define B1000111 71
|
||||||
|
#define B01000111 71
|
||||||
|
#define B1001000 72
|
||||||
|
#define B01001000 72
|
||||||
|
#define B1001001 73
|
||||||
|
#define B01001001 73
|
||||||
|
#define B1001010 74
|
||||||
|
#define B01001010 74
|
||||||
|
#define B1001011 75
|
||||||
|
#define B01001011 75
|
||||||
|
#define B1001100 76
|
||||||
|
#define B01001100 76
|
||||||
|
#define B1001101 77
|
||||||
|
#define B01001101 77
|
||||||
|
#define B1001110 78
|
||||||
|
#define B01001110 78
|
||||||
|
#define B1001111 79
|
||||||
|
#define B01001111 79
|
||||||
|
#define B1010000 80
|
||||||
|
#define B01010000 80
|
||||||
|
#define B1010001 81
|
||||||
|
#define B01010001 81
|
||||||
|
#define B1010010 82
|
||||||
|
#define B01010010 82
|
||||||
|
#define B1010011 83
|
||||||
|
#define B01010011 83
|
||||||
|
#define B1010100 84
|
||||||
|
#define B01010100 84
|
||||||
|
#define B1010101 85
|
||||||
|
#define B01010101 85
|
||||||
|
#define B1010110 86
|
||||||
|
#define B01010110 86
|
||||||
|
#define B1010111 87
|
||||||
|
#define B01010111 87
|
||||||
|
#define B1011000 88
|
||||||
|
#define B01011000 88
|
||||||
|
#define B1011001 89
|
||||||
|
#define B01011001 89
|
||||||
|
#define B1011010 90
|
||||||
|
#define B01011010 90
|
||||||
|
#define B1011011 91
|
||||||
|
#define B01011011 91
|
||||||
|
#define B1011100 92
|
||||||
|
#define B01011100 92
|
||||||
|
#define B1011101 93
|
||||||
|
#define B01011101 93
|
||||||
|
#define B1011110 94
|
||||||
|
#define B01011110 94
|
||||||
|
#define B1011111 95
|
||||||
|
#define B01011111 95
|
||||||
|
#define B1100000 96
|
||||||
|
#define B01100000 96
|
||||||
|
#define B1100001 97
|
||||||
|
#define B01100001 97
|
||||||
|
#define B1100010 98
|
||||||
|
#define B01100010 98
|
||||||
|
#define B1100011 99
|
||||||
|
#define B01100011 99
|
||||||
|
#define B1100100 100
|
||||||
|
#define B01100100 100
|
||||||
|
#define B1100101 101
|
||||||
|
#define B01100101 101
|
||||||
|
#define B1100110 102
|
||||||
|
#define B01100110 102
|
||||||
|
#define B1100111 103
|
||||||
|
#define B01100111 103
|
||||||
|
#define B1101000 104
|
||||||
|
#define B01101000 104
|
||||||
|
#define B1101001 105
|
||||||
|
#define B01101001 105
|
||||||
|
#define B1101010 106
|
||||||
|
#define B01101010 106
|
||||||
|
#define B1101011 107
|
||||||
|
#define B01101011 107
|
||||||
|
#define B1101100 108
|
||||||
|
#define B01101100 108
|
||||||
|
#define B1101101 109
|
||||||
|
#define B01101101 109
|
||||||
|
#define B1101110 110
|
||||||
|
#define B01101110 110
|
||||||
|
#define B1101111 111
|
||||||
|
#define B01101111 111
|
||||||
|
#define B1110000 112
|
||||||
|
#define B01110000 112
|
||||||
|
#define B1110001 113
|
||||||
|
#define B01110001 113
|
||||||
|
#define B1110010 114
|
||||||
|
#define B01110010 114
|
||||||
|
#define B1110011 115
|
||||||
|
#define B01110011 115
|
||||||
|
#define B1110100 116
|
||||||
|
#define B01110100 116
|
||||||
|
#define B1110101 117
|
||||||
|
#define B01110101 117
|
||||||
|
#define B1110110 118
|
||||||
|
#define B01110110 118
|
||||||
|
#define B1110111 119
|
||||||
|
#define B01110111 119
|
||||||
|
#define B1111000 120
|
||||||
|
#define B01111000 120
|
||||||
|
#define B1111001 121
|
||||||
|
#define B01111001 121
|
||||||
|
#define B1111010 122
|
||||||
|
#define B01111010 122
|
||||||
|
#define B1111011 123
|
||||||
|
#define B01111011 123
|
||||||
|
#define B1111100 124
|
||||||
|
#define B01111100 124
|
||||||
|
#define B1111101 125
|
||||||
|
#define B01111101 125
|
||||||
|
#define B1111110 126
|
||||||
|
#define B01111110 126
|
||||||
|
#define B1111111 127
|
||||||
|
#define B01111111 127
|
||||||
|
#define B10000000 128
|
||||||
|
#define B10000001 129
|
||||||
|
#define B10000010 130
|
||||||
|
#define B10000011 131
|
||||||
|
#define B10000100 132
|
||||||
|
#define B10000101 133
|
||||||
|
#define B10000110 134
|
||||||
|
#define B10000111 135
|
||||||
|
#define B10001000 136
|
||||||
|
#define B10001001 137
|
||||||
|
#define B10001010 138
|
||||||
|
#define B10001011 139
|
||||||
|
#define B10001100 140
|
||||||
|
#define B10001101 141
|
||||||
|
#define B10001110 142
|
||||||
|
#define B10001111 143
|
||||||
|
#define B10010000 144
|
||||||
|
#define B10010001 145
|
||||||
|
#define B10010010 146
|
||||||
|
#define B10010011 147
|
||||||
|
#define B10010100 148
|
||||||
|
#define B10010101 149
|
||||||
|
#define B10010110 150
|
||||||
|
#define B10010111 151
|
||||||
|
#define B10011000 152
|
||||||
|
#define B10011001 153
|
||||||
|
#define B10011010 154
|
||||||
|
#define B10011011 155
|
||||||
|
#define B10011100 156
|
||||||
|
#define B10011101 157
|
||||||
|
#define B10011110 158
|
||||||
|
#define B10011111 159
|
||||||
|
#define B10100000 160
|
||||||
|
#define B10100001 161
|
||||||
|
#define B10100010 162
|
||||||
|
#define B10100011 163
|
||||||
|
#define B10100100 164
|
||||||
|
#define B10100101 165
|
||||||
|
#define B10100110 166
|
||||||
|
#define B10100111 167
|
||||||
|
#define B10101000 168
|
||||||
|
#define B10101001 169
|
||||||
|
#define B10101010 170
|
||||||
|
#define B10101011 171
|
||||||
|
#define B10101100 172
|
||||||
|
#define B10101101 173
|
||||||
|
#define B10101110 174
|
||||||
|
#define B10101111 175
|
||||||
|
#define B10110000 176
|
||||||
|
#define B10110001 177
|
||||||
|
#define B10110010 178
|
||||||
|
#define B10110011 179
|
||||||
|
#define B10110100 180
|
||||||
|
#define B10110101 181
|
||||||
|
#define B10110110 182
|
||||||
|
#define B10110111 183
|
||||||
|
#define B10111000 184
|
||||||
|
#define B10111001 185
|
||||||
|
#define B10111010 186
|
||||||
|
#define B10111011 187
|
||||||
|
#define B10111100 188
|
||||||
|
#define B10111101 189
|
||||||
|
#define B10111110 190
|
||||||
|
#define B10111111 191
|
||||||
|
#define B11000000 192
|
||||||
|
#define B11000001 193
|
||||||
|
#define B11000010 194
|
||||||
|
#define B11000011 195
|
||||||
|
#define B11000100 196
|
||||||
|
#define B11000101 197
|
||||||
|
#define B11000110 198
|
||||||
|
#define B11000111 199
|
||||||
|
#define B11001000 200
|
||||||
|
#define B11001001 201
|
||||||
|
#define B11001010 202
|
||||||
|
#define B11001011 203
|
||||||
|
#define B11001100 204
|
||||||
|
#define B11001101 205
|
||||||
|
#define B11001110 206
|
||||||
|
#define B11001111 207
|
||||||
|
#define B11010000 208
|
||||||
|
#define B11010001 209
|
||||||
|
#define B11010010 210
|
||||||
|
#define B11010011 211
|
||||||
|
#define B11010100 212
|
||||||
|
#define B11010101 213
|
||||||
|
#define B11010110 214
|
||||||
|
#define B11010111 215
|
||||||
|
#define B11011000 216
|
||||||
|
#define B11011001 217
|
||||||
|
#define B11011010 218
|
||||||
|
#define B11011011 219
|
||||||
|
#define B11011100 220
|
||||||
|
#define B11011101 221
|
||||||
|
#define B11011110 222
|
||||||
|
#define B11011111 223
|
||||||
|
#define B11100000 224
|
||||||
|
#define B11100001 225
|
||||||
|
#define B11100010 226
|
||||||
|
#define B11100011 227
|
||||||
|
#define B11100100 228
|
||||||
|
#define B11100101 229
|
||||||
|
#define B11100110 230
|
||||||
|
#define B11100111 231
|
||||||
|
#define B11101000 232
|
||||||
|
#define B11101001 233
|
||||||
|
#define B11101010 234
|
||||||
|
#define B11101011 235
|
||||||
|
#define B11101100 236
|
||||||
|
#define B11101101 237
|
||||||
|
#define B11101110 238
|
||||||
|
#define B11101111 239
|
||||||
|
#define B11110000 240
|
||||||
|
#define B11110001 241
|
||||||
|
#define B11110010 242
|
||||||
|
#define B11110011 243
|
||||||
|
#define B11110100 244
|
||||||
|
#define B11110101 245
|
||||||
|
#define B11110110 246
|
||||||
|
#define B11110111 247
|
||||||
|
#define B11111000 248
|
||||||
|
#define B11111001 249
|
||||||
|
#define B11111010 250
|
||||||
|
#define B11111011 251
|
||||||
|
#define B11111100 252
|
||||||
|
#define B11111101 253
|
||||||
|
#define B11111110 254
|
||||||
|
#define B11111111 255
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,196 @@
|
|||||||
|
/*
|
||||||
|
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"
|
||||||
|
|
||||||
|
cbuf::cbuf(size_t size) :
|
||||||
|
next(NULL), _size(size+1), _buf(new char[size+1]), _bufend(_buf + size + 1), _begin(_buf), _end(_begin)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
cbuf::~cbuf()
|
||||||
|
{
|
||||||
|
delete[] _buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::resizeAdd(size_t addSize)
|
||||||
|
{
|
||||||
|
return resize(_size + addSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::resize(size_t newSize)
|
||||||
|
{
|
||||||
|
|
||||||
|
size_t bytes_available = available();
|
||||||
|
newSize += 1;
|
||||||
|
// not lose any data
|
||||||
|
// if data can be lost use remove or flush before resize
|
||||||
|
if((newSize < bytes_available) || (newSize == _size)) {
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
char *newbuf = new char[newSize];
|
||||||
|
char *oldbuf = _buf;
|
||||||
|
|
||||||
|
if(!newbuf) {
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(_buf) {
|
||||||
|
read(newbuf, bytes_available);
|
||||||
|
memset((newbuf + bytes_available), 0x00, (newSize - bytes_available));
|
||||||
|
}
|
||||||
|
|
||||||
|
_begin = newbuf;
|
||||||
|
_end = newbuf + bytes_available;
|
||||||
|
_bufend = newbuf + newSize;
|
||||||
|
_size = newSize;
|
||||||
|
|
||||||
|
_buf = newbuf;
|
||||||
|
delete[] oldbuf;
|
||||||
|
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::available() const
|
||||||
|
{
|
||||||
|
if(_end >= _begin) {
|
||||||
|
return _end - _begin;
|
||||||
|
}
|
||||||
|
return _size - (_begin - _end);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::size()
|
||||||
|
{
|
||||||
|
return _size;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::room() const
|
||||||
|
{
|
||||||
|
if(_end >= _begin) {
|
||||||
|
return _size - (_end - _begin) - 1;
|
||||||
|
}
|
||||||
|
return _begin - _end - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cbuf::peek()
|
||||||
|
{
|
||||||
|
if(empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<int>(*_begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::peek(char *dst, size_t size)
|
||||||
|
{
|
||||||
|
size_t bytes_available = available();
|
||||||
|
size_t size_to_read = (size < bytes_available) ? size : bytes_available;
|
||||||
|
size_t size_read = size_to_read;
|
||||||
|
char * begin = _begin;
|
||||||
|
if(_end < _begin && size_to_read > (size_t) (_bufend - _begin)) {
|
||||||
|
size_t top_size = _bufend - _begin;
|
||||||
|
memcpy(dst, _begin, top_size);
|
||||||
|
begin = _buf;
|
||||||
|
size_to_read -= top_size;
|
||||||
|
dst += top_size;
|
||||||
|
}
|
||||||
|
memcpy(dst, begin, size_to_read);
|
||||||
|
return size_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cbuf::read()
|
||||||
|
{
|
||||||
|
if(empty()) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
char result = *_begin;
|
||||||
|
_begin = wrap_if_bufend(_begin + 1);
|
||||||
|
return static_cast<int>(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::read(char* dst, size_t size)
|
||||||
|
{
|
||||||
|
size_t bytes_available = available();
|
||||||
|
size_t size_to_read = (size < bytes_available) ? size : bytes_available;
|
||||||
|
size_t size_read = size_to_read;
|
||||||
|
if(_end < _begin && size_to_read > (size_t) (_bufend - _begin)) {
|
||||||
|
size_t top_size = _bufend - _begin;
|
||||||
|
memcpy(dst, _begin, top_size);
|
||||||
|
_begin = _buf;
|
||||||
|
size_to_read -= top_size;
|
||||||
|
dst += top_size;
|
||||||
|
}
|
||||||
|
memcpy(dst, _begin, size_to_read);
|
||||||
|
_begin = wrap_if_bufend(_begin + size_to_read);
|
||||||
|
return size_read;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::write(char c)
|
||||||
|
{
|
||||||
|
if(full()) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*_end = c;
|
||||||
|
_end = wrap_if_bufend(_end + 1);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::write(const char* src, size_t size)
|
||||||
|
{
|
||||||
|
size_t bytes_available = room();
|
||||||
|
size_t size_to_write = (size < bytes_available) ? size : bytes_available;
|
||||||
|
size_t size_written = size_to_write;
|
||||||
|
if(_end >= _begin && size_to_write > (size_t) (_bufend - _end)) {
|
||||||
|
size_t top_size = _bufend - _end;
|
||||||
|
memcpy(_end, src, top_size);
|
||||||
|
_end = _buf;
|
||||||
|
size_to_write -= top_size;
|
||||||
|
src += top_size;
|
||||||
|
}
|
||||||
|
memcpy(_end, src, size_to_write);
|
||||||
|
_end = wrap_if_bufend(_end + size_to_write);
|
||||||
|
return size_written;
|
||||||
|
}
|
||||||
|
|
||||||
|
void cbuf::flush()
|
||||||
|
{
|
||||||
|
_begin = _buf;
|
||||||
|
_end = _buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t cbuf::remove(size_t size)
|
||||||
|
{
|
||||||
|
size_t bytes_available = available();
|
||||||
|
if(size >= bytes_available) {
|
||||||
|
flush();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t size_to_remove = (size < bytes_available) ? size : bytes_available;
|
||||||
|
if(_end < _begin && size_to_remove > (size_t) (_bufend - _begin)) {
|
||||||
|
size_t top_size = _bufend - _begin;
|
||||||
|
_begin = _buf;
|
||||||
|
size_to_remove -= top_size;
|
||||||
|
}
|
||||||
|
_begin = wrap_if_bufend(_begin + size_to_remove);
|
||||||
|
return available();
|
||||||
|
}
|
@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
cbuf.h - 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef __cbuf_h
|
||||||
|
#define __cbuf_h
|
||||||
|
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
class cbuf
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
cbuf(size_t size);
|
||||||
|
~cbuf();
|
||||||
|
|
||||||
|
size_t resizeAdd(size_t addSize);
|
||||||
|
size_t resize(size_t newSize);
|
||||||
|
size_t available() const;
|
||||||
|
size_t size();
|
||||||
|
|
||||||
|
size_t room() const;
|
||||||
|
|
||||||
|
inline bool empty() const
|
||||||
|
{
|
||||||
|
return _begin == _end;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool full() const
|
||||||
|
{
|
||||||
|
return wrap_if_bufend(_end + 1) == _begin;
|
||||||
|
}
|
||||||
|
|
||||||
|
int peek();
|
||||||
|
size_t peek(char *dst, size_t size);
|
||||||
|
|
||||||
|
int read();
|
||||||
|
size_t read(char* dst, size_t size);
|
||||||
|
|
||||||
|
size_t write(char c);
|
||||||
|
size_t write(const char* src, size_t size);
|
||||||
|
|
||||||
|
void flush();
|
||||||
|
size_t remove(size_t size);
|
||||||
|
|
||||||
|
cbuf *next;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
inline char* wrap_if_bufend(char* ptr) const
|
||||||
|
{
|
||||||
|
return (ptr == _bufend) ? _buf : ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t _size;
|
||||||
|
char* _buf;
|
||||||
|
const char* _bufend;
|
||||||
|
char* _begin;
|
||||||
|
char* _end;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif//__cbuf_h
|
@ -0,0 +1,4 @@
|
|||||||
|
#define ARDUINO_ESP32_GIT_VER 0x725146d1
|
||||||
|
#define ARDUINO_ESP32_GIT_DESC 2.0.12
|
||||||
|
#define ARDUINO_ESP32_RELEASE_2_0_12
|
||||||
|
#define ARDUINO_ESP32_RELEASE "2_0_12"
|
@ -0,0 +1,281 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal-adc.h"
|
||||||
|
#include "driver/adc.h"
|
||||||
|
#include "esp_adc_cal.h"
|
||||||
|
|
||||||
|
#if SOC_DAC_SUPPORTED //ESP32, ESP32S2
|
||||||
|
#include "soc/dac_channel.h"
|
||||||
|
#include "soc/sens_reg.h"
|
||||||
|
#include "soc/rtc_io_reg.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define DEFAULT_VREF 1100
|
||||||
|
|
||||||
|
static uint8_t __analogAttenuation = 3;//11db
|
||||||
|
static uint8_t __analogWidth = ADC_WIDTH_MAX - 1; //3 for ESP32/ESP32C3; 4 for ESP32S2
|
||||||
|
static uint8_t __analogReturnedWidth = SOC_ADC_MAX_BITWIDTH; //12 for ESP32/ESP32C3; 13 for ESP32S2
|
||||||
|
static uint8_t __analogClockDiv = 1;
|
||||||
|
static adc_attenuation_t __pin_attenuation[SOC_GPIO_PIN_COUNT];
|
||||||
|
|
||||||
|
static uint16_t __analogVRef = 0;
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
static uint8_t __analogVRefPin = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static inline uint16_t mapResolution(uint16_t value)
|
||||||
|
{
|
||||||
|
uint8_t from = __analogWidth + 9;
|
||||||
|
if (from == __analogReturnedWidth) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
if (from > __analogReturnedWidth) {
|
||||||
|
return value >> (from - __analogReturnedWidth);
|
||||||
|
}
|
||||||
|
return value << (__analogReturnedWidth - from);
|
||||||
|
}
|
||||||
|
|
||||||
|
void __analogSetClockDiv(uint8_t clockDiv){
|
||||||
|
if(!clockDiv){
|
||||||
|
clockDiv = 1;
|
||||||
|
}
|
||||||
|
__analogClockDiv = clockDiv;
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 || CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
adc_set_clk_div(__analogClockDiv);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void __analogSetAttenuation(adc_attenuation_t attenuation)
|
||||||
|
{
|
||||||
|
__analogAttenuation = attenuation & 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
void __analogSetWidth(uint8_t bits){
|
||||||
|
if(bits < 9){
|
||||||
|
bits = 9;
|
||||||
|
} else if(bits > 12){
|
||||||
|
bits = 12;
|
||||||
|
}
|
||||||
|
__analogWidth = bits - 9;
|
||||||
|
adc1_config_width(__analogWidth);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void __analogInit(){
|
||||||
|
static bool initialized = false;
|
||||||
|
if(initialized){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
initialized = true;
|
||||||
|
__analogSetClockDiv(__analogClockDiv);
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
__analogSetWidth(__analogWidth + 9);//in bits
|
||||||
|
#endif
|
||||||
|
for(int i=0; i<SOC_GPIO_PIN_COUNT; i++){
|
||||||
|
__pin_attenuation[i] = ADC_ATTENDB_MAX;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void __analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation)
|
||||||
|
{
|
||||||
|
int8_t channel = digitalPinToAnalogChannel(pin);
|
||||||
|
if(channel < 0 || attenuation > 3){
|
||||||
|
return ;
|
||||||
|
}
|
||||||
|
if(channel > (SOC_ADC_MAX_CHANNEL_NUM - 1)){
|
||||||
|
adc2_config_channel_atten(channel - SOC_ADC_MAX_CHANNEL_NUM, attenuation);
|
||||||
|
} else {
|
||||||
|
adc1_config_channel_atten(channel, attenuation);
|
||||||
|
}
|
||||||
|
__analogInit();
|
||||||
|
if((__pin_attenuation[pin] != ADC_ATTENDB_MAX) || (attenuation != __analogAttenuation)){
|
||||||
|
__pin_attenuation[pin] = attenuation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool __adcAttachPin(uint8_t pin){
|
||||||
|
int8_t channel = digitalPinToAnalogChannel(pin);
|
||||||
|
if(channel < 0){
|
||||||
|
log_e("Pin %u is not ADC pin!", pin);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
__analogInit();
|
||||||
|
int8_t pad = digitalPinToTouchChannel(pin);
|
||||||
|
if(pad >= 0){
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
uint32_t touch = READ_PERI_REG(SENS_SAR_TOUCH_ENABLE_REG);
|
||||||
|
if(touch & (1 << pad)){
|
||||||
|
touch &= ~((1 << (pad + SENS_TOUCH_PAD_OUTEN2_S))
|
||||||
|
| (1 << (pad + SENS_TOUCH_PAD_OUTEN1_S))
|
||||||
|
| (1 << (pad + SENS_TOUCH_PAD_WORKEN_S)));
|
||||||
|
WRITE_PERI_REG(SENS_SAR_TOUCH_ENABLE_REG, touch);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#if SOC_DAC_SUPPORTED
|
||||||
|
else if(pin == DAC_CHANNEL_1_GPIO_NUM){
|
||||||
|
CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC1_REG, RTC_IO_PDAC1_XPD_DAC | RTC_IO_PDAC1_DAC_XPD_FORCE);//stop dac1
|
||||||
|
} else if(pin == DAC_CHANNEL_2_GPIO_NUM){
|
||||||
|
CLEAR_PERI_REG_MASK(RTC_IO_PAD_DAC2_REG, RTC_IO_PDAC2_XPD_DAC | RTC_IO_PDAC2_DAC_XPD_FORCE);//stop dac2
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
pinMode(pin, ANALOG);
|
||||||
|
__analogSetPinAttenuation(pin, (__pin_attenuation[pin] != ADC_ATTENDB_MAX)?__pin_attenuation[pin]:__analogAttenuation);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void __analogReadResolution(uint8_t bits)
|
||||||
|
{
|
||||||
|
if(!bits || bits > 16){
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
__analogReturnedWidth = bits;
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
__analogSetWidth(bits); // hadware from 9 to 12
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t __analogRead(uint8_t pin)
|
||||||
|
{
|
||||||
|
int8_t channel = digitalPinToAnalogChannel(pin);
|
||||||
|
int value = 0;
|
||||||
|
esp_err_t r = ESP_OK;
|
||||||
|
if(channel < 0){
|
||||||
|
log_e("Pin %u is not ADC pin!", pin);
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
__adcAttachPin(pin);
|
||||||
|
if(channel > (SOC_ADC_MAX_CHANNEL_NUM - 1)){
|
||||||
|
channel -= SOC_ADC_MAX_CHANNEL_NUM;
|
||||||
|
r = adc2_get_raw( channel, __analogWidth, &value);
|
||||||
|
if ( r == ESP_OK ) {
|
||||||
|
return mapResolution(value);
|
||||||
|
} else if ( r == ESP_ERR_INVALID_STATE ) {
|
||||||
|
log_e("GPIO%u: %s: ADC2 not initialized yet.", pin, esp_err_to_name(r));
|
||||||
|
} else if ( r == ESP_ERR_TIMEOUT ) {
|
||||||
|
log_e("GPIO%u: %s: ADC2 is in use by Wi-Fi. Please see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/adc.html#adc-limitations for more info", pin, esp_err_to_name(r));
|
||||||
|
} else {
|
||||||
|
log_e("GPIO%u: %s", pin, esp_err_to_name(r));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
value = adc1_get_raw(channel);
|
||||||
|
return mapResolution(value);
|
||||||
|
}
|
||||||
|
return mapResolution(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t __analogReadMilliVolts(uint8_t pin){
|
||||||
|
int8_t channel = digitalPinToAnalogChannel(pin);
|
||||||
|
if(channel < 0){
|
||||||
|
log_e("Pin %u is not ADC pin!", pin);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!__analogVRef){
|
||||||
|
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_TP) == ESP_OK) {
|
||||||
|
log_d("eFuse Two Point: Supported");
|
||||||
|
__analogVRef = DEFAULT_VREF;
|
||||||
|
}
|
||||||
|
if (esp_adc_cal_check_efuse(ESP_ADC_CAL_VAL_EFUSE_VREF) == ESP_OK) {
|
||||||
|
log_d("eFuse Vref: Supported");
|
||||||
|
__analogVRef = DEFAULT_VREF;
|
||||||
|
}
|
||||||
|
if(!__analogVRef){
|
||||||
|
__analogVRef = DEFAULT_VREF;
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
if(__analogVRefPin){
|
||||||
|
esp_adc_cal_characteristics_t chars;
|
||||||
|
if(adc_vref_to_gpio(ADC_UNIT_2, __analogVRefPin) == ESP_OK){
|
||||||
|
__analogVRef = __analogRead(__analogVRefPin);
|
||||||
|
esp_adc_cal_characterize(1, __analogAttenuation, __analogWidth, DEFAULT_VREF, &chars);
|
||||||
|
__analogVRef = esp_adc_cal_raw_to_voltage(__analogVRef, &chars);
|
||||||
|
log_d("Vref to GPIO%u: %u", __analogVRefPin, __analogVRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
uint8_t unit = 1;
|
||||||
|
if(channel > (SOC_ADC_MAX_CHANNEL_NUM - 1)){
|
||||||
|
unit = 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t adc_reading = __analogRead(pin);
|
||||||
|
|
||||||
|
uint8_t atten = __analogAttenuation;
|
||||||
|
if (__pin_attenuation[pin] != ADC_ATTENDB_MAX){
|
||||||
|
atten = __pin_attenuation[pin];
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_adc_cal_characteristics_t chars = {};
|
||||||
|
esp_adc_cal_value_t val_type = esp_adc_cal_characterize(unit, atten, __analogWidth, __analogVRef, &chars);
|
||||||
|
|
||||||
|
static bool print_chars_info = true;
|
||||||
|
if(print_chars_info)
|
||||||
|
{
|
||||||
|
if (val_type == ESP_ADC_CAL_VAL_EFUSE_TP) {
|
||||||
|
log_i("ADC%u: Characterized using Two Point Value: %u\n", unit, chars.vref);
|
||||||
|
}
|
||||||
|
else if (val_type == ESP_ADC_CAL_VAL_EFUSE_VREF) {
|
||||||
|
log_i("ADC%u: Characterized using eFuse Vref: %u\n", unit, chars.vref);
|
||||||
|
}
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
else if(__analogVRef != DEFAULT_VREF){
|
||||||
|
log_i("ADC%u: Characterized using Vref to GPIO%u: %u\n", unit, __analogVRefPin, chars.vref);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
else {
|
||||||
|
log_i("ADC%u: Characterized using Default Vref: %u\n", unit, chars.vref);
|
||||||
|
}
|
||||||
|
print_chars_info = false;
|
||||||
|
}
|
||||||
|
return esp_adc_cal_raw_to_voltage((uint32_t)adc_reading, &chars);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
|
||||||
|
void __analogSetVRefPin(uint8_t pin){
|
||||||
|
if(pin <25 || pin > 27){
|
||||||
|
pin = 0;
|
||||||
|
}
|
||||||
|
__analogVRefPin = pin;
|
||||||
|
}
|
||||||
|
|
||||||
|
int __hallRead() //hall sensor using idf read
|
||||||
|
{
|
||||||
|
pinMode(36, ANALOG);
|
||||||
|
pinMode(39, ANALOG);
|
||||||
|
__analogSetWidth(12);
|
||||||
|
return hall_sensor_read();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
extern uint16_t analogRead(uint8_t pin) __attribute__ ((weak, alias("__analogRead")));
|
||||||
|
extern uint32_t analogReadMilliVolts(uint8_t pin) __attribute__ ((weak, alias("__analogReadMilliVolts")));
|
||||||
|
extern void analogReadResolution(uint8_t bits) __attribute__ ((weak, alias("__analogReadResolution")));
|
||||||
|
extern void analogSetClockDiv(uint8_t clockDiv) __attribute__ ((weak, alias("__analogSetClockDiv")));
|
||||||
|
extern void analogSetAttenuation(adc_attenuation_t attenuation) __attribute__ ((weak, alias("__analogSetAttenuation")));
|
||||||
|
extern void analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation) __attribute__ ((weak, alias("__analogSetPinAttenuation")));
|
||||||
|
|
||||||
|
extern bool adcAttachPin(uint8_t pin) __attribute__ ((weak, alias("__adcAttachPin")));
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
extern void analogSetVRefPin(uint8_t pin) __attribute__ ((weak, alias("__analogSetVRefPin")));
|
||||||
|
extern void analogSetWidth(uint8_t bits) __attribute__ ((weak, alias("__analogSetWidth")));
|
||||||
|
extern int hallRead() __attribute__ ((weak, alias("__hallRead")));
|
||||||
|
#endif
|
@ -0,0 +1,104 @@
|
|||||||
|
/*
|
||||||
|
Arduino.h - Main include file for the Arduino SDK
|
||||||
|
Copyright (c) 2005-2013 Arduino Team. All right 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 MAIN_ESP32_HAL_ADC_H_
|
||||||
|
#define MAIN_ESP32_HAL_ADC_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
ADC_0db,
|
||||||
|
ADC_2_5db,
|
||||||
|
ADC_6db,
|
||||||
|
ADC_11db,
|
||||||
|
ADC_ATTENDB_MAX
|
||||||
|
} adc_attenuation_t;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get ADC value for pin
|
||||||
|
* */
|
||||||
|
uint16_t analogRead(uint8_t pin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get MilliVolts value for pin
|
||||||
|
* */
|
||||||
|
uint32_t analogReadMilliVolts(uint8_t pin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the resolution of analogRead return values. Default is 12 bits (range from 0 to 4096).
|
||||||
|
* If between 9 and 12, it will equal the set hardware resolution, else value will be shifted.
|
||||||
|
* Range is 1 - 16
|
||||||
|
*
|
||||||
|
* Note: compatibility with Arduino SAM
|
||||||
|
*/
|
||||||
|
void analogReadResolution(uint8_t bits);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the divider for the ADC clock.
|
||||||
|
* Default is 1
|
||||||
|
* Range is 1 - 255
|
||||||
|
* */
|
||||||
|
void analogSetClockDiv(uint8_t clockDiv);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the attenuation for all channels
|
||||||
|
* Default is 11db
|
||||||
|
* */
|
||||||
|
void analogSetAttenuation(adc_attenuation_t attenuation);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the attenuation for particular pin
|
||||||
|
* Default is 11db
|
||||||
|
* */
|
||||||
|
void analogSetPinAttenuation(uint8_t pin, adc_attenuation_t attenuation);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Attach pin to ADC (will also clear any other analog mode that could be on)
|
||||||
|
* */
|
||||||
|
bool adcAttachPin(uint8_t pin);
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
/*
|
||||||
|
* Sets the sample bits and read resolution
|
||||||
|
* Default is 12bit (0 - 4095)
|
||||||
|
* Range is 9 - 12
|
||||||
|
* */
|
||||||
|
void analogSetWidth(uint8_t bits);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set pin to use for ADC calibration if the esp is not already calibrated (25, 26 or 27)
|
||||||
|
* */
|
||||||
|
void analogSetVRefPin(uint8_t pin);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Get value for HALL sensor (without LNA)
|
||||||
|
* connected to pins 36(SVP) and 39(SVN)
|
||||||
|
* */
|
||||||
|
int hallRead();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MAIN_ESP32_HAL_ADC_H_ */
|
@ -0,0 +1,105 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal-bt.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_BT_ENABLED
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
bool btInUse(){ return true; }
|
||||||
|
#else
|
||||||
|
// user may want to change it to free resources
|
||||||
|
__attribute__((weak)) bool btInUse(){ return true; }
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp_bt.h"
|
||||||
|
|
||||||
|
#ifdef CONFIG_BTDM_CONTROLLER_MODE_BTDM
|
||||||
|
#define BT_MODE ESP_BT_MODE_BTDM
|
||||||
|
#elif defined(CONFIG_BTDM_CONTROLLER_MODE_BR_EDR_ONLY)
|
||||||
|
#define BT_MODE ESP_BT_MODE_CLASSIC_BT
|
||||||
|
#else
|
||||||
|
#define BT_MODE ESP_BT_MODE_BLE
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool btStarted(){
|
||||||
|
return (esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool btStart(){
|
||||||
|
esp_bt_controller_config_t cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE){
|
||||||
|
esp_bt_controller_init(&cfg);
|
||||||
|
while(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE){}
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED){
|
||||||
|
if (esp_bt_controller_enable(BT_MODE)) {
|
||||||
|
log_e("BT Enable failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log_e("BT Start failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool btStop(){
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_IDLE){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED){
|
||||||
|
if (esp_bt_controller_disable()) {
|
||||||
|
log_e("BT Disable failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
while(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_ENABLED);
|
||||||
|
}
|
||||||
|
if(esp_bt_controller_get_status() == ESP_BT_CONTROLLER_STATUS_INITED){
|
||||||
|
if (esp_bt_controller_deinit()) {
|
||||||
|
log_e("BT deint failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vTaskDelay(1);
|
||||||
|
if (esp_bt_controller_get_status() != ESP_BT_CONTROLLER_STATUS_IDLE) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
log_e("BT Stop failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else // CONFIG_BT_ENABLED
|
||||||
|
bool btStarted()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool btStart()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool btStop()
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CONFIG_BT_ENABLED
|
||||||
|
|
@ -0,0 +1,32 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef _ESP32_ESP32_HAL_BT_H_
|
||||||
|
#define _ESP32_ESP32_HAL_BT_H_
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
bool btStarted();
|
||||||
|
bool btStart();
|
||||||
|
bool btStop();
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _ESP32_ESP32_HAL_BT_H_ */
|
@ -0,0 +1,262 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "esp_attr.h"
|
||||||
|
#include "esp_log.h"
|
||||||
|
#include "soc/rtc.h"
|
||||||
|
#include "soc/rtc_cntl_reg.h"
|
||||||
|
#include "soc/apb_ctrl_reg.h"
|
||||||
|
#include "soc/efuse_reg.h"
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "esp32-hal-cpu.h"
|
||||||
|
|
||||||
|
#include "esp_system.h"
|
||||||
|
#ifdef ESP_IDF_VERSION_MAJOR // IDF 4+
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32 // ESP32/PICO-D4
|
||||||
|
#include "freertos/xtensa_timer.h"
|
||||||
|
#include "esp32/rom/rtc.h"
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
#include "freertos/xtensa_timer.h"
|
||||||
|
#include "esp32s2/rom/rtc.h"
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
#include "freertos/xtensa_timer.h"
|
||||||
|
#include "esp32s3/rom/rtc.h"
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
#include "esp32c3/rom/rtc.h"
|
||||||
|
#else
|
||||||
|
#error Target CONFIG_IDF_TARGET is not supported
|
||||||
|
#endif
|
||||||
|
#else // ESP32 Before IDF 4.0
|
||||||
|
#include "rom/rtc.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef struct apb_change_cb_s {
|
||||||
|
struct apb_change_cb_s * prev;
|
||||||
|
struct apb_change_cb_s * next;
|
||||||
|
void * arg;
|
||||||
|
apb_change_cb_t cb;
|
||||||
|
} apb_change_t;
|
||||||
|
|
||||||
|
|
||||||
|
static apb_change_t * apb_change_callbacks = NULL;
|
||||||
|
static xSemaphoreHandle apb_change_lock = NULL;
|
||||||
|
|
||||||
|
static void initApbChangeCallback(){
|
||||||
|
static volatile bool initialized = false;
|
||||||
|
if(!initialized){
|
||||||
|
initialized = true;
|
||||||
|
apb_change_lock = xSemaphoreCreateMutex();
|
||||||
|
if(!apb_change_lock){
|
||||||
|
initialized = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void triggerApbChangeCallback(apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb){
|
||||||
|
initApbChangeCallback();
|
||||||
|
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
|
||||||
|
apb_change_t * r = apb_change_callbacks;
|
||||||
|
if( r != NULL ){
|
||||||
|
if(ev_type == APB_BEFORE_CHANGE )
|
||||||
|
while(r != NULL){
|
||||||
|
r->cb(r->arg, ev_type, old_apb, new_apb);
|
||||||
|
r=r->next;
|
||||||
|
}
|
||||||
|
else { // run backwards through chain
|
||||||
|
while(r->next != NULL) r = r->next; // find first added
|
||||||
|
while( r != NULL){
|
||||||
|
r->cb(r->arg, ev_type, old_apb, new_apb);
|
||||||
|
r=r->prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool addApbChangeCallback(void * arg, apb_change_cb_t cb){
|
||||||
|
initApbChangeCallback();
|
||||||
|
apb_change_t * c = (apb_change_t*)malloc(sizeof(apb_change_t));
|
||||||
|
if(!c){
|
||||||
|
log_e("Callback Object Malloc Failed");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
c->next = NULL;
|
||||||
|
c->prev = NULL;
|
||||||
|
c->arg = arg;
|
||||||
|
c->cb = cb;
|
||||||
|
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
|
||||||
|
if(apb_change_callbacks == NULL){
|
||||||
|
apb_change_callbacks = c;
|
||||||
|
} else {
|
||||||
|
apb_change_t * r = apb_change_callbacks;
|
||||||
|
// look for duplicate callbacks
|
||||||
|
while( (r != NULL ) && !((r->cb == cb) && ( r->arg == arg))) r = r->next;
|
||||||
|
if (r) {
|
||||||
|
log_e("duplicate func=%8p arg=%8p",c->cb,c->arg);
|
||||||
|
free(c);
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
c->next = apb_change_callbacks;
|
||||||
|
apb_change_callbacks-> prev = c;
|
||||||
|
apb_change_callbacks = c;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool removeApbChangeCallback(void * arg, apb_change_cb_t cb){
|
||||||
|
initApbChangeCallback();
|
||||||
|
xSemaphoreTake(apb_change_lock, portMAX_DELAY);
|
||||||
|
apb_change_t * r = apb_change_callbacks;
|
||||||
|
// look for matching callback
|
||||||
|
while( (r != NULL ) && !((r->cb == cb) && ( r->arg == arg))) r = r->next;
|
||||||
|
if ( r == NULL ) {
|
||||||
|
log_e("not found func=%8p arg=%8p",cb,arg);
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// patch links
|
||||||
|
if(r->prev) r->prev->next = r->next;
|
||||||
|
else { // this is first link
|
||||||
|
apb_change_callbacks = r->next;
|
||||||
|
}
|
||||||
|
if(r->next) r->next->prev = r->prev;
|
||||||
|
free(r);
|
||||||
|
}
|
||||||
|
xSemaphoreGive(apb_change_lock);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t calculateApb(rtc_cpu_freq_config_t * conf){
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return APB_CLK_FREQ;
|
||||||
|
#else
|
||||||
|
if(conf->freq_mhz >= 80){
|
||||||
|
return 80 * MHZ;
|
||||||
|
}
|
||||||
|
return (conf->source_freq_mhz * MHZ) / conf->div;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void esp_timer_impl_update_apb_freq(uint32_t apb_ticks_per_us); //private in IDF
|
||||||
|
|
||||||
|
bool setCpuFrequencyMhz(uint32_t cpu_freq_mhz){
|
||||||
|
rtc_cpu_freq_config_t conf, cconf;
|
||||||
|
uint32_t capb, apb;
|
||||||
|
//Get XTAL Frequency and calculate min CPU MHz
|
||||||
|
rtc_xtal_freq_t xtal = rtc_clk_xtal_freq_get();
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
if(xtal > RTC_XTAL_FREQ_AUTO){
|
||||||
|
if(xtal < RTC_XTAL_FREQ_40M) {
|
||||||
|
if(cpu_freq_mhz <= xtal && cpu_freq_mhz != xtal && cpu_freq_mhz != (xtal/2)){
|
||||||
|
log_e("Bad frequency: %u MHz! Options are: 240, 160, 80, %u and %u MHz", cpu_freq_mhz, xtal, xtal/2);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else if(cpu_freq_mhz <= xtal && cpu_freq_mhz != xtal && cpu_freq_mhz != (xtal/2) && cpu_freq_mhz != (xtal/4)){
|
||||||
|
log_e("Bad frequency: %u MHz! Options are: 240, 160, 80, %u, %u and %u MHz", cpu_freq_mhz, xtal, xtal/2, xtal/4);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(cpu_freq_mhz > xtal && cpu_freq_mhz != 240 && cpu_freq_mhz != 160 && cpu_freq_mhz != 80){
|
||||||
|
if(xtal >= RTC_XTAL_FREQ_40M){
|
||||||
|
log_e("Bad frequency: %u MHz! Options are: 240, 160, 80, %u, %u and %u MHz", cpu_freq_mhz, xtal, xtal/2, xtal/4);
|
||||||
|
} else {
|
||||||
|
log_e("Bad frequency: %u MHz! Options are: 240, 160, 80, %u and %u MHz", cpu_freq_mhz, xtal, xtal/2);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
//check if cpu supports the frequency
|
||||||
|
if(cpu_freq_mhz == 240){
|
||||||
|
//Check if ESP32 is rated for a CPU frequency of 160MHz only
|
||||||
|
if (REG_GET_BIT(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_CPU_FREQ_RATED) &&
|
||||||
|
REG_GET_BIT(EFUSE_BLK0_RDATA3_REG, EFUSE_RD_CHIP_CPU_FREQ_LOW)) {
|
||||||
|
log_e("Can not switch to 240 MHz! Chip CPU frequency rated for 160MHz.");
|
||||||
|
cpu_freq_mhz = 160;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
//Get current CPU clock configuration
|
||||||
|
rtc_clk_cpu_freq_get_config(&cconf);
|
||||||
|
//return if frequency has not changed
|
||||||
|
if(cconf.freq_mhz == cpu_freq_mhz){
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
//Get configuration for the new CPU frequency
|
||||||
|
if(!rtc_clk_cpu_freq_mhz_to_config(cpu_freq_mhz, &conf)){
|
||||||
|
log_e("CPU clock could not be set to %u MHz", cpu_freq_mhz);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
//Current APB
|
||||||
|
capb = calculateApb(&cconf);
|
||||||
|
//New APB
|
||||||
|
apb = calculateApb(&conf);
|
||||||
|
|
||||||
|
//Call peripheral functions before the APB change
|
||||||
|
if(apb_change_callbacks){
|
||||||
|
triggerApbChangeCallback(APB_BEFORE_CHANGE, capb, apb);
|
||||||
|
}
|
||||||
|
//Make the frequency change
|
||||||
|
rtc_clk_cpu_freq_set_config_fast(&conf);
|
||||||
|
if(capb != apb){
|
||||||
|
//Update REF_TICK (uncomment if REF_TICK is different than 1MHz)
|
||||||
|
//if(conf.freq_mhz < 80){
|
||||||
|
// ESP_REG(APB_CTRL_XTAL_TICK_CONF_REG) = conf.freq_mhz / (REF_CLK_FREQ / MHZ) - 1;
|
||||||
|
// }
|
||||||
|
//Update APB Freq REG
|
||||||
|
rtc_clk_apb_freq_update(apb);
|
||||||
|
//Update esp_timer divisor
|
||||||
|
esp_timer_impl_update_apb_freq(apb / MHZ);
|
||||||
|
}
|
||||||
|
//Update FreeRTOS Tick Divisor
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3
|
||||||
|
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
|
||||||
|
#else
|
||||||
|
uint32_t fcpu = (conf.freq_mhz >= 80)?(conf.freq_mhz * MHZ):(apb);
|
||||||
|
_xt_tick_divisor = fcpu / XT_TICK_PER_SEC;
|
||||||
|
#endif
|
||||||
|
//Call peripheral functions after the APB change
|
||||||
|
if(apb_change_callbacks){
|
||||||
|
triggerApbChangeCallback(APB_AFTER_CHANGE, capb, apb);
|
||||||
|
}
|
||||||
|
log_d("%s: %u / %u = %u Mhz, APB: %u Hz", (conf.source == RTC_CPU_FREQ_SRC_PLL)?"PLL":((conf.source == RTC_CPU_FREQ_SRC_APLL)?"APLL":((conf.source == RTC_CPU_FREQ_SRC_XTAL)?"XTAL":"8M")), conf.source_freq_mhz, conf.div, conf.freq_mhz, apb);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getCpuFrequencyMhz(){
|
||||||
|
rtc_cpu_freq_config_t conf;
|
||||||
|
rtc_clk_cpu_freq_get_config(&conf);
|
||||||
|
return conf.freq_mhz;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getXtalFrequencyMhz(){
|
||||||
|
return rtc_clk_xtal_freq_get();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getApbFrequency(){
|
||||||
|
rtc_cpu_freq_config_t conf;
|
||||||
|
rtc_clk_cpu_freq_get_config(&conf);
|
||||||
|
return calculateApb(&conf);
|
||||||
|
}
|
@ -0,0 +1,48 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#ifndef _ESP32_HAL_CPU_H_
|
||||||
|
#define _ESP32_HAL_CPU_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
typedef enum { APB_BEFORE_CHANGE, APB_AFTER_CHANGE } apb_change_ev_t;
|
||||||
|
|
||||||
|
typedef void (* apb_change_cb_t)(void * arg, apb_change_ev_t ev_type, uint32_t old_apb, uint32_t new_apb);
|
||||||
|
|
||||||
|
bool addApbChangeCallback(void * arg, apb_change_cb_t cb);
|
||||||
|
bool removeApbChangeCallback(void * arg, apb_change_cb_t cb);
|
||||||
|
|
||||||
|
//function takes the following frequencies as valid values:
|
||||||
|
// 240, 160, 80 <<< For all XTAL types
|
||||||
|
// 40, 20, 10 <<< For 40MHz XTAL
|
||||||
|
// 26, 13 <<< For 26MHz XTAL
|
||||||
|
// 24, 12 <<< For 24MHz XTAL
|
||||||
|
bool setCpuFrequencyMhz(uint32_t cpu_freq_mhz);
|
||||||
|
|
||||||
|
uint32_t getCpuFrequencyMhz(); // In MHz
|
||||||
|
uint32_t getXtalFrequencyMhz(); // In MHz
|
||||||
|
uint32_t getApbFrequency(); // In Hz
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* _ESP32_HAL_CPU_H_ */
|
@ -0,0 +1,49 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
|
||||||
|
#ifndef SOC_DAC_SUPPORTED
|
||||||
|
#define NODAC
|
||||||
|
#else
|
||||||
|
#include "soc/dac_channel.h"
|
||||||
|
#include "driver/dac_common.h"
|
||||||
|
|
||||||
|
void ARDUINO_ISR_ATTR __dacWrite(uint8_t pin, uint8_t value)
|
||||||
|
{
|
||||||
|
if(pin < DAC_CHANNEL_1_GPIO_NUM || pin > DAC_CHANNEL_2_GPIO_NUM){
|
||||||
|
return;//not dac pin
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t channel = pin - DAC_CHANNEL_1_GPIO_NUM;
|
||||||
|
dac_output_enable(channel);
|
||||||
|
dac_output_voltage(channel, value);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void ARDUINO_ISR_ATTR __dacDisable(uint8_t pin)
|
||||||
|
{
|
||||||
|
if(pin < DAC_CHANNEL_1_GPIO_NUM || pin > DAC_CHANNEL_2_GPIO_NUM){
|
||||||
|
return;//not dac pin
|
||||||
|
}
|
||||||
|
|
||||||
|
uint8_t channel = pin - DAC_CHANNEL_1_GPIO_NUM;
|
||||||
|
dac_output_disable(channel);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void dacWrite(uint8_t pin, uint8_t value) __attribute__ ((weak, alias("__dacWrite")));
|
||||||
|
extern void dacDisable(uint8_t pin) __attribute__ ((weak, alias("__dacDisable")));
|
||||||
|
|
||||||
|
#endif
|
@ -0,0 +1,37 @@
|
|||||||
|
/*
|
||||||
|
Arduino.h - Main include file for the Arduino SDK
|
||||||
|
Copyright (c) 2005-2013 Arduino Team. All right 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 MAIN_ESP32_HAL_DAC_H_
|
||||||
|
#define MAIN_ESP32_HAL_DAC_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
|
||||||
|
void dacWrite(uint8_t pin, uint8_t value);
|
||||||
|
void dacDisable(uint8_t pin);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MAIN_ESP32_HAL_DAC_H_ */
|
@ -0,0 +1,236 @@
|
|||||||
|
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include "esp32-hal-gpio.h"
|
||||||
|
#include "hal/gpio_hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
|
||||||
|
// It fixes lack of pin definition for S3 and for any future SoC
|
||||||
|
// this function works for ESP32, ESP32-S2 and ESP32-S3 - including the C3, it will return -1 for any pin
|
||||||
|
#if SOC_TOUCH_SENSOR_NUM > 0
|
||||||
|
#include "soc/touch_sensor_periph.h"
|
||||||
|
|
||||||
|
int8_t digitalPinToTouchChannel(uint8_t pin)
|
||||||
|
{
|
||||||
|
int8_t ret = -1;
|
||||||
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
|
for (uint8_t i = 0; i < SOC_TOUCH_SENSOR_NUM; i++) {
|
||||||
|
if (touch_sensor_channel_io_map[i] == pin) {
|
||||||
|
ret = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// No Touch Sensor available
|
||||||
|
int8_t digitalPinToTouchChannel(uint8_t pin)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef SOC_ADC_SUPPORTED
|
||||||
|
#include "soc/adc_periph.h"
|
||||||
|
|
||||||
|
int8_t digitalPinToAnalogChannel(uint8_t pin)
|
||||||
|
{
|
||||||
|
uint8_t channel = 0;
|
||||||
|
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||||
|
for (uint8_t i = 0; i < SOC_ADC_PERIPH_NUM; i++) {
|
||||||
|
for (uint8_t j = 0; j < SOC_ADC_MAX_CHANNEL_NUM; j++) {
|
||||||
|
if (adc_channel_io_map[i][j] == pin) {
|
||||||
|
return channel;
|
||||||
|
}
|
||||||
|
channel++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int8_t analogChannelToDigitalPin(uint8_t channel)
|
||||||
|
{
|
||||||
|
if (channel >= (SOC_ADC_PERIPH_NUM * SOC_ADC_MAX_CHANNEL_NUM)) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
uint8_t adc_unit = (channel / SOC_ADC_MAX_CHANNEL_NUM);
|
||||||
|
uint8_t adc_chan = (channel % SOC_ADC_MAX_CHANNEL_NUM);
|
||||||
|
return adc_channel_io_map[adc_unit][adc_chan];
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
// No Analog channels availible
|
||||||
|
int8_t analogChannelToDigitalPin(uint8_t channel)
|
||||||
|
{
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
typedef void (*voidFuncPtr)(void);
|
||||||
|
typedef void (*voidFuncPtrArg)(void*);
|
||||||
|
typedef struct {
|
||||||
|
voidFuncPtr fn;
|
||||||
|
void* arg;
|
||||||
|
bool functional;
|
||||||
|
} InterruptHandle_t;
|
||||||
|
static InterruptHandle_t __pinInterruptHandlers[SOC_GPIO_PIN_COUNT] = {0,};
|
||||||
|
|
||||||
|
#include "driver/rtc_io.h"
|
||||||
|
|
||||||
|
extern void ARDUINO_ISR_ATTR __pinMode(uint8_t pin, uint8_t mode)
|
||||||
|
{
|
||||||
|
#ifdef RGB_BUILTIN
|
||||||
|
if (pin == RGB_BUILTIN){
|
||||||
|
__pinMode(RGB_BUILTIN-SOC_GPIO_PIN_COUNT, mode);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!GPIO_IS_VALID_GPIO(pin)) {
|
||||||
|
log_e("Invalid pin selected");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
gpio_hal_context_t gpiohal;
|
||||||
|
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
|
||||||
|
|
||||||
|
gpio_config_t conf = {
|
||||||
|
.pin_bit_mask = (1ULL<<pin), /*!< GPIO pin: set with bit mask, each bit maps to a GPIO */
|
||||||
|
.mode = GPIO_MODE_DISABLE, /*!< GPIO mode: set input/output mode */
|
||||||
|
.pull_up_en = GPIO_PULLUP_DISABLE, /*!< GPIO pull-up */
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE, /*!< GPIO pull-down */
|
||||||
|
.intr_type = gpiohal.dev->pin[pin].int_type /*!< GPIO interrupt type - previously set */
|
||||||
|
};
|
||||||
|
if (mode < 0x20) {//io
|
||||||
|
conf.mode = mode & (INPUT | OUTPUT);
|
||||||
|
if (mode & OPEN_DRAIN) {
|
||||||
|
conf.mode |= GPIO_MODE_DEF_OD;
|
||||||
|
}
|
||||||
|
if (mode & PULLUP) {
|
||||||
|
conf.pull_up_en = GPIO_PULLUP_ENABLE;
|
||||||
|
}
|
||||||
|
if (mode & PULLDOWN) {
|
||||||
|
conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(gpio_config(&conf) != ESP_OK)
|
||||||
|
{
|
||||||
|
log_e("GPIO config failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void ARDUINO_ISR_ATTR __digitalWrite(uint8_t pin, uint8_t val)
|
||||||
|
{
|
||||||
|
#ifdef RGB_BUILTIN
|
||||||
|
if(pin == RGB_BUILTIN){
|
||||||
|
//use RMT to set all channels on/off
|
||||||
|
const uint8_t comm_val = val != 0 ? RGB_BRIGHTNESS : 0;
|
||||||
|
neopixelWrite(RGB_BUILTIN, comm_val, comm_val, comm_val);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
gpio_set_level((gpio_num_t)pin, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern int ARDUINO_ISR_ATTR __digitalRead(uint8_t pin)
|
||||||
|
{
|
||||||
|
return gpio_get_level((gpio_num_t)pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ARDUINO_ISR_ATTR __onPinInterrupt(void * arg) {
|
||||||
|
InterruptHandle_t * isr = (InterruptHandle_t*)arg;
|
||||||
|
if(isr->fn) {
|
||||||
|
if(isr->arg){
|
||||||
|
((voidFuncPtrArg)isr->fn)(isr->arg);
|
||||||
|
} else {
|
||||||
|
isr->fn();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void cleanupFunctional(void* arg);
|
||||||
|
|
||||||
|
extern void __attachInterruptFunctionalArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type, bool functional)
|
||||||
|
{
|
||||||
|
static bool interrupt_initialized = false;
|
||||||
|
|
||||||
|
// makes sure that pin -1 (255) will never work -- this follows Arduino standard
|
||||||
|
if (pin >= SOC_GPIO_PIN_COUNT) return;
|
||||||
|
|
||||||
|
if(!interrupt_initialized) {
|
||||||
|
esp_err_t err = gpio_install_isr_service((int)ARDUINO_ISR_FLAG);
|
||||||
|
interrupt_initialized = (err == ESP_OK) || (err == ESP_ERR_INVALID_STATE);
|
||||||
|
}
|
||||||
|
if(!interrupt_initialized) {
|
||||||
|
log_e("GPIO ISR Service Failed To Start");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if new attach without detach remove old info
|
||||||
|
if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg)
|
||||||
|
{
|
||||||
|
cleanupFunctional(__pinInterruptHandlers[pin].arg);
|
||||||
|
}
|
||||||
|
__pinInterruptHandlers[pin].fn = (voidFuncPtr)userFunc;
|
||||||
|
__pinInterruptHandlers[pin].arg = arg;
|
||||||
|
__pinInterruptHandlers[pin].functional = functional;
|
||||||
|
|
||||||
|
gpio_set_intr_type((gpio_num_t)pin, (gpio_int_type_t)(intr_type & 0x7));
|
||||||
|
if(intr_type & 0x8){
|
||||||
|
gpio_wakeup_enable((gpio_num_t)pin, (gpio_int_type_t)(intr_type & 0x7));
|
||||||
|
}
|
||||||
|
gpio_isr_handler_add((gpio_num_t)pin, __onPinInterrupt, &__pinInterruptHandlers[pin]);
|
||||||
|
|
||||||
|
|
||||||
|
//FIX interrupts on peripherals outputs (eg. LEDC,...)
|
||||||
|
//Enable input in GPIO register
|
||||||
|
gpio_hal_context_t gpiohal;
|
||||||
|
gpiohal.dev = GPIO_LL_GET_HW(GPIO_PORT_0);
|
||||||
|
gpio_hal_input_enable(&gpiohal, pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __attachInterruptArg(uint8_t pin, voidFuncPtrArg userFunc, void * arg, int intr_type)
|
||||||
|
{
|
||||||
|
__attachInterruptFunctionalArg(pin, userFunc, arg, intr_type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __attachInterrupt(uint8_t pin, voidFuncPtr userFunc, int intr_type) {
|
||||||
|
__attachInterruptFunctionalArg(pin, (voidFuncPtrArg)userFunc, NULL, intr_type, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern void __detachInterrupt(uint8_t pin)
|
||||||
|
{
|
||||||
|
gpio_isr_handler_remove((gpio_num_t)pin); //remove handle and disable isr for pin
|
||||||
|
gpio_wakeup_disable((gpio_num_t)pin);
|
||||||
|
|
||||||
|
if (__pinInterruptHandlers[pin].functional && __pinInterruptHandlers[pin].arg)
|
||||||
|
{
|
||||||
|
cleanupFunctional(__pinInterruptHandlers[pin].arg);
|
||||||
|
}
|
||||||
|
__pinInterruptHandlers[pin].fn = NULL;
|
||||||
|
__pinInterruptHandlers[pin].arg = NULL;
|
||||||
|
__pinInterruptHandlers[pin].functional = false;
|
||||||
|
|
||||||
|
gpio_set_intr_type((gpio_num_t)pin, GPIO_INTR_DISABLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extern void pinMode(uint8_t pin, uint8_t mode) __attribute__ ((weak, alias("__pinMode")));
|
||||||
|
extern void digitalWrite(uint8_t pin, uint8_t val) __attribute__ ((weak, alias("__digitalWrite")));
|
||||||
|
extern int digitalRead(uint8_t pin) __attribute__ ((weak, alias("__digitalRead")));
|
||||||
|
extern void attachInterrupt(uint8_t pin, voidFuncPtr handler, int mode) __attribute__ ((weak, alias("__attachInterrupt")));
|
||||||
|
extern void attachInterruptArg(uint8_t pin, voidFuncPtrArg handler, void * arg, int mode) __attribute__ ((weak, alias("__attachInterruptArg")));
|
||||||
|
extern void detachInterrupt(uint8_t pin) __attribute__ ((weak, alias("__detachInterrupt")));
|
@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
Arduino.h - Main include file for the Arduino SDK
|
||||||
|
Copyright (c) 2005-2013 Arduino Team. All right 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 MAIN_ESP32_HAL_GPIO_H_
|
||||||
|
#define MAIN_ESP32_HAL_GPIO_H_
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "esp32-hal.h"
|
||||||
|
#include "soc/soc_caps.h"
|
||||||
|
#include "pins_arduino.h"
|
||||||
|
|
||||||
|
#if (CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3)
|
||||||
|
#define NUM_OUPUT_PINS 46
|
||||||
|
#define PIN_DAC1 17
|
||||||
|
#define PIN_DAC2 18
|
||||||
|
#else
|
||||||
|
#define NUM_OUPUT_PINS 34
|
||||||
|
#define PIN_DAC1 25
|
||||||
|
#define PIN_DAC2 26
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define LOW 0x0
|
||||||
|
#define HIGH 0x1
|
||||||
|
|
||||||
|
//GPIO FUNCTIONS
|
||||||
|
#define INPUT 0x01
|
||||||
|
// Changed OUTPUT from 0x02 to behave the same as Arduino pinMode(pin,OUTPUT)
|
||||||
|
// where you can read the state of pin even when it is set as OUTPUT
|
||||||
|
#define OUTPUT 0x03
|
||||||
|
#define PULLUP 0x04
|
||||||
|
#define INPUT_PULLUP 0x05
|
||||||
|
#define PULLDOWN 0x08
|
||||||
|
#define INPUT_PULLDOWN 0x09
|
||||||
|
#define OPEN_DRAIN 0x10
|
||||||
|
#define OUTPUT_OPEN_DRAIN 0x13
|
||||||
|
#define ANALOG 0xC0
|
||||||
|
|
||||||
|
//Interrupt Modes
|
||||||
|
#define DISABLED 0x00
|
||||||
|
#define RISING 0x01
|
||||||
|
#define FALLING 0x02
|
||||||
|
#define CHANGE 0x03
|
||||||
|
#define ONLOW 0x04
|
||||||
|
#define ONHIGH 0x05
|
||||||
|
#define ONLOW_WE 0x0C
|
||||||
|
#define ONHIGH_WE 0x0D
|
||||||
|
|
||||||
|
|
||||||
|
#define digitalPinIsValid(pin) GPIO_IS_VALID_GPIO(pin)
|
||||||
|
#define digitalPinCanOutput(pin) GPIO_IS_VALID_OUTPUT_GPIO(pin)
|
||||||
|
|
||||||
|
#define digitalPinToRtcPin(pin) ((RTC_GPIO_IS_VALID_GPIO(pin))?rtc_io_number_get(pin):-1)
|
||||||
|
#define digitalPinToDacChannel(pin) (((pin) == DAC_CHANNEL_1_GPIO_NUM)?0:((pin) == DAC_CHANNEL_2_GPIO_NUM)?1:-1)
|
||||||
|
|
||||||
|
void pinMode(uint8_t pin, uint8_t mode);
|
||||||
|
void digitalWrite(uint8_t pin, uint8_t val);
|
||||||
|
int digitalRead(uint8_t pin);
|
||||||
|
|
||||||
|
void attachInterrupt(uint8_t pin, void (*)(void), int mode);
|
||||||
|
void attachInterruptArg(uint8_t pin, void (*)(void*), void * arg, int mode);
|
||||||
|
void detachInterrupt(uint8_t pin);
|
||||||
|
|
||||||
|
int8_t digitalPinToTouchChannel(uint8_t pin);
|
||||||
|
int8_t digitalPinToAnalogChannel(uint8_t pin);
|
||||||
|
int8_t analogChannelToDigitalPin(uint8_t channel);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif /* MAIN_ESP32_HAL_GPIO_H_ */
|
@ -0,0 +1,841 @@
|
|||||||
|
// Copyright 2015-2021 Espressif Systems (Shanghai) PTE LTD
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <inttypes.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
|
#include "sdkconfig.h"
|
||||||
|
#include "esp_attr.h"
|
||||||
|
#include "rom/gpio.h"
|
||||||
|
#include "soc/gpio_sig_map.h"
|
||||||
|
#include "hal/gpio_types.h"
|
||||||
|
#include "driver/gpio.h"
|
||||||
|
#include "esp_err.h"
|
||||||
|
|
||||||
|
#include "freertos/FreeRTOS.h"
|
||||||
|
#include "freertos/task.h"
|
||||||
|
#include "freertos/queue.h"
|
||||||
|
#include "freertos/semphr.h"
|
||||||
|
#include "freertos/ringbuf.h"
|
||||||
|
|
||||||
|
#include "esp_intr_alloc.h"
|
||||||
|
#include "driver/periph_ctrl.h"
|
||||||
|
#include "soc/i2c_reg.h"
|
||||||
|
#include "soc/i2c_struct.h"
|
||||||
|
#include "hal/i2c_ll.h"
|
||||||
|
#include "esp32-hal-log.h"
|
||||||
|
#include "esp32-hal-i2c-slave.h"
|
||||||
|
|
||||||
|
#define I2C_SLAVE_USE_RX_QUEUE 0 // 1: Queue, 0: RingBuffer
|
||||||
|
|
||||||
|
#if SOC_I2C_NUM > 1
|
||||||
|
#define I2C_SCL_IDX(p) ((p==0)?I2CEXT0_SCL_OUT_IDX:((p==1)?I2CEXT1_SCL_OUT_IDX:0))
|
||||||
|
#define I2C_SDA_IDX(p) ((p==0)?I2CEXT0_SDA_OUT_IDX:((p==1)?I2CEXT1_SDA_OUT_IDX:0))
|
||||||
|
#else
|
||||||
|
#define I2C_SCL_IDX(p) I2CEXT0_SCL_OUT_IDX
|
||||||
|
#define I2C_SDA_IDX(p) I2CEXT0_SDA_OUT_IDX
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
#define I2C_TXFIFO_WM_INT_ENA I2C_TXFIFO_EMPTY_INT_ENA
|
||||||
|
#define I2C_RXFIFO_WM_INT_ENA I2C_RXFIFO_FULL_INT_ENA
|
||||||
|
#endif
|
||||||
|
|
||||||
|
enum {
|
||||||
|
I2C_SLAVE_EVT_RX, I2C_SLAVE_EVT_TX
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef struct i2c_slave_struct_t {
|
||||||
|
i2c_dev_t * dev;
|
||||||
|
uint8_t num;
|
||||||
|
int8_t sda;
|
||||||
|
int8_t scl;
|
||||||
|
i2c_slave_request_cb_t request_callback;
|
||||||
|
i2c_slave_receive_cb_t receive_callback;
|
||||||
|
void * arg;
|
||||||
|
intr_handle_t intr_handle;
|
||||||
|
TaskHandle_t task_handle;
|
||||||
|
xQueueHandle event_queue;
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
xQueueHandle rx_queue;
|
||||||
|
#else
|
||||||
|
RingbufHandle_t rx_ring_buf;
|
||||||
|
#endif
|
||||||
|
xQueueHandle tx_queue;
|
||||||
|
uint32_t rx_data_count;
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
xSemaphoreHandle lock;
|
||||||
|
#endif
|
||||||
|
} i2c_slave_struct_t;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
struct {
|
||||||
|
uint32_t event : 2;
|
||||||
|
uint32_t stop : 1;
|
||||||
|
uint32_t param : 29;
|
||||||
|
};
|
||||||
|
uint32_t val;
|
||||||
|
} i2c_slave_queue_event_t;
|
||||||
|
|
||||||
|
static i2c_slave_struct_t _i2c_bus_array[SOC_I2C_NUM] = {
|
||||||
|
{ &I2C0, 0, -1, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
, NULL
|
||||||
|
#endif
|
||||||
|
},
|
||||||
|
#if SOC_I2C_NUM > 1
|
||||||
|
{ &I2C1, 1, -1, -1, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, 0
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
, NULL
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
#if CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
#define I2C_SLAVE_MUTEX_LOCK()
|
||||||
|
#define I2C_SLAVE_MUTEX_UNLOCK()
|
||||||
|
#else
|
||||||
|
#define I2C_SLAVE_MUTEX_LOCK() if(i2c->lock){xSemaphoreTake(i2c->lock, portMAX_DELAY);}
|
||||||
|
#define I2C_SLAVE_MUTEX_UNLOCK() if(i2c->lock){xSemaphoreGive(i2c->lock);}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//-------------------------------------- HAL_LL (Missing Functions) ------------------------------------------------
|
||||||
|
typedef enum {
|
||||||
|
I2C_STRETCH_CAUSE_MASTER_READ,
|
||||||
|
I2C_STRETCH_CAUSE_TX_FIFO_EMPTY,
|
||||||
|
I2C_STRETCH_CAUSE_RX_FIFO_FULL,
|
||||||
|
I2C_STRETCH_CAUSE_MAX
|
||||||
|
} i2c_stretch_cause_t;
|
||||||
|
|
||||||
|
static inline i2c_stretch_cause_t i2c_ll_stretch_cause(i2c_dev_t *hw)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return hw->sr.stretch_cause;
|
||||||
|
#elif CONFIG_IDF_TARGET_ESP32S2
|
||||||
|
return hw->status_reg.stretch_cause;
|
||||||
|
#else
|
||||||
|
return I2C_STRETCH_CAUSE_MAX;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void i2c_ll_set_stretch(i2c_dev_t *hw, uint16_t time)
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
typeof(hw->scl_stretch_conf) scl_stretch_conf;
|
||||||
|
scl_stretch_conf.val = 0;
|
||||||
|
scl_stretch_conf.slave_scl_stretch_en = (time > 0);
|
||||||
|
scl_stretch_conf.stretch_protect_num = time;
|
||||||
|
scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||||
|
hw->scl_stretch_conf.val = scl_stretch_conf.val;
|
||||||
|
if(time > 0){
|
||||||
|
//enable interrupt
|
||||||
|
hw->int_ena.val |= I2C_SLAVE_STRETCH_INT_ENA;
|
||||||
|
} else {
|
||||||
|
//disable interrupt
|
||||||
|
hw->int_ena.val &= (~I2C_SLAVE_STRETCH_INT_ENA);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void i2c_ll_stretch_clr(i2c_dev_t *hw)
|
||||||
|
{
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
hw->scl_stretch_conf.slave_scl_stretch_clr = 1;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool i2c_ll_slave_addressed(i2c_dev_t *hw)
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return hw->sr.slave_addressed;
|
||||||
|
#else
|
||||||
|
return hw->status_reg.slave_addressed;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline bool i2c_ll_slave_rw(i2c_dev_t *hw)//not exposed by hal_ll
|
||||||
|
{
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32S3
|
||||||
|
return hw->sr.slave_rw;
|
||||||
|
#else
|
||||||
|
return hw->status_reg.slave_rw;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
//-------------------------------------- PRIVATE (Function Prototypes) ------------------------------------------------
|
||||||
|
static void i2c_slave_free_resources(i2c_slave_struct_t * i2c);
|
||||||
|
static void i2c_slave_delay_us(uint64_t us);
|
||||||
|
static void i2c_slave_gpio_mode(int8_t pin, gpio_mode_t mode);
|
||||||
|
static bool i2c_slave_check_line_state(int8_t sda, int8_t scl);
|
||||||
|
static bool i2c_slave_attach_gpio(i2c_slave_struct_t * i2c, int8_t sda, int8_t scl);
|
||||||
|
static bool i2c_slave_detach_gpio(i2c_slave_struct_t * i2c);
|
||||||
|
static bool i2c_slave_set_frequency(i2c_slave_struct_t * i2c, uint32_t clk_speed);
|
||||||
|
static bool i2c_slave_send_event(i2c_slave_struct_t * i2c, i2c_slave_queue_event_t* event);
|
||||||
|
static bool i2c_slave_handle_tx_fifo_empty(i2c_slave_struct_t * i2c);
|
||||||
|
static bool i2c_slave_handle_rx_fifo_full(i2c_slave_struct_t * i2c, uint32_t len);
|
||||||
|
static size_t i2c_slave_read_rx(i2c_slave_struct_t * i2c, uint8_t * data, size_t len);
|
||||||
|
static void i2c_slave_isr_handler(void* arg);
|
||||||
|
static void i2c_slave_task(void *pv_args);
|
||||||
|
|
||||||
|
|
||||||
|
//=====================================================================================================================
|
||||||
|
//-------------------------------------- Public Functions -------------------------------------------------------------
|
||||||
|
//=====================================================================================================================
|
||||||
|
|
||||||
|
esp_err_t i2cSlaveAttachCallbacks(uint8_t num, i2c_slave_request_cb_t request_callback, i2c_slave_receive_cb_t receive_callback, void * arg){
|
||||||
|
if(num >= SOC_I2C_NUM){
|
||||||
|
log_e("Invalid port num: %u", num);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
i2c_slave_struct_t * i2c = &_i2c_bus_array[num];
|
||||||
|
I2C_SLAVE_MUTEX_LOCK();
|
||||||
|
i2c->request_callback = request_callback;
|
||||||
|
i2c->receive_callback = receive_callback;
|
||||||
|
i2c->arg = arg;
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cSlaveInit(uint8_t num, int sda, int scl, uint16_t slaveID, uint32_t frequency, size_t rx_len, size_t tx_len) {
|
||||||
|
if(num >= SOC_I2C_NUM){
|
||||||
|
log_e("Invalid port num: %u", num);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sda < 0 || scl < 0) {
|
||||||
|
log_e("invalid pins sda=%d, scl=%d", sda, scl);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!frequency){
|
||||||
|
frequency = 100000;
|
||||||
|
} else if(frequency > 1000000){
|
||||||
|
frequency = 1000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
log_i("Initialising I2C Slave: sda=%d scl=%d freq=%d, addr=0x%x", sda, scl, frequency, slaveID);
|
||||||
|
|
||||||
|
i2c_slave_struct_t * i2c = &_i2c_bus_array[num];
|
||||||
|
esp_err_t ret = ESP_OK;
|
||||||
|
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(!i2c->lock){
|
||||||
|
i2c->lock = xSemaphoreCreateMutex();
|
||||||
|
if (i2c->lock == NULL) {
|
||||||
|
log_e("RX queue create failed");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
I2C_SLAVE_MUTEX_LOCK();
|
||||||
|
i2c_slave_free_resources(i2c);
|
||||||
|
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
i2c->rx_queue = xQueueCreate(rx_len, sizeof(uint8_t));
|
||||||
|
if (i2c->rx_queue == NULL) {
|
||||||
|
log_e("RX queue create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
i2c->rx_ring_buf = xRingbufferCreate(rx_len, RINGBUF_TYPE_BYTEBUF);
|
||||||
|
if (i2c->rx_ring_buf == NULL) {
|
||||||
|
log_e("RX RingBuf create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
i2c->tx_queue = xQueueCreate(tx_len, sizeof(uint8_t));
|
||||||
|
if (i2c->tx_queue == NULL) {
|
||||||
|
log_e("TX queue create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c->event_queue = xQueueCreate(16, sizeof(i2c_slave_queue_event_t));
|
||||||
|
if (i2c->event_queue == NULL) {
|
||||||
|
log_e("Event queue create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
xTaskCreate(i2c_slave_task, "i2c_slave_task", 4096, i2c, 20, &i2c->task_handle);
|
||||||
|
if(i2c->task_handle == NULL){
|
||||||
|
log_e("Event thread create failed");
|
||||||
|
ret = ESP_ERR_NO_MEM;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frequency == 0) {
|
||||||
|
frequency = 100000L;
|
||||||
|
}
|
||||||
|
frequency = (frequency * 5) / 4;
|
||||||
|
|
||||||
|
if (i2c->num == 0) {
|
||||||
|
periph_module_enable(PERIPH_I2C0_MODULE);
|
||||||
|
#if SOC_I2C_NUM > 1
|
||||||
|
} else {
|
||||||
|
periph_module_enable(PERIPH_I2C1_MODULE);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_ll_slave_init(i2c->dev);
|
||||||
|
i2c_ll_set_fifo_mode(i2c->dev, true);
|
||||||
|
i2c_ll_set_slave_addr(i2c->dev, slaveID, false);
|
||||||
|
i2c_ll_set_tout(i2c->dev, I2C_LL_MAX_TIMEOUT);
|
||||||
|
i2c_slave_set_frequency(i2c, frequency);
|
||||||
|
|
||||||
|
if (!i2c_slave_check_line_state(sda, scl)) {
|
||||||
|
log_e("bad pin state");
|
||||||
|
ret = ESP_FAIL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_slave_attach_gpio(i2c, sda, scl);
|
||||||
|
|
||||||
|
if (i2c_ll_is_bus_busy(i2c->dev)) {
|
||||||
|
log_w("Bus busy, reinit");
|
||||||
|
ret = ESP_FAIL;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_ll_disable_intr_mask(i2c->dev, I2C_LL_INTR_MASK);
|
||||||
|
i2c_ll_clr_intsts_mask(i2c->dev, I2C_LL_INTR_MASK);
|
||||||
|
i2c_ll_set_fifo_mode(i2c->dev, true);
|
||||||
|
|
||||||
|
if (!i2c->intr_handle) {
|
||||||
|
uint32_t flags = ESP_INTR_FLAG_LOWMED | ESP_INTR_FLAG_SHARED;
|
||||||
|
if(i2c->num == 0) {
|
||||||
|
ret = esp_intr_alloc(ETS_I2C_EXT0_INTR_SOURCE, flags, &i2c_slave_isr_handler, i2c, &i2c->intr_handle);
|
||||||
|
#if SOC_I2C_NUM > 1
|
||||||
|
} else {
|
||||||
|
ret = esp_intr_alloc(ETS_I2C_EXT1_INTR_SOURCE, flags, &i2c_slave_isr_handler, i2c, &i2c->intr_handle);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ret != ESP_OK) {
|
||||||
|
log_e("install interrupt handler Failed=%d", ret);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_ll_txfifo_rst(i2c->dev);
|
||||||
|
i2c_ll_rxfifo_rst(i2c->dev);
|
||||||
|
i2c_ll_slave_enable_rx_it(i2c->dev);
|
||||||
|
i2c_ll_set_stretch(i2c->dev, 0x3FF);
|
||||||
|
i2c_ll_update(i2c->dev);
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
i2c_slave_free_resources(i2c);
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
esp_err_t i2cSlaveDeinit(uint8_t num){
|
||||||
|
if(num >= SOC_I2C_NUM){
|
||||||
|
log_e("Invalid port num: %u", num);
|
||||||
|
return ESP_ERR_INVALID_ARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c_slave_struct_t * i2c = &_i2c_bus_array[num];
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(!i2c->lock){
|
||||||
|
log_e("Lock is not initialized! Did you call i2c_slave_init()?");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
I2C_SLAVE_MUTEX_LOCK();
|
||||||
|
i2c_slave_free_resources(i2c);
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return ESP_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t i2cSlaveWrite(uint8_t num, const uint8_t *buf, uint32_t len, uint32_t timeout_ms) {
|
||||||
|
if(num >= SOC_I2C_NUM){
|
||||||
|
log_e("Invalid port num: %u", num);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
size_t to_queue = 0, to_fifo = 0;
|
||||||
|
i2c_slave_struct_t * i2c = &_i2c_bus_array[num];
|
||||||
|
#if !CONFIG_DISABLE_HAL_LOCKS
|
||||||
|
if(!i2c->lock){
|
||||||
|
log_e("Lock is not initialized! Did you call i2c_slave_init()?");
|
||||||
|
return ESP_ERR_NO_MEM;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
if(!i2c->tx_queue){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
I2C_SLAVE_MUTEX_LOCK();
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
i2c_ll_slave_disable_tx_it(i2c->dev);
|
||||||
|
if (i2c_ll_get_txfifo_len(i2c->dev) < SOC_I2C_FIFO_LEN) {
|
||||||
|
i2c_ll_txfifo_rst(i2c->dev);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
to_fifo = i2c_ll_get_txfifo_len(i2c->dev);
|
||||||
|
if(len < to_fifo){
|
||||||
|
to_fifo = len;
|
||||||
|
}
|
||||||
|
i2c_ll_write_txfifo(i2c->dev, (uint8_t*)buf, to_fifo);
|
||||||
|
buf += to_fifo;
|
||||||
|
len -= to_fifo;
|
||||||
|
//reset tx_queue
|
||||||
|
xQueueReset(i2c->tx_queue);
|
||||||
|
//write the rest of the bytes to the queue
|
||||||
|
if(len){
|
||||||
|
to_queue = uxQueueSpacesAvailable(i2c->tx_queue);
|
||||||
|
if(len < to_queue){
|
||||||
|
to_queue = len;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < to_queue; i++) {
|
||||||
|
if (xQueueSend(i2c->tx_queue, &buf[i], timeout_ms / portTICK_RATE_MS) != pdTRUE) {
|
||||||
|
xQueueReset(i2c->tx_queue);
|
||||||
|
to_queue = 0;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//no need to enable TX_EMPTY if tx_queue is empty
|
||||||
|
if(to_queue){
|
||||||
|
i2c_ll_slave_enable_tx_it(i2c->dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
I2C_SLAVE_MUTEX_UNLOCK();
|
||||||
|
return to_queue + to_fifo;
|
||||||
|
}
|
||||||
|
|
||||||
|
//=====================================================================================================================
|
||||||
|
//-------------------------------------- Private Functions ------------------------------------------------------------
|
||||||
|
//=====================================================================================================================
|
||||||
|
|
||||||
|
static void i2c_slave_free_resources(i2c_slave_struct_t * i2c){
|
||||||
|
i2c_slave_detach_gpio(i2c);
|
||||||
|
i2c_ll_set_slave_addr(i2c->dev, 0, false);
|
||||||
|
i2c_ll_disable_intr_mask(i2c->dev, I2C_LL_INTR_MASK);
|
||||||
|
i2c_ll_clr_intsts_mask(i2c->dev, I2C_LL_INTR_MASK);
|
||||||
|
|
||||||
|
if (i2c->intr_handle) {
|
||||||
|
esp_intr_free(i2c->intr_handle);
|
||||||
|
i2c->intr_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(i2c->task_handle){
|
||||||
|
vTaskDelete(i2c->task_handle);
|
||||||
|
i2c->task_handle = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
if (i2c->rx_queue) {
|
||||||
|
vQueueDelete(i2c->rx_queue);
|
||||||
|
i2c->rx_queue = NULL;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if (i2c->rx_ring_buf) {
|
||||||
|
vRingbufferDelete(i2c->rx_ring_buf);
|
||||||
|
i2c->rx_ring_buf = NULL;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (i2c->tx_queue) {
|
||||||
|
vQueueDelete(i2c->tx_queue);
|
||||||
|
i2c->tx_queue = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i2c->event_queue) {
|
||||||
|
vQueueDelete(i2c->event_queue);
|
||||||
|
i2c->event_queue = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c->rx_data_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_set_frequency(i2c_slave_struct_t * i2c, uint32_t clk_speed)
|
||||||
|
{
|
||||||
|
if (i2c == NULL) {
|
||||||
|
log_e("no control buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(clk_speed > 1100000UL){
|
||||||
|
clk_speed = 1100000UL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adjust Fifo thresholds based on frequency
|
||||||
|
uint32_t a = (clk_speed / 50000L) + 2;
|
||||||
|
log_d("Fifo thresholds: rx_fifo_full = %d, tx_fifo_empty = %d", SOC_I2C_FIFO_LEN - a, a);
|
||||||
|
|
||||||
|
i2c_clk_cal_t clk_cal;
|
||||||
|
#if SOC_I2C_SUPPORT_APB
|
||||||
|
i2c_ll_cal_bus_clk(APB_CLK_FREQ, clk_speed, &clk_cal);
|
||||||
|
i2c_ll_set_source_clk(i2c->dev, I2C_SCLK_APB); /*!< I2C source clock from APB, 80M*/
|
||||||
|
#elif SOC_I2C_SUPPORT_XTAL
|
||||||
|
i2c_ll_cal_bus_clk(XTAL_CLK_FREQ, clk_speed, &clk_cal);
|
||||||
|
i2c_ll_set_source_clk(i2c->dev, I2C_SCLK_XTAL); /*!< I2C source clock from XTAL, 40M */
|
||||||
|
#endif
|
||||||
|
i2c_ll_set_txfifo_empty_thr(i2c->dev, a);
|
||||||
|
i2c_ll_set_rxfifo_full_thr(i2c->dev, SOC_I2C_FIFO_LEN - a);
|
||||||
|
i2c_ll_set_bus_timing(i2c->dev, &clk_cal);
|
||||||
|
i2c_ll_set_filter(i2c->dev, 3);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_delay_us(uint64_t us)
|
||||||
|
{
|
||||||
|
uint64_t m = esp_timer_get_time();
|
||||||
|
if (us) {
|
||||||
|
uint64_t e = (m + us);
|
||||||
|
if (m > e) { //overflow
|
||||||
|
while ((uint64_t)esp_timer_get_time() > e);
|
||||||
|
}
|
||||||
|
while ((uint64_t)esp_timer_get_time() < e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_gpio_mode(int8_t pin, gpio_mode_t mode)
|
||||||
|
{
|
||||||
|
gpio_config_t conf = {
|
||||||
|
.pin_bit_mask = 1LL << pin,
|
||||||
|
.mode = mode,
|
||||||
|
.pull_up_en = GPIO_PULLUP_ENABLE,
|
||||||
|
.pull_down_en = GPIO_PULLDOWN_DISABLE,
|
||||||
|
.intr_type = GPIO_INTR_DISABLE
|
||||||
|
};
|
||||||
|
gpio_config(&conf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_check_line_state(int8_t sda, int8_t scl)
|
||||||
|
{
|
||||||
|
if (sda < 0 || scl < 0) {
|
||||||
|
return false;//return false since there is nothing to do
|
||||||
|
}
|
||||||
|
// if the bus is not 'clear' try the cycling SCL until SDA goes High or 9 cycles
|
||||||
|
gpio_set_level(sda, 1);
|
||||||
|
gpio_set_level(scl, 1);
|
||||||
|
i2c_slave_gpio_mode(sda, GPIO_MODE_INPUT | GPIO_MODE_DEF_OD);
|
||||||
|
i2c_slave_gpio_mode(scl, GPIO_MODE_INPUT | GPIO_MODE_DEF_OD);
|
||||||
|
gpio_set_level(scl, 1);
|
||||||
|
|
||||||
|
if (!gpio_get_level(sda) || !gpio_get_level(scl)) { // bus in busy state
|
||||||
|
log_w("invalid state sda(%d)=%d, scl(%d)=%d", sda, gpio_get_level(sda), scl, gpio_get_level(scl));
|
||||||
|
for (uint8_t a=0; a<9; a++) {
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
if (gpio_get_level(sda) && gpio_get_level(scl)) { // bus recovered
|
||||||
|
log_w("Recovered after %d Cycles",a);
|
||||||
|
gpio_set_level(sda,0); // start
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
for (uint8_t a=0;a<9; a++) {
|
||||||
|
gpio_set_level(scl,1);
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
gpio_set_level(scl,0);
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
}
|
||||||
|
gpio_set_level(scl,1);
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
gpio_set_level(sda,1); // stop
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
gpio_set_level(scl, 0);
|
||||||
|
i2c_slave_delay_us(5);
|
||||||
|
gpio_set_level(scl, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!gpio_get_level(sda) || !gpio_get_level(scl)) { // bus in busy state
|
||||||
|
log_e("Bus Invalid State, Can't init sda=%d, scl=%d",gpio_get_level(sda),gpio_get_level(scl));
|
||||||
|
return false; // bus is busy
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_attach_gpio(i2c_slave_struct_t * i2c, int8_t sda, int8_t scl)
|
||||||
|
{
|
||||||
|
if (i2c == NULL) {
|
||||||
|
log_e("no control block");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((sda < 0)||( scl < 0)) {
|
||||||
|
log_e("bad pins sda=%d, scl=%d",sda,scl);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
i2c->scl = scl;
|
||||||
|
gpio_set_level(scl, 1);
|
||||||
|
i2c_slave_gpio_mode(scl, GPIO_MODE_INPUT_OUTPUT_OD);
|
||||||
|
gpio_matrix_out(scl, I2C_SCL_IDX(i2c->num), false, false);
|
||||||
|
gpio_matrix_in(scl, I2C_SCL_IDX(i2c->num), false);
|
||||||
|
|
||||||
|
i2c->sda = sda;
|
||||||
|
gpio_set_level(sda, 1);
|
||||||
|
i2c_slave_gpio_mode(sda, GPIO_MODE_INPUT_OUTPUT_OD);
|
||||||
|
gpio_matrix_out(sda, I2C_SDA_IDX(i2c->num), false, false);
|
||||||
|
gpio_matrix_in(sda, I2C_SDA_IDX(i2c->num), false);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_detach_gpio(i2c_slave_struct_t * i2c)
|
||||||
|
{
|
||||||
|
if (i2c == NULL) {
|
||||||
|
log_e("no control Block");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (i2c->scl >= 0) {
|
||||||
|
gpio_matrix_out(i2c->scl, 0x100, false, false);
|
||||||
|
gpio_matrix_in(0x30, I2C_SCL_IDX(i2c->num), false);
|
||||||
|
i2c_slave_gpio_mode(i2c->scl, GPIO_MODE_INPUT);
|
||||||
|
i2c->scl = -1; // un attached
|
||||||
|
}
|
||||||
|
if (i2c->sda >= 0) {
|
||||||
|
gpio_matrix_out(i2c->sda, 0x100, false, false);
|
||||||
|
gpio_matrix_in(0x30, I2C_SDA_IDX(i2c->num), false);
|
||||||
|
i2c_slave_gpio_mode(i2c->sda, GPIO_MODE_INPUT);
|
||||||
|
i2c->sda = -1; // un attached
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_send_event(i2c_slave_struct_t * i2c, i2c_slave_queue_event_t* event)
|
||||||
|
{
|
||||||
|
bool pxHigherPriorityTaskWoken = false;
|
||||||
|
if(i2c->event_queue) {
|
||||||
|
if(xQueueSendFromISR(i2c->event_queue, event, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){
|
||||||
|
//log_e("event_queue_full");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxHigherPriorityTaskWoken;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_handle_tx_fifo_empty(i2c_slave_struct_t * i2c)
|
||||||
|
{
|
||||||
|
bool pxHigherPriorityTaskWoken = false;
|
||||||
|
uint32_t d = 0, moveCnt = i2c_ll_get_txfifo_len(i2c->dev);
|
||||||
|
while (moveCnt > 0) { // read tx queue until Fifo is full or queue is empty
|
||||||
|
if(xQueueReceiveFromISR(i2c->tx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) == pdTRUE){
|
||||||
|
i2c_ll_write_txfifo(i2c->dev, (uint8_t*)&d, 1);
|
||||||
|
moveCnt--;
|
||||||
|
} else {
|
||||||
|
i2c_ll_slave_disable_tx_it(i2c->dev);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pxHigherPriorityTaskWoken;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool i2c_slave_handle_rx_fifo_full(i2c_slave_struct_t * i2c, uint32_t len)
|
||||||
|
{
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
uint32_t d = 0;
|
||||||
|
#else
|
||||||
|
uint8_t data[SOC_I2C_FIFO_LEN];
|
||||||
|
#endif
|
||||||
|
bool pxHigherPriorityTaskWoken = false;
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
while (len > 0) {
|
||||||
|
i2c_ll_read_rxfifo(i2c->dev, (uint8_t*)&d, 1);
|
||||||
|
if(xQueueSendFromISR(i2c->rx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){
|
||||||
|
log_e("rx_queue_full");
|
||||||
|
} else {
|
||||||
|
i2c->rx_data_count++;
|
||||||
|
}
|
||||||
|
if (--len == 0) {
|
||||||
|
len = i2c_ll_get_rxfifo_cnt(i2c->dev);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
if(len){
|
||||||
|
i2c_ll_read_rxfifo(i2c->dev, data, len);
|
||||||
|
if(xRingbufferSendFromISR(i2c->rx_ring_buf, (void*) data, len, (BaseType_t * const)&pxHigherPriorityTaskWoken) != pdTRUE){
|
||||||
|
log_e("rx_ring_buf_full");
|
||||||
|
} else {
|
||||||
|
i2c->rx_data_count += len;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
return pxHigherPriorityTaskWoken;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_isr_handler(void* arg)
|
||||||
|
{
|
||||||
|
bool pxHigherPriorityTaskWoken = false;
|
||||||
|
i2c_slave_struct_t * i2c = (i2c_slave_struct_t *) arg; // recover data
|
||||||
|
|
||||||
|
uint32_t activeInt = i2c_ll_get_intsts_mask(i2c->dev);
|
||||||
|
i2c_ll_clr_intsts_mask(i2c->dev, activeInt);
|
||||||
|
uint8_t rx_fifo_len = i2c_ll_get_rxfifo_cnt(i2c->dev);
|
||||||
|
bool slave_rw = i2c_ll_slave_rw(i2c->dev);
|
||||||
|
|
||||||
|
if(activeInt & I2C_RXFIFO_WM_INT_ENA){ // RX FiFo Full
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len);
|
||||||
|
i2c_ll_slave_enable_rx_it(i2c->dev);//is this necessary?
|
||||||
|
}
|
||||||
|
|
||||||
|
if(activeInt & I2C_TRANS_COMPLETE_INT_ENA){ // STOP
|
||||||
|
if(rx_fifo_len){ //READ RX FIFO
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len);
|
||||||
|
}
|
||||||
|
if(i2c->rx_data_count){ //WRITE or RepeatedStart
|
||||||
|
//SEND RX Event
|
||||||
|
i2c_slave_queue_event_t event;
|
||||||
|
event.event = I2C_SLAVE_EVT_RX;
|
||||||
|
event.stop = !slave_rw;
|
||||||
|
event.param = i2c->rx_data_count;
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event);
|
||||||
|
//Zero RX count
|
||||||
|
i2c->rx_data_count = 0;
|
||||||
|
}
|
||||||
|
if(slave_rw){ // READ
|
||||||
|
#if CONFIG_IDF_TARGET_ESP32
|
||||||
|
if(i2c->dev->status_reg.scl_main_state_last == 6){
|
||||||
|
//SEND TX Event
|
||||||
|
i2c_slave_queue_event_t event;
|
||||||
|
event.event = I2C_SLAVE_EVT_TX;
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
//reset TX data
|
||||||
|
i2c_ll_txfifo_rst(i2c->dev);
|
||||||
|
uint8_t d;
|
||||||
|
while (xQueueReceiveFromISR(i2c->tx_queue, &d, (BaseType_t * const)&pxHigherPriorityTaskWoken) == pdTRUE) ;//flush partial write
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef CONFIG_IDF_TARGET_ESP32
|
||||||
|
if(activeInt & I2C_SLAVE_STRETCH_INT_ENA){ // STRETCH
|
||||||
|
i2c_stretch_cause_t cause = i2c_ll_stretch_cause(i2c->dev);
|
||||||
|
if(cause == I2C_STRETCH_CAUSE_MASTER_READ){
|
||||||
|
//on C3 RX data dissapears with repeated start, so we need to get it here
|
||||||
|
if(rx_fifo_len){
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len);
|
||||||
|
}
|
||||||
|
//SEND TX Event
|
||||||
|
i2c_slave_queue_event_t event;
|
||||||
|
event.event = I2C_SLAVE_EVT_TX;
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_send_event(i2c, &event);
|
||||||
|
//will clear after execution
|
||||||
|
} else if(cause == I2C_STRETCH_CAUSE_TX_FIFO_EMPTY){
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_tx_fifo_empty(i2c);
|
||||||
|
i2c_ll_stretch_clr(i2c->dev);
|
||||||
|
} else if(cause == I2C_STRETCH_CAUSE_RX_FIFO_FULL){
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_rx_fifo_full(i2c, rx_fifo_len);
|
||||||
|
i2c_ll_stretch_clr(i2c->dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if(activeInt & I2C_TXFIFO_WM_INT_ENA){ // TX FiFo Empty
|
||||||
|
pxHigherPriorityTaskWoken |= i2c_slave_handle_tx_fifo_empty(i2c);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pxHigherPriorityTaskWoken){
|
||||||
|
portYIELD_FROM_ISR();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t i2c_slave_read_rx(i2c_slave_struct_t * i2c, uint8_t * data, size_t len){
|
||||||
|
if(!len){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#if I2C_SLAVE_USE_RX_QUEUE
|
||||||
|
uint8_t d = 0;
|
||||||
|
BaseType_t res = pdTRUE;
|
||||||
|
for(size_t i=0; i<len; i++) {
|
||||||
|
if(data){
|
||||||
|
res = xQueueReceive(i2c->rx_queue, &data[i], 0);
|
||||||
|
} else {
|
||||||
|
res = xQueueReceive(i2c->rx_queue, &d, 0);
|
||||||
|
}
|
||||||
|
if (res != pdTRUE) {
|
||||||
|
log_e("Read Queue(%u) Failed", i);
|
||||||
|
len = i;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return (data)?len:0;
|
||||||
|
#else
|
||||||
|
size_t dlen = 0,
|
||||||
|
to_read = len,
|
||||||
|
so_far = 0,
|
||||||
|
available = 0;
|
||||||
|
uint8_t * rx_data = NULL;
|
||||||
|
|
||||||
|
vRingbufferGetInfo(i2c->rx_ring_buf, NULL, NULL, NULL, NULL, &available);
|
||||||
|
if(available < to_read){
|
||||||
|
log_e("Less available than requested. %u < %u", available, len);
|
||||||
|
to_read = available;
|
||||||
|
}
|
||||||
|
|
||||||
|
while(to_read){
|
||||||
|
dlen = 0;
|
||||||
|
rx_data = (uint8_t *)xRingbufferReceiveUpTo(i2c->rx_ring_buf, &dlen, 0, to_read);
|
||||||
|
if(!rx_data){
|
||||||
|
log_e("Receive %u Failed", to_read);
|
||||||
|
return so_far;
|
||||||
|
}
|
||||||
|
if(data){
|
||||||
|
memcpy(data+so_far, rx_data, dlen);
|
||||||
|
}
|
||||||
|
vRingbufferReturnItem(i2c->rx_ring_buf, rx_data);
|
||||||
|
so_far+=dlen;
|
||||||
|
to_read-=dlen;
|
||||||
|
}
|
||||||
|
return (data)?so_far:0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
static void i2c_slave_task(void *pv_args)
|
||||||
|
{
|
||||||
|
i2c_slave_struct_t * i2c = (i2c_slave_struct_t *)pv_args;
|
||||||
|
i2c_slave_queue_event_t event;
|
||||||
|
size_t len = 0;
|
||||||
|
bool stop = false;
|
||||||
|
uint8_t * data = NULL;
|
||||||
|
for(;;){
|
||||||
|
if(xQueueReceive(i2c->event_queue, &event, portMAX_DELAY) == pdTRUE){
|
||||||
|
// Write
|
||||||
|
if(event.event == I2C_SLAVE_EVT_RX){
|
||||||
|
len = event.param;
|
||||||
|
stop = event.stop;
|
||||||
|
data = (len > 0)?(uint8_t*)malloc(len):NULL;
|
||||||
|
|
||||||
|
if(len && data == NULL){
|
||||||
|
log_e("Malloc (%u) Failed", len);
|
||||||
|
}
|
||||||
|
len = i2c_slave_read_rx(i2c, data, len);
|
||||||
|
if(i2c->receive_callback){
|
||||||
|
i2c->receive_callback(i2c->num, data, len, stop, i2c->arg);
|
||||||
|
}
|
||||||
|
free(data);
|
||||||
|
|
||||||
|
// Read
|
||||||
|
} else if(event.event == I2C_SLAVE_EVT_TX){
|
||||||
|
if(i2c->request_callback){
|
||||||
|
i2c->request_callback(i2c->num, i2c->arg);
|
||||||
|
}
|
||||||
|
i2c_ll_stretch_clr(i2c->dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vTaskDelete(NULL);
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue