1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-31 03:13:45 +02:00

Compare commits

...

142 Commits

Author SHA1 Message Date
pmantoine
a894ebbdaa Tidy up WiFiNINA #ifdef 2023-11-23 15:36:14 +08:00
Ash-4
5bc677e566 Merge pull request #364 from travis-farmer/devel-giga
Giga onboard Wifi, and signal updates. from Travis Farmer
2023-11-21 12:26:22 -06:00
travis-farmer
d690b09bb0 parse "@" 2023-11-20 03:52:11 -05:00
travis-farmer
5906735c56 patching in PR # 366 2023-11-19 16:55:06 -05:00
travis-farmer
1a4fff8924 adding more locos for Giga 2023-11-19 15:27:26 -05:00
travis-farmer
5aca3a62d8 solved WiFiNINA reset/restart bug 2023-11-19 03:09:53 -05:00
travis-farmer
1d881a4b43 cleaning up 2023-11-18 14:07:19 -05:00
travis-farmer
330bdf58a1 works, but uses multi connects per client 2023-11-18 13:43:31 -05:00
travis-farmer
7f430ce6bd current 2023-11-18 13:10:29 -05:00
travis-farmer
961470302d getting rid of a comment 2023-11-16 09:40:08 -05:00
travis-farmer
35d1247240 commit current 2023-11-14 16:27:21 -05:00
travis-farmer
83a22dfae5 12-bit analog, vs just 10-bit 2023-11-12 04:44:59 -05:00
travis-farmer
98f00300ec removed I2C port change option as it is breaking 2023-11-10 15:09:56 -05:00
travis-farmer
9a6a305fc5 so far, 8 good signals, one (tested) is DC 2023-11-10 14:24:14 -05:00
travis-farmer
904fd5a780 possible fix for 4+ tracks 2023-11-10 13:36:40 -05:00
travis-farmer
40c0c20df8 Post tests - PASSED 2023-11-10 10:46:14 -05:00
travis-farmer
36db724466 fixed Typo 2023-11-10 09:17:39 -05:00
travis-farmer
5c0ab9a311 pre-test storing of signal updates 2023-11-10 08:56:21 -05:00
travis-farmer
c458e98cd3 digitalPinToPort back from arduino core 2023-11-08 13:40:39 -05:00
travis-farmer
f254643a1d added note about SystemCoreClock 2023-11-07 16:37:38 -05:00
travis-farmer
8223d30892 added XGIGA config for giga experimental 2023-11-07 14:30:52 -05:00
travis-farmer
6b731d6f5d fixed timers, still only two tracks 2023-11-07 13:12:33 -05:00
travis-farmer
832ec44c67 clean up IP reading 2023-11-07 06:37:17 -05:00
travis-farmer
4430e9acdc committing to save, see pull comment 2023-11-06 09:46:58 -05:00
travis-farmer
e6797d1095 Giga wifi works for 3 clients MAX 2023-11-04 07:36:23 -04:00
travis-farmer
8a813c2a1e updating current, still crashes 2023-11-03 14:11:55 -04:00
travis-farmer
050fed6e9a crashes 2023-11-03 13:40:48 -04:00
travis-farmer
741771c4fe added diags for debug tracking... 2023-11-03 11:49:25 -04:00
travis-farmer
b632088b19 cleaning... 2023-11-03 09:06:42 -04:00
travis-farmer
d95d9c193e allows client, but immidiatly drops client 2023-11-03 07:48:12 -04:00
travis-farmer
05db1bdd90 cleaning 2023-11-03 07:34:05 -04:00
travis-farmer
b1f5c34ef2 compiles, but crashes on client connect 2023-11-03 07:17:17 -04:00
travis-farmer
701bc0a837 keeping up to date - cleaning 2023-11-02 14:00:02 -04:00
travis-farmer
dfa798c149 seems to work now 2023-11-02 09:12:31 -04:00
pmantoine
d46a6f092a Giga Wifi driver, based on WifiNINA driver 2023-11-01 17:49:24 +08:00
peteGSX
d0759e97fd Merge pull request #363 from travis-farmer:devel_Giga
Devel giga
2023-11-01 10:37:34 +10:00
travis-farmer
22323ea065 adjusted to allow user to decide which I2C to use 2023-10-31 14:26:58 -04:00
travis-farmer
13bb1175f3 added external EEPROM support 2023-10-31 09:47:29 -04:00
Travis Farmer
2f72a3dd57 Delete IO_CMRI.h 2023-10-30 15:37:23 -04:00
Travis Farmer
141b46f09e Delete IO_CMRI.cpp 2023-10-30 15:37:02 -04:00
travis-farmer
10a4d1632a including timer library into code source 2023-10-28 15:02:55 -04:00
Harald Barth
33b2820095 Bugfix version detection logic and better message 2023-10-28 19:21:29 +02:00
travis-farmer
b81b7ee27f adding some final comments, already checked build 2023-10-27 11:11:21 -04:00
travis-farmer
d52f422f05 adding copywrite info 2023-10-27 10:59:09 -04:00
travis-farmer
6ec16c94d7 removed last DIAG i entered, off to test I2C 2023-10-27 08:47:24 -04:00
travis-farmer
a7a1daf5b4 cleaning out a DIAG 2023-10-27 03:51:59 -04:00
travis-farmer
b98ddf7886 better mac address 2023-10-27 03:43:41 -04:00
travis-farmer
f2c9b5a496 I2C left to test, all so far works well 2023-10-26 17:36:35 -04:00
travis-farmer
88f1c0c580 needed for wifi on giga, added serial ports 2023-10-24 18:35:15 -04:00
travis-farmer
8bbe30e789 maybe it all works 2023-10-24 10:11:55 -04:00
travis-farmer
84b6207988 changine timers to match 2023-10-24 07:09:30 -04:00
travis-farmer
80365c214e ...forgot to clen something out... 2023-10-23 17:47:40 -04:00
travis-farmer
e54cd0919c lap test pass, bench test tomorrow 2023-10-23 17:41:45 -04:00
travis-farmer
0a63befee9 doesn't work, gonna commit before i try a new path 2023-10-23 15:50:02 -04:00
travis-farmer
bbca4379d2 sorta works, but no adc yet 2023-10-23 14:38:29 -04:00
travis-farmer
dff7eb37ab works, so far 2023-10-23 10:30:04 -04:00
travis-farmer
2edc223beb cleaning up some comments 2023-10-23 08:06:31 -04:00
travis-farmer
38f4d5f9d9 don't see why ADCee fails 2023-10-23 07:29:37 -04:00
Harald Barth
7b3b16b211 Divide out C for config and D for diag commands 2023-10-23 11:45:52 +02:00
travis-farmer
decebd1802 getting up to date, in case i abandon 2023-10-22 17:24:25 -04:00
travis-farmer
f1aace96d5 haba's changes 2023-10-22 11:43:48 -04:00
travis-farmer
169050853a possible HA DCC waveform running on pins 8 and 9 2023-10-22 11:19:07 -04:00
travis-farmer
4bc7c2632a fixed freeMemory, though now reads in Kb for giga 2023-10-22 08:16:34 -04:00
travis-farmer
1745fa72db using a bunch of memory? 2023-10-21 15:57:50 -04:00
travis-farmer
f88c617dbe up to date, no signal still 2023-10-21 14:17:56 -04:00
travis-farmer
70c1f1db2a compiles without error, nogo <1> 2023-10-21 13:40:44 -04:00
travis-farmer
e816ef2b03 Portenta_H7_TimerInterrupt compiles in, but crash 2023-10-21 11:55:21 -04:00
travis-farmer
a84eba7ab6 fixing a few stale placeholders 2023-10-21 06:45:52 -04:00
travis-farmer
be0471679d compiles, crashes with JMRI 2023-10-21 06:16:46 -04:00
travis-farmer
706076e4f1 commit, but i think board is bricked 2023-10-21 05:38:13 -04:00
travis-farmer
7259924466 It Compiles! 2023-10-21 04:10:50 -04:00
travis-farmer
917fb569ba cleaning out unused include 2023-10-20 17:11:09 -04:00
travis-farmer
809388c547 compiles, but no link.. 2023-10-20 17:04:08 -04:00
travis-farmer
3d4b80d02e haba said delete the * 2023-10-20 15:07:42 -04:00
travis-farmer
881c490ae0 up to date - just one error now 2023-10-20 14:17:12 -04:00
travis-farmer
1d6b7d4c63 moved extern, no change... 2023-10-20 13:45:02 -04:00
travis-farmer
9cff33a414 compiles but will not link... 2023-10-20 13:30:52 -04:00
travis-farmer
6ea3366c75 reverting back a bit 2023-10-20 12:40:40 -04:00
travis-farmer
e5ffab582f cleaning up a mistake 2023-10-20 12:24:22 -04:00
travis-farmer
2993725a14 still no compile... following a lead 2023-10-20 11:49:26 -04:00
travis-farmer
b417ad6575 corrected changes from haba 2023-10-20 04:45:39 -04:00
travis-farmer
94273b6e11 keeping up to date 2023-10-19 17:04:00 -04:00
travis-farmer
5b4a1dd6fa Keeping up to date 2023-10-19 16:23:36 -04:00
travis-farmer
9e6104fc16 adding CMRI to save 2023-10-19 15:11:25 -04:00
travis-farmer
5955a9f593 commit current 2023-10-19 15:01:05 -04:00
travis-farmer
bcf8e27e43 abandoning port to giga... 2023-10-19 11:56:33 -04:00
travis-farmer
c8e46fed76 commit current work 2023-10-19 10:39:25 -04:00
travis-farmer
23a0a42df2 work arounds 2023-10-19 09:10:44 -04:00
travis-farmer
2a03b08d28 no HA support 2023-10-19 08:35:21 -04:00
Travis Farmer
ef78b52c2a initial work, no compile yet 2023-10-18 16:59:56 -04:00
peteGSX
27a5f76a8d Merge pull request #361 from DCC-EX:separate-hal-extt-turntable
Separate-hal-extt-turntable
2023-10-17 05:17:24 +10:00
peteGSX
754bd99381 Update version 2023-10-17 05:08:04 +10:00
peteGSX
650e411a4f Add vpin parameter 2023-10-17 05:06:35 +10:00
peteGSX
0978bb0c11 Changes made, but non-functional 2023-10-16 08:12:11 +10:00
Asbelos
6eb7051fd6 LCC and signal compile-out
LCC commands in EXRAIL for OpenMRN Adapter

FIrst use of compile-out of unused features.
2023-10-13 13:59:06 +01:00
peteGSX
5726844c83 Merge pull request #360 from DCC-EX:fix-ifttposition
Fixed
2023-10-13 04:46:05 +10:00
peteGSX
0214a55b23 Fixed 2023-10-13 04:37:38 +10:00
Asbelos
7db4a9575a Merge branch 'master' into devel 2023-10-12 11:07:39 +01:00
Asbelos
8b8e9e4919 clean result from invalid <JR n> 2023-10-12 11:07:05 +01:00
peteGSX
ce84974967 Missed one i 2023-10-12 13:42:14 +10:00
peteGSX
034c441c34 Merge pull request #359 from DCC-EX:turntable-broadcast-I
Change broadcast
2023-10-12 13:35:55 +10:00
peteGSX
d5978b1578 Change broadcast 2023-10-12 13:28:39 +10:00
Colin Murdoch
ea4f90d5fc Merged in Power changes
Merge in power changes and EXRAIL command & update to version.h
2023-10-11 17:06:56 +01:00
Colin Murdoch
1181fd855d Merge branch 'devel-power-chm' into devel 2023-10-11 16:58:17 +01:00
Colin Murdoch
a092e06a6f Update .gitignore
added UserAddin.txt to gitignore
2023-10-10 12:11:49 +01:00
Colin Murdoch
68fd56e7fc Added returnDCAddr
Added function to return DC address
2023-10-10 11:52:46 +01:00
Asbelos
370dae0ab8 Merge branch 'master' into devel 2023-10-09 18:15:36 +01:00
Asbelos
bef4b2ec35 fix <JR> default roster 2023-10-09 18:09:48 +01:00
Colin Murdoch
fe618d0b85 Add getModeName()
Add facility to get the name of the track mode
2023-10-06 19:11:11 +01:00
pmantoine
2ff1619ad1 STM32 reinstate 100% duty cycle PWM 2023-10-04 14:54:06 +08:00
pmantoine
7afd4443d6 STM32 revised I2C clock setup 2023-10-02 12:04:47 +08:00
Colin Murdoch
52cfc18754 Remove Diags not needed
Tidy up Diags and responses - use HASH_KEYWORD in place of 'A'
2023-09-28 15:02:30 +01:00
pmantoine
ed0cfee091 STM32 DCCEXanalogWrite for TrackManager PWM 2023-09-28 17:43:22 +08:00
Colin Murdoch
25bbfa4c68 Fix <1 JOIN>
Fixed <1 JOIN> issue in TrackManager
2023-09-27 14:46:48 +01:00
Colin Murdoch
2a46b96083 Updates to power
Updates to powere routines and EXRAIL
2023-09-26 18:02:39 +01:00
Colin Murdoch
17c004aecf Code corrections
code corrections
2023-09-25 14:32:54 +01:00
Colin Murdoch
9e3ae21bb8 Change to EXRAIL Set_Power
Change to EXRAIL SET_Power
2023-09-25 09:59:17 +01:00
Harald Barth
624656ebc9 date tag 2023-09-24 20:56:27 +02:00
Harald Barth
5a7f278b1e correct return when requesting D RAM 2023-09-24 20:55:16 +02:00
Harald Barth
9333beda49 correct return when requesting D RAM 2023-09-24 20:54:17 +02:00
Colin Murdoch
aacb980dc8 Power control plus EXRAIL
Power Control <0 A> etc plus EXRAIL SET_POWER
Not yet fully tested.
2023-09-24 15:40:42 +01:00
kempe63
11b9fd4ef5 Fixed IO_PCA9555.h to work with PCA9548 mux 2023-09-23 20:25:10 +01:00
kempe63
d07718be8c Revert "Update version.h to 5.1.7"
This reverts commit d57b5ba537.
2023-09-23 20:18:59 +01:00
kempe63
d57b5ba537 Update version.h to 5.1.7 2023-09-23 20:09:01 +01:00
kempe63
dab02ec659 Update IO_PCA9555.h
Now compiles and works on PCA9548 Mux. Tested with MCP23017 and PCA9555 on the same Subbus
2023-09-23 16:10:16 +01:00
kempe63
6ad5326f1d PCA9555 compiles and tested on PCA9548 mux
IO_PCA9555.h added changes that had been applied to IO_MCP23017 for support on PCA9548 Mux. Constructor now also private and type casting of variables made the same for IO_PCA9555. Tested with MCP23017 and PCA9555 simultaneous on the same Mux Subbus
2023-09-23 16:06:14 +01:00
kempe63
39e1363ce0 Merge branch 'devel' of https://github.com/DCC-EX/CommandStation-EX into devel 2023-09-23 16:04:26 +01:00
kempe63
c9d4f5e94d Update IO_PCA9555.h 2023-09-23 15:56:09 +01:00
Colin Murdoch
8052090e0f Added Single Track Power On/Off
Added power On/Off <> commands
2023-09-22 17:03:40 +01:00
pmantoine
dfe3e9d42c STM32 ADCee extensions 2023-09-21 16:09:04 +08:00
pmantoine
5d810a620b STM32 variant support extension and tidy 2023-09-21 16:05:35 +08:00
pmantoine
550ad58c4d STM32 ADCee extended to support ADC2 and ADC3 2023-09-21 14:39:25 +08:00
Harald Barth
46289fa78c Check bad AT firmware version 2023-09-14 09:05:23 +02:00
Harald Barth
b3cafd126e sample file corrections 2023-08-30 23:26:20 +02:00
Harald Barth
c55fa9f9d2 version number update 2023-08-25 19:08:58 +02:00
Harald Barth
210d96a3e3 Bugfix: ESP32 30ms off time 2023-08-25 19:07:57 +02:00
Harald Barth
42f3c7c128 version number update 2023-08-24 10:05:31 +02:00
Harald Barth
6cd7002e91 Bugfix: execute 30ms off time before rejoin 2023-08-24 10:03:29 +02:00
peteGSX
085762e800 Add OPCODE list to DCCEXParser.cpp 2023-08-18 18:52:34 +10:00
Harald Barth
2db2b0ecc6 Committing a SHA 2023-08-07 20:27:22 +02:00
Harald Barth
fd58a749ef Committing a SHA 2023-08-07 20:25:14 +02:00
Harald Barth
3bddf4dfd1 Make 4.2.69 the 5.0.0 release 2023-08-07 19:45:45 +02:00
39 changed files with 5104 additions and 302 deletions

2
.gitignore vendored
View File

@@ -13,3 +13,5 @@ myFilter.cpp
my*.h
!my*.example.h
compile_commands.json
newcode.txt.old
UserAddin.txt

View File

@@ -162,7 +162,7 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
}
void CommandDistributor::broadcastTurntable(int16_t id, uint8_t position, bool moving) {
broadcastReply(COMMAND_TYPE, F("<i %d %d %d>\n"), id, position, moving);
broadcastReply(COMMAND_TYPE, F("<I %d %d %d>\n"), id, position, moving);
}
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
@@ -269,6 +269,53 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
broadcastReply(type, F("%s"),msg);
}
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr);
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) {
broadcastReply(COMMAND_TYPE, format,trackLetter, dcAddr);
}
Print * CommandDistributor::getVirtualLCDSerial(byte screen, byte row) {
Print * stream=virtualLCDSerial;
#ifdef CD_HANDLE_RING
rememberVLCDClient=RingStream::NO_CLIENT;
if (!stream && virtualLCDClient!=RingStream::NO_CLIENT) {
// If we are broadcasting from a wifi/eth process we need to complete its output
// before merging broadcasts in the ring, then reinstate it in case
// the process continues to output to its client.
if ((rememberVLCDClient = ring->peekTargetMark()) != RingStream::NO_CLIENT) {
ring->commit();
}
ring->mark(virtualLCDClient);
stream=ring;
}
#endif
if (stream) StringFormatter::send(stream,F("<@ %d %d \""), screen,row);
return stream;
}
void CommandDistributor::commitVirtualLCDSerial() {
#ifdef CD_HANDLE_RING
if (virtualLCDClient!=RingStream::NO_CLIENT) {
StringFormatter::send(ring,F("\">\n"));
ring->commit();
if (rememberVLCDClient!=RingStream::NO_CLIENT) ring->mark(rememberVLCDClient);
return;
}
#endif
StringFormatter::send(virtualLCDSerial,F("\">\n"));
}
void CommandDistributor::setVirtualLCDSerial(Print * stream) {
#ifdef CD_HANDLE_RING
virtualLCDClient=RingStream::NO_CLIENT;
if (stream && stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM) {
virtualLCDClient=((RingStream *) stream)->peekTargetMark();
virtualLCDSerial=nullptr;
return;
}
#endif
virtualLCDSerial=stream;
}
Print* CommandDistributor::virtualLCDSerial=nullptr;
byte CommandDistributor::virtualLCDClient=0xFF;
byte CommandDistributor::rememberVLCDClient=0;

View File

@@ -55,10 +55,18 @@ public :
static int16_t retClockTime();
static void broadcastPower();
static void broadcastRaw(clientType type,char * msg);
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr);
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
static void forget(byte clientId);
// Handling code for virtual LCD receiver.
static Print * getVirtualLCDSerial(byte screen, byte row);
static void commitVirtualLCDSerial();
static void setVirtualLCDSerial(Print * stream);
private:
static Print * virtualLCDSerial;
static byte virtualLCDClient;
static byte rememberVLCDClient;
};
#endif

View File

@@ -96,7 +96,11 @@ void setup()
// Start Ethernet if it exists
#ifndef ARDUINO_ARCH_ESP32
#if WIFI_ON
#ifndef WIFI_NINA
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#else
WifiNINA::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
#endif // WIFI_NINA
#endif // WIFI_ON
#else
// ESP32 needs wifi on always
@@ -144,7 +148,11 @@ void loop()
// Responsibility 3: Optionally handle any incoming WiFi traffic
#ifndef ARDUINO_ARCH_ESP32
#if WIFI_ON
#ifndef WIFI_NINA
WifiInterface::loop();
#else
WifiNINA::loop();
#endif //WIFI_NINA
#endif //WIFI_ON
#else //ARDUINO_ARCH_ESP32
#ifndef WIFI_TASK_ON_CORE0

4
DCC.h
View File

@@ -43,7 +43,11 @@ const uint16_t LONG_ADDR_MARKER = 0x4000;
// Allocations with memory implications..!
// Base system takes approx 900 bytes + 8 per loco. Turnouts, Sensors etc are dynamically created
#if defined(HAS_ENOUGH_MEMORY)
#if defined(ARDUINO_GIGA) // yes giga
const byte MAX_LOCOS = 100;
#else // no giga
const byte MAX_LOCOS = 50;
#endif // giga
#else
const byte MAX_LOCOS = 30;
#endif

View File

@@ -1,4 +1,5 @@
/*
* © 2023 Paul M. Antoine
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
@@ -33,8 +34,13 @@
#include "SerialManager.h"
#include "version.h"
#ifndef ARDUINO_ARCH_ESP32
#include "WifiInterface.h"
#ifdef WIFI_NINA
#include "Wifi_NINA.h"
#else
#include "WifiInterface.h"
#endif // WIFI_NINA
#else
#undef WIFI_NINA
#include "WifiESP32.h"
#endif
#if ETHERNET_ON == true

View File

@@ -49,7 +49,7 @@ Once a new OPCODE is decided upon, update this list.
b, Write CV bit on main
B, Write CV bit
c, Request current command
C,
C, configure the CS
d,
D, Diagnostic commands
e, Erase EEPROM
@@ -60,14 +60,14 @@ Once a new OPCODE is decided upon, update this list.
G,
h,
H, Turnout state broadcast
i, Reserved for future use - Turntable object broadcast
I, Reserved for future use - Turntable object command and control
i, Server details string
I, Turntable object command, control, and broadcast
j, Throttle responses
J, Throttle queries
k, Reserved for future use - Potentially Railcom
K, Reserved for future use - Potentially Railcom
l, Loco speedbyte/function map broadcast
L,
L, Reserved for LCC interface (implemented in EXRAIL)
m,
M, Write DCC packet
n,
@@ -122,7 +122,7 @@ Once a new OPCODE is decided upon, update this list.
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
int16_t value=GETHIGHFLASHW(flashList,i); \
if (value==INT16_MAX) break; \
if (value != 0) StringFormatter::send(stream,F(" %d"),value); \
StringFormatter::send(stream,F(" %d"),value); \
}
@@ -157,6 +157,7 @@ const int16_t HASH_KEYWORD_VPIN=-415;
const int16_t HASH_KEYWORD_A='A';
const int16_t HASH_KEYWORD_C='C';
const int16_t HASH_KEYWORD_G='G';
const int16_t HASH_KEYWORD_H='H';
const int16_t HASH_KEYWORD_I='I';
const int16_t HASH_KEYWORD_O='O';
const int16_t HASH_KEYWORD_P='P';
@@ -552,69 +553,131 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
{
bool main=false;
bool prog=false;
bool join=false;
if (params > 1) break;
if (params==0) { // All
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
bool main=false;
bool prog=false;
bool join=false;
bool singletrack=false;
//byte t=0;
if (params > 1) break;
if (params==0) { // All
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
main=true;
}
#ifndef DISABLE_PROG
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true;
prog=true;
join=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true;
}
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
main=true;
prog=true;
join=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
prog=true;
}
#endif
else break; // will reply <X>
}
TrackManager::setJoin(join);
if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON);
//else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H>
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
byte t = (p[0] - 'A');
//DIAG(F("Processing track - %d "), t);
if (TrackManager::isProg(t)) {
main = false;
prog = true;
}
else
{
main=true;
prog=false;
}
singletrack=true;
if (main) TrackManager::setTrackPower(false, false, POWERMODE::ON, t);
if (prog) TrackManager::setTrackPower(true, false, POWERMODE::ON, t);
StringFormatter::send(stream, F("<1 %c>\n"), t+'A');
//CommandDistributor::broadcastPower();
//TrackManager::streamTrackState(NULL,t);
return;
}
CommandDistributor::broadcastPower();
return;
else break; // will reply <X>
}
if (!singletrack) {
TrackManager::setJoin(join);
if (join) TrackManager::setJoinPower(POWERMODE::ON);
else {
if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON);
}
CommandDistributor::broadcastPower();
return;
}
}
case '0': // POWEROFF <0 [MAIN | PROG] >
{
bool main=false;
bool prog=false;
if (params > 1) break;
if (params==0) { // All
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true;
}
bool main=false;
bool prog=false;
bool singletrack=false;
//byte t=0;
if (params > 1) break;
if (params==0) { // All
main=true;
prog=true;
}
if (params==1) {
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
main=true;
}
#ifndef DISABLE_PROG
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true;
}
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true;
}
#endif
else break; // will reply <X>
}
//else if (p[0] >= 'A' && p[0] <= 'H') { // <1 A-H>
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
byte t = (p[0] - 'A');
//DIAG(F("Processing track - %d "), t);
if (TrackManager::isProg(t)) {
main = false;
prog = true;
}
else
{
main=true;
prog=false;
}
singletrack=true;
TrackManager::setJoin(false);
if (main) TrackManager::setTrackPower(false, false, POWERMODE::OFF, t);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setTrackPower(true, false, POWERMODE::OFF, t);
}
StringFormatter::send(stream, F("<0 %c>\n"), t+'A');
//CommandDistributor::broadcastPower();
//TrackManager::streamTrackState(NULL, t);
return;
}
TrackManager::setJoin(false);
if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setProgPower(POWERMODE::OFF);
}
else break; // will reply <X>
}
CommandDistributor::broadcastPower();
return;
if (!singletrack) {
TrackManager::setJoin(false);
if (main) TrackManager::setMainPower(POWERMODE::OFF);
if (prog) {
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
TrackManager::setProgPower(POWERMODE::OFF);
}
CommandDistributor::broadcastPower();
return;
}
}
case '!': // ESTOP ALL <!>
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
@@ -630,7 +693,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
Sensor::printAll(stream);
return;
case 's': // <s>
case 's': // STATUS <s>
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
Turnout::printAll(stream); //send all Turnout states
@@ -651,13 +714,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
case ' ': // < >
StringFormatter::send(stream, F("\n"));
return;
case 'C': // CONFIG <C [params]>
if (parseC(stream, params, p))
return;
break;
#ifndef DISABLE_DIAG
case 'D': // < >
case 'D': // DIAG <D [params]>
if (parseD(stream, params, p))
return;
break;
#endif
case '=': // <= Track manager control >
case '=': // TACK MANAGER CONTROL <= [params]>
if (TrackManager::parseJ(stream, params, p))
return;
break;
@@ -742,11 +809,15 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else {
const FSH * functionNames= RMFT2::getRosterFunctions(id);
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id),
functionNames == NULL ? RMFT2::getRosterFunctions(0) : functionNames);
}
auto rosterName= RMFT2::getRosterName(id);
if (!rosterName) rosterName=F("");
auto functionNames= RMFT2::getRosterFunctions(id);
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
if (!functionNames) functionNames=F("");
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, rosterName, functionNames);
}
#endif
StringFormatter::send(stream, F(">\n"));
return;
@@ -839,6 +910,16 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
break;
#endif
case 'L': // LCC interface implemented in EXRAIL parser
break; // Will <X> if not intercepted by EXRAIL
case '@': // JMRI saying "give me virtual LCD msgs"
CommandDistributor::setVirtualLCDSerial(stream);
StringFormatter::send(stream,
F("<@ 0 0 \"DCC-EX v" VERSION "\">\n"
"<@ 0 1 \"Lic GPLv3\">\n"));
return;
default: //anything else will diagnose and drop out to <X>
DIAG(F("Opcode=%c params=%d"), opcode, params);
for (int i = 0; i < params; i++)
@@ -1044,20 +1125,29 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
return false;
}
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
{
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0])
{
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
#ifndef DISABLE_PROG
case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
#endif
case HASH_KEYWORD_RESET:
DCCTimer::reset();
break; // and <X> if we didnt restart
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
DIAG(F("28 Speedsteps"));
return true;
case HASH_KEYWORD_RAM: // <D RAM>
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
break;
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true;
#ifndef DISABLE_PROG
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
@@ -1076,12 +1166,33 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
}
} else {
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
Diag::ACK = onOff;
}
return true;
#endif
default: // invalid/unknown
break;
}
return false;
}
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
{
if (params == 0)
return false;
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
switch (p[0])
{
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
return true;
case HASH_KEYWORD_RAM: // <D RAM>
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
return true;
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
Diag::CMD = onOff;
return true;
@@ -1103,34 +1214,14 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
Diag::LCN = onOff;
return true;
#endif
#ifndef DISABLE_PROG
case HASH_KEYWORD_PROGBOOST:
TrackManager::progTrackBoosted=true;
return true;
#endif
case HASH_KEYWORD_RESET:
DCCTimer::reset();
break; // and <X> if we didnt restart
#ifndef DISABLE_EEPROM
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
if (params >= 2)
EEStore::dump(p[1]);
return true;
#endif
case HASH_KEYWORD_SPEED28:
DCC::setGlobalSpeedsteps(28);
StringFormatter::send(stream, F("28 Speedsteps"));
return true;
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
StringFormatter::send(stream, F("128 Speedsteps"));
return true;
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
break;
@@ -1153,7 +1244,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
break;
default: // invalid/unknown
break;
return parseC(stream, params, p);
}
return false;
}
@@ -1181,7 +1272,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
if (tto) {
bool type = tto->isEXTT();
uint8_t position = tto->getPosition();
StringFormatter::send(stream, F("<i %d %d>\n"), type, position);
StringFormatter::send(stream, F("<I %d %d>\n"), type, position);
} else {
return false;
}
@@ -1207,7 +1298,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
if (!DCCTurntable::create(p[0])) return false;
Turntable *tto = Turntable::get(p[0]);
tto->addPosition(0, 0, p[2]);
StringFormatter::send(stream, F("<i>\n"));
StringFormatter::send(stream, F("<I>\n"));
} else {
if (!tto) return false;
if (!tto->isEXTT()) return false;
@@ -1224,7 +1315,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
Turntable *tto = Turntable::get(p[0]);
tto->addPosition(0, 0, p[3]);
StringFormatter::send(stream, F("<i>\n"));
StringFormatter::send(stream, F("<I>\n"));
} else {
return false;
}
@@ -1238,7 +1329,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
// tto must exist, no more than 48 positions, angle 0 - 3600
if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false;
tto->addPosition(p[2], p[3], p[4]);
StringFormatter::send(stream, F("<i>\n"));
StringFormatter::send(stream, F("<I>\n"));
} else {
return false;
}

View File

@@ -49,6 +49,7 @@ struct DCCEXParser
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
static bool parseS(Print * stream, int16_t params, int16_t p[]);
static bool parsef(Print * stream, int16_t params, int16_t p[]);
static bool parseC(Print * stream, int16_t params, int16_t p[]);
static bool parseD(Print * stream, int16_t params, int16_t p[]);
#ifndef IO_NO_HAL
static bool parseI(Print * stream, int16_t params, int16_t p[]);

View File

@@ -3,6 +3,7 @@
* © 2021 Mike S
* © 2021-2023 Harald Barth
* © 2021 Fred Decker
* © 2023 Travis Farmer
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -90,6 +91,9 @@ private:
static const int DCC_SIGNAL_TIME=58; // this is the 58uS DCC 1-bit waveform half-cycle
#if defined(ARDUINO_ARCH_STM32) // TODO: PMA temporary hack - assumes 100Mhz F_CPU as STM32 can change frequency
static const long CLOCK_CYCLES=(100000000L / 1000000 * DCC_SIGNAL_TIME) >>1;
#elif defined(ARDUINO_GIGA)
///TJF: we could get F_CPU from SystemCoreClock, but it will not allow as it is a non-constant value
static const long CLOCK_CYCLES=(480000000L / 1000000 * DCC_SIGNAL_TIME) >>1;
#else
static const long CLOCK_CYCLES=(F_CPU / 1000000 * DCC_SIGNAL_TIME) >>1;
#endif
@@ -125,8 +129,13 @@ private:
// On platforms that scan, it is called from waveform ISR
// only on a regular basis.
static void scan();
#if defined (ARDUINO_ARCH_STM32)
// bit array of used pins (max 32)
static uint32_t usedpins;
#else
// bit array of used pins (max 16)
static uint16_t usedpins;
#endif
static uint8_t highestPin;
// cached analog values (malloc:ed to actual number of ADC channels)
static int *analogvals;

193
DCCTimerGiga.cpp Normal file
View File

@@ -0,0 +1,193 @@
/*
* © 2023 Travis Farmer
* © 2023 Neil McKechnie
* © 2022-2023 Paul M. Antoine
* © 2021 Mike S
* © 2021, 2023 Harald Barth
* © 2021 Fred Decker
* © 2021 Chris Harlow
* © 2021 David Cutting
* All rights reserved.
*
* This file is part of Asbelos DCC 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 <https://www.gnu.org/licenses/>.
*/
// ATTENTION: this file only compiles on a STM32 based boards
// Please refer to DCCTimer.h for general comments about how this class works
// This is to avoid repetition and duplication.
#if defined(ARDUINO_GIGA)
#include "DCCTimer.h"
#include "DIAG.h"
#include "GigaHardwareTimer.h"
#include <Arduino_AdvancedAnalog.h>
//#include "config.h"
///////////////////////////////////////////////////////////////////////////////////////////////
// Experimental code for High Accuracy (HA) DCC Signal mode
// Warning - use of TIM2 and TIM3 can affect the use of analogWrite() function on certain pins,
// which is used by the DC motor types.
///////////////////////////////////////////////////////////////////////////////////////////////
INTERRUPT_CALLBACK interruptHandler=0;
#ifndef DCC_EX_TIMER
#if defined(TIM6)
#define DCC_EX_TIMER TIM6
#elif defined(TIM7)
#define DCC_EX_TIMER TIM7
#elif defined(TIM12)
#define DCC_EX_TIMER TIM12
#else
#warning This Giga variant does not have Timers 1,8 or 11!!
#endif
#endif // ifndef DCC_EX_TIMER
HardwareTimer dcctimer(TIM8);
void DCCTimer_Handler() __attribute__((interrupt));
void DCCTimer_Handler() {
interruptHandler();
}
void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
dcctimer.pause();
dcctimer.setPrescaleFactor(1);
// timer.setOverflow(CLOCK_CYCLES * 2);
dcctimer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
// dcctimer.attachInterrupt(Timer11_Handler);
dcctimer.attachInterrupt(DCCTimer_Handler);
dcctimer.setInterruptPriority(0, 0); // Set highest preemptive priority!
dcctimer.refresh();
dcctimer.resume();
interrupts();
}
bool DCCTimer::isPWMPin(byte pin) {
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
// there's no support yet for High Accuracy, so for now return false
// return digitalPinHasPWM(pin);
(void) pin;
return false;
}
void DCCTimer::setPWM(byte pin, bool high) {
// TODO: High Accuracy mode is not supported as yet, and may never need to be
(void) pin;
(void) high;
return;
}
void DCCTimer::clearPWM() {
return;
}
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
volatile uint32_t *serno1 = (volatile uint32_t *)UID_BASE;
volatile uint32_t *serno2 = (volatile uint32_t *)UID_BASE+4;
volatile uint32_t *serno3 = (volatile uint32_t *)UID_BASE+8;
volatile uint32_t m1 = *serno1;
volatile uint32_t m2 = *serno2;
volatile uint32_t m3 = *serno3;
mac[0] = 0xBE;
mac[1] = 0xEF;
mac[2] = m1 ^ m3 >> 24;
mac[3] = m1 ^ m3 >> 16;
mac[4] = m1 ^ m3 >> 8;
mac[5] = m1 ^ m3 >> 0;
//DIAG(F("MAC: %P:%P:%P:%P:%P:%P"),mac[0],mac[1],mac[2],mac[3],mac[4],mac[5]);
}
volatile int DCCTimer::minimum_free_memory=__INT_MAX__;
// Return low memory value...
int DCCTimer::getMinimumFreeMemory() {
noInterrupts(); // Disable interrupts to get volatile value
int retval = freeMemory();
interrupts();
return retval;
}
extern "C" char* sbrk(int incr);
int DCCTimer::freeMemory() {
char top;
unsigned int tmp = (unsigned int)(&top - reinterpret_cast<char*>(sbrk(0)));
return (int)(tmp / 1000);
}
void DCCTimer::reset() {
//Watchdog &watchdog = Watchdog::get_instance();
//Watchdog::stop();
//Watchdog::start(500);
//while(true) {};
return;
}
int * ADCee::analogvals = NULL;
int16_t ADCee::ADCmax()
{
return 4095;
}
AdvancedADC adc;
pin_size_t active_pins[] = {A0, A1, A2, A3};
pin_size_t active_pinsB[] = {A4, A5, A6, A7};
int num_active_pins = 4;
const int samples_per_round = 512;
int ADCee::init(uint8_t pin) {
adc.stop();
if (pin >= A0 && pin <= A3) adc.begin(AN_RESOLUTION_12, 16000, 1, samples_per_round, num_active_pins, active_pins);
else if (pin >= A4 && pin <= A7) adc.begin(AN_RESOLUTION_12, 16000, 1, samples_per_round, num_active_pins, active_pinsB);
return 123;
}
/*
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
*/
int ADCee::read(uint8_t pin, bool fromISR) {
int tmpPin = 0;
if (pin >= A0 && pin <= A3) tmpPin = (pin - A0);
else if (pin >= A4 && pin <= A7) tmpPin = ((pin - A0) - 4);
static SampleBuffer buf = adc.read();
int retVal = -123;
if (adc.available()) {
buf.release();
buf = adc.read();
}
return (buf[tmpPin]);
}
/*
* Scan function that is called from interrupt
*/
#pragma GCC push_options
#pragma GCC optimize ("-O3")
void ADCee::scan() {
}
#pragma GCC pop_options
void ADCee::begin() {
noInterrupts();
interrupts();
}
#endif

View File

@@ -1,6 +1,6 @@
/*
* © 2023 Neil McKechnie
* © 2022-23 Paul M. Antoine
* © 2022-2023 Paul M. Antoine
* © 2021 Mike S
* © 2021, 2023 Harald Barth
* © 2021 Fred Decker
@@ -52,7 +52,7 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG)
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// Nucleo-144 boards don't have Serial1 defined by default
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
@@ -154,13 +154,28 @@ HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
///////////////////////////////////////////////////////////////////////////////////////////////
INTERRUPT_CALLBACK interruptHandler=0;
// Let's use STM32's timer #11 until disabused of this notion
// Timer #11 is used for "servo" library, but as DCC-EX is not using
// this libary, we should be free and clear.
HardwareTimer timer(TIM11);
// On STM32F4xx models that have them, Timers 6 and 7 have no PWM output capability,
// so are good choices for general timer duties - they are used for tone and servo
// in stm32duino so we shall usurp those as DCC-EX doesn't use tone or servo libs.
// NB: the F401, F410 and F411 do **not** have Timer 6 or 7, so we use Timer 11
#ifndef DCC_EX_TIMER
#if defined(TIM6)
#define DCC_EX_TIMER TIM6
#elif defined(TIM7)
#define DCC_EX_TIMER TIM7
#elif defined(TIM11)
#define DCC_EX_TIMER TIM11
#else
#warning This STM32F4XX variant does not have Timers 6,7 or 11!!
#endif
#endif // ifndef DCC_EX_TIMER
HardwareTimer dcctimer(DCC_EX_TIMER);
void DCCTimer_Handler() __attribute__((interrupt));
// Timer IRQ handler
void Timer11_Handler() {
void DCCTimer_Handler() {
interruptHandler();
}
@@ -168,22 +183,24 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
timer.pause();
timer.setPrescaleFactor(1);
dcctimer.pause();
dcctimer.setPrescaleFactor(1);
// timer.setOverflow(CLOCK_CYCLES * 2);
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
timer.attachInterrupt(Timer11_Handler);
timer.refresh();
timer.resume();
dcctimer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
// dcctimer.attachInterrupt(Timer11_Handler);
dcctimer.attachInterrupt(DCCTimer_Handler);
dcctimer.setInterruptPriority(0, 0); // Set highest preemptive priority!
dcctimer.refresh();
dcctimer.resume();
interrupts();
}
bool DCCTimer::isPWMPin(byte pin) {
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
// there's no support yet for High Accuracy, so for now return false
// return digitalPinHasPWM(pin);
(void) pin;
return false;
}
@@ -235,22 +252,91 @@ void DCCTimer::reset() {
while(true) {};
}
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
#endif
// For now, define the max of 16 ports - some variants have more, but this not **yet** supported
#define NUM_ADC_INPUTS 16
// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
// TODO: rationalise the size of these... could really use sparse arrays etc.
static HardwareTimer * pin_timer[100] = {0};
static uint32_t channel_frequency[100] = {0};
static uint32_t pin_channel[100] = {0};
uint16_t ADCee::usedpins = 0;
uint8_t ADCee::highestPin = 0;
int * ADCee::analogvals = NULL;
uint32_t * analogchans = NULL;
bool adc1configured = false;
// Using the HardwareTimer library API included in stm32duino core to handle PWM duties
// TODO: in order to use the HA code above which Neil kindly wrote, we may have to do something more
// sophisticated about detecting any clash between the timer we'd like to use for PWM and the ones
// currently used for HA so they don't interfere with one another. For now we'll just make PWM
// work well... then work backwards to integrate with HA mode if we can.
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency)
{
if (pin_timer[pin] == NULL) {
// Automatically retrieve TIM instance and channel associated to pin
// This is used to be compatible with all STM32 series automatically.
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
if (Instance == NULL) {
// We shouldn't get here (famous last words) as it ought to have been caught by brakeCanPWM()!
DIAG(F("DCCEXanalogWriteFrequency::Pin %d has no PWM function!"), pin);
return;
}
pin_channel[pin] = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
int16_t ADCee::ADCmax() {
return 4095;
// Instantiate HardwareTimer object. Thanks to 'new' instantiation,
// HardwareTimer is not destructed when setup function is finished.
pin_timer[pin] = new HardwareTimer(Instance);
// Configure and start PWM
// MyTim->setPWM(channel, pin, 5, 10, NULL, NULL); // No callback required, we can simplify the function call
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);
}
else
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
}
else
{
// Frequency change request
if (frequency != channel_frequency[pin])
{
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
}
}
channel_frequency[pin] = frequency;
return;
}
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
// Calculate percentage duty cycle from value given
uint32_t duty_cycle = (value * 100 / 256) + 1;
if (pin_timer[pin] != NULL) {
// if (duty_cycle == 100)
// {
// pin_timer[pin]->pauseChannel(pin_channel[pin]);
// DIAG(F("DCCEXanalogWrite::Pausing timer channel on pin %d"), pin);
// }
// else
// {
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
// pin_timer[pin]->resumeChannel(pin_channel[pin]);
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
// }
}
else
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
}
// Now we can handle more ADCs, maybe this works!
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
uint32_t ADCee::usedpins = 0; // Max of 32 ADC input channels!
uint8_t ADCee::highestPin = 0; // Highest pin to scan
int * ADCee::analogvals = NULL; // Array of analog values last captured
uint32_t * analogchans = NULL; // Array of channel numbers to be scanned
// bool adc1configured = false;
ADC_TypeDef * * adcchans = NULL; // Array to capture which ADC is each input channel on
int16_t ADCee::ADCmax()
{
return 4095;
}
int ADCee::init(uint8_t pin) {
@@ -261,11 +347,33 @@ int ADCee::init(uint8_t pin) {
return -1024; // some silly value as error
uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32)
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
GPIO_TypeDef * gpioBase;
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC input channel
ADC_TypeDef *adc = (ADC_TypeDef *)pinmap_find_peripheral(stmpin, PinMap_ADC); // find which ADC this pin is on ADC1/2/3 etc.
int adcnum = 1;
if (adc == ADC1)
DIAG(F("ADCee::init(): found pin %d on ADC1"), pin);
// Checking for ADC2 and ADC3 being defined helps cater for more variants later
#if defined(ADC2)
else if (adc == ADC2)
{
DIAG(F("ADCee::init(): found pin %d on ADC2"), pin);
adcnum = 2;
}
#endif
#if defined(ADC3)
else if (adc == ADC3)
{
DIAG(F("ADCee::init(): found pin %d on ADC3"), pin);
adcnum = 3;
}
#endif
else DIAG(F("ADCee::init(): found pin %d on unknown ADC!"), pin);
// Port config - find which port we're on and power it up
switch(stmgpio) {
// Port config - find which port we're on and power it up
GPIO_TypeDef *gpioBase;
switch (stmgpio)
{
case 0x00:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
gpioBase = GPIOA;
@@ -278,6 +386,20 @@ int ADCee::init(uint8_t pin) {
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
gpioBase = GPIOC;
break;
case 0x03:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; //Power up PORTD
gpioBase = GPIOD;
break;
case 0x04:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; //Power up PORTE
gpioBase = GPIOE;
break;
#if defined(GPIOF)
case 0x05:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; //Power up PORTF
gpioBase = GPIOF;
break;
#endif
default:
return -1023; // some silly value as error
}
@@ -293,31 +415,33 @@ int ADCee::init(uint8_t pin) {
if (adcchan > 18)
return -1022; // silly value as error
if (adcchan < 10)
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
adc->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
else
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
adc->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
// Read the inital ADC value for this analog input
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
value = ADC1->DR; // Read value from register
adc->SQR3 = adcchan; // 1st conversion in regular sequence
adc->CR2 |= ADC_CR2_SWSTART; //(1 << 30); // Start 1st conversion SWSTART
while(!(adc->SR & (1 << 1))); // Wait until conversion is complete
value = adc->DR; // Read value from register
uint8_t id = pin - PNUM_ANALOG_BASE;
if (id > 15) { // today we have not enough bits in the mask to support more
return -1021;
}
// if (id > 15) { // today we have not enough bits in the mask to support more
// return -1021;
// }
if (analogvals == NULL) { // allocate analogvals and analogchans if this is the first invocation of init.
if (analogvals == NULL) { // allocate analogvals, analogchans and adcchans if this is the first invocation of init
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
adcchans = (ADC_TypeDef **)calloc(NUM_ADC_INPUTS+1, sizeof(ADC_TypeDef));
}
analogvals[id] = value; // Store sampled value
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
usedpins |= (1 << id); // This pin is now ready
adcchans[id] = adc; // Keep track of which ADC this channel is on
usedpins |= (1 << id); // This pin is now ready
if (id > highestPin) highestPin = id; // Store our highest pin in use
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
DIAG(F("ADCee::init(): value=%d, ADC%d: channel=%d, id=%d"), value, adcnum, adcchan, id);
return value;
}
@@ -344,13 +468,16 @@ void ADCee::scan() {
static uint8_t id = 0; // id and mask are the same thing but it is faster to
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
static bool waiting = false;
static ADC_TypeDef *adc;
if (waiting) {
adc = adcchans[id];
if (waiting)
{
// look if we have a result
if (!(ADC1->SR & (1 << 1)))
if (!(adc->SR & (1 << 1)))
return; // no result, continue to wait
// found value
analogvals[id] = ADC1->DR;
analogvals[id] = adc->DR;
// advance at least one track
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
@@ -369,9 +496,10 @@ void ADCee::scan() {
// look for a valid track to sample or until we are around
while (true) {
if (mask & usedpins) {
// start new ADC aquire on id
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
// start new ADC aquire on id
adc = adcchans[id];
adc->SQR3 = analogchans[id]; // 1st conversion in regular sequence
adc->CR2 |= (1 << 30); // Start 1st conversion SWSTART
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
@@ -392,19 +520,83 @@ void ADCee::scan() {
void ADCee::begin() {
noInterrupts();
//ADC1 config sequence
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable ADC1 clock
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
// Disable the DMA controller for ADC1
ADC1->CR2 &= ~ADC_CR2_DMA;
ADC1->CR2 &= ~(1 << 1); //Single conversion
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC1->CR2 |= (1 << 0); // Switch on ADC1
// Wait for ADC1 to become ready (calibration complete)
while (!(ADC1->CR2 & ADC_CR2_ADON)) {
}
#if defined(ADC2)
// Enable the ADC2 clock
RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
// Initialize ADC2
ADC2->CR1 = 0; // Disable all channels
ADC2->CR2 = 0; // Clear CR2 register
ADC2->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
ADC2->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
ADC2->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
ADC2->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
ADC2->CR2 &= ~(1 << 1); //Single conversion
ADC2->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
ADC2->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC2->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC2->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
// Enable the ADC
ADC2->CR2 |= ADC_CR2_ADON;
// Wait for ADC2 to become ready (calibration complete)
while (!(ADC2->CR2 & ADC_CR2_ADON)) {
}
// Perform ADC3 calibration (optional)
// ADC3->CR2 |= ADC_CR2_CAL;
// while (ADC3->CR2 & ADC_CR2_CAL) {
// }
#endif
#if defined(ADC3)
// Enable the ADC3 clock
RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
// Initialize ADC3
ADC3->CR1 = 0; // Disable all channels
ADC3->CR2 = 0; // Clear CR2 register
ADC3->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
ADC3->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
ADC3->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
ADC3->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
ADC3->CR2 &= ~(1 << 1); //Single conversion
ADC3->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
ADC3->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC3->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
ADC3->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
// Enable the ADC
ADC3->CR2 |= ADC_CR2_ADON;
// Wait for ADC3 to become ready (calibration complete)
while (!(ADC3->CR2 & ADC_CR2_ADON)) {
}
// Perform ADC3 calibration (optional)
// ADC3->CR2 |= ADC_CR2_CAL;
// while (ADC3->CR2 & ADC_CR2_CAL) {
// }
#endif
interrupts();
}
#endif

View File

@@ -31,12 +31,12 @@
#include "Sensors.h"
#include "Turnouts.h"
#if defined(ARDUINO_ARCH_SAMC)
#if defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_GIGA)
ExternalEEPROM EEPROM;
#endif
void EEStore::init() {
#if defined(ARDUINO_ARCH_SAMC)
#if defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_GIGA)
EEPROM.begin(0x50); // Address for Microchip 24-series EEPROM with all three
// A pins grounded (0b1010000 = 0x50)
#endif

View File

@@ -26,7 +26,7 @@
#include <Arduino.h>
#if defined(ARDUINO_ARCH_SAMC)
#if defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_GIGA)
#include <SparkFun_External_EEPROM.h>
extern ExternalEEPROM EEPROM;
#else

View File

@@ -85,7 +85,7 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
// when pausingTask is set, that is the ONLY task that gets any service,
// and all others will have their locos stopped, then resumed after the pausing task resumes.
byte RMFT2::flags[MAX_FLAGS];
Print * RMFT2::LCCSerial=0;
LookList * RMFT2::sequenceLookup=NULL;
LookList * RMFT2::onThrowLookup=NULL;
LookList * RMFT2::onCloseLookup=NULL;
@@ -176,23 +176,26 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
onDeactivateLookup=LookListLoader(OPCODE_ONDEACTIVATE);
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
onClockLookup=LookListLoader(OPCODE_ONTIME);
#ifndef IO_NO_HAL
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
#endif
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
// onLCCLookup is not the same so not loaded here.
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
if (compileFeatures & FEATURE_SIGNAL) {
onRedLookup=LookListLoader(OPCODE_ONRED);
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
}
}
int progCounter;
for (progCounter=0;; SKIPOP){
@@ -343,13 +346,65 @@ void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16
reject=!parseSlash(stream,paramCount,p);
opcode=0;
break;
case 'L':
if (compileFeatures & FEATURE_LCC) {
// This entire code block is compiled out if LLC macros not used
if (paramCount==0) { //<L> LCC adapter introducing self
LCCSerial=stream; // now we know where to send events we raise
// loop through all possible sent events
for (int progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
if (opcode==OPCODE_LCCX) { // long form LCC
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
}}
// we stream the hex events we wish to listen to
// and at the same time build the event index looku.
int eventIndex=0;
for (int progCounter=0;; SKIPOP) {
byte opcode=GET_OPCODE;
if (opcode==OPCODE_ENDEXRAIL) break;
if (opcode==OPCODE_ONLCC) {
onLCCLookup[eventIndex]=progCounter; // TODO skip...
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
eventIndex,
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
eventIndex++;
}
}
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
opcode=0;
break;
}
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
int16_t eventid=p[0];
reject=eventid<0 || eventid>=countLCCLookup;
if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
opcode=0;
}
}
break;
default: // other commands pass through
break;
}
if (reject) {
opcode=0;
StringFormatter::send(stream,F("<X>"));
StringFormatter::send(stream,F("<X>\n"));
}
}
@@ -377,17 +432,19 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
}
}
// do the signals
// flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"),
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
sigid & SIGNAL_ID_MASK);
}
if (compileFeatures & FEATURE_SIGNAL) {
// do the signals
// flags[n] represents the state of the nth signal in the table
for (int sigslot=0;;sigslot++) {
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
if (sigid==0) break; // end of signal list
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
StringFormatter::send(stream,F("\n%S[%d]"),
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
sigid & SIGNAL_ID_MASK);
}
}
StringFormatter::send(stream,F(" *>\n"));
return true;
}
@@ -780,6 +837,20 @@ void RMFT2::loop2() {
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
break;
case OPCODE_SET_POWER:
// operand is TRACK_POWER , trackid
//byte thistrack=getOperand(1);
switch (operand) {
case TRACK_POWER_0:
TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::OFF, getOperand(1));
break;
case TRACK_POWER_1:
TrackManager::setTrackPower(TrackManager::isProg(getOperand(1)), false, POWERMODE::ON, getOperand(1));
break;
}
break;
case OPCODE_SET_TRACK:
// operand is trackmode<<8 | track id
@@ -1020,7 +1091,21 @@ void RMFT2::loop2() {
invert=false;
}
break;
case OPCODE_LCC: // short form LCC
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
StringFormatter::send(LCCSerial,F("<L x%h>"),(uint16_t)operand);
break;
case OPCODE_LCCX: // long form LCC
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
StringFormatter::send(LCCSerial,F("<L x%h%h%h%h>\n"),
getOperand(progCounter,1),
getOperand(progCounter,2),
getOperand(progCounter,3),
getOperand(progCounter,0)
);
break;
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
@@ -1058,6 +1143,7 @@ void RMFT2::loop2() {
case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime
case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime
case OPCODE_ONCLOSE: // Turnout event catchers ignored here
case OPCODE_ONLCC: // LCC event catchers ignored here
case OPCODE_ONTHROW:
case OPCODE_ONACTIVATE: // Activate event catchers ignored here
case OPCODE_ONDEACTIVATE:
@@ -1127,6 +1213,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
}
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return; // dont compile code below
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
// Schedule any event handler for this signal change.
@@ -1194,6 +1281,7 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
}
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
int16_t sigslot=getSignalSlot(id);
if (sigslot<0) return false;
return (flags[sigslot] & SIGNAL_MASK) == rag;
@@ -1246,8 +1334,10 @@ void RMFT2::powerEvent(int16_t track, bool overload) {
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id);
if (pc<0) return;
if (pc>=0) startNonRecursiveTask(reason,id,pc);
}
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
// Check we dont already have a task running this handler
RMFT2 * task=loopTask;
while(task) {

View File

@@ -59,15 +59,14 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_ROSTER,OPCODE_KILLALL,
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
OPCODE_SET_TRACK,
OPCODE_SET_TRACK,OPCODE_SET_POWER,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE,
OPCODE_ONCLOCKTIME,
OPCODE_ONTIME,
#ifndef IO_NO_HAL
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_IFTTPOSITION,OPCODE_WAITFORTT,
#endif
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
OPCODE_ONOVERLOAD,
// OPcodes below this point are skip-nesting IF operations
@@ -81,7 +80,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
OPCODE_IFLOCO
OPCODE_IFLOCO,
OPCODE_IFTTPOSITION
};
// Ensure thrunge_lcd is put last as there may be more than one display,
@@ -95,7 +95,11 @@ enum thrunger: byte {
thrunge_lcd, // Must be last!!
};
// Flag bits for compile time features.
static const byte FEATURE_SIGNAL= 0x80;
static const byte FEATURE_LCC = 0x40;
static const byte FEATURE_ROSTER= 0x20;
// Flag bits for status of hardware and TPL
static const byte SECTION_FLAG = 0x80;
@@ -174,6 +178,7 @@ private:
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
static uint16_t getOperand(int progCounter,byte n);
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
static RMFT2 * loopTask;
static RMFT2 * pausingTask;
void delayMe(long millisecs);
@@ -192,6 +197,7 @@ private:
static const HIGHFLASH byte RouteCode[];
static const HIGHFLASH int16_t SignalDefinitions[];
static byte flags[MAX_FLAGS];
static Print * LCCSerial;
static LookList * sequenceLookup;
static LookList * onThrowLookup;
static LookList * onCloseLookup;
@@ -206,6 +212,10 @@ private:
static LookList * onRotateLookup;
#endif
static LookList * onOverloadLookup;
static const int countLCCLookup;
static int onLCCLookup[];
static const byte compileFeatures;
// Local variables - exist for each instance/task
RMFT2 *next; // loop chain

View File

@@ -86,6 +86,8 @@
#undef LATCH
#undef LCD
#undef SCREEN
#undef LCC
#undef LCCX
#undef LCN
#undef MOVETT
#undef ONACTIVATE
@@ -94,6 +96,7 @@
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONLCC
#undef ONTIME
#undef ONCLOCKTIME
#undef ONCLOCKMINS
@@ -138,6 +141,7 @@
#undef SERVO_SIGNAL
#undef SET
#undef SET_TRACK
#undef SET_POWER
#undef SETLOCO
#undef SIGNAL
#undef SIGNALH
@@ -192,7 +196,7 @@
#define ENDTASK
#define ESTOP
#define EXRAIL
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description)
#define EXTT_TURNTABLE(id,vpin,home,description)
#define FADE(pin,value,ms)
#define FOFF(func)
#define FOLLOW(route)
@@ -220,7 +224,9 @@
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
#define LATCH(sensor_id)
#define LATCH(sensor_id)
#define LCC(eventid)
#define LCCX(senderid,eventid)
#define LCD(row,msg)
#define SCREEN(display,row,msg)
#define LCN(msg)
@@ -235,6 +241,7 @@
#define ONDEACTIVATE(addr,subaddr)
#define ONDEACTIVATEL(linear)
#define ONCLOSE(turnout_id)
#define ONLCC(sender,event)
#define ONGREEN(signal_id)
#define ONRED(signal_id)
#define ONROTATE(turntable_id)
@@ -275,6 +282,7 @@
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
#define SET(pin)
#define SET_TRACK(track,mode)
#define SET_POWER(track,onoff)
#define SETLOCO(loco)
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin)

View File

@@ -63,6 +63,11 @@
// (10#mins)%100)
#define STRIP_ZERO(value) 10##value%100
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
//const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
//const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
// Pass 1 Implements aliases
#include "EXRAIL2MacroReset.h"
#undef ALIAS
@@ -74,12 +79,34 @@
#include "EXRAIL2MacroReset.h"
#undef HAL
#define HAL(haltype,params...) haltype::create(params);
#undef EXTT_TURNTABLE
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) EXTurntable::create(vpin,1,i2c_address);
void exrailHalSetup() {
#include "myAutomation.h"
}
// Pass 1c detect compile time featurtes
#include "EXRAIL2MacroReset.h"
#undef SIGNAL
#define SIGNAL(redpin,amberpin,greenpin) | FEATURE_SIGNAL
#undef SIGNALH
#define SIGNALH(redpin,amberpin,greenpin) | FEATURE_SIGNAL
#undef SERVO_SIGNAL
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) | FEATURE_SIGNAL
#undef DCC_SIGNAL
#define DCC_SIGNAL(id,addr,subaddr) | FEATURE_SIGNAL
#undef VIRTUAL_SIGNAL
#define VIRTUAL_SIGNAL(id) | FEATURE_SIGNAL
#undef LCC
#define LCC(eventid) | FEATURE_LCC
#undef LCCX
#define LCCX(senderid,eventid) | FEATURE_LCC
#undef ONLCC
#define ONLCC(senderid,eventid) | FEATURE_LCC
const byte RMFT2::compileFeatures = 0
#include "myAutomation.h"
;
// Pass 2 create throttle route list
#include "EXRAIL2MacroReset.h"
#undef ROUTE
@@ -197,7 +224,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
#undef DCC_TURNTABLE
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
#undef EXTT_TURNTABLE
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) O_DESC(id,description)
#define EXTT_TURNTABLE(id,vpin,home,description...) O_DESC(id,description)
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
switch (turntableId) {
@@ -273,6 +300,16 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#include "myAutomation.h"
0,0,0,0 };
// Pass 9 ONLCC counter and lookup array
#include "EXRAIL2MacroReset.h"
#undef ONLCC
#define ONLCC(sender,event) +1
const int RMFT2::countLCCLookup=0
#include "myAutomation.h"
;
int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
// Last Pass : create main routes table
// Only undef the macros, not dummy them.
#define RMFT2_UNDEF_ONLY
@@ -317,7 +354,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ESTOP OPCODE_SPEED,V(1),
#define EXRAIL
#ifndef IO_NO_HAL
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(i2c_address),OPCODE_PAD,V(home),
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
#endif
#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),
@@ -349,6 +386,11 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define JOIN OPCODE_JOIN,0,0,
#define KILLALL OPCODE_KILLALL,0,0,
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
#define LCC(eventid) OPCODE_LCC,V(eventid),
#define LCCX(sender,event) OPCODE_LCCX,V(event),\
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
#define LCD(id,msg) PRINT(msg)
#define SCREEN(display,id,msg) PRINT(msg)
#define LCN(msg) PRINT(msg)
@@ -357,6 +399,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
#define ONLCC(sender,event) OPCODE_ONLCC,V(event),\
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
#define ONTIME(value) OPCODE_ONTIME,V(value),
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
@@ -407,11 +453,12 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
#define SET(pin) OPCODE_SET,V(pin),
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
#define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track),
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
#define SIGNAL(redpin,amberpin,greenpin)
#define SIGNALH(redpin,amberpin,greenpin)
#define SPEED(speed) OPCODE_SPEED,V(speed),
#define START(route) OPCODE_START,V(route),
#define START(route) OPCODE_START,V(route),
#define STOP OPCODE_SPEED,V(0),
#define THROW(id) OPCODE_THROW,V(id),
#ifndef IO_NO_HAL

View File

@@ -1 +1 @@
#define GITHUB_SHA "devel-202308302157Z"
#define GITHUB_SHA "devel-202310230944Z"

2003
GigaHardwareTimer.cpp Normal file

File diff suppressed because it is too large Load Diff

220
GigaHardwareTimer.h Normal file
View File

@@ -0,0 +1,220 @@
/****************************************************************************************************************************
HardwareTimer.h
For Portenta_H7 boards
Written by Khoi Hoang
Built by Khoi Hoang https://github.com/khoih-prog/Portenta_H7_TimerInterrupt
Licensed under MIT license
Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by
unsigned long miliseconds), you just consume only one Portenta_H7 STM32 timer and avoid conflicting with other cores' tasks.
The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers
Therefore, their executions are not blocked by bad-behaving functions / tasks.
This important feature is absolutely necessary for mission-critical tasks.
Version: 1.4.0
Version Modified By Date Comments
------- ----------- ---------- -----------
1.2.1 K.Hoang 15/09/2021 Initial coding for Portenta_H7
1.3.0 K.Hoang 17/09/2021 Add PWM features and examples
1.3.1 K.Hoang 21/09/2021 Fix warnings in PWM examples
1.4.0 K.Hoang 22/01/2022 Fix `multiple-definitions` linker error. Fix bug
*****************************************************************************************************************************/
// Modified from stm32 core v2.0.0
/*
Copyright (c) 2017 Daniel Fekete
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Copyright (c) 2019 STMicroelectronics
Modified to support Arduino_Core_STM32
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef GIGAHARDWARETIMER_H_
#define GIGAHARDWARETIMER_H_
#if defined(ARDUINO_GIGA)
/* Includes ------------------------------------------------------------------*/
#include "Gigatimer.h"
#if defined(HAL_TIM_MODULE_ENABLED) && !defined(HAL_TIM_MODULE_ONLY)
#define TIMER_CHANNELS 4 // channel5 and channel 6 are not considered here has they don't have gpio output and they don't have interrupt
typedef enum
{
TIMER_DISABLED, // == TIM_OCMODE_TIMING no output, useful for only-interrupt
// Output Compare
TIMER_OUTPUT_COMPARE, // == Obsolete, use TIMER_DISABLED instead. Kept for compatibility reason
TIMER_OUTPUT_COMPARE_ACTIVE, // == TIM_OCMODE_ACTIVE pin is set high when counter == channel compare
TIMER_OUTPUT_COMPARE_INACTIVE, // == TIM_OCMODE_INACTIVE pin is set low when counter == channel compare
TIMER_OUTPUT_COMPARE_TOGGLE, // == TIM_OCMODE_TOGGLE pin toggles when counter == channel compare
TIMER_OUTPUT_COMPARE_PWM1, // == TIM_OCMODE_PWM1 pin high when counter < channel compare, low otherwise
TIMER_OUTPUT_COMPARE_PWM2, // == TIM_OCMODE_PWM2 pin low when counter < channel compare, high otherwise
TIMER_OUTPUT_COMPARE_FORCED_ACTIVE, // == TIM_OCMODE_FORCED_ACTIVE pin always high
TIMER_OUTPUT_COMPARE_FORCED_INACTIVE, // == TIM_OCMODE_FORCED_INACTIVE pin always low
//Input capture
TIMER_INPUT_CAPTURE_RISING, // == TIM_INPUTCHANNELPOLARITY_RISING
TIMER_INPUT_CAPTURE_FALLING, // == TIM_INPUTCHANNELPOLARITY_FALLING
TIMER_INPUT_CAPTURE_BOTHEDGE, // == TIM_INPUTCHANNELPOLARITY_BOTHEDGE
// Used 2 channels for a single pin. One channel in TIM_INPUTCHANNELPOLARITY_RISING another channel in TIM_INPUTCHANNELPOLARITY_FALLING.
// Channels must be used by pair: CH1 with CH2, or CH3 with CH4
// This mode is very useful for Frequency and Dutycycle measurement
TIMER_INPUT_FREQ_DUTY_MEASUREMENT,
TIMER_NOT_USED = 0xFFFF // This must be the last item of this enum
} TimerModes_t;
typedef enum
{
TICK_FORMAT, // default
MICROSEC_FORMAT,
HERTZ_FORMAT,
} TimerFormat_t;
typedef enum
{
RESOLUTION_1B_COMPARE_FORMAT = 1, // used for Dutycycle: [0 .. 1]
RESOLUTION_2B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 3]
RESOLUTION_3B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 7]
RESOLUTION_4B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 15]
RESOLUTION_5B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 31]
RESOLUTION_6B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 63]
RESOLUTION_7B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 127]
RESOLUTION_8B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 255]
RESOLUTION_9B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 511]
RESOLUTION_10B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 1023]
RESOLUTION_11B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 2047]
RESOLUTION_12B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 4095]
RESOLUTION_13B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 8191]
RESOLUTION_14B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 16383]
RESOLUTION_15B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 32767]
RESOLUTION_16B_COMPARE_FORMAT, // used for Dutycycle: [0 .. 65535]
TICK_COMPARE_FORMAT = 0x80, // default
MICROSEC_COMPARE_FORMAT,
HERTZ_COMPARE_FORMAT,
PERCENT_COMPARE_FORMAT, // used for Dutycycle
} TimerCompareFormat_t;
#ifdef __cplusplus
#include <functional>
using callback_function_t = std::function<void(void)>;
/* Class --------------------------------------------------------*/
class HardwareTimer
{
public:
HardwareTimer(TIM_TypeDef *instance);
~HardwareTimer(); // destructor
void pause(void); // Pause counter and all output channels
void pauseChannel(uint32_t channel); // Timer is still running but channel (output and interrupt) is disabled
void resume(void); // Resume counter and all output channels
void resumeChannel(uint32_t channel); // Resume only one channel
void setPrescaleFactor(uint32_t prescaler); // set prescaler register (which is factor value - 1)
uint32_t getPrescaleFactor();
void setOverflow(uint32_t val, TimerFormat_t format =
TICK_FORMAT); // set AutoReload register depending on format provided
uint32_t getOverflow(TimerFormat_t format = TICK_FORMAT); // return overflow depending on format provided
void setPWM(uint32_t channel, PinName pin, uint32_t frequency, uint32_t dutycycle,
callback_function_t PeriodCallback = nullptr,
callback_function_t CompareCallback = nullptr); // Set all in one command freq in HZ, Duty in percentage. Including both interrup.
void setPWM(uint32_t channel, uint32_t pin, uint32_t frequency, uint32_t dutycycle,
callback_function_t PeriodCallback = nullptr, callback_function_t CompareCallback = nullptr);
void setCount(uint32_t val, TimerFormat_t format =
TICK_FORMAT); // set timer counter to value 'val' depending on format provided
uint32_t getCount(TimerFormat_t format =
TICK_FORMAT); // return current counter value of timer depending on format provided
void setMode(uint32_t channel, TimerModes_t mode,
PinName pin = NC); // Configure timer channel with specified mode on specified pin if available
void setMode(uint32_t channel, TimerModes_t mode, uint32_t pin);
TimerModes_t getMode(uint32_t channel); // Retrieve configured mode
void setPreloadEnable(bool value); // Configure overflow preload enable setting
uint32_t getCaptureCompare(uint32_t channel,
TimerCompareFormat_t format = TICK_COMPARE_FORMAT); // return Capture/Compare register value of specified channel depending on format provided
void setCaptureCompare(uint32_t channel, uint32_t compare,
TimerCompareFormat_t format = TICK_COMPARE_FORMAT); // set Compare register value of specified channel depending on format provided
void setInterruptPriority(uint32_t preemptPriority, uint32_t subPriority); // set interrupt priority
//Add interrupt to period update
void attachInterrupt(callback_function_t
callback); // Attach interrupt callback which will be called upon update event (timer rollover)
void detachInterrupt(); // remove interrupt callback which was attached to update event
bool hasInterrupt(); //returns true if a timer rollover interrupt has already been set
//Add interrupt to capture/compare channel
void attachInterrupt(uint32_t channel,
callback_function_t callback); // Attach interrupt callback which will be called upon compare match event of specified channel
void detachInterrupt(uint32_t
channel); // remove interrupt callback which was attached to compare match event of specified channel
bool hasInterrupt(uint32_t channel); //returns true if an interrupt has already been set on the channel compare match
void timerHandleDeinit(); // Timer deinitialization
// Refresh() is usefull while timer is running after some registers update
void refresh(
void); // Generate update event to force all registers (Autoreload, prescaler, compare) to be taken into account
uint32_t getTimerClkFreq(); // return timer clock frequency in Hz.
static void captureCompareCallback(TIM_HandleTypeDef
*htim); // Generic Caputre and Compare callback which will call user callback
static void updateCallback(TIM_HandleTypeDef
*htim); // Generic Update (rollover) callback which will call user callback
// The following function(s) are available for more advanced timer options
TIM_HandleTypeDef *getHandle(); // return the handle address for HAL related configuration
int getChannel(uint32_t channel);
int getLLChannel(uint32_t channel);
int getIT(uint32_t channel);
int getAssociatedChannel(uint32_t channel);
#if defined(TIM_CCER_CC1NE)
bool isComplementaryChannel[TIMER_CHANNELS];
#endif
private:
TimerModes_t _ChannelMode[TIMER_CHANNELS];
timerObj_t _timerObj;
callback_function_t callbacks[1 +
TIMER_CHANNELS]; //Callbacks: 0 for update, 1-4 for channels. (channel5/channel6, if any, doesn't have interrupt)
};
extern timerObj_t *HardwareTimer_Handle[TIMER_NUM];
extern timer_index_t get_timer_index(TIM_TypeDef *htim);
#endif /* __cplusplus */
#endif // HAL_TIM_MODULE_ENABLED && !HAL_TIM_MODULE_ONLY
#endif
#endif // GIGAHARDWARETIMER_H_

950
Gigatimer.c Normal file
View File

@@ -0,0 +1,950 @@
/****************************************************************************************************************************
timer.c
For Portenta_H7 boards
Written by Khoi Hoang
Built by Khoi Hoang https://github.com/khoih-prog/Portenta_H7_TimerInterrupt
Licensed under MIT license
Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by
unsigned long miliseconds), you just consume only one Portenta_H7 STM32 timer and avoid conflicting with other cores' tasks.
The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers
Therefore, their executions are not blocked by bad-behaving functions / tasks.
This important feature is absolutely necessary for mission-critical tasks.
Version: 1.4.0
Version Modified By Date Comments
------- ----------- ---------- -----------
1.2.1 K.Hoang 15/09/2021 Initial coding for Portenta_H7
1.3.0 K.Hoang 17/09/2021 Add PWM features and examples
1.3.1 K.Hoang 21/09/2021 Fix warnings in PWM examples
1.4.0 K.Hoang 22/01/2022 Fix `multiple-definitions` linker error. Fix bug
*****************************************************************************************************************************/
// Modified from stm32 core v2.0.0
/*
*******************************************************************************
Copyright (c) 2019, STMicroelectronics
All rights reserved.
This software component is licensed by ST under BSD 3-Clause license,
the "License"; You may not use this file except in compliance with the
License. You may obtain a copy of the License at:
opensource.org/licenses/BSD-3-Clause
*******************************************************************************
*/
#if defined(ARDUINO_GIGA)
#include "Gigatimer.h"
#ifdef __cplusplus
extern "C" {
#endif
#if defined(HAL_TIM_MODULE_ENABLED) && !defined(HAL_TIM_MODULE_ONLY)
/* Private Functions */
/* Aim of the function is to get _timerObj pointer using htim pointer */
/* Highly inspired from magical linux kernel's "container_of" */
/* (which was not directly used since not compatible with IAR toolchain) */
timerObj_t *get_timer_obj(TIM_HandleTypeDef *htim)
{
timerObj_t *obj;
obj = (timerObj_t *)((char *)htim - offsetof(timerObj_t, handle));
return (obj);
}
/**
@brief TIMER Initialization - clock init and nvic init
@param htim_base: TIM handle
@retval None
*/
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim_base)
{
timerObj_t *obj = get_timer_obj(htim_base);
enableTimerClock(htim_base);
// configure Update interrupt
HAL_NVIC_SetPriority(getTimerUpIrq(htim_base->Instance), obj->preemptPriority, obj->subPriority);
HAL_NVIC_EnableIRQ(getTimerUpIrq(htim_base->Instance));
if (getTimerCCIrq(htim_base->Instance) != getTimerUpIrq(htim_base->Instance))
{
// configure Capture Compare interrupt
HAL_NVIC_SetPriority(getTimerCCIrq(htim_base->Instance), obj->preemptPriority, obj->subPriority);
HAL_NVIC_EnableIRQ(getTimerCCIrq(htim_base->Instance));
}
}
/**
@brief TIMER Deinitialization - clock and nvic
@param htim_base: TIM handle
@retval None
*/
void HAL_TIM_Base_MspDeInit(TIM_HandleTypeDef *htim_base)
{
disableTimerClock(htim_base);
HAL_NVIC_DisableIRQ(getTimerUpIrq(htim_base->Instance));
HAL_NVIC_DisableIRQ(getTimerCCIrq(htim_base->Instance));
}
/**
@brief Initializes the TIM Output Compare MSP.
@param htim: TIM handle
@retval None
*/
void HAL_TIM_OC_MspInit(TIM_HandleTypeDef *htim)
{
timerObj_t *obj = get_timer_obj(htim);
enableTimerClock(htim);
// configure Update interrupt
HAL_NVIC_SetPriority(getTimerUpIrq(htim->Instance), obj->preemptPriority, obj->subPriority);
HAL_NVIC_EnableIRQ(getTimerUpIrq(htim->Instance));
if (getTimerCCIrq(htim->Instance) != getTimerUpIrq(htim->Instance))
{
// configure Capture Compare interrupt
HAL_NVIC_SetPriority(getTimerCCIrq(htim->Instance), obj->preemptPriority, obj->subPriority);
HAL_NVIC_EnableIRQ(getTimerCCIrq(htim->Instance));
}
}
/**
@brief DeInitialize TIM Output Compare MSP.
@param htim: TIM handle
@retval None
*/
void HAL_TIM_OC_MspDeInit(TIM_HandleTypeDef *htim)
{
disableTimerClock(htim);
HAL_NVIC_DisableIRQ(getTimerUpIrq(htim->Instance));
HAL_NVIC_DisableIRQ(getTimerCCIrq(htim->Instance));
}
/**
@brief Initializes the TIM Input Capture MSP.
@param htim: TIM handle
@retval None
*/
void HAL_TIM_IC_MspInit(TIM_HandleTypeDef *htim)
{
enableTimerClock(htim);
}
/**
@brief DeInitialize TIM Input Capture MSP.
@param htim: TIM handle
@retval None
*/
void HAL_TIM_IC_MspDeInit(TIM_HandleTypeDef *htim)
{
disableTimerClock(htim);
}
/* Exported functions */
/**
@brief Enable the timer clock
@param htim: TIM handle
@retval None
*/
void enableTimerClock(TIM_HandleTypeDef *htim)
{
// Enable TIM clock
#if defined(TIM1_BASE)
if (htim->Instance == TIM1)
{
__HAL_RCC_TIM1_CLK_ENABLE();
}
#endif
#if defined(TIM2_BASE)
if (htim->Instance == TIM2)
{
__HAL_RCC_TIM2_CLK_ENABLE();
}
#endif
#if defined(TIM3_BASE)
if (htim->Instance == TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE();
}
#endif
#if defined(TIM4_BASE)
if (htim->Instance == TIM4)
{
__HAL_RCC_TIM4_CLK_ENABLE();
}
#endif
#if defined(TIM5_BASE)
if (htim->Instance == TIM5)
{
__HAL_RCC_TIM5_CLK_ENABLE();
}
#endif
#if defined(TIM6_BASE)
if (htim->Instance == TIM6)
{
__HAL_RCC_TIM6_CLK_ENABLE();
}
#endif
#if defined(TIM7_BASE)
if (htim->Instance == TIM7)
{
__HAL_RCC_TIM7_CLK_ENABLE();
}
#endif
#if defined(TIM8_BASE)
if (htim->Instance == TIM8)
{
__HAL_RCC_TIM8_CLK_ENABLE();
}
#endif
#if defined(TIM9_BASE)
if (htim->Instance == TIM9)
{
__HAL_RCC_TIM9_CLK_ENABLE();
}
#endif
#if defined(TIM10_BASE)
if (htim->Instance == TIM10)
{
__HAL_RCC_TIM10_CLK_ENABLE();
}
#endif
#if defined(TIM11_BASE)
if (htim->Instance == TIM11)
{
__HAL_RCC_TIM11_CLK_ENABLE();
}
#endif
#if defined(TIM12_BASE)
if (htim->Instance == TIM12)
{
__HAL_RCC_TIM12_CLK_ENABLE();
}
#endif
#if defined(TIM13_BASE)
if (htim->Instance == TIM13)
{
__HAL_RCC_TIM13_CLK_ENABLE();
}
#endif
#if defined(TIM14_BASE)
if (htim->Instance == TIM14)
{
__HAL_RCC_TIM14_CLK_ENABLE();
}
#endif
#if defined(TIM15_BASE)
if (htim->Instance == TIM15)
{
__HAL_RCC_TIM15_CLK_ENABLE();
}
#endif
#if defined(TIM16_BASE)
if (htim->Instance == TIM16)
{
__HAL_RCC_TIM16_CLK_ENABLE();
}
#endif
#if defined(TIM17_BASE)
if (htim->Instance == TIM17)
{
__HAL_RCC_TIM17_CLK_ENABLE();
}
#endif
#if defined(TIM18_BASE)
if (htim->Instance == TIM18)
{
__HAL_RCC_TIM18_CLK_ENABLE();
}
#endif
#if defined(TIM19_BASE)
if (htim->Instance == TIM19)
{
__HAL_RCC_TIM19_CLK_ENABLE();
}
#endif
#if defined(TIM20_BASE)
if (htim->Instance == TIM20)
{
__HAL_RCC_TIM20_CLK_ENABLE();
}
#endif
#if defined(TIM21_BASE)
if (htim->Instance == TIM21)
{
__HAL_RCC_TIM21_CLK_ENABLE();
}
#endif
#if defined(TIM22_BASE)
if (htim->Instance == TIM22)
{
__HAL_RCC_TIM22_CLK_ENABLE();
}
#endif
}
/**
@brief Disable the timer clock
@param htim: TIM handle
@retval None
*/
void disableTimerClock(TIM_HandleTypeDef *htim)
{
// Enable TIM clock
#if defined(TIM1_BASE)
if (htim->Instance == TIM1)
{
__HAL_RCC_TIM1_CLK_DISABLE();
}
#endif
#if defined(TIM2_BASE)
if (htim->Instance == TIM2)
{
__HAL_RCC_TIM2_CLK_DISABLE();
}
#endif
#if defined(TIM3_BASE)
if (htim->Instance == TIM3)
{
__HAL_RCC_TIM3_CLK_DISABLE();
}
#endif
#if defined(TIM4_BASE)
if (htim->Instance == TIM4)
{
__HAL_RCC_TIM4_CLK_DISABLE();
}
#endif
#if defined(TIM5_BASE)
if (htim->Instance == TIM5)
{
__HAL_RCC_TIM5_CLK_DISABLE();
}
#endif
#if defined(TIM6_BASE)
if (htim->Instance == TIM6)
{
__HAL_RCC_TIM6_CLK_DISABLE();
}
#endif
#if defined(TIM7_BASE)
if (htim->Instance == TIM7)
{
__HAL_RCC_TIM7_CLK_DISABLE();
}
#endif
#if defined(TIM8_BASE)
if (htim->Instance == TIM8)
{
__HAL_RCC_TIM8_CLK_DISABLE();
}
#endif
#if defined(TIM9_BASE)
if (htim->Instance == TIM9)
{
__HAL_RCC_TIM9_CLK_DISABLE();
}
#endif
#if defined(TIM10_BASE)
if (htim->Instance == TIM10)
{
__HAL_RCC_TIM10_CLK_DISABLE();
}
#endif
#if defined(TIM11_BASE)
if (htim->Instance == TIM11)
{
__HAL_RCC_TIM11_CLK_DISABLE();
}
#endif
#if defined(TIM12_BASE)
if (htim->Instance == TIM12)
{
__HAL_RCC_TIM12_CLK_DISABLE();
}
#endif
#if defined(TIM13_BASE)
if (htim->Instance == TIM13)
{
__HAL_RCC_TIM13_CLK_DISABLE();
}
#endif
#if defined(TIM14_BASE)
if (htim->Instance == TIM14)
{
__HAL_RCC_TIM14_CLK_DISABLE();
}
#endif
#if defined(TIM15_BASE)
if (htim->Instance == TIM15)
{
__HAL_RCC_TIM15_CLK_DISABLE();
}
#endif
#if defined(TIM16_BASE)
if (htim->Instance == TIM16)
{
__HAL_RCC_TIM16_CLK_DISABLE();
}
#endif
#if defined(TIM17_BASE)
if (htim->Instance == TIM17)
{
__HAL_RCC_TIM17_CLK_DISABLE();
}
#endif
#if defined(TIM18_BASE)
if (htim->Instance == TIM18)
{
__HAL_RCC_TIM18_CLK_DISABLE();
}
#endif
#if defined(TIM19_BASE)
if (htim->Instance == TIM19)
{
__HAL_RCC_TIM19_CLK_DISABLE();
}
#endif
#if defined(TIM20_BASE)
if (htim->Instance == TIM20)
{
__HAL_RCC_TIM20_CLK_DISABLE();
}
#endif
#if defined(TIM21_BASE)
if (htim->Instance == TIM21)
{
__HAL_RCC_TIM21_CLK_DISABLE();
}
#endif
#if defined(TIM22_BASE)
if (htim->Instance == TIM22)
{
__HAL_RCC_TIM22_CLK_DISABLE();
}
#endif
}
/**
@brief This function return IRQ number corresponding to update interrupt event of timer instance.
@param tim: timer instance
@retval IRQ number
*/
IRQn_Type getTimerUpIrq(TIM_TypeDef *tim)
{
IRQn_Type IRQn = NonMaskableInt_IRQn;
if (tim != (TIM_TypeDef *)NC)
{
/* Get IRQn depending on TIM instance */
switch ((uint32_t)tim)
{
#if defined(TIM1_BASE)
case (uint32_t)TIM1_BASE:
IRQn = TIM1_IRQn;
break;
#endif
#if defined(TIM2_BASE)
case (uint32_t)TIM2_BASE:
IRQn = TIM2_IRQn;
break;
#endif
#if defined(TIM3_BASE)
case (uint32_t)TIM3_BASE:
IRQn = TIM3_IRQn;
break;
#endif
#if defined(TIM4_BASE)
case (uint32_t)TIM4_BASE:
IRQn = TIM4_IRQn;
break;
#endif
#if defined(TIM5_BASE)
case (uint32_t)TIM5_BASE:
IRQn = TIM5_IRQn;
break;
#endif
// KH
#if 0
#if defined(TIM6_BASE)
case (uint32_t)TIM6_BASE:
IRQn = TIM6_IRQn;
break;
#endif
#endif
//////
#if defined(TIM7_BASE)
case (uint32_t)TIM7_BASE:
IRQn = TIM7_IRQn;
break;
#endif
#if defined(TIM8_BASE)
case (uint32_t)TIM8_BASE:
IRQn = TIM8_IRQn;
break;
#endif
#if defined(TIM9_BASE)
case (uint32_t)TIM9_BASE:
IRQn = TIM9_IRQn;
break;
#endif
#if defined(TIM10_BASE)
case (uint32_t)TIM10_BASE:
IRQn = TIM10_IRQn;
break;
#endif
#if defined(TIM11_BASE)
case (uint32_t)TIM11_BASE:
IRQn = TIM11_IRQn;
break;
#endif
#if defined(TIM12_BASE)
case (uint32_t)TIM12_BASE:
IRQn = TIM12_IRQn;
break;
#endif
#if defined(TIM13_BASE)
case (uint32_t)TIM13_BASE:
IRQn = TIM13_IRQn;
break;
#endif
#if defined(TIM14_BASE)
case (uint32_t)TIM14_BASE:
IRQn = TIM14_IRQn;
break;
#endif
#if defined(TIM15_BASE)
case (uint32_t)TIM15_BASE:
IRQn = TIM15_IRQn;
break;
#endif
#if defined(TIM16_BASE)
case (uint32_t)TIM16_BASE:
IRQn = TIM16_IRQn;
break;
#endif
#if defined(TIM17_BASE)
case (uint32_t)TIM17_BASE:
IRQn = TIM17_IRQn;
break;
#endif
#if defined(TIM18_BASE)
case (uint32_t)TIM18_BASE:
IRQn = TIM18_IRQn;
break;
#endif
#if defined(TIM19_BASE)
case (uint32_t)TIM19_BASE:
IRQn = TIM19_IRQn;
break;
#endif
#if defined(TIM20_BASE)
case (uint32_t)TIM20_BASE:
IRQn = TIM20_IRQn;
break;
#endif
#if defined(TIM21_BASE)
case (uint32_t)TIM21_BASE:
IRQn = TIM21_IRQn;
break;
#endif
#if defined(TIM22_BASE)
case (uint32_t)TIM22_BASE:
IRQn = TIM22_IRQn;
break;
#endif
default:
//_Error_Handler("TIM: Unknown timer IRQn", (int)tim);
break;
}
}
return IRQn;
}
/**
@brief This function return IRQ number corresponding to Capture or Compare interrupt event of timer instance.
@param tim: timer instance
@retval IRQ number
*/
IRQn_Type getTimerCCIrq(TIM_TypeDef *tim)
{
IRQn_Type IRQn = NonMaskableInt_IRQn;
if (tim != (TIM_TypeDef *)NC)
{
/* Get IRQn depending on TIM instance */
switch ((uint32_t)tim)
{
#if defined(TIM1_BASE)
case (uint32_t)TIM1_BASE:
IRQn = TIM1_CC_IRQn;
break;
#endif
#if defined(TIM2_BASE)
case (uint32_t)TIM2_BASE:
IRQn = TIM2_IRQn;
break;
#endif
#if defined(TIM3_BASE)
case (uint32_t)TIM3_BASE:
IRQn = TIM3_IRQn;
break;
#endif
#if defined(TIM4_BASE)
case (uint32_t)TIM4_BASE:
IRQn = TIM4_IRQn;
break;
#endif
#if defined(TIM5_BASE)
case (uint32_t)TIM5_BASE:
IRQn = TIM5_IRQn;
break;
#endif
#if 0
// KH
#if defined(TIM6_BASE)
case (uint32_t)TIM6_BASE:
IRQn = TIM6_IRQn;
break;
#endif
#endif
//////
#if defined(TIM7_BASE)
case (uint32_t)TIM7_BASE:
IRQn = TIM7_IRQn;
break;
#endif
#if defined(TIM8_BASE)
case (uint32_t)TIM8_BASE:
IRQn = TIM8_CC_IRQn;
break;
#endif
#if defined(TIM9_BASE)
case (uint32_t)TIM9_BASE:
IRQn = TIM9_IRQn;
break;
#endif
#if defined(TIM10_BASE)
case (uint32_t)TIM10_BASE:
IRQn = TIM10_IRQn;
break;
#endif
#if defined(TIM11_BASE)
case (uint32_t)TIM11_BASE:
IRQn = TIM11_IRQn;
break;
#endif
#if defined(TIM12_BASE)
case (uint32_t)TIM12_BASE:
IRQn = TIM12_IRQn;
break;
#endif
#if defined(TIM13_BASE)
case (uint32_t)TIM13_BASE:
IRQn = TIM13_IRQn;
break;
#endif
#if defined(TIM14_BASE)
case (uint32_t)TIM14_BASE:
IRQn = TIM14_IRQn;
break;
#endif
#if defined(TIM15_BASE)
case (uint32_t)TIM15_BASE:
IRQn = TIM15_IRQn;
break;
#endif
#if defined(TIM16_BASE)
case (uint32_t)TIM16_BASE:
IRQn = TIM16_IRQn;
break;
#endif
#if defined(TIM17_BASE)
case (uint32_t)TIM17_BASE:
IRQn = TIM17_IRQn;
break;
#endif
#if defined(TIM18_BASE)
case (uint32_t)TIM18_BASE:
IRQn = TIM18_IRQn;
break;
#endif
#if defined(TIM19_BASE)
case (uint32_t)TIM19_BASE:
IRQn = TIM19_IRQn;
break;
#endif
#if defined(TIM20_BASE)
case (uint32_t)TIM20_BASE:
IRQn = TIM20_CC_IRQn;
break;
#endif
#if defined(TIM21_BASE)
case (uint32_t)TIM21_BASE:
IRQn = TIM21_IRQn;
break;
#endif
#if defined(TIM22_BASE)
case (uint32_t)TIM22_BASE:
IRQn = TIM22_IRQn;
break;
#endif
break;
default:
//_Error_Handler("TIM: Unknown timer IRQn", (int)tim);
break;
}
}
return IRQn;
}
/**
@brief This function return the timer clock source.
@param tim: timer instance
@retval 1 = PCLK1 or 2 = PCLK2
*/
uint8_t getTimerClkSrc(TIM_TypeDef *tim)
{
uint8_t clkSrc = 0;
if (tim != (TIM_TypeDef *)NC)
#if defined(STM32F0xx) || defined(STM32G0xx)
/* TIMx source CLK is PCKL1 */
clkSrc = 1;
#else
{
/* Get source clock depending on TIM instance */
switch ((uint32_t)tim)
{
#if defined(TIM2_BASE)
case (uint32_t)TIM2:
#endif
#if defined(TIM3_BASE)
case (uint32_t)TIM3:
#endif
#if defined(TIM4_BASE)
case (uint32_t)TIM4:
#endif
#if defined(TIM5_BASE)
case (uint32_t)TIM5:
#endif
#if defined(TIM6_BASE)
case (uint32_t)TIM6:
#endif
#if defined(TIM7_BASE)
case (uint32_t)TIM7:
#endif
#if defined(TIM12_BASE)
case (uint32_t)TIM12:
#endif
#if defined(TIM13_BASE)
case (uint32_t)TIM13:
#endif
#if defined(TIM14_BASE)
case (uint32_t)TIM14:
#endif
#if defined(TIM18_BASE)
case (uint32_t)TIM18:
#endif
clkSrc = 1;
break;
#if defined(TIM1_BASE)
case (uint32_t)TIM1:
#endif
#if defined(TIM8_BASE)
case (uint32_t)TIM8:
#endif
#if defined(TIM9_BASE)
case (uint32_t)TIM9:
#endif
#if defined(TIM10_BASE)
case (uint32_t)TIM10:
#endif
#if defined(TIM11_BASE)
case (uint32_t)TIM11:
#endif
#if defined(TIM15_BASE)
case (uint32_t)TIM15:
#endif
#if defined(TIM16_BASE)
case (uint32_t)TIM16:
#endif
#if defined(TIM17_BASE)
case (uint32_t)TIM17:
#endif
#if defined(TIM19_BASE)
case (uint32_t)TIM19:
#endif
#if defined(TIM20_BASE)
case (uint32_t)TIM20:
#endif
#if defined(TIM21_BASE)
case (uint32_t)TIM21:
#endif
#if defined(TIM22_BASE)
case (uint32_t)TIM22:
#endif
clkSrc = 2;
break;
default:
////_Error_Handler("TIM: Unknown timer instance", (int)tim);
break;
}
}
#endif
return clkSrc;
}
#endif /* HAL_TIM_MODULE_ENABLED && !HAL_TIM_MODULE_ONLY */
#ifdef __cplusplus
}
#endif
#endif
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

198
Gigatimer.h Normal file
View File

@@ -0,0 +1,198 @@
/****************************************************************************************************************************
timer.h
For Portenta_H7 boards
Written by Khoi Hoang
Built by Khoi Hoang https://github.com/khoih-prog/Portenta_H7_TimerInterrupt
Licensed under MIT license
Now even you use all these new 16 ISR-based timers,with their maximum interval practically unlimited (limited only by
unsigned long miliseconds), you just consume only one Portenta_H7 STM32 timer and avoid conflicting with other cores' tasks.
The accuracy is nearly perfect compared to software timers. The most important feature is they're ISR-based timers
Therefore, their executions are not blocked by bad-behaving functions / tasks.
This important feature is absolutely necessary for mission-critical tasks.
Version: 1.4.0
Version Modified By Date Comments
------- ----------- ---------- -----------
1.2.1 K.Hoang 15/09/2021 Initial coding for Portenta_H7
1.3.0 K.Hoang 17/09/2021 Add PWM features and examples
1.3.1 K.Hoang 21/09/2021 Fix warnings in PWM examples
1.4.0 K.Hoang 22/01/2022 Fix `multiple-definitions` linker error. Fix bug
*****************************************************************************************************************************/
// Modified from stm32 core v2.0.0
/*
*******************************************************************************
Copyright (c) 2019, STMicroelectronics
All rights reserved.
This software component is licensed by ST under BSD 3-Clause license,
the "License"; You may not use this file except in compliance with the
License. You may obtain a copy of the License at:
opensource.org/licenses/BSD-3-Clause
*******************************************************************************
*/
/* Define to prevent recursive inclusion -------------------------------------*/
#ifndef __GIGATIMER_H
#define __GIGATIMER_H
#if defined(ARDUINO_GIGA)
/* Includes ------------------------------------------------------------------*/
#include "PinNames.h"
#ifdef __cplusplus
extern "C" {
#endif
#if defined(HAL_TIM_MODULE_ENABLED) && !defined(HAL_TIM_MODULE_ONLY)
/* Exported constants --------------------------------------------------------*/
#ifndef TIM_IRQ_PRIO
#if (__CORTEX_M == 0x00U)
#define TIM_IRQ_PRIO 3
#else
#define TIM_IRQ_PRIO 14
#endif /* __CORTEX_M */
#endif /* TIM_IRQ_PRIO */
#ifndef TIM_IRQ_SUBPRIO
#define TIM_IRQ_SUBPRIO 0
#endif
#if defined(TIM1_BASE) && !defined(TIM1_IRQn)
#define TIM1_IRQn TIM1_UP_IRQn
#define TIM1_IRQHandler TIM1_UP_IRQHandler
#endif
#if defined(TIM8_BASE) && !defined(TIM8_IRQn)
#define TIM8_IRQn TIM8_UP_TIM13_IRQn
#define TIM8_IRQHandler TIM8_UP_TIM13_IRQHandler
#endif
#if defined(TIM12_BASE) && !defined(TIM12_IRQn)
#define TIM12_IRQn TIM8_BRK_TIM12_IRQn
#define TIM12_IRQHandler TIM8_BRK_TIM12_IRQHandler
#endif
#if defined(TIM13_BASE) && !defined(TIM13_IRQn)
#define TIM13_IRQn TIM8_UP_TIM13_IRQn
#endif
#if defined(TIM14_BASE) && !defined(TIM14_IRQn)
#define TIM14_IRQn TIM8_TRG_COM_TIM14_IRQn
#define TIM14_IRQHandler TIM8_TRG_COM_TIM14_IRQHandler
#endif
typedef enum
{
#if defined(TIM1_BASE)
TIMER1_INDEX,
#endif
#if defined(TIM2_BASE)
TIMER2_INDEX,
#endif
#if defined(TIM3_BASE)
TIMER3_INDEX,
#endif
#if defined(TIM4_BASE)
TIMER4_INDEX,
#endif
#if defined(TIM5_BASE)
TIMER5_INDEX,
#endif
#if defined(TIM6_BASE)
TIMER6_INDEX,
#endif
#if defined(TIM7_BASE)
TIMER7_INDEX,
#endif
#if defined(TIM8_BASE)
TIMER8_INDEX,
#endif
#if defined(TIM9_BASE)
TIMER9_INDEX,
#endif
#if defined(TIM10_BASE)
TIMER10_INDEX,
#endif
#if defined(TIM11_BASE)
TIMER11_INDEX,
#endif
#if defined(TIM12_BASE)
TIMER12_INDEX,
#endif
#if defined(TIM13_BASE)
TIMER13_INDEX,
#endif
#if defined(TIM14_BASE)
TIMER14_INDEX,
#endif
#if defined(TIM15_BASE)
TIMER15_INDEX,
#endif
#if defined(TIM16_BASE)
TIMER16_INDEX,
#endif
#if defined(TIM17_BASE)
TIMER17_INDEX,
#endif
#if defined(TIM18_BASE)
TIMER18_INDEX,
#endif
#if defined(TIM19_BASE)
TIMER19_INDEX,
#endif
#if defined(TIM20_BASE)
TIMER20_INDEX,
#endif
#if defined(TIM21_BASE)
TIMER21_INDEX,
#endif
#if defined(TIM22_BASE)
TIMER22_INDEX,
#endif
TIMER_NUM,
UNKNOWN_TIMER = 0XFFFF
} timer_index_t;
// This structure is used to be able to get HardwareTimer instance (C++ class)
// from handler (C structure) specially for interrupt management
typedef struct
{
// Those 2 first fields must remain in this order at the beginning of the structure
void *__this;
TIM_HandleTypeDef handle;
uint32_t preemptPriority;
uint32_t subPriority;
} timerObj_t;
/* Exported functions ------------------------------------------------------- */
timerObj_t *get_timer_obj(TIM_HandleTypeDef *htim);
void enableTimerClock(TIM_HandleTypeDef *htim);
void disableTimerClock(TIM_HandleTypeDef *htim);
uint32_t getTimerIrq(TIM_TypeDef *tim);
uint8_t getTimerClkSrc(TIM_TypeDef *tim);
IRQn_Type getTimerUpIrq(TIM_TypeDef *tim);
IRQn_Type getTimerCCIrq(TIM_TypeDef *tim);
#endif /* HAL_TIM_MODULE_ENABLED && !HAL_TIM_MODULE_ONLY */
#ifdef __cplusplus
}
#endif
#endif
#endif /* __GIGATIMER_H */
/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/

View File

@@ -37,9 +37,11 @@
* I2C bus, or more than one I2C bus on the STM32 architecture
*****************************************************************************/
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
#if defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
#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)
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely all Nucleo-64
// and Nucleo-144variants
// and Nucleo-144 variants
I2C_TypeDef *s = I2C1;
// In init we will ask the STM32 HAL layer for the configured APB1 clock frequency in Hz
@@ -115,35 +117,46 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
// Disable the I2C device, as TRISE can only be programmed whilst disabled
s->CR1 &= ~(I2C_CR1_PE); // Disable I2C
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
asm("nop"); // wait a bit... suggestion from online!
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
if (i2cClockSpeed > 100000L)
if (i2cClockSpeed > 100000UL)
{
if (i2cClockSpeed > 400000L)
i2cClockSpeed = 400000L;
// if (i2cClockSpeed > 400000L)
// i2cClockSpeed = 400000L;
t_rise = 300; // nanoseconds
}
else
{
i2cClockSpeed = 100000L;
// i2cClockSpeed = 100000L;
t_rise = 1000; // nanoseconds
}
// Configure the rise time register
s->TRISE = (t_rise / (1000 / i2c_MHz)) + 1;
// Configure the rise time register - max allowed tRISE is 1000ns,
// so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
s->TRISE = (t_rise * i2c_MHz / 1000) + 1;
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
// Bit 14: Duty, fast mode duty cycle (use 2:1)
// Bit 11-0: FREQR
if (i2cClockSpeed > 100000L) {
// In fast mode, I2C period is 3 * CCR * TPCLK1.
//APB1clk1 / 3 / i2cClockSpeed = 38, but that results in 306KHz not 400!
ccr_freq = 30; // So 30 gives 396KHz or so!
s->CCR = (uint16_t)(ccr_freq | 0x8000); // We need Fast Mode set
} else {
// In standard mode, I2C period is 2 * CCR * TPCLK1
ccr_freq = (APB1clk1 / 2 / i2cClockSpeed); // Should be 225 for 45Mhz APB1 clock
s->CCR |= (uint16_t)ccr_freq;
}
// if (i2cClockSpeed > 400000UL) {
// // In fast mode plus, I2C period is 3 * CCR * TPCLK1.
// // s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
// s->CCR = APB1clk1 / 3 / i2cClockSpeed; // Set I2C clockspeed to start!
// s->CCR |= 0xC000; // We need Fast Mode AND DUTY bits set
// } else {
// In standard and fast mode, I2C period is 2 * CCR * TPCLK1
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
s->CCR |= (APB1clk1 / 2 / i2cClockSpeed); // Set I2C clockspeed to start!
// s->CCR |= (i2c_MHz * 500 / (i2cClockSpeed / 1000)); // Set I2C clockspeed to start!
// if (i2cClockSpeed > 100000UL)
// s->CCR |= 0xC000; // We need Fast Mode bits set as well
// }
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
@@ -157,6 +170,7 @@ void I2CManagerClass::I2C_init()
// Query the clockspeed from the STM32 HAL layer
APB1clk1 = HAL_RCC_GetPCLK1Freq();
i2c_MHz = APB1clk1 / 1000000UL;
// DIAG(F("I2C_init() peripheral clock speed is: %d"), i2c_MHz);
// Enable clocks
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;//(1 << 21); // Enable I2C CLOCK
// Reset the I2C1 peripheral to initial state
@@ -179,6 +193,7 @@ void I2CManagerClass::I2C_init()
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
// Software reset the I2C peripheral
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1 peripheral
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
asm("nop"); // wait a bit... suggestion from online!
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
@@ -189,6 +204,7 @@ void I2CManagerClass::I2C_init()
// Set I2C peripheral clock frequency
// s->CR2 |= I2C_PERIPH_CLK;
s->CR2 |= i2c_MHz;
// DIAG(F("I2C_init() peripheral clock is now: %d"), s->CR2);
// set own address to 00 - not used in master mode
I2C1->OAR1 = (1 << 14); // bit 14 should be kept at 1 according to the datasheet
@@ -212,6 +228,7 @@ void I2CManagerClass::I2C_init()
s->CR2 |= (I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable Buffer, Event and Error interrupts
#endif
// DIAG(F("I2C_init() setting initial I2C clock to 100KHz"));
// Calculate baudrate and set default rate for now
// Configure the Clock Control Register for 100KHz SCL frequency
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
@@ -219,12 +236,14 @@ void I2CManagerClass::I2C_init()
// Bit 11-0: so CCR divisor would be clk / 2 / 100000 (where clk is in Hz)
// s->CCR = I2C_PERIPH_CLK * 5;
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
s->CCR |= (APB1clk1 / 2 / 100000UL); // i2c_MHz * 5;
// s->CCR = i2c_MHz * 5;
s->CCR |= (APB1clk1 / 2 / 100000UL); // Set a default of 100KHz I2C clockspeed to start!
// Configure the rise time register - max allowed is 1000ns, so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
// s->TRISE = I2C_PERIPH_CLK + 1; // 1000 ns / 50 ns = 20 + 1 = 21
s->TRISE = i2c_MHz + 1;
s->TRISE = (1000 * i2c_MHz / 1000) + 1;
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C

View File

@@ -35,6 +35,10 @@
#define WIRE_HAS_TIMEOUT
#endif
/***************************************************************************
* Initialise I2C interface software
***************************************************************************/

View File

@@ -33,17 +33,16 @@ public:
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
if (checkNoOverlap(vpin, nPins, i2cAddress)) new PCA9555(vpin,nPins, i2cAddress, interruptPin);
}
private:
// Constructor
PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
PCA9555(VPIN vpin, uint8_t nPins, I2CAddress I2CAddress, int interruptPin=-1)
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
{
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
outputBuffer, sizeof(outputBuffer));
outputBuffer[0] = REG_INPUT_P0;
}
private:
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
}

View File

@@ -5,6 +5,7 @@
* © 2020-2023 Harald Barth
* © 2020-2021 Chris Harlow
* © 2023 Colin Murdoch
* © 2023 Travis Farmer
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -34,6 +35,20 @@ unsigned long MotorDriver::globalOverloadStart = 0;
volatile portreg_t shadowPORTA;
volatile portreg_t shadowPORTB;
volatile portreg_t shadowPORTC;
#if defined(ARDUINO_ARCH_STM32) || (defined(ARDUINO_GIGA) && defined(XGIGA))
volatile portreg_t shadowPORTD;
volatile portreg_t shadowPORTE;
volatile portreg_t shadowPORTF;
#endif
#if defined(ARDUINO_GIGA) && defined(XGIGA)
#define STM_PORT(X) (((uint32_t)(X) >> 4) & 0xF)
#define STM_PIN(X) ((uint32_t)(X) & 0xF)
#define STM_GPIO_PIN(X) ((uint16_t)(1<<STM_PIN(X)))
#define digitalPinToBitMask(p) (STM_GPIO_PIN(digitalPinToPinName(p)))
#define portOutputRegister(P) (&(P->ODR))
#define portInputRegister(P) (&(P->IDR))
#endif
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) {
@@ -52,6 +67,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
getFastPin(F("SIG"),signalPin,fastSignalPin);
pinMode(signalPin, OUTPUT);
#if !defined(ARDUINO_GIGA) || (defined(ARDUINO_GIGA) && defined(XGIGA)) // no giga
fastSignalPin.shadowinout = NULL;
if (HAVE_PORTA(fastSignalPin.inout == &PORTA)) {
DIAG(F("Found PORTA pin %d"),signalPin);
@@ -68,13 +84,29 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTC;
}
if (HAVE_PORTD(fastSignalPin.inout == &PORTD)) {
DIAG(F("Found PORTD pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTD;
}
if (HAVE_PORTE(fastSignalPin.inout == &PORTE)) {
DIAG(F("Found PORTE pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTE;
}
if (HAVE_PORTF(fastSignalPin.inout == &PORTF)) {
DIAG(F("Found PORTF pin %d"),signalPin);
fastSignalPin.shadowinout = fastSignalPin.inout;
fastSignalPin.inout = &shadowPORTF;
}
#endif // giga
signalPin2=signal_pin2;
if (signalPin2!=UNUSED_PIN) {
dualSignal=true;
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
pinMode(signalPin2, OUTPUT);
#if !defined(ARDUINO_GIGA) || (defined(ARDUINO_GIGA) && defined(XGIGA)) // no giga
fastSignalPin2.shadowinout = NULL;
if (HAVE_PORTA(fastSignalPin2.inout == &PORTA)) {
DIAG(F("Found PORTA pin %d"),signalPin2);
@@ -91,6 +123,22 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTC;
}
if (HAVE_PORTD(fastSignalPin2.inout == &PORTD)) {
DIAG(F("Found PORTD pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTD;
}
if (HAVE_PORTE(fastSignalPin2.inout == &PORTE)) {
DIAG(F("Found PORTE pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTE;
}
if (HAVE_PORTF(fastSignalPin2.inout == &PORTF)) {
DIAG(F("Found PORTF pin %d"),signalPin2);
fastSignalPin2.shadowinout = fastSignalPin2.inout;
fastSignalPin2.inout = &shadowPORTF;
}
#endif // giga
}
else dualSignal=false;
@@ -279,7 +327,7 @@ void MotorDriver::startCurrentFromHW() {
#pragma GCC pop_options
#endif //ANALOG_READ_INTERRUPT
#if defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
#ifdef VARIABLE_TONES
uint16_t taurustones[28] = { 165, 175, 196, 220,
247, 262, 294, 330,
@@ -330,7 +378,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
byte tDir=speedcode & 0x80;
byte brake;
#if defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
{
int f = 131;
#ifdef VARIABLE_TONES
@@ -348,7 +396,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
else brake = 2 * (128-tSpeed);
if (invertBrake)
brake=255-brake;
#if defined(ARDUINO_ARCH_ESP32)
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
DCCTimer::DCCEXanalogWrite(brakePin,brake);
#else
analogWrite(brakePin,brake);
@@ -372,6 +420,24 @@ void MotorDriver::setDCSignal(byte speedcode) {
setSignal(tDir);
HAVE_PORTC(PORTC=shadowPORTC);
interrupts();
} else if (HAVE_PORTD(fastSignalPin.shadowinout == &PORTD)) {
noInterrupts();
HAVE_PORTD(shadowPORTD=PORTD);
setSignal(tDir);
HAVE_PORTD(PORTD=shadowPORTD);
interrupts();
} else if (HAVE_PORTE(fastSignalPin.shadowinout == &PORTE)) {
noInterrupts();
HAVE_PORTE(shadowPORTE=PORTE);
setSignal(tDir);
HAVE_PORTE(PORTE=shadowPORTE);
interrupts();
} else if (HAVE_PORTF(fastSignalPin.shadowinout == &PORTF)) {
noInterrupts();
HAVE_PORTF(shadowPORTF=PORTF);
setSignal(tDir);
HAVE_PORTF(PORTF=shadowPORTF);
interrupts();
} else {
noInterrupts();
setSignal(tDir);
@@ -393,6 +459,13 @@ void MotorDriver::throttleInrush(bool on) {
} else {
ledcDetachPin(brakePin);
}
#elif defined(ARDUINO_ARCH_STM32)
if(on) {
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
DCCTimer::DCCEXanalogWrite(brakePin,duty);
} else {
pinMode(brakePin, OUTPUT);
}
#else
if(on){
switch(brakePin) {
@@ -441,13 +514,24 @@ unsigned int MotorDriver::mA2raw( unsigned int mA) {
return (int32_t)mA * senseScale / senseFactorInternal;
}
void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & result) {
// DIAG(F("MotorDriver %S Pin=%d,"),type,pin);
#if defined(ARDUINO_GIGA) && !defined(XGIGA) // yes giga
(void)type;
(void)input; // no warnings please
result = pin;
#else // no giga
(void) type; // avoid compiler warning if diag not used above.
#if defined(ARDUINO_ARCH_SAMD)
PortGroup *port = digitalPinToPort(pin);
#elif defined(ARDUINO_ARCH_STM32)
GPIO_TypeDef *port = digitalPinToPort(pin);
#elif defined(ARDUINO_GIGA)
//auto * port = ((GPIO_TypeDef *)(GPIOA_BASE + (GPIOB_BASE - GPIOA_BASE) * (digitalPinToPinName(pin) >> 4)));
GPIO_TypeDef *port = (GPIO_TypeDef *)digitalPinToPort(pin);
#else
uint8_t port = digitalPinToPort(pin);
#endif
@@ -457,6 +541,7 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
result.inout = portOutputRegister(port);
result.maskHIGH = digitalPinToBitMask(pin);
result.maskLOW = ~result.maskHIGH;
#endif // giga
// DIAG(F(" port=0x%x, inoutpin=0x%x, isinput=%d, mask=0x%x"),port, result.inout,input,result.maskHIGH);
}

View File

@@ -1,9 +1,10 @@
/*
* © 2022 Paul M Antoine
* © 2022-2023 Paul M. Antoine
* © 2021 Mike S
* © 2021 Fred Decker
* © 2020 Chris Harlow
* © 2022 Harald Barth
* © 2023 Travis Farmer
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -30,12 +31,21 @@
// use powers of two so we can do logical and/or on the track modes in if clauses.
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
#if defined(ARDUINO_GIGA) && !defined(XGIGA) // yes giga
#define setHIGH(fastpin) digitalWrite(fastpin,1)
#define setLOW(fastpin) digitalWrite(fastpin,0)
#else // no giga
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
#endif // giga
#if defined(ARDUINO_GIGA) && !defined(XGIGA) // yes giga
#define isHIGH(fastpin) ((PinStatus)digitalRead(fastpin)==1)
#define isLOW(fastpin) ((PinStatus)digitalRead(fastpin)==0)
#else // no giga
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
#define isLOW(fastpin) (!isHIGH(fastpin))
#endif // giga
#define TOKENPASTE(x, y) x ## y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
@@ -60,6 +70,35 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
#define HAVE_PORTB(X) X
#define PORTC GPIOC->ODR
#define HAVE_PORTC(X) X
#define PORTD GPIOD->ODR
#define HAVE_PORTD(X) X
#if defined(GPIOE)
#define PORTE GPIOE->ODR
#define HAVE_PORTE(X) X
#endif
#if defined(GPIOF)
#define PORTF GPIOF->ODR
#define HAVE_PORTF(X) X
#endif
#endif
#if defined(ARDUINO_GIGA) && defined(XGIGA)
#define PORTA GPIOA->ODR
#define HAVE_PORTA(X) X
#define PORTB GPIOB->ODR
#define HAVE_PORTB(X) X
#define PORTC GPIOC->ODR
#define HAVE_PORTC(X) X
#define PORTD GPIOD->ODR
#define HAVE_PORTD(X) X
#if defined(GPIOE)
#define PORTE GPIOE->ODR
#define HAVE_PORTE(X) X
#endif
#if defined(GPIOF)
#define PORTF GPIOF->ODR
#define HAVE_PORTF(X) X
#endif
#endif
// if macros not defined as pass-through we define
@@ -74,6 +113,15 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
#ifndef HAVE_PORTC
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTD
#define HAVE_PORTD(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTE
#define HAVE_PORTE(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
#ifndef HAVE_PORTF
#define HAVE_PORTF(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
#endif
// Virtualised Motor shield 1-track hardware Interface
@@ -92,24 +140,34 @@ public:
byte invpin = UNUSED_PIN;
};
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) || (defined(ARDUINO_GIGA) && defined(XGIGA))
typedef uint32_t portreg_t;
#else
typedef uint8_t portreg_t;
#endif
#if defined(ARDUINO_GIGA) && !defined(XGIGA) // yes giga
typedef int FASTPIN;
#else // no giga
struct FASTPIN {
volatile portreg_t *inout;
portreg_t maskHIGH;
portreg_t maskLOW;
volatile portreg_t *shadowinout;
};
#endif // giga
// The port registers that are shadowing
// the real port registers. These are
// defined in Motordriver.cpp
extern volatile portreg_t shadowPORTA;
extern volatile portreg_t shadowPORTB;
extern volatile portreg_t shadowPORTC;
extern volatile portreg_t shadowPORTD;
extern volatile portreg_t shadowPORTE;
extern volatile portreg_t shadowPORTF;
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
@@ -126,6 +184,12 @@ class MotorDriver {
// otherwise the call from interrupt context can undo whatever we do
// from outside interrupt
void setBrake( bool on, bool interruptContext=false);
#if defined(ARDUINO_GIGA) && !defined(XGIGA) // yes giga
__attribute__((always_inline)) inline void setSignal( bool high) {
digitalWrite(signalPin, high);
if (dualSignal) digitalWrite(signalPin2, !high);
};
#else // no giga
__attribute__((always_inline)) inline void setSignal( bool high) {
if (trackPWM) {
DCCTimer::setPWM(signalPin,high);
@@ -141,6 +205,7 @@ class MotorDriver {
}
}
};
#endif // giga
inline void enableSignal(bool on) {
if (on)
pinMode(signalPin, OUTPUT);
@@ -162,17 +227,23 @@ class MotorDriver {
int getCurrentRaw(bool fromISR=false);
unsigned int raw2mA( int raw);
unsigned int mA2raw( unsigned int mA);
#if defined(ARDUINO_GIGA) // yes giga
inline bool digitalPinHasPWM(int pin) {
if (pin!=UNUSED_PIN && pin>=2 && pin<=13) return true;
else return false;
}
#endif // giga
inline bool brakeCanPWM() {
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
return true;
#else
#ifdef digitalPinToTimer
#if defined(ARDUINO_ARCH_ESP32)
return (brakePin != UNUSED_PIN); // This was just (true) but we probably do need to check for UNUSED_PIN!
#elif defined(__arm__)
// On ARM we can use digitalPinHasPWM
return ((brakePin!=UNUSED_PIN) && (digitalPinHasPWM(brakePin)));
#elif defined(digitalPinToTimer)
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
#else
return (brakePin<14 && brakePin >1);
#endif //digitalPinToTimer
#endif //ESP32/ARM
#endif
}
inline int getRawCurrentTripValue() {
return rawCurrentTripValue;

View File

@@ -39,11 +39,11 @@ void StringFormatter::diag( const FSH* input...) {
void StringFormatter::lcd(byte row, const FSH* input...) {
va_list args;
// Issue the LCD as a diag first
send(&USB_SERIAL,F("<* LCD%d:"),row);
// Copy to serial client for display 0 <@ display# line# "message">
send(&USB_SERIAL,F("<@ 0 %d \""),row);
va_start(args, input);
send2(&USB_SERIAL,input,args);
send(&USB_SERIAL,F(" *>\n"));
send(&USB_SERIAL,F("\">\n"));
DisplayInterface::setRow(row);
va_start(args, input);
@@ -53,6 +53,12 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
va_list args;
// Copy to serial client <@ display# line# "message">
send(&USB_SERIAL,F("<@ %d %d \""),display,row);
va_start(args, input);
send2(&USB_SERIAL,input,args);
send(&USB_SERIAL,F("\">\n"));
DisplayInterface::setRow(display, row);
va_start(args, input);
send2(DisplayInterface::getDisplayHandler(),input,args);
@@ -117,6 +123,7 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
case 'o': stream->print(va_arg(args, int), OCT); break;
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
case 'h': printHex(stream,(unsigned int)va_arg(args, unsigned int)); break;
case 'M':
{ // this prints a unsigned long microseconds time in readable format
unsigned long time = va_arg(args, long);
@@ -218,4 +225,15 @@ void StringFormatter::printPadded(Print* stream, long value, byte width, bool fo
if (!formatLeft) stream->print(value, DEC);
}
// printHex prints the full 2 byte hex with leading zeros, unlike print(value,HEX)
const char FLASH hexchars[]="0123456789ABCDEF";
void StringFormatter::printHex(Print * stream,uint16_t value) {
char result[5];
for (int i=3;i>=0;i--) {
result[i]=GETFLASH(hexchars+(value & 0x0F));
value>>=4;
}
result[4]='\0';
stream->print(result);
}

View File

@@ -49,6 +49,7 @@ class StringFormatter
static void lcd2(uint8_t display, byte row, const FSH* input...);
static void printEscapes(char * input);
static void printEscape( char c);
static void printHex(Print * stream,uint16_t value);
private:
static void send2(Print * serial, const FSH* input,va_list args);

View File

@@ -26,7 +26,8 @@
#include "MotorDriver.h"
#include "DCCTimer.h"
#include "DIAG.h"
#include"CommandDistributor.h"
#include "CommandDistributor.h"
#include "DCCEXParser.h"
// Virtualised Motor shield multi-track hardware Interface
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
@@ -154,10 +155,16 @@ void TrackManager::setDCCSignal( bool on) {
HAVE_PORTA(shadowPORTA=PORTA);
HAVE_PORTB(shadowPORTB=PORTB);
HAVE_PORTC(shadowPORTC=PORTC);
HAVE_PORTD(shadowPORTD=PORTD);
HAVE_PORTE(shadowPORTE=PORTE);
HAVE_PORTF(shadowPORTF=PORTF);
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
HAVE_PORTA(PORTA=shadowPORTA);
HAVE_PORTB(PORTB=shadowPORTB);
HAVE_PORTC(PORTC=shadowPORTC);
HAVE_PORTD(PORTD=shadowPORTD);
HAVE_PORTE(PORTE=shadowPORTE);
HAVE_PORTF(PORTF=shadowPORTF);
}
void TrackManager::setCutout( bool on) {
@@ -172,10 +179,16 @@ void TrackManager::setPROGSignal( bool on) {
HAVE_PORTA(shadowPORTA=PORTA);
HAVE_PORTB(shadowPORTB=PORTB);
HAVE_PORTC(shadowPORTC=PORTC);
HAVE_PORTD(shadowPORTD=PORTD);
HAVE_PORTE(shadowPORTE=PORTE);
HAVE_PORTF(shadowPORTF=PORTF);
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
HAVE_PORTA(PORTA=shadowPORTA);
HAVE_PORTB(PORTB=shadowPORTB);
HAVE_PORTC(PORTC=shadowPORTC);
HAVE_PORTD(PORTD=shadowPORTD);
HAVE_PORTE(PORTE=shadowPORTE);
HAVE_PORTF(PORTF=shadowPORTF);
}
// setDCSignal(), called from normal context
@@ -319,6 +332,7 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
FOR_EACH_TRACK(t)
streamTrackState(stream,t);
return true;
}
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
@@ -353,32 +367,36 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
// null stream means send to commandDistributor for broadcast
if (track[t]==NULL) return;
auto format=F("");
bool pstate = TrackManager::isPowerOn(t);
switch(track[t]->getMode()) {
case TRACK_MODE_MAIN:
format=F("<= %c MAIN>\n");
if (pstate) {format=F("<= %c MAIN ON>\n");} else {format = F("<= %c MAIN OFF>\n");}
break;
#ifndef DISABLE_PROG
case TRACK_MODE_PROG:
format=F("<= %c PROG>\n");
if (pstate) {format=F("<= %c PROG ON>\n");} else {format=F("<= %c PROG OFF>\n");}
break;
#endif
case TRACK_MODE_NONE:
format=F("<= %c NONE>\n");
if (pstate) {format=F("<= %c NONE ON>\n");} else {format=F("<= %c NONE OFF>\n");}
break;
case TRACK_MODE_EXT:
format=F("<= %c EXT>\n");
if (pstate) {format=F("<= %c EXT ON>\n");} else {format=F("<= %c EXT OFF>\n");}
break;
case TRACK_MODE_DC:
format=F("<= %c DC %d>\n");
if (pstate) {format=F("<= %c DC %d ON>\n");} else {format=F("<= %c DC %d OFF>\n");}
break;
case TRACK_MODE_DCX:
format=F("<= %c DCX %d>\n");
if (pstate) {format=F("<= %c DCX %d ON>\n");} else {format=F("<= %c DCX %d OFF>\n");}
break;
default:
break; // unknown, dont care
}
if (stream) StringFormatter::send(stream,format,'A'+t,trackDCAddr[t]);
else CommandDistributor::broadcastTrackState(format,'A'+t,trackDCAddr[t]);
if (stream) StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]);
else CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]);
}
byte TrackManager::nextCycleTrack=MAX_TRACKS;
@@ -412,49 +430,70 @@ std::vector<MotorDriver *>TrackManager::getMainDrivers() {
}
#endif
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
void TrackManager::setPower2(bool setProg,bool setJoin, POWERMODE mode) {
if (!setProg) mainPowerGuess=mode;
FOR_EACH_TRACK(t) {
MotorDriver * driver=track[t];
if (!driver) continue;
switch (track[t]->getMode()) {
case TRACK_MODE_MAIN:
if (setProg) break;
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
// XXX see if we can make this conditional
driver->setBrake(true);
driver->setBrake(false); // DCC runs with brake off
driver->setPower(mode);
break;
case TRACK_MODE_DC:
case TRACK_MODE_DCX:
if (setProg) break;
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(t); // speed match DCC throttles
driver->setPower(mode);
break;
case TRACK_MODE_PROG:
if (!setProg) break;
driver->setBrake(true);
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_EXT:
driver->setBrake(true);
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_NONE:
break;
}
TrackManager::setTrackPower(setProg, setJoin, mode, t);
}
return;
}
void TrackManager::setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack) {
//DIAG(F("SetTrackPower Processing Track %d"), thistrack);
MotorDriver * driver=track[thistrack];
if (!driver) return;
switch (track[thistrack]->getMode()) {
case TRACK_MODE_MAIN:
if (setProg) break;
// toggle brake before turning power on - resets overcurrent error
// on the Pololu board if brake is wired to ^D2.
// XXX see if we can make this conditional
driver->setBrake(true);
driver->setBrake(false); // DCC runs with brake off
driver->setPower(mode);
break;
case TRACK_MODE_DC:
case TRACK_MODE_DCX:
//DIAG(F("Processing track - %d setProg %d"), thistrack, setProg);
if (setProg || setJoin) break;
driver->setBrake(true); // DC starts with brake on
applyDCSpeed(thistrack); // speed match DCC throttles
driver->setPower(mode);
break;
case TRACK_MODE_PROG:
if (!setProg && !setJoin) break;
driver->setBrake(true);
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_EXT:
driver->setBrake(true);
driver->setBrake(false);
driver->setPower(mode);
break;
case TRACK_MODE_NONE:
break;
}
}
void TrackManager::reportPowerChange(Print* stream, byte thistrack) {
// This function is for backward JMRI compatibility only
// It reports the first track only, as main, regardless of track settings.
// <c MeterName value C/V unit min max res warn>
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
}
POWERMODE TrackManager::getProgPower() {
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG)
return track[t]->getPower();
return track[t]->getPower();
return POWERMODE::OFF;
}
@@ -526,3 +565,32 @@ bool TrackManager::isPowerOn(byte t) {
return true;
}
bool TrackManager::isProg(byte t) {
if (track[t]->getMode()==TRACK_MODE_PROG)
return true;
return false;
}
byte TrackManager::returnMode(byte t) {
return (track[t]->getMode());
}
int16_t TrackManager::returnDCAddr(byte t) {
return (trackDCAddr[t]);
}
const char* TrackManager::getModeName(byte Mode) {
//DIAG(F("PowerMode %d"), Mode);
switch (Mode)
{
case 1: return "NONE";
case 2: return "MAIN";
case 4: return "PROG";
case 8: return "DC";
case 16: return "DCX";
case 32: return "EXT";
default: return "----";
}
}

View File

@@ -39,6 +39,10 @@ const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5;
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
class TrackManager {
public:
static void Setup(const FSH * shieldName,
@@ -60,10 +64,14 @@ class TrackManager {
#ifdef ARDUINO_ARCH_ESP32
static std::vector<MotorDriver *>getMainDrivers();
#endif
static void setPower2(bool progTrack,POWERMODE mode);
static void setPower2(bool progTrack,bool joinTrack,POWERMODE mode);
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
static void setMainPower(POWERMODE mode) {setPower2(false,mode);}
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
static void setMainPower(POWERMODE mode) {setPower2(false,false,mode);}
static void setProgPower(POWERMODE mode) {setPower2(true,false,mode);}
static void setJoinPower(POWERMODE mode) {setPower2(false,true,mode);}
static void setTrackPower(bool setProg, bool setJoin, POWERMODE mode, byte thistrack);
static const int16_t MAX_TRACKS=8;
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
@@ -77,9 +85,14 @@ class TrackManager {
static void sampleCurrent();
static void reportGauges(Print* stream);
static void reportCurrent(Print* stream);
static void reportPowerChange(Print* stream, byte thistrack);
static void reportObsoleteCurrent(Print* stream);
static void streamTrackState(Print* stream, byte t);
static bool isPowerOn(byte t);
static bool isProg(byte t);
static byte returnMode(byte t);
static int16_t returnDCAddr(byte t);
static const char* getModeName(byte Mode);
static int16_t joinRelay;
static bool progTrackSyncMain; // true when prog track is a siding switched to main

View File

@@ -185,7 +185,7 @@ public:
for (Turntable *tto = _firstTurntable; tto != 0; tto = tto->_nextTurntable)
if (!tto->isHidden()) {
gotOne = true;
StringFormatter::send(stream, F("<i %d %d>\n"), tto->getId(), tto->getPosition());
StringFormatter::send(stream, F("<I %d %d>\n"), tto->getId(), tto->getPosition());
}
return gotOne;
}

View File

@@ -3,6 +3,7 @@
* © 2020-2022 Harald Barth
* © 2020-2022 Chris Harlow
* © 2023 Nathan Kellenicki
* © 2023 Travis Farmer
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -57,6 +58,14 @@ Stream * WifiInterface::wifiStream;
#define SERIAL3 Serial3
#endif
#if defined(ARDUINO_GIGA) // yes giga
#define NUM_SERIAL 5
#define SERIAL1 Serial1
#define SERIAL2 Serial2
#define SERIAL3 Serial3
#define SERIAL4 Serial4
#endif // giga
#if defined(ARDUINO_ARCH_STM32)
// Handle serial ports availability on STM32 for variants!
// #undef NUM_SERIAL
@@ -206,12 +215,13 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
while(!wifiStream->available());
version[i]=wifiStream->read();
StringFormatter::printEscape(version[i]);
if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '0') ||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) {
SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE");
forceAP = true;
}
}
if ((version[0] == '0') ||
(version[0] == '2' && version[2] == '0') ||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) {
DIAG(F("You need to up/downgrade the ESP firmware"));
SSid = F("UPDATE_ESP_FIRMWARE");
forceAP = true;
}
}
checkForOK(2000, true, false);

284
Wifi_NINA.cpp Normal file
View File

@@ -0,0 +1,284 @@
/*
© 2023 Paul M. Antoine
© 2021-23 Harald Barth
© 2023 Nathan Kellenicki
© 2023 Travis Farmer
© 2023 Chris Harlow
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(WIFI_NINA) || defined(GIGA_WIFI)
//#include <vector>
#include <SPI.h>
#ifndef ARDUINO_GIGA
#include <WifiNINA.h>
#else
#if defined(GIGA_WIFI)
#include <WiFi.h>
#else
#include <WiFiNINA.h>
#endif
#endif
#include "Wifi_NINA.h"
#include "DIAG.h"
#include "RingStream.h"
#include "CommandDistributor.h"
#include "WiThrottle.h"
// Configure the pins used for the ESP32 connection
#if !defined(ARDUINO_GIGA) && defined(ARDUINO_ARCH_STM32) // Here my STM32 configuration
#define SPIWIFI SPI // The SPI port
#define SPIWIFI_SS PA4 // Chip select pin
#define ESP32_RESETN PA10 // Reset pin
#define SPIWIFI_ACK PB3 // a.k.a BUSY or READY pin
#define ESP32_GPIO0 -1
#elif defined(ARDUINO_GIGA)
#define SPIWIFI SPI
#define SPIWIFI_SS 10 // Chip select pin
#define SPIWIFI_ACK 7 // a.k.a BUSY or READY pin
#define ESP32_RESETN 5 // Reset pin
#define ESP32_GPIO0 -1 // Not connected
#else
#warning "WiFiNINA has no SPI port or pin allocations for this archiecture yet!"
#endif
#define MAX_CLIENTS 10
static WiFiServer *server = NULL;
static RingStream *outboundRing = new RingStream(10240);
static bool APmode = false;
static IPAddress ip;
char asciitolower(char in) {
if (in <= 'Z' && in >= 'A')
return in - ('Z' - 'z');
return in;
}
bool WifiNINA::setup(const char *SSid,
const char *password,
const char *hostname,
int port,
const byte channel,
const bool forceAP) {
bool havePassword = true;
bool haveSSID = true;
bool wifiUp = false;
uint8_t tries = 40;
// Set up the pins!
#if !defined(GIGA_WIFI)
WiFi.setPins(SPIWIFI_SS, SPIWIFI_ACK, ESP32_RESETN, ESP32_GPIO0, &SPIWIFI);
#endif
// check for the WiFi module:
if (WiFi.status() == WL_NO_MODULE) {
DIAG(F("Communication with WiFi module failed!"));
// don't continue for now!
while (true);
}
// Print firmware version on the module
String fv = WiFi.firmwareVersion();
DIAG(F("WifiNINA Firmware version found:%s"), fv.c_str());
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 && !forceAP) {
#ifndef ARDUINO_GIGA
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
#endif
// 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) {
IPAddress ip = WiFi.localIP();
DIAG(F("Wifi STA IP %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
wifiUp = true;
} else {
DIAG(F("Could not connect to Wifi SSID %s"),SSid);
DIAG(F("Forcing one more Wifi restart"));
// esp_wifi_start();
// esp_wifi_connect();
WiFi.end();
WiFi.begin(SSid, password);
tries=40;
while (WiFi.status() != WL_CONNECTED && tries) {
Serial.print('.');
tries--;
delay(500);
}
if (WiFi.status() == WL_CONNECTED) {
ip = WiFi.localIP();
DIAG(F("Wifi STA IP 2nd try %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
wifiUp = true;
} else {
DIAG(F("Wifi STA mode FAIL. Will revert to AP mode"));
haveSSID=false;
}
}
}
if (!haveSSID || forceAP) {
// prepare all strings
String strSSID(forceAP ? SSid : "DCCEX_");
String strPass(forceAP ? password : "PASS_");
if (!forceAP) {
byte mac[6];
WiFi.macAddress(mac);
String strMac;
for (int i = 0; i++; i < 6) {
strMac += String(mac[i], HEX);
}
DIAG(F("MAC address: %x:%x:%x:%x:%x:%x"), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
strMac.remove(0,9);
strMac.replace(":","");
strMac.replace(":","");
// convert mac addr hex chars to lower case to be compatible with AT software
//std::transform(strMac.begin(), strMac.end(), strMac.begin(), asciitolower); ///TJF: why does this fail compile with WiFiNINA, but not giga WiFi???
strSSID.concat(strMac);
strPass.concat(strMac);
}
if (WiFi.beginAP(strSSID.c_str(),
havePassword ? password : strPass.c_str(),
channel) == WL_AP_LISTENING) {
DIAG(F("Wifi AP SSID %s PASS %s"),strSSID.c_str(),havePassword ? password : strPass.c_str());
ip = WiFi.localIP();
DIAG(F("Wifi AP IP %d.%d.%d.%d"),ip[0], ip[1], ip[2], ip[3]);
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;
}
// TODO: we need to run the MDNS_Generic server I suspect
// // Now Wifi is up, register the mDNS service
// if(!MDNS.begin(hostname)) {
// DIAG(F("Wifi setup failed to start mDNS"));
// }
// if(!MDNS.addService("withrottle", "tcp", 2560)) {
// DIAG(F("Wifi setup failed to add withrottle service to mDNS"));
// }
server = new WiFiServer(port); // start listening on tcp port
server->begin();
// server started here
DIAG(F("Server will be started on port %d"),port);
ip = WiFi.localIP();
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
LCD(5,F("Port:%d"), port);
return true;
}
const char *wlerror[] = {
"WL_IDLE_STATUS",
"WL_NO_SSID_AVAIL",
"WL_SCAN_COMPLETED",
"WL_CONNECTED",
"WL_CONNECT_FAILED",
"WL_CONNECTION_LOST",
"WL_DISCONNECTED"
};
WiFiClient * clients[MAX_CLIENTS]; // nulled in setup
void WifiNINA::checkForNewClient() {
auto newClient=server->available();
if (!newClient) return;
for (byte clientId=0; clientId<MAX_CLIENTS; clientId++){
if (!clients[clientId]) {
clients[clientId]= new WiFiClient(newClient); // use this slot
//DIAG(F("New client connected to slot %d"),clientId); //TJF: brought in for debugging.
return;
}
}
}
void WifiNINA::checkForLostClients() {
for (byte clientId=0; clientId<MAX_CLIENTS; clientId++){
auto c=clients[clientId];
if(c && !c->connected()) {
clients[clientId]->stop();
//DIAG(F("Remove client %d"), clientId);
CommandDistributor::forget(clientId);
clients[clientId]=nullptr;
}
}
}
void WifiNINA::checkForClientInput() {
// Find a client providing input
for (byte clientId=0; clientId<MAX_CLIENTS; clientId++){
auto c=clients[clientId];
if(c) {
auto len=c->available();
if (len) {
// read data from client
byte cmd[len+1];
for(int i=0; i<len; i++) cmd[i]=c->read();
cmd[len]=0x00;
CommandDistributor::parse(clientId,cmd,outboundRing);
}
}
}
}
void WifiNINA::checkForClientOutput() {
// something to write out?
auto clientId=outboundRing->read();
if (clientId < 0) return;
auto replySize=outboundRing->count();
if (replySize==0) return; // nothing to send
auto c=clients[clientId];
if (!c) {
// client is gone, throw away msg
for (int i=0;i<replySize;i++) outboundRing->read();
//DIAG(F("gone, drop message.")); //TJF: only for diag
return;
}
// emit data to the client object
for (int i=0;i<replySize;i++) c->write(outboundRing->read());
}
void WifiNINA::loop() {
checkForLostClients(); // ***
checkForNewClient();
checkForClientInput(); // ***
WiThrottle::loop(outboundRing); // allow withrottle to broadcast if needed
checkForClientOutput();
}
#endif // WIFI_NINA

46
Wifi_NINA.h Normal file
View File

@@ -0,0 +1,46 @@
/*
* © 2023 Paul M. Antoine
* © 2021 Harald Barth
* © 2023 Nathan Kellenicki
*
* 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/>.
*/
#ifndef WifiNINA_h
#define WifiNINA_h
// #include "FSH.h"
#include <Arduino.h>
// #include <SPI.h>
// #include <WifiNINA.h>
class WifiNINA
{
public:
static bool setup(const char *wifiESSID,
const char *wifiPassword,
const char *hostname,
const int port,
const byte channel,
const bool forceAP);
static void loop();
private:
static void checkForNewClient();
static void checkForLostClients();
static void checkForClientInput();
static void checkForClientOutput();
};
#endif //WifiNINA_h

View File

@@ -5,6 +5,7 @@
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2023 Travis Farmer
*
* This file is part of CommandStation-EX
*
@@ -147,7 +148,20 @@
// #ifndef I2C_USE_WIRE
// #define I2C_USE_WIRE
// #endif
#elif defined(ARDUINO_GIGA)
#define ARDUINO_TYPE "Giga"
#ifndef GIGA_EXT_EEPROM
#define DISABLE_EEPROM
#endif
#if defined(ENABLE_WIFI) && !defined(WIFI_NINA)
#define WIFI_NINA
#endif
//#if !defined(I2C_USE_WIRE)
//#define I2C_USE_WIRE
//#endif
#define SDA I2C_SDA
#define SCL I2C_SCL
/* TODO when ready
#elif defined(ARDUINO_ARCH_RP2040)
#define ARDUINO_TYPE "RP2040"

View File

@@ -24,6 +24,7 @@
//#include "IO_TouchKeypad.h // Touch keypad with 16 keys
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
//#include "IO_EXFastClock.h" // FastClock driver
//#include "IO_PCA9555.h" // 16-bit I/O expander (NXP & Texas Instruments).
//==========================================================================
// The function halSetup() is invoked from CS if it exists within the build.

View File

@@ -31,7 +31,6 @@ include_dir = .
[env]
build_flags = -Wall -Wextra
; monitor_filters = time
; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0
[env:samd21-dev-usb]
platform = atmelsam
@@ -60,7 +59,7 @@ framework = arduino
lib_deps = ${env.lib_deps}
monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
build_flags = -std=c++17
[env:mega2560-debug]
platform = atmelavr
@@ -72,7 +71,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
[env:mega2560-no-HAL]
platform = atmelavr
@@ -84,7 +83,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = -DIO_NO_HAL
build_flags = -DIO_NO_HAL
[env:mega2560-I2C-wire]
platform = atmelavr
@@ -108,7 +107,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags = ; -DDIAG_LOOPTIMES
build_flags =
[env:mega328]
platform = atmelavr
@@ -190,10 +189,75 @@ platform = ststm32
board = nucleo_f446re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
; Experimental - no reason this should not work, but not
; tested as yet
;
[env:Nucleo-F401RE]
platform = ststm32
board = nucleo_f401re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable
monitor_speed = 115200
monitor_echo = yes
; Commented out by default as the F13ZH has variant files
; but NOT the nucleo_f413zh.json file which needs to be
; installed before you can let PlatformIO see this
;
; [env:Nucleo-F413ZH]
; platform = ststm32
; board = nucleo_f413zh
; framework = arduino
; lib_deps = ${env.lib_deps}
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
; monitor_speed = 115200
; monitor_echo = yes
; Commented out by default as the F446ZE needs variant files
; installed before you can let PlatformIO see this
;
; [env:Nucleo-F446ZE]
; platform = ststm32
; board = nucleo_f446ze
; framework = arduino
; lib_deps = ${env.lib_deps}
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
; monitor_speed = 115200
; monitor_echo = yes
; Commented out by default as the F412ZG needs variant files
; installed before you can let PlatformIO see this
;
; [env:Nucleo-F412ZG]
; platform = ststm32
; board = blah_f412zg
; framework = arduino
; lib_deps = ${env.lib_deps}
; 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-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
[env:Teensy3_2]
platform = teensy
board = teensy31
@@ -232,5 +296,4 @@ board = teensy41
framework = arduino
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore =
lib_ignore =

View File

@@ -3,7 +3,26 @@
#include "StringFormatter.h"
#define VERSION "5.1.7"
#define VERSION "5.1.17gw"
// 5.1.17gw - Giga support by Travis Farmer, with WifiNINA integrated to see if it works
// 5.1.17 - Divide out C for config and D for diag commands
// 5.1.16 - Remove I2C address from EXTT_TURNTABLE macro to work with MUX, requires separate HAL macro to create
// 5.1.15 - LCC/Adapter support and Exrail feature-compile-out.
// 5.1.14 - Fixed IFTTPOSITION
// 5.1.13 - Changed turntable broadcast from i to I due to server string conflict
// 5.1.12 - Added Power commands <0 A> & <1 A> etc. and update to <=>
// Added EXRAIL SET_POWER(track, ON/OFF)
// Fixed a problem whereby <1 MAIN> also powered on PROG track
// Added functions to TrackManager.cpp to allow UserAddin code for power display on OLED/LCD
// Added - returnMode(byte t), returnDCAddr(byte t) & getModeName(byte Mode)
// 5.1.11 - STM32F4xx revised I2C clock setup, no correctly sets clock and has fully variable frequency selection
// 5.1.10 - STM32F4xx DCCEXanalogWrite to handle PWM generation for TrackManager DC/DCX
// - STM32F4xx DCC 58uS timer now using non-PWM output timers where possible
// - ESP32 brakeCanPWM check now detects UNUSED_PIN
// - ARM architecture brakeCanPWM now uses digitalPinHasPWM()
// - STM32F4xx shadowpin extensions to handle pins on ports D, E and F
// 5.1.9 - Fixed IO_PCA9555'h to work with PCA9548 mux, tested OK
// 5.1.8 - STM32Fxx ADCee extension to support ADCs #2 and #3
// 5.1.7 - Fix turntable broadcasts for non-movement activities and <JP> result
// 5.1.6 - STM32F4xx native I2C driver added
// 5.1.5 - Added turntable object and EXRAIL commands