diff --git a/DCCRMT.cpp b/DCCRMT.cpp new file mode 100644 index 0000000..1ac1aa4 --- /dev/null +++ b/DCCRMT.cpp @@ -0,0 +1,205 @@ +/* + * © 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 . + */ + +#include "config.h" +#include "defines.h" +#if defined(ARDUINO_ARCH_ESP32) +#include "DIAG.h" +#include "DCCRMT.h" +#include "DCCWaveform.h" // for MAX_PACKET_SIZE +#include "soc/gpio_sig_map.h" + +#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) { + RMTChannel *tt = (RMTChannel *)t; + tt->RMTinterrupt(); +} + +RMTChannel::RMTChannel(byte pin, bool isMain) { + byte ch; + byte plen; + if (isMain) { + ch = 0; + plen = PREAMBLE_BITS_MAIN; + } else { + ch = 2; + plen = PREAMBLE_BITS_PROG; + } + + // 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 0) // we have still old work to do + return dataRepeat; + if (DATA_LEN(packet.length) > maxDataLen) { // this would overun our allocated memory for data + DIAG(F("Can not convert DCC bytes # %d to DCC bits %d, buffer too small"), packet.length, maxDataLen); + return -1; // something very broken, can not convert packet + } + + byte *buffer = packet.data; + + // convert bytes to RMT stream of "bits" + byte bitcounter = 0; + for(byte n=0; n 0) // if a repeat count was specified, work on that + dataRepeat--; + return; +} +#endif //ESP32 diff --git a/DCCRMT.h b/DCCRMT.h new file mode 100644 index 0000000..6e75219 --- /dev/null +++ b/DCCRMT.h @@ -0,0 +1,61 @@ +/* + * © 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 . + */ + +#pragma once +#include +#if defined(ARDUINO_ARCH_ESP32) +#include "DCCPacket.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 RMTChannel { + public: + RMTChannel(byte pin, bool isMain); + void IRAM_ATTR RMTinterrupt(); + void RMTprefill(); + int RMTfillData(dccPacket packet); + //bool RMTfillData(const byte buffer[], byte byteCount, byte repeatCount); + + static RMTChannel mainRMTChannel; + static RMTChannel progRMTChannel; + + 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; +}; +#endif //ESP32 diff --git a/WifiESP32.cpp b/WifiESP32.cpp new file mode 100644 index 0000000..48d40d6 --- /dev/null +++ b/WifiESP32.cpp @@ -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 . +*/ + +#include +#include "defines.h" +#if defined(ARDUINO_ARCH_ESP32) +#include +#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 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; clientIdavailable()) { + clients.push_back(client); + DIAG(F("New client %s"), client.remoteIP().toString().c_str()); + } + } + // loop over all connected clients + for (clientId=0; clientId 0) { + // read data from client + byte cmd[len+1]; + for(int i=0; imark(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;iread(); + 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 diff --git a/WifiESP32.h b/WifiESP32.h new file mode 100644 index 0000000..100e393 --- /dev/null +++ b/WifiESP32.h @@ -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 . + */ + +#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