mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-30 19:03:44 +02:00
Compare commits
53 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
2632d44ec9 | ||
|
c8e5123c0a | ||
|
e7e26551ce | ||
|
50b854c526 | ||
|
55a789d65a | ||
|
a69b7ee113 | ||
|
114686d124 | ||
|
005ddef665 | ||
|
d7fd9e1538 | ||
|
10209ed6f3 | ||
|
71117bc7a1 | ||
|
97065e892d | ||
|
4668e116f4 | ||
|
5cbf0c2cad | ||
|
c02e976c9f | ||
|
55c7a0a1e8 | ||
|
d7e46ac625 | ||
|
877db433a4 | ||
|
4901f12fcd | ||
|
836ccc143e | ||
|
77ee57eb83 | ||
|
837b0a9fb6 | ||
|
a109ba4e01 | ||
|
c87a80928b | ||
|
c5b283bd8c | ||
|
500fe2f717 | ||
|
278f7618f4 | ||
|
9d74b0f6a5 | ||
|
31059a615c | ||
|
7d7b337f82 | ||
|
05eb0d763a | ||
|
b6cfc39d23 | ||
|
8a0ddb0d74 | ||
|
faeb3194db | ||
|
26bd3ac342 | ||
|
d174c05127 | ||
|
75dffd9dfa | ||
|
0a10dbea0b | ||
|
43191e225e | ||
|
50bb1c950b | ||
|
0bb6b577fa | ||
|
cf0c818138 | ||
|
426b27f0dd | ||
|
19b4893b5f | ||
|
1c7a5320d8 | ||
|
afd4626988 | ||
|
a194b8965c | ||
|
696d12fc5e | ||
|
35cba02ee7 | ||
|
fa1d1619b6 | ||
|
b048879eaa | ||
|
34474cbf5c | ||
|
7397a4089b |
@@ -58,6 +58,9 @@ void setup()
|
|||||||
// Responsibility 1: Start the usb connection for diagnostics
|
// Responsibility 1: Start the usb connection for diagnostics
|
||||||
// This is normally Serial but uses SerialUSB on a SAMD processor
|
// This is normally Serial but uses SerialUSB on a SAMD processor
|
||||||
Serial.begin(115200);
|
Serial.begin(115200);
|
||||||
|
#ifdef ESP_DEBUG
|
||||||
|
Serial.setDebugOutput(true);
|
||||||
|
#endif
|
||||||
|
|
||||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||||
|
|
||||||
@@ -71,9 +74,12 @@ void setup()
|
|||||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||||
// Start Ethernet if it exists
|
// Start Ethernet if it exists
|
||||||
#if WIFI_ON
|
#if WIFI_ON
|
||||||
|
#ifndef ESP_FAMILY
|
||||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
||||||
|
#else
|
||||||
|
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
|
||||||
|
#endif
|
||||||
#endif // WIFI_ON
|
#endif // WIFI_ON
|
||||||
|
|
||||||
#if ETHERNET_ON
|
#if ETHERNET_ON
|
||||||
EthernetInterface::setup();
|
EthernetInterface::setup();
|
||||||
#endif // ETHERNET_ON
|
#endif // ETHERNET_ON
|
||||||
@@ -112,14 +118,18 @@ void loop()
|
|||||||
// Responsibility 1: Handle DCC background processes
|
// Responsibility 1: Handle DCC background processes
|
||||||
// (loco reminders and power checks)
|
// (loco reminders and power checks)
|
||||||
DCC::loop();
|
DCC::loop();
|
||||||
|
|
||||||
// Responsibility 2: handle any incoming commands on USB connection
|
// Responsibility 2: handle any incoming commands on USB connection
|
||||||
serialParser.loop(Serial);
|
serialParser.loop(Serial);
|
||||||
|
|
||||||
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
// Responsibility 3: Optionally handle any incoming WiFi traffic
|
||||||
#if WIFI_ON
|
#if WIFI_ON
|
||||||
|
#ifndef ESP_FAMILY
|
||||||
WifiInterface::loop();
|
WifiInterface::loop();
|
||||||
#endif
|
#endif
|
||||||
|
#if defined(ARDUINO_ARCH_ESP8266) // on ESP32 own task
|
||||||
|
WifiESP::loop();
|
||||||
|
#endif
|
||||||
|
#endif //WIFI_ON
|
||||||
#if ETHERNET_ON
|
#if ETHERNET_ON
|
||||||
EthernetInterface::loop();
|
EthernetInterface::loop();
|
||||||
#endif
|
#endif
|
||||||
@@ -137,7 +147,9 @@ void loop()
|
|||||||
|
|
||||||
// Report any decrease in memory (will automatically trigger on first call)
|
// Report any decrease in memory (will automatically trigger on first call)
|
||||||
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
|
||||||
|
#ifdef ESP_FAMILY
|
||||||
|
updateMinimumFreeMemory(128);
|
||||||
|
#endif
|
||||||
int freeNow = minimumFreeMemory();
|
int freeNow = minimumFreeMemory();
|
||||||
if (freeNow < ramLowWatermark)
|
if (freeNow < ramLowWatermark)
|
||||||
{
|
{
|
||||||
|
24
DCC.cpp
24
DCC.cpp
@@ -311,14 +311,14 @@ const ackOp FLASH WRITE_BIT0_PROG[] = {
|
|||||||
W0,WACK,
|
W0,WACK,
|
||||||
V0, WACK, // validate bit is 0
|
V0, WACK, // validate bit is 0
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
FAIL // callback (-1)
|
CALLFAIL // callback (-1)
|
||||||
};
|
};
|
||||||
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
const ackOp FLASH WRITE_BIT1_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
W1,WACK,
|
W1,WACK,
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
FAIL // callback (-1)
|
CALLFAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
||||||
@@ -327,7 +327,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = {
|
|||||||
ITC0, // if acked, callback(0)
|
ITC0, // if acked, callback(0)
|
||||||
V1, WACK, // validate bit is 1
|
V1, WACK, // validate bit is 1
|
||||||
ITC1,
|
ITC1,
|
||||||
FAIL // callback (-1)
|
CALLFAIL // callback (-1)
|
||||||
};
|
};
|
||||||
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
||||||
BASELINE,
|
BASELINE,
|
||||||
@@ -335,7 +335,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = {
|
|||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
V0, WACK,
|
V0, WACK,
|
||||||
ITC0,
|
ITC0,
|
||||||
FAIL // callback (-1)
|
CALLFAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH READ_BIT_PROG[] = {
|
const ackOp FLASH READ_BIT_PROG[] = {
|
||||||
@@ -344,7 +344,7 @@ const ackOp FLASH READ_BIT_PROG[] = {
|
|||||||
ITC1, // if acked, callback(1)
|
ITC1, // if acked, callback(1)
|
||||||
V0, WACK, // validate bit is zero
|
V0, WACK, // validate bit is zero
|
||||||
ITC0, // if acked callback 0
|
ITC0, // if acked callback 0
|
||||||
FAIL // bit not readable
|
CALLFAIL // bit not readable
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
const ackOp FLASH WRITE_BYTE_PROG[] = {
|
||||||
@@ -352,7 +352,7 @@ const ackOp FLASH WRITE_BYTE_PROG[] = {
|
|||||||
WB,WACK,ITC1, // Write and callback(1) if ACK
|
WB,WACK,ITC1, // Write and callback(1) if ACK
|
||||||
// handle decoders that dont ack a write
|
// handle decoders that dont ack a write
|
||||||
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
VB,WACK,ITC1, // validate byte and callback(1) if correct
|
||||||
FAIL // callback (-1)
|
CALLFAIL // callback (-1)
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
||||||
@@ -378,7 +378,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
|
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
|
||||||
FAIL };
|
CALLFAIL };
|
||||||
|
|
||||||
|
|
||||||
const ackOp FLASH READ_CV_PROG[] = {
|
const ackOp FLASH READ_CV_PROG[] = {
|
||||||
@@ -401,7 +401,7 @@ const ackOp FLASH READ_CV_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, ITCB, // verify merged byte and return it if acked ok
|
VB, WACK, ITCB, // verify merged byte and return it if acked ok
|
||||||
FAIL }; // verification failed
|
CALLFAIL }; // verification failed
|
||||||
|
|
||||||
|
|
||||||
const ackOp FLASH LOCO_ID_PROG[] = {
|
const ackOp FLASH LOCO_ID_PROG[] = {
|
||||||
@@ -467,7 +467,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
|
|||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
V0, WACK, MERGE,
|
V0, WACK, MERGE,
|
||||||
VB, WACK, ITCB, // verify merged byte and callback
|
VB, WACK, ITCB, // verify merged byte and callback
|
||||||
FAIL
|
CALLFAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
||||||
@@ -484,7 +484,7 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
|
|||||||
SETBYTEL, // low byte of word
|
SETBYTEL, // low byte of word
|
||||||
WB,WACK, // some decoders don't ACK writes
|
WB,WACK, // some decoders don't ACK writes
|
||||||
VB,WACK,ITCB,
|
VB,WACK,ITCB,
|
||||||
FAIL
|
CALLFAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
||||||
@@ -508,7 +508,7 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
|
|||||||
SETBYTEL, // low byte of word
|
SETBYTEL, // low byte of word
|
||||||
WB,WACK,
|
WB,WACK,
|
||||||
VB,WACK,ITC1, // callback(1) means Ok
|
VB,WACK,ITC1, // callback(1) means Ok
|
||||||
FAIL
|
CALLFAIL
|
||||||
};
|
};
|
||||||
|
|
||||||
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
|
||||||
@@ -857,7 +857,7 @@ void DCC::ackManagerLoop() {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case FAIL: // callback(-1)
|
case CALLFAIL: // callback(-1)
|
||||||
callback(-1);
|
callback(-1);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
6
DCC.h
6
DCC.h
@@ -41,7 +41,7 @@ enum ackOp : byte
|
|||||||
ITCBV, // If True callback(byte) - end of Verify Byte
|
ITCBV, // If True callback(byte) - end of Verify Byte
|
||||||
ITCB7, // If True callback(byte &0x7F)
|
ITCB7, // If True callback(byte &0x7F)
|
||||||
NAKFAIL, // if false callback(-1)
|
NAKFAIL, // if false callback(-1)
|
||||||
FAIL, // callback(-1)
|
CALLFAIL, // callback(-1)
|
||||||
BIV, // Set ackManagerByte to initial value for Verify retry
|
BIV, // Set ackManagerByte to initial value for Verify retry
|
||||||
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
STARTMERGE, // Clear bit and byte settings ready for merge pass
|
||||||
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
MERGE, // Merge previous wack response with byte value and decrement bit number (use for readimng CV bytes)
|
||||||
@@ -207,6 +207,10 @@ private:
|
|||||||
#define ARDUINO_TYPE "TEENSY40"
|
#define ARDUINO_TYPE "TEENSY40"
|
||||||
#elif defined(ARDUINO_TEENSY41)
|
#elif defined(ARDUINO_TEENSY41)
|
||||||
#define ARDUINO_TYPE "TEENSY41"
|
#define ARDUINO_TYPE "TEENSY41"
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
#define ARDUINO_TYPE "ESP8266"
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
|
#define ARDUINO_TYPE "ESP32"
|
||||||
#else
|
#else
|
||||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
|
||||||
#endif
|
#endif
|
||||||
|
11
DCCEX.h
11
DCCEX.h
@@ -30,7 +30,13 @@
|
|||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
#if defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
#include "WifiESP8266.h"
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
|
#include "WifiESP32.h"
|
||||||
|
#else
|
||||||
#include "WifiInterface.h"
|
#include "WifiInterface.h"
|
||||||
|
#endif
|
||||||
#if ETHERNET_ON == true
|
#if ETHERNET_ON == true
|
||||||
#include "EthernetInterface.h"
|
#include "EthernetInterface.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -43,5 +49,10 @@
|
|||||||
#include "Outputs.h"
|
#include "Outputs.h"
|
||||||
#include "RMFT.h"
|
#include "RMFT.h"
|
||||||
|
|
||||||
|
// not yet in this branch
|
||||||
|
//#if __has_include ( "myAutomation.h")
|
||||||
|
// #include "RMFT.h"
|
||||||
|
// #define RMFT_ACTIVE
|
||||||
|
//#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -17,9 +17,10 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#include "DCC.h" // includes "Motordriver.h" and <Arduino.h>
|
||||||
|
#include "defines.h"
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include "DCC.h"
|
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "Turnouts.h"
|
#include "Turnouts.h"
|
||||||
#include "Outputs.h"
|
#include "Outputs.h"
|
||||||
@@ -31,7 +32,9 @@
|
|||||||
|
|
||||||
#include "EEStore.h"
|
#include "EEStore.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#ifndef ESP_FAMILY
|
||||||
#include <avr/wdt.h>
|
#include <avr/wdt.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
@@ -860,8 +863,12 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
|||||||
|
|
||||||
case HASH_KEYWORD_RESET:
|
case HASH_KEYWORD_RESET:
|
||||||
{
|
{
|
||||||
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
#ifndef ESP_FAMILY
|
||||||
delay(50); // wait for the prescaller time to expire
|
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
|
||||||
|
delay(50); // wait for the prescaler time to expire
|
||||||
|
#else
|
||||||
|
/* XXX do right thing to reboot */
|
||||||
|
#endif
|
||||||
break; // and <X> if we didnt restart
|
break; // and <X> if we didnt restart
|
||||||
}
|
}
|
||||||
|
|
||||||
|
168
DCCRMT.cpp
Normal file
168
DCCRMT.cpp
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Harald Barth.
|
||||||
|
*
|
||||||
|
* This file is part of DCC-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "config.h"
|
||||||
|
#include "defines.h"
|
||||||
|
#include "DIAG.h"
|
||||||
|
#include "DCCRMT.h"
|
||||||
|
#include "DCCWaveform.h" // for MAX_PACKET_SIZE
|
||||||
|
|
||||||
|
#define DATA_LEN(X) ((X)*9+1) // Each byte has one bit extra and we have one EOF marker
|
||||||
|
|
||||||
|
#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4,2,0)
|
||||||
|
#error wrong IDF version
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void setDCCBit1(rmt_item32_t* item) {
|
||||||
|
item->level0 = 1;
|
||||||
|
item->duration0 = DCC_1_HALFPERIOD;
|
||||||
|
item->level1 = 0;
|
||||||
|
item->duration1 = DCC_1_HALFPERIOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDCCBit0(rmt_item32_t* item) {
|
||||||
|
item->level0 = 1;
|
||||||
|
item->duration0 = DCC_0_HALFPERIOD;
|
||||||
|
item->level1 = 0;
|
||||||
|
item->duration1 = DCC_0_HALFPERIOD;
|
||||||
|
}
|
||||||
|
|
||||||
|
// special long zero to trigger scope
|
||||||
|
void setDCCBit0Long(rmt_item32_t* item) {
|
||||||
|
item->level0 = 1;
|
||||||
|
item->duration0 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
|
||||||
|
item->level1 = 0;
|
||||||
|
item->duration1 = DCC_0_HALFPERIOD + DCC_0_HALFPERIOD/10;
|
||||||
|
}
|
||||||
|
|
||||||
|
void setEOT(rmt_item32_t* item) {
|
||||||
|
item->val = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR interrupt(rmt_channel_t channel, void *t) {
|
||||||
|
RMTPin *tt = (RMTPin *)t;
|
||||||
|
tt->RMTinterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
RMTPin::RMTPin(byte pin, byte ch, byte plen) {
|
||||||
|
|
||||||
|
// preamble
|
||||||
|
preambleLen = plen+2; // plen 1 bits, one 0 bit and one EOF marker
|
||||||
|
preamble = (rmt_item32_t*)malloc(preambleLen*sizeof(rmt_item32_t));
|
||||||
|
for (byte n=0; n<plen; n++)
|
||||||
|
setDCCBit1(preamble + n); // preamble bits
|
||||||
|
#ifdef SCOPE
|
||||||
|
setDCCBit0Long(preamble + plen); // start of packet 0 bit long version
|
||||||
|
#else
|
||||||
|
setDCCBit0(preamble + plen); // start of packet 0 bit normal version
|
||||||
|
#endif
|
||||||
|
setEOT(preamble + plen + 1); // EOT marker
|
||||||
|
|
||||||
|
// idle
|
||||||
|
idleLen = 28;
|
||||||
|
idle = (rmt_item32_t*)malloc(idleLen*sizeof(rmt_item32_t));
|
||||||
|
for (byte n=0; n<8; n++) // 0 to 7
|
||||||
|
setDCCBit1(idle + n);
|
||||||
|
for (byte n=8; n<18; n++) // 8, 9 to 16, 17
|
||||||
|
setDCCBit0(idle + n);
|
||||||
|
for (byte n=18; n<26; n++) // 18 to 25
|
||||||
|
setDCCBit1(idle + n);
|
||||||
|
setDCCBit1(idle + 26); // end bit
|
||||||
|
setEOT(idle + 27); // EOT marker
|
||||||
|
|
||||||
|
// data: max packet size today is 5 + checksum
|
||||||
|
maxDataLen = DATA_LEN(MAX_PACKET_SIZE);
|
||||||
|
data = (rmt_item32_t*)malloc(maxDataLen*sizeof(rmt_item32_t));
|
||||||
|
|
||||||
|
rmt_config_t config;
|
||||||
|
// Configure the RMT channel for TX
|
||||||
|
bzero(&config, sizeof(rmt_config_t));
|
||||||
|
config.rmt_mode = RMT_MODE_TX;
|
||||||
|
config.channel = channel = (rmt_channel_t)ch;
|
||||||
|
config.clk_div = RMT_CLOCK_DIVIDER;
|
||||||
|
config.gpio_num = (gpio_num_t)pin;
|
||||||
|
config.mem_block_num = 2; // With longest DCC packet 11 inc checksum (future expansion)
|
||||||
|
// number of bits needed is 22preamble + start +
|
||||||
|
// 11*9 + extrazero + EOT = 124
|
||||||
|
// 2 mem block of 64 RMT items should be enough
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(rmt_config(&config));
|
||||||
|
// NOTE: ESP_INTR_FLAG_IRAM is *NOT* included in this bitmask
|
||||||
|
ESP_ERROR_CHECK(rmt_driver_install(config.channel, 0, ESP_INTR_FLAG_LOWMED|ESP_INTR_FLAG_SHARED));
|
||||||
|
|
||||||
|
DIAG(F("Register interrupt on core %d"), xPortGetCoreID());
|
||||||
|
|
||||||
|
ESP_ERROR_CHECK(rmt_set_tx_loop_mode(channel, true));
|
||||||
|
rmt_register_tx_end_callback(interrupt, this);
|
||||||
|
rmt_set_tx_intr_en(channel, true);
|
||||||
|
|
||||||
|
DIAG(F("Starting channel %d signal generator"), config.channel);
|
||||||
|
|
||||||
|
// send one bit to kickstart the signal, remaining data will come from the
|
||||||
|
// packet queue. We intentionally do not wait for the RMT TX complete here.
|
||||||
|
//rmt_write_items(channel, preamble, preambleLen, false);
|
||||||
|
RMTprefill();
|
||||||
|
preambleNext = true;
|
||||||
|
dataReady = false;
|
||||||
|
RMTinterrupt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RMTPin::RMTprefill() {
|
||||||
|
rmt_fill_tx_items(channel, preamble, preambleLen, 0);
|
||||||
|
rmt_fill_tx_items(channel, idle, idleLen, preambleLen-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const byte transmitMask[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
||||||
|
|
||||||
|
bool RMTPin::RMTfillData(const byte buffer[], byte byteCount, byte repeatCount=1) {
|
||||||
|
if (dataReady == true || dataRepeat > 0) // we have still old work to do
|
||||||
|
return false;
|
||||||
|
if (DATA_LEN(byteCount) > maxDataLen) // this would overun our allocated memory for data
|
||||||
|
return false; // something very broken, can not convert packet
|
||||||
|
|
||||||
|
// convert bytes to RMT stream of "bits"
|
||||||
|
byte bitcounter = 0;
|
||||||
|
for(byte n=0; n<byteCount; n++) {
|
||||||
|
for(byte bit=0; bit<8; bit++) {
|
||||||
|
if (buffer[n] & transmitMask[bit])
|
||||||
|
setDCCBit1(data + bitcounter++);
|
||||||
|
else
|
||||||
|
setDCCBit0(data + bitcounter++);
|
||||||
|
}
|
||||||
|
setDCCBit0(data + bitcounter++); // zero at end of each byte
|
||||||
|
}
|
||||||
|
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
|
||||||
|
setEOT(data + bitcounter++); // EOT marker
|
||||||
|
dataLen = bitcounter;
|
||||||
|
dataReady = true;
|
||||||
|
dataRepeat = repeatCount;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void IRAM_ATTR RMTPin::RMTinterrupt() {
|
||||||
|
//no rmt_tx_start(channel,true) as we run in loop mode
|
||||||
|
//preamble is always loaded at beginning of buffer
|
||||||
|
if (dataReady) { // if we have new data, fill while preamble is running
|
||||||
|
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
|
||||||
|
dataReady = false;
|
||||||
|
}
|
||||||
|
if (dataRepeat > 0) // if a repeat count was specified, work on that
|
||||||
|
dataRepeat--;
|
||||||
|
return;
|
||||||
|
}
|
57
DCCRMT.h
Normal file
57
DCCRMT.h
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Harald Barth.
|
||||||
|
*
|
||||||
|
* This file is part of DCC-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <Arduino.h>
|
||||||
|
#include "driver/rmt.h"
|
||||||
|
#include "soc/rmt_reg.h"
|
||||||
|
#include "soc/rmt_struct.h"
|
||||||
|
|
||||||
|
// make calculations easy and set up for microseconds
|
||||||
|
#define RMT_CLOCK_DIVIDER 80
|
||||||
|
#define DCC_1_HALFPERIOD 58 //4640 // 1 / 80000000 * 4640 = 58us
|
||||||
|
#define DCC_0_HALFPERIOD 100 //8000
|
||||||
|
|
||||||
|
class RMTPin {
|
||||||
|
public:
|
||||||
|
RMTPin(byte pin, byte ch, byte plen);
|
||||||
|
void IRAM_ATTR RMTinterrupt();
|
||||||
|
void RMTprefill();
|
||||||
|
bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount);
|
||||||
|
|
||||||
|
static RMTPin mainRMTPin;
|
||||||
|
static RMTPin progRMTPin;
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
|
rmt_channel_t channel;
|
||||||
|
// 3 types of data to send, preamble and then idle or data
|
||||||
|
// if this is prog track, idle will contain reset instead
|
||||||
|
rmt_item32_t *idle;
|
||||||
|
byte idleLen;
|
||||||
|
rmt_item32_t *preamble;
|
||||||
|
byte preambleLen;
|
||||||
|
rmt_item32_t *data;
|
||||||
|
byte dataLen;
|
||||||
|
byte maxDataLen;
|
||||||
|
// flags
|
||||||
|
volatile bool preambleNext = true; // alternate between preamble and content
|
||||||
|
volatile bool dataReady = false; // do we have real data available or send idle
|
||||||
|
volatile byte dataRepeat = 0;
|
||||||
|
};
|
54
DCCTimer.cpp
54
DCCTimer.cpp
@@ -44,7 +44,7 @@
|
|||||||
|
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
|
||||||
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
|
const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME);
|
||||||
|
|
||||||
INTERRUPT_CALLBACK interruptHandler=0;
|
INTERRUPT_CALLBACK interruptHandler=0;
|
||||||
|
|
||||||
@@ -53,11 +53,11 @@ INTERRUPT_CALLBACK interruptHandler=0;
|
|||||||
|
|
||||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interruptHandler=callback;
|
interruptHandler=callback;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
ADC0.CTRLC = (ADC0.CTRLC & 0b00110000) | 0b01000011; // speed up analogRead sample time
|
||||||
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
TCB0.CTRLB = TCB_CNTMODE_INT_gc & ~TCB_CCMPEN_bm; // timer compare mode with output disabled
|
||||||
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
TCB0.CTRLA = TCB_CLKSEL_CLKDIV2_gc; // 8 MHz ~ 0.125 us
|
||||||
TCB0.CCMP = CLOCK_CYCLES -1; // 1 tick less for timer reset
|
TCB0.CCMP = (CLOCK_CYCLES>>1) -1; // 1 tick less for timer reset
|
||||||
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
TCB0.INTFLAGS = TCB_CAPT_bm; // clear interrupt request flag
|
||||||
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
TCB0.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
|
||||||
TCB0.CNT = 0;
|
TCB0.CNT = 0;
|
||||||
@@ -146,6 +146,50 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
|
||||||
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
|
interruptHandler=callback;
|
||||||
|
timer1_disable();
|
||||||
|
|
||||||
|
// There seem to be differnt ways to attach interrupt handler
|
||||||
|
// ETS_FRC_TIMER1_INTR_ATTACH(NULL, NULL);
|
||||||
|
// ETS_FRC_TIMER1_NMI_INTR_ATTACH(interruptHandler);
|
||||||
|
// Let us choose the one from the API
|
||||||
|
timer1_attachInterrupt(interruptHandler);
|
||||||
|
|
||||||
|
// not exactly sure of order:
|
||||||
|
timer1_enable(TIM_DIV1, TIM_EDGE, TIM_LOOP);
|
||||||
|
timer1_write(CLOCK_CYCLES);
|
||||||
|
}
|
||||||
|
// We do not support to use PWM to make the Waveform on ESP
|
||||||
|
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
|
// https://www.visualmicro.com/page/Timer-Interrupts-Explained.aspx
|
||||||
|
|
||||||
|
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
|
||||||
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
|
interruptHandler = callback;
|
||||||
|
hw_timer_t *timer = NULL;
|
||||||
|
timer = timerBegin(0, 2, true); // prescaler can be 2 to 65536 so choose 2
|
||||||
|
timerAttachInterrupt(timer, interruptHandler, true);
|
||||||
|
timerAlarmWrite(timer, CLOCK_CYCLES / 6, true); // divide by prescaler*3 (Clockbase is 80Mhz and not F_CPU 240Mhz)
|
||||||
|
timerAlarmEnable(timer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We do not support to use PWM to make the Waveform on ESP
|
||||||
|
bool IRAM_ATTR DCCTimer::isPWMPin(byte pin) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
void IRAM_ATTR DCCTimer::setPWM(byte pin, bool high) {
|
||||||
|
}
|
||||||
|
|
||||||
#else
|
#else
|
||||||
// Arduino nano, uno, mega etc
|
// Arduino nano, uno, mega etc
|
||||||
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__)
|
||||||
@@ -159,10 +203,10 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
|
|||||||
|
|
||||||
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||||
interruptHandler=callback;
|
interruptHandler=callback;
|
||||||
noInterrupts();
|
noInterrupts();
|
||||||
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
|
||||||
TCCR1A = 0;
|
TCCR1A = 0;
|
||||||
ICR1 = CLOCK_CYCLES;
|
ICR1 = CLOCK_CYCLES>>1;
|
||||||
TCNT1 = 0;
|
TCNT1 = 0;
|
||||||
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
|
||||||
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
TIMSK1 = _BV(TOIE1); // Enable Software interrupt
|
||||||
|
10
DCCTimer.h
10
DCCTimer.h
@@ -37,4 +37,14 @@ class DCCTimer {
|
|||||||
private:
|
private:
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
extern portMUX_TYPE timerMux;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if !(defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266))
|
||||||
|
#ifndef IRAM_ATTR
|
||||||
|
#define IRAM_ATTR
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif //DCCTimer.h
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
#include "DCCWaveform.h"
|
#include "DCCWaveform.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
@@ -36,6 +37,9 @@ volatile uint8_t DCCWaveform::numAckSamples=0;
|
|||||||
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
uint8_t DCCWaveform::trailingEdgeCounter=0;
|
||||||
|
|
||||||
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
||||||
|
|
||||||
|
mainTrack.rmtPin = new RMTPin(21, 0, PREAMBLE_BITS_MAIN);
|
||||||
|
|
||||||
mainTrack.motorDriver=mainDriver;
|
mainTrack.motorDriver=mainDriver;
|
||||||
progTrack.motorDriver=progDriver;
|
progTrack.motorDriver=progDriver;
|
||||||
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
progTripValue = progDriver->mA2raw(TRIP_CURRENT_PROG); // need only calculate once hence static
|
||||||
@@ -51,33 +55,63 @@ void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
|
|||||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void DCCWaveform::loop(bool ackManagerActive) {
|
#ifdef SLOW_ANALOG_READ
|
||||||
mainTrack.checkPowerOverload(false);
|
// Flag to hold if we need to run ack checking in loop
|
||||||
|
volatile bool ackflag = 0;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void IRAM_ATTR DCCWaveform::loop(bool ackManagerActive) {
|
||||||
|
|
||||||
|
if (mainTrack.packetPendingRMT) {
|
||||||
|
mainTrack.rmtPin->RMTfillData(mainTrack.pendingPacket, mainTrack.pendingLength, mainTrack.pendingRepeats);
|
||||||
|
mainTrack.packetPendingRMT=false;
|
||||||
|
// sentResetsSincePacket = 0 // later when progtrack
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef SLOW_ANALOG_READ
|
||||||
|
if (ackflag) {
|
||||||
|
progTrack.checkAck();
|
||||||
|
// reset flag AFTER check is done
|
||||||
|
portENTER_CRITICAL(&timerMux);
|
||||||
|
ackflag = 0;
|
||||||
|
portEXIT_CRITICAL(&timerMux);
|
||||||
|
} else {
|
||||||
|
progTrack.checkPowerOverload(ackManagerActive);
|
||||||
|
}
|
||||||
|
#else
|
||||||
progTrack.checkPowerOverload(ackManagerActive);
|
progTrack.checkPowerOverload(ackManagerActive);
|
||||||
|
#endif
|
||||||
|
mainTrack.checkPowerOverload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
void DCCWaveform::interruptHandler() {
|
void IRAM_ATTR DCCWaveform::interruptHandler() {
|
||||||
// call the timer edge sensitive actions for progtrack and maintrack
|
// call the timer edge sensitive actions for progtrack and maintrack
|
||||||
// member functions would be cleaner but have more overhead
|
// member functions would be cleaner but have more overhead
|
||||||
byte sigMain=signalTransform[mainTrack.state];
|
byte sigMain=signalTransform[mainTrack.state];
|
||||||
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
|
||||||
|
|
||||||
// Set the signal state for both tracks
|
// Set the signal state for both tracks
|
||||||
mainTrack.motorDriver->setSignal(sigMain);
|
mainTrack.motorDriver->setSignal(sigMain);
|
||||||
progTrack.motorDriver->setSignal(sigProg);
|
progTrack.motorDriver->setSignal(sigProg);
|
||||||
|
|
||||||
// Move on in the state engine
|
// Move on in the state engine
|
||||||
mainTrack.state=stateTransform[mainTrack.state];
|
mainTrack.state=stateTransform[mainTrack.state];
|
||||||
progTrack.state=stateTransform[progTrack.state];
|
progTrack.state=stateTransform[progTrack.state];
|
||||||
|
|
||||||
|
|
||||||
// WAVE_PENDING means we dont yet know what the next bit is
|
// WAVE_PENDING means we dont yet know what the next bit is
|
||||||
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
|
if (mainTrack.state==WAVE_PENDING)
|
||||||
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
|
mainTrack.interrupt2();
|
||||||
else if (progTrack.ackPending) progTrack.checkAck();
|
if (progTrack.state==WAVE_PENDING)
|
||||||
|
progTrack.interrupt2();
|
||||||
|
#ifdef SLOW_ANALOG_READ
|
||||||
|
else if (progTrack.ackPending && ackflag == 0) { // We need AND we are not already checking
|
||||||
|
portENTER_CRITICAL(&timerMux);
|
||||||
|
ackflag = 1;
|
||||||
|
portEXIT_CRITICAL(&timerMux);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
else if (progTrack.ackPending)
|
||||||
|
progTrack.checkAck();
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
|
|
||||||
@@ -94,6 +128,7 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
|
|||||||
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
|
||||||
isMainTrack = isMain;
|
isMainTrack = isMain;
|
||||||
packetPending = false;
|
packetPending = false;
|
||||||
|
packetPendingRMT = false;
|
||||||
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
|
||||||
state = WAVE_START;
|
state = WAVE_START;
|
||||||
// The +1 below is to allow the preamble generator to create the stop bit
|
// The +1 below is to allow the preamble generator to create the stop bit
|
||||||
@@ -202,7 +237,7 @@ const bool DCCWaveform::signalTransform[]={
|
|||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
void DCCWaveform::interrupt2() {
|
void IRAM_ATTR DCCWaveform::interrupt2() {
|
||||||
// calculate the next bit to be sent:
|
// calculate the next bit to be sent:
|
||||||
// set state WAVE_MID_1 for a 1=bit
|
// set state WAVE_MID_1 for a 1=bit
|
||||||
// or WAVE_HIGH_0 for a 0 bit.
|
// or WAVE_HIGH_0 for a 0 bit.
|
||||||
@@ -212,7 +247,9 @@ void DCCWaveform::interrupt2() {
|
|||||||
remainingPreambles--;
|
remainingPreambles--;
|
||||||
// Update free memory diagnostic as we don't have anything else to do this time.
|
// Update free memory diagnostic as we don't have anything else to do this time.
|
||||||
// Allow for checkAck and its called functions using 22 bytes more.
|
// Allow for checkAck and its called functions using 22 bytes more.
|
||||||
updateMinimumFreeMemory(22);
|
#ifndef ESP_FAMILY
|
||||||
|
updateMinimumFreeMemory(22);
|
||||||
|
#endif
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -237,7 +274,8 @@ void DCCWaveform::interrupt2() {
|
|||||||
transmitRepeats--;
|
transmitRepeats--;
|
||||||
}
|
}
|
||||||
else if (packetPending) {
|
else if (packetPending) {
|
||||||
// Copy pending packet to transmit packet
|
portENTER_CRITICAL(&timerMux);
|
||||||
|
// Copy pending packet to transmit packet
|
||||||
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
// a fixed length memcpy is faster than a variable length loop for these small lengths
|
||||||
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
|
||||||
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
|
||||||
@@ -246,6 +284,7 @@ void DCCWaveform::interrupt2() {
|
|||||||
transmitRepeats = pendingRepeats;
|
transmitRepeats = pendingRepeats;
|
||||||
packetPending = false;
|
packetPending = false;
|
||||||
sentResetsSincePacket=0;
|
sentResetsSincePacket=0;
|
||||||
|
portEXIT_CRITICAL(&timerMux);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Fortunately reset and idle packets are the same length
|
// Fortunately reset and idle packets are the same length
|
||||||
@@ -264,7 +303,7 @@ void DCCWaveform::interrupt2() {
|
|||||||
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repeats) {
|
||||||
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
|
||||||
while (packetPending);
|
while (packetPending);
|
||||||
|
portENTER_CRITICAL(&timerMux);
|
||||||
byte checksum = 0;
|
byte checksum = 0;
|
||||||
for (byte b = 0; b < byteCount; b++) {
|
for (byte b = 0; b < byteCount; b++) {
|
||||||
checksum ^= buffer[b];
|
checksum ^= buffer[b];
|
||||||
@@ -275,7 +314,9 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
|
|||||||
pendingLength = byteCount + 1;
|
pendingLength = byteCount + 1;
|
||||||
pendingRepeats = repeats;
|
pendingRepeats = repeats;
|
||||||
packetPending = true;
|
packetPending = true;
|
||||||
|
packetPendingRMT = true;
|
||||||
sentResetsSincePacket=0;
|
sentResetsSincePacket=0;
|
||||||
|
portEXIT_CRITICAL(&timerMux);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Operations applicable to PROG track ONLY.
|
// Operations applicable to PROG track ONLY.
|
||||||
@@ -313,14 +354,14 @@ byte DCCWaveform::getAck() {
|
|||||||
|
|
||||||
#pragma GCC push_options
|
#pragma GCC push_options
|
||||||
#pragma GCC optimize ("-O3")
|
#pragma GCC optimize ("-O3")
|
||||||
void DCCWaveform::checkAck() {
|
void IRAM_ATTR DCCWaveform::checkAck() {
|
||||||
// This function operates in interrupt() time so must be fast and can't DIAG
|
// This function operates in interrupt() time so must be fast and can't DIAG
|
||||||
if (sentResetsSincePacket > 6) { //ACK timeout
|
if (sentResetsSincePacket > 6) { //ACK timeout
|
||||||
ackCheckDuration=millis()-ackCheckStart;
|
ackCheckDuration=millis()-ackCheckStart;
|
||||||
ackPending = false;
|
ackPending = false;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int current=motorDriver->getCurrentRaw();
|
int current=motorDriver->getCurrentRaw();
|
||||||
numAckSamples++;
|
numAckSamples++;
|
||||||
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
if (current > ackMaxCurrent) ackMaxCurrent=current;
|
||||||
|
@@ -20,6 +20,7 @@
|
|||||||
#ifndef DCCWaveform_h
|
#ifndef DCCWaveform_h
|
||||||
#define DCCWaveform_h
|
#define DCCWaveform_h
|
||||||
|
|
||||||
|
#include "DCCRMT.h"
|
||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
|
|
||||||
// Wait times for power management. Unit: milliseconds
|
// Wait times for power management. Unit: milliseconds
|
||||||
@@ -82,6 +83,7 @@ class DCCWaveform {
|
|||||||
}
|
}
|
||||||
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
|
||||||
volatile bool packetPending;
|
volatile bool packetPending;
|
||||||
|
volatile bool packetPendingRMT;
|
||||||
volatile byte sentResetsSincePacket;
|
volatile byte sentResetsSincePacket;
|
||||||
volatile bool autoPowerOff=false;
|
volatile bool autoPowerOff=false;
|
||||||
void setAckBaseline(); //prog track only
|
void setAckBaseline(); //prog track only
|
||||||
@@ -122,6 +124,7 @@ class DCCWaveform {
|
|||||||
|
|
||||||
bool isMainTrack;
|
bool isMainTrack;
|
||||||
MotorDriver* motorDriver;
|
MotorDriver* motorDriver;
|
||||||
|
RMTPin* rmtPin;
|
||||||
// Transmission controller
|
// Transmission controller
|
||||||
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
|
||||||
byte transmitLength;
|
byte transmitLength;
|
||||||
|
@@ -1 +1 @@
|
|||||||
#define GITHUB_SHA "ee5db61"
|
#define GITHUB_SHA "ESP32-2021120-11:30"
|
||||||
|
@@ -22,6 +22,12 @@
|
|||||||
|
|
||||||
#include "IO_GPIOBase.h"
|
#include "IO_GPIOBase.h"
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32) // min seems to be missing from that package
|
||||||
|
#ifndef min
|
||||||
|
#define min(a,b) ((a)<(b)?(a):(b))
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
class MCP23008 : public GPIOBase<uint8_t> {
|
class MCP23008 : public GPIOBase<uint8_t> {
|
||||||
public:
|
public:
|
||||||
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||||
@@ -97,4 +103,4 @@ private:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -75,7 +75,7 @@ private:
|
|||||||
if (immediate) {
|
if (immediate) {
|
||||||
uint8_t buffer[1];
|
uint8_t buffer[1];
|
||||||
I2CManager.read(_I2CAddress, buffer, 1);
|
I2CManager.read(_I2CAddress, buffer, 1);
|
||||||
_portInputState = ((uint16_t)buffer) & 0xff;
|
_portInputState = buffer[0];
|
||||||
} else {
|
} else {
|
||||||
requestBlock.wait(); // Wait for preceding operation to complete
|
requestBlock.wait(); // Wait for preceding operation to complete
|
||||||
// Issue new request to read GPIO register
|
// Issue new request to read GPIO register
|
||||||
@@ -86,7 +86,7 @@ private:
|
|||||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||||
void _processCompletion(uint8_t status) override {
|
void _processCompletion(uint8_t status) override {
|
||||||
if (status == I2C_STATUS_OK)
|
if (status == I2C_STATUS_OK)
|
||||||
_portInputState = ((uint16_t)inputBuffer[0]) & 0xff;
|
_portInputState = inputBuffer[0];
|
||||||
else
|
else
|
||||||
_portInputState = 0xff;
|
_portInputState = 0xff;
|
||||||
}
|
}
|
||||||
@@ -99,4 +99,4 @@ private:
|
|||||||
uint8_t inputBuffer[1];
|
uint8_t inputBuffer[1];
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
@@ -20,11 +20,10 @@
|
|||||||
#include "MotorDriver.h"
|
#include "MotorDriver.h"
|
||||||
#include "DCCTimer.h"
|
#include "DCCTimer.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
#include <driver/adc.h>
|
||||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
|
||||||
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
#endif
|
||||||
#define isLOW(fastpin) (!isHIGH(fastpin))
|
|
||||||
|
|
||||||
bool MotorDriver::usePWM=false;
|
bool MotorDriver::usePWM=false;
|
||||||
bool MotorDriver::commonFaultPin=false;
|
bool MotorDriver::commonFaultPin=false;
|
||||||
@@ -59,8 +58,15 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
|
|||||||
|
|
||||||
currentPin=current_pin;
|
currentPin=current_pin;
|
||||||
if (currentPin!=UNUSED_PIN) {
|
if (currentPin!=UNUSED_PIN) {
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
pinMode(currentPin, ANALOG);
|
||||||
|
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||||
|
adc1_config_channel_atten(pinToADC1Channel(currentPin),ADC_ATTEN_DB_11);
|
||||||
|
senseOffset = adc1_get_raw(pinToADC1Channel(currentPin));
|
||||||
|
#else
|
||||||
pinMode(currentPin, INPUT);
|
pinMode(currentPin, INPUT);
|
||||||
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
senseOffset=analogRead(currentPin); // value of sensor at zero current
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
faultPin=fault_pin;
|
faultPin=fault_pin;
|
||||||
@@ -110,7 +116,7 @@ void MotorDriver::setBrake(bool on) {
|
|||||||
else setLOW(fastBrakePin);
|
else setLOW(fastBrakePin);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MotorDriver::setSignal( bool high) {
|
void IRAM_ATTR MotorDriver::setSignal( bool high) {
|
||||||
if (usePWM) {
|
if (usePWM) {
|
||||||
DCCTimer::setPWM(signalPin,high);
|
DCCTimer::setPWM(signalPin,high);
|
||||||
}
|
}
|
||||||
@@ -155,6 +161,8 @@ int MotorDriver::getCurrentRaw() {
|
|||||||
current = analogRead(currentPin)-senseOffset;
|
current = analogRead(currentPin)-senseOffset;
|
||||||
overflow_count = 0;
|
overflow_count = 0;
|
||||||
SREG = sreg_backup; /* restore interrupt state */
|
SREG = sreg_backup; /* restore interrupt state */
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP32)
|
||||||
|
current = adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset;
|
||||||
#else
|
#else
|
||||||
current = analogRead(currentPin)-senseOffset;
|
current = analogRead(currentPin)-senseOffset;
|
||||||
#endif
|
#endif
|
||||||
@@ -176,8 +184,8 @@ int MotorDriver::mA2raw( unsigned int mA) {
|
|||||||
|
|
||||||
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
|
||||||
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
|
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
|
||||||
(void) type; // avoid compiler warning if diag not used above.
|
(void) type; // avoid compiler warning if diag not used above.
|
||||||
uint8_t port = digitalPinToPort(pin);
|
PORTTYPE port = digitalPinToPort(pin);
|
||||||
if (input)
|
if (input)
|
||||||
result.inout = portInputRegister(port);
|
result.inout = portInputRegister(port);
|
||||||
else
|
else
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef MotorDriver_h
|
#ifndef MotorDriver_h
|
||||||
#define MotorDriver_h
|
#define MotorDriver_h
|
||||||
|
#include "defines.h"
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
|
|
||||||
// Virtualised Motor shield 1-track hardware Interface
|
// Virtualised Motor shield 1-track hardware Interface
|
||||||
@@ -26,13 +27,15 @@
|
|||||||
#define UNUSED_PIN 127 // inside int8_t
|
#define UNUSED_PIN 127 // inside int8_t
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if defined(__IMXRT1062__)
|
#if defined(__IMXRT1062__) || defined(ESP_FAMILY)
|
||||||
|
typedef uint32_t PORTTYPE;
|
||||||
struct FASTPIN {
|
struct FASTPIN {
|
||||||
volatile uint32_t *inout;
|
volatile uint32_t *inout;
|
||||||
uint32_t maskHIGH;
|
uint32_t maskHIGH;
|
||||||
uint32_t maskLOW;
|
uint32_t maskLOW;
|
||||||
};
|
};
|
||||||
#else
|
#else
|
||||||
|
typedef uint8_t PORTTYPE;
|
||||||
struct FASTPIN {
|
struct FASTPIN {
|
||||||
volatile uint8_t *inout;
|
volatile uint8_t *inout;
|
||||||
uint8_t maskHIGH;
|
uint8_t maskHIGH;
|
||||||
@@ -40,12 +43,30 @@ struct FASTPIN {
|
|||||||
};
|
};
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||||
|
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||||
|
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||||
|
#define isLOW(fastpin) (!isHIGH(fastpin))
|
||||||
|
|
||||||
class MotorDriver {
|
class MotorDriver {
|
||||||
public:
|
public:
|
||||||
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||||
virtual void setPower( bool on);
|
virtual void setPower( bool on);
|
||||||
virtual void setSignal( bool high);
|
void setSignal( bool high);/* {
|
||||||
|
if (usePWM) {
|
||||||
|
DCCTimer::setPWM(signalPin,high);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (high) {
|
||||||
|
setHIGH(fastSignalPin);
|
||||||
|
if (dualSignal) setLOW(fastSignalPin2);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
setLOW(fastSignalPin);
|
||||||
|
if (dualSignal) setHIGH(fastSignalPin2);
|
||||||
|
}
|
||||||
|
};*/
|
||||||
virtual void setBrake( bool on);
|
virtual void setBrake( bool on);
|
||||||
virtual int getCurrentRaw();
|
virtual int getCurrentRaw();
|
||||||
virtual unsigned int raw2mA( int raw);
|
virtual unsigned int raw2mA( int raw);
|
||||||
|
26
RMFTMacros.h
26
RMFTMacros.h
@@ -226,16 +226,16 @@ const int StringMacroTracker1=__COUNTER__;
|
|||||||
|
|
||||||
// Define macros for route code creation
|
// Define macros for route code creation
|
||||||
#define V(val) ((int16_t)(val))&0x00FF,((int16_t)(val)>>8)&0x00FF
|
#define V(val) ((int16_t)(val))&0x00FF,((int16_t)(val)>>8)&0x00FF
|
||||||
#define NOP 0,0
|
#define NOOPERAND 0,0
|
||||||
|
|
||||||
#define ALIAS(name,value)
|
#define ALIAS(name,value)
|
||||||
#define EXRAIL const FLASH byte RMFT2::RouteCode[] = {
|
#define EXRAIL const FLASH byte RMFT2::RouteCode[] = {
|
||||||
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
|
||||||
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
||||||
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
||||||
#define ENDTASK OPCODE_ENDTASK,NOP,
|
#define ENDTASK OPCODE_ENDTASK,NOOPERAND,
|
||||||
#define DONE OPCODE_ENDTASK,NOP,
|
#define DONE OPCODE_ENDTASK,NOOPERAND,
|
||||||
#define ENDEXRAIL OPCODE_ENDTASK,NOP,OPCODE_ENDEXRAIL,NOP };
|
#define ENDEXRAIL OPCODE_ENDTASK,NOOPERAND,OPCODE_ENDEXRAIL,NOOPERAND };
|
||||||
|
|
||||||
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
||||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||||
@@ -245,7 +245,7 @@ const int StringMacroTracker1=__COUNTER__;
|
|||||||
#define DELAY(ms) OPCODE_DELAY,V(ms/100L),
|
#define DELAY(ms) OPCODE_DELAY,V(ms/100L),
|
||||||
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
#define DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
|
||||||
#define DELAYRANDOM(mindelay,maxdelay) OPCODE_DELAY,V(mindelay/100L),OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
#define DELAYRANDOM(mindelay,maxdelay) OPCODE_DELAY,V(mindelay/100L),OPCODE_RANDWAIT,V((maxdelay-mindelay)/100L),
|
||||||
#define ENDIF OPCODE_ENDIF,NOP,
|
#define ENDIF OPCODE_ENDIF,NOOPERAND,
|
||||||
#define ESTOP OPCODE_SPEED,V(1),
|
#define ESTOP OPCODE_SPEED,V(1),
|
||||||
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
||||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||||
@@ -258,23 +258,23 @@ const int StringMacroTracker1=__COUNTER__;
|
|||||||
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,NOP,
|
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,NOOPERAND,
|
||||||
#define JOIN OPCODE_JOIN,NOP,
|
#define JOIN OPCODE_JOIN,NOOPERAND,
|
||||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||||
#define LCD(id,msg) PRINT(msg)
|
#define LCD(id,msg) PRINT(msg)
|
||||||
#define LCN(msg) PRINT(msg)
|
#define LCN(msg) PRINT(msg)
|
||||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||||
#define PAUSE OPCODE_PAUSE,NOP,
|
#define PAUSE OPCODE_PAUSE,NOOPERAND,
|
||||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||||
#define POWEROFF OPCODE_POWEROFF,NOP,
|
#define POWEROFF OPCODE_POWEROFF,NOOPERAND,
|
||||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||||
#define READ_LOCO OPCODE_READ_LOCO1,NOP,OPCODE_READ_LOCO2,NOP,
|
#define READ_LOCO OPCODE_READ_LOCO1,NOOPERAND,OPCODE_READ_LOCO2,NOOPERAND,
|
||||||
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
||||||
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
||||||
#define RESET(pin) OPCODE_RESET,V(pin),
|
#define RESET(pin) OPCODE_RESET,V(pin),
|
||||||
#define RESUME OPCODE_RESUME,NOP,
|
#define RESUME OPCODE_RESUME,NOOPERAND,
|
||||||
#define RETURN OPCODE_RETURN,NOP,
|
#define RETURN OPCODE_RETURN,NOOPERAND,
|
||||||
#define REV(speed) OPCODE_REV,V(speed),
|
#define REV(speed) OPCODE_REV,V(speed),
|
||||||
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
||||||
#define SERIAL(msg) PRINT(msg)
|
#define SERIAL(msg) PRINT(msg)
|
||||||
@@ -293,7 +293,7 @@ const int StringMacroTracker1=__COUNTER__;
|
|||||||
#define PIN_TURNOUT(id,pin) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
#define PIN_TURNOUT(id,pin) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||||
#define THROW(id) OPCODE_THROW,V(id),
|
#define THROW(id) OPCODE_THROW,V(id),
|
||||||
#define TURNOUT(id,addr,subaddr) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
#define TURNOUT(id,addr,subaddr) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
||||||
#define UNJOIN OPCODE_UNJOIN,NOP,
|
#define UNJOIN OPCODE_UNJOIN,NOOPERAND,
|
||||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||||
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
||||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||||
|
@@ -18,6 +18,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "RingStream.h"
|
#include "RingStream.h"
|
||||||
|
#include "defines.h"
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
|
|
||||||
RingStream::RingStream( const uint16_t len)
|
RingStream::RingStream( const uint16_t len)
|
||||||
@@ -30,27 +31,36 @@ RingStream::RingStream( const uint16_t len)
|
|||||||
_overflow=false;
|
_overflow=false;
|
||||||
_mark=0;
|
_mark=0;
|
||||||
_count=0;
|
_count=0;
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
_bufMux = portMUX_INITIALIZER_UNLOCKED;
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t RingStream::write(uint8_t b) {
|
size_t RingStream::write(uint8_t b) {
|
||||||
if (_overflow) return 0;
|
if (_overflow) return 0;
|
||||||
|
portENTER_CRITICAL(&_bufMux);
|
||||||
_buffer[_pos_write] = b;
|
_buffer[_pos_write] = b;
|
||||||
++_pos_write;
|
++_pos_write;
|
||||||
if (_pos_write==_len) _pos_write=0;
|
if (_pos_write==_len) _pos_write=0;
|
||||||
if (_pos_write==_pos_read) {
|
if (_pos_write==_pos_read) {
|
||||||
_overflow=true;
|
_overflow=true;
|
||||||
|
portEXIT_CRITICAL(&_bufMux);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
_count++;
|
_count++;
|
||||||
|
portEXIT_CRITICAL(&_bufMux);
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
int RingStream::read() {
|
int RingStream::read(byte advance) {
|
||||||
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
||||||
|
if (_pos_read == _mark) return -1;
|
||||||
|
portENTER_CRITICAL(&_bufMux);
|
||||||
byte b=_buffer[_pos_read];
|
byte b=_buffer[_pos_read];
|
||||||
_pos_read++;
|
_pos_read += advance;
|
||||||
if (_pos_read==_len) _pos_read=0;
|
if (_pos_read==_len) _pos_read=0;
|
||||||
_overflow=false;
|
_overflow=false;
|
||||||
|
portEXIT_CRITICAL(&_bufMux);
|
||||||
return b;
|
return b;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -68,11 +78,14 @@ int RingStream::freeSpace() {
|
|||||||
|
|
||||||
// mark start of message with client id (0...9)
|
// mark start of message with client id (0...9)
|
||||||
void RingStream::mark(uint8_t b) {
|
void RingStream::mark(uint8_t b) {
|
||||||
|
//DIAG(F("Mark1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
||||||
|
portENTER_CRITICAL(&_bufMux);
|
||||||
_mark=_pos_write;
|
_mark=_pos_write;
|
||||||
write(b); // client id
|
write(b); // client id
|
||||||
write((uint8_t)0); // count MSB placemarker
|
write((uint8_t)0); // count MSB placemarker
|
||||||
write((uint8_t)0); // count LSB placemarker
|
write((uint8_t)0); // count LSB placemarker
|
||||||
_count=0;
|
_count=0;
|
||||||
|
portEXIT_CRITICAL(&_bufMux);
|
||||||
}
|
}
|
||||||
|
|
||||||
// peekTargetMark is used by the parser stash routines to know which client
|
// peekTargetMark is used by the parser stash routines to know which client
|
||||||
@@ -81,17 +94,25 @@ uint8_t RingStream::peekTargetMark() {
|
|||||||
return _buffer[_mark];
|
return _buffer[_mark];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RingStream::info() {
|
||||||
|
DIAG(F("Info len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
||||||
|
}
|
||||||
|
|
||||||
bool RingStream::commit() {
|
bool RingStream::commit() {
|
||||||
|
//DIAG(F("Commit1 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
||||||
|
portENTER_CRITICAL(&_bufMux);
|
||||||
if (_overflow) {
|
if (_overflow) {
|
||||||
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
|
||||||
// just throw it away
|
// just throw it away
|
||||||
_pos_write=_mark;
|
_pos_write=_mark;
|
||||||
_overflow=false;
|
_overflow=false;
|
||||||
return false; // commit failed
|
portEXIT_CRITICAL(&_bufMux);
|
||||||
|
return false; // commit failed
|
||||||
}
|
}
|
||||||
if (_count==0) {
|
if (_count==0) {
|
||||||
// ignore empty response
|
// ignore empty response
|
||||||
_pos_write=_mark;
|
_pos_write=_mark;
|
||||||
|
portEXIT_CRITICAL(&_bufMux);
|
||||||
return true; // true=commit ok
|
return true; // true=commit ok
|
||||||
}
|
}
|
||||||
// Go back to the _mark and inject the count 1 byte later
|
// Go back to the _mark and inject the count 1 byte later
|
||||||
@@ -101,5 +122,8 @@ bool RingStream::commit() {
|
|||||||
_mark++;
|
_mark++;
|
||||||
if (_mark==_len) _mark=0;
|
if (_mark==_len) _mark=0;
|
||||||
_buffer[_mark]=lowByte(_count);
|
_buffer[_mark]=lowByte(_count);
|
||||||
|
_mark=_len+1;
|
||||||
|
//DIAG(F("Commit2 len=%d count=%d pr=%d pw=%d m=%d"),_len, _count,_pos_read,_pos_write,_mark);
|
||||||
|
portEXIT_CRITICAL(&_bufMux);
|
||||||
return true; // commit worked
|
return true; // commit worked
|
||||||
}
|
}
|
||||||
|
10
RingStream.h
10
RingStream.h
@@ -28,14 +28,17 @@ class RingStream : public Print {
|
|||||||
|
|
||||||
virtual size_t write(uint8_t b);
|
virtual size_t write(uint8_t b);
|
||||||
using Print::write;
|
using Print::write;
|
||||||
int read();
|
inline int read() { return read(1); };
|
||||||
|
inline int peek() { return read(0); };
|
||||||
int count();
|
int count();
|
||||||
int freeSpace();
|
int freeSpace();
|
||||||
void mark(uint8_t b);
|
void mark(uint8_t b);
|
||||||
bool commit();
|
bool commit();
|
||||||
uint8_t peekTargetMark();
|
uint8_t peekTargetMark();
|
||||||
|
void info();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
int read(byte advance);
|
||||||
int _len;
|
int _len;
|
||||||
int _pos_write;
|
int _pos_write;
|
||||||
int _pos_read;
|
int _pos_read;
|
||||||
@@ -43,6 +46,9 @@ class RingStream : public Print {
|
|||||||
int _mark;
|
int _mark;
|
||||||
int _count;
|
int _count;
|
||||||
byte * _buffer;
|
byte * _buffer;
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
portMUX_TYPE _bufMux;
|
||||||
|
#endif
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
246
WifiESP32.cpp
Normal file
246
WifiESP32.cpp
Normal file
@@ -0,0 +1,246 @@
|
|||||||
|
/*
|
||||||
|
© 2021, Harald Barth.
|
||||||
|
|
||||||
|
This file is part of CommandStation-EX
|
||||||
|
|
||||||
|
This is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
It 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
#include "defines.h"
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
#include <WiFi.h>
|
||||||
|
#include "WifiESP32.h"
|
||||||
|
#include "DIAG.h"
|
||||||
|
#include "RingStream.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
/*
|
||||||
|
#include "soc/rtc_wdt.h"
|
||||||
|
#include "esp_task_wdt.h"
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "soc/timer_group_struct.h"
|
||||||
|
#include "soc/timer_group_reg.h"
|
||||||
|
void feedTheDog0(){
|
||||||
|
// feed dog 0
|
||||||
|
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||||
|
TIMERG0.wdt_feed=1; // feed dog
|
||||||
|
TIMERG0.wdt_wprotect=0; // write protect
|
||||||
|
// feed dog 1
|
||||||
|
//TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||||
|
//TIMERG1.wdt_feed=1; // feed dog
|
||||||
|
//TIMERG1.wdt_wprotect=0; // write protect
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
void enableCoreWDT(byte core){
|
||||||
|
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
|
||||||
|
if(idle == NULL){
|
||||||
|
DIAG(F("Get idle rask on core %d failed"),core);
|
||||||
|
} else {
|
||||||
|
if(esp_task_wdt_add(idle) != ESP_OK){
|
||||||
|
DIAG(F("Failed to add Core %d IDLE task to WDT"),core);
|
||||||
|
} else {
|
||||||
|
DIAG(F("Added Core %d IDLE task to WDT"),core);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void disableCoreWDT(byte core){
|
||||||
|
TaskHandle_t idle = xTaskGetIdleTaskHandleForCPU(core);
|
||||||
|
if(idle == NULL || esp_task_wdt_delete(idle) != ESP_OK){
|
||||||
|
DIAG(F("Failed to remove Core %d IDLE task from WDT"),core);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
static std::vector<WiFiClient> clients; // a list to hold all clients
|
||||||
|
static WiFiServer *server = NULL;
|
||||||
|
static RingStream *outboundRing = new RingStream(2048);
|
||||||
|
static bool APmode = false;
|
||||||
|
|
||||||
|
void wifiLoop(void *){
|
||||||
|
for(;;){
|
||||||
|
WifiESP::loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool WifiESP::setup(const char *SSid,
|
||||||
|
const char *password,
|
||||||
|
const char *hostname,
|
||||||
|
int port,
|
||||||
|
const byte channel) {
|
||||||
|
bool havePassword = true;
|
||||||
|
bool haveSSID = true;
|
||||||
|
bool wifiUp = false;
|
||||||
|
uint8_t tries = 40;
|
||||||
|
|
||||||
|
// tests
|
||||||
|
// enableCoreWDT(1);
|
||||||
|
// disableCoreWDT(0);
|
||||||
|
|
||||||
|
const char *yourNetwork = "Your network ";
|
||||||
|
if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0)
|
||||||
|
haveSSID = false;
|
||||||
|
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
||||||
|
havePassword = false;
|
||||||
|
|
||||||
|
if (haveSSID && havePassword) {
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.setAutoReconnect(true);
|
||||||
|
WiFi.begin(SSid, password);
|
||||||
|
while (WiFi.status() != WL_CONNECTED && tries) {
|
||||||
|
Serial.print('.');
|
||||||
|
tries--;
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
||||||
|
wifiUp = true;
|
||||||
|
} else {
|
||||||
|
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!haveSSID) {
|
||||||
|
// prepare all strings
|
||||||
|
String strSSID("DCC_");
|
||||||
|
String strPass("PASS_");
|
||||||
|
String strMac = WiFi.macAddress();
|
||||||
|
strMac.remove(0,9);
|
||||||
|
strMac.replace(":","");
|
||||||
|
strMac.replace(":","");
|
||||||
|
strSSID.concat(strMac);
|
||||||
|
strPass.concat(strMac);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
if (WiFi.softAP(strSSID.c_str(),
|
||||||
|
havePassword ? password : strPass.c_str(),
|
||||||
|
channel, false, 8)) {
|
||||||
|
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
||||||
|
DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
||||||
|
wifiUp = true;
|
||||||
|
APmode = true;
|
||||||
|
} else {
|
||||||
|
DIAG(F("Could not set up AP with Wifi SSID %s"),strSSID.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!wifiUp) {
|
||||||
|
DIAG(F("Wifi setup all fail (STA and AP mode)"));
|
||||||
|
// no idea to go on
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
server = new WiFiServer(port); // start listening on tcp port
|
||||||
|
server->begin();
|
||||||
|
// server started here
|
||||||
|
|
||||||
|
//start loop task
|
||||||
|
if (pdPASS != xTaskCreatePinnedToCore(
|
||||||
|
wifiLoop, /* Task function. */
|
||||||
|
"wifiLoop",/* name of task. */
|
||||||
|
10000, /* Stack size of task */
|
||||||
|
NULL, /* parameter of the task */
|
||||||
|
1, /* priority of the task */
|
||||||
|
NULL, /* Task handle to keep track of created task */
|
||||||
|
0)) { /* pin task to core 0 */
|
||||||
|
DIAG(F("Could not create wifiLoop task"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// report server started after wifiLoop creation
|
||||||
|
// when everything looks good
|
||||||
|
DIAG(F("Server up port %d"),port);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiESP::loop() {
|
||||||
|
int clientId; //tmp loop var
|
||||||
|
|
||||||
|
// really no good way to check for LISTEN especially in AP mode?
|
||||||
|
if (APmode || WiFi.status() == WL_CONNECTED) {
|
||||||
|
if (server->hasClient()) {
|
||||||
|
// loop over all clients and remove inactive
|
||||||
|
for (clientId=0; clientId<clients.size(); clientId++){
|
||||||
|
// check if client is there and alive
|
||||||
|
if(!clients[clientId].connected()) {
|
||||||
|
clients[clientId].stop();
|
||||||
|
clients.erase(clients.begin()+clientId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WiFiClient client;
|
||||||
|
while (client = server->available()) {
|
||||||
|
clients.push_back(client);
|
||||||
|
DIAG(F("New client %s"), client.remoteIP().toString().c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// loop over all connected clients
|
||||||
|
for (clientId=0; clientId<clients.size(); clientId++){
|
||||||
|
if(clients[clientId].connected()) {
|
||||||
|
int len;
|
||||||
|
if ((len = clients[clientId].available()) > 0) {
|
||||||
|
// read data from client
|
||||||
|
byte cmd[len+1];
|
||||||
|
for(int i=0; i<len; i++) {
|
||||||
|
cmd[i]=clients[clientId].read();
|
||||||
|
}
|
||||||
|
cmd[len]=0;
|
||||||
|
outboundRing->mark(clientId);
|
||||||
|
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||||
|
outboundRing->commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // all clients
|
||||||
|
|
||||||
|
// something to write out?
|
||||||
|
clientId=outboundRing->peek();
|
||||||
|
if (clientId >= 0) {
|
||||||
|
if ((unsigned int)clientId > clients.size()) {
|
||||||
|
// something is wrong with the ringbuffer position
|
||||||
|
outboundRing->info();
|
||||||
|
} else {
|
||||||
|
// we have data to send in outboundRing
|
||||||
|
if(clients[clientId].connected()) {
|
||||||
|
outboundRing->read(); // read over peek()
|
||||||
|
int count=outboundRing->count();
|
||||||
|
{
|
||||||
|
char buffer[count+1];
|
||||||
|
for(int i=0;i<count;i++) {
|
||||||
|
int c = outboundRing->read();
|
||||||
|
if (c >= 0)
|
||||||
|
buffer[i] = (char)c;
|
||||||
|
else {
|
||||||
|
DIAG(F("Ringread fail at %d"),i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer[count]=0;
|
||||||
|
clients[clientId].write(buffer,count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} //connected
|
||||||
|
|
||||||
|
// when loop() is running on core0 we must
|
||||||
|
// feed the core0 wdt ourselves as yield()
|
||||||
|
// is not necessarily yielding to a low
|
||||||
|
// prio task. On core1 this is not a problem
|
||||||
|
// as there the wdt is disabled by the
|
||||||
|
// arduio IDE startup routines.
|
||||||
|
if (xPortGetCoreID() == 0)
|
||||||
|
feedTheDog0();
|
||||||
|
yield();
|
||||||
|
}
|
||||||
|
#endif //ESP32
|
39
WifiESP32.h
Normal file
39
WifiESP32.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Harald Barth.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
#ifndef WifiESP32_h
|
||||||
|
#define WifiESP32_h
|
||||||
|
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
|
class WifiESP
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool setup(const char *wifiESSID,
|
||||||
|
const char *wifiPassword,
|
||||||
|
const char *hostname,
|
||||||
|
const int port,
|
||||||
|
const byte channel);
|
||||||
|
static void loop();
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
#endif //WifiESP8266_h
|
||||||
|
#endif //ESP8266
|
270
WifiESP8266.cpp
Normal file
270
WifiESP8266.cpp
Normal file
@@ -0,0 +1,270 @@
|
|||||||
|
/*
|
||||||
|
© 2021, Harald Barth.
|
||||||
|
|
||||||
|
This file is part of CommandStation-EX
|
||||||
|
|
||||||
|
This is free software: you can redistribute it and/or modify
|
||||||
|
it under the terms of the GNU General Public License as published by
|
||||||
|
the Free Software Foundation, either version 3 of the License, or
|
||||||
|
(at your option) any later version.
|
||||||
|
|
||||||
|
It 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 General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License
|
||||||
|
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "defines.h"
|
||||||
|
#if defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
#include <ESP8266WiFi.h>
|
||||||
|
#include <ESPAsyncTCP.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "WifiESP8266.h"
|
||||||
|
#include "DIAG.h"
|
||||||
|
#include "RingStream.h"
|
||||||
|
#include "CommandDistributor.h"
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static std::vector<AsyncClient*> clients; // a list to hold all clients
|
||||||
|
static AsyncServer *server;
|
||||||
|
|
||||||
|
static RingStream *outboundRing = new RingStream(2048);
|
||||||
|
|
||||||
|
static void handleError(void* arg, AsyncClient* client, int8_t error) {
|
||||||
|
(void)arg;
|
||||||
|
DIAG(F("connection error %s from client %s"), client->errorToString(error), client->remoteIP().toString().c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleData(void* arg, AsyncClient* client, void *data, size_t len) {
|
||||||
|
(void)arg;
|
||||||
|
//DIAG(F("data received from client %s"), client->remoteIP().toString().c_str());
|
||||||
|
uint8_t clientId;
|
||||||
|
for (clientId=0; clientId<clients.size(); clientId++){
|
||||||
|
if (clients[clientId] == client) break;
|
||||||
|
}
|
||||||
|
if (clientId < clients.size()) {
|
||||||
|
byte cmd[len+1];
|
||||||
|
memcpy(cmd,data,len);
|
||||||
|
cmd[len]=0;
|
||||||
|
outboundRing->mark(clientId);
|
||||||
|
CommandDistributor::parse(clientId,cmd,outboundRing);
|
||||||
|
outboundRing->commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//static AsyncClient *debugclient = NULL;
|
||||||
|
|
||||||
|
bool sendData(AsyncClient *client, char* data, size_t count) {
|
||||||
|
size_t willsend = 0;
|
||||||
|
|
||||||
|
// reply to client
|
||||||
|
if (client->canSend()) {
|
||||||
|
while (count > 0) {
|
||||||
|
if (client->connected())
|
||||||
|
willsend = client->add(data, count); // add checks for space()
|
||||||
|
else
|
||||||
|
willsend = 0;
|
||||||
|
if (willsend < count) {
|
||||||
|
DIAG(F("Willsend %d of count %d"), willsend, count);
|
||||||
|
}
|
||||||
|
if (client->connected() && client->send()) {
|
||||||
|
count = count - willsend;
|
||||||
|
data = data + willsend;
|
||||||
|
} else {
|
||||||
|
DIAG(F("Could not send promised %d"), count);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Did send all bytes we wanted
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
DIAG(F("Aborting: Busy or space=0"));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void deleteClient(AsyncClient* client) {
|
||||||
|
uint8_t clientId;
|
||||||
|
for (clientId=0; clientId<clients.size(); clientId++){
|
||||||
|
if (clients[clientId] == client) break;
|
||||||
|
}
|
||||||
|
if (clientId < clients.size()) {
|
||||||
|
clients[clientId] = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static void handleDisconnect(void* arg, AsyncClient* client) {
|
||||||
|
(void)arg;
|
||||||
|
DIAG(F("Client disconnected"));
|
||||||
|
deleteClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void handleTimeOut(void* arg, AsyncClient* client, uint32_t time) {
|
||||||
|
(void)arg;
|
||||||
|
(void)time;
|
||||||
|
DIAG(F("client ACK timeout ip: %s"), client->remoteIP().toString().c_str());
|
||||||
|
deleteClient(client);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static void handleNewClient(void* arg, AsyncClient* client) {
|
||||||
|
(void)arg;
|
||||||
|
DIAG(F("New client %s"), client->remoteIP().toString().c_str());
|
||||||
|
|
||||||
|
// add to list
|
||||||
|
clients.push_back(client);
|
||||||
|
|
||||||
|
// register events
|
||||||
|
client->onData(&handleData, NULL);
|
||||||
|
client->onError(&handleError, NULL);
|
||||||
|
client->onDisconnect(&handleDisconnect, NULL);
|
||||||
|
client->onTimeout(&handleTimeOut, NULL);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Things one _might_ want to do:
|
||||||
|
Disable soft watchdog: ESP.wdtDisable()
|
||||||
|
Enable soft watchdog: ESP.wdtEnable(X) ignores the value of X and enables it for fixed
|
||||||
|
time at least in version 3.0.2 of the esp8266 package.
|
||||||
|
|
||||||
|
Internet says:
|
||||||
|
|
||||||
|
I manage to complety disable the hardware watchdog on ESP8266 in order to run the benchmark CoreMark.
|
||||||
|
|
||||||
|
void hw_wdt_disable(){
|
||||||
|
*((volatile uint32_t*) 0x60000900) &= ~(1); // Hardware WDT OFF
|
||||||
|
}
|
||||||
|
|
||||||
|
void hw_wdt_enable(){
|
||||||
|
*((volatile uint32_t*) 0x60000900) |= 1; // Hardware WDT ON
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
bool WifiESP::setup(const char *SSid,
|
||||||
|
const char *password,
|
||||||
|
const char *hostname,
|
||||||
|
int port,
|
||||||
|
const byte channel) {
|
||||||
|
bool havePassword = true;
|
||||||
|
bool haveSSID = true;
|
||||||
|
bool wifiUp = false;
|
||||||
|
|
||||||
|
// We are server and should not sleep
|
||||||
|
wifi_set_sleep_type(NONE_SLEEP_T);
|
||||||
|
// connects to access point
|
||||||
|
|
||||||
|
const char *yourNetwork = "Your network ";
|
||||||
|
if (strncmp(yourNetwork, SSid, 13) == 0 || strncmp("", SSid, 13) == 0)
|
||||||
|
haveSSID = false;
|
||||||
|
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
||||||
|
havePassword = false;
|
||||||
|
|
||||||
|
if (haveSSID && havePassword) {
|
||||||
|
WiFi.mode(WIFI_STA);
|
||||||
|
WiFi.setAutoReconnect(true);
|
||||||
|
WiFi.begin(SSid, password);
|
||||||
|
while (WiFi.status() != WL_CONNECTED) {
|
||||||
|
Serial.print('.');
|
||||||
|
delay(500);
|
||||||
|
}
|
||||||
|
if (WiFi.status() == WL_CONNECTED) {
|
||||||
|
DIAG(F("Wifi STA IP %s"),WiFi.localIP().toString().c_str());
|
||||||
|
wifiUp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!haveSSID) {
|
||||||
|
// prepare all strings
|
||||||
|
String strSSID("DCC_");
|
||||||
|
String strPass("PASS_");
|
||||||
|
String strMac = WiFi.macAddress();
|
||||||
|
strMac.remove(0,9);
|
||||||
|
strMac.replace(":","");
|
||||||
|
strMac.replace(":","");
|
||||||
|
strSSID.concat(strMac);
|
||||||
|
strPass.concat(strMac);
|
||||||
|
|
||||||
|
WiFi.mode(WIFI_AP);
|
||||||
|
if (WiFi.softAP(strSSID.c_str(),
|
||||||
|
havePassword ? password : strPass.c_str(),
|
||||||
|
channel, false, 8)) {
|
||||||
|
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
|
||||||
|
DIAG(F("Wifi AP IP %s"),WiFi.softAPIP().toString().c_str());
|
||||||
|
wifiUp = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (!wifiUp) {
|
||||||
|
DIAG(F("Wifi all fail"));
|
||||||
|
// no idea to go on
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
server = new AsyncServer(port); // start listening on tcp port
|
||||||
|
|
||||||
|
server->onClient(&handleNewClient, server);
|
||||||
|
server->begin();
|
||||||
|
DIAG(F("Server up port %d"),port);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void WifiESP::loop() {
|
||||||
|
AsyncClient *client = NULL;
|
||||||
|
// Do something with outboundRing
|
||||||
|
// call sendData
|
||||||
|
int clientId=outboundRing->peek();
|
||||||
|
if (clientId >= 0) {
|
||||||
|
if ((unsigned int)clientId > clients.size()) {
|
||||||
|
// something is wrong with the ringbuffer position
|
||||||
|
outboundRing->info();
|
||||||
|
client = NULL;
|
||||||
|
} else {
|
||||||
|
client = clients[clientId];
|
||||||
|
}
|
||||||
|
// if (client != debugclient) {
|
||||||
|
// DIAG(F("new client pointer = %x from id %d"), client, clientId);
|
||||||
|
// debugclient = client;
|
||||||
|
// }
|
||||||
|
} else {
|
||||||
|
client = NULL;
|
||||||
|
}
|
||||||
|
if (clientId>=0 && client && client->connected() && client->canSend()) {
|
||||||
|
outboundRing->read();
|
||||||
|
int count=outboundRing->count();
|
||||||
|
//DIAG(F("Wifi reply client=%d, count=%d"), clientId,count);
|
||||||
|
{
|
||||||
|
char buffer[count+1];
|
||||||
|
for(int i=0;i<count;i++) {
|
||||||
|
int c = outboundRing->read();
|
||||||
|
if (c >= 0)
|
||||||
|
buffer[i] = (char)c;
|
||||||
|
else {
|
||||||
|
DIAG(F("Ringread fail at %d"),i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
buffer[count]=0;
|
||||||
|
//DIAG(F("SEND:%s COUNT:%d"),buffer,count);
|
||||||
|
uint8_t tries = 3;
|
||||||
|
while (! sendData(client, buffer, count)) {
|
||||||
|
DIAG(F("senData fail"));
|
||||||
|
yield();
|
||||||
|
if (tries == 0) break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#ifdef ESP_DEBUG
|
||||||
|
static unsigned long last = 0;
|
||||||
|
if (millis() - last > 60000) {
|
||||||
|
last = millis();
|
||||||
|
DIAG(F("+"));
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
ESP.wdtFeed();
|
||||||
|
}
|
||||||
|
#endif //ESP_FAMILY
|
39
WifiESP8266.h
Normal file
39
WifiESP8266.h
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
/*
|
||||||
|
* © 2021, Harald Barth.
|
||||||
|
*
|
||||||
|
* This file is part of CommandStation-EX
|
||||||
|
*
|
||||||
|
* This is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* It 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 General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#if defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
#ifndef WifiESP8266_h
|
||||||
|
#define WifiESP8266_h
|
||||||
|
|
||||||
|
#include "FSH.h"
|
||||||
|
|
||||||
|
class WifiESP
|
||||||
|
{
|
||||||
|
|
||||||
|
public:
|
||||||
|
static bool setup(const char *wifiESSID,
|
||||||
|
const char *wifiPassword,
|
||||||
|
const char *hostname,
|
||||||
|
const int port,
|
||||||
|
const byte channel);
|
||||||
|
static void loop();
|
||||||
|
private:
|
||||||
|
};
|
||||||
|
#endif //WifiESP8266_h
|
||||||
|
#endif //ESP8266
|
@@ -17,9 +17,10 @@
|
|||||||
You should have received a copy of the GNU General Public License
|
You should have received a copy of the GNU General Public License
|
||||||
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
#include "WifiInterface.h" /* config.h included there */
|
||||||
|
#ifndef ESP_FAMILY
|
||||||
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
|
#ifndef ARDUINO_AVR_UNO_WIFI_REV2
|
||||||
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
|
// This code is NOT compiled on a unoWifiRev2 processor which uses a different architecture
|
||||||
#include "WifiInterface.h" /* config.h included there */
|
|
||||||
#include <avr/pgmspace.h>
|
#include <avr/pgmspace.h>
|
||||||
#include "DIAG.h"
|
#include "DIAG.h"
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
@@ -370,4 +371,5 @@ void WifiInterface::loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif //ARDUINO_AVR_UNO_WIFI_REV2
|
||||||
|
#endif //ESP_FAMILY
|
||||||
|
@@ -19,6 +19,8 @@
|
|||||||
*/
|
*/
|
||||||
#ifndef WifiInterface_h
|
#ifndef WifiInterface_h
|
||||||
#define WifiInterface_h
|
#define WifiInterface_h
|
||||||
|
#include "defines.h"
|
||||||
|
#ifndef ESP_FAMILY
|
||||||
#include "FSH.h"
|
#include "FSH.h"
|
||||||
#include "DCCEXParser.h"
|
#include "DCCEXParser.h"
|
||||||
#include <Arduino.h>
|
#include <Arduino.h>
|
||||||
@@ -50,4 +52,5 @@ private:
|
|||||||
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
|
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
|
||||||
static bool connected;
|
static bool connected;
|
||||||
};
|
};
|
||||||
#endif
|
#endif //ESP_FAMILY
|
||||||
|
#endif
|
||||||
|
@@ -41,7 +41,29 @@ The configuration file for DCC-EX Command Station
|
|||||||
// |
|
// |
|
||||||
// +-----------------------v
|
// +-----------------------v
|
||||||
//
|
//
|
||||||
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
//#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
|
||||||
|
|
||||||
|
// https://randomnerdtutorials.com/esp8266-pinout-reference-gpios/
|
||||||
|
// 4 high at boot
|
||||||
|
#define ESP8266_MOTOR_SHIELD F("ESP8266"), \
|
||||||
|
new MotorDriver(D3, D5, UNUSED_PIN, UNUSED_PIN, UNUSED_PIN, 2.99, 2000, UNUSED_PIN),\
|
||||||
|
new MotorDriver(D2, D6, UNUSED_PIN, UNUSED_PIN, A0 , 2.99, 2000, UNUSED_PIN)
|
||||||
|
|
||||||
|
// ESP32 ADC1 only supported GPIO pins 32 to 39, for example
|
||||||
|
// ADC1 CH4 = GPIO32, ADC1 CH5 = GPIO33, ADC1 CH0 = GPIO36
|
||||||
|
//
|
||||||
|
// Adjust pin usage according to info in
|
||||||
|
// https://randomnerdtutorials.com/esp32-adc-analog-read-arduino-ide/
|
||||||
|
// https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
|
||||||
|
//
|
||||||
|
// Adjust conversion factor according to your voltage divider.
|
||||||
|
//
|
||||||
|
#define ESP32_MOTOR_SHIELD F("ESP32"), \
|
||||||
|
new MotorDriver(16, 17, UNUSED_PIN, UNUSED_PIN, 32, 2.00, 2000, UNUSED_PIN),\
|
||||||
|
new MotorDriver(18, 19, UNUSED_PIN, UNUSED_PIN, 33, 2.00, 2000, UNUSED_PIN)
|
||||||
|
|
||||||
|
#define MOTOR_SHIELD_TYPE ESP32_MOTOR_SHIELD
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// The IP port to talk to a WIFI or Ethernet shield.
|
// The IP port to talk to a WIFI or Ethernet shield.
|
||||||
@@ -53,6 +75,8 @@ The configuration file for DCC-EX Command Station
|
|||||||
// NOTE: Only supported on Arduino Mega
|
// NOTE: Only supported on Arduino Mega
|
||||||
// Set to false if you not even want it on the Arduino Mega
|
// Set to false if you not even want it on the Arduino Mega
|
||||||
//
|
//
|
||||||
|
// Currently ESP32 single core only works with WIFI ON because of Watchdog code
|
||||||
|
// and if you have an ESP32 you probably want WIFI anyway.
|
||||||
#define ENABLE_WIFI true
|
#define ENABLE_WIFI true
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////////////////////////////////
|
||||||
|
26
defines.h
26
defines.h
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
© 2020, Harald Barth.
|
© 2020,2021 Harald Barth.
|
||||||
|
|
||||||
This file is part of CommandStation-EX
|
This file is part of CommandStation-EX
|
||||||
|
|
||||||
@@ -31,14 +31,34 @@
|
|||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
#if defined(ARDUINO_ARCH_ESP8266)
|
||||||
|
#define ESP_FAMILY
|
||||||
|
//#define ESP_DEBUG
|
||||||
|
#define SLOW_ANALOG_READ
|
||||||
|
#endif
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
//
|
||||||
|
#if defined(ARDUINO_ARCH_ESP32)
|
||||||
|
#define ESP_FAMILY
|
||||||
|
#define SLOW_ANALOG_READ
|
||||||
|
#else
|
||||||
|
#define portENTER_CRITICAL(A) do {} while (0)
|
||||||
|
#define portEXIT_CRITICAL(A) do {} while (0)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
//
|
//
|
||||||
// WIFI_ON: All prereqs for running with WIFI are met
|
// WIFI_ON: All prereqs for running with WIFI are met
|
||||||
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
|
// Note: WIFI_CHANNEL may not exist in early config.h files so is added here if needed.
|
||||||
|
|
||||||
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO))
|
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560) || defined(ARDUINO_SAMD_ZERO) || defined(TEENSYDUINO) || defined(ESP_FAMILY))
|
||||||
#define BIG_RAM
|
#define BIG_RAM
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if ENABLE_WIFI && defined(BIG_RAM)
|
#if ENABLE_WIFI && defined(BIG_RAM)
|
||||||
#define WIFI_ON true
|
#define WIFI_ON true
|
||||||
#ifndef WIFI_CHANNEL
|
#ifndef WIFI_CHANNEL
|
||||||
@@ -69,4 +89,4 @@
|
|||||||
#define RMFT_ACTIVE
|
#define RMFT_ACTIVE
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
67
esp32wdt.txt
Normal file
67
esp32wdt.txt
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
The ESP-IDF has interrupt watchdogs and task watchdogs. Normally on
|
||||||
|
each core there is a very low prio idle task (IDLE0, ILDE1) that feeds
|
||||||
|
the watchdog (an internal timer) and if that timer expires a
|
||||||
|
reboot/reset happens. This thought to enure that even the lowest prio
|
||||||
|
tasks get run ever.
|
||||||
|
|
||||||
|
Now enter the Arduino IDE generated task loop(). When there are two cores,
|
||||||
|
loop() is run on core1. If loop runs continiously, IDLE1 is never run and
|
||||||
|
triggers the watchdog. It is said that this can be previented by one
|
||||||
|
of the following:
|
||||||
|
|
||||||
|
1. Call delay(X) with big enough X
|
||||||
|
2. Call yield()
|
||||||
|
|
||||||
|
While the delay() method works, big enough X to run idle seem to be in
|
||||||
|
the ms range and there are definitely applications that can not accept
|
||||||
|
a several ms long pause in loop().
|
||||||
|
|
||||||
|
The yield() method does not work because it only seems not to yield to
|
||||||
|
a low prio task like IDLE1 in all circumstances.
|
||||||
|
|
||||||
|
Then the makers of the Arduino IDE did get the brilliant idea to
|
||||||
|
disable that IDLE1 calls the watchdog. Then loop() can spin on core1
|
||||||
|
and other tasks (like wifi or interrupts or whatever) can run on core0
|
||||||
|
and are watched by the IDLE0 watchdog. All swell and well. Almost.
|
||||||
|
|
||||||
|
Enter: SINGLE CORE ESP32
|
||||||
|
|
||||||
|
As the IDLE0 watchdog is not disabled it will fire when loop() runs on
|
||||||
|
core0. The next idea is to feed the watchdog from loop() just
|
||||||
|
alongside the yield. There is a function called esp_task_wdt_feed(),
|
||||||
|
so can that be used to feed the watchdog? Yes and no. While it
|
||||||
|
will feed the watchdog, there is as well as check in the ESP-IDF
|
||||||
|
that the watchdog is fed from ALL tasks that should feed it. So
|
||||||
|
if the setup is that IDLE0 should feed the watchdog, we can not
|
||||||
|
get away by calling esp_task_wdt_feed() from loop(). BUMMER!
|
||||||
|
|
||||||
|
But there seems to be a way around this. The watchdog is implemented
|
||||||
|
by low level timers/counters and these are accessible. So we can feed
|
||||||
|
the dog behind the back of the ESP-IDF:
|
||||||
|
|
||||||
|
#include "soc/timer_group_struct.h"
|
||||||
|
#include "soc/timer_group_reg.h"
|
||||||
|
void feedTheDog(){
|
||||||
|
// feed dog 0
|
||||||
|
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||||
|
TIMERG0.wdt_feed=1; // feed dog
|
||||||
|
TIMERG0.wdt_wprotect=0; // write protect
|
||||||
|
// feed dog 1
|
||||||
|
TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||||
|
TIMERG1.wdt_feed=1; // feed dog
|
||||||
|
TIMERG1.wdt_wprotect=0; // write protect
|
||||||
|
}
|
||||||
|
|
||||||
|
As I do not have a single core ESP32 I tested this by enabling the
|
||||||
|
IDLE1 watchdog (which normally is disabled) and checking that I
|
||||||
|
do get watchdog resets. Then I call feedTheDog() from loop()
|
||||||
|
and the resets disappear. So I guess the feeding operation is
|
||||||
|
successful. For a single core ESP32 of course only dog0 has
|
||||||
|
to be fed.
|
||||||
|
|
||||||
|
Feed dog directly behind back of the ESP-IDF routines:
|
||||||
|
https://forum.arduino.cc/t/esp32-a-better-way-than-vtaskdelay-to-get-around-watchdog-crash/596889/13
|
||||||
|
Disable/Endable WDT code:
|
||||||
|
https://github.com/espressif/arduino-esp32/commit/b8f8502f
|
||||||
|
Get/set taskid on cores:
|
||||||
|
https://techtutorialsx.com/2017/05/09/esp32-get-task-execution-core/
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
* © 2020, Harald Barth
|
* © 2020,2021 Harald Barth
|
||||||
* © 2021, Neil McKechnie
|
* © 2021, Neil McKechnie
|
||||||
*
|
*
|
||||||
* This file is part of Asbelos DCC-EX
|
* This file is part of Asbelos DCC-EX
|
||||||
@@ -27,6 +27,8 @@ extern "C" char* sbrk(int);
|
|||||||
#elif defined(__AVR__)
|
#elif defined(__AVR__)
|
||||||
extern char *__brkval;
|
extern char *__brkval;
|
||||||
extern char *__malloc_heap_start;
|
extern char *__malloc_heap_start;
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||||
|
// supported but nothing needed here
|
||||||
#else
|
#else
|
||||||
#error Unsupported board type
|
#error Unsupported board type
|
||||||
#endif
|
#endif
|
||||||
@@ -34,7 +36,7 @@ extern char *__malloc_heap_start;
|
|||||||
|
|
||||||
static volatile int minimum_free_memory = __INT_MAX__;
|
static volatile int minimum_free_memory = __INT_MAX__;
|
||||||
|
|
||||||
#if !defined(__IMXRT1062__)
|
#if !defined(__IMXRT1062__) && !defined(ARDUINO_ARCH_ESP8266) && !defined(ARDUINO_ARCH_ESP32)
|
||||||
static inline int freeMemory() {
|
static inline int freeMemory() {
|
||||||
char top;
|
char top;
|
||||||
#if defined(__arm__)
|
#if defined(__arm__)
|
||||||
@@ -55,7 +57,18 @@ int minimumFreeMemory() {
|
|||||||
return retval;
|
return retval;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
|
||||||
|
// ESP8266 and ESP32
|
||||||
|
static inline int freeMemory() {
|
||||||
|
return ESP.getFreeHeap();
|
||||||
|
}
|
||||||
|
// Return low memory value.
|
||||||
|
int minimumFreeMemory() {
|
||||||
|
int retval = minimum_free_memory;
|
||||||
|
return retval;
|
||||||
|
}
|
||||||
#else
|
#else
|
||||||
|
// All types of TEENSYs
|
||||||
#if defined(ARDUINO_TEENSY40)
|
#if defined(ARDUINO_TEENSY40)
|
||||||
static const unsigned DTCM_START = 0x20000000UL;
|
static const unsigned DTCM_START = 0x20000000UL;
|
||||||
static const unsigned OCRAM_START = 0x20200000UL;
|
static const unsigned OCRAM_START = 0x20200000UL;
|
||||||
@@ -108,4 +121,3 @@ void updateMinimumFreeMemory(unsigned char extraBytes) {
|
|||||||
if (spare < 0) spare = 0;
|
if (spare < 0) spare = 0;
|
||||||
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
if (spare < minimum_free_memory) minimum_free_memory = spare;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "StringFormatter.h"
|
#include "StringFormatter.h"
|
||||||
|
|
||||||
#define VERSION "3.2.0 rc4"
|
#define VERSION "3.2.0 ESP32"
|
||||||
// 3.2.0 Major functional and non-functional changes.
|
// 3.2.0 Major functional and non-functional changes.
|
||||||
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
|
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
|
||||||
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.
|
||||||
|
Reference in New Issue
Block a user