1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2025-07-30 10:53:44 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
FrightRisk
041c9bc045 add monitor commands to csb1 branch for the esp32 2024-07-16 08:45:39 -04:00
FrightRisk
156a29a629 Add EX-CSB1 definitions 2024-05-24 08:46:11 -04:00
57 changed files with 676 additions and 7099 deletions

2
.gitignore vendored
View File

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

View File

@@ -161,10 +161,6 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
#endif
}
void CommandDistributor::broadcastTurntable(int16_t id, uint8_t position, bool moving) {
broadcastReply(COMMAND_TYPE, F("<I %d %d %d>\n"), id, position, moving);
}
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
// The JMRI clock command is of the form : PFT65871<;>4
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
@@ -269,53 +265,6 @@ 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

@@ -49,24 +49,15 @@ public :
static void broadcastLoco(byte slot);
static void broadcastSensor(int16_t id, bool value);
static void broadcastTurnout(int16_t id, bool isClosed);
static void broadcastTurntable(int16_t id, uint8_t position, bool moving);
static void broadcastClockTime(int16_t time, int8_t rate);
static void setClockTime(int16_t time, int8_t rate, byte opt);
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

@@ -30,7 +30,6 @@
* © 2021 Neil McKechnie
* © 2020-2021 Chris Harlow, Harald Barth, David Cutting,
* Fred Decker, Gregor Baues, Anthony W - Dayton
* © 2023 Nathan Kellenicki
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -96,15 +95,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
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
#endif // WIFI_ON
#else
// ESP32 needs wifi on always
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
#endif // ARDUINO_ARCH_ESP32
#if ETHERNET_ON
@@ -148,11 +143,7 @@ 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,11 +43,7 @@ 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

@@ -351,7 +351,7 @@ void DCCACK::callback(int value) {
switch (callbackState) {
case AFTER_READ:
if (ackManagerRejoin && !autoPowerOff) {
if (ackManagerRejoin && autoPowerOff) {
progDriver->setPower(POWERMODE::OFF);
callbackStart=millis();
callbackState=WAITING_30;

View File

@@ -1,5 +1,4 @@
/*
* © 2023 Paul M. Antoine
* © 2021 Fred Decker
* © 2020-2021 Harald Barth
* © 2020-2021 Chris Harlow
@@ -34,13 +33,8 @@
#include "SerialManager.h"
#include "version.h"
#ifndef ARDUINO_ARCH_ESP32
#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

@@ -25,79 +25,6 @@
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
/*
List of single character OPCODEs in use for reference.
When determining a new OPCODE for a new feature, refer to this list as the source of truth.
Once a new OPCODE is decided upon, update this list.
Character, Usage
/, |EX-R| interactive commands
-, Remove from reminder table
=, |TM| configuration
!, Emergency stop
@, Reserved for future use - LCD messages to JMRI
#, Request number of supported cabs/locos; heartbeat
+, WiFi AT commands
?, Reserved for future use
0, Track power off
1, Track power on
a, DCC accessory control
A,
b, Write CV bit on main
B, Write CV bit
c, Request current command
C, configure the CS
d,
D, Diagnostic commands
e, Erase EEPROM
E, Store configuration in EEPROM
f, Loco decoder function control (deprecated)
F, Loco decoder function control
g,
G,
h,
H, Turnout state broadcast
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, Reserved for LCC interface (implemented in EXRAIL)
m,
M, Write DCC packet
n,
N,
o,
O, Output broadcast
p, Broadcast power state
P, Write DCC packet
q, Sensor deactivated
Q, Sensor activated
r, Broadcast address read on programming track
R, Read CVs
s, Display status
S, Sensor configuration
t, Cab/loco update command
T, Turnout configuration/control
u, Reserved for user commands
U, Reserved for user commands
v,
V, Verify CVs
w, Write CV on main
W, Write CV
x,
X, Invalid command
y,
Y, Output broadcast
z,
Z, Output configuration/control
*/
#include "StringFormatter.h"
#include "DCCEXParser.h"
#include "DCC.h"
@@ -114,7 +41,6 @@ Once a new OPCODE is decided upon, update this list.
#include "TrackManager.h"
#include "DCCTimer.h"
#include "EXRAIL2.h"
#include "Turntables.h"
// This macro can't be created easily as a portable function because the
// flashlist requires a far pointer for high flash access.
@@ -122,7 +48,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; \
StringFormatter::send(stream,F(" %d"),value); \
if (value != 0) StringFormatter::send(stream,F(" %d"),value); \
}
@@ -157,10 +83,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';
const int16_t HASH_KEYWORD_R='R';
const int16_t HASH_KEYWORD_T='T';
const int16_t HASH_KEYWORD_X='X';
@@ -172,8 +95,6 @@ const int16_t HASH_KEYWORD_ANOUT = -26399;
const int16_t HASH_KEYWORD_WIFI = -5583;
const int16_t HASH_KEYWORD_ETHERNET = -30767;
const int16_t HASH_KEYWORD_WIT = 31594;
const int16_t HASH_KEYWORD_EXTT = 8573;
const int16_t HASH_KEYWORD_ADD = 3201;
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
bool DCCEXParser::stashBusy;
@@ -298,9 +219,6 @@ void DCCEXParser::parse(Print *stream, byte *com, RingStream *ringStream) {
void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
{
#ifdef DISABLE_PROG
(void)ringStream;
#endif
#ifndef DISABLE_EEPROM
(void)EEPROM; // tell compiler not to warn this is unused
#endif
@@ -457,16 +375,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
#ifndef DISABLE_PROG
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
if (params != 3)
break;
DCC::writeCVByteMain(p[0], p[1], p[2]);
return;
DCC::writeCVByteMain(p[0], p[1], p[2]);
return;
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
if (params != 4)
break;
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return;
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
return;
#endif
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
@@ -489,16 +403,14 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
#ifndef DISABLE_PROG
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p, ringStream))
break;
if (!stashCallback(stream, p, ringStream))
break;
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
DCC::setLocoId(p[0],callback_Wloco);
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
DCC::writeCVByte(p[0], p[1], callback_W4);
else if (params == 2) // WRITE CV ON PROG <W CV VALUE>
else // WRITE CV ON PROG <W CV VALUE>
DCC::writeCVByte(p[0], p[1], callback_W);
else
break;
return;
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
@@ -518,11 +430,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
}
break;
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB> or <B CV BIT VALUE>
if (params != 3 && params != 5)
break;
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
if (!stashCallback(stream, p, ringStream))
break;
break;
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
return;
@@ -553,131 +463,69 @@ 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;
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;
}
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;
}
#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 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;
}
else break; // will reply <X>
}
if (main) TrackManager::setMainPower(POWERMODE::ON);
if (prog) TrackManager::setProgPower(POWERMODE::ON);
TrackManager::setJoin(join);
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;
}
CommandDistributor::broadcastPower();
return;
}
case '0': // POWEROFF <0 [MAIN | PROG] >
{
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;
}
#endif
//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;
}
else break; // will reply <X>
}
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;
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;
}
#ifndef DISABLE_PROG
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
prog=true;
}
#endif
else break; // will reply <X>
}
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);
}
TrackManager::setJoin(false);
CommandDistributor::broadcastPower();
return;
}
}
case '!': // ESTOP ALL <!>
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
@@ -693,7 +541,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
Sensor::printAll(stream);
return;
case 's': // STATUS <s>
case 's': // <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
@@ -714,17 +562,13 @@ 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': // DIAG <D [params]>
case 'D': // < >
if (parseD(stream, params, p))
return;
break;
#endif
case '=': // TACK MANAGER CONTROL <= [params]>
return;
case '=': // <= Track manager control >
if (TrackManager::parseJ(stream, params, p))
return;
break;
@@ -809,15 +653,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
SENDFLASHLIST(stream,RMFT2::rosterIdList)
}
else {
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);
}
const FSH * functionNames= RMFT2::getRosterFunctions(id);
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
id, RMFT2::getRosterName(id),
functionNames == NULL ? RMFT2::getRosterFunctions(0) : functionNames);
}
#endif
StringFormatter::send(stream, F(">\n"));
return;
@@ -846,80 +686,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
}
StringFormatter::send(stream, F(">\n"));
return;
// No turntables without HAL support
#ifndef IO_NO_HAL
case HASH_KEYWORD_O: // <JO returns turntable list
StringFormatter::send(stream, F("<jO"));
if (params==1) { // <JO>
for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) {
if (tto->isHidden()) continue;
StringFormatter::send(stream, F(" %d"),tto->getId());
}
StringFormatter::send(stream, F(">\n"));
} else { // <JO id>
Turntable *tto=Turntable::get(id);
if (!tto || tto->isHidden()) {
StringFormatter::send(stream, F(" %d X>\n"), id);
} else {
uint8_t pos = tto->getPosition();
uint8_t type = tto->isEXTT();
uint8_t posCount = tto->getPositionCount();
const FSH *todesc = NULL;
#ifdef EXRAIL_ACTIVE
todesc = RMFT2::getTurntableDescription(id);
#endif
if (todesc == NULL) todesc = F("");
StringFormatter::send(stream, F(" %d %d %d %d \"%S\">\n"), id, type, pos, posCount, todesc);
}
}
return;
case HASH_KEYWORD_P: // <JP id> returns turntable position list for the turntable id
if (params==2) { // <JP id>
Turntable *tto=Turntable::get(id);
if (!tto || tto->isHidden()) {
StringFormatter::send(stream, F(" %d X>\n"), id);
} else {
uint8_t posCount = tto->getPositionCount();
const FSH *tpdesc = NULL;
for (uint8_t p = 0; p < posCount; p++) {
StringFormatter::send(stream, F("<jP"));
int16_t angle = tto->getPositionAngle(p);
#ifdef EXRAIL_ACTIVE
tpdesc = RMFT2::getTurntablePositionDescription(id, p);
#endif
if (tpdesc == NULL) tpdesc = F("");
StringFormatter::send(stream, F(" %d %d %d \"%S\""), id, p, angle, tpdesc);
StringFormatter::send(stream, F(">\n"));
}
}
} else {
StringFormatter::send(stream, F("<jP X>\n"));
}
return;
#endif
default: break;
} // switch(p[1])
break; // case J
}
// No turntables without HAL support
#ifndef IO_NO_HAL
case 'I': // TURNTABLE <I ...>
if (parseI(stream, params, p))
return;
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++)
@@ -1125,29 +896,20 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
return false;
}
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
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])
{
#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"));
case HASH_KEYWORD_CABS: // <D CABS>
DCC::displayCabList(stream);
return true;
case HASH_KEYWORD_SPEED128:
DCC::setGlobalSpeedsteps(128);
DIAG(F("128 Speedsteps"));
return true;
case HASH_KEYWORD_RAM: // <D RAM>
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
break;
#ifndef DISABLE_PROG
case HASH_KEYWORD_ACK: // <D ACK ON/OFF> <D ACK [LIMIT|MIN|MAX|RETRY] Value>
@@ -1166,33 +928,12 @@ bool DCCEXParser::parseC(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 {
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
StringFormatter::send(stream, F("Ack diag %S\n"), 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;
@@ -1214,14 +955,34 @@ 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_SERVO: // <D SERVO vpin position [profile]>
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;
@@ -1244,104 +1005,11 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
break;
default: // invalid/unknown
return parseC(stream, params, p);
break;
}
return false;
}
// ==========================
// Turntable - no support if no HAL
// <I> - list all
// <I id> - broadcast type and current position
// <I id DCC> - create DCC - This is TBA
// <I id steps> - operate (DCC)
// <I id steps activity> - operate (EXTT)
// <I id ADD position value> - add position
// <I id EXTT i2caddress vpin home> - create EXTT
#ifndef IO_NO_HAL
bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
{
switch (params)
{
case 0: // <I> list turntable objects
return Turntable::printAll(stream);
case 1: // <I id> broadcast type and current position
{
Turntable *tto = Turntable::get(p[0]);
if (tto) {
bool type = tto->isEXTT();
uint8_t position = tto->getPosition();
StringFormatter::send(stream, F("<I %d %d>\n"), type, position);
} else {
return false;
}
}
return true;
case 2: // <I id position> - rotate a DCC turntable
{
Turntable *tto = Turntable::get(p[0]);
if (tto && !tto->isEXTT()) {
if (!tto->setPosition(p[0], p[1])) return false;
} else {
return false;
}
}
return true;
case 3: // <I id position activity> | <I id DCC home> - rotate to position for EX-Turntable or create DCC turntable
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_DCC) {
if (tto || p[2] < 0 || p[2] > 3600) return false;
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"));
} else {
if (!tto) return false;
if (!tto->isEXTT()) return false;
if (!tto->setPosition(p[0], p[1], p[2])) return false;
}
}
return true;
case 4: // <I id EXTT vpin home> create an EXTT turntable
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_EXTT) {
if (tto || p[3] < 0 || p[3] > 3600) return false;
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"));
} else {
return false;
}
}
return true;
case 5: // <I id ADD position value angle> add a position
{
Turntable *tto = Turntable::get(p[0]);
if (p[1] == HASH_KEYWORD_ADD) {
// 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"));
} else {
return false;
}
}
return true;
default: // Anything else is invalid
return false;
}
}
#endif
// CALLBACKS must be static
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
{

View File

@@ -24,7 +24,6 @@
#include <Arduino.h>
#include "FSH.h"
#include "RingStream.h"
#include "defines.h"
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
typedef void (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command);
@@ -46,17 +45,13 @@ struct DCCEXParser
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command, bool usehex);
static bool parseT(Print * stream, int16_t params, int16_t p[]);
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[]);
#endif
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 parseD(Print * stream, int16_t params, int16_t p[]);
static Print * getAsyncReplyStream();
static void commitAsyncReplyStream();
static Print * getAsyncReplyStream();
static void commitAsyncReplyStream();
static bool stashBusy;
static byte stashTarget;

View File

@@ -194,10 +194,8 @@ int RMTChannel::RMTfillData(const byte buffer[], byte byteCount, byte repeatCoun
setDCCBit1(data + bitcounter-1); // overwrite previous zero bit with one bit
setEOT(data + bitcounter++); // EOT marker
dataLen = bitcounter;
noInterrupts(); // keep dataReady and dataRepeat consistnet to each other
dataReady = true;
dataRepeat = repeatCount+1; // repeatCount of 0 means send once
interrupts();
return 0;
}
@@ -214,8 +212,6 @@ void IRAM_ATTR RMTChannel::RMTinterrupt() {
if (dataReady) { // if we have new data, fill while preamble is running
rmt_fill_tx_items(channel, data, dataLen, preambleLen-1);
dataReady = false;
if (dataRepeat == 0) // all data should go out at least once
DIAG(F("Channel %d DCC signal lost data"), channel);
}
if (dataRepeat > 0) // if a repeat count was specified, work on that
dataRepeat--;

View File

@@ -3,7 +3,6 @@
* © 2021 Mike S
* © 2021-2023 Harald Barth
* © 2021 Fred Decker
* © 2023 Travis Farmer
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -91,9 +90,6 @@ 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
@@ -129,13 +125,8 @@ 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;

View File

@@ -180,8 +180,8 @@ void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
return;
}
pin_to_channel[pin] = --cnt_channel;
ledcSetup(cnt_channel, 1000, 8);
ledcAttachPin(pin, cnt_channel);
ledcSetup(cnt_channel, 1000, 8);
} else {
ledcAttachPin(pin, pin_to_channel[pin]);
}

View File

@@ -1,193 +0,0 @@
/*
* © 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-2023 Paul M. Antoine
* © 2022-23 Paul M. Antoine
* © 2021 Mike S
* © 2021, 2023 Harald Barth
* © 2021 Fred Decker
@@ -35,7 +35,7 @@
#endif
#include "DIAG.h"
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
#if defined(ARDUINO_NUCLEO_F411RE)
// Nucleo-64 boards don't have additional serial ports defined by default
HardwareSerial Serial1(PB7, PA15); // Rx=PB7, Tx=PA15 -- CN7 pins 17 and 21 - F411RE
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
@@ -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_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
#elif defined(ARDUINO_NUCLEO_F412ZG) || 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,28 +154,13 @@ HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
///////////////////////////////////////////////////////////////////////////////////////////////
INTERRUPT_CALLBACK interruptHandler=0;
// 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));
// 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);
// Timer IRQ handler
void DCCTimer_Handler() {
void Timer11_Handler() {
interruptHandler();
}
@@ -183,24 +168,22 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
interruptHandler=callback;
noInterrupts();
dcctimer.pause();
dcctimer.setPrescaleFactor(1);
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
timer.pause();
timer.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();
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
timer.attachInterrupt(Timer11_Handler);
timer.refresh();
timer.resume();
interrupts();
}
bool DCCTimer::isPWMPin(byte pin) {
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
//TODO: SAMD 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;
}
@@ -252,91 +235,22 @@ void DCCTimer::reset() {
while(true) {};
}
// 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};
// 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
// 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));
uint16_t ADCee::usedpins = 0;
uint8_t ADCee::highestPin = 0;
int * ADCee::analogvals = NULL;
uint32_t * analogchans = NULL;
bool adc1configured = false;
// 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;
int16_t ADCee::ADCmax() {
return 4095;
}
int ADCee::init(uint8_t pin) {
@@ -347,33 +261,11 @@ 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 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);
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
GPIO_TypeDef * gpioBase;
// Port config - find which port we're on and power it up
GPIO_TypeDef *gpioBase;
switch (stmgpio)
{
// Port config - find which port we're on and power it up
switch(stmgpio) {
case 0x00:
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
gpioBase = GPIOA;
@@ -386,20 +278,6 @@ 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
}
@@ -415,33 +293,31 @@ int ADCee::init(uint8_t pin) {
if (adcchan > 18)
return -1022; // silly value as error
if (adcchan < 10)
adc->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
else
adc->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
// Read the inital ADC value for this analog input
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
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
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, analogchans and adcchans if this is the first invocation of init
if (analogvals == NULL) { // allocate analogvals and analogchans 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
adcchans[id] = adc; // Keep track of which ADC this channel is on
usedpins |= (1 << id); // This pin is now ready
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, ADC%d: channel=%d, id=%d"), value, adcnum, adcchan, id);
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
return value;
}
@@ -468,16 +344,13 @@ 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;
adc = adcchans[id];
if (waiting)
{
if (waiting) {
// look if we have a result
if (!(adc->SR & (1 << 1)))
if (!(ADC1->SR & (1 << 1)))
return; // no result, continue to wait
// found value
analogvals[id] = adc->DR;
analogvals[id] = ADC1->DR;
// advance at least one track
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(0);
@@ -496,10 +369,9 @@ 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
adc = adcchans[id];
adc->SQR3 = analogchans[id]; // 1st conversion in regular sequence
adc->CR2 |= (1 << 30); // Start 1st conversion SWSTART
// start new ADC aquire on id
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
#ifdef DEBUG_ADC
if (id == 1) TrackManager::track[1]->setBrake(1);
#endif
@@ -520,83 +392,19 @@ void ADCee::scan() {
void ADCee::begin() {
noInterrupts();
//ADC1 config sequence
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable ADC1 clock
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
// 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

@@ -247,9 +247,6 @@ void DCCWaveform::schedulePacket(const byte buffer[], byte byteCount, byte repea
pendingPacket[byteCount] = checksum;
pendingLength = byteCount + 1;
pendingRepeats = repeats;
// DIAG repeated commands (accesories)
// if (pendingRepeats > 0)
// DIAG(F("Repeats=%d on %s track"), pendingRepeats, isMainTrack ? "MAIN" : "PROG");
// The resets will be zero not only now but as well repeats packets into the future
clearResets(repeats+1);
{

View File

@@ -31,12 +31,12 @@
#include "Sensors.h"
#include "Turnouts.h"
#if defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_GIGA)
#if defined(ARDUINO_ARCH_SAMC)
ExternalEEPROM EEPROM;
#endif
void EEStore::init() {
#if defined(ARDUINO_ARCH_SAMC) || defined(ARDUINO_GIGA)
#if defined(ARDUINO_ARCH_SAMC)
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) || defined(ARDUINO_GIGA)
#if defined(ARDUINO_ARCH_SAMC)
#include <SparkFun_External_EEPROM.h>
extern ExternalEEPROM EEPROM;
#else

View File

@@ -2,7 +2,7 @@
* © 2021 Neil McKechnie
* © 2021-2023 Harald Barth
* © 2020-2023 Chris Harlow
* © 2022-2023 Colin Murdoch
* © 2022 Colin Murdoch
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -52,8 +52,6 @@
#include "Turnouts.h"
#include "CommandDistributor.h"
#include "TrackManager.h"
#include "Turntables.h"
#include "IODevice.h"
// Command parsing keywords
const int16_t HASH_KEYWORD_EXRAIL=15435;
@@ -85,7 +83,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;
@@ -96,10 +94,6 @@ LookList * RMFT2::onAmberLookup=NULL;
LookList * RMFT2::onGreenLookup=NULL;
LookList * RMFT2::onChangeLookup=NULL;
LookList * RMFT2::onClockLookup=NULL;
#ifndef IO_NO_HAL
LookList * RMFT2::onRotateLookup=NULL;
#endif
LookList * RMFT2::onOverloadLookup=NULL;
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
#define SKIPOP progCounter+=3
@@ -176,26 +170,20 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
onDeactivateLookup=LookListLoader(OPCODE_ONDEACTIVATE);
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);
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
onClockLookup=LookListLoader(OPCODE_ONTIME);
// Second pass startup, define any turnouts or servos, set signals red
// add sequences onRoutines to the lookups
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){
@@ -207,7 +195,6 @@ if (compileFeatures & FEATURE_SIGNAL) {
case OPCODE_AT:
case OPCODE_ATTIMEOUT2:
case OPCODE_AFTER:
case OPCODE_AFTEROVERLOAD:
case OPCODE_IF:
case OPCODE_IFNOT: {
int16_t pin = (int16_t)operand;
@@ -251,38 +238,7 @@ if (compileFeatures & FEATURE_SIGNAL) {
setTurnoutHiddenState(VpinTurnout::create(id,pin));
break;
}
#ifndef IO_NO_HAL
case OPCODE_DCCTURNTABLE: {
VPIN id=operand;
int home=getOperand(progCounter,1);
setTurntableHiddenState(DCCTurntable::create(id));
Turntable *tto=Turntable::get(id);
tto->addPosition(0,0,home);
break;
}
case OPCODE_EXTTTURNTABLE: {
VPIN id=operand;
VPIN pin=getOperand(progCounter,1);
int home=getOperand(progCounter,3);
setTurntableHiddenState(EXTTTurntable::create(id,pin));
Turntable *tto=Turntable::get(id);
tto->addPosition(0,0,home);
break;
}
case OPCODE_TTADDPOSITION: {
VPIN id=operand;
int position=getOperand(progCounter,1);
int value=getOperand(progCounter,2);
int angle=getOperand(progCounter,3);
Turntable *tto=Turntable::get(id);
tto->addPosition(position,value,angle);
break;
}
#endif
case OPCODE_AUTOSTART:
// automatically create a task from here at startup.
// Removed if (progCounter>0) check 4.2.31 because
@@ -307,12 +263,6 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) {
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
}
#ifndef IO_NO_HAL
void RMFT2::setTurntableHiddenState(Turntable * tto) {
tto->setHidden(GETFLASH(getTurntableDescription(tto->getId()))==0x01);
}
#endif
char RMFT2::getRouteType(int16_t id) {
for (int16_t i=0;;i+=2) {
int16_t rid= GETHIGHFLASHW(routeIdList,i);
@@ -346,65 +296,13 @@ 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>\n"));
StringFormatter::send(stream,F("<X>"));
}
}
@@ -432,19 +330,17 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
}
}
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);
}
}
// 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;
}
@@ -703,14 +599,6 @@ void RMFT2::loop2() {
Turnout::setClosed(operand, true);
break;
#ifndef IO_NO_HAL
case OPCODE_ROTATE:
uint8_t activity;
activity=getOperand(2);
Turntable::setPosition(operand,getOperand(1),activity);
break;
#endif
case OPCODE_REV:
forward = false;
driveLoco(operand);
@@ -796,17 +684,7 @@ void RMFT2::loop2() {
}
if (millis()-waitAfter < 500 ) return;
break;
case OPCODE_AFTEROVERLOAD: // waits for the power to be turned back on - either by power routine or button
if (!TrackManager::isPowerOn(operand)) {
// reset timer to half a second and keep waiting
waitAfter=millis();
delayMe(50);
return;
}
if (millis()-waitAfter < 500 ) return;
break;
case OPCODE_LATCH:
setFlag(operand,LATCH_FLAG);
break;
@@ -837,20 +715,6 @@ 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
@@ -924,13 +788,7 @@ void RMFT2::loop2() {
case OPCODE_IFCLOSED:
skipIf=Turnout::isThrown(operand);
break;
#ifndef IO_NO_HAL
case OPCODE_IFTTPOSITION: // do block if turntable at this position
skipIf=Turntable::getPosition(operand)!=(int)getOperand(1);
break;
#endif
case OPCODE_ENDIF:
break;
@@ -1091,21 +949,7 @@ 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));
@@ -1117,16 +961,7 @@ void RMFT2::loop2() {
return;
}
break;
#ifndef IO_NO_HAL
case OPCODE_WAITFORTT: // OPCODE_WAITFOR,V(turntable_id)
if (Turntable::ttMoving(operand)) {
delayMe(100);
return;
}
break;
#endif
case OPCODE_PRINT:
printMessage(operand);
break;
@@ -1143,7 +978,6 @@ 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:
@@ -1152,13 +986,6 @@ void RMFT2::loop2() {
case OPCODE_ONGREEN:
case OPCODE_ONCHANGE:
case OPCODE_ONTIME:
#ifndef IO_NO_HAL
case OPCODE_DCCTURNTABLE: // Turntable definition ignored at runtime
case OPCODE_EXTTTURNTABLE: // Turntable definition ignored at runtime
case OPCODE_TTADDPOSITION: // Turntable position definition ignored at runtime
case OPCODE_ONROTATE:
#endif
case OPCODE_ONOVERLOAD:
break;
@@ -1213,7 +1040,6 @@ 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.
@@ -1281,7 +1107,6 @@ 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;
@@ -1305,13 +1130,6 @@ void RMFT2::changeEvent(int16_t vpin, bool change) {
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
}
#ifndef IO_NO_HAL
void RMFT2::rotateEvent(int16_t turntableId, bool change) {
// Hunt or an ONROTATE for this turntable
if (change) handleEvent(F("ROTATE"),onRotateLookup,turntableId);
}
#endif
void RMFT2::clockEvent(int16_t clocktime, bool change) {
// Hunt for an ONTIME for this time
if (Diag::CMD)
@@ -1322,22 +1140,10 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
}
}
void RMFT2::powerEvent(int16_t track, bool overload) {
// Hunt for an ONOVERLOAD for this item
if (Diag::CMD)
DIAG(F("Looking for Power event on track : %c"), track);
if (overload) {
handleEvent(F("POWER"),onOverloadLookup,track);
}
}
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
int pc= handlers->find(id);
if (pc>=0) startNonRecursiveTask(reason,id,pc);
}
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
if (pc<0) return;
// Check we dont already have a task running this handler
RMFT2 * task=loopTask;
while(task) {

View File

@@ -1,7 +1,7 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2022-2023 Colin Murdoch
* © 2022 Colin Murdoch
* © 2023 Harald Barth
* All rights reserved.
*
@@ -25,7 +25,6 @@
#include "FSH.h"
#include "IODevice.h"
#include "Turnouts.h"
#include "Turntables.h"
// The following are the operation codes (or instructions) for a kind of virtual machine.
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
@@ -36,8 +35,7 @@
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
OPCODE_RESERVE,OPCODE_FREE,
OPCODE_AT,OPCODE_AFTER,
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
OPCODE_ATGTE,OPCODE_ATLT,
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
@@ -59,15 +57,11 @@ 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_POWER,
OPCODE_SET_TRACK,
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
OPCODE_ONCHANGE,
OPCODE_ONCLOCKTIME,
OPCODE_ONTIME,
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
OPCODE_ONOVERLOAD,
// OPcodes below this point are skip-nesting IF operations
// placed here so that they may be skipped as a group
@@ -80,8 +74,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
OPCODE_IFRE,
OPCODE_IFLOCO,
OPCODE_IFTTPOSITION
OPCODE_IFLOCO
};
// Ensure thrunge_lcd is put last as there may be more than one display,
@@ -95,11 +88,7 @@ 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;
@@ -141,8 +130,6 @@ class LookList {
static void activateEvent(int16_t addr, bool active);
static void changeEvent(int16_t id, bool change);
static void clockEvent(int16_t clocktime, bool change);
static void rotateEvent(int16_t id, bool change);
static void powerEvent(int16_t track, bool overload);
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
static const int16_t DCC_SIGNAL_FLAG=0x1000;
@@ -157,8 +144,6 @@ class LookList {
static const FSH * getTurnoutDescription(int16_t id);
static const FSH * getRosterName(int16_t id);
static const FSH * getRosterFunctions(int16_t id);
static const FSH * getTurntableDescription(int16_t id);
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
private:
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
@@ -171,14 +156,10 @@ private:
static bool isSignal(int16_t id,char rag);
static int16_t getSignalSlot(int16_t id);
static void setTurnoutHiddenState(Turnout * t);
#ifndef IO_NO_HAL
static void setTurntableHiddenState(Turntable * tto);
#endif
static LookList* LookListLoader(OPCODE op1,
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);
@@ -197,7 +178,6 @@ 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;
@@ -208,14 +188,6 @@ private:
static LookList * onGreenLookup;
static LookList * onChangeLookup;
static LookList * onClockLookup;
#ifndef IO_NO_HAL
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

@@ -1,6 +1,6 @@
/*
* © 2020-2022 Chris Harlow. All rights reserved.
* © 2022-2023 Colin Murdoch
* © 2022 Colin Murdoch
* © 2023 Harald Barth
*
* This file is part of CommandStation-EX
@@ -27,7 +27,6 @@
#undef ACTIVATE
#undef ACTIVATEL
#undef AFTER
#undef AFTEROVERLOAD
#undef ALIAS
#undef AMBER
#undef ANOUT
@@ -41,7 +40,6 @@
#undef CALL
#undef CLOSE
#undef DCC_SIGNAL
#undef DCC_TURNTABLE
#undef DEACTIVATE
#undef DEACTIVATEL
#undef DELAY
@@ -53,9 +51,8 @@
#undef ENDEXRAIL
#undef ENDIF
#undef ENDTASK
#undef ESTOP
#undef EXRAIL
#undef EXTT_TURNTABLE
#undef ESTOP
#undef EXRAIL
#undef FADE
#undef FOFF
#undef FOLLOW
@@ -78,7 +75,6 @@
#undef IFRESERVE
#undef IFTHROWN
#undef IFTIMEOUT
#undef IFTTPOSITION
#undef IFRE
#undef INVERT_DIRECTION
#undef JOIN
@@ -86,8 +82,6 @@
#undef LATCH
#undef LCD
#undef SCREEN
#undef LCC
#undef LCCX
#undef LCN
#undef MOVETT
#undef ONACTIVATE
@@ -96,14 +90,11 @@
#undef ONDEACTIVATE
#undef ONDEACTIVATEL
#undef ONCLOSE
#undef ONLCC
#undef ONTIME
#undef ONCLOCKTIME
#undef ONCLOCKMINS
#undef ONOVERLOAD
#undef ONGREEN
#undef ONRED
#undef ONROTATE
#undef ONTHROW
#undef ONCHANGE
#undef PARSE
@@ -122,9 +113,7 @@
#undef RESUME
#undef RETURN
#undef REV
#undef ROSTER
#undef ROTATE
#undef ROTATE_DCC
#undef ROSTER
#undef ROUTE
#undef SENDLOCO
#undef SEQUENCE
@@ -141,15 +130,13 @@
#undef SERVO_SIGNAL
#undef SET
#undef SET_TRACK
#undef SET_POWER
#undef SETLOCO
#undef SIGNAL
#undef SIGNALH
#undef SPEED
#undef START
#undef STOP
#undef THROW
#undef TT_ADDPOSITION
#undef THROW
#undef TURNOUT
#undef TURNOUTL
#undef UNJOIN
@@ -157,9 +144,6 @@
#undef VIRTUAL_SIGNAL
#undef VIRTUAL_TURNOUT
#undef WAITFOR
#ifndef IO_NO_HAL
#undef WAITFORTT
#endif
#undef WITHROTTLE
#undef XFOFF
#undef XFON
@@ -168,7 +152,6 @@
#define ACTIVATE(addr,subaddr)
#define ACTIVATEL(addr)
#define AFTER(sensor_id)
#define AFTEROVERLOAD(track_id)
#define ALIAS(name,value...)
#define AMBER(signal_id)
#define ANOUT(vpin,value,param1,param2)
@@ -182,7 +165,6 @@
#define CALL(route)
#define CLOSE(id)
#define DCC_SIGNAL(id,add,subaddr)
#define DCC_TURNTABLE(id,home,description)
#define DEACTIVATE(addr,subaddr)
#define DEACTIVATEL(addr)
#define DELAY(mindelay)
@@ -195,8 +177,7 @@
#define ENDIF
#define ENDTASK
#define ESTOP
#define EXRAIL
#define EXTT_TURNTABLE(id,vpin,home,description)
#define EXRAIL
#define FADE(pin,value,ms)
#define FOFF(func)
#define FOLLOW(route)
@@ -219,14 +200,11 @@
#define IFTHROWN(turnout_id)
#define IFRESERVE(block)
#define IFTIMEOUT
#define IFTTPOSITION(turntable_id,position)
#define IFRE(sensor_id,value)
#define INVERT_DIRECTION
#define JOIN
#define KILLALL
#define LATCH(sensor_id)
#define LCC(eventid)
#define LCCX(senderid,eventid)
#define LATCH(sensor_id)
#define LCD(row,msg)
#define SCREEN(display,row,msg)
#define LCN(msg)
@@ -237,14 +215,11 @@
#define ONTIME(value)
#define ONCLOCKTIME(hours,mins)
#define ONCLOCKMINS(mins)
#define ONOVERLOAD(track_id)
#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)
#define ONRED(signal_id)
#define ONTHROW(turnout_id)
#define ONCHANGE(sensor_id)
#define PAUSE
@@ -263,10 +238,8 @@
#define RESUME
#define RETURN
#define REV(speed)
#define ROTATE(turntable_id,position,activity)
#define ROTATE_DCC(turntable_id,position)
#define ROSTER(cab,name,funcmap...)
#define ROUTE(id,description)
#define ROSTER(cab,name,funcmap...)
#define SENDLOCO(cab,route)
#define SEQUENCE(id)
#define SERIAL(msg)
@@ -282,15 +255,13 @@
#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)
#define SPEED(speed)
#define START(route)
#define STOP
#define THROW(id)
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
#define THROW(id)
#define TURNOUT(id,addr,subaddr,description...)
#define TURNOUTL(id,addr,description...)
#define UNJOIN
@@ -298,9 +269,6 @@
#define VIRTUAL_SIGNAL(id)
#define VIRTUAL_TURNOUT(id,description...)
#define WAITFOR(pin)
#ifndef IO_NO_HAL
#define WAITFORTT(turntable_id)
#endif
#define WITHROTTLE(msg)
#define XFOFF(cab,func)
#define XFON(cab,func)

View File

@@ -1,7 +1,7 @@
/*
* © 2021 Neil McKechnie
* © 2020-2022 Chris Harlow
* © 2022-2023 Colin Murdoch
* © 2022 Colin Murdoch
* © 2023 Harald Barth
* All rights reserved.
*
@@ -54,8 +54,6 @@
// helper macro for turnout descriptions, creates NULL for missing description
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
// helper macro for turntable descriptions, creates NULL for missing description
#define T_DESC(tid,pid,desc) if(turntableId==tid && positionId==pid) return ("" desc)[0]?F("" desc):NULL;
// helper macro for turnout description as HIDDEN
#define HIDDEN "\x01"
@@ -63,19 +61,13 @@
// (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
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
#include "myAutomation.h"
// Pass 1h Implements HAL macro by creating exrailHalSetup function
// Also allows creating EXTurntable object
// Pass 1h Implements HAL macro by creating exrailHalSetup function
#include "EXRAIL2MacroReset.h"
#undef HAL
#define HAL(haltype,params...) haltype::create(params);
@@ -83,30 +75,6 @@ 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
@@ -219,31 +187,6 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
return NULL;
}
// Pass to get turntable descriptions (optional)
#include "EXRAIL2MacroReset.h"
#undef DCC_TURNTABLE
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
#undef EXTT_TURNTABLE
#define EXTT_TURNTABLE(id,vpin,home,description...) O_DESC(id,description)
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
switch (turntableId) {
#include "myAutomation.h"
default:break;
}
return NULL;
}
// Pass to get turntable position descriptions (optional)
#include "EXRAIL2MacroReset.h"
#undef TT_ADDPOSITION
#define TT_ADDPOSITION(turntable_id,position,value,home,description...) T_DESC(turntable_id,position,description)
const FSH * RMFT2::getTurntablePositionDescription(int16_t turntableId, uint8_t positionId) {
#include "myAutomation.h"
return NULL;
}
// Pass 6: Roster IDs (count)
#include "EXRAIL2MacroReset.h"
#undef ROSTER
@@ -300,16 +243,6 @@ 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
@@ -323,7 +256,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
#define AFTEROVERLOAD(track_id) OPCODE_AFTEROVERLOAD,V(TRACK_NUMBER_##track_id),
#define ALIAS(name,value...)
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2),
@@ -336,9 +268,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define BROADCAST(msg) PRINT(msg)
#define CALL(route) OPCODE_CALL,V(route),
#define CLOSE(id) OPCODE_CLOSE,V(id),
#ifndef IO_NO_HAL
#define DCC_TURNTABLE(id,home,description...) OPCODE_DCCTURNTABLE,V(id),OPCODE_PAD,V(home),
#endif
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
#define DEACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1),
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
@@ -352,10 +281,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define ENDIF OPCODE_ENDIF,0,0,
#define ENDTASK OPCODE_ENDTASK,0,0,
#define ESTOP OPCODE_SPEED,V(1),
#define EXRAIL
#ifndef IO_NO_HAL
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
#endif
#define EXRAIL
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
#define FOFF(func) OPCODE_FOFF,V(func),
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
@@ -378,19 +304,11 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
#ifndef IO_NO_HAL
#define IFTTPOSITION(id,position) OPCODE_IFTTPOSITION,V(id),OPCODE_PAD,V(position),
#endif
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
#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)
@@ -399,21 +317,13 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#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)
#define ONOVERLOAD(track_id) OPCODE_ONOVERLOAD,V(TRACK_NUMBER_##track_id),
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
#ifndef IO_NO_HAL
#define ONROTATE(id) OPCODE_ONROTATE,V(id),
#endif
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
#define PAUSE OPCODE_PAUSE,0,0,
@@ -433,10 +343,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define RETURN OPCODE_RETURN,0,0,
#define REV(speed) OPCODE_REV,V(speed),
#define ROSTER(cabid,name,funcmap...)
#ifndef IO_NO_HAL
#define ROTATE(id,position,activity) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(EXTurntable::activity),
#define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0),
#endif
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
@@ -453,17 +359,13 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#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
#define TT_ADDPOSITION(id,position,value,angle,description...) OPCODE_TTADDPOSITION,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(value),OPCODE_PAD,V(angle),
#endif
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
#define TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description)
#define UNJOIN OPCODE_UNJOIN,0,0,
@@ -472,9 +374,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
#define WITHROTTLE(msg) PRINT(msg)
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
#ifndef IO_NO_HAL
#define WAITFORTT(turntable_id) OPCODE_WAITFORTT,V(turntable_id),
#endif
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,220 +0,0 @@
/****************************************************************************************************************************
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_

View File

@@ -1,950 +0,0 @@
/****************************************************************************************************************************
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****/

View File

@@ -1,198 +0,0 @@
/****************************************************************************************************************************
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

@@ -92,7 +92,7 @@ void I2CManagerClass::begin(void) {
// Probe and list devices. Use standard mode
// (clock speed 100kHz) for best device compatibility.
_setClock(100000);
uint32_t originalTimeout = _timeout;
unsigned long originalTimeout = _timeout;
setTimeout(1000); // use 1ms timeout for probes
#if defined(I2C_EXTENDED_ADDRESS)

View File

@@ -485,7 +485,7 @@ private:
// When retries are enabled, the timeout applies to each
// try, and failure from timeout does not get retried.
// A value of 0 means disable timeout monitoring.
uint32_t _timeout = 100000UL;
unsigned long _timeout = 100000UL;
// Finish off request block by waiting for completion and posting status.
uint8_t finishRB(I2CRB *rb, uint8_t status);
@@ -532,15 +532,14 @@ private:
uint8_t bytesToSend = 0;
uint8_t bytesToReceive = 0;
uint8_t operation = 0;
uint32_t startTime = 0;
unsigned long startTime = 0;
uint8_t muxPhase = 0;
uint8_t muxAddress = 0;
uint8_t muxData[1];
uint8_t deviceAddress;
const uint8_t *sendBuffer;
uint8_t *receiveBuffer;
uint8_t transactionState = 0;
volatile uint32_t pendingClockSpeed = 0;
void startTransaction();

View File

@@ -172,10 +172,6 @@ void I2CManagerClass::startTransaction() {
* Function to queue a request block and initiate operations.
***************************************************************************/
void I2CManagerClass::queueRequest(I2CRB *req) {
if (((req->operation & OPERATION_MASK) == OPERATION_READ) && req->readLen == 0)
return; // Ignore null read
req->status = I2C_STATUS_PENDING;
req->nextRequest = NULL;
ATOMIC_BLOCK() {
@@ -188,7 +184,6 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
}
/***************************************************************************
* Initiate a write to an I2C device (non-blocking operation)
***************************************************************************/
@@ -245,8 +240,8 @@ void I2CManagerClass::checkForTimeout() {
I2CRB *t = queueHead;
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
// Check for timeout
int32_t elapsed = micros() - startTime;
if (elapsed > (int32_t)_timeout) {
unsigned long elapsed = micros() - startTime;
if (elapsed > _timeout) {
#ifdef DIAG_IO
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
#endif
@@ -305,12 +300,12 @@ void I2CManagerClass::handleInterrupt() {
// Check if current request has completed. If there's a current request
// and state isn't active then state contains the completion status of the request.
if (state == I2C_STATE_COMPLETED && currentRequest != NULL && currentRequest == queueHead) {
if (state == I2C_STATE_COMPLETED && currentRequest != NULL) {
// Operation has completed.
if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|| currentRequest->operation & OPERATION_NORETRY)
{
// Status is OK, or has failed and retry count exceeded, or failed and retries disabled.
// Status is OK, or has failed and retry count exceeded, or retries disabled.
#if defined(I2C_EXTENDED_ADDRESS)
if (muxPhase == MuxPhase_PROLOG ) {
overallStatus = completionStatus;

View File

@@ -26,44 +26,27 @@
#include "I2CManager.h"
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
//#include <avr/io.h>
//#include <avr/interrupt.h>
#include <wiring_private.h>
#include "stm32f4xx_hal_rcc.h"
/*****************************************************************************
* STM32F4xx I2C native driver support
*
* Nucleo-64 and Nucleo-144 boards all use I2C1 as the default I2C peripheral
* Later we may wish to support other STM32 boards, allow use of an alternate
* I2C bus, or more than one I2C bus on the STM32 architecture
*****************************************************************************/
/***************************************************************************
* Interrupt handler.
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
* bus on the SAMD architecture
***************************************************************************/
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) \
|| defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) \
|| defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely all Nucleo-64
// and Nucleo-144 variants
void I2C1_IRQHandler() {
I2CManager.handleInterrupt();
}
#endif
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants
I2C_TypeDef *s = I2C1;
// In init we will ask the STM32 HAL layer for the configured APB1 clock frequency in Hz
uint32_t APB1clk1; // Peripheral Input Clock speed in Hz.
uint32_t i2c_MHz; // Peripheral Input Clock speed in MHz.
// IRQ handler for I2C1, replacing the weak definition in the STM32 HAL
extern "C" void I2C1_EV_IRQHandler(void) {
I2CManager.handleInterrupt();
}
extern "C" void I2C1_ER_IRQHandler(void) {
I2CManager.handleInterrupt();
}
#else
#warning STM32 board selected is not yet supported - so I2C1 peripheral is not defined
#endif
#endif
// Peripheral Input Clock speed in MHz.
// For STM32F446RE, the speed is 45MHz. Ideally, this should be determined
// at run-time from the APB1 clock, as it can vary from STM32 family to family.
// #define I2C_PERIPH_CLK 45
#define I2C_IRQn I2C1_EV_IRQn
#define I2C_BUSFREQ 16
// I2C SR1 Status Register #1 bit definitions for convenience
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
@@ -97,66 +80,52 @@ extern "C" void I2C1_ER_IRQHandler(void) {
// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C
// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable
// States of the STM32 I2C driver state machine
enum {TS_IDLE,TS_START,TS_W_ADDR,TS_W_DATA,TS_W_STOP,TS_R_ADDR,TS_R_DATA,TS_R_STOP};
/***************************************************************************
* Set I2C clock speed register. This should only be called outside of
* a transmission. The I2CManagerClass::_setClock() function ensures
* that it is only called at the beginning of an I2C transaction.
***************************************************************************/
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
// Calculate a rise time appropriate to the requested bus speed
// Use 10x the rise time spec to enable integer divide of 50ns clock period
// Use 10x the rise time spec to enable integer divide of 62.5ns clock period
uint16_t t_rise;
uint32_t ccr_freq;
while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further
// writes to CR1 while STOP is being executed!
// 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 > 100000UL)
if (i2cClockSpeed < 200000L) {
// i2cClockSpeed = 100000L;
t_rise = 0x11; // (1000ns /62.5ns) + 1;
}
else if (i2cClockSpeed < 800000L)
{
// if (i2cClockSpeed > 400000L)
// i2cClockSpeed = 400000L;
t_rise = 300; // nanoseconds
i2cClockSpeed = 400000L;
t_rise = 0x06; // (300ns / 62.5ns) + 1;
// } else if (i2cClockSpeed < 1200000L) {
// i2cClockSpeed = 1000000L;
// t_rise = 120;
}
else
{
// i2cClockSpeed = 100000L;
t_rise = 1000; // nanoseconds
i2cClockSpeed = 100000L;
t_rise = 0x11; // (1000ns /62.5ns) + 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;
// Enable the I2C master mode
s->CR1 &= ~(I2C_CR1_PE); // Enable I2C
// Software reset the I2C peripheral
// s->CR1 |= I2C_CR1_SWRST; // reset the I2C
// Release reset
// s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
// Calculate baudrate - using a rise time appropriate for the speed
ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2;
// 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 > 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
// }
// Bit 14: Duty, fast mode duty cycle
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
s->CCR = (uint16_t)ccr_freq;
// 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);
// Configure the rise time register
s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
@@ -167,54 +136,32 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
***************************************************************************/
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
RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST;
RCC->APB1RSTR &= ~RCC_APB1RSTR_I2C1RST;
// Standard I2C pins are SCL on PB8 and SDA on PB9
//Setting up the clocks
RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
// Standard I2C pins are SCL on PB8 and SDA on PB9
// Bits (17:16)= 1:0 --> Alternate Function for Pin PB8;
// Bits (19:18)= 1:0 --> Alternate Function for Pin PB9
GPIOB->MODER &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all MODER bits for PB8 and PB9
GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
GPIOB->PUPDR &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all PUPDR bits for PB8 and PB9
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
// Alt Function High register routing pins PB8 and PB9 for I2C1:
// Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8
// Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9
GPIOB->AFR[1] &= ~((15<<0) | (15<<4)); // Clear all AFR bits for PB8 on low nibble, PB9 on next nibble up
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
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
// Clear all bits in I2C CR2 register except reserved bits
s->CR2 &= 0xE000;
// 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
// Program the peripheral input clock in CR2 Register in order to generate correct timings
s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz
#if defined(I2C_USE_INTERRUPTS)
// Setting NVIC
NVIC_SetPriority(I2C1_EV_IRQn, 1); // Match default priorities
NVIC_EnableIRQ(I2C1_EV_IRQn);
NVIC_SetPriority(I2C1_ER_IRQn, 1); // Match default priorities
NVIC_EnableIRQ(I2C1_ER_IRQn);
NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities
NVIC_EnableIRQ(I2C_IRQn);
// CR2 Interrupt Settings
// Bit 15-13: reserved
@@ -225,28 +172,23 @@ void I2CManagerClass::I2C_init()
// Bit 8: ITERREN - Error interrupt enable
// Bit 7-6: reserved
// Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz)
s->CR2 |= (I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable Buffer, Event and Error interrupts
// s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts
s->CR2 |= 0x0300; // Enable 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
// Bit 14: Duty, fast mode duty cycle
// 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); // Set a default of 100KHz I2C clockspeed to start!
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
s->CCR = 0x0050;
// Configure the rise time register - max allowed is 1000ns, so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 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);
// Configure the rise time register - max allowed in 1000ns
s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1
// Enable the I2C master mode
s->CR1 |= I2C_CR1_PE; // Enable I2C
// Setting bus idle mode and wait for sync
}
/***************************************************************************
@@ -256,30 +198,49 @@ void I2CManagerClass::I2C_sendStart() {
// Set counters here in case this is a retry.
rxCount = txCount = 0;
uint8_t temp;
// On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a
// multi-master bus, the bus may be BUSY under control of another master,
// On a single-master I2C bus, the start bit won't be sent until the bus
// state goes to IDLE so we can request it without waiting. On a
// multi-master bus, the bus may be BUSY under control of another master,
// in which case we can avoid some arbitration failures by waiting until
// the bus state is IDLE. We don't do that here.
//while (s->SR2 & I2C_SR2_BUSY) {}
// Check there's no STOP still in progress. If we OR the START bit into CR1
// and the STOP bit is already set, we could output multiple STOP conditions.
while (s->CR1 & I2C_CR1_STOP) {} // Wait for STOP bit to reset
s->CR2 |= (I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable interrupts
s->CR2 &= ~I2C_CR2_ITBUFEN; // Don't enable buffer interupts yet.
s->CR1 &= ~I2C_CR1_POS; // Clear the POS bit
s->CR1 |= (I2C_CR1_ACK | I2C_CR1_START); // Enable the ACK and generate START
transactionState = TS_START;
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
{
// Send start for read operation
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
s->CR1 |= I2C_CR1_START; // Generate START
// Send address with read flag (1) or'd in
s->DR = (deviceAddress << 1) | 1; // send the address
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
// Special case for 1 byte reads!
if (bytesToReceive == 1)
{
s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit
temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
s->CR1 |= I2C_CR1_STOP; // Stop I2C
}
else
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
}
else {
// Send start for write operation
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
s->CR1 |= I2C_CR1_START; // Generate START
// Send address with write flag (0) or'd in
s->DR = (deviceAddress << 1) | 0; // send the address
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
}
}
/***************************************************************************
* Initiate a stop bit for transmission (does not interrupt)
***************************************************************************/
void I2CManagerClass::I2C_sendStop() {
s->CR1 |= I2C_CR1_STOP; // Stop I2C
s->CR1 |= I2C_CR1_STOP; // Stop I2C
}
/***************************************************************************
@@ -291,11 +252,9 @@ void I2CManagerClass::I2C_close() {
s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral
// Should never happen, but wait for up to 500us only.
unsigned long startTime = micros();
while ((s->CR1 & I2C_CR1_PE) != 0) {
if ((int32_t)(micros() - startTime) >= 500) break;
while ((s->CR1 && I2C_CR1_PE) != 0) {
if (micros() - startTime >= 500UL) break;
}
NVIC_DisableIRQ(I2C1_EV_IRQn);
NVIC_DisableIRQ(I2C1_ER_IRQn);
}
/***************************************************************************
@@ -304,217 +263,50 @@ void I2CManagerClass::I2C_close() {
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
***************************************************************************/
void I2CManagerClass::I2C_handleInterrupt() {
volatile uint16_t temp_sr1, temp_sr2;
temp_sr1 = s->SR1;
// Check for errors first
if (temp_sr1 & (I2C_SR1_AF | I2C_SR1_ARLO | I2C_SR1_BERR)) {
// Check which error flag is set
if (temp_sr1 & I2C_SR1_AF)
{
s->SR1 &= ~(I2C_SR1_AF); // Clear AF
I2C_sendStop(); // Clear the bus
transactionState = TS_IDLE;
if (s->SR1 && I2C_SR1_ARLO) {
// Arbitration lost, restart
I2C_sendStart(); // Reinitiate request
} else if (s->SR1 && I2C_SR1_BERR) {
// Bus error
completionStatus = I2C_STATUS_BUS_ERROR;
state = I2C_STATE_COMPLETED;
} else if (s->SR1 && I2C_SR1_TXE) {
// Master write completed
if (s->SR1 && (1<<10)) {
// Nacked, send stop.
I2C_sendStop();
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
state = I2C_STATE_COMPLETED;
}
else if (temp_sr1 & I2C_SR1_ARLO)
{
// Arbitration lost, restart
s->SR1 &= ~(I2C_SR1_ARLO); // Clear ARLO
I2C_sendStart(); // Reinitiate request
transactionState = TS_START;
}
else if (temp_sr1 & I2C_SR1_BERR)
{
// Bus error
s->SR1 &= ~(I2C_SR1_BERR); // Clear BERR
I2C_sendStop(); // Clear the bus
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_BUS_ERROR;
} else if (bytesToSend) {
// Acked, so send next byte
s->DR = sendBuffer[txCount++];
bytesToSend--;
} else if (bytesToReceive) {
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
// s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
} else {
// Check both TxE/BTF == 1 before generating stop
while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE
while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF
// No more data to send/receive. Initiate a STOP condition and finish
I2C_sendStop();
state = I2C_STATE_COMPLETED;
}
}
else {
// No error flags, so process event according to current state.
switch (transactionState) {
case TS_START:
if (temp_sr1 & I2C_SR1_SB) {
// Event EV5
// Start bit has been sent successfully and we have the bus.
// If anything to send, initiate write. Otherwise initiate read.
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) {
// Send address with read flag (1) or'd in
s->DR = (deviceAddress << 1) | 1; // send the address
transactionState = TS_R_ADDR;
} else {
// Send address with write flag (0) or'd in
s->DR = (deviceAddress << 1) | 0; // send the address
transactionState = TS_W_ADDR;
}
}
// SB bit is cleared by writing to DR (already done).
break;
case TS_W_ADDR:
if (temp_sr1 & I2C_SR1_ADDR) {
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
// Event EV6
// Address sent successfully, device has ack'd in response.
if (!bytesToSend) {
I2C_sendStop();
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_OK;
state = I2C_STATE_COMPLETED;
} else {
// Put one byte into DR to load shift register.
s->DR = sendBuffer[txCount++];
bytesToSend--;
if (bytesToSend) {
// Put another byte to load DR
s->DR = sendBuffer[txCount++];
bytesToSend--;
}
if (!bytesToSend) {
// No more bytes to send.
// The TXE interrupt occurs when the DR is empty, and the BTF interrupt
// occurs when the shift register is also empty (one character later).
// To avoid repeated TXE interrupts during this time, we disable TXE interrupt.
s->CR2 &= ~I2C_CR2_ITBUFEN; // Wait for BTF interrupt, disable TXE interrupt
transactionState = TS_W_STOP;
} else {
// More data remaining to send after this interrupt, enable TXE interrupt.
s->CR2 |= I2C_CR2_ITBUFEN;
transactionState = TS_W_DATA;
}
}
}
break;
case TS_W_DATA:
if (temp_sr1 & I2C_SR1_TXE) {
// Event EV8_1/EV8
// Transmitter empty, write a byte to it.
if (bytesToSend) {
s->DR = sendBuffer[txCount++];
bytesToSend--;
if (!bytesToSend) {
s->CR2 &= ~I2C_CR2_ITBUFEN; // Disable TXE interrupt
transactionState = TS_W_STOP;
}
}
}
break;
case TS_W_STOP:
if (temp_sr1 & I2C_SR1_BTF) {
// Event EV8_2
// Done, last character sent. Anything to receive?
if (bytesToReceive) {
I2C_sendStart();
// NOTE: Three redundant BTF interrupts take place between the
// first BTF interrupt and the START interrupt. I've tried all sorts
// of ways to eliminate them, and the only thing that worked for
// me was to loop until the BTF bit becomes reset. Either way,
// it's a waste of processor time. Anyone got a solution?
//while (s->SR1 && I2C_SR1_BTF) {}
transactionState = TS_START;
} else {
I2C_sendStop();
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_OK;
state = I2C_STATE_COMPLETED;
}
s->SR1 &= I2C_SR1_BTF; // Clear BTF interrupt
}
break;
case TS_R_ADDR:
if (temp_sr1 & I2C_SR1_ADDR) {
// Event EV6
// Address sent for receive.
// The next bit is different depending on whether there are
// 1 byte, 2 bytes or >2 bytes to be received, in accordance with the
// Programmers Reference RM0390.
if (bytesToReceive == 1) {
// Receive 1 byte
s->CR1 &= ~I2C_CR1_ACK; // Disable ack
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
// Next step will occur after a RXNE interrupt, so enable it
s->CR2 |= I2C_CR2_ITBUFEN;
transactionState = TS_R_STOP;
} else if (bytesToReceive == 2) {
// Receive 2 bytes
s->CR1 &= ~I2C_CR1_ACK; // Disable ACK for final byte
s->CR1 |= I2C_CR1_POS; // set POS flag to delay effect of ACK flag
// Next step will occur after a BTF interrupt, so disable RXNE interrupt
s->CR2 &= ~I2C_CR2_ITBUFEN;
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
transactionState = TS_R_STOP;
} else {
// >2 bytes, just wait for bytes to come in and ack them for the time being
// (ack flag has already been set).
// Next step will occur after a BTF interrupt, so disable RXNE interrupt
s->CR2 &= ~I2C_CR2_ITBUFEN;
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
transactionState = TS_R_DATA;
}
}
break;
case TS_R_DATA:
// Event EV7/EV7_1
if (temp_sr1 & I2C_SR1_BTF) {
// Byte received in receiver - read next byte
if (bytesToReceive == 3) {
// Getting close to the last byte, so a specific sequence is recommended.
s->CR1 &= ~I2C_CR1_ACK; // Reset ack for next byte received.
transactionState = TS_R_STOP;
}
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive--;
}
break;
case TS_R_STOP:
if (temp_sr1 & I2C_SR1_BTF) {
// Event EV7 (last one)
// When we've got here, the receiver has got the last two bytes
// (or one byte, if only one byte is being received),
// and NAK has already been sent, so we need to read from the receiver.
if (bytesToReceive) {
if (bytesToReceive > 1)
I2C_sendStop();
while(bytesToReceive) {
receiveBuffer[rxCount++] = s->DR; // Store received byte(s)
bytesToReceive--;
}
// Finish.
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_OK;
state = I2C_STATE_COMPLETED;
}
} else if (temp_sr1 & I2C_SR1_RXNE) {
if (bytesToReceive == 1) {
// One byte on a single-byte transfer. Ack has already been set.
I2C_sendStop();
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive--;
// Finish.
transactionState = TS_IDLE;
completionStatus = I2C_STATUS_OK;
state = I2C_STATE_COMPLETED;
} else
s->SR1 &= I2C_SR1_RXNE; // Acknowledge interrupt
}
break;
} else if (s->SR1 && I2C_SR1_RXNE) {
// Master read completed without errors
if (bytesToReceive == 1) {
// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
I2C_sendStop(); // send stop
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive = 0;
state = I2C_STATE_COMPLETED;
} else if (bytesToReceive) {
// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
receiveBuffer[rxCount++] = s->DR; // Store received byte
bytesToReceive--;
}
// If we've received an interrupt at any other time, we're not interested so clear it
// to prevent it recurring ad infinitum.
s->SR1 = 0;
}
}
#endif /* I2CMANAGER_STM32_H */

View File

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

View File

@@ -176,13 +176,6 @@ bool IODevice::exists(VPIN vpin) {
return findDevice(vpin) != NULL;
}
// Return the status of the device att vpin.
uint8_t IODevice::getStatus(VPIN vpin) {
IODevice *dev = findDevice(vpin);
if (!dev) return false;
return dev->_deviceState;
}
// check whether the pin supports notification. If so, then regular _read calls are not required.
bool IODevice::hasCallback(VPIN vpin) {
IODevice *dev = findDevice(vpin);

View File

@@ -27,6 +27,12 @@
// Define symbol DIAG_LOOPTIMES to enable CS loop execution time to be reported
//#define DIAG_LOOPTIMES
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
#define IO_NO_HAL
#endif
// Define symbol IO_SWITCH_OFF_SERVO to set the PCA9685 output to 0 when an
// animation has completed. This switches off the servo motor, preventing
// the continuous buzz sometimes found on servos, and reducing the
@@ -154,9 +160,6 @@ public:
// exists checks whether there is a device owning the specified vpin
static bool exists(VPIN vpin);
// getStatus returns the state of the device at the specified vpin
static uint8_t getStatus(VPIN vpin);
// Enable shared interrupt on specified pin for GPIO extender modules. The extender module
// should pull down this pin when requesting a scan. The pin may be shared by multiple modules.
// Without the shared interrupt, input states are scanned periodically to detect changes on
@@ -380,7 +383,6 @@ private:
uint8_t *_pinInUse;
};
#ifndef IO_NO_HAL
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for EX-Turntable.
@@ -409,14 +411,10 @@ private:
void _begin() override;
void _loop(unsigned long currentMicros) override;
int _read(VPIN vpin) override;
void _broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity);
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
void _display() override;
uint8_t _stepperStatus;
uint8_t _previousStatus;
uint8_t _currentActivity;
};
#endif
/////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -20,21 +20,20 @@
/*
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
*
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/EX-Turntable) and contains the stepper motor logic.
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic.
*
* This device driver sends a step position to EX-Turntable to indicate the step position to move to using either of these commands:
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
* <D TT vpin steps activity> in the serial console
* MOVETT(vpin, steps, activity) in EX-RAIL
* Refer to the documentation for further information including the valid activities.
*/
#ifndef IO_EXTurntable_h
#define IO_EXTurntable_h
#include "IODevice.h"
#include "I2CManager.h"
#include "DIAG.h"
#include "Turntables.h"
#include "CommandDistributor.h"
#ifndef IO_NO_HAL
void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
new EXTurntable(firstVpin, nPins, I2CAddress);
@@ -45,21 +44,18 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
_firstVpin = firstVpin;
_nPins = nPins;
_I2CAddress = I2CAddress;
_stepperStatus = 0;
_previousStatus = 0;
addDevice(this);
}
// Initialisation of EXTurntable
void EXTurntable::_begin() {
I2CManager.begin();
I2CManager.setClock(1000000);
if (I2CManager.exists(_I2CAddress)) {
DIAG(F("EX-Turntable device found, I2C:%s"), _I2CAddress.toString());
#ifdef DIAG_IO
_display();
#endif
} else {
DIAG(F("EX-Turntable I2C:%s device not found"), _I2CAddress.toString());
_deviceState = DEVSTATE_FAILED;
}
}
@@ -71,19 +67,15 @@ void EXTurntable::_loop(unsigned long currentMicros) {
uint8_t readBuffer[1];
I2CManager.read(_I2CAddress, readBuffer, 1);
_stepperStatus = readBuffer[0];
if (_stepperStatus != _previousStatus && _stepperStatus == 0) { // Broadcast when a rotation finishes
if ( _currentActivity < 4) {
_broadcastStatus(_firstVpin, _stepperStatus, _currentActivity);
}
_previousStatus = _stepperStatus;
}
delayUntil(currentMicros + 100000); // Wait 100ms before checking again
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
}
// Read returns status as obtained in our loop.
// Return false if our status value is invalid.
int EXTurntable::_read(VPIN vpin) {
if (_deviceState == DEVSTATE_FAILED) return 0;
// DIAG(F("_read status: %d"), _stepperStatus);
if (_stepperStatus > 1) {
return false;
} else {
@@ -91,17 +83,6 @@ int EXTurntable::_read(VPIN vpin) {
}
}
// If a status change has occurred for a turntable object, broadcast it
void EXTurntable::_broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity) {
Turntable *tto = Turntable::getByVpin(vpin);
if (tto) {
if (activity < 4) {
tto->setMoving(status);
CommandDistributor::broadcastTurntable(tto->getId(), tto->getPosition(), status);
}
}
}
// writeAnalogue to send the steps and activity to Turntable-EX.
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
// value contains the steps, bit shifted to MSB + LSB.
@@ -119,7 +100,6 @@ void EXTurntable::_broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity)
// Acc_Off = 9 // Turn accessory pin off
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
if (_deviceState == DEVSTATE_FAILED) return;
if (value < 0) return;
uint8_t stepsMSB = value >> 8;
uint8_t stepsLSB = value & 0xFF;
#ifdef DIAG_IO
@@ -128,10 +108,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
#endif
if (activity < 4) _stepperStatus = 1; // Tell the device driver Turntable-EX is busy
_previousStatus = _stepperStatus;
_currentActivity = activity;
_broadcastStatus(vpin, _stepperStatus, activity); // Broadcast when the rotation starts
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
}

View File

@@ -1,111 +0,0 @@
/*
* © 2021, Neil McKechnie. All rights reserved.
*
* This file is part of DCC++EX API
*
* This is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* It is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
*/
#ifndef io_pca9555_h
#define io_pca9555_h
#include "IO_GPIOBase.h"
#include "FSH.h"
/////////////////////////////////////////////////////////////////////////////////////////////////////
/*
* IODevice subclass for PCA9555 16-bit I/O expander (NXP & Texas Instruments).
*/
class PCA9555 : public GPIOBase<uint16_t> {
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, 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;
}
void _writeGpioPort() override {
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
}
void _writePullups() override {
// Do nothing, pull-ups are always in place for input ports
// This function is here for HAL GPIOBase API compatibilitiy
}
void _writePortModes() override {
// Write 0 to REG_CONF_P0 & REG_CONF_P1 for in-use pins that are outputs, 1 for others.
// PCA9555 & TCA9555, Interrupt is always enabled for raising and falling edge
uint16_t temp = ~(_portMode & _portInUse);
I2CManager.write(_I2CAddress, 3, REG_CONF_P0, temp, temp>>8);
}
void _readGpioPort(bool immediate) override {
if (immediate) {
uint8_t buffer[2];
I2CManager.read(_I2CAddress, buffer, 2, 1, REG_INPUT_P0);
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0];
/* PCA9555 Int bug fix, from PCA9555 datasheet: "must change command byte to something besides 00h
* after a Read operation to the PCA9555 device or before reading from
* another device"
* Recommended solution, read from REG_OUTPUT_P0, then do nothing with the received data
* Issue not seen during testing, uncomment if needed
*/
//I2CManager.read(_I2CAddress, buffer, 2, 1, REG_OUTPUT_P0);
} else {
// Queue new request
requestBlock.wait(); // Wait for preceding operation to complete
// Issue new request to read GPIO register
I2CManager.queueRequest(&requestBlock);
}
}
// This function is invoked when an I/O operation on the requestBlock completes.
void _processCompletion(uint8_t status) override {
if (status == I2C_STATUS_OK)
_portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0];
else
_portInputState = 0xffff;
}
void _setupDevice() override {
// HAL API calls
_writePortModes();
_writePullups();
_writeGpioPort();
}
uint8_t inputBuffer[2];
uint8_t outputBuffer[1];
enum {
REG_INPUT_P0 = 0x00,
REG_INPUT_P1 = 0x01,
REG_OUTPUT_P0 = 0x02,
REG_OUTPUT_P1 = 0x03,
REG_POL_INV_P0 = 0x04,
REG_POL_INV_P1 = 0x05,
REG_CONF_P0 = 0x06,
REG_CONF_P1 = 0x07,
};
};
#endif

View File

@@ -134,13 +134,12 @@ private:
}
}
// Return the position sent by the rotary encoder software
// Device specific read function
int _readAnalogue(VPIN vpin) override {
if (_deviceState == DEVSTATE_FAILED) return 0;
return _position;
}
// Send the feedback value to the rotary encoder software
void _write(VPIN vpin, int value) override {
if (vpin == _firstVpin + 1) {
if (value != 0) value = 0x01;
@@ -149,12 +148,9 @@ private:
}
}
// Send a position update to the rotary encoder software
// To be valid, must be 0 to 255, and different to the current position
// If the current position is the same, it was initiated by the rotary encoder
void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override {
if (vpin == _firstVpin + 2) {
if (position >= 0 && position <= 255 && position != _position) {
if (position >= 0 && position <= 255) {
byte newPosition = position & 0xFF;
byte _positionBuffer[2] = {RE_MOVE, newPosition};
I2CManager.write(_I2CAddress, _positionBuffer, 2);

View File

@@ -4,8 +4,6 @@
* © 2021 Fred Decker
* © 2020-2023 Harald Barth
* © 2020-2021 Chris Harlow
* © 2023 Colin Murdoch
* © 2023 Travis Farmer
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -28,27 +26,12 @@
#include "DCCWaveform.h"
#include "DCCTimer.h"
#include "DIAG.h"
#include "EXRAIL2.h"
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) {
@@ -67,7 +50,6 @@ 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);
@@ -84,29 +66,13 @@ 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);
@@ -123,22 +89,6 @@ 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;
@@ -185,11 +135,7 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
// float calculations or libraray code.
senseFactorInternal=sense_factor * senseScale;
tripMilliamps=trip_milliamps;
#ifdef MAX_CURRENT
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
tripMilliamps = MAX_CURRENT;
#endif
rawCurrentTripValue=mA2raw(tripMilliamps);
rawCurrentTripValue=mA2raw(trip_milliamps);
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
// This would mean that the values obtained from the ADC never
@@ -223,11 +169,7 @@ bool MotorDriver::isPWMCapable() {
void MotorDriver::setPower(POWERMODE mode) {
if (powerMode == mode) return;
//DIAG(F("Track %c POWERMODE=%d"), trackLetter, (int)mode);
lastPowerChange[(int)mode] = micros();
if (mode == POWERMODE::OVERLOAD)
globalOverloadStart = lastPowerChange[(int)mode];
bool on=(mode==POWERMODE::ON || mode ==POWERMODE::ALERT);
bool on=mode==POWERMODE::ON;
if (on) {
// when switching a track On, we need to check the crrentOffset with the pin OFF
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
@@ -267,8 +209,8 @@ bool MotorDriver::canMeasureCurrent() {
return currentPin!=UNUSED_PIN;
}
/*
* Return the current reading as pin reading 0 to max resolution (1024 or 4096).
* If the fault pin is activated return a negative current to show active fault pin.
* Return the current reading as pin reading 0 to 1023. If the fault
* pin is activated return a negative current to show active fault pin.
* As there is no -0, cheat a little and return -1 in that case.
*
* senseOffset handles the case where a shield returns values above or below
@@ -327,8 +269,7 @@ void MotorDriver::startCurrentFromHW() {
#pragma GCC pop_options
#endif //ANALOG_READ_INTERRUPT
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
#ifdef VARIABLE_TONES
#if defined(ARDUINO_ARCH_ESP32)
uint16_t taurustones[28] = { 165, 175, 196, 220,
247, 262, 294, 330,
349, 392, 440, 494,
@@ -337,57 +278,29 @@ uint16_t taurustones[28] = { 165, 175, 196, 220,
330, 284, 262, 247,
220, 196, 175, 165 };
#endif
#endif
void MotorDriver::setDCSignal(byte speedcode) {
if (brakePin == UNUSED_PIN)
return;
switch(brakePin) {
#if defined(ARDUINO_AVR_UNO)
// Not worth doin something here as:
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
#endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
case 9:
case 10:
// Timer2 (is differnet)
TCCR2A = (TCCR2A & B11111100) | B00000001; // set WGM1=0 and WGM0=1 phase correct PWM
TCCR2B = (TCCR2B & B11110000) | B00000110; // set WGM2=0 ; set divisor on timer 2 to 1/256 for 122.55Hz
//DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
break;
case 6:
case 7:
case 8:
// Timer4
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
TCCR4B = (TCCR4B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
break;
case 46:
case 45:
case 44:
// Timer5
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for normal PWM 8-bit
TCCR5B = (TCCR5B & B11100000) | B00000100; // set WGM2=0 and WGM3=0 for normal PWM 8 bit and div 1/256 for 122.55Hz
break;
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
TCCR4B = (TCCR4B & B11111000) | B00000100; // same for timer 4 but maxcount and thus divisor differs
TCCR5B = (TCCR5B & B11111000) | B00000100; // same for timer 5 which is like timer 4
#endif
default:
break;
}
// spedcoode is a dcc speed & direction
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) || defined(ARDUINO_ARCH_STM32)
#if defined(ARDUINO_ARCH_ESP32)
{
int f = 131;
#ifdef VARIABLE_TONES
if (tSpeed > 2) {
if (tSpeed <= 58) {
f = taurustones[ (tSpeed-2)/2 ] ;
}
}
#endif
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
}
#endif
@@ -396,7 +309,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
else brake = 2 * (128-tSpeed);
if (invertBrake)
brake=255-brake;
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
#if defined(ARDUINO_ARCH_ESP32)
DCCTimer::DCCEXanalogWrite(brakePin,brake);
#else
analogWrite(brakePin,brake);
@@ -420,91 +333,13 @@ 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);
interrupts();
}
}
void MotorDriver::throttleInrush(bool on) {
if (brakePin == UNUSED_PIN)
return;
if ( !(trackMode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_EXT)))
return;
byte duty = on ? 208 : 0;
if (invertBrake)
duty = 255-duty;
#if defined(ARDUINO_ARCH_ESP32)
if(on) {
DCCTimer::DCCEXanalogWrite(brakePin,duty);
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
} 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) {
#if defined(ARDUINO_AVR_UNO)
// Not worth doin something here as:
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
// We are most likely not on pin 3 or 11 as no known motor shield has that as brake.
#endif
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
case 9:
case 10:
// Timer2 (is different)
TCCR2A = (TCCR2A & B11111100) | B00000011; // set WGM0=1 and WGM1=1 for fast PWM
TCCR2B = (TCCR2B & B11110000) | B00000001; // set WGM2=0 and prescaler div=1 (max)
DIAG(F("2 A=%x B=%x"), TCCR2A, TCCR2B);
break;
case 6:
case 7:
case 8:
// Timer4
TCCR4A = (TCCR4A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
TCCR4B = (TCCR4B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
break;
case 46:
case 45:
case 44:
// Timer5
TCCR5A = (TCCR5A & B11111100) | B00000001; // set WGM0=1 and WGM1=0 for fast PWM 8-bit
TCCR5B = (TCCR5B & B11100000) | B00001001; // set WGM2=1 and WGM3=0 for fast PWM 8 bit and div=1 (max)
break;
#endif
default:
break;
}
}
analogWrite(brakePin,duty);
#endif
}
unsigned int MotorDriver::raw2mA( int raw) {
//DIAG(F("%d = %d * %d / %d"), (int32_t)raw * senseFactorInternal / senseScale, raw, senseFactorInternal, senseScale);
return (int32_t)raw * senseFactorInternal / senseScale;
@@ -514,24 +349,13 @@ 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
@@ -541,175 +365,115 @@ 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);
}
///////////////////////////////////////////////////////////////////////////////////////////
// checkPowerOverload(useProgLimit, trackno)
// bool useProgLimit: Trackmanager knows if this track is in prog mode or in main mode
// byte trackno: trackmanager knows it's number (could be skipped?)
//
// Short ciruit handling strategy:
//
// There are the following power states: ON ALERT OVERLOAD OFF
// OFF state is only changed to/from manually. Power is on
// during ON and ALERT. Power is off during OVERLOAD and OFF.
// The overload mechanism changes between the other states like
//
// ON -1-> ALERT -2-> OVERLOAD -3-> ALERT -4-> ON
// or
// ON -1-> ALERT -4-> ON
//
// Times are in class MotorDriver (MotorDriver.h).
//
// 1. ON to ALERT:
// Transition on fault pin condition or current overload
//
// 2. ALERT to OVERLOAD:
// Transition happens if different timeouts have elapsed.
// If only the fault pin is active, timeout is
// POWER_SAMPLE_IGNORE_FAULT_LOW (100ms)
// If only overcurrent is detected, timeout is
// POWER_SAMPLE_IGNORE_CURRENT (100ms)
// If fault pin and overcurrent are active, timeout is
// POWER_SAMPLE_IGNORE_FAULT_HIGH (5ms)
// Transition to OVERLOAD turns off power to the affected
// output (unless fault pins are shared)
// If the transition conditions are not fullfilled,
// transition according to 4 is tested.
//
// 3. OVERLOAD to ALERT
// Transiton happens when timeout has elapsed, timeout
// is named power_sample_overload_wait. It is started
// at POWER_SAMPLE_OVERLOAD_WAIT (40ms) at first entry
// to OVERLOAD and then increased by a factor of 2
// at further entries to the OVERLOAD condition. This
// happens until POWER_SAMPLE_RETRY_MAX (10sec) is reached.
// power_sample_overload_wait is reset by a poweroff or
// a POWER_SAMPLE_ALL_GOOD (5sec) period during ON.
// After timeout power is turned on again and state
// goes back to ALERT.
//
// 4. ALERT to ON
// Transition happens by watching the current and fault pin
// samples during POWER_SAMPLE_ALERT_GOOD (20ms) time. If
// values have been good during that time, transition is
// made back to ON. Note that even if state is back to ON,
// the power_sample_overload_wait time is first reset
// later (see above).
//
// The time keeping is handled by timestamps lastPowerChange[]
// which are set by each power change and by lastBadSample which
// keeps track if conditions during ALERT have been good enough
// to go back to ON. The time differences are calculated by
// microsSinceLastPowerChange().
//
void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
switch (powerMode) {
case POWERMODE::OFF: {
lastPowerMode = POWERMODE::OFF;
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
break;
}
case POWERMODE::ON: {
lastPowerMode = POWERMODE::ON;
bool cF = checkFault();
bool cC = checkCurrent(useProgLimit);
if(cF || cC ) {
if (cC) {
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c ALERT %s %dmA"), trackno + 'A',
cF ? "FAULT" : "",
mA);
} else {
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
case POWERMODE::OFF:
if (overloadNow) {
// reset overload condition as we have just turned off power
// DIAG(F("OVERLOAD POFF OFF"));
overloadNow=false;
setLastPowerChange();
}
setPower(POWERMODE::ALERT);
break;
}
// all well
if (microsSinceLastPowerChange(POWERMODE::ON) > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
}
break;
}
case POWERMODE::ALERT: {
// set local flags that handle how much is output to diag (do not output duplicates)
bool notFromOverload = (lastPowerMode != POWERMODE::OVERLOAD);
bool powerModeChange = (powerMode != lastPowerMode);
unsigned long now = micros();
if (powerModeChange)
lastBadSample = now;
lastPowerMode = POWERMODE::ALERT;
// check how long we have been in this state
unsigned long mslpc = microsSinceLastPowerChange(POWERMODE::ALERT);
if(checkFault()) {
throttleInrush(true);
lastBadSample = now;
unsigned long timeout = checkCurrent(useProgLimit) ? POWER_SAMPLE_IGNORE_FAULT_HIGH : POWER_SAMPLE_IGNORE_FAULT_LOW;
if ( mslpc < timeout) {
if (powerModeChange)
DIAG(F("TRACK %c FAULT PIN (%M ignore)"), trackno + 'A', timeout);
break;
if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
}
DIAG(F("TRACK %c FAULT PIN detected after %4M. Pause %4M)"), trackno + 'A', mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD);
break;
}
if (checkCurrent(useProgLimit)) {
lastBadSample = now;
if (mslpc < POWER_SAMPLE_IGNORE_CURRENT) {
if (powerModeChange) {
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c CURRENT (%M ignore) %dmA"), trackno + 'A', POWER_SAMPLE_IGNORE_CURRENT, mA);
case POWERMODE::ON:
// Check current
lastCurrent=getCurrentRaw();
if (lastCurrent < 0) {
// We have a fault pin condition to take care of
if (!overloadNow) {
// turn on overload condition as fault pin has gone active
// DIAG(F("OVERLOAD FPIN ON"));
overloadNow=true;
setLastPowerChangeOverload();
}
lastCurrent = -lastCurrent;
{
if (lastCurrent < tripValue) {
if (power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) && // almost virgin
microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_LOW) {
// Ignore 50ms fault pin if no current
DIAG(F("TRACK %c FAULT PIN (50ms ignore)"), trackno + 'A');
break;
}
lastCurrent = tripValue; // exaggerate so condition below (*) is true
} else {
if (power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && // virgin
microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_HIGH) {
// Ignore 5ms fault pin if we see current
DIAG(F("TRACK %c FAULT PIN (5ms ignore)"), trackno + 'A');
break;
}
}
DIAG(F("TRACK %c FAULT PIN"), trackno + 'A');
}
}
// // //
// above we looked at fault pin, below we look at current
// // //
if (lastCurrent < tripValue) { // see above (*)
if (overloadNow) {
// current is below trip value, turn off overload condition
// DIAG(F("OVERLOAD PON OFF"));
overloadNow=false;
setLastPowerChange();
}
if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) {
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
}
} else {
// too much current
if (!overloadNow) {
// current is over trip value, turn on overload condition
// DIAG(F("OVERLOAD PON ON"));
overloadNow=true;
setLastPowerChange();
}
unsigned long uSecs = microsSinceLastPowerChange();
if (power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || // not virgin
uSecs > POWER_SAMPLE_OFF_DELAY) {
// Overload has existed longer than delay (typ. 10ms)
setPower(POWERMODE::OVERLOAD);
if (overloadNow) {
// the setPower just turned off, so overload is now gone
// DIAG(F("OVERLOAD PON OFF"));
overloadNow=false;
setLastPowerChangeOverload();
}
unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue);
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
trackno + 'A', mA, maxmA, uSecs, power_sample_overload_wait);
}
break;
}
unsigned int mA=raw2mA(lastCurrent);
unsigned int maxmA=raw2mA(tripValue);
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
trackno + 'A', mA, maxmA, mslpc, power_sample_overload_wait);
throttleInrush(false);
setPower(POWERMODE::OVERLOAD);
break;
}
// all well
unsigned long goodtime = micros() - lastBadSample;
if (goodtime > POWER_SAMPLE_ALERT_GOOD) {
if (true || notFromOverload) { // we did a RESTORE message XXX
unsigned int mA=raw2mA(lastCurrent);
DIAG(F("TRACK %c NORMAL (after %M/%M) %dmA"), trackno + 'A', goodtime, mslpc, mA);
}
throttleInrush(false);
setPower(POWERMODE::ON);
}
break;
}
case POWERMODE::OVERLOAD: {
lastPowerMode = POWERMODE::OVERLOAD;
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
case POWERMODE::OVERLOAD:
{
// Try setting it back on after the OVERLOAD_WAIT
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange());
if (mslpc > power_sample_overload_wait) {
// adjust next wait time
power_sample_overload_wait *= 2;
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
DIAG(F("Calling EXRAIL"));
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
// power on test
setPower(POWERMODE::ON);
// here we change power but not the overloadNow as that was
// already changed to false when we entered POWERMODE::OVERLOAD
// so we need to set the lastPowerChange anyway.
overloadNow=false;
setLastPowerChange();
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
setPower(POWERMODE::ALERT);
}
break;
}
break;
default:
break;
}

View File

@@ -1,10 +1,9 @@
/*
* © 2022-2023 Paul M. Antoine
* © 2022 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
@@ -28,24 +27,11 @@
#include "IODevice.h"
#include "DCCTimer.h"
// 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)
@@ -70,35 +56,6 @@ 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
@@ -113,15 +70,6 @@ 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
@@ -140,36 +88,26 @@ public:
byte invpin = UNUSED_PIN;
};
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32) || (defined(ARDUINO_GIGA) && defined(XGIGA))
#if defined(__IMXRT1062__) || defined(ARDUINO_ARCH_ESP8266) || defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
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 };
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
class MotorDriver {
public:
@@ -184,12 +122,6 @@ 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);
@@ -205,7 +137,6 @@ class MotorDriver {
}
}
};
#endif // giga
inline void enableSignal(bool on) {
if (on)
pinMode(signalPin, OUTPUT);
@@ -214,7 +145,6 @@ class MotorDriver {
};
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
void setDCSignal(byte speedByte);
void throttleInrush(bool on);
inline void detachDCSignal() {
#if defined(__arm__)
pinMode(brakePin, OUTPUT);
@@ -227,23 +157,17 @@ 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)
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)
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
return true;
#else
#ifdef digitalPinToTimer
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
#else
return (brakePin<14 && brakePin >1);
#endif
#endif //digitalPinToTimer
#endif //ESP32/ARM
}
inline int getRawCurrentTripValue() {
return rawCurrentTripValue;
@@ -268,43 +192,35 @@ class MotorDriver {
// this returns how much time has passed since the last power change. If it
// was really long ago (approx > 52min) advance counter approx 35 min so that
// we are at 18 minutes again. Times for 32 bit unsigned long.
inline unsigned long microsSinceLastPowerChange(POWERMODE mode) {
inline unsigned long microsSinceLastPowerChange() {
unsigned long now = micros();
unsigned long diff = now - lastPowerChange[(int)mode];
unsigned long diff = now - lastPowerChange;
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago
lastPowerChange = now - 30000000UL; // 30 seconds ago
return diff;
};
inline void setLastPowerChange() {
lastPowerChange = micros();
};
// as setLastPowerChange but sets the global timestamp as well which
// is only used to sync power restore in case of common Fault pin.
inline void setLastPowerChangeOverload() {
if (commonFaultPin)
globalOverloadStart = lastPowerChange = micros();
else
setLastPowerChange();
};
#ifdef ANALOG_READ_INTERRUPT
bool sampleCurrentFromHW();
void startCurrentFromHW();
#endif
inline void setMode(TRACK_MODE m) {
trackMode = m;
};
inline TRACK_MODE getMode() {
return trackMode;
};
private:
char trackLetter = '?';
bool isProgTrack = false; // tells us if this is a prog track
void getFastPin(const FSH* type,int pin, bool input, FASTPIN & result);
inline void getFastPin(const FSH* type,int pin, FASTPIN & result) {
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
getFastPin(type, pin, 0, result);
};
// side effect sets lastCurrent and tripValue
inline bool checkCurrent(bool useProgLimit) {
tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
lastCurrent = getCurrentRaw();
if (lastCurrent < 0)
lastCurrent = -lastCurrent;
return lastCurrent >= tripValue;
};
// side effect sets lastCurrent
inline bool checkFault() {
lastCurrent = getCurrentRaw();
return lastCurrent < 0;
};
}
VPIN powerPin;
byte signalPin, signalPin2, currentPin, faultPin, brakePin;
FASTPIN fastSignalPin, fastSignalPin2, fastBrakePin,fastFaultPin;
@@ -325,14 +241,12 @@ class MotorDriver {
int rawCurrentTripValue;
// current sampling
POWERMODE powerMode;
POWERMODE lastPowerMode;
unsigned long lastPowerChange[4]; // timestamp in microseconds
unsigned long lastBadSample; // timestamp in microseconds
bool overloadNow = false;
unsigned long lastPowerChange; // timestamp in microseconds
// used to sync restore time when common Fault pin detected
static unsigned long globalOverloadStart; // timestamp in microseconds
int progTripValue;
int lastCurrent; //temp value
int tripValue; //temp value
int lastCurrent;
#ifdef ANALOG_READ_INTERRUPT
volatile unsigned long sampleCurrentTimestamp;
volatile uint16_t sampleCurrent;
@@ -342,17 +256,15 @@ class MotorDriver {
// Times for overload management. Unit: microseconds.
// Base for wait time until power is turned on again
static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 40000UL;
static const unsigned long POWER_SAMPLE_OVERLOAD_WAIT = 100UL;
// Time after we consider all faults old and forgotten
static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL;
// Time after which we consider a ALERT over
static const unsigned long POWER_SAMPLE_ALERT_GOOD = 20000UL;
// How long to ignore fault pin if current is under limit
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 100000UL;
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 50000UL;
// How long to ignore fault pin if current is higher than limit
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL;
// How long to wait between overcurrent and turning off
static const unsigned long POWER_SAMPLE_IGNORE_CURRENT = 100000UL;
static const unsigned long POWER_SAMPLE_OFF_DELAY = 10000UL;
// Upper limit for retry period
static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL;
@@ -361,7 +273,6 @@ class MotorDriver {
static const int TRIP_CURRENT_PROG=250;
unsigned long power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
unsigned int power_good_counter = 0;
TRACK_MODE trackMode = TRACK_MODE_NONE; // we assume track not assigned at startup
};
#endif

View File

@@ -89,10 +89,27 @@
// EX 8874 based shield connected to a 3.3V system (like ESP32) and 12bit (4096) ADC
// numbers are GPIO numbers. comments are UNO form factor shield pin numbers
#define EX8874_SHIELD F("EX8874"),\
#define EX8874_SHIELD F("EX-8874"),\
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 1.27, 5000, 36 /*A4*/), \
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 1.27, 5000, 39 /*A5*/)
// EX-CSB1 motor shield definition - note it is different from ESPduino32 pins!
#define EX_CSB1 F("EX-CSB1"),\
new MotorDriver(25, 0, UNUSED_PIN, -14, 34, 1.27, 5000, 19), \
new MotorDriver(27, 15, UNUSED_PIN, -2, 35, 1.27, 5000, 23)
// EX-CSB1 with EX-8874 stacked on top for 4 outputs
#define EX_CSB1_STACK F("EX-CSB1 Stacked"),\
new MotorDriver(25, 0, UNUSED_PIN, -14, 34, 1.27, 5000, 19), \
new MotorDriver(27, 15, UNUSED_PIN, -2, 35, 1.27, 5000, 23), \
new MotorDriver(26, 5, UNUSED_PIN, 13, 36, 1.27, 5000, 18), \
new MotorDriver(16, 4, UNUSED_PIN, 12, 39, 1.27, 5000, 17)
// BOOSTER PIN INPUT ON ESP32
// On ESP32 you have the possibility to define a pin as booster input
// Arduino pin D2 is GPIO 26 on ESPDuino32, and GPIO 32 on EX-CSB1
#define BOOSTER_INPUT 32
#else
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \

View File

@@ -39,11 +39,11 @@ void StringFormatter::diag( const FSH* input...) {
void StringFormatter::lcd(byte row, const FSH* input...) {
va_list args;
// Copy to serial client for display 0 <@ display# line# "message">
send(&USB_SERIAL,F("<@ 0 %d \""),row);
// Issue the LCD as a diag first
send(&USB_SERIAL,F("<* LCD%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,12 +53,6 @@ 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);
@@ -123,7 +117,6 @@ 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);
@@ -225,15 +218,4 @@ 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,7 +49,6 @@ 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

@@ -1,7 +1,6 @@
/*
* © 2022 Chris Harlow
* © 2022 Harald Barth
* © 2023 Colin Murdoch
* All rights reserved.
*
* This file is part of DCC++EX
@@ -26,27 +25,26 @@
#include "MotorDriver.h"
#include "DCCTimer.h"
#include "DIAG.h"
#include "CommandDistributor.h"
#include "DCCEXParser.h"
#include"CommandDistributor.h"
// Virtualised Motor shield multi-track hardware Interface
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
#define APPLY_BY_MODE(findmode,function) \
FOR_EACH_TRACK(t) \
if (track[t]->getMode()==findmode) \
if (trackMode[t]==findmode) \
track[t]->function;
#ifndef DISABLE_PROG
const int16_t HASH_KEYWORD_PROG = -29718;
#endif
const int16_t HASH_KEYWORD_MAIN = 11339;
const int16_t HASH_KEYWORD_OFF = 22479;
const int16_t HASH_KEYWORD_NONE = -26550;
const int16_t HASH_KEYWORD_DC = 2183;
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
MotorDriver * TrackManager::track[MAX_TRACKS];
TRACK_MODE TrackManager::trackMode[MAX_TRACKS];
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
POWERMODE TrackManager::mainPowerGuess=POWERMODE::OFF;
@@ -55,7 +53,7 @@ bool TrackManager::progTrackSyncMain=false;
bool TrackManager::progTrackBoosted=false;
int16_t TrackManager::joinRelay=UNUSED_PIN;
#ifdef ARDUINO_ARCH_ESP32
byte TrackManager::tempProgTrack=MAX_TRACKS+1; // MAX_TRACKS+1 is the unused flag
byte TrackManager::tempProgTrack=MAX_TRACKS+1;
#endif
#ifdef ANALOG_READ_INTERRUPT
@@ -76,7 +74,7 @@ void TrackManager::sampleCurrent() {
waiting = false;
tr++;
if (tr > lastTrack) tr = 0;
if (lastTrack < 2 || track[tr]->getMode() & TRACK_MODE_PROG) {
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) {
return; // We could continue but for prog track we
// rather do it in next interrupt beacuse
// that gives us well defined sampling point.
@@ -87,7 +85,7 @@ void TrackManager::sampleCurrent() {
if (!waiting) {
// look for a valid track to sample or until we are around
while (true) {
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
track[tr]->startCurrentFromHW();
// for scope debug track[1]->setBrake(1);
waiting = true;
@@ -140,10 +138,10 @@ void TrackManager::Setup(const FSH * shieldname,
}
void TrackManager::addTrack(byte t, MotorDriver* driver) {
trackMode[t]=TRACK_MODE_OFF;
track[t]=driver;
if (driver) {
track[t]->setPower(POWERMODE::OFF);
track[t]->setMode(TRACK_MODE_NONE);
track[t]->setTrackLetter('A'+t);
lastTrack=t;
}
@@ -155,16 +153,10 @@ 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) {
@@ -179,16 +171,10 @@ 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
@@ -196,27 +182,22 @@ void TrackManager::setPROGSignal( bool on) {
// with interrupts turned off around the critical section
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
FOR_EACH_TRACK(t) {
if (trackDCAddr[t]!=cab && cab != 0) continue;
if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
if (trackDCAddr[t]!=cab) continue;
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
}
}
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
//DIAG(F("Track=%c"),trackToSet+'A');
// DC tracks require a motorDriver that can set brake!
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
#if defined(ARDUINO_AVR_UNO)
DIAG(F("Uno has no PWM timers available for DC"));
return false;
#endif
if (!track[trackToSet]->brakeCanPWM()) {
DIAG(F("Brake pin can't PWM: No DC"));
return false;
}
}
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
&& !track[trackToSet]->brakeCanPWM()) {
DIAG(F("Brake pin can't PWM: No DC"));
return false;
}
#ifdef ARDUINO_ARCH_ESP32
// remove pin from MUX matrix and turn it off
@@ -237,9 +218,9 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
#endif
// only allow 1 track to be prog
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) {
track[t]->setPower(POWERMODE::OFF);
track[t]->setMode(TRACK_MODE_NONE);
trackMode[t]=TRACK_MODE_OFF;
track[t]->makeProgTrack(false); // revoke prog track special handling
streamTrackState(NULL,t);
}
@@ -247,7 +228,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
} else {
track[trackToSet]->makeProgTrack(false); // only the prog track knows it's type
}
track[trackToSet]->setMode(mode);
trackMode[trackToSet]=mode;
trackDCAddr[trackToSet]=dcAddr;
streamTrackState(NULL,trackToSet);
@@ -274,7 +255,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
// DC tracks must not have the DCC PWM switched on
// so we globally turn it off if one of the PWM
// capable tracks is now DC or DCX.
if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) {
if (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) {
if (track[t]->isPWMCapable()) {
canDo=false; // this track is capable but can not run PWM
break; // in this mode, so abort and prevent globally below
@@ -282,7 +263,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
track[t]->trackPWM=false; // this track sure can not run with PWM
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
}
} else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) {
} else if (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) {
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
canDo &= track[t]->trackPWM;
@@ -320,7 +301,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
void TrackManager::applyDCSpeed(byte t) {
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
if (track[t]->getMode()==TRACK_MODE_DCX)
if (trackMode[t]==TRACK_MODE_DCX)
speedByte = speedByte ^ 128; // reverse direction bit
track[t]->setDCSignal(speedByte);
}
@@ -332,7 +313,6 @@ 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....
@@ -348,8 +328,8 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
return setTrackMode(p[0],TRACK_MODE_PROG);
#endif
if (params==2 && (p[1]==HASH_KEYWORD_OFF || p[1]==HASH_KEYWORD_NONE)) // <= id OFF> <= id NONE>
return setTrackMode(p[0],TRACK_MODE_NONE);
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF>
return setTrackMode(p[0],TRACK_MODE_OFF);
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
return setTrackMode(p[0],TRACK_MODE_EXT);
@@ -367,36 +347,32 @@ 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()) {
switch(trackMode[t]) {
case TRACK_MODE_MAIN:
if (pstate) {format=F("<= %c MAIN ON>\n");} else {format = F("<= %c MAIN OFF>\n");}
format=F("<= %c MAIN>\n");
break;
#ifndef DISABLE_PROG
case TRACK_MODE_PROG:
if (pstate) {format=F("<= %c PROG ON>\n");} else {format=F("<= %c PROG OFF>\n");}
format=F("<= %c PROG>\n");
break;
#endif
case TRACK_MODE_NONE:
if (pstate) {format=F("<= %c NONE ON>\n");} else {format=F("<= %c NONE OFF>\n");}
case TRACK_MODE_OFF:
format=F("<= %c OFF>\n");
break;
case TRACK_MODE_EXT:
if (pstate) {format=F("<= %c EXT ON>\n");} else {format=F("<= %c EXT OFF>\n");}
format=F("<= %c EXT>\n");
break;
case TRACK_MODE_DC:
if (pstate) {format=F("<= %c DC %d ON>\n");} else {format=F("<= %c DC %d OFF>\n");}
format=F("<= %c DC %d>\n");
break;
case TRACK_MODE_DCX:
if (pstate) {format=F("<= %c DCX %d ON>\n");} else {format=F("<= %c DCX %d OFF>\n");}
format=F("<= %c DCX %d>\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;
@@ -411,13 +387,13 @@ void TrackManager::loop() {
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
if (track[nextCycleTrack]==NULL) return;
MotorDriver * motorDriver=track[nextCycleTrack];
bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG;
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
}
MotorDriver * TrackManager::getProgDriver() {
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
if (trackMode[t]==TRACK_MODE_PROG) return track[t];
return NULL;
}
@@ -425,75 +401,54 @@ MotorDriver * TrackManager::getProgDriver() {
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
std::vector<MotorDriver *> v;
FOR_EACH_TRACK(t)
if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]);
return v;
}
#endif
void TrackManager::setPower2(bool setProg,bool setJoin, POWERMODE mode) {
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
if (!setProg) mainPowerGuess=mode;
FOR_EACH_TRACK(t) {
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;
MotorDriver * driver=track[t];
if (!driver) continue;
switch (trackMode[t]) {
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_OFF:
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();
if (trackMode[t]==TRACK_MODE_PROG)
return track[t]->getPower();
return POWERMODE::OFF;
}
@@ -537,7 +492,7 @@ void TrackManager::setJoin(bool joined) {
#ifdef ARDUINO_ARCH_ESP32
if (joined) {
FOR_EACH_TRACK(t) {
if (track[t]->getMode()==TRACK_MODE_PROG) {
if (trackMode[t]==TRACK_MODE_PROG) {
tempProgTrack = t;
setTrackMode(t, TRACK_MODE_MAIN);
break;
@@ -545,12 +500,7 @@ void TrackManager::setJoin(bool joined) {
}
} else {
if (tempProgTrack != MAX_TRACKS+1) {
// as setTrackMode with TRACK_MODE_PROG defaults to
// power off, we will take the current power state
// of our track and then preserve that state.
POWERMODE tPTmode = track[tempProgTrack]->getPower(); //get current power status of this track
setTrackMode(tempProgTrack, TRACK_MODE_PROG);
track[tempProgTrack]->setPower(tPTmode); //set track status as it was before
tempProgTrack = MAX_TRACKS+1;
}
}
@@ -558,39 +508,3 @@ void TrackManager::setJoin(bool joined) {
progTrackSyncMain=joined;
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
}
bool TrackManager::isPowerOn(byte t) {
if (track[t]->getPower()!=POWERMODE::ON)
return false;
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

@@ -1,8 +1,6 @@
/*
* © 2022 Chris Harlow
* © 2022 Harald Barth
* © 2023 Colin Murdoch
*
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -29,6 +27,10 @@
#include "MotorDriver.h"
// Virtualised Motor shield multi-track hardware Interface
// use powers of two so we can do logical and/or on the track modes in if clauses.
enum TRACK_MODE : byte {TRACK_MODE_OFF = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
// These constants help EXRAIL macros say SET_TRACK(2,mode) OR SET_TRACK(C,mode) etc.
const byte TRACK_NUMBER_0=0, TRACK_NUMBER_A=0;
const byte TRACK_NUMBER_1=1, TRACK_NUMBER_B=1;
@@ -39,10 +41,6 @@ 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,
@@ -64,14 +62,10 @@ class TrackManager {
#ifdef ARDUINO_ARCH_ESP32
static std::vector<MotorDriver *>getMainDrivers();
#endif
static void setPower2(bool progTrack,bool joinTrack,POWERMODE mode);
static void setPower2(bool progTrack,POWERMODE mode);
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(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 void setMainPower(POWERMODE mode) {setPower2(false,mode);}
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
static const int16_t MAX_TRACKS=8;
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
@@ -85,14 +79,8 @@ 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
@@ -112,6 +100,7 @@ class TrackManager {
static POWERMODE mainPowerGuess;
static void applyDCSpeed(byte t);
static TRACK_MODE trackMode[MAX_TRACKS];
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
#ifdef ARDUINO_ARCH_ESP32
static byte tempProgTrack; // holds the prog track number during join

View File

@@ -1,268 +0,0 @@
/*
* © 2023 Peter Cole
* All rights reserved.
*
* 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"
#include <Arduino.h>
#include "Turntables.h"
#include "StringFormatter.h"
#include "CommandDistributor.h"
#include "EXRAIL2.h"
#include "DCC.h"
// No turntable support without HAL
#ifndef IO_NO_HAL
/*
* Protected static data
*/
Turntable *Turntable::_firstTurntable = 0;
/*
* Public static data
*/
int Turntable::turntablelistHash = 0;
/*
* Protected static functions
*/
// Add new turntable to end of list
void Turntable::add(Turntable *tto) {
if (!_firstTurntable) {
_firstTurntable = tto;
} else {
Turntable *ptr = _firstTurntable;
for ( ; ptr->_nextTurntable!=0; ptr=ptr->_nextTurntable) {}
ptr->_nextTurntable = tto;
}
turntablelistHash++;
}
// Add a position
void Turntable::addPosition(uint8_t idx, uint16_t value, uint16_t angle) {
_turntablePositions.insert(idx, value, angle);
}
// Get value for position
uint16_t Turntable::getPositionValue(uint8_t position) {
TurntablePosition* currentPosition = _turntablePositions.getHead();
while (currentPosition) {
if (currentPosition->index == position) {
return currentPosition->data;
}
currentPosition = currentPosition->next;
}
return false;
}
// Get value for position
uint16_t Turntable::getPositionAngle(uint8_t position) {
TurntablePosition* currentPosition = _turntablePositions.getHead();
while (currentPosition) {
if (currentPosition->index == position) {
return currentPosition->angle;
}
currentPosition = currentPosition->next;
}
return false;
}
// Get the count of positions associated with the turntable
uint8_t Turntable::getPositionCount() {
TurntablePosition* currentPosition = _turntablePositions.getHead();
uint8_t count = 0;
while (currentPosition) {
count++;
currentPosition = currentPosition->next;
}
return count;
}
/*
* Public static functions
*/
// Find turntable from list
Turntable *Turntable::get(uint16_t id) {
for (Turntable *tto = _firstTurntable; tto != nullptr; tto = tto->_nextTurntable)
if (tto->_turntableData.id == id) return tto;
return NULL;
}
// Find turntable via Vpin
Turntable *Turntable::getByVpin(VPIN vpin) {
for (Turntable *tto = _firstTurntable; tto != nullptr; tto = tto->_nextTurntable) {
if (tto->isEXTT()) {
EXTTTurntable *exttTto = static_cast<EXTTTurntable*>(tto);
if (exttTto->getVpin() == vpin) {
return tto;
}
}
}
return nullptr;
}
// Get the current position for turntable with the specified ID
uint8_t Turntable::getPosition(uint16_t id) {
Turntable *tto = get(id);
if (!tto) return false;
return tto->getPosition();
}
// Got the moving state of the specified turntable
bool Turntable::ttMoving(uint16_t id) {
Turntable *tto = get(id);
if (!tto) return false;
return tto->isMoving();
}
// Initiate a turntable move
bool Turntable::setPosition(uint16_t id, uint8_t position, uint8_t activity) {
#if defined(DIAG_IO)
DIAG(F("Rotate turntable %d to position %d, activity %d)"), id, position, activity);
#endif
Turntable *tto = Turntable::get(id);
if (!tto) return false;
if (tto->isMoving()) return false;
bool ok = tto->setPositionInternal(position, activity);
if (ok) {
// We only deal with broadcasts for DCC turntables here, EXTT in the device driver
if (!tto->isEXTT()) {
CommandDistributor::broadcastTurntable(id, position, false);
}
// Trigger EXRAIL rotateEvent for both types here if changed
#if defined(EXRAIL_ACTIVE)
bool rotated = false;
if (position != tto->_previousPosition) rotated = true;
RMFT2::rotateEvent(id, rotated);
#endif
}
return ok;
}
/*************************************************************************************
* EXTTTurntable - EX-Turntable device.
*
*************************************************************************************/
// Private constructor
EXTTTurntable::EXTTTurntable(uint16_t id, VPIN vpin) :
Turntable(id, TURNTABLE_EXTT)
{
_exttTurntableData.vpin = vpin;
}
using DevState = IODevice::DeviceStateEnum;
// Create function
Turntable *EXTTTurntable::create(uint16_t id, VPIN vpin) {
#ifndef IO_NO_HAL
Turntable *tto = get(id);
if (tto) {
if (tto->isType(TURNTABLE_EXTT)) {
EXTTTurntable *extt = (EXTTTurntable *)tto;
extt->_exttTurntableData.vpin = vpin;
return tto;
}
}
if (!IODevice::exists(vpin)) return nullptr;
if (IODevice::getStatus(vpin) == DevState::DEVSTATE_FAILED) return nullptr;
if (Turntable::getByVpin(vpin)) return nullptr;
tto = (Turntable *)new EXTTTurntable(id, vpin);
DIAG(F("Turntable 0x%x size %d size %d"), tto, sizeof(Turntable), sizeof(struct TurntableData));
return tto;
#else
(void)id;
(void)vpin;
return NULL;
#endif
}
void EXTTTurntable::print(Print *stream) {
StringFormatter::send(stream, F("<i %d EXTURNTABLE %d>\n"), _turntableData.id, _exttTurntableData.vpin);
}
// EX-Turntable specific code for moving to the specified position
bool EXTTTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
#ifndef IO_NO_HAL
int16_t value;
if (position == 0) {
value = 0; // Position 0 is just to send activities
} else {
if (activity > 1) return false; // If sending a position update, only phase changes valid (0|1)
value = getPositionValue(position); // Get position value from position list
}
if (position > 0 && !value) return false; // Return false if it's not a valid position
// Set position via device driver
_previousPosition = _turntableData.position;
_turntableData.position = position;
EXTurntable::writeAnalogue(_exttTurntableData.vpin, value, activity);
#else
(void)position;
#endif
return true;
}
/*************************************************************************************
* DCCTurntable - DCC Turntable device.
*
*************************************************************************************/
// Private constructor
DCCTurntable::DCCTurntable(uint16_t id) : Turntable(id, TURNTABLE_DCC) {}
// Create function
Turntable *DCCTurntable::create(uint16_t id) {
#ifndef IO_NO_HAL
Turntable *tto = get(id);
if (!tto) {
tto = (Turntable *)new DCCTurntable(id);
DIAG(F("Turntable 0x%x size %d size %d"), tto, sizeof(Turntable), sizeof(struct TurntableData));
}
return tto;
#else
(void)id;
return NULL;
#endif
}
void DCCTurntable::print(Print *stream) {
StringFormatter::send(stream, F("<i %d DCCTURNTABLE>\n"), _turntableData.id);
}
// EX-Turntable specific code for moving to the specified position
bool DCCTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
#ifndef IO_NO_HAL
int16_t value = getPositionValue(position);
if (position == 0 || !value) return false; // Return false if it's not a valid position
// Set position via device driver
int16_t addr=value>>3;
int16_t subaddr=(value>>1) & 0x03;
bool active=value & 0x01;
_previousPosition = _turntableData.position;
_turntableData.position = position;
DCC::setAccessory(addr, subaddr, active);
#else
(void)position;
#endif
return true;
}
#endif

View File

@@ -1,243 +0,0 @@
/*
* © 2023 Peter Cole
* All rights reserved.
*
* 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 TURNTABLES_H
#define TURNTABLES_H
#include <Arduino.h>
#include "IODevice.h"
#include "StringFormatter.h"
// No turntable support without HAL
#ifndef IO_NO_HAL
// Turntable type definitions
// EXTT = EX-Turntable
// DCC = DCC accessory turntables - to be added later
enum {
TURNTABLE_EXTT = 0,
TURNTABLE_DCC = 1,
};
/*************************************************************************************
* Turntable positions.
*
*************************************************************************************/
struct TurntablePosition {
uint8_t index;
uint16_t data;
uint16_t angle;
TurntablePosition* next;
TurntablePosition(uint8_t idx, uint16_t value, uint16_t angle) : index(idx), data(value), angle(angle), next(nullptr) {}
};
class TurntablePositionList {
public:
TurntablePositionList() : head(nullptr) {}
void insert(uint8_t idx, uint16_t value, uint16_t angle) {
TurntablePosition* newPosition = new TurntablePosition(idx, value, angle);
if(!head) {
head = newPosition;
} else {
newPosition->next = head;
head = newPosition;
}
}
TurntablePosition* getHead() {
return head;
}
private:
TurntablePosition* head;
};
/*************************************************************************************
* Turntable - Base class for turntables.
*
*************************************************************************************/
class Turntable {
protected:
/*
* Object data
*/
// Data common to all turntable types
struct TurntableData {
union {
struct {
bool hidden : 1;
bool turntableType : 1;
uint8_t position : 6; // Allows up to 63 positions including 0/home
};
uint8_t flags;
};
uint16_t id;
} _turntableData;
// Pointer to next turntable object
Turntable *_nextTurntable = 0;
// Linked list for positions
TurntablePositionList _turntablePositions;
// Store the previous position to allow checking for changes
uint8_t _previousPosition = 0;
// Store the current state of the turntable
bool _isMoving = false;
/*
* Constructor
*/
Turntable(uint16_t id, uint8_t turntableType) {
_turntableData.id = id;
_turntableData.turntableType = turntableType;
_turntableData.hidden = false;
_turntableData.position = 0;
add(this);
}
/*
* Static data
*/
static Turntable *_firstTurntable;
static int _turntablelistHash;
/*
* Virtual functions
*/
virtual bool setPositionInternal(uint8_t position, uint8_t activity) = 0;
/*
* Static functions
*/
static void add(Turntable *tto);
public:
static Turntable *get(uint16_t id);
static Turntable *getByVpin(VPIN vpin);
/*
* Static data
*/
static int turntablelistHash;
/*
* Public base class functions
*/
inline uint8_t getPosition() { return _turntableData.position; }
inline bool isHidden() { return _turntableData.hidden; }
inline void setHidden(bool h) {_turntableData.hidden=h; }
inline bool isType(uint8_t type) { return _turntableData.turntableType == type; }
inline bool isEXTT() const { return _turntableData.turntableType == TURNTABLE_EXTT; }
inline uint16_t getId() { return _turntableData.id; }
inline Turntable *next() { return _nextTurntable; }
void printState(Print *stream);
void addPosition(uint8_t idx, uint16_t value, uint16_t angle);
uint16_t getPositionValue(uint8_t position);
uint16_t getPositionAngle(uint8_t position);
uint8_t getPositionCount();
bool isMoving() { return _isMoving; }
void setMoving(bool moving) { _isMoving=moving; }
/*
* Virtual functions
*/
virtual void print(Print *stream) {
(void)stream; // suppress compiler warnings
}
virtual ~Turntable() {} // Destructor
/*
* Public static functions
*/
inline static bool exists(uint16_t id) { return get(id) != 0; }
static bool setPosition(uint16_t id, uint8_t position, uint8_t activity=0);
static uint8_t getPosition(uint16_t id);
static bool ttMoving(uint16_t id);
inline static Turntable *first() { return _firstTurntable; }
static bool printAll(Print *stream) {
bool gotOne = false;
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());
}
return gotOne;
}
};
/*************************************************************************************
* EXTTTurntable - EX-Turntable device.
*
*************************************************************************************/
class EXTTTurntable : public Turntable {
private:
// EXTTTurntableData contains device specific data
struct EXTTTurntableData {
VPIN vpin;
} _exttTurntableData;
// Constructor
EXTTTurntable(uint16_t id, VPIN vpin);
public:
// Create function
static Turntable *create(uint16_t id, VPIN vpin);
void print(Print *stream) override;
VPIN getVpin() const { return _exttTurntableData.vpin; }
protected:
// EX-Turntable specific code for setting position
bool setPositionInternal(uint8_t position, uint8_t activity) override;
};
/*************************************************************************************
* DCCTurntable - DCC accessory Turntable device.
*
*************************************************************************************/
class DCCTurntable : public Turntable {
private:
// Constructor
DCCTurntable(uint16_t id);
public:
// Create function
static Turntable *create(uint16_t id);
void print(Print *stream) override;
protected:
// DCC specific code for setting position
bool setPositionInternal(uint8_t position, uint8_t activity=0) override;
};
#endif
#endif

View File

@@ -1,7 +1,5 @@
/*
© 2023 Paul M. Antoine
© 2021 Harald Barth
© 2023 Nathan Kellenicki
© 2021, Harald Barth.
This file is part of CommandStation-EX
@@ -22,7 +20,6 @@
#if defined(ARDUINO_ARCH_ESP32)
#include <vector>
#include "defines.h"
#include "ESPmDNS.h"
#include <WiFi.h>
#include "esp_wifi.h"
#include "WifiESP32.h"
@@ -108,18 +105,11 @@ void wifiLoop(void *){
}
#endif
char asciitolower(char in) {
if (in <= 'Z' && in >= 'A')
return in - ('Z' - 'z');
return in;
}
bool WifiESP::setup(const char *SSid,
const char *password,
const char *hostname,
int port,
const byte channel,
const bool forceAP) {
const byte channel) {
bool havePassword = true;
bool haveSSID = true;
bool wifiUp = false;
@@ -147,8 +137,7 @@ bool WifiESP::setup(const char *SSid,
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
havePassword = false;
if (haveSSID && havePassword && !forceAP) {
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
if (haveSSID && havePassword) {
WiFi.mode(WIFI_STA);
#ifdef SERIAL_BT_COMMANDS
WiFi.setSleep(true);
@@ -185,20 +174,16 @@ bool WifiESP::setup(const char *SSid,
}
}
}
if (!haveSSID || forceAP) {
if (!haveSSID) {
// prepare all strings
String strSSID(forceAP ? SSid : "DCCEX_");
String strPass(forceAP ? password : "PASS_");
if (!forceAP) {
String strMac = WiFi.macAddress();
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);
strSSID.concat(strMac);
strPass.concat(strMac);
}
String strSSID("DCCEX_");
String strPass("PASS_");
String strMac = WiFi.macAddress();
strMac.remove(0,9);
strMac.replace(":","");
strMac.replace(":","");
strSSID.concat(strMac);
strPass.concat(strMac);
WiFi.mode(WIFI_AP);
#ifdef SERIAL_BT_COMMANDS
@@ -224,15 +209,6 @@ bool WifiESP::setup(const char *SSid,
// no idea to go on
return false;
}
// 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

View File

@@ -1,6 +1,5 @@
/*
* © 2021 Harald Barth
* © 2023 Nathan Kellenicki
* © 2021, Harald Barth.
*
* This file is part of CommandStation-EX
*
@@ -32,8 +31,7 @@ public:
const char *wifiPassword,
const char *hostname,
const int port,
const byte channel,
const bool forceAP);
const byte channel);
static void loop();
private:
};

View File

@@ -2,8 +2,6 @@
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2022 Chris Harlow
* © 2023 Nathan Kellenicki
* © 2023 Travis Farmer
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -58,18 +56,10 @@ 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
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE)
#if defined(ARDUINO_NUCLEO_F411RE)
#define NUM_SERIAL 3
#define SERIAL1 Serial1
#define SERIAL3 Serial6
@@ -77,11 +67,9 @@ Stream * WifiInterface::wifiStream;
#define NUM_SERIAL 3
#define SERIAL1 Serial3
#define SERIAL3 Serial5
#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_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
#define NUM_SERIAL 2
#define SERIAL1 Serial6
#else
#warning This variant of Nucleo not yet explicitly supported
#endif
#endif
@@ -95,8 +83,7 @@ bool WifiInterface::setup(long serial_link_speed,
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel,
const bool forceAP) {
const byte channel) {
wifiSerialState wifiUp = WIFI_NOAT;
@@ -108,13 +95,12 @@ bool WifiInterface::setup(long serial_link_speed,
(void) hostname;
(void) port;
(void) channel;
(void) forceAP;
#endif
// See if the WiFi is attached to the first serial port
#if NUM_SERIAL > 0 && !defined(SERIAL1_COMMANDS)
SERIAL1.begin(serial_link_speed);
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel);
#endif
// Other serials are tried, depending on hardware.
@@ -124,7 +110,7 @@ bool WifiInterface::setup(long serial_link_speed,
if (wifiUp == WIFI_NOAT)
{
Serial2.begin(serial_link_speed);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
#endif
@@ -135,7 +121,7 @@ bool WifiInterface::setup(long serial_link_speed,
if (wifiUp == WIFI_NOAT)
{
SERIAL3.begin(serial_link_speed);
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel);
}
#endif
@@ -153,7 +139,7 @@ bool WifiInterface::setup(long serial_link_speed,
}
wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel, bool forceAP) {
const FSH* hostname, int port, byte channel) {
wifiSerialState wifiState;
static uint8_t ntry = 0;
ntry++;
@@ -162,7 +148,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
wifiState = setup2( SSid, password, hostname, port, channel, forceAP);
wifiState = setup2( SSid, password, hostname, port, channel);
if (wifiState == WIFI_NOAT) {
LCD(4, F("WiFi no AT chip"));
@@ -186,7 +172,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
#pragma GCC diagnostic ignored "-Wunused-parameter"
#endif
wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
const FSH* hostname, int port, byte channel, bool forceAP) {
const FSH* hostname, int port, byte channel) {
bool ipOK = false;
bool oldCmd = false;
@@ -209,22 +195,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
// Display the AT version information
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
if (checkForOK(2000, F("AT version:"), true, false)) {
char version[] = "0.0.0.0";
for (int i=0; i<8;i++) {
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')) {
DIAG(F("You need to up/downgrade the ESP firmware"));
SSid = F("UPDATE_ESP_FIRMWARE");
forceAP = true;
}
}
checkForOK(2000, true, false);
checkForOK(2000, true, false); // Makes this visible on the console
#ifdef DONT_TOUCH_WIFI_CONF
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
@@ -254,7 +225,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
if (!checkForOK(1000, F("0.0.0.0"), true,false))
ipOK = true;
}
} else if (!forceAP) {
} else {
// SSID was configured, so we assume station (client) mode.
if (oldCmd) {
// AT command early version supports CWJAP/CWSAP
@@ -314,19 +285,14 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
i=0;
do {
if (!forceAP) {
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
// unconfigured
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
oldCmd ? "" : "_CUR", macTail, macTail, channel);
} else {
// password configured by user
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
macTail, password, channel);
}
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
// unconfigured
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
oldCmd ? "" : "_CUR", macTail, macTail, channel);
} else {
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"),
oldCmd ? "" : "_CUR", SSid, password, channel);
// password configured by user
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
macTail, password, channel);
}
} while (!checkForOK(WIFI_CONNECT_TIMEOUT, true) && i++<2); // do twice if necessary but ignore failure as AP mode may still be ok
if (i >= 2)

View File

@@ -1,7 +1,6 @@
/*
* © 2020-2021 Chris Harlow
* © 2020, Harald Barth.
* © 2023 Nathan Kellenicki
* All rights reserved.
*
* This file is part of CommandStation-EX
@@ -37,18 +36,17 @@ public:
const FSH *wifiPassword,
const FSH *hostname,
const int port,
const byte channel,
const bool forceAP);
const byte channel);
static void loop();
static void ATCommand(HardwareSerial * stream,const byte *command);
private:
static wifiSerialState setup(Stream &setupStream, const FSH *SSSid, const FSH *password,
const FSH *hostname, int port, byte channel, bool forceAP);
const FSH *hostname, int port, byte channel);
static Stream *wifiStream;
static DCCEXParser parser;
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
const FSH *hostname, int port, byte channel, bool forceAP);
const FSH *hostname, int port, byte channel);
static bool checkForOK(const unsigned int timeout, bool echo, bool escapeEcho = true);
static bool checkForOK(const unsigned int timeout, const FSH *waitfor, bool echo, bool escapeEcho = true);
static bool connected;

View File

@@ -1,284 +0,0 @@
/*
© 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

View File

@@ -1,46 +0,0 @@
/*
* © 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

@@ -4,7 +4,6 @@
* © 2020-2023 Harald Barth
* © 2020-2021 Fred Decker
* © 2020-2021 Chris Harlow
* © 2023 Nathan Kellenicki
*
* This file is part of CommandStation-EX
*
@@ -58,21 +57,6 @@ The configuration file for DCC-EX Command Station
// +-----------------------v
//
#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD
//
/////////////////////////////////////////////////////////////////////////////////////
//
// If you want to restrict the maximum current LOWER than what your
// motor shield can provide, you can do that here. For example if you
// have a motor shield that can provide 5A and your power supply can
// only provide 2.5A then you should restict the maximum current to
// 2.25A (90% of 2.5A) so that DCC-EX does shut off the track before
// your PS does shut DCC-EX. MAX_CURRENT is in mA so for this example
// it would be 2250, adjust the number according to your PS. If your
// PS has a higher rating than your motor shield you do not need this.
// You can use this as well if you are cautious and your trains do not
// need full current.
// #define MAX_CURRENT 2250
//
/////////////////////////////////////////////////////////////////////////////////////
//
// The IP port to talk to a WIFI or Ethernet shield.
@@ -124,11 +108,6 @@ The configuration file for DCC-EX Command Station
// this line exists or not. If you need to use an alternate channel (we recommend
// using only 1,6, or 11) you may change it here.
#define WIFI_CHANNEL 1
//
// WIFI_FORCE_AP: If you'd like to specify your own WIFI_SSID in AP mode, set this
// true. Otherwise it is assumed that you'd like to connect to an existing network
// with that SSID.
#define WIFI_FORCE_AP false
/////////////////////////////////////////////////////////////////////////////////////
//

View File

@@ -5,7 +5,6 @@
* © 2021 Fred Decker
* © 2020-2022 Harald Barth
* © 2020-2021 Chris Harlow
* © 2023 Travis Farmer
*
* This file is part of CommandStation-EX
*
@@ -145,23 +144,10 @@
#define DISABLE_EEPROM
#endif
// STM32 support for native I2C is awaiting development
// #ifndef I2C_USE_WIRE
// #define I2C_USE_WIRE
// #endif
#elif defined(ARDUINO_GIGA)
#define ARDUINO_TYPE "Giga"
#ifndef GIGA_EXT_EEPROM
#define DISABLE_EEPROM
#ifndef I2C_USE_WIRE
#define I2C_USE_WIRE
#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"
@@ -196,15 +182,6 @@
#define WIFI_ON false
#endif
#ifndef WIFI_FORCE_AP
#define WIFI_FORCE_AP false
#else
#if WIFI_FORCE_AP==true || WIFI_FORCE_AP==false
#else
#error WIFI_FORCE_AP needs to be true or false
#endif
#endif
#if ENABLE_ETHERNET
#if defined(HAS_ENOUGH_MEMORY)
#define ETHERNET_ON true
@@ -227,21 +204,8 @@
//
#define WIFI_SERIAL_LINK_SPEED 115200
////////////////////////////////////////////////////////////////////////////////
//
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
//
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
#if defined(DISABLE_DIAG) && defined(DISABLE_EEPROM) && defined(DISABLE_PROG)
#warning you have sacrificed DIAG for HAL
#else
#define IO_NO_HAL
#endif
#endif
#if __has_include ( "myAutomation.h")
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM)
#define EXRAIL_ACTIVE
#else
#define EXRAIL_WARNING

View File

@@ -1,7 +1,7 @@
#!/bin/bash
#
# © 2022,2023 Harald Barth
# © 2022 Harald Barth
#
# This file is part of CommandStation-EX
#
@@ -29,33 +29,14 @@ ACLI="./bin/arduino-cli"
function need () {
type -p $1 > /dev/null && return
dpkg -l $1 2>&1 | egrep ^ii >/dev/null && return
sudo apt-get install $1
type -p $1 > /dev/null && return
echo "Could not install $1, abort"
exit 255
}
need git
if cat /etc/issue | egrep '^Raspbian' 2>&1 >/dev/null ; then
# we are on a raspi where we do not support graphical
unset DISPLAY
fi
if [ x$DISPLAY != x ] ; then
# we have DISPLAY, do the graphic thing
need python3-tk
need python3.8-venv
mkdir -p ~/ex-installer/venv
python3 -m venv ~/ex-installer/venv
cd ~/ex-installer/venv || exit 255
source ./bin/activate
git clone https://github.com/DCC-EX/EX-Installer
cd EX-Installer || exit 255
pip3 install -r requirements.txt
exec python3 -m ex_installer
fi
if test -d `basename "$DCCEXGITURL"` ; then
: assume we are almost there
cd `basename "$DCCEXGITURL"` || exit 255

View File

@@ -24,7 +24,6 @@
//#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.
@@ -52,7 +51,7 @@ void halSetup() {
// Create a 20x4 LCD display device as display number 2
// (line 0 is written by EX-RAIL 'SCREEN(2, 0, "text")').
// HALDisplay<LiquidCrystal>::create(2, 0x27, 20, 4);
// HALDisplay<LiquidCrystal>(2, 0x27, 20, 4);
//=======================================================================

View File

@@ -30,7 +30,8 @@ include_dir = .
[env]
build_flags = -Wall -Wextra
; monitor_filters = time
monitor_filters = time
; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0
[env:samd21-dev-usb]
platform = atmelsam
@@ -59,7 +60,7 @@ framework = arduino
lib_deps = ${env.lib_deps}
monitor_speed = 115200
monitor_echo = yes
build_flags = -std=c++17
build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
[env:mega2560-debug]
platform = atmelavr
@@ -71,7 +72,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
@@ -83,7 +84,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
@@ -107,7 +108,7 @@ lib_deps =
SPI
monitor_speed = 115200
monitor_echo = yes
build_flags =
build_flags = ; -DDIAG_LOOPTIMES
[env:mega328]
platform = atmelavr
@@ -189,75 +190,10 @@ platform = ststm32
board = nucleo_f446re
framework = arduino
lib_deps = ${env.lib_deps}
build_flags = -std=c++17 -Os -g2 -Wunused-variable
build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO
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
@@ -296,4 +232,5 @@ board = teensy41
framework = arduino
build_flags = -std=c++17 -Os -g2
lib_deps = ${env.lib_deps}
lib_ignore =
lib_ignore =

View File

@@ -3,54 +3,8 @@
#include "StringFormatter.h"
#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
// - <I ...>, <JO ...>, <JP ...> - turntable commands
// - DCC_TURNTABLE, EXTT_TURNTABLE, IFTTPOSITION, ONROTATE, ROTATE, ROTATE_DCC, TT_ADDPOSITION, WAITFORTT EXRAIL
// 5.1.4 - Added ONOVERLOAD & AFTEROVERLOAD to EXRAIL
// 5.1.3 - Make parser more fool proof
// 5.1.2 - Bugfix: ESP32 30ms off time
// 5.1.1 - Check bad AT firmware version
// - Update IO_PCA9555.h reflecting IO_MCP23017.h changes to support PCA9548 mux
// 5.0.1 - Bugfix: execute 30ms off time before rejoin
// 5.0.0 - Make 4.2.69 the 5.0.0 release
// 4.2.69 - Bugfix: Make <!> work in DC mode
// 4.2.68 - Rename track mode OFF to NONE
// 4.2.67 - AVR: Pin specific timer register seting
// - Protect Uno user from choosing DC(X)
// - More Nucleo variant defines
// - GPIO PCA9555 / TCA9555 support
// 4.2.66 - Throttle inrush current by applying PWM to brake pin when
// fault pin goes active
// 4.2.65 - new config WIFI_FORCE_AP option
// 4.2.63 - completely new overcurrent detection
// - ESP32 protect from race in RMT code
// 4.2.62 - Update IO_RotaryEncoder.h to ignore sending current position
// - Update IO_EXTurntable.h to remove forced I2C clock speed
// - Show device offline if EX-Turntable not connected
// 4.2.61 - MAX_CURRENT restriction (caps motor shield value)
// 4.2.60 - Add mDNS capability to ESP32 for autodiscovery
#define VERSION "4.2.59"
// 4.2.59 - Fix: AP SSID was DCC_ instead of DCCEX_
// 4.2.58 - Start motordriver as soon as possible but without waveform
// 4.2.57 - New overload handling (faster and handles commonFaultPin again)