1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-29 10:23:45 +02:00

Compare commits

...

52 Commits

Author SHA1 Message Date
Harald Barth
2632d44ec9 remove packetPendingRMT from wrong if 2021-11-21 21:28:56 +01:00
Harald Barth
c8e5123c0a fix compile errors on ESP32 2021-11-21 00:51:59 +01:00
Harald Barth
e7e26551ce Merge branch 'master' into ESP32 2021-11-20 23:38:12 +01:00
Harald Barth
50b854c526 remove extra zero bit 2021-11-19 00:34:56 +01:00
Harald Barth
55a789d65a set RMT clock to microseconds 2021-11-19 00:03:21 +01:00
Harald Barth
a69b7ee113 change to RMT loop mode 2021-11-18 23:57:53 +01:00
Harald Barth
114686d124 cleanup comments 2021-11-15 23:10:23 +01:00
Harald Barth
005ddef665 Transmit DCC packet to loco 2021-11-15 22:28:30 +01:00
Harald Barth
10209ed6f3 remove uneccessary workaround, compensate for interrupt length 2021-11-14 15:35:26 +01:00
Harald Barth
71117bc7a1 special version 2021-11-14 14:49:55 +01:00
Harald Barth
97065e892d transmit preamble and idle 2021-11-14 14:48:32 +01:00
Harald Barth
4668e116f4 preambles running 2021-11-14 13:10:16 +01:00
Harald Barth
5cbf0c2cad defines.h needed to get ESP32 macro on non-ESP32 2021-11-07 00:21:15 +01:00
Harald Barth
c02e976c9f protect ringstream typo fix 2021-11-07 00:12:11 +01:00
Harald Barth
55c7a0a1e8 protect ringstream 2021-11-06 23:51:32 +01:00
Harald Barth
d7e46ac625 set version 2021-11-06 03:04:50 +01:00
Harald Barth
877db433a4 make task startup nicer 2021-11-06 02:59:57 +01:00
Harald Barth
4901f12fcd make own task on core0 for WifiESP::loop() on ESP32 2021-11-06 02:40:49 +01:00
Harald Barth
836ccc143e check power overload only when not ack check 2021-11-03 09:45:30 +01:00
Harald Barth
77ee57eb83 give up eventually 2021-11-02 17:50:32 +01:00
Harald Barth
837b0a9fb6 typo 2021-10-31 23:46:25 +01:00
Harald Barth
a109ba4e01 unknown locos should have speed forward 2021-10-31 23:35:28 +01:00
Harald Barth
c87a80928b special tag 2021-10-31 22:06:22 +01:00
Harald Barth
c5b283bd8c should compile for all boards 2021-10-31 01:10:13 +02:00
Harald Barth
500fe2f717 more diag messages 2021-10-31 00:40:35 +02:00
Harald Barth
278f7618f4 do something i AP mode 2021-10-31 00:10:58 +02:00
Harald Barth
9d74b0f6a5 set pinMode analog 2021-10-29 22:19:23 +02:00
Harald Barth
31059a615c use ESP-IDF ADC functions instead of analogRead() which breaks waveform 2021-10-27 23:03:37 +02:00
Harald Barth
7d7b337f82 on ESP32 currently WIFI should be on 2021-10-24 19:38:07 +02:00
Harald Barth
05eb0d763a explain ESP32 watchdog 2021-10-24 12:59:28 +02:00
Harald Barth
b6cfc39d23 ESP32 watchdog workaround (with diag code) 2021-10-24 12:09:54 +02:00
Harald Barth
8a0ddb0d74 ESP32 I/O info 2021-10-22 08:35:29 +02:00
Harald Barth
faeb3194db ESP32 motorshield as default 2021-10-22 08:21:44 +02:00
Harald Barth
26bd3ac342 Example ESP motor shields 2021-10-05 21:55:13 +02:00
Harald Barth
d174c05127 Wifi connect and waveform 2021-10-05 21:53:02 +02:00
Harald Barth
75dffd9dfa first ESP32 compile 2021-10-05 10:39:08 +02:00
Harald Barth
0a10dbea0b not forget volatile 2021-10-04 23:12:47 +02:00
Harald Barth
43191e225e first stab at ESP32 2021-10-04 23:03:36 +02:00
Harald Barth
50bb1c950b less warnings 2021-10-03 19:58:05 +02:00
Harald Barth
0bb6b577fa Wifi STA or AP mode 2021-10-01 11:32:09 +02:00
Harald Barth
cf0c818138 Cleanup ESP specific details 2021-10-01 09:09:30 +02:00
Harald Barth
426b27f0dd Reworked use of ringbuffer 2021-09-30 22:55:14 +02:00
Harald Barth
19b4893b5f counter should be int, not uint8_t 2021-09-28 21:08:41 +02:00
Harald Barth
1c7a5320d8 more send diag 2021-09-28 17:31:12 +02:00
Harald Barth
afd4626988 send diag 2021-09-28 17:20:44 +02:00
Harald Barth
a194b8965c Ack read outside interrupt 2021-09-27 20:01:46 +02:00
Harald Barth
696d12fc5e test A0 2021-09-26 11:57:15 +02:00
Harald Barth
35cba02ee7 outboundRing uses sendData 2021-09-26 10:59:07 +02:00
Harald Barth
fa1d1619b6 wifi sendData 2021-09-26 08:37:59 +02:00
Harald Barth
b048879eaa Wifi active 2021-09-25 23:18:10 +02:00
Harald Barth
34474cbf5c WifiESP skeleton files 2021-09-21 09:23:52 +02:00
Harald Barth
7397a4089b first waveform on esp 2021-09-21 00:31:05 +02:00
30 changed files with 1230 additions and 86 deletions

View File

@@ -58,6 +58,9 @@ void setup()
// Responsibility 1: Start the usb connection for diagnostics
// This is normally Serial but uses SerialUSB on a SAMD processor
Serial.begin(115200);
#ifdef ESP_DEBUG
Serial.setDebugOutput(true);
#endif
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 Ethernet if it exists
#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);
#else
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
#endif
#endif // WIFI_ON
#if ETHERNET_ON
EthernetInterface::setup();
#endif // ETHERNET_ON
@@ -112,14 +118,18 @@ void loop()
// Responsibility 1: Handle DCC background processes
// (loco reminders and power checks)
DCC::loop();
// Responsibility 2: handle any incoming commands on USB connection
serialParser.loop(Serial);
// Responsibility 3: Optionally handle any incoming WiFi traffic
#if WIFI_ON
#ifndef ESP_FAMILY
WifiInterface::loop();
#endif
#if defined(ARDUINO_ARCH_ESP8266) // on ESP32 own task
WifiESP::loop();
#endif
#endif //WIFI_ON
#if ETHERNET_ON
EthernetInterface::loop();
#endif
@@ -137,7 +147,9 @@ void loop()
// Report any decrease in memory (will automatically trigger on first call)
static int ramLowWatermark = __INT_MAX__; // replaced on first loop
#ifdef ESP_FAMILY
updateMinimumFreeMemory(128);
#endif
int freeNow = minimumFreeMemory();
if (freeNow < ramLowWatermark)
{

24
DCC.cpp
View File

@@ -311,14 +311,14 @@ const ackOp FLASH WRITE_BIT0_PROG[] = {
W0,WACK,
V0, WACK, // validate bit is 0
ITC1, // if acked, callback(1)
FAIL // callback (-1)
CALLFAIL // callback (-1)
};
const ackOp FLASH WRITE_BIT1_PROG[] = {
BASELINE,
W1,WACK,
V1, WACK, // validate bit is 1
ITC1, // if acked, callback(1)
FAIL // callback (-1)
CALLFAIL // callback (-1)
};
const ackOp FLASH VERIFY_BIT0_PROG[] = {
@@ -327,7 +327,7 @@ const ackOp FLASH VERIFY_BIT0_PROG[] = {
ITC0, // if acked, callback(0)
V1, WACK, // validate bit is 1
ITC1,
FAIL // callback (-1)
CALLFAIL // callback (-1)
};
const ackOp FLASH VERIFY_BIT1_PROG[] = {
BASELINE,
@@ -335,7 +335,7 @@ const ackOp FLASH VERIFY_BIT1_PROG[] = {
ITC1, // if acked, callback(1)
V0, WACK,
ITC0,
FAIL // callback (-1)
CALLFAIL // callback (-1)
};
const ackOp FLASH READ_BIT_PROG[] = {
@@ -344,7 +344,7 @@ const ackOp FLASH READ_BIT_PROG[] = {
ITC1, // if acked, callback(1)
V0, WACK, // validate bit is zero
ITC0, // if acked callback 0
FAIL // bit not readable
CALLFAIL // bit not readable
};
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
// handle decoders that dont ack a write
VB,WACK,ITC1, // validate byte and callback(1) if correct
FAIL // callback (-1)
CALLFAIL // callback (-1)
};
const ackOp FLASH VERIFY_BYTE_PROG[] = {
@@ -378,7 +378,7 @@ const ackOp FLASH VERIFY_BYTE_PROG[] = {
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCBV, // verify merged byte and return it if acked ok - with retry report
FAIL };
CALLFAIL };
const ackOp FLASH READ_CV_PROG[] = {
@@ -401,7 +401,7 @@ const ackOp FLASH READ_CV_PROG[] = {
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and return it if acked ok
FAIL }; // verification failed
CALLFAIL }; // verification failed
const ackOp FLASH LOCO_ID_PROG[] = {
@@ -467,7 +467,7 @@ const ackOp FLASH LOCO_ID_PROG[] = {
V0, WACK, MERGE,
V0, WACK, MERGE,
VB, WACK, ITCB, // verify merged byte and callback
FAIL
CALLFAIL
};
const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
@@ -484,7 +484,7 @@ const ackOp FLASH SHORT_LOCO_ID_PROG[] = {
SETBYTEL, // low byte of word
WB,WACK, // some decoders don't ACK writes
VB,WACK,ITCB,
FAIL
CALLFAIL
};
const ackOp FLASH LONG_LOCO_ID_PROG[] = {
@@ -508,7 +508,7 @@ const ackOp FLASH LONG_LOCO_ID_PROG[] = {
SETBYTEL, // low byte of word
WB,WACK,
VB,WACK,ITC1, // callback(1) means Ok
FAIL
CALLFAIL
};
void DCC::writeCVByte(int16_t cv, byte byteValue, ACK_CALLBACK callback) {
@@ -857,7 +857,7 @@ void DCC::ackManagerLoop() {
}
break;
case FAIL: // callback(-1)
case CALLFAIL: // callback(-1)
callback(-1);
return;

6
DCC.h
View File

@@ -41,7 +41,7 @@ enum ackOp : byte
ITCBV, // If True callback(byte) - end of Verify Byte
ITCB7, // If True callback(byte &0x7F)
NAKFAIL, // if false callback(-1)
FAIL, // callback(-1)
CALLFAIL, // callback(-1)
BIV, // Set ackManagerByte to initial value for Verify retry
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)
@@ -207,6 +207,10 @@ private:
#define ARDUINO_TYPE "TEENSY40"
#elif defined(ARDUINO_TEENSY41)
#define ARDUINO_TYPE "TEENSY41"
#elif defined(ARDUINO_ARCH_ESP8266)
#define ARDUINO_TYPE "ESP8266"
#elif defined(ARDUINO_ARCH_ESP32)
#define ARDUINO_TYPE "ESP32"
#else
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH AN ARDUINO UNO, NANO 328, OR ARDUINO MEGA 1280/2560
#endif

11
DCCEX.h
View File

@@ -30,7 +30,13 @@
#include "DIAG.h"
#include "DCCEXParser.h"
#include "version.h"
#if defined(ARDUINO_ARCH_ESP8266)
#include "WifiESP8266.h"
#elif defined(ARDUINO_ARCH_ESP32)
#include "WifiESP32.h"
#else
#include "WifiInterface.h"
#endif
#if ETHERNET_ON == true
#include "EthernetInterface.h"
#endif
@@ -43,5 +49,10 @@
#include "Outputs.h"
#include "RMFT.h"
// not yet in this branch
//#if __has_include ( "myAutomation.h")
// #include "RMFT.h"
// #define RMFT_ACTIVE
//#endif
#endif

View File

@@ -17,9 +17,10 @@
* You should have received a copy of the GNU General Public License
* 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 "DCCEXParser.h"
#include "DCC.h"
#include "DCCWaveform.h"
#include "Turnouts.h"
#include "Outputs.h"
@@ -31,7 +32,9 @@
#include "EEStore.h"
#include "DIAG.h"
#ifndef ESP_FAMILY
#include <avr/wdt.h>
#endif
////////////////////////////////////////////////////////////////////////////////
//
@@ -860,8 +863,12 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
case HASH_KEYWORD_RESET:
{
wdt_enable( WDTO_15MS); // set Arduino watchdog timer for 15ms
delay(50); // wait for the prescaller time to expire
#ifndef ESP_FAMILY
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
}

168
DCCRMT.cpp Normal file
View 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
View 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;
};

View File

@@ -44,7 +44,7 @@
#include "DCCTimer.h"
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;
@@ -53,11 +53,11 @@ INTERRUPT_CALLBACK interruptHandler=0;
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
noInterrupts();
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.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.INTCTRL = TCB_CAPT_bm; // Enable the interrupt
TCB0.CNT = 0;
@@ -146,6 +146,50 @@ void DCCTimer::read(uint8_t word, uint8_t *mac, uint8_t offset) {
}
#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
// Arduino nano, uno, mega etc
#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) {
interruptHandler=callback;
noInterrupts();
noInterrupts();
ADCSRA = (ADCSRA & 0b11111000) | 0b00000100; // speed up analogRead sample time
TCCR1A = 0;
ICR1 = CLOCK_CYCLES;
ICR1 = CLOCK_CYCLES>>1;
TCNT1 = 0;
TCCR1B = _BV(WGM13) | _BV(CS10); // Mode 8, clock select 1
TIMSK1 = _BV(TOIE1); // Enable Software interrupt

View File

@@ -37,4 +37,14 @@ class DCCTimer {
private:
};
#if defined(ARDUINO_ARCH_ESP32)
extern portMUX_TYPE timerMux;
#endif
#if !(defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_ESP8266))
#ifndef IRAM_ATTR
#define IRAM_ATTR
#endif
#endif
#endif //DCCTimer.h

View File

@@ -20,6 +20,7 @@
#include <Arduino.h>
#include "defines.h"
#include "DCCWaveform.h"
#include "DCCTimer.h"
#include "DIAG.h"
@@ -36,6 +37,9 @@ volatile uint8_t DCCWaveform::numAckSamples=0;
uint8_t DCCWaveform::trailingEdgeCounter=0;
void DCCWaveform::begin(MotorDriver * mainDriver, MotorDriver * progDriver) {
mainTrack.rmtPin = new RMTPin(21, 0, PREAMBLE_BITS_MAIN);
mainTrack.motorDriver=mainDriver;
progTrack.motorDriver=progDriver;
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);
}
void DCCWaveform::loop(bool ackManagerActive) {
mainTrack.checkPowerOverload(false);
#ifdef SLOW_ANALOG_READ
// 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);
#endif
mainTrack.checkPowerOverload(false);
}
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void DCCWaveform::interruptHandler() {
void IRAM_ATTR DCCWaveform::interruptHandler() {
// call the timer edge sensitive actions for progtrack and maintrack
// member functions would be cleaner but have more overhead
byte sigMain=signalTransform[mainTrack.state];
byte sigProg=progTrackSyncMain? sigMain : signalTransform[progTrack.state];
// Set the signal state for both tracks
mainTrack.motorDriver->setSignal(sigMain);
progTrack.motorDriver->setSignal(sigProg);
// Move on in the state engine
mainTrack.state=stateTransform[mainTrack.state];
progTrack.state=stateTransform[progTrack.state];
// WAVE_PENDING means we dont yet know what the next bit is
if (mainTrack.state==WAVE_PENDING) mainTrack.interrupt2();
if (progTrack.state==WAVE_PENDING) progTrack.interrupt2();
else if (progTrack.ackPending) progTrack.checkAck();
if (mainTrack.state==WAVE_PENDING)
mainTrack.interrupt2();
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
@@ -94,6 +128,7 @@ const byte bitMask[] = {0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};
DCCWaveform::DCCWaveform( byte preambleBits, bool isMain) {
isMainTrack = isMain;
packetPending = false;
packetPendingRMT = false;
memcpy(transmitPacket, idlePacket, sizeof(idlePacket));
state = WAVE_START;
// 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 optimize ("-O3")
void DCCWaveform::interrupt2() {
void IRAM_ATTR DCCWaveform::interrupt2() {
// calculate the next bit to be sent:
// set state WAVE_MID_1 for a 1=bit
// or WAVE_HIGH_0 for a 0 bit.
@@ -212,7 +247,9 @@ void DCCWaveform::interrupt2() {
remainingPreambles--;
// 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.
updateMinimumFreeMemory(22);
#ifndef ESP_FAMILY
updateMinimumFreeMemory(22);
#endif
return;
}
@@ -237,7 +274,8 @@ void DCCWaveform::interrupt2() {
transmitRepeats--;
}
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
// for (int b = 0; b < pendingLength; b++) transmitPacket[b] = pendingPacket[b];
memcpy( transmitPacket, pendingPacket, sizeof(pendingPacket));
@@ -246,6 +284,7 @@ void DCCWaveform::interrupt2() {
transmitRepeats = pendingRepeats;
packetPending = false;
sentResetsSincePacket=0;
portEXIT_CRITICAL(&timerMux);
}
else {
// 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) {
if (byteCount > MAX_PACKET_SIZE) return; // allow for chksum
while (packetPending);
portENTER_CRITICAL(&timerMux);
byte checksum = 0;
for (byte b = 0; b < byteCount; b++) {
checksum ^= buffer[b];
@@ -275,7 +314,9 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
pendingLength = byteCount + 1;
pendingRepeats = repeats;
packetPending = true;
packetPendingRMT = true;
sentResetsSincePacket=0;
portEXIT_CRITICAL(&timerMux);
}
// Operations applicable to PROG track ONLY.
@@ -313,14 +354,14 @@ byte DCCWaveform::getAck() {
#pragma GCC push_options
#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
if (sentResetsSincePacket > 6) { //ACK timeout
ackCheckDuration=millis()-ackCheckStart;
ackPending = false;
return;
}
int current=motorDriver->getCurrentRaw();
numAckSamples++;
if (current > ackMaxCurrent) ackMaxCurrent=current;

View File

@@ -20,6 +20,7 @@
#ifndef DCCWaveform_h
#define DCCWaveform_h
#include "DCCRMT.h"
#include "MotorDriver.h"
// Wait times for power management. Unit: milliseconds
@@ -82,6 +83,7 @@ class DCCWaveform {
}
void schedulePacket(const byte buffer[], byte byteCount, byte repeats);
volatile bool packetPending;
volatile bool packetPendingRMT;
volatile byte sentResetsSincePacket;
volatile bool autoPowerOff=false;
void setAckBaseline(); //prog track only
@@ -122,6 +124,7 @@ class DCCWaveform {
bool isMainTrack;
MotorDriver* motorDriver;
RMTPin* rmtPin;
// Transmission controller
byte transmitPacket[MAX_PACKET_SIZE+1]; // +1 for checksum
byte transmitLength;

View File

@@ -1 +1 @@
#define GITHUB_SHA "197228c"
#define GITHUB_SHA "ESP32-2021120-11:30"

View File

@@ -22,6 +22,12 @@
#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> {
public:
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
@@ -97,4 +103,4 @@ private:
};
#endif
#endif

View File

@@ -75,7 +75,7 @@ private:
if (immediate) {
uint8_t buffer[1];
I2CManager.read(_I2CAddress, buffer, 1);
_portInputState = ((uint16_t)buffer) & 0xff;
_portInputState = buffer[0];
} else {
requestBlock.wait(); // Wait for preceding operation to complete
// 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.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = ((uint16_t)inputBuffer[0]) & 0xff;
_portInputState = inputBuffer[0];
else
_portInputState = 0xff;
}
@@ -99,4 +99,4 @@ private:
uint8_t inputBuffer[1];
};
#endif
#endif

View File

@@ -20,11 +20,10 @@
#include "MotorDriver.h"
#include "DCCTimer.h"
#include "DIAG.h"
#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))
#if defined(ARDUINO_ARCH_ESP32)
#include <driver/adc.h>
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
#endif
bool MotorDriver::usePWM=false;
bool MotorDriver::commonFaultPin=false;
@@ -59,8 +58,15 @@ MotorDriver::MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8
currentPin=current_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);
senseOffset=analogRead(currentPin); // value of sensor at zero current
#endif
}
faultPin=fault_pin;
@@ -110,7 +116,7 @@ void MotorDriver::setBrake(bool on) {
else setLOW(fastBrakePin);
}
void MotorDriver::setSignal( bool high) {
void IRAM_ATTR MotorDriver::setSignal( bool high) {
if (usePWM) {
DCCTimer::setPWM(signalPin,high);
}
@@ -155,6 +161,8 @@ int MotorDriver::getCurrentRaw() {
current = analogRead(currentPin)-senseOffset;
overflow_count = 0;
SREG = sreg_backup; /* restore interrupt state */
#elif defined(ARDUINO_ARCH_ESP32)
current = adc1_get_raw(pinToADC1Channel(currentPin))-senseOffset;
#else
current = analogRead(currentPin)-senseOffset;
#endif
@@ -176,8 +184,8 @@ int MotorDriver::mA2raw( unsigned int mA) {
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
(void) type; // avoid compiler warning if diag not used above.
uint8_t port = digitalPinToPort(pin);
(void) type; // avoid compiler warning if diag not used above.
PORTTYPE port = digitalPinToPort(pin);
if (input)
result.inout = portInputRegister(port);
else

View File

@@ -18,6 +18,7 @@
*/
#ifndef MotorDriver_h
#define MotorDriver_h
#include "defines.h"
#include "FSH.h"
// Virtualised Motor shield 1-track hardware Interface
@@ -26,13 +27,15 @@
#define UNUSED_PIN 127 // inside int8_t
#endif
#if defined(__IMXRT1062__)
#if defined(__IMXRT1062__) || defined(ESP_FAMILY)
typedef uint32_t PORTTYPE;
struct FASTPIN {
volatile uint32_t *inout;
uint32_t maskHIGH;
uint32_t maskLOW;
};
#else
typedef uint8_t PORTTYPE;
struct FASTPIN {
volatile uint8_t *inout;
uint8_t maskHIGH;
@@ -40,12 +43,30 @@ struct FASTPIN {
};
#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 {
public:
MotorDriver(byte power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
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 int getCurrentRaw();
virtual unsigned int raw2mA( int raw);

View File

@@ -226,16 +226,16 @@ const int StringMacroTracker1=__COUNTER__;
// Define macros for route code creation
#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 EXRAIL const FLASH byte RMFT2::RouteCode[] = {
#define AUTOMATION(id, description) OPCODE_AUTOMATION, V(id),
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
#define ENDTASK OPCODE_ENDTASK,NOP,
#define DONE OPCODE_ENDTASK,NOP,
#define ENDEXRAIL OPCODE_ENDTASK,NOP,OPCODE_ENDEXRAIL,NOP };
#define ENDTASK OPCODE_ENDTASK,NOOPERAND,
#define DONE OPCODE_ENDTASK,NOOPERAND,
#define ENDEXRAIL OPCODE_ENDTASK,NOOPERAND,OPCODE_ENDEXRAIL,NOOPERAND };
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_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 DELAYMINS(mindelay) OPCODE_DELAYMINS,V(mindelay),
#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 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),
@@ -258,23 +258,23 @@ const int StringMacroTracker1=__COUNTER__;
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,NOP,
#define JOIN OPCODE_JOIN,NOP,
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,NOOPERAND,
#define JOIN OPCODE_JOIN,NOOPERAND,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCD(id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg)
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,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 POWEROFF OPCODE_POWEROFF,NOP,
#define POWEROFF OPCODE_POWEROFF,NOOPERAND,
#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 RESERVE(blockid) OPCODE_RESERVE,V(blockid),
#define RESET(pin) OPCODE_RESET,V(pin),
#define RESUME OPCODE_RESUME,NOP,
#define RETURN OPCODE_RETURN,NOP,
#define RESUME OPCODE_RESUME,NOOPERAND,
#define RETURN OPCODE_RETURN,NOOPERAND,
#define REV(speed) OPCODE_REV,V(speed),
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
#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 THROW(id) OPCODE_THROW,V(id),
#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 WAITFOR(pin) OPCODE_WAITFOR,V(pin),
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),

View File

@@ -18,6 +18,7 @@
*/
#include "RingStream.h"
#include "defines.h"
#include "DIAG.h"
RingStream::RingStream( const uint16_t len)
@@ -30,27 +31,36 @@ RingStream::RingStream( const uint16_t len)
_overflow=false;
_mark=0;
_count=0;
#if defined(ARDUINO_ARCH_ESP32)
_bufMux = portMUX_INITIALIZER_UNLOCKED;
#endif
}
size_t RingStream::write(uint8_t b) {
if (_overflow) return 0;
portENTER_CRITICAL(&_bufMux);
_buffer[_pos_write] = b;
++_pos_write;
if (_pos_write==_len) _pos_write=0;
if (_pos_write==_pos_read) {
_overflow=true;
portEXIT_CRITICAL(&_bufMux);
return 0;
}
_count++;
portEXIT_CRITICAL(&_bufMux);
return 1;
}
int RingStream::read() {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
int RingStream::read(byte advance) {
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
if (_pos_read == _mark) return -1;
portENTER_CRITICAL(&_bufMux);
byte b=_buffer[_pos_read];
_pos_read++;
_pos_read += advance;
if (_pos_read==_len) _pos_read=0;
_overflow=false;
portEXIT_CRITICAL(&_bufMux);
return b;
}
@@ -68,11 +78,14 @@ int RingStream::freeSpace() {
// mark start of message with client id (0...9)
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;
write(b); // client id
write((uint8_t)0); // count MSB placemarker
write((uint8_t)0); // count LSB placemarker
_count=0;
portEXIT_CRITICAL(&_bufMux);
}
// peekTargetMark is used by the parser stash routines to know which client
@@ -81,17 +94,25 @@ uint8_t RingStream::peekTargetMark() {
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() {
//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) {
DIAG(F("RingStream(%d) commit(%d) OVERFLOW"),_len, _count);
// just throw it away
_pos_write=_mark;
_overflow=false;
return false; // commit failed
portEXIT_CRITICAL(&_bufMux);
return false; // commit failed
}
if (_count==0) {
// ignore empty response
_pos_write=_mark;
portEXIT_CRITICAL(&_bufMux);
return true; // true=commit ok
}
// Go back to the _mark and inject the count 1 byte later
@@ -101,5 +122,8 @@ bool RingStream::commit() {
_mark++;
if (_mark==_len) _mark=0;
_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
}

View File

@@ -28,14 +28,17 @@ class RingStream : public Print {
virtual size_t write(uint8_t b);
using Print::write;
int read();
inline int read() { return read(1); };
inline int peek() { return read(0); };
int count();
int freeSpace();
void mark(uint8_t b);
bool commit();
uint8_t peekTargetMark();
void info();
private:
int read(byte advance);
int _len;
int _pos_write;
int _pos_read;
@@ -43,6 +46,9 @@ class RingStream : public Print {
int _mark;
int _count;
byte * _buffer;
#if defined(ARDUINO_ARCH_ESP32)
portMUX_TYPE _bufMux;
#endif
};
#endif

246
WifiESP32.cpp Normal file
View 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
View 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
View 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
View 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

View File

@@ -17,9 +17,10 @@
You should have received a copy of the GNU General Public License
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
// 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 "DIAG.h"
#include "StringFormatter.h"
@@ -370,4 +371,5 @@ void WifiInterface::loop() {
}
}
#endif
#endif //ARDUINO_AVR_UNO_WIFI_REV2
#endif //ESP_FAMILY

View File

@@ -19,6 +19,8 @@
*/
#ifndef WifiInterface_h
#define WifiInterface_h
#include "defines.h"
#ifndef ESP_FAMILY
#include "FSH.h"
#include "DCCEXParser.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 connected;
};
#endif
#endif //ESP_FAMILY
#endif

View File

@@ -41,7 +41,29 @@ The configuration file for DCC-EX Command Station
// |
// +-----------------------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.
@@ -53,6 +75,8 @@ The configuration file for DCC-EX Command Station
// NOTE: Only supported on 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
/////////////////////////////////////////////////////////////////////////////////////

View File

@@ -1,5 +1,5 @@
/*
© 2020, Harald Barth.
© 2020,2021 Harald Barth.
This file is part of CommandStation-EX
@@ -31,14 +31,34 @@
#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
// 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
#endif
#if ENABLE_WIFI && defined(BIG_RAM)
#define WIFI_ON true
#ifndef WIFI_CHANNEL
@@ -69,4 +89,4 @@
#define RMFT_ACTIVE
#endif
#endif
#endif

67
esp32wdt.txt Normal file
View 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/

View File

@@ -1,5 +1,5 @@
/*
* © 2020, Harald Barth
* © 2020,2021 Harald Barth
* © 2021, Neil McKechnie
*
* This file is part of Asbelos DCC-EX
@@ -27,6 +27,8 @@ extern "C" char* sbrk(int);
#elif defined(__AVR__)
extern char *__brkval;
extern char *__malloc_heap_start;
#elif defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32)
// supported but nothing needed here
#else
#error Unsupported board type
#endif
@@ -34,7 +36,7 @@ extern char *__malloc_heap_start;
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() {
char top;
#if defined(__arm__)
@@ -55,7 +57,18 @@ int minimumFreeMemory() {
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
// All types of TEENSYs
#if defined(ARDUINO_TEENSY40)
static const unsigned DTCM_START = 0x20000000UL;
static const unsigned OCRAM_START = 0x20200000UL;
@@ -108,4 +121,3 @@ void updateMinimumFreeMemory(unsigned char extraBytes) {
if (spare < 0) spare = 0;
if (spare < minimum_free_memory) minimum_free_memory = spare;
}

View File

@@ -3,7 +3,7 @@
#include "StringFormatter.h"
#define VERSION "3.2.0 rc4"
#define VERSION "3.2.0 ESP32"
// 3.2.0 Major functional and non-functional changes.
// New HAL added for I/O (digital and analogue inputs and outputs, servos etc).
// Support for MCP23008, MCP23017 and PCF9584 I2C GPIO Extender modules.