mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 18:33:44 +02:00
Compare commits
315 Commits
v4.2.36-De
...
v5.1.9-Dev
Author | SHA1 | Date | |
---|---|---|---|
|
11b9fd4ef5 | ||
|
d07718be8c | ||
|
d57b5ba537 | ||
|
dab02ec659 | ||
|
6ad5326f1d | ||
|
39e1363ce0 | ||
|
c9d4f5e94d | ||
|
dfe3e9d42c | ||
|
5d810a620b | ||
|
550ad58c4d | ||
|
7a305e179c | ||
|
8437b0e7aa | ||
|
ebbeea5fbb | ||
|
a8321fff42 | ||
|
a16790f585 | ||
|
da6a3c442f | ||
|
4fcd81a118 | ||
|
eb450dbd89 | ||
|
a0562fdf5c | ||
|
7ee2c29a52 | ||
|
dba5d35aa2 | ||
|
be10be5a1a | ||
|
dca023ffd7 | ||
|
4eef9581fe | ||
|
bd02d1c15b | ||
|
004d7b6631 | ||
|
21ce87eb3e | ||
|
ab393047c1 | ||
|
1f5f7754c1 | ||
|
6adff43f4b | ||
|
1ac104704e | ||
|
2f8e915b1e | ||
|
152f9850bb | ||
|
3094c52bf8 | ||
|
86f4567556 | ||
|
dd890e65bf | ||
|
1e48c59cd8 | ||
|
004d10ee58 | ||
|
e734661d1b | ||
|
bcb250bacf | ||
|
798241927f | ||
|
df2f09f4d2 | ||
|
f40d57d8bd | ||
|
9fa213e198 | ||
|
44d8154223 | ||
|
01919b33df | ||
|
a0c1ad182c | ||
|
dbf053858b | ||
|
232ac993ec | ||
|
26ddd27ecf | ||
|
6cad794411 | ||
|
b0d8510127 | ||
|
3bfdd16288 | ||
|
df4a501e8a | ||
|
2202cb0c5e | ||
|
1425da20b5 | ||
|
b823a647ac | ||
|
2c64f10da8 | ||
|
25426d076d | ||
|
3453da0671 | ||
|
fb226311e5 | ||
|
6392c74ead | ||
|
25f8852af6 | ||
|
9842ea8a42 | ||
|
fa0aa27d46 | ||
|
57d4655d54 | ||
|
ff9c558b61 | ||
|
b277d204f0 | ||
|
c4febd1d0f | ||
|
98f8022268 | ||
|
1491da4813 | ||
|
4b2c0702a4 | ||
|
e27cceeb74 | ||
|
247763ac00 | ||
|
e327e0ae8d | ||
|
9f38dae8ba | ||
|
e0e965f81c | ||
|
f2be3aeac3 | ||
|
a74d85e895 | ||
|
df2e651217 | ||
|
36d139268d | ||
|
e3ac3a8ddf | ||
|
415e756020 | ||
|
f754fe2fbf | ||
|
399030d8ae | ||
|
4c7e11ddc1 | ||
|
495bbf66bf | ||
|
2950ef010a | ||
|
c2eb5f23b4 | ||
|
94648ead28 | ||
|
ec0499e9da | ||
|
9b75026eef | ||
|
6036ff9b15 | ||
|
6476a7aac2 | ||
|
0edf34bfe2 | ||
|
aa1f25fc72 | ||
|
b44bebc1c6 | ||
|
1a17cdb62f | ||
|
7ce1618a9c | ||
|
4192c1f5a3 | ||
|
c2fcdddd1f | ||
|
f19db3aa5c | ||
|
e6a40e622c | ||
|
b3251e89d7 | ||
|
ae2bbbf668 | ||
|
96a46f36c2 | ||
|
10c59028e1 | ||
|
ab1356d070 | ||
|
70d4c016ef | ||
|
efe96d1d84 | ||
|
5d17f247de | ||
|
7c41ec7c25 | ||
|
9c5e48c3d5 | ||
|
2eb0f48994 | ||
|
1bdb05a471 | ||
|
c2fa76c76a | ||
|
35fd912c60 | ||
|
dfba6c6fc1 | ||
|
f3cb263aaa | ||
|
ec6e730559 | ||
|
196a27a27a | ||
|
99b6ca025a | ||
|
db555e8820 | ||
|
0d679ad993 | ||
|
dd80260781 | ||
|
08f41415dc | ||
|
2f65d4347e | ||
|
995c6f8ede | ||
|
4331ddfdf0 | ||
|
2b8b995307 | ||
|
2af01e3c42 | ||
|
73b45ba9b8 | ||
|
247cea6dc1 | ||
|
c932325120 | ||
|
988011475c | ||
|
0be9af2270 | ||
|
c3eb6b8d8a | ||
|
08c00d275d | ||
|
1888073dc2 | ||
|
6dd175f63b | ||
|
ae54a747bb | ||
|
955ff4f96d | ||
|
0cf81d589e | ||
|
d5dad767a4 | ||
|
56fcb4e5f7 | ||
|
7783837545 | ||
|
0266936875 | ||
|
befb41ce98 | ||
|
f83be05220 | ||
|
cade89ba16 | ||
|
277825c530 | ||
|
95fe7aafe0 | ||
|
f99deb4276 | ||
|
f5d4dcb97c | ||
|
8a69403dda | ||
|
e81d1cc93a | ||
|
82929245ed | ||
|
db0e0cbf8b | ||
|
803b996e0b | ||
|
5607ff7167 | ||
|
58e62aaa81 | ||
|
5a9adea2b6 | ||
|
bf136d49e0 | ||
|
50313ebbd2 | ||
|
72bfc6abc7 | ||
|
342d9263d1 | ||
|
20d66fad4e | ||
|
be4235e792 | ||
|
c22d789513 | ||
|
951a6637f0 | ||
|
fdbcbdf418 | ||
|
9478c3263d | ||
|
16f94ecbdc | ||
|
b80d7bd517 | ||
|
8786285624 | ||
|
132b0773ef | ||
|
99521f8a3f | ||
|
1a3d295564 | ||
|
3b6789ef01 | ||
|
c472f48d93 | ||
|
94e9c2021b | ||
|
9aad2e3206 | ||
|
ecc366cbd1 | ||
|
f4e3ca7c81 | ||
|
991bda63e0 | ||
|
5164bd143c | ||
|
3759fc2a1a | ||
|
df7b890758 | ||
|
6d802f3a73 | ||
|
9d953c70b8 | ||
|
6781e44fdd | ||
|
a3c9800aba | ||
|
efdbfcb030 | ||
|
28d9843133 | ||
|
fcf05206b4 | ||
|
cc3aba1feb | ||
|
4eaad2d05b | ||
|
72d131035e | ||
|
2d1e695ac7 | ||
|
e780c40b34 | ||
|
7addb13785 | ||
|
b6f8889e8c | ||
|
33306219c8 | ||
|
d857c4f2e4 | ||
|
70fae16ab3 | ||
|
f465020e93 | ||
|
235bcc9ff0 | ||
|
d2cc60812d | ||
|
75f274e3b7 | ||
|
ff53b90034 | ||
|
1daa0a9ba9 | ||
|
294b9693c5 | ||
|
16e44eb11a | ||
|
e51f8e9c0a | ||
|
4bad334875 | ||
|
32491854ff | ||
|
3e95372816 | ||
|
05b0fc3d2e | ||
|
bb7cdc5422 | ||
|
6199cecd42 | ||
|
1aae0aed0a | ||
|
1d29be9de6 | ||
|
bfa33a9df7 | ||
|
49c0a1a55a | ||
|
9b7d1ae858 | ||
|
c11d8f6359 | ||
|
3868bb19ac | ||
|
7589917638 | ||
|
e7d9626a72 | ||
|
fe035f4096 | ||
|
4d236446b0 | ||
|
a100d709ce | ||
|
0d82370380 | ||
|
ff6034dff2 | ||
|
5d0de6b807 | ||
|
2e518fcac2 | ||
|
be273454bc | ||
|
751b46b6bb | ||
|
91bc9df44e | ||
|
72ceb63913 | ||
|
d2c7e7fb8d | ||
|
61db37c7ea | ||
|
3a3071f35b | ||
|
ef3d36ae25 | ||
|
c3d2e5b222 | ||
|
9f212c27bf | ||
|
de06c0ae3e | ||
|
273f55b143 | ||
|
86cb8f4666 | ||
|
9571088e1b | ||
|
18a992bf08 | ||
|
de6e91a778 | ||
|
b18df1405c | ||
|
305e0902f4 | ||
|
95c1b1da31 | ||
|
1b4faa92cd | ||
|
6fbaca7930 | ||
|
6b535654f8 | ||
|
4f43a413b5 | ||
|
2c943d250e | ||
|
89664eff9d | ||
|
4f56837d28 | ||
f8b054cf6a | |||
|
d2a8aebd0f | ||
|
86c3020672 | ||
|
60ea7f081a | ||
|
f348857ddb | ||
|
cc2846d932 | ||
|
83325ebf78 | ||
|
91d36ae909 | ||
|
98af5c45ed | ||
|
d3eceb6d6c | ||
|
79eaaa85fa | ||
|
0f5b8adb6b | ||
|
da8faa808b | ||
|
31ecba08d8 | ||
|
7311f2ce64 | ||
|
7e4f9eb0e1 | ||
|
1f5eafbcca | ||
|
7e16ec7088 | ||
|
912646f8ff | ||
|
dd309a3705 | ||
|
5376c9f410 | ||
|
b1d110ecbf | ||
|
5b7801ca6c | ||
|
aca9c9c941 | ||
|
6f94cd71ab | ||
|
1827a11f83 | ||
|
0023ce3356 | ||
|
7b9f3ae08d | ||
|
5e50731a78 | ||
|
df6c511d1d | ||
|
4bfd4b1a12 | ||
|
4a3f3d0f34 | ||
|
f0d1909d9f | ||
|
daf6799ac1 | ||
|
b7a010f904 | ||
|
d1518b8af0 | ||
|
39a85903ce | ||
|
d72474cd8f | ||
|
941e74beaf | ||
|
e618b91900 | ||
|
dcab5a0e72 | ||
|
1901d9547e | ||
|
7388d14bab | ||
|
2da28ad2db | ||
|
06bd80438e | ||
|
cd15eed005 | ||
|
23d0158804 | ||
|
2e9e614ad5 | ||
|
64b1de08be | ||
|
34c3d10767 | ||
|
f2eb64fd21 | ||
|
a80b16acba | ||
|
b1f5e9f48c |
@@ -2,6 +2,7 @@
|
||||
* © 2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* © 2022 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -160,6 +161,10 @@ 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
|
||||
@@ -167,7 +172,7 @@ void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
||||
// be safe for both types.
|
||||
broadcastReply(COMMAND_TYPE, F("<jC %d %d>\n"),time, rate);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PFT%d<;>%d\n"), time*60, rate);
|
||||
broadcastReply(WITHROTTLE_TYPE, F("PFT%l<;>%d\n"), (int32_t)time*60, rate);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -204,6 +209,39 @@ int16_t CommandDistributor::retClockTime() {
|
||||
void CommandDistributor::broadcastLoco(byte slot) {
|
||||
DCC::LOCO * sp=&DCC::speedTable[slot];
|
||||
broadcastReply(COMMAND_TYPE, F("<l %d %d %d %l>\n"), sp->loco,slot,sp->speedCode,sp->functions);
|
||||
#ifdef SABERTOOTH
|
||||
if (Serial2 && sp->loco == SABERTOOTH) {
|
||||
static uint8_t rampingmode = 0;
|
||||
bool direction = (sp->speedCode & 0x80) !=0; // true for forward
|
||||
int32_t speed = sp->speedCode & 0x7f;
|
||||
if (speed == 1) { // emergency stop
|
||||
if (rampingmode != 1) {
|
||||
rampingmode = 1;
|
||||
Serial2.print("R1: 0\r\n");
|
||||
Serial2.print("R2: 0\r\n");
|
||||
}
|
||||
Serial2.print("MD: 0\r\n");
|
||||
} else {
|
||||
if (speed != 0) {
|
||||
// speed is here 2 to 127
|
||||
speed = (speed - 1) * 1625 / 100;
|
||||
speed = speed * (direction ? 1 : -1);
|
||||
// speed is here -2047 to 2047
|
||||
}
|
||||
if (rampingmode != 2) {
|
||||
rampingmode = 2;
|
||||
Serial2.print("R1: 2047\r\n");
|
||||
Serial2.print("R2: 2047\r\n");
|
||||
}
|
||||
Serial2.print("M1: ");
|
||||
Serial2.print(speed);
|
||||
Serial2.print("\r\n");
|
||||
Serial2.print("M2: ");
|
||||
Serial2.print(speed);
|
||||
Serial2.print("\r\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef CD_HANDLE_RING
|
||||
WiThrottle::markForBroadcast(sp->loco);
|
||||
#endif
|
||||
@@ -227,11 +265,8 @@ void CommandDistributor::broadcastPower() {
|
||||
LCD(2,F("Power %S%S"),state=='1'?F("On"):F("Off"),reason);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastText(const FSH * msg) {
|
||||
broadcastReply(COMMAND_TYPE, F("<I %S>\n"),msg);
|
||||
#ifdef CD_HANDLE_RING
|
||||
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
|
||||
#endif
|
||||
void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||
broadcastReply(type, F("%s"),msg);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
|
||||
|
@@ -2,6 +2,8 @@
|
||||
* © 2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* © 2022 Colin Murdoch
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -33,8 +35,9 @@
|
||||
#endif
|
||||
|
||||
class CommandDistributor {
|
||||
private:
|
||||
public:
|
||||
enum clientType: byte {NONE_TYPE,COMMAND_TYPE,WITHROTTLE_TYPE};
|
||||
private:
|
||||
static void broadcastToClients(clientType type);
|
||||
static StringBuffer * broadcastBufferWriter;
|
||||
#ifdef CD_HANDLE_RING
|
||||
@@ -46,11 +49,12 @@ 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 broadcastText(const FSH * msg);
|
||||
static void broadcastRaw(clientType type,char * msg);
|
||||
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);
|
||||
|
@@ -30,6 +30,7 @@
|
||||
* © 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
|
||||
@@ -75,6 +76,15 @@ void setup()
|
||||
|
||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||
|
||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
// As the setup of a motor shield may require a read of the current sense input from the ADC,
|
||||
// let's make sure to initialise the ADCee class!
|
||||
ADCee::begin();
|
||||
// Set up MotorDrivers early to initialize all pins
|
||||
TrackManager::Setup(MOTOR_SHIELD_TYPE);
|
||||
|
||||
DISPLAY_START (
|
||||
// This block is still executed for DIAGS if display not in use
|
||||
LCD(0,F("DCC-EX v%S"),F(VERSION));
|
||||
@@ -86,29 +96,19 @@ void setup()
|
||||
// Start Ethernet if it exists
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
#if WIFI_ON
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL);
|
||||
WifiInterface::setup(WIFI_SERIAL_LINK_SPEED, F(WIFI_SSID), F(WIFI_PASSWORD), F(WIFI_HOSTNAME), IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // WIFI_ON
|
||||
#else
|
||||
// ESP32 needs wifi on always
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL);
|
||||
WifiESP::setup(WIFI_SSID, WIFI_PASSWORD, WIFI_HOSTNAME, IP_PORT, WIFI_CHANNEL, WIFI_FORCE_AP);
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
|
||||
#if ETHERNET_ON
|
||||
EthernetInterface::setup();
|
||||
#endif // ETHERNET_ON
|
||||
|
||||
// Initialise HAL layer before reading EEprom or setting up MotorDrivers
|
||||
IODevice::begin();
|
||||
|
||||
// As the setup of a motor shield may require a read of the current sense input from the ADC,
|
||||
// let's make sure to initialise the ADCee class!
|
||||
ADCee::begin();
|
||||
|
||||
// Responsibility 3: Start the DCC engine.
|
||||
// Note: this provides DCC with two motor drivers, main and prog, which handle the motor shield(s)
|
||||
// Standard supported devices have pre-configured macros but custome hardware installations require
|
||||
// detailed pin mappings and may also require modified subclasses of the MotorDriver to implement specialist logic.
|
||||
// STANDARD_MOTOR_SHIELD, POLOLU_MOTOR_SHIELD, FIREBOX_MK1, FIREBOX_MK1S are pre defined in MotorShields.h
|
||||
TrackManager::Setup(MOTOR_SHIELD_TYPE);
|
||||
DCC::begin();
|
||||
|
||||
// Start RMFT aka EX-RAIL (ignored if no automnation)
|
||||
RMFT::begin();
|
||||
|
5
DCC.cpp
5
DCC.cpp
@@ -60,8 +60,7 @@ const byte FN_GROUP_5=0x10;
|
||||
FSH* DCC::shieldName=NULL;
|
||||
byte DCC::globalSpeedsteps=128;
|
||||
|
||||
void DCC::begin(const FSH * motorShieldName) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
void DCC::begin() {
|
||||
StringFormatter::send(&USB_SERIAL,F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), shieldName, F(GITHUB_SHA));
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Load stuff from EEprom
|
||||
@@ -693,7 +692,7 @@ void DCC::updateLocoReminder(int loco, byte speedCode) {
|
||||
|
||||
if (loco==0) {
|
||||
// broadcast stop/estop but dont change direction
|
||||
for (int reg = 0; reg < highestUsedReg; reg++) {
|
||||
for (int reg = 0; reg <= highestUsedReg; reg++) {
|
||||
if (speedTable[reg].loco==0) continue;
|
||||
byte newspeed=(speedTable[reg].speedCode & 0x80) | (speedCode & 0x7f);
|
||||
if (speedTable[reg].speedCode != newspeed) {
|
||||
|
5
DCC.h
5
DCC.h
@@ -51,7 +51,10 @@ const byte MAX_LOCOS = 30;
|
||||
class DCC
|
||||
{
|
||||
public:
|
||||
static void begin(const FSH * motorShieldName);
|
||||
static inline void setShieldName(const FSH * motorShieldName) {
|
||||
shieldName=(FSH *)motorShieldName;
|
||||
};
|
||||
static void begin();
|
||||
static void loop();
|
||||
|
||||
// Public DCC API functions
|
||||
|
@@ -152,7 +152,7 @@ byte DCCACK::getAck() {
|
||||
return(0); // pending set off but not detected means no ACK.
|
||||
}
|
||||
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
void DCCACK::loop() {
|
||||
while (ackManagerProg) {
|
||||
byte opcode=GETFLASH(ackManagerProg);
|
||||
@@ -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;
|
||||
@@ -414,7 +414,7 @@ void DCCACK::callback(int value) {
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void DCCACK::checkAck(byte sentResetsSincePacket) {
|
||||
if (!ackPending) return;
|
||||
|
322
DCCEXParser.cpp
322
DCCEXParser.cpp
@@ -3,10 +3,11 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Herb Morton
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 M Steve Todd
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -24,6 +25,79 @@
|
||||
* 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,
|
||||
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, Reserved for future use - Turntable object broadcast
|
||||
I, Reserved for future use - Turntable object command and control
|
||||
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,
|
||||
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"
|
||||
@@ -40,22 +114,21 @@
|
||||
#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.
|
||||
#define SENDFLASHLIST(stream,flashList) \
|
||||
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
|
||||
int16_t value=GETHIGHFLASHW(flashList,i); \
|
||||
if (value==0) break; \
|
||||
StringFormatter::send(stream,F(" %d"),value); \
|
||||
if (value==INT16_MAX) break; \
|
||||
if (value != 0) StringFormatter::send(stream,F(" %d"),value); \
|
||||
}
|
||||
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
// To discover new keyword numbers , use the <$ YOURKEYWORD> command
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_JOIN = -30750;
|
||||
const int16_t HASH_KEYWORD_CABS = -11981;
|
||||
const int16_t HASH_KEYWORD_RAM = 25982;
|
||||
const int16_t HASH_KEYWORD_CMD = 9962;
|
||||
@@ -63,7 +136,11 @@ const int16_t HASH_KEYWORD_ACK = 3113;
|
||||
const int16_t HASH_KEYWORD_ON = 2657;
|
||||
const int16_t HASH_KEYWORD_DCC = 6436;
|
||||
const int16_t HASH_KEYWORD_SLOW = -17209;
|
||||
#ifndef DISABLE_PROG
|
||||
const int16_t HASH_KEYWORD_JOIN = -30750;
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
const int16_t HASH_KEYWORD_PROGBOOST = -6353;
|
||||
#endif
|
||||
#ifndef DISABLE_EEPROM
|
||||
const int16_t HASH_KEYWORD_EEPROM = -7168;
|
||||
#endif
|
||||
@@ -81,6 +158,8 @@ 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_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';
|
||||
@@ -92,6 +171,8 @@ 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;
|
||||
@@ -216,6 +297,9 @@ 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
|
||||
@@ -285,6 +369,8 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
if (direction < 0 || direction > 1)
|
||||
break; // invalid direction code
|
||||
if (cab > 10239 || cab < 0)
|
||||
break; // beyond DCC range
|
||||
|
||||
DCC::setThrottle(cab, tspeed, direction);
|
||||
if (params == 4) // send obsolete format T response
|
||||
@@ -368,16 +454,24 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
break;
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
|
||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||
return;
|
||||
if (params != 3)
|
||||
break;
|
||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||
return;
|
||||
|
||||
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
||||
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
|
||||
return;
|
||||
if (params != 4)
|
||||
break;
|
||||
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
|
||||
return;
|
||||
#endif
|
||||
|
||||
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
||||
#ifndef DISABLE_PROG
|
||||
case 'P': // WRITE TRANSPARENT DCC PACKET PROG <P REG X1 ... X9>
|
||||
#endif
|
||||
// NOTE: this command was parsed in HEX instead of decimal
|
||||
params--; // drop REG
|
||||
if (params<1) break;
|
||||
@@ -392,15 +486,18 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
return;
|
||||
|
||||
#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 // WRITE CV ON PROG <W CV VALUE>
|
||||
else if (params == 2) // 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>
|
||||
@@ -420,9 +517,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
break;
|
||||
|
||||
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
||||
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB> or <B CV BIT VALUE>
|
||||
if (params != 3 && params != 5)
|
||||
break;
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
break;
|
||||
break;
|
||||
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
|
||||
return;
|
||||
|
||||
@@ -449,6 +548,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||
{
|
||||
@@ -456,27 +556,29 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
bool prog=false;
|
||||
bool join=false;
|
||||
if (params > 1) break;
|
||||
if (params==0 || MotorDriver::commonFaultPin) { // <1> or tracks can not be handled individually
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
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_MAIN) { // <1 MAIN>
|
||||
main=true;
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
prog=true;
|
||||
}
|
||||
#endif
|
||||
else break; // will reply <X>
|
||||
}
|
||||
TrackManager::setJoin(join);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(join);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -487,7 +589,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
if (params > 1) break;
|
||||
if (params==0 || MotorDriver::commonFaultPin) { // <0> or tracks can not be handled individually
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
@@ -495,18 +597,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
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>
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
TrackManager::setJoin(false);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
@@ -547,12 +651,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case ' ': // < >
|
||||
StringFormatter::send(stream, F("\n"));
|
||||
return;
|
||||
|
||||
#ifndef DISABLE_DIAG
|
||||
case 'D': // < >
|
||||
if (parseD(stream, params, p))
|
||||
return;
|
||||
return;
|
||||
|
||||
break;
|
||||
#endif
|
||||
case '=': // <= Track manager control >
|
||||
if (TrackManager::parseJ(stream, params, p))
|
||||
return;
|
||||
@@ -637,8 +741,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
if (params==1) {
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
}
|
||||
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
|
||||
else {
|
||||
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;
|
||||
@@ -667,11 +775,70 @@ 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
|
||||
|
||||
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++)
|
||||
@@ -892,6 +1059,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
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>
|
||||
if (params >= 3) {
|
||||
if (p[1] == HASH_KEYWORD_LIMIT) {
|
||||
@@ -912,6 +1080,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
Diag::ACK = onOff;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
|
||||
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
|
||||
Diag::CMD = onOff;
|
||||
@@ -934,11 +1103,11 @@ 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
|
||||
@@ -967,7 +1136,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
break;
|
||||
|
||||
case HASH_KEYWORD_ANIN: // <D ANIN vpin> Display analogue input value
|
||||
DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
||||
DIAG(F("VPIN=%u value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
||||
break;
|
||||
|
||||
#if !defined(IO_NO_HAL)
|
||||
@@ -989,6 +1158,99 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
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)
|
||||
{
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#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);
|
||||
@@ -45,13 +46,16 @@ 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 parseD(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 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 Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
static Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
|
||||
static bool stashBusy;
|
||||
static byte stashTarget;
|
||||
|
@@ -194,8 +194,10 @@ 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;
|
||||
}
|
||||
|
||||
@@ -212,6 +214,8 @@ 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--;
|
||||
|
27
DCCTimer.h
27
DCCTimer.h
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -62,6 +62,9 @@ class DCCTimer {
|
||||
static bool isPWMPin(byte pin);
|
||||
static void setPWM(byte pin, bool high);
|
||||
static void clearPWM();
|
||||
static void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||
static void DCCEXanalogWrite(uint8_t pin, int value);
|
||||
|
||||
// Update low ram level. Allow for extra bytes to be specified
|
||||
// by estimation or inspection, that may be used by other
|
||||
// called subroutines. Must be called with interrupts disabled.
|
||||
@@ -102,9 +105,14 @@ private:
|
||||
// that an offset can be initialized.
|
||||
class ADCee {
|
||||
public:
|
||||
// init does add the pin to the list of scanned pins (if this
|
||||
// begin is called for any setup that must be done before
|
||||
// **init** can be called. On some architectures this involves ADC
|
||||
// initialisation and clock routing, sampling times etc.
|
||||
static void begin();
|
||||
// init adds the pin to the list of scanned pins (if this
|
||||
// platform's implementation scans pins) and returns the first
|
||||
// read value. It is called before the regular scan is started.
|
||||
// read value (which is why it required begin to have been called first!)
|
||||
// It must be called before the regular scan is started.
|
||||
static int init(uint8_t pin);
|
||||
// read does read the pin value from the scanned cache or directly
|
||||
// if this is a platform that does not scan. fromISR is a hint if
|
||||
@@ -113,19 +121,20 @@ public:
|
||||
static int read(uint8_t pin, bool fromISR=false);
|
||||
// returns possible max value that the ADC can return
|
||||
static int16_t ADCmax();
|
||||
// begin is called for any setup that must be done before
|
||||
// scan can be called.
|
||||
static void begin();
|
||||
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;
|
||||
// ids to scan (new way)
|
||||
static byte *idarr;
|
||||
// friend so that we can call scan() and begin()
|
||||
friend class DCCWaveform;
|
||||
};
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021 Mike S
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
@@ -29,6 +29,9 @@
|
||||
#include <avr/boot.h>
|
||||
#include <avr/wdt.h>
|
||||
#include "DCCTimer.h"
|
||||
#ifdef DEBUG_ADC
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
|
||||
// Arduino nano, uno, mega etc
|
||||
@@ -128,8 +131,8 @@ void DCCTimer::reset() {
|
||||
#define NUM_ADC_INPUTS 8
|
||||
#endif
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
uint8_t ADCee::highestPin = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
byte *ADCee::idarr = NULL;
|
||||
static bool ADCusesHighPort = false;
|
||||
|
||||
/*
|
||||
@@ -139,28 +142,17 @@ static bool ADCusesHighPort = false;
|
||||
*/
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint8_t id = pin - A0;
|
||||
byte n;
|
||||
if (id >= NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
if (id > 7)
|
||||
ADCusesHighPort = true;
|
||||
pinMode(pin, INPUT);
|
||||
int value = analogRead(pin);
|
||||
if (analogvals == NULL) {
|
||||
if (analogvals == NULL)
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS, sizeof(int));
|
||||
for (n=0 ; n < NUM_ADC_INPUTS; n++) // set unreasonable value at startup as marker
|
||||
analogvals[n] = -32768; // 16 bit int min value
|
||||
idarr = (byte *)calloc(NUM_ADC_INPUTS+1, sizeof(byte)); // +1 for terminator value
|
||||
for (n=0 ; n <= NUM_ADC_INPUTS; n++)
|
||||
idarr[n] = 255; // set 255 as end of array marker
|
||||
}
|
||||
analogvals[id] = value; // store before enable by idarr[n]
|
||||
for (n=0 ; n <= NUM_ADC_INPUTS; n++) {
|
||||
if (idarr[n] == 255) {
|
||||
idarr[n] = id;
|
||||
break;
|
||||
}
|
||||
}
|
||||
analogvals[id] = value;
|
||||
usedpins |= (1<<id);
|
||||
if (id > highestPin) highestPin = id;
|
||||
return value;
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
@@ -170,14 +162,14 @@ int16_t ADCee::ADCmax() {
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
(void)fromISR; // AVR does ignore this arg
|
||||
uint8_t id = pin - A0;
|
||||
int a;
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
// we do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
noInterrupts();
|
||||
a = analogvals[id];
|
||||
interrupts();
|
||||
if (!fromISR) noInterrupts();
|
||||
int a = analogvals[id];
|
||||
if (!fromISR) interrupts();
|
||||
return a;
|
||||
}
|
||||
/*
|
||||
@@ -186,7 +178,8 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static byte num = 0; // index into id array
|
||||
static byte 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;
|
||||
|
||||
if (waiting) {
|
||||
@@ -198,26 +191,49 @@ void ADCee::scan() {
|
||||
low = ADCL; //must read low before high
|
||||
high = ADCH;
|
||||
bitSet(ADCSRA, ADIF);
|
||||
analogvals[idarr[num]] = (high << 8) | low;
|
||||
analogvals[id] = (high << 8) | low;
|
||||
// advance at least one track
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(0);
|
||||
#endif
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id > highestPin) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
// cycle around in-use analogue pins
|
||||
num++;
|
||||
if (idarr[num] == 255)
|
||||
num = 0;
|
||||
// start new ADC aquire on id
|
||||
if (usedpins == 0) // otherwise we would loop forever
|
||||
return;
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
#if defined(ADCSRB) && defined(MUX5)
|
||||
if (ADCusesHighPort) { // if we ever have started to use high pins)
|
||||
if (idarr[num] > 7) // if we use a high ADC pin
|
||||
bitSet(ADCSRB, MUX5); // set MUX5 bit
|
||||
else
|
||||
bitClear(ADCSRB, MUX5);
|
||||
}
|
||||
if (ADCusesHighPort) { // if we ever have started to use high pins)
|
||||
if (id > 7) // if we use a high ADC pin
|
||||
bitSet(ADCSRB, MUX5); // set MUX5 bit
|
||||
else
|
||||
bitClear(ADCSRB, MUX5);
|
||||
}
|
||||
#endif
|
||||
ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA, ADSC); // start conversion
|
||||
waiting = true;
|
||||
ADMUX=(1<<REFS0)|(id & 0x07); //select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA,ADSC); // start conversion
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(1);
|
||||
#endif
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id > highestPin) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
@@ -231,4 +247,4 @@ void ADCee::begin() {
|
||||
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -150,6 +150,45 @@ int DCCTimer::freeMemory() {
|
||||
void DCCTimer::reset() {
|
||||
ESP.restart();
|
||||
}
|
||||
|
||||
#include "esp32-hal.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
|
||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||
#else
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
|
||||
#endif
|
||||
|
||||
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||
static int cnt_channel = LEDC_CHANNELS;
|
||||
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] != 0) {
|
||||
ledcSetup(pin_to_channel[pin], frequency, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] == 0) {
|
||||
if (!cnt_channel) {
|
||||
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
||||
return;
|
||||
}
|
||||
pin_to_channel[pin] = --cnt_channel;
|
||||
ledcSetup(cnt_channel, 1000, 8);
|
||||
ledcAttachPin(pin, cnt_channel);
|
||||
} else {
|
||||
ledcAttachPin(pin, pin_to_channel[pin]);
|
||||
}
|
||||
ledcWrite(pin_to_channel[pin], value);
|
||||
}
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
pinMode(pin, ANALOG);
|
||||
adc1_config_width(ADC_WIDTH_BIT_12);
|
||||
|
@@ -162,7 +162,7 @@ uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint id = pin - A0;
|
||||
uint8_t id = pin - A0;
|
||||
int value = 0;
|
||||
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
@@ -210,7 +210,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
||||
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;
|
||||
|
||||
|
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* © 2023 Neil McKechnie
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2022-23 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
* © 2021, 2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
* © 2021 Chris Harlow
|
||||
* © 2021 David Cutting
|
||||
@@ -30,24 +30,35 @@
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
#include "DCCTimer.h"
|
||||
#ifdef DEBUG_ADC
|
||||
#include "TrackManager.h"
|
||||
#endif
|
||||
#include "DIAG.h"
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
// Nucleo-64 boards don't have Serial1 defined by default
|
||||
#if defined(ARDUINO_NUCLEO_F401RE) || 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
|
||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
// Let's define Serial6 as an additional serial port (the only other option for the Nucleo-64s)
|
||||
HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14 - F411RE
|
||||
#elif defined(ARDUINO_NUCLEO_F446RE)
|
||||
// Nucleo-64 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
|
||||
// Nucleo-64 boards don't have additional serial ports defined by default
|
||||
// On the F446RE, Serial1 isn't really useable as it's Rx/Tx pair sit on already used D2/D10 pins
|
||||
// HardwareSerial Serial1(PA10, PB6); // Rx=PA10 (D2), Tx=PB6 (D10) -- CN10 pins 17 and 9 - F446RE
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// On the F446RE, Serial3 and Serial5 are easy to use:
|
||||
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)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
|
||||
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
|
||||
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
#else
|
||||
#warning Serial1 not defined
|
||||
#error STM32 board selected is not yet explicitly supported - so Serial1 peripheral is not defined
|
||||
#endif
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -224,28 +235,56 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
// Now we can handle more ADCs, maybe this works!
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
bool adc1configured = false;
|
||||
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) {
|
||||
uint id = pin - A0;
|
||||
int value = 0;
|
||||
PinName stmpin = digitalPin[analogInputPin[id]];
|
||||
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32
|
||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
||||
GPIO_TypeDef * gpioBase;
|
||||
|
||||
// Port config - find which port we're on and power it up
|
||||
switch(stmgpio) {
|
||||
int value = 0;
|
||||
PinName stmpin = analogInputToPinName(pin);
|
||||
if (stmpin == NC) // do not continue if this is not an analog pin at all
|
||||
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);
|
||||
|
||||
// Port config - find which port we're on and power it up
|
||||
GPIO_TypeDef *gpioBase;
|
||||
|
||||
switch (stmgpio)
|
||||
{
|
||||
case 0x00:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
|
||||
gpioBase = GPIOA;
|
||||
@@ -258,31 +297,62 @@ 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
|
||||
}
|
||||
|
||||
// Set pin mux mode to analog input
|
||||
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
|
||||
// Set pin mux mode to analog input, the 32 bit port mode register has 2 bits per pin
|
||||
gpioBase->MODER |= (0b011 << (STM_PIN(stmpin) << 1)); // Set pin mux to analog mode (binary 11)
|
||||
|
||||
// Set the sampling rate for that analog input
|
||||
// This is F411x specific! Different on for example F334
|
||||
// STM32F11xC/E Reference manual
|
||||
// 11.12.4 ADC sample time register 1 (ADC_SMPR1) (channels 10 to 18)
|
||||
// 11.12.5 ADC sample time register 2 (ADC_SMPR2) (channels 0 to 9)
|
||||
if (adcchan > 18)
|
||||
return -1022; // silly value as error
|
||||
if (adcchan < 10)
|
||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
adc->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
else
|
||||
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
adc->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
|
||||
// Read the inital ADC value for this analog input
|
||||
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = ADC1->DR; // Read value from register
|
||||
adc->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
adc->CR2 |= ADC_CR2_SWSTART; //(1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(adc->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = adc->DR; // Read value from register
|
||||
|
||||
if (analogvals == NULL)
|
||||
{
|
||||
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 (analogvals == NULL) { // allocate analogvals, analogchans and adcchans if this is the first invocation of init
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||
adcchans = (ADC_TypeDef **)calloc(NUM_ADC_INPUTS+1, sizeof(ADC_TypeDef));
|
||||
}
|
||||
analogvals[id] = value; // Store sampled value
|
||||
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
adcchans[id] = adc; // Keep track of which ADC this channel is on
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
if (id > highestPin) highestPin = id; // Store our highest pin in use
|
||||
|
||||
DIAG(F("ADCee::init(): value=%d, ADC%d: channel=%d, id=%d"), value, adcnum, adcchan, id);
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -291,7 +361,7 @@ int ADCee::init(uint8_t pin) {
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
uint8_t id = pin - A0;
|
||||
uint8_t id = pin - PNUM_ANALOG_BASE;
|
||||
// Was this pin initialised yet?
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
@@ -306,22 +376,27 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
static uint id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint8_t id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
static ADC_TypeDef *adc;
|
||||
|
||||
if (waiting) {
|
||||
adc = adcchans[id];
|
||||
if (waiting)
|
||||
{
|
||||
// look if we have a result
|
||||
if (!(ADC1->SR & (1 << 1)))
|
||||
if (!(adc->SR & (1 << 1)))
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
analogvals[id] = ADC1->DR;
|
||||
analogvals[id] = adc->DR;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(0);
|
||||
#endif
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
if (id > highestPin) { // the 1 has been shifted out
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
@@ -332,18 +407,21 @@ void ADCee::scan() {
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
return;
|
||||
// start new ADC aquire on id
|
||||
adc = adcchans[id];
|
||||
adc->SQR3 = analogchans[id]; // 1st conversion in regular sequence
|
||||
adc->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(1);
|
||||
#endif
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
if (id > highestPin) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -353,19 +431,83 @@ void ADCee::scan() {
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
//ADC1 config sequence
|
||||
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
|
||||
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable ADC1 clock
|
||||
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
|
||||
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
|
||||
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
// Disable the DMA controller for ADC1
|
||||
ADC1->CR2 &= ~ADC_CR2_DMA;
|
||||
ADC1->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||
// Wait for ADC1 to become ready (calibration complete)
|
||||
while (!(ADC1->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
#if defined(ADC2)
|
||||
// Enable the ADC2 clock
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
|
||||
|
||||
// Initialize ADC2
|
||||
ADC2->CR1 = 0; // Disable all channels
|
||||
ADC2->CR2 = 0; // Clear CR2 register
|
||||
|
||||
ADC2->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC2->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC2->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC2->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
|
||||
ADC2->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC2->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC2->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC2->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC2->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
|
||||
// Enable the ADC
|
||||
ADC2->CR2 |= ADC_CR2_ADON;
|
||||
|
||||
// Wait for ADC2 to become ready (calibration complete)
|
||||
while (!(ADC2->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
|
||||
// Perform ADC3 calibration (optional)
|
||||
// ADC3->CR2 |= ADC_CR2_CAL;
|
||||
// while (ADC3->CR2 & ADC_CR2_CAL) {
|
||||
// }
|
||||
#endif
|
||||
#if defined(ADC3)
|
||||
// Enable the ADC3 clock
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
|
||||
|
||||
// Initialize ADC3
|
||||
ADC3->CR1 = 0; // Disable all channels
|
||||
ADC3->CR2 = 0; // Clear CR2 register
|
||||
|
||||
ADC3->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC3->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC3->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC3->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
|
||||
ADC3->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC3->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC3->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC3->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC3->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
|
||||
// Enable the ADC
|
||||
ADC3->CR2 |= ADC_CR2_ADON;
|
||||
|
||||
// Wait for ADC3 to become ready (calibration complete)
|
||||
while (!(ADC3->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
|
||||
// Perform ADC3 calibration (optional)
|
||||
// ADC3->CR2 |= ADC_CR2_CAL;
|
||||
// while (ADC3->CR2 & ADC_CR2_CAL) {
|
||||
// }
|
||||
#endif
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -247,6 +247,9 @@ 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);
|
||||
{
|
||||
|
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* © 2022 Harald Barth
|
||||
* 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/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include <Arduino.h>
|
||||
#include "ESP32-fixes.h"
|
||||
|
||||
#include "esp32-hal.h"
|
||||
#include "soc/soc_caps.h"
|
||||
|
||||
|
||||
#ifdef SOC_LEDC_SUPPORT_HS_MODE
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM<<1)
|
||||
#else
|
||||
#define LEDC_CHANNELS (SOC_LEDC_CHANNEL_NUM)
|
||||
#endif
|
||||
|
||||
static int8_t pin_to_channel[SOC_GPIO_PIN_COUNT] = { 0 };
|
||||
static int cnt_channel = LEDC_CHANNELS;
|
||||
|
||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] != 0) {
|
||||
ledcSetup(pin_to_channel[pin], frequency, 8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
if (pin < SOC_GPIO_PIN_COUNT) {
|
||||
if (pin_to_channel[pin] == 0) {
|
||||
if (!cnt_channel) {
|
||||
log_e("No more PWM channels available! All %u already used", LEDC_CHANNELS);
|
||||
return;
|
||||
}
|
||||
pin_to_channel[pin] = --cnt_channel;
|
||||
ledcAttachPin(pin, cnt_channel);
|
||||
ledcSetup(cnt_channel, 1000, 8);
|
||||
} else {
|
||||
ledcAttachPin(pin, pin_to_channel[pin]);
|
||||
}
|
||||
ledcWrite(pin_to_channel[pin], value);
|
||||
}
|
||||
}
|
||||
#endif
|
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* © 2022 Harald Barth
|
||||
* 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/>.
|
||||
*/
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#pragma once
|
||||
#include <Arduino.h>
|
||||
void DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency);
|
||||
void DCCEXanalogWrite(uint8_t pin, int value);
|
||||
#endif
|
||||
|
BIN
EX-CommandStation-installer.exe
Normal file
BIN
EX-CommandStation-installer.exe
Normal file
Binary file not shown.
159
EXRAIL2.cpp
159
EXRAIL2.cpp
@@ -2,6 +2,7 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2023 Chris Harlow
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -51,6 +52,8 @@
|
||||
#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;
|
||||
@@ -93,6 +96,10 @@ 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
|
||||
@@ -174,7 +181,10 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
|
||||
onClockLookup=LookListLoader(OPCODE_ONTIME);
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
|
||||
#endif
|
||||
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
// add sequences onRoutines to the lookups
|
||||
@@ -194,11 +204,12 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
case OPCODE_AT:
|
||||
case OPCODE_ATTIMEOUT2:
|
||||
case OPCODE_AFTER:
|
||||
case OPCODE_AFTEROVERLOAD:
|
||||
case OPCODE_IF:
|
||||
case OPCODE_IFNOT: {
|
||||
int16_t pin = (int16_t)operand;
|
||||
if (pin<0) pin = -pin;
|
||||
DIAG(F("EXRAIL input vpin %d"),pin);
|
||||
DIAG(F("EXRAIL input VPIN %u"),pin);
|
||||
IODevice::configureInput((VPIN)pin,true);
|
||||
break;
|
||||
}
|
||||
@@ -208,7 +219,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
case OPCODE_IFGTE:
|
||||
case OPCODE_IFLT:
|
||||
case OPCODE_DRIVE: {
|
||||
DIAG(F("EXRAIL analog input vpin %d"),(VPIN)operand);
|
||||
DIAG(F("EXRAIL analog input VPIN %u"),(VPIN)operand);
|
||||
IODevice::configureAnalogIn((VPIN)operand);
|
||||
break;
|
||||
}
|
||||
@@ -237,7 +248,38 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
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
|
||||
@@ -262,19 +304,26 @@ 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);
|
||||
if (rid==INT16_MAX) break;
|
||||
if (rid==id) return 'R';
|
||||
if (rid==0) break;
|
||||
}
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(automationIdList,i);
|
||||
if (rid==INT16_MAX) break;
|
||||
if (rid==id) return 'A';
|
||||
if (rid==0) break;
|
||||
}
|
||||
return 'X';
|
||||
}
|
||||
}
|
||||
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||
@@ -597,6 +646,14 @@ 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);
|
||||
@@ -608,6 +665,7 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_SPEED:
|
||||
forward=DCC::getThrottleDirection(loco)^invert;
|
||||
driveLoco(operand);
|
||||
break;
|
||||
|
||||
@@ -681,7 +739,17 @@ 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;
|
||||
@@ -702,11 +770,11 @@ void RMFT2::loop2() {
|
||||
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||
pausingTask=this;
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_POM:
|
||||
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_POWEROFF:
|
||||
TrackManager::setPower(POWERMODE::OFF);
|
||||
TrackManager::setJoin(false);
|
||||
@@ -785,7 +853,13 @@ 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;
|
||||
|
||||
@@ -881,23 +955,18 @@ void RMFT2::loop2() {
|
||||
while(loopTask) loopTask->kill(F("KILLALL"));
|
||||
return;
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
case OPCODE_JOIN:
|
||||
TrackManager::setPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(true);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_POWERON:
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_UNJOIN:
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_READ_LOCO1: // READ_LOCO is implemented as 2 separate opcodes
|
||||
progtrackLocoId=LOCO_ID_WAITING; // Nothing found yet
|
||||
DCC::getLocoId(readLocoCallback);
|
||||
@@ -918,6 +987,13 @@ void RMFT2::loop2() {
|
||||
forward=true;
|
||||
invert=false;
|
||||
break;
|
||||
#endif
|
||||
|
||||
case OPCODE_POWERON:
|
||||
TrackManager::setMainPower(POWERMODE::ON);
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_START:
|
||||
{
|
||||
@@ -956,7 +1032,16 @@ 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;
|
||||
@@ -981,6 +1066,13 @@ 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;
|
||||
|
||||
@@ -1125,13 +1217,33 @@ 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)
|
||||
DIAG(F("Looking for clock event at : %d"), clocktime);
|
||||
if (change) handleEvent(F("CLOCK"),onClockLookup,clocktime);
|
||||
if (change) {
|
||||
handleEvent(F("CLOCK"),onClockLookup,clocktime);
|
||||
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60);
|
||||
}
|
||||
}
|
||||
|
||||
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) return;
|
||||
@@ -1237,7 +1349,10 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL);
|
||||
break;
|
||||
case thrunge_broadcast:
|
||||
// TODO CommandDistributor::broadcastText(buffer->getString());
|
||||
CommandDistributor::broadcastRaw(CommandDistributor::COMMAND_TYPE,buffer->getString());
|
||||
break;
|
||||
case thrunge_withrottle:
|
||||
CommandDistributor::broadcastRaw(CommandDistributor::WITHROTTLE_TYPE,buffer->getString());
|
||||
break;
|
||||
case thrunge_lcd:
|
||||
LCD(id,F("%s"),buffer->getString());
|
||||
|
29
EXRAIL2.h
29
EXRAIL2.h
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -24,6 +25,7 @@
|
||||
#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.
|
||||
@@ -34,7 +36,8 @@
|
||||
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_AUTOSTART,
|
||||
OPCODE_AT,OPCODE_AFTER,
|
||||
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
|
||||
OPCODE_ATGTE,OPCODE_ATLT,
|
||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
|
||||
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
||||
@@ -44,7 +47,10 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_RED,OPCODE_GREEN,OPCODE_AMBER,OPCODE_DRIVE,
|
||||
OPCODE_SERVO,OPCODE_SIGNAL,OPCODE_TURNOUT,OPCODE_WAITFOR,
|
||||
OPCODE_PAD,OPCODE_FOLLOW,OPCODE_CALL,OPCODE_RETURN,
|
||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,OPCODE_POM,
|
||||
#ifndef DISABLE_PROG
|
||||
OPCODE_JOIN,OPCODE_UNJOIN,OPCODE_READ_LOCO1,OPCODE_READ_LOCO2,
|
||||
#endif
|
||||
OPCODE_POM,
|
||||
OPCODE_START,OPCODE_SETLOCO,OPCODE_SENDLOCO,OPCODE_FORGET,
|
||||
OPCODE_PAUSE, OPCODE_RESUME,OPCODE_POWEROFF,OPCODE_POWERON,
|
||||
OPCODE_ONCLOSE, OPCODE_ONTHROW, OPCODE_SERVOTURNOUT, OPCODE_PINTURNOUT,
|
||||
@@ -58,6 +64,11 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ONCHANGE,
|
||||
OPCODE_ONCLOCKTIME,
|
||||
OPCODE_ONTIME,
|
||||
#ifndef IO_NO_HAL
|
||||
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
|
||||
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_IFTTPOSITION,OPCODE_WAITFORTT,
|
||||
#endif
|
||||
OPCODE_ONOVERLOAD,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -76,7 +87,8 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
// Ensure thrunge_lcd is put last as there may be more than one display,
|
||||
// sequentially numbered from thrunge_lcd.
|
||||
enum thrunger: byte {
|
||||
thrunge_print, thrunge_broadcast, thrunge_serial,thrunge_parse,
|
||||
thrunge_print, thrunge_broadcast, thrunge_withrottle,
|
||||
thrunge_serial,thrunge_parse,
|
||||
thrunge_serial1, thrunge_serial2, thrunge_serial3,
|
||||
thrunge_serial4, thrunge_serial5, thrunge_serial6,
|
||||
thrunge_lcn,
|
||||
@@ -125,6 +137,8 @@ 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;
|
||||
@@ -139,6 +153,8 @@ 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[]);
|
||||
@@ -151,6 +167,9 @@ 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);
|
||||
@@ -183,6 +202,10 @@ private:
|
||||
static LookList * onGreenLookup;
|
||||
static LookList * onChangeLookup;
|
||||
static LookList * onClockLookup;
|
||||
#ifndef IO_NO_HAL
|
||||
static LookList * onRotateLookup;
|
||||
#endif
|
||||
static LookList * onOverloadLookup;
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -26,6 +27,7 @@
|
||||
#undef ACTIVATE
|
||||
#undef ACTIVATEL
|
||||
#undef AFTER
|
||||
#undef AFTEROVERLOAD
|
||||
#undef ALIAS
|
||||
#undef AMBER
|
||||
#undef ANOUT
|
||||
@@ -39,6 +41,7 @@
|
||||
#undef CALL
|
||||
#undef CLOSE
|
||||
#undef DCC_SIGNAL
|
||||
#undef DCC_TURNTABLE
|
||||
#undef DEACTIVATE
|
||||
#undef DEACTIVATEL
|
||||
#undef DELAY
|
||||
@@ -50,8 +53,9 @@
|
||||
#undef ENDEXRAIL
|
||||
#undef ENDIF
|
||||
#undef ENDTASK
|
||||
#undef ESTOP
|
||||
#undef EXRAIL
|
||||
#undef ESTOP
|
||||
#undef EXRAIL
|
||||
#undef EXTT_TURNTABLE
|
||||
#undef FADE
|
||||
#undef FOFF
|
||||
#undef FOLLOW
|
||||
@@ -74,6 +78,7 @@
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef IFTTPOSITION
|
||||
#undef IFRE
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JOIN
|
||||
@@ -91,15 +96,20 @@
|
||||
#undef ONCLOSE
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONCLOCKMINS
|
||||
#undef ONOVERLOAD
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONROTATE
|
||||
#undef ONTHROW
|
||||
#undef ONCHANGE
|
||||
#undef PARSE
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
#undef PRINT
|
||||
#ifndef DISABLE_PROG
|
||||
#undef POM
|
||||
#endif
|
||||
#undef POWEROFF
|
||||
#undef POWERON
|
||||
#undef READ_LOCO
|
||||
@@ -109,7 +119,9 @@
|
||||
#undef RESUME
|
||||
#undef RETURN
|
||||
#undef REV
|
||||
#undef ROSTER
|
||||
#undef ROSTER
|
||||
#undef ROTATE
|
||||
#undef ROTATE_DCC
|
||||
#undef ROUTE
|
||||
#undef SENDLOCO
|
||||
#undef SEQUENCE
|
||||
@@ -132,13 +144,19 @@
|
||||
#undef SPEED
|
||||
#undef START
|
||||
#undef STOP
|
||||
#undef THROW
|
||||
#undef THROW
|
||||
#undef TT_ADDPOSITION
|
||||
#undef TURNOUT
|
||||
#undef TURNOUTL
|
||||
#undef UNJOIN
|
||||
#undef UNLATCH
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#undef WAITFOR
|
||||
#ifndef IO_NO_HAL
|
||||
#undef WAITFORTT
|
||||
#endif
|
||||
#undef WITHROTTLE
|
||||
#undef XFOFF
|
||||
#undef XFON
|
||||
|
||||
@@ -146,6 +164,7 @@
|
||||
#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)
|
||||
@@ -159,6 +178,7 @@
|
||||
#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)
|
||||
@@ -171,7 +191,8 @@
|
||||
#define ENDIF
|
||||
#define ENDTASK
|
||||
#define ESTOP
|
||||
#define EXRAIL
|
||||
#define EXRAIL
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description)
|
||||
#define FADE(pin,value,ms)
|
||||
#define FOFF(func)
|
||||
#define FOLLOW(route)
|
||||
@@ -194,6 +215,7 @@
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define IFTTPOSITION(turntable_id,position)
|
||||
#define IFRE(sensor_id,value)
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
@@ -208,18 +230,23 @@
|
||||
#define ONAMBER(signal_id)
|
||||
#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 ONGREEN(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONROTATE(turntable_id)
|
||||
#define ONTHROW(turnout_id)
|
||||
#define ONCHANGE(sensor_id)
|
||||
#define PAUSE
|
||||
#define PIN_TURNOUT(id,pin,description...)
|
||||
#define PRINT(msg)
|
||||
#define PARSE(msg)
|
||||
#ifndef DISABLE_PROG
|
||||
#define POM(cv,value)
|
||||
#endif
|
||||
#define POWEROFF
|
||||
#define POWERON
|
||||
#define READ_LOCO
|
||||
@@ -229,8 +256,10 @@
|
||||
#define RESUME
|
||||
#define RETURN
|
||||
#define REV(speed)
|
||||
#define ROUTE(id,description)
|
||||
#define ROTATE(turntable_id,position,activity)
|
||||
#define ROTATE_DCC(turntable_id,position)
|
||||
#define ROSTER(cab,name,funcmap...)
|
||||
#define ROUTE(id,description)
|
||||
#define SENDLOCO(cab,route)
|
||||
#define SEQUENCE(id)
|
||||
#define SERIAL(msg)
|
||||
@@ -252,13 +281,19 @@
|
||||
#define SPEED(speed)
|
||||
#define START(route)
|
||||
#define STOP
|
||||
#define THROW(id)
|
||||
#define THROW(id)
|
||||
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
|
||||
#define TURNOUT(id,addr,subaddr,description...)
|
||||
#define TURNOUTL(id,addr,description...)
|
||||
#define UNJOIN
|
||||
#define UNLATCH(sensor_id)
|
||||
#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)
|
||||
#endif
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -53,6 +54,8 @@
|
||||
|
||||
// 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"
|
||||
|
||||
@@ -66,10 +69,13 @@
|
||||
#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
|
||||
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
||||
// Also allows creating EXTurntable object
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef HAL
|
||||
#define HAL(haltype,params...) haltype::create(params);
|
||||
#undef EXTT_TURNTABLE
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) EXTurntable::create(vpin,1,i2c_address);
|
||||
void exrailHalSetup() {
|
||||
#include "myAutomation.h"
|
||||
}
|
||||
@@ -80,14 +86,14 @@ void exrailHalSetup() {
|
||||
#define ROUTE(id, description) id,
|
||||
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
INT16_MAX};
|
||||
// Pass 2a create throttle automation list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) id,
|
||||
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
INT16_MAX};
|
||||
|
||||
// Pass 3 Create route descriptions:
|
||||
#undef ROUTE
|
||||
@@ -152,6 +158,8 @@ const int StringMacroTracker1=__COUNTER__;
|
||||
lcdid=id;\
|
||||
break;\
|
||||
}
|
||||
#undef WITHROTTLE
|
||||
#define WITHROTTLE(msg) THRUNGE(msg,thrunge_withrottle)
|
||||
|
||||
void RMFT2::printMessage(uint16_t id) {
|
||||
thrunger tmode;
|
||||
@@ -184,10 +192,35 @@ 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,i2c_address,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
|
||||
#define ROSTER(cabid,name,funcmap...) +1
|
||||
#define ROSTER(cabid,name,funcmap...) +(cabid <= 0 ? 0 : 1)
|
||||
const byte RMFT2::rosterNameCount=0
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
@@ -198,7 +231,7 @@ const byte RMFT2::rosterNameCount=0
|
||||
#define ROSTER(cabid,name,funcmap...) cabid,
|
||||
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
INT16_MAX};
|
||||
|
||||
// Pass 7: Roster names getter
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
@@ -220,7 +253,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
#include "myAutomation.h"
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Pass 8 Signal definitions
|
||||
@@ -253,6 +286,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#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),
|
||||
@@ -265,6 +299,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#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)),
|
||||
@@ -278,7 +315,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ENDIF OPCODE_ENDIF,0,0,
|
||||
#define ENDTASK OPCODE_ENDTASK,0,0,
|
||||
#define ESTOP OPCODE_SPEED,V(1),
|
||||
#define EXRAIL
|
||||
#define EXRAIL
|
||||
#ifndef IO_NO_HAL
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(i2c_address),OPCODE_PAD,V(home),
|
||||
#endif
|
||||
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
||||
@@ -301,6 +341,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#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,
|
||||
@@ -316,15 +359,22 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||
#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,
|
||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||
#define PIN_TURNOUT(id,pin,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(pin),
|
||||
#ifndef DISABLE_PROG
|
||||
#define POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||
#endif
|
||||
#define POWEROFF OPCODE_POWEROFF,0,0,
|
||||
#define POWERON OPCODE_POWERON,0,0,
|
||||
#define PRINT(msg) OPCODE_PRINT,V(__COUNTER__ - StringMacroTracker2),
|
||||
@@ -337,6 +387,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#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),
|
||||
@@ -360,12 +414,20 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#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,
|
||||
#define UNLATCH(sensor_id) OPCODE_UNLATCH,V(sensor_id),
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
#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),
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202303252126Z"
|
||||
#define GITHUB_SHA "devel-202308302157Z"
|
||||
|
@@ -92,7 +92,7 @@ void I2CManagerClass::begin(void) {
|
||||
// Probe and list devices. Use standard mode
|
||||
// (clock speed 100kHz) for best device compatibility.
|
||||
_setClock(100000);
|
||||
unsigned long originalTimeout = _timeout;
|
||||
uint32_t originalTimeout = _timeout;
|
||||
setTimeout(1000); // use 1ms timeout for probes
|
||||
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
|
@@ -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.
|
||||
unsigned long _timeout = 100000UL;
|
||||
uint32_t _timeout = 100000UL;
|
||||
|
||||
// Finish off request block by waiting for completion and posting status.
|
||||
uint8_t finishRB(I2CRB *rb, uint8_t status);
|
||||
@@ -532,14 +532,15 @@ private:
|
||||
uint8_t bytesToSend = 0;
|
||||
uint8_t bytesToReceive = 0;
|
||||
uint8_t operation = 0;
|
||||
unsigned long startTime = 0;
|
||||
uint32_t 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();
|
||||
|
@@ -172,6 +172,10 @@ 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() {
|
||||
@@ -184,6 +188,7 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a write to an I2C device (non-blocking operation)
|
||||
***************************************************************************/
|
||||
@@ -240,8 +245,8 @@ void I2CManagerClass::checkForTimeout() {
|
||||
I2CRB *t = queueHead;
|
||||
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
|
||||
// Check for timeout
|
||||
unsigned long elapsed = micros() - startTime;
|
||||
if (elapsed > _timeout) {
|
||||
int32_t elapsed = micros() - startTime;
|
||||
if (elapsed > (int32_t)_timeout) {
|
||||
#ifdef DIAG_IO
|
||||
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
|
||||
#endif
|
||||
@@ -300,12 +305,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) {
|
||||
if (state == I2C_STATE_COMPLETED && currentRequest != NULL && currentRequest == queueHead) {
|
||||
// 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 retries disabled.
|
||||
// Status is OK, or has failed and retry count exceeded, or failed and retries disabled.
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
if (muxPhase == MuxPhase_PROLOG ) {
|
||||
overallStatus = completionStatus;
|
||||
|
@@ -26,27 +26,44 @@
|
||||
#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"
|
||||
|
||||
/***************************************************************************
|
||||
* 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
|
||||
***************************************************************************/
|
||||
/*****************************************************************************
|
||||
* 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
|
||||
*****************************************************************************/
|
||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
|
||||
void I2C1_IRQHandler() {
|
||||
#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
|
||||
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
|
||||
|
||||
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants
|
||||
I2C_TypeDef *s = I2C1;
|
||||
#define I2C_IRQn I2C1_EV_IRQn
|
||||
#define I2C_BUSFREQ 16
|
||||
// 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
|
||||
|
||||
// I2C SR1 Status Register #1 bit definitions for convenience
|
||||
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
|
||||
@@ -80,52 +97,55 @@ I2C_TypeDef *s = I2C1;
|
||||
// #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 62.5ns clock period
|
||||
// Use 10x the rise time spec to enable integer divide of 50ns clock period
|
||||
uint16_t t_rise;
|
||||
uint32_t ccr_freq;
|
||||
if (i2cClockSpeed < 200000L) {
|
||||
// i2cClockSpeed = 100000L;
|
||||
t_rise = 0x11; // (1000ns /62.5ns) + 1;
|
||||
}
|
||||
else if (i2cClockSpeed < 800000L)
|
||||
|
||||
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
|
||||
|
||||
if (i2cClockSpeed > 100000L)
|
||||
{
|
||||
i2cClockSpeed = 400000L;
|
||||
t_rise = 0x06; // (300ns / 62.5ns) + 1;
|
||||
// } else if (i2cClockSpeed < 1200000L) {
|
||||
// i2cClockSpeed = 1000000L;
|
||||
// t_rise = 120;
|
||||
if (i2cClockSpeed > 400000L)
|
||||
i2cClockSpeed = 400000L;
|
||||
|
||||
t_rise = 300; // nanoseconds
|
||||
}
|
||||
else
|
||||
{
|
||||
i2cClockSpeed = 100000L;
|
||||
t_rise = 0x11; // (1000ns /62.5ns) + 1;
|
||||
t_rise = 1000; // nanoseconds
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Configure the rise time register
|
||||
s->TRISE = (t_rise / (1000 / i2c_MHz)) + 1;
|
||||
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
// 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;
|
||||
|
||||
// Configure the rise time register
|
||||
s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1
|
||||
// Bit 14: Duty, fast mode duty cycle (use 2:1)
|
||||
// Bit 11-0: FREQR
|
||||
if (i2cClockSpeed > 100000L) {
|
||||
// In fast mode, I2C period is 3 * CCR * TPCLK1.
|
||||
//APB1clk1 / 3 / i2cClockSpeed = 38, but that results in 306KHz not 400!
|
||||
ccr_freq = 30; // So 30 gives 396KHz or so!
|
||||
s->CCR = (uint16_t)(ccr_freq | 0x8000); // We need Fast Mode set
|
||||
} else {
|
||||
// In standard mode, I2C period is 2 * CCR * TPCLK1
|
||||
ccr_freq = (APB1clk1 / 2 / i2cClockSpeed); // Should be 225 for 45Mhz APB1 clock
|
||||
s->CCR |= (uint16_t)ccr_freq;
|
||||
}
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
@@ -136,32 +156,51 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_init()
|
||||
{
|
||||
//Setting up the clocks
|
||||
RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK
|
||||
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
|
||||
// Query the clockspeed from the STM32 HAL layer
|
||||
APB1clk1 = HAL_RCC_GetPCLK1Freq();
|
||||
i2c_MHz = APB1clk1 / 1000000UL;
|
||||
// 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
|
||||
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/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
|
||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
asm("nop"); // wait a bit... suggestion from online!
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
|
||||
// Program the peripheral input clock in CR2 Register in order to generate correct timings
|
||||
s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz
|
||||
// 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;
|
||||
|
||||
// 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
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// Setting NVIC
|
||||
NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities
|
||||
NVIC_EnableIRQ(I2C_IRQn);
|
||||
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);
|
||||
|
||||
// CR2 Interrupt Settings
|
||||
// Bit 15-13: reserved
|
||||
@@ -172,23 +211,25 @@ 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 |= 0x0700; // Enable Buffer, Event and Error interrupts
|
||||
s->CR2 |= 0x0300; // Enable Event and Error interrupts
|
||||
s->CR2 |= (I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable Buffer, Event and Error interrupts
|
||||
#endif
|
||||
|
||||
// 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: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
|
||||
s->CCR = 0x0050;
|
||||
// Bit 11-0: so CCR divisor would be clk / 2 / 100000 (where clk is in Hz)
|
||||
// s->CCR = I2C_PERIPH_CLK * 5;
|
||||
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
s->CCR |= (APB1clk1 / 2 / 100000UL); // i2c_MHz * 5;
|
||||
// s->CCR = i2c_MHz * 5;
|
||||
|
||||
// Configure the rise time register - max allowed in 1000ns
|
||||
s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1
|
||||
// Configure the rise time register - max allowed is 1000ns, so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||
// s->TRISE = I2C_PERIPH_CLK + 1; // 1000 ns / 50 ns = 20 + 1 = 21
|
||||
s->TRISE = i2c_MHz + 1;
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
// Setting bus idle mode and wait for sync
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -198,49 +239,30 @@ 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) {}
|
||||
|
||||
// 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
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* 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
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -252,9 +274,11 @@ 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 (micros() - startTime >= 500UL) break;
|
||||
while ((s->CR1 & I2C_CR1_PE) != 0) {
|
||||
if ((int32_t)(micros() - startTime) >= 500) break;
|
||||
}
|
||||
NVIC_DisableIRQ(I2C1_EV_IRQn);
|
||||
NVIC_DisableIRQ(I2C1_ER_IRQn);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -263,50 +287,217 @@ void I2CManagerClass::I2C_close() {
|
||||
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_handleInterrupt() {
|
||||
volatile uint16_t temp_sr1, temp_sr2;
|
||||
|
||||
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();
|
||||
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;
|
||||
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} 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();
|
||||
}
|
||||
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;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
} 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--;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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 */
|
||||
|
59
IODevice.cpp
59
IODevice.cpp
@@ -63,15 +63,31 @@ void IODevice::begin() {
|
||||
if (exrailHalSetup)
|
||||
exrailHalSetup();
|
||||
|
||||
// Predefine two PCA9685 modules 0x40-0x41
|
||||
// Predefine two PCA9685 modules 0x40-0x41 if no conflicts
|
||||
// Allocates 32 pins 100-131
|
||||
PCA9685::create(100, 16, 0x40);
|
||||
PCA9685::create(116, 16, 0x41);
|
||||
if (checkNoOverlap(100, 16, 0x40)) {
|
||||
PCA9685::create(100, 16, 0x40);
|
||||
} else {
|
||||
DIAG(F("Default PCA9685 at I2C 0x40 disabled due to configured user device"));
|
||||
}
|
||||
if (checkNoOverlap(116, 16, 0x41)) {
|
||||
PCA9685::create(116, 16, 0x41);
|
||||
} else {
|
||||
DIAG(F("Default PCA9685 at I2C 0x41 disabled due to configured user device"));
|
||||
}
|
||||
|
||||
// Predefine two MCP23017 module 0x20/0x21
|
||||
// Predefine two MCP23017 module 0x20/0x21 if no conflicts
|
||||
// Allocates 32 pins 164-195
|
||||
MCP23017::create(164, 16, 0x20);
|
||||
MCP23017::create(180, 16, 0x21);
|
||||
if (checkNoOverlap(164, 16, 0x20)) {
|
||||
MCP23017::create(164, 16, 0x20);
|
||||
} else {
|
||||
DIAG(F("Default MCP23017 at I2C 0x20 disabled due to configured user device"));
|
||||
}
|
||||
if (checkNoOverlap(180, 16, 0x21)) {
|
||||
MCP23017::create(180, 16, 0x21);
|
||||
} else {
|
||||
DIAG(F("Default MCP23017 at I2C 0x21 disabled due to configured user device"));
|
||||
}
|
||||
}
|
||||
|
||||
// reset() function to reinitialise all devices
|
||||
@@ -160,6 +176,13 @@ 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);
|
||||
@@ -169,7 +192,7 @@ bool IODevice::hasCallback(VPIN vpin) {
|
||||
|
||||
// Display (to diagnostics) details of the device.
|
||||
void IODevice::_display() {
|
||||
DIAG(F("Unknown device Vpins:%d-%d %S"),
|
||||
DIAG(F("Unknown device Vpins:%u-%u %S"),
|
||||
(int)_firstVpin, (int)_firstVpin+_nPins-1, _deviceState==DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
@@ -179,7 +202,7 @@ bool IODevice::configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
|
||||
IODevice *dev = findDevice(vpin);
|
||||
if (dev) return dev->_configure(vpin, configType, paramCount, params);
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::configure(): Vpin ID %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::configure(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -191,7 +214,7 @@ int IODevice::read(VPIN vpin) {
|
||||
return dev->_read(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::read(): Vpin %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::read(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
@@ -203,7 +226,7 @@ int IODevice::readAnalogue(VPIN vpin) {
|
||||
return dev->_readAnalogue(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::readAnalogue(): Vpin %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::readAnalogue(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
return -1023;
|
||||
}
|
||||
@@ -213,7 +236,7 @@ int IODevice::configureAnalogIn(VPIN vpin) {
|
||||
return dev->_configureAnalogIn(vpin);
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::configureAnalogIn(): Vpin %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::configureAnalogIn(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
return -1023;
|
||||
}
|
||||
@@ -227,7 +250,7 @@ void IODevice::write(VPIN vpin, int value) {
|
||||
return;
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::write(): Vpin ID %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::write(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -246,7 +269,7 @@ void IODevice::writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t para
|
||||
return;
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IODevice::writeAnalogue(): Vpin ID %d not found!"), (int)vpin);
|
||||
DIAG(F("IODevice::writeAnalogue(): VPIN %u not found!"), (int)vpin);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -314,9 +337,11 @@ IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
|
||||
|
||||
// Private helper function to check for vpin overlap. Run during setup only.
|
||||
// returns true if pins DONT overlap with existing device
|
||||
// TODO: Move the I2C address reservation and checks into the I2CManager code.
|
||||
// That will enable non-HAL devices to reserve I2C addresses too.
|
||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Check no overlap %d %d %s"), firstPin,nPins,i2cAddress.toString());
|
||||
DIAG(F("Check no overlap %u %u %s"), firstPin,nPins,i2cAddress.toString());
|
||||
#endif
|
||||
VPIN lastPin=firstPin+nPins-1;
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
@@ -327,7 +352,7 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddres
|
||||
VPIN lastDevPin=firstDevPin+dev->_nPins-1;
|
||||
bool noOverlap= firstPin>lastDevPin || lastPin<firstDevPin;
|
||||
if (!noOverlap) {
|
||||
DIAG(F("WARNING HAL Overlap definition of pins %d to %d ignored."),
|
||||
DIAG(F("WARNING HAL Overlap, redefinition of Vpins %u to %u ignored."),
|
||||
firstPin, lastPin);
|
||||
return false;
|
||||
}
|
||||
@@ -374,7 +399,7 @@ void IODevice::begin() { DIAG(F("NO HAL CONFIGURED!")); }
|
||||
bool IODevice::configure(VPIN pin, ConfigTypeEnum configType, int nParams, int p[]) {
|
||||
if (configType!=CONFIGURE_INPUT || nParams!=1 || pin >= NUM_DIGITAL_PINS) return false;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Arduino _configurePullup Pin:%d Val:%d"), pin, p[0]);
|
||||
DIAG(F("Arduino _configurePullup pin:%d Val:%d"), pin, p[0]);
|
||||
#endif
|
||||
pinMode(pin, p[0] ? INPUT_PULLUP : INPUT);
|
||||
return true;
|
||||
@@ -528,7 +553,7 @@ int ArduinoPins::_configureAnalogIn(VPIN vpin) {
|
||||
}
|
||||
|
||||
void ArduinoPins::_display() {
|
||||
DIAG(F("Arduino Vpins:%d-%d"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
|
||||
DIAG(F("Arduino Vpins:%u-%u"), (int)_firstVpin, (int)_firstVpin+_nPins-1);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
83
IODevice.h
83
IODevice.h
@@ -27,12 +27,6 @@
|
||||
// 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
|
||||
@@ -160,6 +154,9 @@ 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
|
||||
@@ -383,6 +380,7 @@ private:
|
||||
uint8_t *_pinInUse;
|
||||
};
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for EX-Turntable.
|
||||
@@ -411,10 +409,14 @@ 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
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -467,6 +469,75 @@ protected:
|
||||
}
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// This HAL device driver is intended for communication in automation
|
||||
// sequences. A VPIN can be SET or RESET within a sequence, and its
|
||||
// current state checked elsewhere using IF, IFNOT, AT etc. or monitored
|
||||
// from JMRI using a Sensor object (DCC-EX <S ...> command).
|
||||
// Alternatively, the flag can be set from JMRI and other interfaces
|
||||
// using the <Z ...> command, to enable or disable actions within a sequence.
|
||||
//
|
||||
// Example of configuration in halSetup.h:
|
||||
//
|
||||
// FLAGS::create(32000, 128);
|
||||
//
|
||||
// or in myAutomation.h:
|
||||
//
|
||||
// HAL(FLAGS, 32000, 128);
|
||||
//
|
||||
// Both create 128 flags numbered with VPINs 32000-32127.
|
||||
//
|
||||
//
|
||||
|
||||
class FLAGS : IODevice {
|
||||
private:
|
||||
uint8_t *_states = NULL;
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, unsigned int nPins) {
|
||||
if (checkNoOverlap(firstVpin, nPins))
|
||||
new FLAGS(firstVpin, nPins);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor performs static initialisation of the device object
|
||||
FLAGS (VPIN firstVpin, int nPins) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_states = (uint8_t *)calloc(1, (_nPins+7)/8);
|
||||
if (!_states) {
|
||||
DIAG(F("FLAGS: ERROR Memory Allocation Failure"));
|
||||
return;
|
||||
}
|
||||
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
if (pin >= _nPins || pin < 0) return 0;
|
||||
uint8_t mask = 1 << (pin & 7);
|
||||
return (_states[pin>>3] & mask) ? 1 : 0;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
if (pin >= _nPins || pin < 0) return;
|
||||
uint8_t mask = 1 << (pin & 7);
|
||||
if (value)
|
||||
_states[pin>>3] |= mask;
|
||||
else
|
||||
_states[pin>>3] &= ~mask;
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("FLAGS configured on VPINs %u-%u"),
|
||||
_firstVpin, _firstVpin+_nPins-1);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#include "IO_MCP23008.h"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "IO_PCF8574.h"
|
||||
|
@@ -119,7 +119,7 @@ private:
|
||||
case STATE_GETVALUE:
|
||||
_value[_currentPin] = ((uint16_t)_inBuffer[0] << 8) + (uint16_t)_inBuffer[1];
|
||||
#ifdef IO_ANALOGUE_SLOW
|
||||
DIAG(F("ADS111x pin:%d value:%d"), _currentPin, _value[_currentPin]);
|
||||
DIAG(F("ADS111x VPIN:%u value:%d"), _currentPin, _value[_currentPin]);
|
||||
#endif
|
||||
|
||||
// Move to next pin
|
||||
@@ -142,7 +142,7 @@ private:
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("ADS111x I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
|
||||
DIAG(F("ADS111x I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
@@ -62,7 +62,7 @@ void DCCAccessoryDecoder::_write(VPIN id, int state) {
|
||||
|
||||
void DCCAccessoryDecoder::_display() {
|
||||
int endAddress = _packedAddress + _nPins - 1;
|
||||
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%d-%d Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
||||
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%u-%u Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
||||
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
|
||||
}
|
||||
|
||||
|
165
IO_DFPlayer.h
165
IO_DFPlayer.h
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022, Neil McKechnie. All rights reserved.
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
@@ -33,10 +33,13 @@
|
||||
* and Serialn is the name of the Serial port connected to the DFPlayer (e.g. Serial1).
|
||||
*
|
||||
* Example:
|
||||
* In mySetup function within mySetup.cpp:
|
||||
* In halSetup function within myHal.cpp:
|
||||
* DFPlayer::create(3500, 5, Serial1);
|
||||
* or in myAutomation.h:
|
||||
* HAL(DFPlayer, 3500, 5, Serial1)
|
||||
*
|
||||
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the SD card;
|
||||
* Writing an analogue value 1-2999 to the first pin (3500) will play the numbered file from the
|
||||
* SD card; e.g. a value of 1 will play the first file, 2 for the second file etc.
|
||||
* Writing an analogue value 0 to the first pin (3500) will stop the file playing;
|
||||
* Writing an analogue value 0-30 to the second pin (3501) will set the volume;
|
||||
* Writing a digital value of 1 to a pin will play the file corresponding to that pin, e.g.
|
||||
@@ -61,6 +64,10 @@
|
||||
* card (as listed by the DIR command in Windows). This may not match the order of the files
|
||||
* as displayed by Windows File Manager, which sorts the file names. It is suggested that
|
||||
* files be copied into an empty SDcard in the desired order, one at a time.
|
||||
*
|
||||
* The driver now polls the device for its current status every second. Should the device
|
||||
* fail to respond it will be marked off-line and its busy indicator cleared, to avoid
|
||||
* lock-ups in automation scripts that are executing for a WAITFOR().
|
||||
*/
|
||||
|
||||
#ifndef IO_DFPlayer_h
|
||||
@@ -74,7 +81,10 @@ private:
|
||||
HardwareSerial *_serial;
|
||||
bool _playing = false;
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Allows timeout processing
|
||||
unsigned long _commandSendTime; // Time (us) that last transmit took place.
|
||||
unsigned long _timeoutTime;
|
||||
uint8_t _recvCMD; // Last received command code byte
|
||||
bool _awaitingResponse = false;
|
||||
uint8_t _requestedVolumeLevel = MAXVOLUME;
|
||||
uint8_t _currentVolume = MAXVOLUME;
|
||||
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
|
||||
@@ -102,80 +112,137 @@ protected:
|
||||
|
||||
// Send a query to the device to see if it responds
|
||||
sendPacket(0x42);
|
||||
_commandSendTime = micros();
|
||||
_timeoutTime = micros() + 5000000UL; // 5 second timeout
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Check for incoming data on _serial, and update busy flag accordingly.
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
while (_serial->available()) {
|
||||
int c = _serial->read();
|
||||
if (c == 0x7E && _inputIndex == 0)
|
||||
_inputIndex = 1;
|
||||
else if ((c==0xFF && _inputIndex==1)
|
||||
|| (c==0x3D && _inputIndex==3)
|
||||
|| (_inputIndex >=4 && _inputIndex <= 8))
|
||||
_inputIndex++;
|
||||
else if (c==0x06 && _inputIndex==2) {
|
||||
// Valid message prefix, so consider the device online
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
_inputIndex++;
|
||||
} else if (c==0xEF && _inputIndex==9) {
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Finished"));
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
_inputIndex = 0;
|
||||
} else
|
||||
_inputIndex = 0; // Unrecognised character sequence, start again!
|
||||
}
|
||||
|
||||
// Check if the initial prompt to device has timed out. Allow 5 seconds
|
||||
if (_deviceState == DEVSTATE_INITIALISING && currentMicros - _commandSendTime > 5000000UL) {
|
||||
// Read responses from device
|
||||
processIncoming();
|
||||
|
||||
// Check if a command sent to device has timed out. Allow 0.5 second for response
|
||||
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) {
|
||||
DIAG(F("DFPlayer device not responding on serial port"));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_awaitingResponse = false;
|
||||
_playing = false;
|
||||
}
|
||||
|
||||
// Send any commands that need to go.
|
||||
processOutgoing(currentMicros);
|
||||
|
||||
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||
}
|
||||
|
||||
// Check for incoming data on _serial, and update busy flag and other state accordingly
|
||||
void processIncoming() {
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
bool ok = false;
|
||||
while (_serial->available()) {
|
||||
int c = _serial->read();
|
||||
switch (_inputIndex) {
|
||||
case 0:
|
||||
if (c == 0x7E) ok = true;
|
||||
break;
|
||||
case 1:
|
||||
if (c == 0xFF) ok = true;
|
||||
break;
|
||||
case 2:
|
||||
if (c== 0x06) ok = true;
|
||||
break;
|
||||
case 3:
|
||||
_recvCMD = c; // CMD byte
|
||||
ok = true;
|
||||
break;
|
||||
case 6:
|
||||
switch (_recvCMD) {
|
||||
case 0x42:
|
||||
// Response to status query
|
||||
_playing = (c != 0);
|
||||
// Mark the device online and cancel timeout
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
_awaitingResponse = false;
|
||||
break;
|
||||
case 0x3d:
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Finished"));
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
// Error code
|
||||
DIAG(F("DFPlayer: Error %d returned from device"), c);
|
||||
_playing = false;
|
||||
break;
|
||||
}
|
||||
ok = true;
|
||||
break;
|
||||
case 4: case 5: case 7: case 8:
|
||||
ok = true; // Skip over these bytes in message.
|
||||
break;
|
||||
case 9:
|
||||
if (c==0xef) {
|
||||
// Message finished
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ok)
|
||||
_inputIndex++; // character as expected, so increment index
|
||||
else
|
||||
_inputIndex = 0; // otherwise reset.
|
||||
}
|
||||
}
|
||||
|
||||
// Send any commands that need to be sent
|
||||
void processOutgoing(unsigned long currentMicros) {
|
||||
|
||||
// When two commands are sent in quick succession, the device will often fail to
|
||||
// execute one. Testing has indicated that a delay of 100ms or more is required
|
||||
// between successive commands to get reliable operation.
|
||||
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
|
||||
if (currentMicros - _commandSendTime > 100000UL) {
|
||||
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||
if (_currentVolume > _requestedVolumeLevel) {
|
||||
// Change volume before changing song if volume is reducing.
|
||||
_currentVolume = _requestedVolumeLevel;
|
||||
sendPacket(0x06, _currentVolume);
|
||||
_commandSendTime = currentMicros;
|
||||
} else if (_requestedSong > 0) {
|
||||
// Change song
|
||||
sendPacket(0x03, _requestedSong);
|
||||
_requestedSong = -1;
|
||||
_commandSendTime = currentMicros;
|
||||
} else if (_requestedSong == 0) {
|
||||
sendPacket(0x0e); // Pause playing
|
||||
sendPacket(0x16); // Stop playing
|
||||
_requestedSong = -1;
|
||||
_commandSendTime = currentMicros;
|
||||
} else if (_currentVolume < _requestedVolumeLevel) {
|
||||
// Change volume after changing song if volume is increasing.
|
||||
_currentVolume = _requestedVolumeLevel;
|
||||
sendPacket(0x06, _currentVolume);
|
||||
_commandSendTime = currentMicros;
|
||||
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
|
||||
// Poll device every second that other commands aren't being sent,
|
||||
// to check if it's still connected and responding.
|
||||
sendPacket(0x42);
|
||||
if (!_awaitingResponse) {
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||
}
|
||||
|
||||
// Write with value 1 starts playing a song. The relative pin number is the file number.
|
||||
// Write with value 0 stops playing.
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value) {
|
||||
// Value 1, start playing
|
||||
@@ -200,10 +267,11 @@ protected:
|
||||
// WriteAnalogue on second pin sets the output volume.
|
||||
//
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: VPIN:%d FileNo:%d Volume:%d"), vpin, value, volume);
|
||||
DIAG(F("DFPlayer: VPIN:%u FileNo:%d Volume:%d"), vpin, value, volume);
|
||||
#endif
|
||||
|
||||
// Validate parameter.
|
||||
@@ -228,11 +296,12 @@ protected:
|
||||
|
||||
// A read on any pin indicates whether the player is still playing.
|
||||
int _read(VPIN) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||
return _playing;
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("DFPlayer Configured on Vpins:%d-%d %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
DIAG(F("DFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
@@ -264,6 +333,8 @@ private:
|
||||
|
||||
// Output the command
|
||||
_serial->write(out, sizeof(out));
|
||||
|
||||
_commandSendTime = micros();
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
|
@@ -20,20 +20,21 @@
|
||||
/*
|
||||
* 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/Turntable-EX) and contains the stepper motor logic.
|
||||
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/EX-Turntable) and contains the stepper motor logic.
|
||||
*
|
||||
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
|
||||
* This device driver sends a step position to EX-Turntable 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);
|
||||
@@ -44,18 +45,21 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -67,15 +71,19 @@ void EXTurntable::_loop(unsigned long currentMicros) {
|
||||
uint8_t readBuffer[1];
|
||||
I2CManager.read(_I2CAddress, readBuffer, 1);
|
||||
_stepperStatus = readBuffer[0];
|
||||
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
|
||||
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
|
||||
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
|
||||
}
|
||||
|
||||
// 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 {
|
||||
@@ -83,6 +91,17 @@ 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.
|
||||
@@ -100,21 +119,25 @@ int EXTurntable::_read(VPIN vpin) {
|
||||
// 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
|
||||
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
|
||||
DIAG(F("EX-Turntable WriteAnalogue VPIN:%u Value:%d Activity:%d Duration:%d"),
|
||||
vpin, value, activity, duration);
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
||||
#endif
|
||||
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
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
|
||||
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
|
||||
}
|
||||
|
||||
// Display Turnetable-EX device driver info.
|
||||
void EXTurntable::_display() {
|
||||
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
@@ -84,7 +84,7 @@ protected:
|
||||
void _write(VPIN vpin, int value) {
|
||||
int pin = vpin -_firstVpin;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value);
|
||||
DIAG(F("IO_ExampleSerial::_write VPIN:%u Value:%d"), (int)vpin, value);
|
||||
#endif
|
||||
// Send a command string over the serial line
|
||||
_serial->print('#');
|
||||
@@ -153,10 +153,10 @@ protected:
|
||||
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
|
||||
// Here we display the current values held for the pins.
|
||||
void _display() {
|
||||
DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin,
|
||||
DIAG(F("IO_ExampleSerial Configured on Vpins:%u-%u"), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1);
|
||||
for (int i=0; i<_nPins; i++)
|
||||
DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]);
|
||||
DIAG(F(" VPin %2u: %d"), _firstVpin+i, _pinValues[i]);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -196,7 +196,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
|
||||
|
||||
template <class T>
|
||||
void GPIOBase<T>::_display() {
|
||||
DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(),
|
||||
DIAG(F("%S I2C:%s Configured on Vpins:%u-%u %S"), _deviceName, _I2CAddress.toString(),
|
||||
_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
@@ -234,7 +234,7 @@ protected:
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("HCSR04 Configured on Vpin:%d TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
|
||||
DIAG(F("HCSR04 Configured on VPIN:%u TrigPin:%d EchoPin:%d On:%dcm Off:%dcm"),
|
||||
_firstVpin, _trigPin, _echoPin, _onThreshold, _offThreshold);
|
||||
}
|
||||
|
||||
|
111
IO_PCA9555.h
Normal file
111
IO_PCA9555.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* © 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
|
@@ -46,7 +46,7 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
|
||||
if (configType != CONFIGURE_SERVO) return false;
|
||||
if (paramCount != 5) return false;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
||||
DIAG(F("PCA9685 Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
||||
vpin, params[0], params[1], params[2], params[3], params[4]);
|
||||
#endif
|
||||
|
||||
@@ -118,7 +118,7 @@ void PCA9685::_begin() {
|
||||
// For this function, the configured profile is used.
|
||||
void PCA9685::_write(VPIN vpin, int value) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||
DIAG(F("PCA9685 Write VPIN:%u Value:%d"), vpin, value);
|
||||
#endif
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value) value = 1;
|
||||
@@ -145,7 +145,7 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||
//
|
||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||
DIAG(F("PCA9685 WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"),
|
||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
@@ -262,7 +262,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
|
||||
|
||||
// Display details of this device.
|
||||
void PCA9685::_display() {
|
||||
DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||
DIAG(F("PCA9685 I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
@@ -121,7 +121,7 @@ private:
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
|
||||
(void)param1; (void)param2; // suppress compiler warning
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"),
|
||||
DIAG(F("PCA9685pwm WriteAnalogue VPIN:%u Value:%d %S"),
|
||||
vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
@@ -134,7 +134,7 @@ private:
|
||||
|
||||
// Display details of this device.
|
||||
void _display() override {
|
||||
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%u-%u %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2023, Peter Cole. All rights reserved.
|
||||
* © 2022, Peter Cole. All rights reserved.
|
||||
*
|
||||
* This file is part of EX-CommandStation
|
||||
@@ -28,9 +29,23 @@
|
||||
* ONCHANGE(vpin) - flag when the rotary encoder position has changed from the previous position
|
||||
* IFRE(vpin, position) - test to see if specified rotary encoder position has been received
|
||||
*
|
||||
* Further to this, feedback can be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
|
||||
* Feedback can also be sent to the rotary encoder by using 2 Vpins, and sending a SET()/RESET() to the second Vpin.
|
||||
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
|
||||
*
|
||||
* In addition, defining a third Vpin will allow a position number to be sent so that when an EXRAIL automation or some other
|
||||
* activity has moved a turntable, the position can be reflected in the rotary encoder software. This can be accomplished
|
||||
* using the EXRAIL SERVO(vpin, position, profile) command, where:
|
||||
* - vpin = the third defined Vpin (any other is ignored)
|
||||
* - position = the defined position in the DCC-EX Rotary Encoder software, 0 (Home) to 255
|
||||
* - profile = Must be defined as per the SERVO() command, but is ignored as it has no relevance
|
||||
*
|
||||
* Defining in myAutomation.h requires the device driver to be included in addition to the HAL() statement. Examples:
|
||||
*
|
||||
* #include "IO_RotaryEncoder.h"
|
||||
* HAL(RotaryEncoder, 700, 1, 0x70) // Define single Vpin, no feedback or position sent to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 2, 0x70) // Define two Vpins, feedback only sent to rotary encoder software
|
||||
* HAL(RotaryEncoder, 700, 3, 0x70) // Define three Vpins, can send feedback and position update to rotary encoder software
|
||||
*
|
||||
* Refer to the documentation for further information including the valid activities and examples.
|
||||
*/
|
||||
|
||||
@@ -44,81 +59,132 @@
|
||||
|
||||
class RotaryEncoder : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
if (_nPins > 3) {
|
||||
_nPins = 3;
|
||||
DIAG(F("RotaryEncoder WARNING:%d vpins defined, only 3 supported"), _nPins);
|
||||
}
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Initiate the device
|
||||
void _begin() {
|
||||
uint8_t _status;
|
||||
// Attempt to initilalise device
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
byte _getVersion[1] = {RE_VER};
|
||||
I2CManager.read(_I2CAddress, _versionBuffer, 3, _getVersion, 1);
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
_buffer[0] = RE_OP;
|
||||
I2CManager.write(_I2CAddress, _buffer, 1);
|
||||
// Send RE_RDY, must receive RE_RDY to be online
|
||||
_sendBuffer[0] = RE_RDY;
|
||||
_status = I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1);
|
||||
if (_status == I2C_STATUS_OK) {
|
||||
if (_rcvBuffer[0] == RE_RDY) {
|
||||
_sendBuffer[0] = RE_VER;
|
||||
if (I2CManager.read(_I2CAddress, _versionBuffer, 3, _sendBuffer, 1) == I2C_STATUS_OK) {
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
}
|
||||
} else {
|
||||
DIAG(F("RotaryEncoder I2C:%s garbage received: %d"), _I2CAddress.toString(), _rcvBuffer[0]);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
DIAG(F("RotaryEncoder I2C:%s ERROR connecting"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
DIAG(F("RotaryEncoder I2C:%s device not found"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
I2CManager.read(_I2CAddress, _buffer, 1);
|
||||
_position = _buffer[0];
|
||||
// This here needs to have a change check, ie. position is a different value.
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
if (_deviceState == DEVSTATE_FAILED) return; // Return if device has failed
|
||||
if (_i2crb.isBusy()) return; // Return if I2C operation still in progress
|
||||
|
||||
if (currentMicros - _lastPositionRead > _positionRefresh) {
|
||||
_lastPositionRead = currentMicros;
|
||||
_sendBuffer[0] = RE_READ;
|
||||
I2CManager.read(_I2CAddress, _rcvBuffer, 1, _sendBuffer, 1, &_i2crb); // Read position from encoder
|
||||
_position = _rcvBuffer[0];
|
||||
// If EXRAIL is active, we need to trigger the ONCHANGE() event handler if it's in use
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
if (_position != _previousPosition) {
|
||||
_previousPosition = _position;
|
||||
RMFT2::changeEvent(_firstVpin,1);
|
||||
RMFT2::changeEvent(_firstVpin, 1);
|
||||
} else {
|
||||
RMFT2::changeEvent(_firstVpin,0);
|
||||
RMFT2::changeEvent(_firstVpin, 0);
|
||||
}
|
||||
#endif
|
||||
delayUntil(currentMicros + 100000);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// Device specific read function
|
||||
// Return the position sent by the rotary encoder software
|
||||
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) {
|
||||
byte _feedbackBuffer[2] = {RE_OP, value};
|
||||
if (value != 0) value = 0x01;
|
||||
byte _feedbackBuffer[2] = {RE_OP, (byte)value};
|
||||
I2CManager.write(_I2CAddress, _feedbackBuffer, 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
byte newPosition = position & 0xFF;
|
||||
byte _positionBuffer[2] = {RE_MOVE, newPosition};
|
||||
I2CManager.write(_I2CAddress, _positionBuffer, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on Vpin:%d-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||
DIAG(F("Rotary Encoder I2C:%s v%d.%d.%d Configured on VPIN:%u-%d %S"), _I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
int8_t _position;
|
||||
int8_t _previousPosition = 0;
|
||||
uint8_t _versionBuffer[3];
|
||||
uint8_t _buffer[1];
|
||||
uint8_t _sendBuffer[1];
|
||||
uint8_t _rcvBuffer[1];
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
I2CRB _i2crb;
|
||||
unsigned long _lastPositionRead = 0;
|
||||
const unsigned long _positionRefresh = 100000UL; // Delay refreshing position for 100ms
|
||||
|
||||
enum {
|
||||
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
|
||||
RE_OP = 0xA1, // Flag for normal operation
|
||||
RE_RDY = 0xA0, // Flag to check if encoder is ready for operation
|
||||
RE_VER = 0xA1, // Flag to retrieve rotary encoder software version
|
||||
RE_READ = 0xA2, // Flag to read the current position of the encoder
|
||||
RE_OP = 0xA3, // Flag for operation start/end, sent to when sending feedback on move start/end
|
||||
RE_MOVE = 0xA4, // Flag for sending a position update from the device driver to the encoder
|
||||
};
|
||||
|
||||
};
|
||||
|
12
IO_Servo.h
12
IO_Servo.h
@@ -98,7 +98,7 @@ private:
|
||||
if (configType != CONFIGURE_SERVO) return false;
|
||||
if (paramCount != 5) return false;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Servo: Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
||||
DIAG(F("Servo: Configure VPIN:%u Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
||||
vpin, params[0], params[1], params[2], params[3], params[4]);
|
||||
#endif
|
||||
|
||||
@@ -140,12 +140,12 @@ private:
|
||||
// Get reference to slave device.
|
||||
_slaveDevice = findDevice(_firstSlavePin);
|
||||
if (!_slaveDevice) {
|
||||
DIAG(F("Servo: Slave device not found on pins %d-%d"),
|
||||
DIAG(F("Servo: Slave device not found on Vpins %u-%u"),
|
||||
_firstSlavePin, _firstSlavePin+_nPins-1);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
if (_slaveDevice != findDevice(_firstSlavePin+_nPins-1)) {
|
||||
DIAG(F("Servo: Slave device does not cover all pins %d-%d"),
|
||||
DIAG(F("Servo: Slave device does not cover all Vpins %u-%u"),
|
||||
_firstSlavePin, _firstSlavePin+_nPins-1);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
@@ -165,7 +165,7 @@ private:
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Servo Write Vpin:%d Value:%d"), vpin, value);
|
||||
DIAG(F("Servo Write VPIN:%u Value:%d"), vpin, value);
|
||||
#endif
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value) value = 1;
|
||||
@@ -193,7 +193,7 @@ private:
|
||||
//
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Servo: WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||
DIAG(F("Servo: WriteAnalogue VPIN:%u Value:%d Profile:%d Duration:%d %S"),
|
||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
@@ -288,7 +288,7 @@ private:
|
||||
|
||||
// Display details of this device.
|
||||
void _display() override {
|
||||
DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"),
|
||||
DIAG(F("Servo Configured on Vpins:%u-%u, slave pins:%d-%d %S"),
|
||||
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
||||
(int)_firstSlavePin, (int)_firstSlavePin+_nPins-1,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
|
@@ -124,7 +124,7 @@ protected:
|
||||
|
||||
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
|
||||
void _display() {
|
||||
DIAG(F("TouchKeypad Configured on VPins:%d-%d SCL=%d SDO=%d"), (int)_firstVpin,
|
||||
DIAG(F("TouchKeypad Configured on Vpins:%u-%u SCL=%d SDO=%d"), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _clockPin, _dataPin);
|
||||
}
|
||||
|
||||
|
@@ -319,7 +319,7 @@ protected:
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"),
|
||||
DIAG(F("VL53L0X I2C:%s Configured on Vpins:%u-%u On:%dmm Off:%dmm %S"),
|
||||
_I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
@@ -55,6 +55,7 @@ public:
|
||||
pinMode(_clockPin,OUTPUT);
|
||||
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
|
||||
_display();
|
||||
if (!_pinMap) _loopOutput();
|
||||
}
|
||||
|
||||
// loop called by HAL supervisor
|
||||
@@ -121,7 +122,7 @@ void _loopOutput() {
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
|
||||
DIAG(F("IO_duinoNodes %SPUT Configured on Vpins:%u-%u shift=%d"),
|
||||
_pinMap?F("IN"):F("OUT"),
|
||||
(int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
|
||||
|
394
MotorDriver.cpp
394
MotorDriver.cpp
@@ -1,9 +1,10 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022-2023 Paul M Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -26,20 +27,18 @@
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "ESP32-fixes.h"
|
||||
#endif
|
||||
|
||||
bool MotorDriver::commonFaultPin=false;
|
||||
unsigned long MotorDriver::globalOverloadStart = 0;
|
||||
|
||||
volatile portreg_t shadowPORTA;
|
||||
volatile portreg_t shadowPORTB;
|
||||
volatile portreg_t shadowPORTC;
|
||||
|
||||
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, byte fault_pin) {
|
||||
powerPin=power_pin;
|
||||
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) {
|
||||
const FSH * warnString = F("** WARNING **");
|
||||
|
||||
invertPower=power_pin < 0;
|
||||
if (invertPower) {
|
||||
powerPin = 0-power_pin;
|
||||
@@ -95,32 +94,54 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
}
|
||||
else dualSignal=false;
|
||||
|
||||
brakePin=brake_pin;
|
||||
if (brake_pin!=UNUSED_PIN){
|
||||
invertBrake=brake_pin < 0;
|
||||
brakePin=invertBrake ? 0-brake_pin : brake_pin;
|
||||
if (invertBrake)
|
||||
brake_pin = 0-brake_pin;
|
||||
if (brake_pin > MAX_PIN)
|
||||
DIAG(F("%S Brake pin %d > %d"), warnString, brake_pin, MAX_PIN);
|
||||
brakePin=(byte)brake_pin;
|
||||
getFastPin(F("BRAKE"),brakePin,fastBrakePin);
|
||||
// if brake is used for railcom cutout we need to do PORTX register trick here as well
|
||||
pinMode(brakePin, OUTPUT);
|
||||
setBrake(true); // start with brake on in case we hace DC stuff going on
|
||||
} else {
|
||||
brakePin=UNUSED_PIN;
|
||||
}
|
||||
else brakePin=UNUSED_PIN;
|
||||
|
||||
currentPin=current_pin;
|
||||
if (currentPin!=UNUSED_PIN) ADCee::init(currentPin);
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
int ret = ADCee::init(currentPin);
|
||||
if (ret < -1010) { // XXX give value a name later
|
||||
DIAG(F("ADCee::init error %d, disable current pin %d"), ret, currentPin);
|
||||
currentPin = UNUSED_PIN;
|
||||
}
|
||||
}
|
||||
senseOffset=0; // value can not be obtained until waveform is activated
|
||||
|
||||
faultPin=fault_pin;
|
||||
if (faultPin != UNUSED_PIN) {
|
||||
if (fault_pin != UNUSED_PIN) {
|
||||
invertFault=fault_pin < 0;
|
||||
if (invertFault)
|
||||
fault_pin = 0-fault_pin;
|
||||
if (fault_pin > MAX_PIN)
|
||||
DIAG(F("%S Fault pin %d > %d"), warnString, fault_pin, MAX_PIN);
|
||||
faultPin=(byte)fault_pin;
|
||||
DIAG(F("Fault pin = %d invert %d"), faultPin, invertFault);
|
||||
getFastPin(F("FAULT"),faultPin, 1 /*input*/, fastFaultPin);
|
||||
pinMode(faultPin, INPUT);
|
||||
} else {
|
||||
faultPin=UNUSED_PIN;
|
||||
}
|
||||
|
||||
// This conversion performed at compile time so the remainder of the code never needs
|
||||
// float calculations or libraray code.
|
||||
senseFactorInternal=sense_factor * senseScale;
|
||||
tripMilliamps=trip_milliamps;
|
||||
rawCurrentTripValue=mA2raw(trip_milliamps);
|
||||
#ifdef MAX_CURRENT
|
||||
if (MAX_CURRENT > 0 && MAX_CURRENT < tripMilliamps)
|
||||
tripMilliamps = MAX_CURRENT;
|
||||
#endif
|
||||
rawCurrentTripValue=mA2raw(tripMilliamps);
|
||||
|
||||
if (rawCurrentTripValue + senseOffset > ADCee::ADCmax()) {
|
||||
// This would mean that the values obtained from the ADC never
|
||||
@@ -135,20 +156,16 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
}
|
||||
|
||||
if (currentPin==UNUSED_PIN)
|
||||
DIAG(F("** WARNING ** No current or short detection"));
|
||||
DIAG(F("%S No current or short detection"), warnString);
|
||||
else {
|
||||
DIAG(F("Track %c, TripValue=%d"), trackLetter, rawCurrentTripValue);
|
||||
DIAG(F("Pin %d Max %dmA (%d)"), currentPin, raw2mA(rawCurrentTripValue), rawCurrentTripValue);
|
||||
|
||||
// self testing diagnostic for the non-float converters... may be removed when happy
|
||||
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
|
||||
// senseFactorInternal, raw2mA(1000),mA2raw(1000));
|
||||
}
|
||||
|
||||
// prepare values for current detection
|
||||
sampleDelay = 0;
|
||||
lastSampleTaken = millis();
|
||||
progTripValue = mA2raw(TRIP_CURRENT_PROG);
|
||||
|
||||
}
|
||||
|
||||
bool MotorDriver::isPWMCapable() {
|
||||
@@ -157,7 +174,12 @@ bool MotorDriver::isPWMCapable() {
|
||||
|
||||
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::ON;
|
||||
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);
|
||||
if (on) {
|
||||
// when switching a track On, we need to check the crrentOffset with the pin OFF
|
||||
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
|
||||
@@ -197,8 +219,8 @@ bool MotorDriver::canMeasureCurrent() {
|
||||
return currentPin!=UNUSED_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.
|
||||
* 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.
|
||||
* 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
|
||||
@@ -210,12 +232,17 @@ int MotorDriver::getCurrentRaw(bool fromISR) {
|
||||
(void)fromISR;
|
||||
if (currentPin==UNUSED_PIN) return 0;
|
||||
int current;
|
||||
current = ADCee::read(currentPin, fromISR)-senseOffset;
|
||||
current = ADCee::read(currentPin, fromISR);
|
||||
// here one can diag raw value
|
||||
// if (fromISR == false) DIAG(F("%c: %d"), trackLetter, current);
|
||||
current = current-senseOffset; // adjust with offset
|
||||
if (current<0) current=0-current;
|
||||
if ((faultPin != UNUSED_PIN) && isLOW(fastFaultPin) && powerMode==POWERMODE::ON)
|
||||
// current >= 0 here, we use negative current as fault pin flag
|
||||
if ((faultPin != UNUSED_PIN) && powerPin) {
|
||||
if (invertFault ? isHIGH(fastFaultPin) : isLOW(fastFaultPin))
|
||||
return (current == 0 ? -1 : -current);
|
||||
}
|
||||
return current;
|
||||
|
||||
}
|
||||
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
@@ -253,6 +280,7 @@ void MotorDriver::startCurrentFromHW() {
|
||||
#endif //ANALOG_READ_INTERRUPT
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#ifdef VARIABLE_TONES
|
||||
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
247, 262, 294, 330,
|
||||
349, 392, 440, 494,
|
||||
@@ -261,16 +289,43 @@ 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)
|
||||
TCCR2B = (TCCR2B & B11111000) | B00000110; // set divisor on timer 2 to result in (approx) 122.55Hz
|
||||
// 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)
|
||||
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
|
||||
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;
|
||||
#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;
|
||||
@@ -278,12 +333,14 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
{
|
||||
int f = 131;
|
||||
#ifdef VARIABLE_TONES
|
||||
if (tSpeed > 2) {
|
||||
if (tSpeed <= 58) {
|
||||
f = taurustones[ (tSpeed-2)/2 ] ;
|
||||
}
|
||||
}
|
||||
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
#endif
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
}
|
||||
#endif
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
@@ -292,7 +349,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
DCCEXanalogWrite(brakePin,brake);
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
#endif
|
||||
@@ -321,7 +378,60 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
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);
|
||||
}
|
||||
#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;
|
||||
@@ -350,64 +460,172 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
||||
// 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) {
|
||||
if (millis() - lastSampleTaken < sampleDelay) return;
|
||||
lastSampleTaken = millis();
|
||||
int tripValue= useProgLimit?progTripValue:getRawCurrentTripValue();
|
||||
|
||||
// Trackname for diag messages later
|
||||
|
||||
switch (powerMode) {
|
||||
case POWERMODE::OFF:
|
||||
sampleDelay = POWER_SAMPLE_OFF_WAIT;
|
||||
break;
|
||||
case POWERMODE::ON:
|
||||
// Check current
|
||||
lastCurrent=getCurrentRaw();
|
||||
if (lastCurrent < 0) {
|
||||
// We have a fault pin condition to take care of
|
||||
lastCurrent = -lastCurrent;
|
||||
setPower(POWERMODE::OVERLOAD); // Turn off, decide later how fast to turn on again
|
||||
if (commonFaultPin) {
|
||||
if (lastCurrent < tripValue) {
|
||||
setPower(POWERMODE::ON); // maybe other track
|
||||
}
|
||||
// Write this after the fact as we want to turn on as fast as possible
|
||||
// because we don't know which output actually triggered the fault pin
|
||||
DIAG(F("COMMON FAULT PIN ACTIVE: POWERTOGGLE TRACK %c"), trackno + 'A');
|
||||
} else {
|
||||
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
}
|
||||
}
|
||||
if (lastCurrent < tripValue) {
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
if(power_good_counter<100)
|
||||
power_good_counter++;
|
||||
else
|
||||
if (power_sample_overload_wait>POWER_SAMPLE_OVERLOAD_WAIT) power_sample_overload_wait=POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
|
||||
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 {
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
unsigned int maxmA=raw2mA(tripValue);
|
||||
power_good_counter=0;
|
||||
sampleDelay = power_sample_overload_wait;
|
||||
DIAG(F("TRACK %c POWER OVERLOAD %dmA (limit %dmA) shutdown for %dms"), trackno + 'A', mA, maxmA, sampleDelay);
|
||||
if (power_sample_overload_wait >= 10000)
|
||||
power_sample_overload_wait = 10000;
|
||||
else
|
||||
power_sample_overload_wait *= 2;
|
||||
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
|
||||
}
|
||||
setPower(POWERMODE::ALERT);
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
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);
|
||||
}
|
||||
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);
|
||||
sampleDelay = POWER_SAMPLE_ON_WAIT;
|
||||
// Debug code....
|
||||
DIAG(F("TRACK %c POWER RESTORE (check %dms)"), trackno + 'A', sampleDelay);
|
||||
break;
|
||||
default:
|
||||
sampleDelay = 999; // cant get here..meaningless statement to avoid compiler warning.
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case POWERMODE::OVERLOAD: {
|
||||
lastPowerMode = POWERMODE::OVERLOAD;
|
||||
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange(POWERMODE::OVERLOAD));
|
||||
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 on test
|
||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||
setPower(POWERMODE::ALERT);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -27,6 +27,10 @@
|
||||
#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};
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
#define isHIGH(fastpin) (*fastpin.inout & fastpin.maskHIGH)
|
||||
@@ -74,8 +78,9 @@
|
||||
// Virtualised Motor shield 1-track hardware Interface
|
||||
|
||||
#ifndef UNUSED_PIN // sync define with the one in MotorDrivers.h
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#define UNUSED_PIN 255 // inside uint8_t
|
||||
#endif
|
||||
#define MAX_PIN 254
|
||||
|
||||
class pinpair {
|
||||
public:
|
||||
@@ -106,13 +111,13 @@ extern volatile portreg_t shadowPORTA;
|
||||
extern volatile portreg_t shadowPORTB;
|
||||
extern volatile portreg_t shadowPORTC;
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
|
||||
|
||||
class MotorDriver {
|
||||
public:
|
||||
|
||||
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int8_t brake_pin,
|
||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, byte faultPin);
|
||||
MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
|
||||
byte current_pin, float senseFactor, unsigned int tripMilliamps, int16_t fault_pin);
|
||||
void setPower( POWERMODE mode);
|
||||
POWERMODE getPower() { return powerMode;}
|
||||
// as the port registers can be shadowed to get syncronized DCC signals
|
||||
@@ -144,6 +149,7 @@ 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);
|
||||
@@ -174,7 +180,10 @@ class MotorDriver {
|
||||
bool isPWMCapable();
|
||||
bool canMeasureCurrent();
|
||||
bool trackPWM = false; // this track uses PWM timer to generate the DCC waveform
|
||||
static bool commonFaultPin; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
bool commonFaultPin = false; // This is a stupid motor shield which has only a common fault pin for both outputs
|
||||
inline byte setCommonFaultPin() {
|
||||
return commonFaultPin = true;
|
||||
}
|
||||
inline byte getFaultPin() {
|
||||
return faultPin;
|
||||
}
|
||||
@@ -185,23 +194,53 @@ class MotorDriver {
|
||||
inline void setTrackLetter(char c) {
|
||||
trackLetter = c;
|
||||
};
|
||||
// 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) {
|
||||
unsigned long now = micros();
|
||||
unsigned long diff = now - lastPowerChange[(int)mode];
|
||||
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
|
||||
lastPowerChange[(int)mode] = now - 30000000UL; // 30 seconds ago
|
||||
return diff;
|
||||
};
|
||||
#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);
|
||||
void getFastPin(const FSH* type,int pin, FASTPIN & result) {
|
||||
inline 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;
|
||||
bool dualSignal; // true to use signalPin2
|
||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||
bool invertPower; // power pin passed as negative means pin is inverted
|
||||
bool invertFault; // fault pin passed as negative means pin is inverted
|
||||
|
||||
// Raw to milliamp conversion factors avoiding float data types.
|
||||
// Milliamps=rawADCreading * sensefactorInternal / senseScale
|
||||
@@ -215,10 +254,14 @@ class MotorDriver {
|
||||
int rawCurrentTripValue;
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
POWERMODE lastPowerMode;
|
||||
unsigned long lastPowerChange[4]; // timestamp in microseconds
|
||||
unsigned long lastBadSample; // timestamp in microseconds
|
||||
// used to sync restore time when common Fault pin detected
|
||||
static unsigned long globalOverloadStart; // timestamp in microseconds
|
||||
int progTripValue;
|
||||
int lastCurrent;
|
||||
int lastCurrent; //temp value
|
||||
int tripValue; //temp value
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
volatile unsigned long sampleCurrentTimestamp;
|
||||
volatile uint16_t sampleCurrent;
|
||||
@@ -226,16 +269,28 @@ class MotorDriver {
|
||||
int maxmA;
|
||||
int tripmA;
|
||||
|
||||
// Wait times for power management. Unit: milliseconds
|
||||
static const int POWER_SAMPLE_ON_WAIT = 100;
|
||||
static const int POWER_SAMPLE_OFF_WAIT = 1000;
|
||||
static const int POWER_SAMPLE_OVERLOAD_WAIT = 20;
|
||||
// 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;
|
||||
// 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;
|
||||
// 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;
|
||||
// Upper limit for retry period
|
||||
static const unsigned long POWER_SAMPLE_RETRY_MAX = 10000000UL;
|
||||
|
||||
// Trip current for programming track, 250mA. Change only if you really
|
||||
// need to be non-NMRA-compliant because of decoders that are not either.
|
||||
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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2023 Harald Barth
|
||||
* (c) 2020 Chris Harlow. All rights reserved.
|
||||
* (c) 2021 Fred Decker. All rights reserved.
|
||||
* (c) 2020 Harald Barth. All rights reserved.
|
||||
@@ -36,7 +36,7 @@
|
||||
// custom defines in config.h.
|
||||
|
||||
#ifndef UNUSED_PIN // sync define with the one in MotorDriver.h
|
||||
#define UNUSED_PIN 127 // inside int8_t
|
||||
#define UNUSED_PIN 255 // inside uint8_t
|
||||
#endif
|
||||
|
||||
// The MotorDriver definition is:
|
||||
@@ -60,7 +60,8 @@
|
||||
// Arduino STANDARD Motor Shield, used on different architectures:
|
||||
|
||||
#if defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
|
||||
// Setup for SAMD21 Sparkfun DEV board using Arduino standard Motor Shield R3 (MUST be R3
|
||||
// Standard Motor Shield definition for 3v3 processors (other than the ESP32)
|
||||
// Setup for SAMD21 Sparkfun DEV board MUST use Arduino Motor Shield R3 (MUST be R3
|
||||
// for 3v3 compatibility!!) senseFactor for 3.3v systems is 1.95 as calculated when using
|
||||
// 10-bit A/D samples, and for 12-bit samples it's more like 0.488, but we probably need
|
||||
// to tweak both these
|
||||
@@ -70,15 +71,27 @@
|
||||
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
|
||||
#define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
|
||||
|
||||
// EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, A5)
|
||||
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
|
||||
// 3.3V compatible R3 version or it has to be modified to not supply more than 3.3V to the
|
||||
// analog inputs. Here we use analog inputs A4 and A5 as A0 and A1 are wired in a way so that
|
||||
// analog inputs. Here we use analog inputs A2 and A3 as A0 and A1 are wired in a way so that
|
||||
// they are not useable at the same time as WiFi (what a bummer). The numbers below are the
|
||||
// actual GPIO numbers. In comments the numbers the pins have on an Uno.
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 36/*A4*/, 0.70, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 39/*A5*/, 0.70, 1500, UNUSED_PIN)
|
||||
#define STANDARD_MOTOR_SHIELD F("STANDARD_MOTOR_SHIELD"), \
|
||||
new MotorDriver(25/* 3*/, 19/*12*/, UNUSED_PIN, 13/*9*/, 35/*A2*/, 0.70, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(23/*11*/, 18/*13*/, UNUSED_PIN, 12/*8*/, 34/*A3*/, 0.70, 1500, UNUSED_PIN)
|
||||
|
||||
// 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"),\
|
||||
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*/)
|
||||
|
||||
#else
|
||||
// STANDARD shield on any Arduino Uno or Mega compatible with the original specification.
|
||||
@@ -88,6 +101,12 @@
|
||||
#define BRAKE_PWM_SWAPPED_MOTOR_SHIELD F("BPS_MOTOR_SHIELD"), \
|
||||
new MotorDriver(-9 , 12, UNUSED_PIN, -3, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(-8 , 13, UNUSED_PIN,-11, A1, 2.99, 1500, UNUSED_PIN)
|
||||
|
||||
// EX 8874 based shield connected to a 5V system (like Arduino) and 10bit (1024) ADC
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 5000, A4), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 5000, A5)
|
||||
|
||||
#endif
|
||||
|
||||
// Pololu Motor Shield
|
||||
|
@@ -87,6 +87,9 @@ void SerialManager::init() {
|
||||
delay(1000);
|
||||
}
|
||||
#endif
|
||||
#ifdef SABERTOOTH
|
||||
Serial2.begin(9600, SERIAL_8N1, 16, 17); // GPIO 16 RXD2; GPIO 17 TXD2 on ESP32
|
||||
#endif
|
||||
}
|
||||
|
||||
void SerialManager::broadcast(char * stringBuffer) {
|
||||
|
@@ -117,6 +117,24 @@ 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 'M':
|
||||
{ // this prints a unsigned long microseconds time in readable format
|
||||
unsigned long time = va_arg(args, long);
|
||||
if (time >= 2000) {
|
||||
time = time / 1000;
|
||||
if (time >= 2000) {
|
||||
printPadded(stream, time/1000, formatWidth, formatLeft);
|
||||
stream->print(F("sec"));
|
||||
} else {
|
||||
printPadded(stream,time, formatWidth, formatLeft);
|
||||
stream->print(F("msec"));
|
||||
}
|
||||
} else {
|
||||
printPadded(stream,time, formatWidth, formatLeft);
|
||||
stream->print(F("usec"));
|
||||
}
|
||||
}
|
||||
break;
|
||||
//case 'f': stream->print(va_arg(args, double), 2); break;
|
||||
//format width prefix
|
||||
case '-':
|
||||
|
123
TrackManager.cpp
123
TrackManager.cpp
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* © 2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
@@ -31,19 +32,20 @@
|
||||
|
||||
#define APPLY_BY_MODE(findmode,function) \
|
||||
FOR_EACH_TRACK(t) \
|
||||
if (trackMode[t]==findmode) \
|
||||
if (track[t]->getMode()==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;
|
||||
@@ -52,7 +54,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;
|
||||
byte TrackManager::tempProgTrack=MAX_TRACKS+1; // MAX_TRACKS+1 is the unused flag
|
||||
#endif
|
||||
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
@@ -73,7 +75,7 @@ void TrackManager::sampleCurrent() {
|
||||
waiting = false;
|
||||
tr++;
|
||||
if (tr > lastTrack) tr = 0;
|
||||
if (lastTrack < 2 || trackMode[tr] & TRACK_MODE_PROG) {
|
||||
if (lastTrack < 2 || track[tr]->getMode() & 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.
|
||||
@@ -84,7 +86,7 @@ void TrackManager::sampleCurrent() {
|
||||
if (!waiting) {
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (trackMode[tr] & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
|
||||
if (track[tr]->getMode() & ( 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;
|
||||
@@ -116,19 +118,31 @@ void TrackManager::Setup(const FSH * shieldname,
|
||||
|
||||
// Default the first 2 tracks (which may be null) and perform HA waveform check.
|
||||
setTrackMode(0,TRACK_MODE_MAIN);
|
||||
#ifndef DISABLE_PROG
|
||||
setTrackMode(1,TRACK_MODE_PROG);
|
||||
#else
|
||||
setTrackMode(1,TRACK_MODE_MAIN);
|
||||
#endif
|
||||
|
||||
// TODO Fault pin config for odd motor boards (example pololu)
|
||||
// MotorDriver::commonFaultPin = ((mainDriver->getFaultPin() == progDriver->getFaultPin())
|
||||
// && (mainDriver->getFaultPin() != UNUSED_PIN));
|
||||
DCC::begin(shieldname);
|
||||
// Fault pin config for odd motor boards (example pololu)
|
||||
FOR_EACH_TRACK(t) {
|
||||
for (byte s=t+1;s<=lastTrack;s++) {
|
||||
if (track[t]->getFaultPin() != UNUSED_PIN &&
|
||||
track[t]->getFaultPin() == track[s]->getFaultPin()) {
|
||||
track[t]->setCommonFaultPin();
|
||||
track[s]->setCommonFaultPin();
|
||||
DIAG(F("Common Fault pin tracks %c and %c"), t+'A', s+'A');
|
||||
}
|
||||
}
|
||||
}
|
||||
DCC::setShieldName(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;
|
||||
}
|
||||
@@ -169,22 +183,27 @@ 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) continue;
|
||||
if (trackMode[t]==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
||||
else if (trackMode[t]==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr) {
|
||||
if (trackToSet>lastTrack || track[trackToSet]==NULL) return false;
|
||||
|
||||
//DIAG(F("Track=%c"),trackToSet+'A');
|
||||
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
if ((mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)
|
||||
&& !track[trackToSet]->brakeCanPWM()) {
|
||||
DIAG(F("Brake pin can't PWM: No DC"));
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
// remove pin from MUX matrix and turn it off
|
||||
@@ -198,12 +217,16 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
}
|
||||
#endif
|
||||
#ifndef DISABLE_PROG
|
||||
if (mode==TRACK_MODE_PROG) {
|
||||
#else
|
||||
if (false) {
|
||||
#endif
|
||||
// only allow 1 track to be prog
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG && t != trackToSet) {
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]->setMode(TRACK_MODE_NONE);
|
||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||
streamTrackState(NULL,t);
|
||||
}
|
||||
@@ -211,7 +234,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
|
||||
}
|
||||
trackMode[trackToSet]=mode;
|
||||
track[trackToSet]->setMode(mode);
|
||||
trackDCAddr[trackToSet]=dcAddr;
|
||||
streamTrackState(NULL,trackToSet);
|
||||
|
||||
@@ -238,7 +261,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 (trackMode[t]==TRACK_MODE_DC || trackMode[t]==TRACK_MODE_DCX) {
|
||||
if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==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
|
||||
@@ -246,7 +269,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 (trackMode[t]==TRACK_MODE_MAIN || trackMode[t]==TRACK_MODE_PROG) {
|
||||
} else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==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;
|
||||
@@ -284,7 +307,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 (trackMode[t]==TRACK_MODE_DCX)
|
||||
if (track[t]->getMode()==TRACK_MODE_DCX)
|
||||
speedByte = speedByte ^ 128; // reverse direction bit
|
||||
track[t]->setDCSignal(speedByte);
|
||||
}
|
||||
@@ -306,11 +329,13 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
if (params==2 && p[1]==HASH_KEYWORD_MAIN) // <= id MAIN>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
if (params==2 && p[1]==HASH_KEYWORD_PROG) // <= id PROG>
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
#endif
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_OFF) // <= id OFF>
|
||||
return setTrackMode(p[0],TRACK_MODE_OFF);
|
||||
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_EXT) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
@@ -328,15 +353,17 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||
// null stream means send to commandDistributor for broadcast
|
||||
if (track[t]==NULL) return;
|
||||
auto format=F("");
|
||||
switch(trackMode[t]) {
|
||||
switch(track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
format=F("<= %c MAIN>\n");
|
||||
break;
|
||||
#ifndef DISABLE_PROG
|
||||
case TRACK_MODE_PROG:
|
||||
format=F("<= %c PROG>\n");
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
format=F("<= %c OFF>\n");
|
||||
#endif
|
||||
case TRACK_MODE_NONE:
|
||||
format=F("<= %c NONE>\n");
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
format=F("<= %c EXT>\n");
|
||||
@@ -357,20 +384,22 @@ void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||
|
||||
void TrackManager::loop() {
|
||||
DCCWaveform::loop();
|
||||
DCCACK::loop();
|
||||
DCCWaveform::loop();
|
||||
#ifndef DISABLE_PROG
|
||||
DCCACK::loop();
|
||||
#endif
|
||||
bool dontLimitProg=DCCACK::isActive() || progTrackSyncMain || progTrackBoosted;
|
||||
nextCycleTrack++;
|
||||
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
||||
if (track[nextCycleTrack]==NULL) return;
|
||||
MotorDriver * motorDriver=track[nextCycleTrack];
|
||||
bool useProgLimit=dontLimitProg? false: trackMode[nextCycleTrack]==TRACK_MODE_PROG;
|
||||
bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
|
||||
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
||||
}
|
||||
|
||||
MotorDriver * TrackManager::getProgDriver() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG) return track[t];
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -378,7 +407,7 @@ MotorDriver * TrackManager::getProgDriver() {
|
||||
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||
std::vector<MotorDriver *> v;
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
return v;
|
||||
}
|
||||
#endif
|
||||
@@ -388,7 +417,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver * driver=track[t];
|
||||
if (!driver) continue;
|
||||
switch (trackMode[t]) {
|
||||
switch (track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
if (setProg) break;
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
@@ -416,7 +445,7 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
case TRACK_MODE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -424,8 +453,8 @@ void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (trackMode[t]==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
@@ -469,7 +498,7 @@ void TrackManager::setJoin(bool joined) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (joined) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackMode[t]==TRACK_MODE_PROG) {
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG) {
|
||||
tempProgTrack = t;
|
||||
setTrackMode(t, TRACK_MODE_MAIN);
|
||||
break;
|
||||
@@ -477,7 +506,12 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -485,3 +519,10 @@ 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;
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* © 2023 Colin Murdoch
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -27,10 +29,6 @@
|
||||
#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;
|
||||
@@ -81,11 +79,19 @@ class TrackManager {
|
||||
static void reportCurrent(Print* stream);
|
||||
static void reportObsoleteCurrent(Print* stream);
|
||||
static void streamTrackState(Print* stream, byte t);
|
||||
static bool isPowerOn(byte t);
|
||||
|
||||
static int16_t joinRelay;
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
|
||||
static bool progTrackBoosted; // true when prog track is not current limited
|
||||
|
||||
#ifdef DEBUG_ADC
|
||||
public:
|
||||
#else
|
||||
private:
|
||||
#endif
|
||||
static MotorDriver* track[MAX_TRACKS];
|
||||
|
||||
private:
|
||||
static void addTrack(byte t, MotorDriver* driver);
|
||||
static byte lastTrack;
|
||||
@@ -93,8 +99,6 @@ class TrackManager {
|
||||
static POWERMODE mainPowerGuess;
|
||||
static void applyDCSpeed(byte t);
|
||||
|
||||
static MotorDriver* track[MAX_TRACKS];
|
||||
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
|
||||
|
@@ -250,6 +250,7 @@
|
||||
}
|
||||
}
|
||||
tt = (Turnout *)new ServoTurnout(id, vpin, thrownPosition, closedPosition, profile, closed);
|
||||
DIAG(F("Turnout 0x%x size %d size %d"), tt, sizeof(Turnout),sizeof(struct TurnoutData));
|
||||
IODevice::writeAnalogue(vpin, closed ? closedPosition : thrownPosition, PCA9685::Instant);
|
||||
return tt;
|
||||
#else
|
||||
|
@@ -69,10 +69,12 @@ protected:
|
||||
uint16_t id;
|
||||
} _turnoutData; // 3 bytes
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Address in eeprom of first byte of the _turnoutData struct (containing the closed flag).
|
||||
// Set to zero if the object has not been saved in EEPROM, e.g. for newly created Turnouts, and
|
||||
// for all LCN turnouts.
|
||||
uint16_t _eepromAddress = 0;
|
||||
#endif
|
||||
|
||||
// Pointer to next turnout on linked list.
|
||||
Turnout *_nextTurnout = 0;
|
||||
|
268
Turntables.cpp
Normal file
268
Turntables.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* © 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
|
243
Turntables.h
Normal file
243
Turntables.h
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* © 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
|
@@ -235,6 +235,10 @@ int WiThrottle::getLocoId(byte * cmd) {
|
||||
void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||
char throttleChar=cmd[1];
|
||||
int locoid=getLocoId(cmd+3); // -1 for *
|
||||
if (locoid > 10239 || locoid < -1) {
|
||||
StringFormatter::send(stream, F("No valid DCC loco %d\n"), locoid);
|
||||
return;
|
||||
}
|
||||
byte * aval=cmd;
|
||||
while(*aval !=';' && *aval !='\0') aval++;
|
||||
if (*aval) aval+=2; // skip ;>
|
||||
@@ -527,10 +531,13 @@ void WiThrottle::sendRoster(Print* stream) {
|
||||
rosterSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||
for (int16_t r=0;;r++) {
|
||||
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
if (cabid == INT16_MAX)
|
||||
break;
|
||||
if (cabid > 0)
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#else
|
||||
@@ -544,14 +551,14 @@ void WiThrottle::sendRoutes(Print* stream) {
|
||||
// first pass automations
|
||||
for (int ix=0;;ix+=2) {
|
||||
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
|
||||
if (id==0) break;
|
||||
if (id==INT16_MAX) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[A%d}|{%S}|{4"),id,desc);
|
||||
}
|
||||
// second pass routes.
|
||||
for (int ix=0;;ix+=2) {
|
||||
int16_t id=GETHIGHFLASHW(RMFT2::routeIdList,ix);
|
||||
if (id==0) break;
|
||||
if (id==INT16_MAX) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
|
||||
}
|
||||
@@ -567,9 +574,13 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
myLocos[loco].functionToggles=1<<2; // F2 (HORN) is a non-toggle
|
||||
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
const char * functionNames=(char *) RMFT2::getRosterFunctions(locoid);
|
||||
if (!functionNames) {
|
||||
// no roster, use non-exrail presets as above
|
||||
const FSH * functionNames= RMFT2::getRosterFunctions(locoid);
|
||||
if (functionNames == NULL) {
|
||||
// no roster entry for locoid, try to find default entry
|
||||
functionNames= RMFT2::getRosterFunctions(0);
|
||||
}
|
||||
if (functionNames == NULL) {
|
||||
// no default roster entry either, use non-exrail presets as above
|
||||
}
|
||||
else if (GETFLASH(functionNames)=='\0') {
|
||||
// "" = Roster but no functions given
|
||||
@@ -584,7 +595,7 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
fkeys=0;
|
||||
bool firstchar=true;
|
||||
for (int fx=0;;fx++) {
|
||||
char c=GETFLASH(functionNames+fx);
|
||||
char c=GETFLASH((char *)functionNames+fx);
|
||||
if (c=='\0') {
|
||||
fkeys++;
|
||||
break;
|
||||
|
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
© 2021, Harald Barth.
|
||||
© 2023 Paul M. Antoine
|
||||
© 2021 Harald Barth
|
||||
© 2023 Nathan Kellenicki
|
||||
|
||||
This file is part of CommandStation-EX
|
||||
|
||||
@@ -20,6 +22,7 @@
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include <vector>
|
||||
#include "defines.h"
|
||||
#include "ESPmDNS.h"
|
||||
#include <WiFi.h>
|
||||
#include "esp_wifi.h"
|
||||
#include "WifiESP32.h"
|
||||
@@ -105,11 +108,18 @@ 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 byte channel,
|
||||
const bool forceAP) {
|
||||
bool havePassword = true;
|
||||
bool haveSSID = true;
|
||||
bool wifiUp = false;
|
||||
@@ -137,7 +147,8 @@ bool WifiESP::setup(const char *SSid,
|
||||
if (strncmp(yourNetwork, password, 13) == 0 || strncmp("", password, 13) == 0)
|
||||
havePassword = false;
|
||||
|
||||
if (haveSSID && havePassword) {
|
||||
if (haveSSID && havePassword && !forceAP) {
|
||||
WiFi.setHostname(hostname); // Strangely does not work unless we do it HERE!
|
||||
WiFi.mode(WIFI_STA);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
WiFi.setSleep(true);
|
||||
@@ -174,16 +185,20 @@ bool WifiESP::setup(const char *SSid,
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!haveSSID) {
|
||||
if (!haveSSID || forceAP) {
|
||||
// prepare all strings
|
||||
String strSSID("DCC_");
|
||||
String strPass("PASS_");
|
||||
String strMac = WiFi.macAddress();
|
||||
strMac.remove(0,9);
|
||||
strMac.replace(":","");
|
||||
strMac.replace(":","");
|
||||
strSSID.concat(strMac);
|
||||
strPass.concat(strMac);
|
||||
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);
|
||||
}
|
||||
|
||||
WiFi.mode(WIFI_AP);
|
||||
#ifdef SERIAL_BT_COMMANDS
|
||||
@@ -209,6 +224,15 @@ 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
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* © 2021, Harald Barth.
|
||||
* © 2021 Harald Barth
|
||||
* © 2023 Nathan Kellenicki
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -31,7 +32,8 @@ public:
|
||||
const char *wifiPassword,
|
||||
const char *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
const byte channel,
|
||||
const bool forceAP);
|
||||
static void loop();
|
||||
private:
|
||||
};
|
||||
|
@@ -2,6 +2,7 @@
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Nathan Kellenicki
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -52,10 +53,32 @@ Stream * WifiInterface::wifiStream;
|
||||
|
||||
#if (defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560))
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial1
|
||||
#define SERIAL3 Serial3
|
||||
#endif
|
||||
|
||||
#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)
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial1
|
||||
#define SERIAL3 Serial6
|
||||
#elif defined(ARDUINO_NUCLEO_F446RE)
|
||||
#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)
|
||||
#define NUM_SERIAL 2
|
||||
#define SERIAL1 Serial6
|
||||
#else
|
||||
#warning This variant of Nucleo not yet explicitly supported
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef NUM_SERIAL
|
||||
#define NUM_SERIAL 1
|
||||
#define SERIAL1 Serial1
|
||||
#endif
|
||||
|
||||
bool WifiInterface::setup(long serial_link_speed,
|
||||
@@ -63,7 +86,8 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel) {
|
||||
const byte channel,
|
||||
const bool forceAP) {
|
||||
|
||||
wifiSerialState wifiUp = WIFI_NOAT;
|
||||
|
||||
@@ -75,27 +99,34 @@ 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);
|
||||
SERIAL1.begin(serial_link_speed);
|
||||
wifiUp = setup(SERIAL1, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
#endif
|
||||
|
||||
// Other serials are tried, depending on hardware.
|
||||
// Currently only the Arduino Mega 2560 has usable Serial2 (Nucleo-64 boards use Serial 2 for console!)
|
||||
#if defined(ARDUINO_AVR_MEGA2560)
|
||||
#if NUM_SERIAL > 1 && !defined(SERIAL2_COMMANDS)
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial2.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
// We guess here that in all architctures that have a Serial3
|
||||
// we can use it for our purpose.
|
||||
#if NUM_SERIAL > 2 && !defined(SERIAL3_COMMANDS)
|
||||
if (wifiUp == WIFI_NOAT)
|
||||
{
|
||||
Serial3.begin(serial_link_speed);
|
||||
wifiUp = setup(Serial3, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
SERIAL3.begin(serial_link_speed);
|
||||
wifiUp = setup(SERIAL3, wifiESSID, wifiPassword, hostname, port, channel, forceAP);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -113,7 +144,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) {
|
||||
const FSH* hostname, int port, byte channel, bool forceAP) {
|
||||
wifiSerialState wifiState;
|
||||
static uint8_t ntry = 0;
|
||||
ntry++;
|
||||
@@ -122,7 +153,7 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
|
||||
|
||||
DIAG(F("++ Wifi Setup Try %d ++"), ntry);
|
||||
|
||||
wifiState = setup2( SSid, password, hostname, port, channel);
|
||||
wifiState = setup2( SSid, password, hostname, port, channel, forceAP);
|
||||
|
||||
if (wifiState == WIFI_NOAT) {
|
||||
LCD(4, F("WiFi no AT chip"));
|
||||
@@ -146,7 +177,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) {
|
||||
const FSH* hostname, int port, byte channel, bool forceAP) {
|
||||
bool ipOK = false;
|
||||
bool oldCmd = false;
|
||||
|
||||
@@ -169,7 +200,21 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
|
||||
// Display the AT version information
|
||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||
checkForOK(2000, true, false); // Makes this visible on the console
|
||||
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')) {
|
||||
SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE");
|
||||
forceAP = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
checkForOK(2000, true, false);
|
||||
|
||||
#ifdef DONT_TOUCH_WIFI_CONF
|
||||
DIAG(F("DONT_TOUCH_WIFI_CONF was set: Using existing config"));
|
||||
@@ -199,7 +244,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
if (!checkForOK(1000, F("0.0.0.0"), true,false))
|
||||
ipOK = true;
|
||||
}
|
||||
} else {
|
||||
} else if (!forceAP) {
|
||||
// SSID was configured, so we assume station (client) mode.
|
||||
if (oldCmd) {
|
||||
// AT command early version supports CWJAP/CWSAP
|
||||
@@ -259,14 +304,19 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
|
||||
i=0;
|
||||
do {
|
||||
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);
|
||||
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);
|
||||
}
|
||||
} else {
|
||||
// password configured by user
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"%S\",%d,4\r\n"), oldCmd ? "" : "_CUR",
|
||||
macTail, password, channel);
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"%S\",\"%S\",%d,4\r\n"),
|
||||
oldCmd ? "" : "_CUR", SSid, 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)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020, Harald Barth.
|
||||
* © 2023 Nathan Kellenicki
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -36,17 +37,18 @@ public:
|
||||
const FSH *wifiPassword,
|
||||
const FSH *hostname,
|
||||
const int port,
|
||||
const byte channel);
|
||||
const byte channel,
|
||||
const bool forceAP);
|
||||
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);
|
||||
const FSH *hostname, int port, byte channel, bool forceAP);
|
||||
static Stream *wifiStream;
|
||||
static DCCEXParser parser;
|
||||
static wifiSerialState setup2(const FSH *SSSid, const FSH *password,
|
||||
const FSH *hostname, int port, byte channel);
|
||||
const FSH *hostname, int port, byte channel, bool forceAP);
|
||||
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;
|
||||
|
@@ -1,9 +1,10 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2023 Nathan Kellenicki
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -27,6 +28,16 @@ The configuration file for DCC-EX Command Station
|
||||
|
||||
**********************************************************************/
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// If you want to add your own motor driver definition(s), add them here
|
||||
// For example MY_SHIELD with display name "MINE":
|
||||
// (remove comment start and end marker if you want to edit and use that)
|
||||
/*
|
||||
#define MY_SHIELD F("MINE"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 5.08, 3000, A4), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 5.08, 1500, A5)
|
||||
*/
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// NOTE: Before connecting these boards and selecting one in this software
|
||||
// check the quick install guides!!! Some of these boards require a voltage
|
||||
@@ -34,19 +45,34 @@ The configuration file for DCC-EX Command Station
|
||||
// the correct resistor could damage the sense pin on your Arduino or destroy
|
||||
// the device.
|
||||
//
|
||||
// DEFINE MOTOR_SHIELD_TYPE BELOW ACCORDING TO THE FOLLOWING TABLE:
|
||||
// DEFINE MOTOR_SHIELD_TYPE BELOW. THESE ARE EXAMPLES. FULL LIST IN MotorDrivers.h
|
||||
//
|
||||
// STANDARD_MOTOR_SHIELD : Arduino Motor shield Rev3 based on the L298 with 18V 2A per channel
|
||||
// POLOLU_MOTOR_SHIELD : Pololu MC33926 Motor Driver (not recommended for prog track)
|
||||
// POLOLU_TB9051FTG : Pololu Dual TB9051FTG Motor Driver
|
||||
// FUNDUMOTO_SHIELD : Fundumoto Shield, no current sensing (not recommended, no short protection)
|
||||
// FIREBOX_MK1 : The Firebox MK1
|
||||
// FIREBOX_MK1S : The Firebox MK1S
|
||||
// IBT_2_WITH_ARDUINO : Arduino Motor Shield for PROG and IBT-2 for MAIN
|
||||
// EX8874_SHIELD : DCC-EX TI DRV8874 based motor shield
|
||||
// |
|
||||
// +-----------------------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.
|
||||
@@ -98,6 +124,11 @@ 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
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
@@ -128,7 +159,7 @@ The configuration file for DCC-EX Command Station
|
||||
//OR define OLED_DRIVER width,height[,address] in pixels (address auto detected if not supplied)
|
||||
// 128x32 or 128x64 I2C SSD1306-based devices are supported.
|
||||
// Use 132,64 for a SH1106-based I2C device with a 128x64 display.
|
||||
// #define OLED_DRIVER 128,32,0x3c
|
||||
// #define OLED_DRIVER 0x3c,128,32
|
||||
|
||||
// Define scroll mode as 0, 1 or 2
|
||||
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
||||
@@ -141,7 +172,7 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
// If you do not need the EEPROM at all, you can disable all the code that saves
|
||||
// data in the EEPROM. You might want to do that if you are in a Arduino UNO
|
||||
// and want to use the EX-RAIL automation. Otherwise you do not have enough RAM
|
||||
// and want to use the EXRAIL automation. Otherwise you do not have enough RAM
|
||||
// to do that. Of course, then none of the EEPROM related commands work.
|
||||
//
|
||||
// EEPROM does not work on ESP32. So on ESP32, EEPROM will always be disabled,
|
||||
@@ -149,6 +180,17 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
// #define DISABLE_EEPROM
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// DISABLE PROG
|
||||
//
|
||||
// If you do not need programming capability, you can disable all programming related
|
||||
// commands. You might want to do that if you are using an Arduino UNO and still want
|
||||
// to use EXRAIL automation, as the Uno is lacking in RAM and Flash to run both.
|
||||
//
|
||||
// Note this disables all programming functionality, including EXRAIL.
|
||||
//
|
||||
// #define DISABLE_PROG
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// REDEFINE WHERE SHORT/LONG ADDR break is. According to NMRA the last short address
|
||||
// is 127 and the first long address is 128. There are manufacturers which have
|
||||
@@ -224,5 +266,15 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
// SABERTOOTH
|
||||
//
|
||||
// This is a very special option and only useful if you happen to have a
|
||||
// sabertooth motor controller from dimension engineering configured to
|
||||
// take commands from and ESP32 via serial at 9600 baud from GPIO17 (TX)
|
||||
// and GPIO16 (RX, currently unused).
|
||||
// The number defined is the DCC address for which speed controls are sent
|
||||
// to the sabertooth controller _as_well_. Default: Undefined.
|
||||
//
|
||||
//#define SABERTOOTH 1
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
31
defines.h
31
defines.h
@@ -144,10 +144,9 @@
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// STM32 support for native I2C is awaiting development
|
||||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
|
||||
// #ifndef I2C_USE_WIRE
|
||||
// #define I2C_USE_WIRE
|
||||
// #endif
|
||||
|
||||
/* TODO when ready
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
@@ -183,6 +182,15 @@
|
||||
#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
|
||||
@@ -205,8 +213,21 @@
|
||||
//
|
||||
#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)
|
||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
|
||||
#define EXRAIL_ACTIVE
|
||||
#else
|
||||
#define EXRAIL_WARNING
|
||||
|
13
install_via_powershell.cmd
Normal file
13
install_via_powershell.cmd
Normal file
@@ -0,0 +1,13 @@
|
||||
@ECHO OFF
|
||||
|
||||
FOR /f "tokens=*" %%a IN ('powershell Get-ExecutionPolicy -Scope CurrentUser') DO SET PS_POLICY=%%a
|
||||
|
||||
IF NOT %PS_POLICY=="Bypass" (
|
||||
powershell Set-ExecutionPolicy -Scope CurrentUser Bypass
|
||||
)
|
||||
|
||||
powershell %~dp0%installer.ps1
|
||||
|
||||
IF NOT %PS_POLICY=="Bypass" (
|
||||
powershell Set-ExecutionPolicy -Scope CurrentUser %PS_POLICY%
|
||||
)
|
540
installer.ps1
Normal file
540
installer.ps1
Normal file
@@ -0,0 +1,540 @@
|
||||
<#
|
||||
# © 2023 Peter Cole
|
||||
#
|
||||
# This file is part of EX-CommandStation
|
||||
#
|
||||
# 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/>.
|
||||
#>
|
||||
|
||||
<############################################
|
||||
For script errors set ExecutionPolicy:
|
||||
Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy Bypass
|
||||
############################################>
|
||||
|
||||
<############################################
|
||||
Optional command line parameters:
|
||||
$buildDirectory - specify an existing directory rather than generating a new unique one
|
||||
$configDirectory - specify a directory containing existing files as per $configFiles
|
||||
############################################>
|
||||
Param(
|
||||
[Parameter()]
|
||||
[String]$buildDirectory,
|
||||
[Parameter()]
|
||||
[String]$configDirectory
|
||||
)
|
||||
|
||||
<############################################
|
||||
Define global parameters here such as known URLs etc.
|
||||
############################################>
|
||||
$installerVersion = "v0.0.8"
|
||||
$configFiles = @("config.h", "myAutomation.h", "myHal.cpp", "mySetup.h")
|
||||
$wifiBoards = @("arduino:avr:mega", "esp32:esp32:esp32")
|
||||
$userDirectory = $env:USERPROFILE + "\"
|
||||
$gitHubAPITags = "https://api.github.com/repos/DCC-EX/CommandStation-EX/git/refs/tags"
|
||||
$gitHubURLPrefix = "https://github.com/DCC-EX/CommandStation-EX/archive/"
|
||||
if ((Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture -eq "64-bit") {
|
||||
$arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_64bit.zip"
|
||||
$arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_64bit.zip"
|
||||
} else {
|
||||
$arduinoCLIURL = "https://downloads.arduino.cc/arduino-cli/arduino-cli_latest_Windows_32bit.zip"
|
||||
$arduinoCLIZip = $userDirectory + "Downloads\" + "arduino-cli_latest_Windows_32bit.zip"
|
||||
}
|
||||
$arduinoCLIDirectory = $userDirectory + "arduino-cli"
|
||||
$arduinoCLI = $arduinoCLIDirectory + "\arduino-cli.exe"
|
||||
|
||||
<############################################
|
||||
List of supported devices with FQBN in case clones used that aren't detected
|
||||
############################################>
|
||||
$supportedDevices = @(
|
||||
@{
|
||||
name = "Arduino Mega or Mega 2560"
|
||||
fqbn = "arduino:avr:mega"
|
||||
},
|
||||
@{
|
||||
name = "Arduino Nano"
|
||||
fqbn = "arduino:avr:nano"
|
||||
},
|
||||
@{
|
||||
name = "Arduino Uno"
|
||||
fqbn = "arduino:avr:uno"
|
||||
},
|
||||
@{
|
||||
name = "ESP32 Dev Module"
|
||||
fqbn = "esp32:esp32:esp32"
|
||||
}
|
||||
)
|
||||
|
||||
<############################################
|
||||
List of supported displays
|
||||
############################################>
|
||||
$displayList = @(
|
||||
@{
|
||||
option = "LCD 16 columns x 2 rows"
|
||||
configLine = "#define LCD_DRIVER 0x27,16,2"
|
||||
},
|
||||
@{
|
||||
option = "LCD 16 columns x 4 rows"
|
||||
configLine = "#define LCD_DRIVER 0x27,16,4"
|
||||
},
|
||||
@{
|
||||
option = "OLED 128 x 32"
|
||||
configLine = "#define OLED_DRIVER 128,32"
|
||||
},
|
||||
@{
|
||||
option = "OLED 128 x 64"
|
||||
configLine = "#define OLED_DRIVER 128,64"
|
||||
}
|
||||
)
|
||||
|
||||
<############################################
|
||||
Basics of config.h
|
||||
############################################>
|
||||
$configLines = @(
|
||||
"/*",
|
||||
"This config.h file was generated by the DCC-EX PowerShell installer $installerVersion",
|
||||
"*/",
|
||||
"",
|
||||
"// Define standard motor shield",
|
||||
"#define MOTOR_SHIELD_TYPE STANDARD_MOTOR_SHIELD",
|
||||
""
|
||||
)
|
||||
|
||||
<############################################
|
||||
Set default action for progress indicators, warnings, and errors
|
||||
############################################>
|
||||
$global:ProgressPreference = "SilentlyContinue"
|
||||
$global:WarningPreference = "SilentlyContinue"
|
||||
$global:ErrorActionPreference = "SilentlyContinue"
|
||||
|
||||
<############################################
|
||||
If $buildDirectory not provided, generate a new time/date stamp based directory to use
|
||||
############################################>
|
||||
if (!$PSBoundParameters.ContainsKey('buildDirectory')) {
|
||||
$buildDate = Get-Date -Format 'yyyyMMdd-HHmmss'
|
||||
$buildDirectory = $userDirectory + "EX-CommandStation-Installer\" + $buildDate
|
||||
}
|
||||
$commandStationDirectory = $buildDirectory + "\CommandStation-EX"
|
||||
|
||||
<############################################
|
||||
Write out intro message and prompt to continue
|
||||
############################################>
|
||||
@"
|
||||
Welcome to the DCC-EX PowerShell installer for EX-CommandStation ($installerVersion)
|
||||
|
||||
Current installer options:
|
||||
|
||||
- EX-CommandStation will be built in $commandStationDirectory
|
||||
- Arduino CLI will downloaded and extracted to $arduinoCLIDirectory
|
||||
|
||||
Before continuing, please ensure:
|
||||
|
||||
- Your computer is connected to the internet
|
||||
- The device you wish to install EX-CommandStation on is connected to a USB port
|
||||
|
||||
This installer will obtain the Arduino CLI (if not already present), and then download and install your chosen version of EX-CommandStation
|
||||
|
||||
"@
|
||||
|
||||
<############################################
|
||||
Prompt user to confirm all is ready to proceed
|
||||
############################################>
|
||||
$confirmation = Read-Host "Enter 'Y' or 'y' then press <Enter> to confirm you are ready to proceed, any other key to exit"
|
||||
if ($confirmation -ne "Y" -and $confirmation -ne "y") {
|
||||
Exit
|
||||
}
|
||||
|
||||
<############################################
|
||||
See if we have the Arduino CLI already, otherwise download and extract it
|
||||
############################################>
|
||||
if (!(Test-Path -PathType Leaf -Path $arduinoCLI)) {
|
||||
if (!(Test-Path -PathType Container -Path $arduinoCLIDirectory)) {
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $arduinoCLIDirectory | Out-Null
|
||||
}
|
||||
catch {
|
||||
Write-Output "Arduino CLI does not exist and cannot create directory $arduinoCLIDirectory"
|
||||
Exit
|
||||
}
|
||||
}
|
||||
Write-Output "`r`nDownloading and extracting Arduino CLI"
|
||||
try {
|
||||
Invoke-WebRequest -Uri $arduinoCLIURL -OutFile $arduinoCLIZip
|
||||
}
|
||||
catch {
|
||||
Write-Output "Failed to download Arduino CLI"
|
||||
Exit
|
||||
}
|
||||
try {
|
||||
Expand-Archive -Path $arduinoCLIZip -DestinationPath $arduinoCLIDirectory -Force
|
||||
}
|
||||
catch {
|
||||
Write-Output "Failed to extract Arduino CLI"
|
||||
}
|
||||
} else {
|
||||
Write-Output "`r`nArduino CLI already downloaded, ensuring it is up to date and you have a board connected"
|
||||
}
|
||||
|
||||
<############################################
|
||||
Make sure Arduino CLI core index updated and list of boards populated
|
||||
############################################>
|
||||
# Need to do an initial board list to download everything first
|
||||
try {
|
||||
& $arduinoCLI core update-index | Out-Null
|
||||
}
|
||||
catch {
|
||||
Write-Output "Failed to update Arduino CLI core index"
|
||||
Exit
|
||||
}
|
||||
# Need to do an initial board list to download everything first
|
||||
try {
|
||||
& $arduinoCLI board list | Out-Null
|
||||
}
|
||||
catch {
|
||||
Write-Output "Failed to update Arduino CLI board list"
|
||||
Exit
|
||||
}
|
||||
|
||||
<############################################
|
||||
Identify available board(s)
|
||||
############################################>
|
||||
try {
|
||||
$boardList = & $arduinoCLI board list --format jsonmini | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
Write-Output "Failed to obtain list of boards"
|
||||
Exit
|
||||
}
|
||||
|
||||
<############################################
|
||||
Get user to select board
|
||||
############################################>
|
||||
if ($boardList.count -eq 0) {
|
||||
Write-Output "Could not find any attached devices, please ensure your device is plugged in to a USB port and Windows recognises it"
|
||||
Exit
|
||||
} else {
|
||||
@"
|
||||
|
||||
Devices attached to COM ports:
|
||||
------------------------------
|
||||
"@
|
||||
|
||||
$boardSelect = 1
|
||||
foreach ($board in $boardList) {
|
||||
if ($board.matching_boards.name) {
|
||||
$boardName = $board.matching_boards.name
|
||||
} else {
|
||||
$boardName = "Unknown device"
|
||||
}
|
||||
$port = $board.port.address
|
||||
Write-Output "$boardSelect - $boardName on port $port"
|
||||
$boardSelect++
|
||||
}
|
||||
Write-Output "$boardSelect - Exit"
|
||||
$userSelection = 0
|
||||
do {
|
||||
[int]$userSelection = Read-Host "`r`nSelect the device to use from the list above"
|
||||
} until (
|
||||
(($userSelection -ge 1) -and ($userSelection -le ($boardList.count + 1)))
|
||||
)
|
||||
if ($userSelection -eq ($boardList.count + 1)) {
|
||||
Write-Output "Exiting installer"
|
||||
Exit
|
||||
} else {
|
||||
$selectedBoard = $userSelection - 1
|
||||
}
|
||||
}
|
||||
|
||||
<############################################
|
||||
If the board is unknown, need to choose which one
|
||||
############################################>
|
||||
if ($null -eq $boardList[$selectedBoard].matching_boards.name) {
|
||||
Write-Output "The device selected is unknown, these boards are supported:`r`n"
|
||||
$deviceSelect = 1
|
||||
foreach ($device in $supportedDevices) {
|
||||
Write-Output "$deviceSelect - $($supportedDevices[$deviceSelect - 1].name)"
|
||||
$deviceSelect++
|
||||
}
|
||||
Write-Output "$deviceSelect - Exit"
|
||||
$userSelection = 0
|
||||
do {
|
||||
[int]$userSelection = Read-Host "Select the board type from the list above"
|
||||
} until (
|
||||
(($userSelection -ge 1) -and ($userSelection -le ($supportedDevices.count + 1)))
|
||||
)
|
||||
if ($userSelection -eq ($supportedDevices.count + 1)) {
|
||||
Write-Output "Exiting installer"
|
||||
Exit
|
||||
} else {
|
||||
$deviceName = $supportedDevices[$userSelection - 1].name
|
||||
$deviceFQBN = $supportedDevices[$userSelection - 1].fqbn
|
||||
$devicePort = $boardList[$selectedBoard].port.address
|
||||
}
|
||||
} else {
|
||||
$deviceName = $boardList[$selectedBoard].matching_boards.name
|
||||
$deviceFQBN = $boardList[$selectedBoard].matching_boards.fqbn
|
||||
$devicePort = $boardList[$selectedBoard].port.address
|
||||
}
|
||||
|
||||
<############################################
|
||||
Get the list of tags
|
||||
############################################>
|
||||
try {
|
||||
$gitHubTags = Invoke-RestMethod -Uri $gitHubAPITags
|
||||
}
|
||||
catch {
|
||||
Write-Output "Failed to obtain list of available EX-CommandStation versions"
|
||||
Exit
|
||||
}
|
||||
|
||||
<############################################
|
||||
Get our GitHub tag list in a hash so we can sort by version numbers and extract just the ones we want
|
||||
############################################>
|
||||
$versionMatch = ".*?v(\d+)\.(\d+).(\d+)-(.*)"
|
||||
$tagList = @{}
|
||||
foreach ($tag in $gitHubTags) {
|
||||
$tagHash = @{}
|
||||
$tagHash["Ref"] = $tag.ref
|
||||
$version = $tag.ref.split("/")[2]
|
||||
$null = $version -match $versionMatch
|
||||
$tagHash["Major"] = [int]$Matches[1]
|
||||
$tagHash["Minor"] = [int]$Matches[2]
|
||||
$tagHash["Patch"] = [int]$Matches[3]
|
||||
$tagHash["Type"] = $Matches[4]
|
||||
$tagList.Add($version, $tagHash)
|
||||
}
|
||||
|
||||
<############################################
|
||||
Get latest two Prod and Devel for user to select
|
||||
############################################>
|
||||
$userList = @{}
|
||||
$prodCount = 1
|
||||
$devCount = 1
|
||||
$select = 1
|
||||
foreach ($tag in $tagList.Keys | Sort-Object {$tagList[$_]["Major"]},{$tagList[$_]["Minor"]},{$tagList[$_]["Patch"]} -Descending) {
|
||||
if (($tagList[$tag]["Type"] -eq "Prod") -and $prodCount -le 2) {
|
||||
$userList[$select] = $tag
|
||||
$select++
|
||||
$prodCount++
|
||||
} elseif (($tagList[$tag]["Type"] -eq "Devel") -and $devCount -le 2) {
|
||||
$userList[$select] = $tag
|
||||
$select++
|
||||
$devCount++
|
||||
}
|
||||
}
|
||||
|
||||
<############################################
|
||||
Display options for user to select and get the selection
|
||||
############################################>
|
||||
@"
|
||||
|
||||
Available EX-CommandStation versions:
|
||||
-------------------------------------
|
||||
"@
|
||||
foreach ($selection in $userList.Keys | Sort-Object $selection) {
|
||||
Write-Output "$selection - $($userList[$selection])"
|
||||
}
|
||||
Write-Output "5 - Exit"
|
||||
$userSelection = 0
|
||||
do {
|
||||
[int]$userSelection = Read-Host "`r`nSelect the version to install from the list above (1 - 5)"
|
||||
} until (
|
||||
(($userSelection -ge 1) -and ($userSelection -le 5))
|
||||
)
|
||||
if ($userSelection -eq 5) {
|
||||
Write-Output "Exiting installer"
|
||||
Exit
|
||||
} else {
|
||||
$downloadURL = $gitHubURLPrefix + $tagList[$userList[$userSelection]]["Ref"] + ".zip"
|
||||
}
|
||||
|
||||
<############################################
|
||||
Create build directory if it doesn't exist, or fail
|
||||
############################################>
|
||||
if (!(Test-Path -PathType Container -Path $buildDirectory)) {
|
||||
try {
|
||||
New-Item -ItemType Directory -Path $buildDirectory | Out-Null
|
||||
}
|
||||
catch {
|
||||
Write-Output "Could not create build directory $buildDirectory"
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
<############################################
|
||||
Download the chosen version to the build directory
|
||||
############################################>
|
||||
$downladFile = $buildDirectory + "\CommandStation-EX.zip"
|
||||
Write-Output "Downloading and extracting $($userList[$userSelection])"
|
||||
try {
|
||||
Invoke-WebRequest -Uri $downloadURL -OutFile $downladFile
|
||||
}
|
||||
catch {
|
||||
Write-Output "Error downloading EX-CommandStation zip file"
|
||||
Exit
|
||||
}
|
||||
|
||||
<############################################
|
||||
If folder exists, bail out and tell user
|
||||
############################################>
|
||||
if (Test-Path -PathType Container -Path "$buildDirectory\CommandStation-EX") {
|
||||
Write-Output "EX-CommandStation directory already exists, please ensure you have copied any user files then delete manually: $buildDirectory\CommandStation-EX"
|
||||
Exit
|
||||
}
|
||||
|
||||
<############################################
|
||||
Extract and rename to CommandStation-EX to allow building
|
||||
############################################>
|
||||
try {
|
||||
Expand-Archive -Path $downladFile -DestinationPath $buildDirectory -Force
|
||||
}
|
||||
catch {
|
||||
Write-Output "Failed to extract EX-CommandStation zip file"
|
||||
Exit
|
||||
}
|
||||
|
||||
$folderName = $buildDirectory + "\CommandStation-EX-" + ($userList[$userSelection] -replace "^v", "")
|
||||
try {
|
||||
Rename-Item -Path $folderName -NewName $commandStationDirectory
|
||||
}
|
||||
catch {
|
||||
Write-Output "Could not rename folder"
|
||||
Exit
|
||||
}
|
||||
|
||||
<############################################
|
||||
If config directory provided, copy files here
|
||||
############################################>
|
||||
if ($PSBoundParameters.ContainsKey('configDirectory')) {
|
||||
if (Test-Path -PathType Container -Path $configDirectory) {
|
||||
foreach ($file in $configFiles) {
|
||||
if (Test-Path -PathType Leaf -Path "$configDirectory\$file") {
|
||||
Copy-Item -Path "$configDirectory\$file" -Destination "$commandStationDirectory\$file"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Write-Output "User provided configuration directory $configDirectory does not exist, skipping"
|
||||
}
|
||||
} else {
|
||||
|
||||
<############################################
|
||||
If no config directory provided, prompt for display option
|
||||
############################################>
|
||||
Write-Output "`r`nIf you have an LCD or OLED display connected, you can configure it here`r`n"
|
||||
Write-Output "1 - I have no display, skip this step"
|
||||
$displaySelect = 2
|
||||
foreach ($display in $displayList) {
|
||||
Write-Output "$displaySelect - $($displayList[$displaySelect - 2].option)"
|
||||
$displaySelect++
|
||||
}
|
||||
Write-Output "$($displayList.Count + 2) - Exit"
|
||||
do {
|
||||
[int]$displayChoice = Read-Host "`r`nSelect a display option"
|
||||
} until (
|
||||
($displayChoice -ge 1 -and $displayChoice -le ($displayList.Count + 2))
|
||||
)
|
||||
if ($displayChoice -eq ($displayList.Count + 2)) {
|
||||
Exit
|
||||
} elseif ($displayChoice -ge 2) {
|
||||
$configLines+= "// Display configuration"
|
||||
$configLines+= "$($displayList[$displayChoice - 2].configLine)"
|
||||
$configLines+= "#define SCROLLMODE 1 // Alternate between pages"
|
||||
}
|
||||
<############################################
|
||||
If device supports WiFi, prompt to configure
|
||||
############################################>
|
||||
if ($wifiBoards.Contains($deviceFQBN)) {
|
||||
Write-Output "`r`nYour chosen board supports WiFi`r`n"
|
||||
Write-Output "1 - I don't want WiFi, skip this step
|
||||
2 - Configure my device as an access point I will connect to directly
|
||||
3 - Configure my device to connect to my home WiFi network
|
||||
4 - Exit"
|
||||
do {
|
||||
[int]$wifiChoice = Read-Host "`r`nSelect a WiFi option"
|
||||
} until (
|
||||
($wifiChoice -ge 1 -and $wifiChoice -le 4)
|
||||
)
|
||||
if ($wifiChoice -eq 4) {
|
||||
Exit
|
||||
} elseif ($wifiChoice -ne 1) {
|
||||
$configLines+= ""
|
||||
$configLines+= "// WiFi configuration"
|
||||
$configLines+= "#define ENABLE_WIFI true"
|
||||
$configLines+= "#define IP_PORT 2560"
|
||||
$configLines+= "#define WIFI_HOSTNAME ""dccex"""
|
||||
$configLines+= "#define WIFI_CHANNEL 1"
|
||||
if ($wifiChoice -eq 2) {
|
||||
$configLines+= "#define WIFI_SSID ""Your network name"""
|
||||
$configLines+= "#define WIFI_PASSWORD ""Your network passwd"""
|
||||
}
|
||||
if ($wifiChoice -eq 3) {
|
||||
$wifiSSID = Read-Host "Please enter the SSID of your home network here"
|
||||
$wifiPassword = Read-Host "Please enter your home network WiFi password here"
|
||||
$configLines+= "#define WIFI_SSID ""$($wifiSSID)"""
|
||||
$configLines+= "#define WIFI_PASSWORD ""$($wifiPassword)"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
<############################################
|
||||
Write out config.h to a file here only if config directory not provided
|
||||
############################################>
|
||||
$configH = $commandStationDirectory + "\config.h"
|
||||
try {
|
||||
$configLines | Out-File -FilePath $configH -Encoding ascii
|
||||
}
|
||||
catch {
|
||||
Write-Output "Error writing config file to $configH"
|
||||
Exit
|
||||
}
|
||||
}
|
||||
|
||||
<############################################
|
||||
Install core libraries for the platform
|
||||
############################################>
|
||||
$platformArray = $deviceFQBN.split(":")
|
||||
$platform = $platformArray[0] + ":" + $platformArray[1]
|
||||
try {
|
||||
& $arduinoCLI core install $platform
|
||||
}
|
||||
catch {
|
||||
Write-Output "Error install core libraries"
|
||||
Exit
|
||||
}
|
||||
|
||||
<############################################
|
||||
Upload the sketch to the selected board
|
||||
############################################>
|
||||
#$arduinoCLI upload -b fqbn -p port $commandStationDirectory
|
||||
Write-Output "Compiling and uploading to $deviceName on $devicePort"
|
||||
try {
|
||||
$output = & $arduinoCLI compile -b $deviceFQBN -u -t -p $devicePort $commandStationDirectory --format jsonmini | ConvertFrom-Json
|
||||
}
|
||||
catch {
|
||||
Write-Output "Failed to compile"
|
||||
Exit
|
||||
}
|
||||
if ($output.success -eq "True") {
|
||||
Write-Output "`r`nCongratulations! DCC-EX EX-CommandStation $($userList[$userSelection]) has been installed on your $deviceName`r`n"
|
||||
} else {
|
||||
Write-Output "`r`nThere was an error installing $($userList[$userSelection]) on your $($deviceName), please take note of the errors provided:`r`n"
|
||||
if ($null -ne $output.compiler_err) {
|
||||
Write-Output "Compiler error: $($output.compiler_err)`r`n"
|
||||
}
|
||||
if ($null -ne $output.builder_result) {
|
||||
Write-Output "Builder result: $($output.builder_result)`r`n"
|
||||
}
|
||||
}
|
||||
|
||||
Write-Output "`r`nPress any key to exit the installer"
|
||||
[void][System.Console]::ReadKey($true)
|
25
installer.sh
25
installer.sh
@@ -1,7 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# © 2022 Harald Barth
|
||||
# © 2022,2023 Harald Barth
|
||||
#
|
||||
# This file is part of CommandStation-EX
|
||||
#
|
||||
@@ -29,14 +29,33 @@ 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
|
||||
@@ -69,10 +88,10 @@ else
|
||||
# need to do this config better
|
||||
cp -p config.example.h config.h
|
||||
fi
|
||||
need curl
|
||||
if test -x "$ACLI" ; then
|
||||
: all well
|
||||
else
|
||||
need curl
|
||||
curl "$ACLIINSTALL" > acliinstall.sh
|
||||
chmod +x acliinstall.sh
|
||||
./acliinstall.sh
|
||||
|
465
myHal.cpp.txt
Normal file
465
myHal.cpp.txt
Normal file
@@ -0,0 +1,465 @@
|
||||
#include "defines.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
|
||||
#include "IO_VL53L0X.h"
|
||||
#include "IO_HCSR04.h"
|
||||
#include "Sensors.h"
|
||||
#include "Turnouts.h"
|
||||
#include "IO_DFPlayer.h"
|
||||
//#include "IO_Wire.h"
|
||||
#include "IO_AnalogueInputs.h"
|
||||
#if __has_include("IO_Servo.h")
|
||||
#include "IO_Servo.h"
|
||||
#include "IO_PCA9685pwm.h"
|
||||
#endif
|
||||
|
||||
#include "IO_HALDisplay.h"
|
||||
#include "LiquidCrystal_I2C.h"
|
||||
|
||||
#if __has_include("IO_CMRI.h")
|
||||
#include "IO_CMRI.h"
|
||||
#endif
|
||||
|
||||
//#include "IO_ExampleSerial.h"
|
||||
|
||||
//#include "IO_EXFastclock.h"
|
||||
//#include "IO_EXTurntable.h"
|
||||
|
||||
#if __has_include("IO_ExternalEEPROM.h")
|
||||
#include "IO_ExternalEEPROM.h"
|
||||
#endif
|
||||
|
||||
#if __has_include("IO_Network.h")
|
||||
#include "IO_Network.h"
|
||||
#include "Net_RF24.h"
|
||||
#include "Net_ENC28J60.h"
|
||||
#include "Net_Ethernet.h"
|
||||
#define NETWORK_PRESENT
|
||||
#endif
|
||||
|
||||
#include "IO_TouchKeypad.h"
|
||||
|
||||
#define WIRE_TEST 0
|
||||
#define TESTHARNESS 1
|
||||
#define I2C_STRESS_TEST 0
|
||||
#define I2C_SETCLOCK 0
|
||||
|
||||
#include "DCC.h"
|
||||
|
||||
|
||||
#if 0 // Long Strings
|
||||
#define s10 "0123456789"
|
||||
#define s100 s10 s10 s10 s10 s10 s10 s10 s10 s10 s10
|
||||
#define s1k s100 s100 s100 s100 s100 s100 s100 s100 s100 s100
|
||||
#define s10k s1k s1k s1k s1k s1k s1k s1k s1k s1k s1k
|
||||
#define s32k s10k s10k s10k s1k s1k
|
||||
volatile const char PROGMEM ss1[] = s32k;
|
||||
#endif
|
||||
|
||||
|
||||
#if TESTHARNESS
|
||||
|
||||
// Function to be invoked by test harness
|
||||
void myTest() {
|
||||
// DIAG(F("VL53L0X #1 Test: dist=%d signal=%d ambient=%d value=%d"),
|
||||
// IODevice::readAnalogue(5000),
|
||||
// IODevice::readAnalogue(5001),
|
||||
// IODevice::readAnalogue(5002),
|
||||
// IODevice::read(5000));
|
||||
// DIAG(F("VL53L0X #2 Test: dist=%d signal=%d ambient=%d value=%d"),
|
||||
// IODevice::readAnalogue(5003),
|
||||
// IODevice::readAnalogue(5004),
|
||||
// IODevice::readAnalogue(5005),
|
||||
// IODevice::read(5003));
|
||||
// DIAG(F("HCSR04 Test: dist=%d value=%d"),
|
||||
// IODevice::readAnalogue(2000),
|
||||
// IODevice::read(2000));
|
||||
// DIAG(F("ADS111x Test: %d %d %d %d %d"),
|
||||
// IODevice::readAnalogue(4500),
|
||||
// IODevice::readAnalogue(4501),
|
||||
// IODevice::readAnalogue(4502),
|
||||
// IODevice::readAnalogue(4503),
|
||||
// IODevice::readAnalogue(A5)
|
||||
// );
|
||||
// DIAG(F("RF24 Test: 4000:%d 4002:%d"),
|
||||
// IODevice::read(4000),
|
||||
// IODevice::read(4002)
|
||||
// );
|
||||
DIAG(F("EXPANDER: 2212:%d 2213:%d 2214:%d"),
|
||||
IODevice::readAnalogue(2212),
|
||||
IODevice::readAnalogue(2213),
|
||||
IODevice::readAnalogue(2214));
|
||||
}
|
||||
#endif
|
||||
|
||||
#if I2C_STRESS_TEST
|
||||
static bool initialised = false;
|
||||
static uint8_t lastStatus = 0;
|
||||
static const int nRBs = 3; // request blocks concurrently
|
||||
static const int I2cTestPeriod = 1; // milliseconds
|
||||
static I2CAddress testDevice = {SubBus_6, 0x27};
|
||||
static I2CRB rb[nRBs];
|
||||
static uint8_t readBuffer[nRBs*32]; // nRB x 32-byte input buffer
|
||||
static uint8_t writeBuffer[nRBs]; // nRB x 1-byte output buffer
|
||||
static unsigned long count = 0;
|
||||
static unsigned long errors = 0;
|
||||
static unsigned long lastOutput = millis();
|
||||
|
||||
void I2CTest() {
|
||||
if (!initialised) {
|
||||
// I2C Loading for stress test.
|
||||
// Write value then read back 32 times
|
||||
for (int i=0; i<nRBs; i++) {
|
||||
writeBuffer[i] = (0xc5 ^ i ^ i<<3 ^ i<<6) & ~0x08; // bit corresponding to 08 is hard-wired low
|
||||
rb[i].setRequestParams(testDevice, &readBuffer[i*32], 32,
|
||||
&writeBuffer[i], 1);
|
||||
I2CManager.queueRequest(&rb[i]);
|
||||
}
|
||||
initialised = true;
|
||||
}
|
||||
|
||||
for (int i=0; i<nRBs; i++) {
|
||||
if (!rb[i].isBusy()) {
|
||||
count++;
|
||||
uint8_t status = rb[i].status;
|
||||
if (status != lastStatus) {
|
||||
DIAG(F("I2CTest: status=%d (%S)"),
|
||||
(int)status, I2CManager.getErrorMessage(status));
|
||||
lastStatus = status;
|
||||
}
|
||||
if (status == I2C_STATUS_OK) {
|
||||
bool diff = false;
|
||||
// Check contents of response
|
||||
for (uint8_t j=0; j<32; j++) {
|
||||
if (readBuffer[i*32+j] != writeBuffer[i]) {
|
||||
DIAG(F("I2CTest: Received message mismatch, sent %2x rcvd %2x"),
|
||||
writeBuffer[i], readBuffer[i*32+j]);
|
||||
diff = true;
|
||||
}
|
||||
}
|
||||
if (diff) errors++;
|
||||
} else
|
||||
errors++;
|
||||
I2CManager.queueRequest(&rb[i]);
|
||||
}
|
||||
}
|
||||
if (millis() - lastOutput > 60000) { // 1 minute
|
||||
DIAG(F("I2CTest: Count=%l Errors=%l"), count, errors);
|
||||
count = errors = 0;
|
||||
lastOutput = millis();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void updateLocoScreen() {
|
||||
for (int i=0; i<8; i++) {
|
||||
if (DCC::speedTable[i].loco > 0) {
|
||||
int speed = DCC::speedTable[i].speedCode;
|
||||
char direction = (speed & 0x80) ? 'R' : 'F';
|
||||
speed = speed & 0x7f;
|
||||
if (speed > 0) speed = speed - 1;
|
||||
SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
|
||||
speed, direction);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateTime() {
|
||||
uint8_t buffer[20];
|
||||
I2CAddress rtc = {SubBus_1, 0x68}; // Real-time clock I2C address
|
||||
buffer[0] = 0;
|
||||
|
||||
// Set time - only needs to be done once if battery is ok.
|
||||
static bool timeSet = false;
|
||||
if (!timeSet) {
|
||||
// I2CManager.read(rtc, buffer+1, sizeof(buffer)-1);
|
||||
// uint8_t year = 23; // 2023
|
||||
// uint8_t day = 2; // tuesday
|
||||
// uint8_t date = 21; // 21st
|
||||
// uint8_t month = 2; // feb
|
||||
// uint8_t hours = 23; // xx:
|
||||
// uint8_t minutes = 25; // :xx
|
||||
// buffer[1] = 0; // seconds
|
||||
// buffer[2] = ((minutes / 10) << 4) | (minutes % 10);
|
||||
// buffer[3] = ((hours / 10) << 4) | (hours % 10);
|
||||
// buffer[4] = day;
|
||||
// buffer[5] = ((date/10) << 4) + date%10; // 24th
|
||||
// buffer[6] = ((month/10) << 4) + month%10; // feb
|
||||
// buffer[7] = ((year/10) << 4) + year%10; // xx23
|
||||
// for (uint8_t i=8; i<sizeof(buffer); i++) buffer[i] = 0;
|
||||
// I2CManager.write(rtc, buffer, sizeof(buffer));
|
||||
timeSet = true;
|
||||
}
|
||||
|
||||
uint8_t status = I2CManager.read(rtc, buffer+1, sizeof(buffer)-1, 1, 0);
|
||||
if (status == I2C_STATUS_OK) {
|
||||
uint8_t seconds10 = buffer[1] >> 4;
|
||||
uint8_t seconds1 = buffer[1] & 0xf;
|
||||
uint8_t minutes10 = buffer[2] >> 4;
|
||||
uint8_t minutes1 = buffer[2] & 0xf;
|
||||
uint8_t hours10 = buffer[3] >> 4;
|
||||
uint8_t hours1 = buffer[3] & 0xf;
|
||||
SCREEN(10, 0, F("Departures %d%d:%d%d:%d%d"),
|
||||
hours10, hours1, minutes10, minutes1, seconds10, seconds1);
|
||||
}
|
||||
}
|
||||
|
||||
void showCharacterSet() {
|
||||
if (millis() < 3000) return;
|
||||
const uint8_t lineLen = 20;
|
||||
char buffer[lineLen+1];
|
||||
static uint8_t nextChar = 0x20;
|
||||
for (uint8_t row=0; row<8; row+=1) {
|
||||
for (uint8_t col=0; col<lineLen; col++) {
|
||||
buffer[col] = nextChar++;
|
||||
buffer[++col] = ' ';
|
||||
if (nextChar == 0) nextChar = 0x20; // check for wrap-around
|
||||
}
|
||||
buffer[lineLen] = '\0';
|
||||
SCREEN(3, row, F("%s"), buffer);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F446RE)
|
||||
HardwareSerial Serial3(PC11, PC10);
|
||||
#endif
|
||||
|
||||
|
||||
// HAL device initialisation
|
||||
void halSetup() {
|
||||
|
||||
I2CManager.setTimeout(500); // microseconds
|
||||
I2CManager.forceClock(400000);
|
||||
|
||||
HALDisplay<OLED>::create(10, {SubBus_5, 0x3c}, 132, 64); // SH1106
|
||||
// UserAddin::create(updateLocoScreen, 1000);
|
||||
// UserAddin::create(showCharacterSet, 5000);
|
||||
// UserAddin::create(updateTime, 1000);
|
||||
|
||||
HALDisplay<OLED>::create(10, {SubBus_4, 0x3c}, 128, 32);
|
||||
HALDisplay<OLED>::create(10, {SubBus_7, 0x3c}, 128, 32);
|
||||
|
||||
//HALDisplay<LiquidCrystal_I2C>::create(10, {SubBus_4, 0x27}, 20, 4);
|
||||
|
||||
// Draw double boxes with X O O X inside.
|
||||
// SCREEN(3, 2, F("\xc9\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xcb\xcd\xcd\xcd\xbb"));
|
||||
// SCREEN(3, 3, F("\xba X \xba O \xba O \xba O \xba X \xba"));
|
||||
// SCREEN(3, 4, F("\xcc\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xce\xcd\xcd\xcd\xb9"));
|
||||
// SCREEN(3, 5, F("\xba X \xba O \xba O \xba O \xba X \xba"));
|
||||
// SCREEN(3, 6, F("\xc8\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xca\xcd\xcd\xcd\xbc"));
|
||||
|
||||
// Draw single boxes with X O O X inside.
|
||||
// SCREEN(3, 0, F("Summary Data:"));
|
||||
// SCREEN(3, 1, F("\xda\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xc2\xc4\xc4\xc4\xbf"));
|
||||
// SCREEN(3, 2, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
|
||||
// SCREEN(3, 3, F("\xc3\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xb4"));
|
||||
// SCREEN(3, 4, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
|
||||
// SCREEN(3, 5, F("\xc3\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xc5\xc4\xc4\xc4\xb4"));
|
||||
// SCREEN(3, 6, F("\xb3 X \xb3 O \xb3 O \xb3 O \xb3 X \xb3"));
|
||||
// SCREEN(3, 7, F("\xc0\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xc1\xc4\xc4\xc4\xd9"));
|
||||
|
||||
// Blocks of different greyness
|
||||
// SCREEN(3, 0, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
|
||||
// SCREEN(3, 1, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
|
||||
// SCREEN(3, 2, F("\xb0\xb0\xb0\xb0\xb1\xb1\xb1\xb1\xb2\xb2\xb2\xb2\xdb\xdb\xdb\xdb"));
|
||||
|
||||
// DCCEX logo
|
||||
// SCREEN(3, 1, F("\xb0\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\x20\xb0\xb0\xb0\x20\xb0\x20\xb0"));
|
||||
// SCREEN(3, 2, F("\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0"));
|
||||
// SCREEN(3, 3, F("\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0\xb0\x20\x20\x20\xb0\x20"));
|
||||
// SCREEN(3, 4, F("\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\xb0"));
|
||||
// SCREEN(3, 5, F("\xb0\xb0\x20\x20\x20\xb0\x20\x20\x20\xb0\x20\x20\x20\x20\xb0\xb0\xb0\x20\xb0\x20\xb0"));
|
||||
// SCREEN(3, 7, F("\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1\xb1"));
|
||||
|
||||
#if 0
|
||||
// List versions of devices that respond to the version request
|
||||
for (uint8_t address = 8; address<0x78; address++) {
|
||||
uint8_t buffer[3];
|
||||
uint8_t status = I2CManager.read(0x7c, buffer, sizeof(buffer), 1, address);
|
||||
if (status == I2C_STATUS_OK) {
|
||||
uint16_t manufacturer = ((uint16_t)buffer[0] << 4 ) | (buffer[1] >> 4);
|
||||
uint16_t deviceID = ((uint16_t)(buffer[1] & 0x0f) << 5) | (buffer[2] >> 3);
|
||||
uint16_t dieRevision = buffer[2] & 0x1f;
|
||||
DIAG(F("Addr %s version: %x %x %x"), address.toString(), manufacturer, deviceID, dieRevision);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if I2C_STRESS_TEST
|
||||
UserAddin::create(I2CTest, I2cTestPeriod);
|
||||
#endif
|
||||
|
||||
#if WIRE_TEST
|
||||
// Test of Wire-I2CManager interface
|
||||
Wire.begin();
|
||||
Wire.setClock(400000);
|
||||
Wire.beginTransmission(0x23);
|
||||
Wire.print("Hello");
|
||||
uint8_t status = Wire.endTransmission();
|
||||
if (status==0) DIAG(F("Wire: device Found on 0x23"));
|
||||
|
||||
Wire.beginTransmission(0x23);
|
||||
Wire.write(0xde);
|
||||
Wire.endTransmission(false); // don't send stop
|
||||
Wire.requestFrom(0x23, 1);
|
||||
if (Wire.available()) {
|
||||
DIAG(F("Wire: value=x%x"), Wire.read());
|
||||
}
|
||||
uint8_t st = I2CManager.write(0x33, 0, 0);
|
||||
DIAG(F("I2CManager 0x33 st=%d \"%S\""), st,
|
||||
I2CManager.getErrorMessage(st));
|
||||
#endif
|
||||
|
||||
#if I2C_SETCLOCK
|
||||
// Test I2C clock changes
|
||||
// Set up two I2C request blocks
|
||||
I2CRB rb1, rb2;
|
||||
uint8_t readBuff[32];
|
||||
rb1.setRequestParams(0x23, readBuff, sizeof(readBuff), readBuff, sizeof(readBuff));
|
||||
rb2.setRequestParams(0x23, readBuff, sizeof(readBuff), readBuff, sizeof(readBuff));
|
||||
// First set clock to 400kHz and then issue requests
|
||||
I2CManager.forceClock(400000);
|
||||
I2CManager.queueRequest(&rb1);
|
||||
I2CManager.queueRequest(&rb2);
|
||||
// Wait a little to allow the first transaction to start
|
||||
delayMicroseconds(2);
|
||||
// ... then request a clock speed change
|
||||
I2CManager.forceClock(100000);
|
||||
DIAG(F("I2CClock: rb1 status=%d"), rb1.wait());
|
||||
DIAG(F("I2CClock: rb2 status=%d"), rb2.wait());
|
||||
// Reset clock speed
|
||||
I2CManager.forceClock(400000);
|
||||
#endif
|
||||
|
||||
EXIOExpander::create(2200, 18, {SubBus_0, 0x65});
|
||||
//UserAddin::create(myTest, 1000);
|
||||
// ServoTurnout::create(2200, 2200, 400, 200, 0);
|
||||
// ServoTurnout::create(2200, 2200, 400, 200, 0);
|
||||
|
||||
TouchKeypad::create(2300, 16, 25, 24);
|
||||
|
||||
// GPIO
|
||||
PCF8574::create(800, 8, {SubBus_1, 0x23});
|
||||
//PCF8574::create(808, 8, {SubBus_2, 0x27});
|
||||
PCF8574::create(65000, 8, 0x27);
|
||||
|
||||
MCP23017::create(164,16,{SubBus_3, 0x20});
|
||||
//MCP23017::create(180,16,{SubBus_0, 0x27});
|
||||
Sensor::create(170, 170, 1); // Hall effect, enable pullup.
|
||||
Sensor::create(171, 171, 1);
|
||||
|
||||
// PWM (LEDs and Servos)
|
||||
// For servos, use default 50Hz pulses.
|
||||
PCA9685::create(100, 16, {SubBus_1, 0x41});
|
||||
// For LEDs, use 1kHz pulses.
|
||||
PCA9685::create(116, 16, {SubBus_1, 0x40}, 1000);
|
||||
|
||||
// 4-pin Analogue Input Module
|
||||
//ADS111x::create(4500, 4, 0x48);
|
||||
|
||||
// Laser Time-Of-Flight Sensors
|
||||
VL53L0X::create(5000, 3, {SubBus_0, 0x60}, 300, 310, 46);
|
||||
//VL53L0X::create(5003, 3, {SubBus_6, 0x61}, 300, 310, 47);
|
||||
Sensor::create(5000, 5000, 0);
|
||||
Sensor::create(5003, 5003, 0);
|
||||
// Monitor reset digital on first TOF
|
||||
//Sensor::create(46,46,0);
|
||||
|
||||
// // External 24C256 EEPROM (256kBytes) on I2C address 0x50.
|
||||
// ExternalEEPROM::create({SubBus_0, 0x50}, 256);
|
||||
|
||||
// Play up to 10 sounds on pins 10000-10009. Player is connected to Serial1 or Serial2.
|
||||
#if defined(HAVE_HWSERIAL1) && !defined(ARDUINO_ARCH_STM32)
|
||||
DFPlayer::create(10000, 14, Serial1);
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
DFPlayer::create(10000, 10, Serial3); // Pins PC11 (RX) and PC10 (TX)
|
||||
#endif
|
||||
|
||||
// Ultrasound echo device
|
||||
HCSR04::create(2000, 32, 33, 80, 85 /*, HCSR04::LOOP */);
|
||||
Sensor::create(2000, 2000, 0);
|
||||
|
||||
#if __has_include("IO_CMRI.h")
|
||||
CMRIbus::create(0, Serial2, 115200, 50, 40); // 50ms cycle, pin 40 for DE/!RE pins
|
||||
CMRInode::create(25000, 72, 0, 0, 'M'); // SMINI address 0
|
||||
for (int pin=0; pin<24; pin++) {
|
||||
Sensor::create(25000+pin, 25000+pin, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
//CMRInode::create(25072, 72, 0, 13, 'M'); // SMINI address 13
|
||||
//CMRInode::create(25144, 288, 0, 14, 'C', 144, 144); // CPNODE address 14
|
||||
|
||||
#ifdef NETWORK_PRESENT
|
||||
// Define remote pins to be used. The range of remote pins is like a common data area shared
|
||||
// between all nodes.
|
||||
// For outputs, a write to a remote VPIN causes a message to be sent to another node, which then performs
|
||||
// the write operation on the device VPIN that is local to that node.
|
||||
// For inputs, the state of remote input VPIN is read on the node where it is connected, and then
|
||||
// sent to other nodes in the system where the state is saved and processed. Updates are sent on change, and
|
||||
// also periodically if no changes.
|
||||
//
|
||||
// Each definition is a triple of remote node, remote pin, indexed by relative pin. Up to 224 rpins can
|
||||
// be configured (per node). This is to fit into a 32-byte packet.
|
||||
REMOTEPINS rpins[] = {
|
||||
{30,164,RPIN_IN} , //4000 Node 30, first MCP23017 pin, input
|
||||
{30,165,RPIN_IN}, //4001 Node 30, second MCP23017 pin, input
|
||||
{30,166,RPIN_OUT}, //4002 Node 30, third MCP23017 pin, output
|
||||
{30,166,RPIN_OUT}, //4003 Node 30, fourth MCP23017 pin, output
|
||||
{30,100,RPIN_INOUT}, //4004 Node 30, first PCA9685 servo pin
|
||||
{30,101,RPIN_INOUT}, //4005 Node 30, second PCA9685 servo pin
|
||||
{30,102,RPIN_INOUT}, //4006 Node 30, third PCA9685 servo pin
|
||||
{30,103,RPIN_INOUT}, //4007 Node 30, fourth PCA9685 servo pin
|
||||
{30,24,RPIN_IN}, //4008 Node 30, Arduino pin D24
|
||||
{30,25,RPIN_IN}, //4009 Node 30, Arduino pin D25
|
||||
{30,26,RPIN_IN}, //4010 Node 30, Arduino pin D26
|
||||
{30,27,RPIN_IN}, //4011 Node 30, Arduino pin D27
|
||||
{30,1000,RPIN_OUT}, //4012 Node 30, DFPlayer playing flag (when read) / Song selector (when written)
|
||||
{30,5000,RPIN_IN}, //4013 Node 30, VL53L0X detect pin
|
||||
{30,VPIN_NONE,0}, //4014 Node 30, spare
|
||||
{30,VPIN_NONE,0}, //4015 Node 30, spare
|
||||
|
||||
{31,164,RPIN_IN} , //4016 Node 31, first MCP23017 pin, input
|
||||
{31,165,RPIN_IN}, //4017 Node 31, second MCP23017 pin, input
|
||||
{31,166,RPIN_OUT}, //4018 Node 31, third MCP23017 pin, output
|
||||
{31,166,RPIN_OUT}, //4019 Node 31, fourth MCP23017 pin, output
|
||||
{31,100,RPIN_INOUT}, //4020 Node 31, first PCA9685 servo pin
|
||||
{31,101,RPIN_INOUT}, //4021 Node 31, second PCA9685 servo pin
|
||||
{31,102,RPIN_INOUT}, //4022 Node 31, third PCA9685 servo pin
|
||||
{31,103,RPIN_INOUT}, //4023 Node 31, fourth PCA9685 servo pin
|
||||
{31,24,RPIN_IN}, //4024 Node 31, Arduino pin D24
|
||||
{31,25,RPIN_IN}, //4025 Node 31, Arduino pin D25
|
||||
{31,26,RPIN_IN}, //4026 Node 31, Arduino pin D26
|
||||
{31,27,RPIN_IN}, //4027 Node 31, Arduino pin D27
|
||||
{31,3,RPIN_IN}, //4028 Node 31, Arduino pin D3
|
||||
{31,VPIN_NONE,0}, //4029 Node 31, spare
|
||||
{31,VPIN_NONE,0}, //4030 Node 31, spare
|
||||
{31,VPIN_NONE,0} //4031 Node 31, spare
|
||||
};
|
||||
// FirstVPIN, nPins, thisNode, pinDefs, CEPin, CSNPin
|
||||
// Net_RF24 *rf24Driver = new Net_RF24(48, 49);
|
||||
// Network<Net_RF24>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, rf24Driver);
|
||||
#if NODE==30
|
||||
//Net_ENC28J60 *encDriver = new Net_ENC28J60(49);
|
||||
//Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, encDriver);
|
||||
#elif NODE==31
|
||||
Net_ENC28J60 *encDriver = new Net_ENC28J60(53);
|
||||
Network<Net_ENC28J60>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, encDriver);
|
||||
#else
|
||||
Net_Ethernet *etherDriver = new Net_Ethernet();
|
||||
Network<Net_Ethernet>::create(4000, NUMREMOTEPINS(rpins), NODE, rpins, etherDriver);
|
||||
#endif
|
||||
for (int i=0; i<=32; i++)
|
||||
Sensor::create(4000+i, 4000+i, 0);
|
||||
#endif
|
||||
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
//PCF8574::create(1900, 8, 0x27);
|
||||
Sensor::create(1900,100,1);
|
||||
Sensor::create(1901,101,1);
|
||||
#endif
|
||||
|
||||
}
|
||||
#endif // IO_NO_HAL
|
@@ -51,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>(2, 0x27, 20, 4);
|
||||
// HALDisplay<LiquidCrystal>::create(2, 0x27, 20, 4);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
|
105
platformio.ini
105
platformio.ini
@@ -20,18 +20,17 @@ default_envs =
|
||||
ESP32
|
||||
Nucleo-F411RE
|
||||
Nucleo-F446RE
|
||||
Teensy3.2
|
||||
Teensy3.5
|
||||
Teensy3.6
|
||||
Teensy4.0
|
||||
Teensy4.1
|
||||
Teensy3_2
|
||||
Teensy3_5
|
||||
Teensy3_6
|
||||
Teensy4_0
|
||||
Teensy4_1
|
||||
src_dir = .
|
||||
include_dir = .
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
monitor_filters = time
|
||||
; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0
|
||||
; monitor_filters = time
|
||||
|
||||
[env:samd21-dev-usb]
|
||||
platform = atmelsam
|
||||
@@ -53,14 +52,14 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:Arduino M0]
|
||||
[env:Arduino-M0]
|
||||
platform = atmelsam
|
||||
board = mzeroUSB
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:mega2560-debug]
|
||||
platform = atmelavr
|
||||
@@ -72,7 +71,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
|
||||
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
|
||||
|
||||
[env:mega2560-no-HAL]
|
||||
platform = atmelavr
|
||||
@@ -84,7 +83,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -DIO_NO_HAL
|
||||
build_flags = -DIO_NO_HAL
|
||||
|
||||
[env:mega2560-I2C-wire]
|
||||
platform = atmelavr
|
||||
@@ -108,7 +107,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = ; -DDIAG_LOOPTIMES
|
||||
build_flags =
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@@ -173,6 +172,8 @@ board = esp32dev
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Nucleo-F411RE]
|
||||
platform = ststm32
|
||||
@@ -188,11 +189,76 @@ platform = ststm32
|
||||
board = nucleo_f446re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable -DDIAG_LOOPTIMES ; -DDIAG_IO
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Teensy3.2]
|
||||
; 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
|
||||
framework = arduino
|
||||
@@ -200,7 +266,7 @@ build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy3.5]
|
||||
[env:Teensy3_5]
|
||||
platform = teensy
|
||||
board = teensy35
|
||||
framework = arduino
|
||||
@@ -208,7 +274,7 @@ build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy3.6]
|
||||
[env:Teensy3_6]
|
||||
platform = teensy
|
||||
board = teensy36
|
||||
framework = arduino
|
||||
@@ -216,7 +282,7 @@ build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy4.0]
|
||||
[env:Teensy4_0]
|
||||
platform = teensy
|
||||
board = teensy40
|
||||
framework = arduino
|
||||
@@ -224,11 +290,10 @@ build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
[env:Teensy4.1]
|
||||
[env:Teensy4_1]
|
||||
platform = teensy
|
||||
board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
|
||||
lib_ignore =
|
||||
|
71
version.h
71
version.h
@@ -3,15 +3,76 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.36"
|
||||
#define VERSION "5.1.9"
|
||||
// 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
|
||||
// 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)
|
||||
// - Optimize analog read STM32
|
||||
// 4.2.56 - Update IO_RotaryEncoder.h:
|
||||
// - Improved I2C communication, non-blocking reads
|
||||
// - Enable sending positions to the encoder from EXRAIL via SERVO()
|
||||
// 4.2.55 - Optimize analog read for AVR
|
||||
// 4.2.54 - EX8874 shield in config.example.h
|
||||
// - Fix: Better warnings for pin number errors
|
||||
// - Fix: Default roster list possible in Withrottle and <jR>
|
||||
// - Fix: Pin handling supports pins up to 254
|
||||
// 4.2.53 - Fix: Fault pin handling made more straight forward
|
||||
// 4.2.52 - Experimental support for sabertooth motor controller on ESP32
|
||||
// 4.2.51 - Add DISABLE_PROG to disable programming to save RAM/Flash
|
||||
// 4.2.50 - Fixes: estop all, turnout eeprom, cab ID check
|
||||
// 4.2.49 - Exrail SPEED take notice of external direction change
|
||||
// 4.2.48 - BROADCAST/WITHROTTLE Exrail macros
|
||||
// 4.2.47 - Correct response to <JA 0>
|
||||
// 4.2.46 - Support boards with inverted fault pin
|
||||
// 4.2.45 - Add ONCLOCKMINS to FastClock to allow hourly repeat events
|
||||
// 4.2.44 - Add PowerShell installer EX-CommandStation-installer.exe
|
||||
// 4.2.43 - Fix STM32 set right port mode bits for analog
|
||||
// 4.2.42 - Added EXRAIL TURNOUTL Macro definition
|
||||
// 4.2.41 - Move HAl startup to ASAP in setup()
|
||||
// - Fix DNOU8 output pin setup to all LOW
|
||||
// 4.2.40 - Automatically detect conflicting default I2C devices and disable
|
||||
// 4.2.39 - DFplayer driver now polls device to detect failures and errors.
|
||||
// 4.2.38 - Clean up compiler warning when IO_RotaryEncoder.h included
|
||||
// 4.2.37 - Add new FLAGS HAL device for communications to/from EX-RAIL;
|
||||
// - Fix diag display of high VPINs within IODevice class.
|
||||
// 4.2.36 - do not broadcast a turnout state that has not changed
|
||||
// - Use A2/A3 for current sensing on ESP32 + Motor Shield
|
||||
// 4.2.35 - add <z> direct pin manipulation command
|
||||
// 4.2.34 - Completely fix EX-IOExpander analogue inputs
|
||||
// 4.2.33 - Fix EX-IOExpander non-working analogue inputs
|
||||
// 4.2.32 - Fix LCD/Display bugfixes from 4.2.29
|
||||
// 4.2.31 - Removes EXRAIL statup from top of file. (BREAKING CHANGE !!)
|
||||
// Just add AUTOSTART to the top of your myAutomation.h to restore this function.
|
||||
// Just add AUTOSTART to the top of your myAutomation.h to restore this function.
|
||||
// 4.2.30 - Fixes/enhancements to EX-IOExpander device driver.
|
||||
// 4.2.29 - Bugfix Scroll LCD without empty lines and consistent
|
||||
// 4.2.28 - Reinstate use of timer11 in STM32 - remove HA mode.
|
||||
@@ -64,6 +125,10 @@
|
||||
// 4.2.11 Exrail IFLOCO feature added
|
||||
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
|
||||
// IO_EXIOExpander.h input speed optimisation
|
||||
// ONCLOCK and ONCLOCKTIME command added to EXRAIL for EX-FastCLock
|
||||
// <JC> Serial command added for EX-FastClock
|
||||
// <jC> Broadcast added for EX-FastClock
|
||||
// IO_EXFastClock.h added for I2C FastClock connection
|
||||
// 4.2.9 duinoNodes support
|
||||
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
|
||||
// Withrottle connect/disconnect improvements
|
||||
|
Reference in New Issue
Block a user