From 07ab7286ba8ea492ce3c6181633b207690ccd119 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Sat, 28 Sep 2024 19:48:11 +0800 Subject: [PATCH 1/7] STM32 Ethernet support --- EthernetInterface.cpp | 128 +++++++++++++++++++++++++----------------- EthernetInterface.h | 9 ++- I2CManager_STM32.h | 5 +- MotorDrivers.h | 10 +++- platformio.ini | 55 ++++++++++-------- version.h | 5 +- 6 files changed, 133 insertions(+), 79 deletions(-) diff --git a/EthernetInterface.cpp b/EthernetInterface.cpp index c88a766..f8306cb 100644 --- a/EthernetInterface.cpp +++ b/EthernetInterface.cpp @@ -1,8 +1,10 @@ /* + * © 2024 Morten "Doc" Nielsen + * © 2023-2024 Paul M. Antoine * © 2022 Bruno Sanches * © 2021 Fred Decker * © 2020-2022 Harald Barth - * © 2020-2021 Chris Harlow + * © 2020-2024 Chris Harlow * © 2020 Gregor Baues * All rights reserved. * @@ -36,9 +38,14 @@ MDNS mdns(udp); #endif + +//extern void looptimer(unsigned long timeout, const FSH* message); +#define looptimer(a,b) + bool EthernetInterface::connected=false; EthernetServer * EthernetInterface::server= nullptr; EthernetClient EthernetInterface::clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield +bool EthernetInterface::inUse[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield uint8_t EthernetInterface::buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv RingStream * EthernetInterface::outboundRing = nullptr; @@ -47,12 +54,13 @@ RingStream * EthernetInterface::outboundRing = nullptr; * */ -void EthernetInterface::setup() // STM32 VERSION +void EthernetInterface::setup() { - DIAG(F("Ethernet begin" + DIAG(F("Ethernet starting" #ifdef DO_MDNS - " with mDNS" + " (with mDNS)" #endif + " Please be patient, especially if no cable is connected!" )); #ifdef STM32_ETHERNET @@ -116,6 +124,47 @@ void EthernetInterface::setup() // STM32 VERSION connected=true; } +#if defined (STM32_ETHERNET) +void EthernetInterface::acceptClient() { // STM32 version + auto client=server->available(); + if (!client) return; + // check for existing client + for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) + if (inUse[socket] && client == clients[socket]) return; + + // new client + for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) + { + if (!inUse[socket]) + { + clients[socket] = client; + inUse[socket]=true; + if (Diag::ETHERNET) + DIAG(F("Ethernet: New client socket %d"), socket); + return; + } + } + DIAG(F("Ethernet OVERFLOW")); +} +#else +void EthernetInterface::acceptClient() { // non-STM32 version + auto client=server->accept(); + if (!client) return; + auto socket=client.getSocketNumber(); + clients[socket]=client; + inUse[socket]=true; + if (Diag::ETHERNET) + DIAG(F("Ethernet: New client socket %d"), socket); +} +#endif + +void EthernetInterface::dropClient(byte socket) +{ + clients[socket].stop(); + inUse[socket]=false; + CommandDistributor::forget(socket); + if (Diag::ETHERNET) DIAG(F("Ethernet: Disconnect %d "), socket); +} /** * @brief Main loop for the EthernetInterface @@ -124,7 +173,8 @@ void EthernetInterface::setup() // STM32 VERSION void EthernetInterface::loop() { if (!connected) return; - + looptimer(5000, F("E.loop")); + static bool warnedAboutLink=false; if (Ethernet.linkStatus() == LinkOFF){ if (warnedAboutLink) return; @@ -132,7 +182,8 @@ void EthernetInterface::loop() warnedAboutLink=true; return; } - + looptimer(5000, F("E.loop warn")); + // link status must be ok here if (warnedAboutLink) { DIAG(F("Ethernet link RESTORED")); @@ -142,6 +193,8 @@ void EthernetInterface::loop() #ifdef DO_MDNS // Always do this because we don't want traffic to intefere with being found! mdns.run(); + looptimer(5000, F("E.mdns")); + #endif // @@ -161,50 +214,22 @@ void EthernetInterface::loop() //DIAG(F("maintained")); break; } + looptimer(5000, F("E.maintain")); + + // get client from the server + acceptClient(); - // get client from the server - #if defined (STM32_ETHERNET) - // STM32Ethernet doesn't use accept(), just available() - auto client = server->available(); - if (client) { - // check for new client - byte socket; - bool sockfound = false; - for (socket = 0; socket < MAX_SOCK_NUM; socket++) - { - if (client == clients[socket]) - { - sockfound = true; - break; - } - } - if (!sockfound) - { // new client - for (socket = 0; socket < MAX_SOCK_NUM; socket++) - { - if (!clients[socket]) - { - clients[socket] = client; - sockfound=true; - if (Diag::ETHERNET) - DIAG(F("Ethernet: New client socket %d"), socket); - break; - } - } - } - if (!sockfound) DIAG(F("new Ethernet OVERFLOW")); - } - - #else - auto client = server->accept(); - if (client) clients[client.getSocketNumber()]=client; - #endif - + // handle disconnected sockets because STM32 library doesnt + // do the read==0 response. + for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) + { + if (inUse[socket] && !clients[socket].connected()) dropClient(socket); + } // check for incoming data from all possible clients for (byte socket = 0; socket < MAX_SOCK_NUM; socket++) { - if (!clients[socket]) continue; // socket is not in use + if (!inUse[socket]) continue; // socket is not in use // read any bytes from this client auto count = clients[socket].read(buffer, MAX_ETH_BUFFER); @@ -216,14 +241,13 @@ void EthernetInterface::loop() if (Diag::ETHERNET) DIAG(F("Ethernet s=%d, c=%d b=:%e"), socket, count, buffer); // execute with data going directly back CommandDistributor::parse(socket,buffer,outboundRing); + //looptimer(5000, F("Ethloop2 parse")); return; // limit the amount of processing that takes place within 1 loop() cycle. } // count=0 The client has disconnected - clients[socket].stop(); - CommandDistributor::forget(socket); - if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket); - } + dropClient(socket); + } WiThrottle::loop(outboundRing); @@ -245,9 +269,11 @@ void EthernetInterface::loop() tmpbuf[i] = outboundRing->read(); } tmpbuf[count]=0; - if (Diag::ETHERNET) DIAG(F("Ethernet reply s=%d, c=%d, b:%e"), + if (inUse[socketOut]) { + if (Diag::ETHERNET) DIAG(F("Ethernet reply s=%d, c=%d, b:%e"), socketOut,count,tmpbuf); - clients[socketOut].write(tmpbuf,count); + clients[socketOut].write(tmpbuf,count); + } } } diff --git a/EthernetInterface.h b/EthernetInterface.h index 9ea2718..16156fa 100644 --- a/EthernetInterface.h +++ b/EthernetInterface.h @@ -1,8 +1,10 @@ /* + * © 2023-2024 Paul M. Antoine * © 2021 Neil McKechnie * © 2021 Mike S * © 2021 Fred Decker - * © 2020-2021 Chris Harlow + * © 2020-2022 Harald Barth + * © 2020-2024 Chris Harlow * © 2020 Gregor Baues * All rights reserved. * @@ -35,6 +37,7 @@ #if defined (ARDUINO_TEENSY41) #include //TEENSY Ethernet Treiber #include + #define MAX_SOCK_NUM 4 #elif defined (ARDUINO_NUCLEO_F429ZI) || defined (ARDUINO_NUCLEO_F439ZI) || defined (ARDUINO_NUCLEO_F4X9ZI) #include // #include "STM32lwipopts.h" @@ -67,8 +70,12 @@ class EthernetInterface { static bool connected; static EthernetServer * server; static EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield + static bool inUse[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield static uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv static RingStream * outboundRing; + static void acceptClient(); + static void dropClient(byte socketnum); + }; #endif diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 7e9e63e..109c89b 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -38,8 +38,9 @@ *****************************************************************************/ #if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32) #if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) \ - || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) \ - || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE) + || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F446ZE) \ + || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI) + // Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely all Nucleo-64 // and Nucleo-144 variants I2C_TypeDef *s = I2C1; diff --git a/MotorDrivers.h b/MotorDrivers.h index d51ab16..defbfd4 100644 --- a/MotorDrivers.h +++ b/MotorDrivers.h @@ -75,11 +75,19 @@ #define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD #define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD +#if defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI) +// EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC +// The Ethernet capable STM32 models cannot use Channel B BRAKE on D8, and must use the ALT pin of D6, +// AND cannot use Channel B PWN on D11, but must use the ALT pin of D5 +#define EX8874_SHIELD F("EX8874"), \ + new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \ + new MotorDriver( 5, 13, UNUSED_PIN, 6, A1, 1.27, 5000, A5) +#else // EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC #define EX8874_SHIELD F("EX8874"), \ new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \ new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, A5) - +#endif #elif defined(ARDUINO_ARCH_ESP32) // STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the diff --git a/platformio.ini b/platformio.ini index 2de9bed..6317372 100644 --- a/platformio.ini +++ b/platformio.ini @@ -202,7 +202,7 @@ monitor_speed = 115200 monitor_echo = yes [env:Nucleo-F411RE] -platform = ststm32 +platform = ststm32 @ 17.6.0 board = nucleo_f411re framework = arduino lib_deps = ${env.lib_deps} @@ -211,7 +211,7 @@ monitor_speed = 115200 monitor_echo = yes [env:Nucleo-F446RE] -platform = ststm32 +platform = ststm32 @ 17.6.0 board = nucleo_f446re framework = arduino lib_deps = ${env.lib_deps} @@ -223,7 +223,7 @@ monitor_echo = yes ; tested as yet ; [env:Nucleo-F401RE] -platform = ststm32 +platform = ststm32 @ 17.6.0 board = nucleo_f401re framework = arduino lib_deps = ${env.lib_deps} @@ -236,7 +236,7 @@ monitor_echo = yes ; installed before you can let PlatformIO see this ; ; [env:Nucleo-F413ZH] -; platform = ststm32 +; platform = ststm32 @ 17.6.0 ; board = nucleo_f413zh ; framework = arduino ; lib_deps = ${env.lib_deps} @@ -248,7 +248,7 @@ monitor_echo = yes ; installed before you can let PlatformIO see this ; [env:Nucleo-F446ZE] -platform = ststm32 +platform = ststm32 @ 17.6.0 board = nucleo_f446ze framework = arduino lib_deps = ${env.lib_deps} @@ -260,8 +260,8 @@ monitor_echo = yes ; installed before you can let PlatformIO see this ; ; [env:Nucleo-F412ZG] -; platform = ststm32 -; board = blah_f412zg +; platform = ststm32 @ 17.6.0 +; board = nucleo_f412zg ; framework = arduino ; lib_deps = ${env.lib_deps} ; build_flags = -std=c++17 -Os -g2 -Wunused-variable @@ -272,12 +272,12 @@ monitor_echo = yes ; Experimental - Ethernet work still in progress ; [env:Nucleo-F429ZI] -platform = ststm32 +platform = ststm32 @ 17.6.0 board = nucleo_f429zi framework = arduino lib_deps = ${env.lib_deps} - stm32duino/STM32Ethernet @ ^1.3.0 - stm32duino/STM32duino LwIP @ ^2.1.2 + stm32duino/STM32Ethernet @ ^1.4.0 + stm32duino/STM32duino LwIP @ ^2.1.3 MDNS_Generic lib_ignore = WiFi101 WiFi101_Generic @@ -289,18 +289,29 @@ monitor_speed = 115200 monitor_echo = yes upload_protocol = stlink -; [env:Nucleo-F429ZI] -; platform = ststm32 -; board = nucleo_f429zi -; framework = arduino -; lib_deps = ${env.lib_deps} -; arduino-libraries/Ethernet @ ^2.0.1 -; stm32duino/STM32Ethernet @ ^1.3.0 -; stm32duino/STM32duino LwIP @ ^2.1.2 -; build_flags = -std=c++17 -Os -g2 -Wunused-variable -; monitor_speed = 115200 -; monitor_echo = yes -; upload_protocol = stlink +; Experimental - Ethernet work still in progress +; +[env:Nucleo-F439ZI] +platform = ststm32 @ 17.6.0 +; board = nucleo_f439zi +; Temporarily treat it as an F429ZI (they are code compatible) until +; the PR to PlatformIO to update the F439ZI JSON file is available +; PMA - 28-Sep-2024 +board = nucleo_f429zi +framework = arduino +lib_deps = ${env.lib_deps} + stm32duino/STM32Ethernet @ ^1.4.0 + stm32duino/STM32duino LwIP @ ^2.1.3 + MDNS_Generic +lib_ignore = WiFi101 + WiFi101_Generic + WiFiEspAT + WiFiMulti_Generic + WiFiNINA_Generic +build_flags = -std=c++17 -Os -g2 -Wunused-variable +monitor_speed = 115200 +monitor_echo = yes +upload_protocol = stlink [env:Teensy3_2] platform = teensy diff --git a/version.h b/version.h index 36c07c7..269e47d 100644 --- a/version.h +++ b/version.h @@ -3,8 +3,9 @@ #include "StringFormatter.h" -#define VERSION "5.2.80" -// 5.2.80 - EthernetInterface upgrade +#define VERSION "5.2.81" +// 5.2.81 - STM32 Ethernet boards support, also now have specific EX8874 motor driver definition +// 5.2.80 - EthernetInterface upgrade, including STM32 Ethernet support // 5.2.79 - serial manager loop that handles quoted strings // - WiFiESP32 reconfig // 5.2.78 - NeoPixel support. From 01533e2cd253fc3fb19ba0c11085f3fb19f4bc52 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 30 Sep 2024 10:05:47 +0200 Subject: [PATCH 2/7] TrackManager and EXRAIL: Introduce more consistent names for <= ...> and SET_TRACK --- DCCEXParser.cpp | 4 ++-- MotorDriver.cpp | 2 +- MotorDriver.h | 30 +++++++++++++++++++++--------- TrackManager.cpp | 41 ++++++++++++++++++++++++++--------------- 4 files changed, 50 insertions(+), 27 deletions(-) diff --git a/DCCEXParser.cpp b/DCCEXParser.cpp index a133a2e..0400ae0 100644 --- a/DCCEXParser.cpp +++ b/DCCEXParser.cpp @@ -577,7 +577,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) { if (params > 1) break; if (params==0) { // All - TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON); + TrackManager::setTrackPower(TRACK_ALL, POWERMODE::ON); } if (params==1) { if (p[0]=="MAIN"_hk) { // <1 MAIN> @@ -610,7 +610,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream) if (params > 1) break; if (params==0) { // All TrackManager::setJoin(false); - TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF); + TrackManager::setTrackPower(TRACK_ALL, POWERMODE::OFF); } if (params==1) { if (p[0]=="MAIN"_hk) { // <0 MAIN> diff --git a/MotorDriver.cpp b/MotorDriver.cpp index 749c425..39ec08d 100644 --- a/MotorDriver.cpp +++ b/MotorDriver.cpp @@ -576,7 +576,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) { DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A'); } setPower(POWERMODE::ALERT); - if ((trackMode & TRACK_MODE_AUTOINV) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){ + if ((trackMode & TRACK_MODIFIER_AUTO) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){ DIAG(F("TRACK %c INVERT"), trackno + 'A'); invertOutput(); } diff --git a/MotorDriver.h b/MotorDriver.h index 3438c05..269fee7 100644 --- a/MotorDriver.h +++ b/MotorDriver.h @@ -29,21 +29,33 @@ #include // use powers of two so we can do logical and/or on the track modes in if clauses. -// RACK_MODE_DCX is (TRACK_MODE_DC|TRACK_MODE_INV) +// For example TRACK_MODE_DC_INV is (TRACK_MODE_DC|TRACK_MODIFIER_INV) template inline T operator~ (T a) { return (T)~(int)a; } template inline T operator| (T a, T b) { return (T)((int)a | (int)b); } template inline T operator& (T a, T b) { return (T)((int)a & (int)b); } template inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); } -enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, - TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16, +enum TRACK_MODE : byte { + // main modes + TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4, + TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16, + // modifiers + TRACK_MODIFIER_INV = 64, TRACK_MODIFIER_AUTO = 128, #ifdef ARDUINO_ARCH_ESP32 - TRACK_MODE_BOOST = 32, + TRACK_MODE_BOOST = 32, + TRACK_MODE_BOOST_INV = TRACK_MODE_BOOST|TRACK_MODIFIER_INV, + TRACK_MODE_BOOST_AUTO = TRACK_MODE_BOOST|TRACK_MODIFIER_AUTO, #else - TRACK_MODE_BOOST = 0, + TRACK_MODE_BOOST = 0, + TRACK_MODE_BOOST_INV = 0, + TRACK_MODE_BOOST_AUTO = 0, #endif - TRACK_MODE_ALL = TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_EXT|TRACK_MODE_BOOST, - TRACK_MODE_INV = 64, - TRACK_MODE_DCX = TRACK_MODE_DC|TRACK_MODE_INV, TRACK_MODE_AUTOINV = 128}; + // derived modes; TRACK_ALL is calles that so it does not match TRACK_MODE_* + TRACK_ALL = TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_EXT|TRACK_MODE_BOOST, + TRACK_MODE_MAIN_INV = TRACK_MODE_MAIN|TRACK_MODIFIER_INV, + TRACK_MODE_MAIN_AUTO = TRACK_MODE_MAIN|TRACK_MODIFIER_AUTO, + TRACK_MODE_DC_INV = TRACK_MODE_DC|TRACK_MODIFIER_INV, + TRACK_MODE_DCX = TRACK_MODE_DC_INV // DCX is other name for historical reasons +}; #define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH #define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW @@ -273,7 +285,7 @@ class MotorDriver { #endif inline void setMode(TRACK_MODE m) { trackMode = m; - invertOutput(trackMode & TRACK_MODE_INV); + invertOutput(trackMode & TRACK_MODIFIER_INV); }; inline void invertOutput() { // toggles output inversion invertPhase = !invertPhase; diff --git a/TrackManager.cpp b/TrackManager.cpp index ba9776b..a164142 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -387,11 +387,15 @@ bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[]) if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS)) return false; - if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN> + if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN> return setTrackMode(p[0],TRACK_MODE_MAIN); + if (params==2 && p[1]=="MAIN_INV"_hk) // <= id MAIN_INV> + return setTrackMode(p[0],TRACK_MODE_MAIN_INV); + if (params==2 && p[1]=="MAIN_AUTO"_hk) // <= id MAIN_AUTO> + return setTrackMode(p[0],TRACK_MODE_MAIN_AUTO); #ifndef DISABLE_PROG - if (params==2 && p[1]=="PROG"_hk) // <= id PROG> + if (params==2 && p[1]=="PROG"_hk) // <= id PROG> return setTrackMode(p[0],TRACK_MODE_PROG); #endif @@ -402,20 +406,27 @@ bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[]) return setTrackMode(p[0],TRACK_MODE_EXT); #ifdef BOOSTER_INPUT if (TRACK_MODE_BOOST != 0 && // compile time optimization - params==2 && p[1]=="BOOST"_hk) // <= id BOOST> + params==2 && p[1]=="BOOST"_hk) // <= id BOOST> return setTrackMode(p[0],TRACK_MODE_BOOST); + if (TRACK_MODE_BOOST_INV != 0 && // compile time optimization + params==2 && p[1]=="BOOST_INV"_hk) // <= id BOOST_INV> + return setTrackMode(p[0],TRACK_MODE_BOOST_INV); + if (TRACK_MODE_BOOST_AUTO != 0 && // compile time optimization + params==2 && p[1]=="BOOST_AUTO"_hk) // <= id BOOST_AUTO> + return setTrackMode(p[0],TRACK_MODE_BOOST_AUTO); #endif - if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO> - return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV); + if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO> + return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_AUTO); - if (params==2 && p[1]=="INV"_hk) // <= id INV> - return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV); + if (params==2 && p[1]=="INV"_hk) // <= id INV> + return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_INV); - if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab> + if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab> return setTrackMode(p[0],TRACK_MODE_DC,p[2]); - if (params==3 && p[1]=="DCX"_hk && p[2]>0) // <= id DCX cab> - return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]); + if (params==3 && (p[1]=="DC_INV"_hk || // <= id DC_INV cab> + p[1]=="DCX"_hk) && p[2]>0) // <= id DCX cab> + return setTrackMode(p[0],TRACK_MODE_DC_INV,p[2]); return false; } @@ -424,9 +435,9 @@ const FSH* TrackManager::getModeName(TRACK_MODE tm) { const FSH *modename=F("---"); if (tm & TRACK_MODE_MAIN) { - if(tm & TRACK_MODE_AUTOINV) + if(tm & TRACK_MODIFIER_AUTO) modename=F("MAIN A"); - else if (tm & TRACK_MODE_INV) + else if (tm & TRACK_MODIFIER_INV) modename=F("MAIN I>\n"); else modename=F("MAIN"); @@ -440,15 +451,15 @@ const FSH* TrackManager::getModeName(TRACK_MODE tm) { else if(tm & TRACK_MODE_EXT) modename=F("EXT"); else if(tm & TRACK_MODE_BOOST) { - if(tm & TRACK_MODE_AUTOINV) + if(tm & TRACK_MODIFIER_AUTO) modename=F("BOOST A"); - else if (tm & TRACK_MODE_INV) + else if (tm & TRACK_MODIFIER_INV) modename=F("BOOST I"); else modename=F("BOOST"); } else if (tm & TRACK_MODE_DC) { - if (tm & TRACK_MODE_INV) + if (tm & TRACK_MODIFIER_INV) modename=F("DCX"); else modename=F("DC"); From 0e99ad143b201d0114e42ff30584facd0f550690 Mon Sep 17 00:00:00 2001 From: Harald Barth Date: Mon, 30 Sep 2024 10:07:07 +0200 Subject: [PATCH 3/7] version 5.2.82 --- GITHUB_SHA.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/GITHUB_SHA.h b/GITHUB_SHA.h index c9bb3bb..f1dae0d 100644 --- a/GITHUB_SHA.h +++ b/GITHUB_SHA.h @@ -1 +1 @@ -#define GITHUB_SHA "devel-202409221903Z" +#define GITHUB_SHA "devel-202409300806Z" diff --git a/version.h b/version.h index 269e47d..c6d0556 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.81" +#define VERSION "5.2.82" +// 5.2.82 - TrackManager and EXRAIL: Introduce more consistent names for <= ...> and SET_TRACK // 5.2.81 - STM32 Ethernet boards support, also now have specific EX8874 motor driver definition // 5.2.80 - EthernetInterface upgrade, including STM32 Ethernet support // 5.2.79 - serial manager loop that handles quoted strings From 33c8ed19a9a5f6f459e754836e688ec55200769b Mon Sep 17 00:00:00 2001 From: pmantoine Date: Mon, 30 Sep 2024 16:13:00 +0800 Subject: [PATCH 4/7] Various STM32 related fixes --- DCCTimerSTM32.cpp | 8 ++++---- I2CManager_STM32.h | 4 ++-- TrackManager.cpp | 16 ++++++++-------- WifiInterface.cpp | 3 ++- version.h | 3 ++- 5 files changed, 18 insertions(+), 16 deletions(-) diff --git a/DCCTimerSTM32.cpp b/DCCTimerSTM32.cpp index 0917455..df00df3 100644 --- a/DCCTimerSTM32.cpp +++ b/DCCTimerSTM32.cpp @@ -70,9 +70,9 @@ HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 - F446RE defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI) // Nucleo-144 boards don't have Serial1 defined by default HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6 -HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 -#if !defined(ARDUINO_NUCLEO_F412ZG) - HardwareSerial Serial2(PD6, PD5); // Rx=PD6, Tx=PD5 -- UART5 +HardwareSerial Serial2(PD6, PD5); // Rx=PD6, Tx=PD5 -- UART2 +#if !defined(ARDUINO_NUCLEO_F412ZG) // F412ZG does not have UART5 + HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 #endif // Serial3 is defined to use USART3 by default, but is in fact used as the diag console // via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc. @@ -328,7 +328,7 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency if (pin_timer[pin] != NULL) { pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle - DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d, frequency %d"), pin, pin_channel[pin], frequency); + DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer Channel %d, frequency %d"), pin, pin_channel[pin], frequency); } else DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!")); diff --git a/I2CManager_STM32.h b/I2CManager_STM32.h index 109c89b..45c924f 100644 --- a/I2CManager_STM32.h +++ b/I2CManager_STM32.h @@ -1,5 +1,5 @@ /* - * © 2022-23 Paul M Antoine + * © 2022-24 Paul M Antoine * © 2023, Neil McKechnie * All rights reserved. * @@ -185,7 +185,7 @@ void I2CManagerClass::I2C_init() GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode GPIOB->PUPDR &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all PUPDR bits for PB8 and PB9 - GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability + // GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability // Alt Function High register routing pins PB8 and PB9 for I2C1: // Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8 // Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9 diff --git a/TrackManager.cpp b/TrackManager.cpp index a164142..c6a30ad 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -151,8 +151,8 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTD(shadowPORTD=PORTD); HAVE_PORTE(shadowPORTE=PORTE); HAVE_PORTF(shadowPORTF=PORTF); - HAVE_PORTG(shadowPORTF=PORTG); - HAVE_PORTH(shadowPORTF=PORTH); + HAVE_PORTG(shadowPORTG=PORTG); + HAVE_PORTH(shadowPORTH=PORTH); APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); @@ -160,8 +160,8 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTD(PORTD=shadowPORTD); HAVE_PORTE(PORTE=shadowPORTE); HAVE_PORTF(PORTF=shadowPORTF); - HAVE_PORTG(shadowPORTF=PORTG); - HAVE_PORTH(shadowPORTF=PORTH); + HAVE_PORTG(shadowPORTG=PORTG); + HAVE_PORTH(shadowPORTH=PORTH); } // setPROGSignal(), called from interrupt context @@ -173,8 +173,8 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTD(shadowPORTD=PORTD); HAVE_PORTE(shadowPORTE=PORTE); HAVE_PORTF(shadowPORTF=PORTF); - HAVE_PORTG(shadowPORTF=PORTG); - HAVE_PORTH(shadowPORTF=PORTH); + HAVE_PORTG(shadowPORTG=PORTG); + HAVE_PORTH(shadowPORTH=PORTH); APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on)); HAVE_PORTA(PORTA=shadowPORTA); HAVE_PORTB(PORTB=shadowPORTB); @@ -182,8 +182,8 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTD(PORTD=shadowPORTD); HAVE_PORTE(PORTE=shadowPORTE); HAVE_PORTF(PORTF=shadowPORTF); - HAVE_PORTG(shadowPORTF=PORTG); - HAVE_PORTH(shadowPORTF=PORTH); + HAVE_PORTG(shadowPORTG=PORTG); + HAVE_PORTH(shadowPORTH=PORTH); } // setDCSignal(), called from normal context diff --git a/WifiInterface.cpp b/WifiInterface.cpp index fcf9932..6b6ec4a 100644 --- a/WifiInterface.cpp +++ b/WifiInterface.cpp @@ -72,8 +72,9 @@ Stream * WifiInterface::wifiStream; #elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) \ || defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG) \ || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI) -#define NUM_SERIAL 2 +#define NUM_SERIAL 3 #define SERIAL1 Serial6 +#define SERIAL3 Serial2 #else #warning This variant of Nucleo not yet explicitly supported #endif diff --git a/version.h b/version.h index c6d0556..a955a86 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.82" +#define VERSION "5.2.83" +// 5.2.83 - Various STM32 related fixes for serial ports, I2C pullups now turned off, and shadowing of PORTG/PORTH for TrackManager now correct // 5.2.82 - TrackManager and EXRAIL: Introduce more consistent names for <= ...> and SET_TRACK // 5.2.81 - STM32 Ethernet boards support, also now have specific EX8874 motor driver definition // 5.2.80 - EthernetInterface upgrade, including STM32 Ethernet support From f5014f5595207a08d2b865aede791930a73c0717 Mon Sep 17 00:00:00 2001 From: pmantoine Date: Thu, 3 Oct 2024 09:28:55 +0800 Subject: [PATCH 5/7] Further STM32 TrackManager fix --- TrackManager.cpp | 8 ++++---- version.h | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/TrackManager.cpp b/TrackManager.cpp index c6a30ad..6b37cef 100644 --- a/TrackManager.cpp +++ b/TrackManager.cpp @@ -160,8 +160,8 @@ void TrackManager::setDCCSignal( bool on) { HAVE_PORTD(PORTD=shadowPORTD); HAVE_PORTE(PORTE=shadowPORTE); HAVE_PORTF(PORTF=shadowPORTF); - HAVE_PORTG(shadowPORTG=PORTG); - HAVE_PORTH(shadowPORTH=PORTH); + HAVE_PORTG(PORTG=shadowPORTG); + HAVE_PORTH(PORTH=shadowPORTH); } // setPROGSignal(), called from interrupt context @@ -182,8 +182,8 @@ void TrackManager::setPROGSignal( bool on) { HAVE_PORTD(PORTD=shadowPORTD); HAVE_PORTE(PORTE=shadowPORTE); HAVE_PORTF(PORTF=shadowPORTF); - HAVE_PORTG(shadowPORTG=PORTG); - HAVE_PORTH(shadowPORTH=PORTH); + HAVE_PORTG(PORTG=shadowPORTG); + HAVE_PORTH(PORTH=shadowPORTH); } // setDCSignal(), called from normal context diff --git a/version.h b/version.h index a955a86..749227b 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.83" +#define VERSION "5.2.84" +// 5.2.84 - Fix TrackManager setDCCSignal and setPROGSignal for STM32 shadowing of PORTG/PORTH - this time it really is correct! // 5.2.83 - Various STM32 related fixes for serial ports, I2C pullups now turned off, and shadowing of PORTG/PORTH for TrackManager now correct // 5.2.82 - TrackManager and EXRAIL: Introduce more consistent names for <= ...> and SET_TRACK // 5.2.81 - STM32 Ethernet boards support, also now have specific EX8874 motor driver definition From fa00e9e11b94a79195c712390c01b44e239c40bb Mon Sep 17 00:00:00 2001 From: Asbelos Date: Thu, 10 Oct 2024 19:38:35 +0100 Subject: [PATCH 6/7] Squashed commit of the following: commit f13824164b853b2fec66f67b7f3296e2264a861b Author: Asbelos Date: Thu Oct 10 16:07:42 2024 +0100 _s7 keyword generator commit 8a7dc2643cd15a6d55dd1f257c4940d4167702fc Author: Asbelos Date: Mon Oct 7 10:54:05 2024 +0100 comments commit 801cddfef70a5ced01da9f022d4b0ad603264e0a Author: Asbelos Date: Sun Oct 6 13:24:07 2024 +0100 simpler macro insert commit 5883f474ee79274aaeca94aee13b2235accddbd6 Author: Asbelos Date: Sun Oct 6 13:18:29 2024 +0100 Auto include commit 312fc255e4eb8aa4f77dcedbe7206aca22e00af6 Author: Asbelos Date: Sun Oct 6 13:12:51 2024 +0100 Cleanup to one class commit 309407434928f14f22fef1ff2cdd0f29238f33fb Author: Asbelos Date: Sun Oct 6 10:34:16 2024 +0100 peeled back commit aa2a6ad1191dbcd6ed75416d457e47b68373277d Author: Asbelos Date: Sat Oct 5 18:27:43 2024 +0100 all fastpins commit 931baf4b6d4d5b4d55389a64efd5885eeb2218ff Author: Asbelos Date: Sat Oct 5 16:28:03 2024 +0100 Partial lib extract commit 47bc3b55fc2259cbec9ab830850ea07044ef66be Author: Asbelos Date: Fri Oct 4 15:41:51 2024 +0100 fixes and SEG7 macro commit 3f26ca2d1a39203428de60cb27f31008847c4233 Author: Asbelos Date: Fri Oct 4 14:33:23 2024 +0100 enums for exrail easy commit 7e7c00594bf34ebfc331485befc9feaf66cc32ff Author: Asbelos Date: Fri Oct 4 13:16:57 2024 +0100 Working commit fc4df878482ee3eca6e1232a1ed49ac0c06d2a55 Author: Asbelos Date: Fri Oct 4 09:27:46 2024 +0100 leds and buttons --- EXRAILMacros.h | 4 + IODevice.h | 2 +- IO_TM1638.cpp | 217 ++++++++++++++++++++++++++++++++++++++++ IO_TM1638.h | 134 +++++++++++++++++++++++++ KeywordHasher.h | 39 ++++++++ Release_Notes/TM1638.md | 84 ++++++++++++++++ 6 files changed, 479 insertions(+), 1 deletion(-) create mode 100644 IO_TM1638.cpp create mode 100644 IO_TM1638.h create mode 100644 Release_Notes/TM1638.md diff --git a/EXRAILMacros.h b/EXRAILMacros.h index 7fcfc73..b2ee920 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -63,6 +63,10 @@ // playing sounds with IO_I2CDFPlayer #define PLAYSOUND ANOUT +// SEG7 is a helper to create ANOUT from a 7-segment requets +#define SEG7(vpin,value,format) \ + ANOUT(vpin,(value & 0xFFFF),TM1638::DF_##format,((uint32_t)value)>>16) + // helper macro to strip leading zeros off time inputs // (10#mins)%100) #define STRIP_ZERO(value) 10##value%100 diff --git a/IODevice.h b/IODevice.h index 05df4df..09fd08d 100644 --- a/IODevice.h +++ b/IODevice.h @@ -570,6 +570,6 @@ protected: #include "IO_EncoderThrottle.h" #include "IO_TCA8418.h" #include "IO_NeoPixel.h" - +#include "IO_TM1638.h" #endif // iodevice_h diff --git a/IO_TM1638.cpp b/IO_TM1638.cpp new file mode 100644 index 0000000..45614f6 --- /dev/null +++ b/IO_TM1638.cpp @@ -0,0 +1,217 @@ +/* + * © 2024, Chris Harlow. All rights reserved. + * + * This file is part of DCC++EX API + * + * 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 . + */ + +/* Credit to https://github.com/dvarrel/TM1638 for the basic formulae.*/ + + +#include +#include "IODevice.h" +#include "DIAG.h" + + +const uint8_t HIGHFLASH _digits[16]={ + 0b00111111,0b00000110,0b01011011,0b01001111, + 0b01100110,0b01101101,0b01111101,0b00000111, + 0b01111111,0b01101111,0b01110111,0b01111100, + 0b00111001,0b01011110,0b01111001,0b01110001 + }; + + // Constructor + TM1638::TM1638(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin){ + _firstVpin = firstVpin; + _nPins = 8; + _clk_pin = clk_pin; + _stb_pin = stb_pin; + _dio_pin = dio_pin; + pinMode(clk_pin,OUTPUT); + pinMode(stb_pin,OUTPUT); + pinMode(dio_pin,OUTPUT); + _pulse = PULSE1_16; + + _buttons=0; + _leds=0; + _lastLoop=micros(); + addDevice(this); + } + + + void TM1638::create(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin) { + if (checkNoOverlap(firstVpin,8)) + new TM1638(firstVpin, clk_pin,dio_pin,stb_pin); + } + + void TM1638::_begin() { + displayClear(); + test(); + _display(); + } + + + void TM1638::_loop(unsigned long currentMicros) { + if (currentMicros - _lastLoop > (1000000UL/LoopHz)) { + _buttons=getButtons();// Read the buttons + _lastLoop=currentMicros; + } + } + + void TM1638::_display() { + DIAG(F("TM1638 Configured on Vpins:%u-%u"), _firstVpin, _firstVpin+_nPins-1); + } + +// digital read gets button state +int TM1638::_read(VPIN vpin) { + byte pin=vpin - _firstVpin; + bool result=bitRead(_buttons,pin); + // DIAG(F("TM1638 read (%d) buttons %x = %d"),pin,_buttons,result); + return result; +} + +// digital write sets led state +void TM1638::_write(VPIN vpin, int value) { + // TODO.. skip if no state change + writeLed(vpin - _firstVpin + 1,value!=0); + } + +// Analog write sets digit displays + +void TM1638::_writeAnalogue(VPIN vpin, int lowBytes, uint8_t mode, uint16_t highBytes) { + // mode is in DataFormat defined above. + byte formatLength=mode & 0x0F; // last 4 bits + byte formatType=mode & 0xF0; // + int8_t leftDigit=vpin-_firstVpin; // 0..7 from left + int8_t rightDigit=leftDigit+formatLength-1; // 0..7 from left + + // loading is done right to left startDigit first + int8_t startDigit=7-rightDigit; // reverse as 7 on left + int8_t lastDigit=7-leftDigit; // reverse as 7 on left + uint32_t value=highBytes; + value<<=16; + value |= (uint16_t)lowBytes; + + //DIAG(F("TM1638 fl=%d ft=%x sd=%d ld=%d v=%l vx=%X"), + // formatLength,formatType,startDigit,lastDigit,value,value); + while(startDigit<=lastDigit) { + switch (formatType) { + case _DF_DECIMAL:// decimal (leading zeros) + displayDig(startDigit,GETHIGHFLASH(_digits,(value%10))); + value=value/10; + break; + case _DF_HEX:// HEX (leading zeros) + displayDig(startDigit,GETHIGHFLASH(_digits,(value & 0x0F))); + value>>=4; + break; + case _DF_RAW:// Raw 7-segment pattern + displayDig(startDigit,value & 0xFF); + value>>=8; + break; + default: + DIAG(F("TM1368 invalid mode 0x%x"),mode); + return; + } + startDigit++; + } +} + +uint8_t TM1638::getButtons(){ + ArduinoPins::fastWriteDigital(_stb_pin, LOW); + writeData(INSTRUCTION_READ_KEY); + pinMode(_dio_pin, INPUT); + ArduinoPins::fastWriteDigital(_clk_pin, LOW); + uint8_t buttons=0; + for (uint8_t eachByte=0; eachByte<4;eachByte++) { + uint8_t value = 0; + for (uint8_t eachBit = 0; eachBit < 8; eachBit++) { + ArduinoPins::fastWriteDigital(_clk_pin, HIGH); + value |= ArduinoPins::fastReadDigital(_dio_pin) << eachBit; + ArduinoPins::fastWriteDigital(_clk_pin, LOW); + } + buttons |= value << eachByte; + delayMicroseconds(1); + } + pinMode(_dio_pin, OUTPUT); + ArduinoPins::fastWriteDigital(_stb_pin, HIGH); + return buttons; +} + + +void TM1638::displayDig(uint8_t digitId, uint8_t pgfedcba){ + if (digitId>7) return; + setDataInstruction(DISPLAY_TURN_ON | _pulse); + setDataInstruction(INSTRUCTION_WRITE_DATA| INSTRUCTION_ADDRESS_FIXED); + writeDataAt(FIRST_DISPLAY_ADDRESS+14-(digitId*2), pgfedcba); +} + +void TM1638::displayClear(){ + setDataInstruction(DISPLAY_TURN_ON | _pulse); + setDataInstruction(INSTRUCTION_WRITE_DATA | INSTRUCTION_ADDRESS_FIXED); + for (uint8_t i=0;i<15;i+=2){ + writeDataAt(FIRST_DISPLAY_ADDRESS+i,0x00); + } +} + +void TM1638::writeLed(uint8_t num,bool state){ + if ((num<1) | (num>8)) return; + setDataInstruction(DISPLAY_TURN_ON | _pulse); + setDataInstruction(INSTRUCTION_WRITE_DATA | INSTRUCTION_ADDRESS_FIXED); + writeDataAt(FIRST_DISPLAY_ADDRESS + (num*2-1), state); +} + + +void TM1638::writeData(uint8_t data){ + for (uint8_t i = 0; i < 8; i++) { + ArduinoPins::fastWriteDigital(_dio_pin, data & 1); + data >>= 1; + ArduinoPins::fastWriteDigital(_clk_pin, HIGH); + ArduinoPins::fastWriteDigital(_clk_pin, LOW); + } +} + +void TM1638::writeDataAt(uint8_t displayAddress, uint8_t data){ + ArduinoPins::fastWriteDigital(_stb_pin, LOW); + writeData(displayAddress); + writeData(data); + ArduinoPins::fastWriteDigital(_stb_pin, HIGH); + delayMicroseconds(1); +} + +void TM1638::setDataInstruction(uint8_t dataInstruction){ + ArduinoPins::fastWriteDigital(_stb_pin, LOW); + writeData(dataInstruction); + ArduinoPins::fastWriteDigital(_stb_pin, HIGH); + delayMicroseconds(1); +} + +void TM1638::test(){ + DIAG(F("TM1638 test")); + uint8_t val=0; + for(uint8_t i=0;i<5;i++){ + setDataInstruction(DISPLAY_TURN_ON | _pulse); + setDataInstruction(INSTRUCTION_WRITE_DATA| INSTRUCTION_ADDRESS_AUTO); + ArduinoPins::fastWriteDigital(_stb_pin, LOW); + writeData(FIRST_DISPLAY_ADDRESS); + for(uint8_t i=0;i<16;i++) + writeData(val); + ArduinoPins::fastWriteDigital(_stb_pin, HIGH); + delay(1000); + val = ~val; + } + +} + + diff --git a/IO_TM1638.h b/IO_TM1638.h new file mode 100644 index 0000000..9907e6a --- /dev/null +++ b/IO_TM1638.h @@ -0,0 +1,134 @@ + /* + * © 2024, Chris Harlow. All rights reserved. + * + * This file is part of DCC++EX API + * + * 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 . + */ + +#ifndef IO_TM1638_h +#define IO_TM1638_h +#include +#include "IODevice.h" +#include "DIAG.h" + +class TM1638 : public IODevice { +private: + + uint8_t _buttons; + uint8_t _leds; + unsigned long _lastLoop; + static const int LoopHz=20; + + static const byte + INSTRUCTION_WRITE_DATA=0x40, + INSTRUCTION_READ_KEY=0x42, + INSTRUCTION_ADDRESS_AUTO=0x40, + INSTRUCTION_ADDRESS_FIXED=0x44, + INSTRUCTION_NORMAL_MODE=0x40, + INSTRUCTION_TEST_MODE=0x48, + + FIRST_DISPLAY_ADDRESS=0xC0, + + DISPLAY_TURN_OFF=0x80, + DISPLAY_TURN_ON=0x88; + + + uint8_t _clk_pin; + uint8_t _stb_pin; + uint8_t _dio_pin; + uint8_t _pulse; + bool _isOn; + + + // Constructor + TM1638(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin); + +public: + enum DigitFormat : byte { + // last 4 bits are length. + // DF_1.. DF_8 decimal + DF_1=0x01,DF_2=0x02,DF_3=0x03,DF_4=0x04, + DF_5=0x05,DF_6=0x06,DF_7=0x07,DF_8=0x08, + // DF_1X.. DF_8X HEX + DF_1X=0x11,DF_2X=0x12,DF_3X=0x13,DF_4X=0x14, + DF_5X=0x15,DF_6X=0x16,DF_7X=0x17,DF_8X=0x18, + // DF_1R .. DF_4R raw 7 segmnent data + // only 4 because HAL analogWrite only passes 4 bytes + DF_1R=0x21,DF_2R=0x22,DF_3R=0x23,DF_4R=0x24, + + // bits of data conversion type (ored with length) + _DF_DECIMAL=0x00,// right adjusted decimal unsigned leading zeros + _DF_HEX=0x10, // right adjusted hex leading zeros + _DF_RAW=0x20 // bytes are raw 7-segment pattern (max length 4) + }; + + static void create(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin); + + // Functions overridden in IODevice + void _begin(); + void _loop(unsigned long currentMicros) override ; + void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override; + void _display() override ; + int _read(VPIN pin) override; + void _write(VPIN pin,int value) override; + + // Device driving functions + private: + enum pulse_t { + PULSE1_16, + PULSE2_16, + PULSE4_16, + PULSE10_16, + PULSE11_16, + PULSE12_16, + PULSE13_16, + PULSE14_16 + }; + + /** + * @fn getButtons + * @return state of 8 buttons + */ + uint8_t getButtons(); + + /** + * @fn writeLed + * @brief put led ON or OFF + * @param num num of led(1-8) + * @param state (true or false) + */ + void writeLed(uint8_t num, bool state); + + + /** + * @fn displayDig + * @brief set 7 segment display + dot + * @param digitId num of digit(0-7) + * @param val value 8 bits + */ + void displayDig(uint8_t digitId, uint8_t pgfedcba); + + /** + * @fn displayClear + * @brief switch off all leds and segment display + */ + void displayClear(); + void test(); + void writeData(uint8_t data); + void writeDataAt(uint8_t displayAddress, uint8_t data); + void setDisplayMode(uint8_t displayMode); + void setDataInstruction(uint8_t dataInstruction); +}; +#endif diff --git a/KeywordHasher.h b/KeywordHasher.h index 4b1e0fb..fe85eb4 100644 --- a/KeywordHasher.h +++ b/KeywordHasher.h @@ -54,4 +54,43 @@ static_assert("MAIN"_hk == 11339,"Keyword hasher error"); static_assert("SLOW"_hk == -17209,"Keyword hasher error"); static_assert("SPEED28"_hk == -17064,"Keyword hasher error"); static_assert("SPEED128"_hk == 25816,"Keyword hasher error"); + +// Compile time converter from "abcd"_s7 to the 7 segment nearest equivalent + +constexpr uint8_t seg7Digits[]={ + 0b00111111,0b00000110,0b01011011,0b01001111, // 0..3 + 0b01100110,0b01101101,0b01111101,0b00000111, // 4..7 + 0b01111111,0b01101111 // 8..9 + }; + +constexpr uint8_t seg7Letters[]={ + 0b01110111,0b01111100,0b00111001,0b01011110, // ABCD + 0b01111001,0b01110001,0b00111101,0b01110110, // EFGH + 0b00000100,0b00011110,0b01110010,0b00111000, //IJKL + 0b01010101,0b01010100,0b01011100,0b01110011, // MNOP + 0b10111111,0b01010000,0b01101101,0b01111000, // QRST + 0b00111110,0b00011100,0b01101010,0b01001001, //UVWX + 0b01100110,0b01011011 //YZ + }; +constexpr uint8_t seg7Space=0b00000000; +constexpr uint8_t seg7Minus=0b01000000; +constexpr uint8_t seg7Equals=0b01001000; + + +constexpr uint32_t CompiletimeSeg7(const char * sv, uint32_t running, size_t rlen) { + return (*sv==0 || rlen==0) ? running << (8*rlen) : CompiletimeSeg7(sv+1, + (*sv >= '0' && *sv <= '9') ? (running<<8) | seg7Digits[*sv-'0'] : + (*sv >= 'A' && *sv <= 'Z') ? (running<<8) | seg7Letters[*sv-'A'] : + (*sv >= 'a' && *sv <= 'z') ? (running<<8) | seg7Letters[*sv-'a'] : + (*sv == '-') ? (running<<8) | seg7Minus : + (*sv == '=') ? (running<<8) | seg7Equals : + (running<<8) | seg7Space, + rlen-1 + ); // +} + +constexpr uint32_t operator""_s7(const char * keyword, size_t len) +{ + return CompiletimeSeg7(keyword,0*len,4); +} #endif \ No newline at end of file diff --git a/Release_Notes/TM1638.md b/Release_Notes/TM1638.md new file mode 100644 index 0000000..adec7ae --- /dev/null +++ b/Release_Notes/TM1638.md @@ -0,0 +1,84 @@ +## TM1638 ## + +The TM1638 board provides a very cheap way of implementing 8 buttons, 8 leds and an 8 digit 7segment display in a package requiring just 5 Dupont wires (vcc, gnd + 3 GPIO pins) from the command station without soldering. + + +This is ideal for prototyping and testing, simulating sensors and signals, displaying states etc. For a built layout, this could provide a control for things that are not particularly suited to throttle 'route' buttons, perhaps lineside automations or fiddle yard lane selection. + +By adding a simple HAL statement to myAutomation.h it creates 8 buttons/sensors and 8 leds. + +`HAL(TM1638,500,29,31,33)` +Creates VPINs 500-507 And desscribes the GPIO pins used to connect the clk,dio,stb pins on the TM1638 board. + +Setting each of the VPINs will control the associated LED (using for example SET, RESET or BLINK in Exrail or ` from a command). + +Unlike most pins, you can also read the same pin number and get the button state, using Exrail IF/AT/ONBUTTON etc. + +For example: +` +HAL(TM1638,500,29,31,33) +` +All the folowing examples assume you are using VPIN 500 as the first, leftmost, led/button on the TM1638 board. + + +`ONBUTTON(500) + SET(500) // light the first led + BLINK(501,500,500) // blink the second led + SETLOCO(3) FWD(50) // set a loco going + AT(501) STOP // press second button to stop + RESET(500) RESET(501) // turn leds off + DONE +` + +Buttons behave like any other sensor, so using `` will cause the command station to issue `` and `` messages when the first button is pressed or released. + +Exrail `JMRI_SENSOR(500,8)` will create ` Date: Thu, 10 Oct 2024 19:42:27 +0100 Subject: [PATCH 7/7] TM1638 support --- EXRAILMacros.h | 2 +- version.h | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/EXRAILMacros.h b/EXRAILMacros.h index b2ee920..b7f6b0e 100644 --- a/EXRAILMacros.h +++ b/EXRAILMacros.h @@ -63,7 +63,7 @@ // playing sounds with IO_I2CDFPlayer #define PLAYSOUND ANOUT -// SEG7 is a helper to create ANOUT from a 7-segment requets +// SEG7 is a helper to create ANOUT from a 7-segment request #define SEG7(vpin,value,format) \ ANOUT(vpin,(value & 0xFFFF),TM1638::DF_##format,((uint32_t)value)>>16) diff --git a/version.h b/version.h index 749227b..e6d43d3 100644 --- a/version.h +++ b/version.h @@ -3,7 +3,8 @@ #include "StringFormatter.h" -#define VERSION "5.2.84" +#define VERSION "5.2.85" +// 5.2.85 - IO_TM1638 driver, SEG7 Exrail macro and _s7 segment pattern generator. // 5.2.84 - Fix TrackManager setDCCSignal and setPROGSignal for STM32 shadowing of PORTG/PORTH - this time it really is correct! // 5.2.83 - Various STM32 related fixes for serial ports, I2C pullups now turned off, and shadowing of PORTG/PORTH for TrackManager now correct // 5.2.82 - TrackManager and EXRAIL: Introduce more consistent names for <= ...> and SET_TRACK