mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 18:33:44 +02:00
Compare commits
230 Commits
devel-nmck
...
v4.2.60-De
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
1a3d295564 | ||
|
3b6789ef01 | ||
|
c472f48d93 | ||
|
94e9c2021b | ||
|
9aad2e3206 | ||
|
ecc366cbd1 | ||
|
f4e3ca7c81 | ||
|
991bda63e0 | ||
|
5164bd143c | ||
|
3759fc2a1a | ||
|
df7b890758 | ||
|
6d802f3a73 | ||
|
9d953c70b8 | ||
|
6781e44fdd | ||
|
a3c9800aba | ||
|
efdbfcb030 | ||
|
28d9843133 | ||
|
4eaad2d05b | ||
|
72d131035e | ||
|
2d1e695ac7 | ||
|
e780c40b34 | ||
|
7addb13785 | ||
|
b6f8889e8c | ||
|
33306219c8 | ||
|
d857c4f2e4 | ||
|
70fae16ab3 | ||
|
f465020e93 | ||
|
235bcc9ff0 | ||
|
d2cc60812d | ||
|
75f274e3b7 | ||
|
ff53b90034 | ||
|
1daa0a9ba9 | ||
|
294b9693c5 | ||
|
16e44eb11a | ||
|
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 | ||
|
2c943d250e | ||
|
89664eff9d | ||
f8b054cf6a | |||
|
d2a8aebd0f | ||
|
86c3020672 | ||
|
60ea7f081a | ||
|
f348857ddb | ||
|
bdd4bc9999 | ||
|
8a425fe0ef | ||
|
1ec378281b | ||
|
51a480dff3 | ||
|
f0ee8aeb85 | ||
|
94d0aa92d9 | ||
|
82c447e8a4 | ||
|
a3d03ac68c | ||
|
b183439a5b | ||
|
d51281f1f2 | ||
|
168368864c | ||
|
a75ca00e3c | ||
|
7ab5f556d9 | ||
|
337bd969a1 | ||
|
21c99c8694 | ||
|
4087cd6e29 | ||
|
2fb485847f | ||
|
43c7baf8f5 | ||
|
2e1a2d38e3 | ||
|
fe18341994 | ||
|
329dc41452 | ||
|
c4b4e11a67 | ||
|
e55dc51bdb | ||
|
5dd2770442 | ||
|
d67b07fe46 | ||
|
3073061596 | ||
|
278347756a | ||
|
3d35e78533 | ||
|
75b5806eb7 | ||
|
4a1210fa64 | ||
|
72c76391a5 | ||
|
325d4bce73 | ||
|
27ba551986 | ||
|
ce12f3b6c5 | ||
|
48cd567bda | ||
|
25676aab6b | ||
|
42bddb587e | ||
|
a51cefbdeb | ||
|
5fc925bfc6 | ||
|
ca2e5e6ce3 | ||
|
9728e15e0a | ||
|
c83741d2b4 | ||
|
df3eb11eb9 | ||
|
d89dd0d1fa | ||
|
95d0120204 | ||
|
0cc07ed1df | ||
|
5e60fb4e27 | ||
|
881463ada9 | ||
|
45cf610028 | ||
|
471b8ac8e1 | ||
|
3ae1859ec7 | ||
|
679e5885c4 | ||
|
59c6c1e5af | ||
|
f94a5f971e | ||
|
1d29436008 | ||
|
bec8aea5a5 | ||
|
4f23dbc984 | ||
|
46070e2999 | ||
|
31ecba08d8 | ||
|
22c0bff697 | ||
|
19bbb186e7 | ||
|
a17a55d904 | ||
|
b969563d35 | ||
|
f0c1ea958c | ||
|
ca4592dc3e | ||
|
0663cc6138 | ||
|
5fb10fa6d0 | ||
|
1d47dce473 | ||
|
8a126906f3 | ||
|
b01e4388ce | ||
|
4adcdc1b0b | ||
|
95b640686a | ||
|
f56f5d9ebe | ||
|
8c8a913678 | ||
|
711ad6f030 | ||
|
c2983efebb | ||
|
02ee8ad080 | ||
|
780c6ea162 | ||
|
8582e51483 | ||
|
cdf3927aad | ||
|
ddfc67d2e3 | ||
|
a23f5839b7 | ||
|
eddd6382d9 | ||
|
45e3e3d185 | ||
|
5e9ae4a0ac | ||
|
39d953ed29 |
8
.gitignore
vendored
8
.gitignore
vendored
@@ -7,15 +7,9 @@ Release/*
|
||||
.pio/
|
||||
.vscode/
|
||||
config.h
|
||||
.vscode/*
|
||||
# mySetup.h
|
||||
mySetup.cpp
|
||||
myHal.cpp
|
||||
# myAutomation.h
|
||||
myFilter.cpp
|
||||
# myAutomation.h
|
||||
# myLayout.h
|
||||
my*.h
|
||||
!my*.example.h
|
||||
.vscode/extensions.json
|
||||
.vscode/extensions.json
|
||||
compile_commands.json
|
||||
|
10
.vscode/extensions.json
vendored
10
.vscode/extensions.json
vendored
@@ -1,10 +0,0 @@
|
||||
{
|
||||
// See http://go.microsoft.com/fwlink/?LinkId=827846
|
||||
// for the documentation about the extensions.json format
|
||||
"recommendations": [
|
||||
"platformio.platformio-ide"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"ms-vscode.cpptools-extension-pack"
|
||||
]
|
||||
}
|
12
.vscode/settings.json
vendored
12
.vscode/settings.json
vendored
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"array": "cpp",
|
||||
"deque": "cpp",
|
||||
"string": "cpp",
|
||||
"unordered_map": "cpp",
|
||||
"vector": "cpp",
|
||||
"string_view": "cpp",
|
||||
"initializer_list": "cpp",
|
||||
"cstdint": "cpp"
|
||||
}
|
||||
}
|
@@ -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
|
||||
@@ -167,7 +168,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
|
||||
}
|
||||
|
||||
@@ -179,10 +180,7 @@ void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte
|
||||
{
|
||||
case 1:
|
||||
if (clocktime != lastclocktime){
|
||||
if (Diag::CMD) {
|
||||
DIAG(F("Clock Command Received"));
|
||||
DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate);
|
||||
}
|
||||
// CAH. DIAG removed because LCD does it anyway.
|
||||
LCD(6,F("Clk Time:%d Sp %d"), clocktime, clockrate);
|
||||
// look for an event for this time
|
||||
RMFT2::clockEvent(clocktime,1);
|
||||
@@ -207,6 +205,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
|
||||
@@ -230,9 +261,10 @@ 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) {
|
||||
broadcastReply(COMMAND_TYPE, format,trackLetter,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
|
||||
@@ -50,7 +53,8 @@ public :
|
||||
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);
|
||||
|
||||
|
@@ -75,6 +75,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));
|
||||
@@ -96,19 +105,9 @@ void setup()
|
||||
#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();
|
||||
|
13
DCC.cpp
13
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
|
||||
@@ -576,9 +575,11 @@ void DCC::setLocoId(int id,ACK_CALLBACK callback) {
|
||||
|
||||
void DCC::forgetLoco(int cab) { // removes any speed reminders for this loco
|
||||
setThrottle2(cab,1); // ESTOP this loco if still on track
|
||||
int reg=lookupSpeedTable(cab);
|
||||
if (reg>=0) speedTable[reg].loco=0;
|
||||
setThrottle2(cab,1); // ESTOP if this loco still on track
|
||||
int reg=lookupSpeedTable(cab, false);
|
||||
if (reg>=0) {
|
||||
speedTable[reg].loco=0;
|
||||
setThrottle2(cab,1); // ESTOP if this loco still on track
|
||||
}
|
||||
}
|
||||
void DCC::forgetAllLocos() { // removes all speed reminders
|
||||
setThrottle2(0,1); // ESTOP all locos still on track
|
||||
@@ -691,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);
|
||||
@@ -414,7 +414,7 @@ void DCCACK::callback(int value) {
|
||||
(ackManagerCallback)( value);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void DCCACK::checkAck(byte sentResetsSincePacket) {
|
||||
if (!ackPending) return;
|
||||
|
@@ -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
|
||||
@@ -46,16 +47,14 @@
|
||||
#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 +62,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
|
||||
@@ -79,6 +82,8 @@ const int16_t HASH_KEYWORD_TT=2688;
|
||||
const int16_t HASH_KEYWORD_VPIN=-415;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
const int16_t HASH_KEYWORD_G='G';
|
||||
const int16_t HASH_KEYWORD_I='I';
|
||||
const int16_t HASH_KEYWORD_R='R';
|
||||
const int16_t HASH_KEYWORD_T='T';
|
||||
const int16_t HASH_KEYWORD_X='X';
|
||||
@@ -283,6 +288,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
|
||||
@@ -342,6 +349,20 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
break;
|
||||
|
||||
case 'z': // direct pin manipulation
|
||||
if (p[0]==0) break;
|
||||
if (params==1) { // <z vpin | -vpin>
|
||||
if (p[0]>0) IODevice::write(p[0],HIGH);
|
||||
else IODevice::write(-p[0],LOW);
|
||||
return;
|
||||
}
|
||||
if (params>=2 && params<=4) { // <z vpin ana;og profile duration>
|
||||
// unused params default to 0
|
||||
IODevice::writeAnalogue(p[0],p[1],p[2],p[3]);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'Z': // OUTPUT <Z ...>
|
||||
if (parseZ(stream, params, p))
|
||||
return;
|
||||
@@ -352,6 +373,7 @@ 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;
|
||||
@@ -359,9 +381,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
||||
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;
|
||||
@@ -376,6 +401,7 @@ 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;
|
||||
@@ -433,6 +459,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||
{
|
||||
@@ -440,22 +467,24 @@ 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>
|
||||
}
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
@@ -471,7 +500,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;
|
||||
}
|
||||
@@ -479,9 +508,11 @@ 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>
|
||||
}
|
||||
|
||||
@@ -501,8 +532,10 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
|
||||
case 'c': // SEND METER RESPONSES <c>
|
||||
// No longer supported because of multiple tracks <c MeterName value C/V unit min max res warn>
|
||||
break;
|
||||
// No longer useful because of multiple tracks See <JG> and <JI>
|
||||
if (params>0) break;
|
||||
TrackManager::reportObsoleteCurrent(stream);
|
||||
return;
|
||||
|
||||
case 'Q': // SENSORS <Q>
|
||||
Sensor::printAll(stream);
|
||||
@@ -512,9 +545,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
Sensor::printAll(stream); //send all Sensor states
|
||||
// TODO Send stats of speed reminders table
|
||||
return;
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
@@ -583,6 +614,16 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
CommandDistributor::setClockTime(p[1], p[2], 1);
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_G: // <JG> current gauge limits
|
||||
if (params>1) break;
|
||||
TrackManager::reportGauges(stream); // <g limit...limit>
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_I: // <JI> current values
|
||||
if (params>1) break;
|
||||
TrackManager::reportCurrent(stream); // <g limit...limit>
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
@@ -611,8 +652,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;
|
||||
@@ -866,6 +911,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) {
|
||||
@@ -886,6 +932,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;
|
||||
@@ -908,11 +955,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
|
||||
@@ -941,7 +988,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)
|
||||
|
22
DCCTimer.h
22
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,15 @@ 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();
|
||||
// bit array of used pins (max 16)
|
||||
static uint16_t usedpins;
|
||||
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;
|
||||
ledcAttachPin(pin, cnt_channel);
|
||||
ledcSetup(cnt_channel, 1000, 8);
|
||||
} 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,37 +30,137 @@
|
||||
#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
|
||||
// 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, Tx=PB6 -- CN10 pins 33 and 17 - 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.
|
||||
// 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_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
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Experimental code for High Accuracy (HA) DCC Signal mode
|
||||
// Warning - use of TIM2 and TIM3 can affect the use of analogWrite() function on certain pins,
|
||||
// which is used by the DC motor types.
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// INTERRUPT_CALLBACK interruptHandler=0;
|
||||
// // Let's use STM32's timer #2 which supports hardware pulse generation on pin D13.
|
||||
// // Also, timer #3 will do hardware pulses on pin D12. This gives
|
||||
// // accurate timing, independent of the latency of interrupt handling.
|
||||
// // We only need to interrupt on one of these (TIM2), the other will just generate
|
||||
// // pulses.
|
||||
// HardwareTimer timer(TIM2);
|
||||
// HardwareTimer timerAux(TIM3);
|
||||
// static bool tim2ModeHA = false;
|
||||
// static bool tim3ModeHA = false;
|
||||
|
||||
// // Timer IRQ handler
|
||||
// void Timer_Handler() {
|
||||
// interruptHandler();
|
||||
// }
|
||||
|
||||
// void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
// interruptHandler=callback;
|
||||
// noInterrupts();
|
||||
|
||||
// // adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
||||
// timer.pause();
|
||||
// timerAux.pause();
|
||||
// timer.setPrescaleFactor(1);
|
||||
// timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
// timer.attachInterrupt(Timer_Handler);
|
||||
// timer.refresh();
|
||||
// timerAux.setPrescaleFactor(1);
|
||||
// timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
// timerAux.refresh();
|
||||
|
||||
// timer.resume();
|
||||
// timerAux.resume();
|
||||
|
||||
// interrupts();
|
||||
// }
|
||||
|
||||
// bool DCCTimer::isPWMPin(byte pin) {
|
||||
// // Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12.
|
||||
// // Enable the appropriate timer channel.
|
||||
// switch (pin) {
|
||||
// case 12:
|
||||
// return true;
|
||||
// case 13:
|
||||
// return true;
|
||||
// default:
|
||||
// return false;
|
||||
// }
|
||||
// }
|
||||
|
||||
// void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// // Set the timer so that, at the next counter overflow, the requested
|
||||
// // pin state is activated automatically before the interrupt code runs.
|
||||
// // TIM2 is timer, TIM3 is timerAux.
|
||||
// switch (pin) {
|
||||
// case 12:
|
||||
// if (!tim3ModeHA) {
|
||||
// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12);
|
||||
// tim3ModeHA = true;
|
||||
// }
|
||||
// if (high)
|
||||
// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
|
||||
// else
|
||||
// TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
|
||||
// break;
|
||||
// case 13:
|
||||
// if (!tim2ModeHA) {
|
||||
// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13);
|
||||
// tim2ModeHA = true;
|
||||
// }
|
||||
// if (high)
|
||||
// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
|
||||
// else
|
||||
// TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
// void DCCTimer::clearPWM() {
|
||||
// timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
||||
// tim2ModeHA = false;
|
||||
// timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
||||
// tim3ModeHA = false;
|
||||
// }
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
// Let's use STM32's timer #2 which supports hardware pulse generation on pin D13.
|
||||
// Also, timer #3 will do hardware pulses on pin D12. This gives
|
||||
// accurate timing, independent of the latency of interrupt handling.
|
||||
// We only need to interrupt on one of these (TIM2), the other will just generate
|
||||
// pulses.
|
||||
HardwareTimer timer(TIM2);
|
||||
HardwareTimer timerAux(TIM3);
|
||||
// Let's use STM32's timer #11 until disabused of this notion
|
||||
// Timer #11 is used for "servo" library, but as DCC-EX is not using
|
||||
// this libary, we should be free and clear.
|
||||
HardwareTimer timer(TIM11);
|
||||
|
||||
// Timer IRQ handler
|
||||
void Timer_Handler() {
|
||||
void Timer11_Handler() {
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
@@ -70,59 +170,31 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
|
||||
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
||||
timer.pause();
|
||||
timerAux.pause();
|
||||
timer.setPrescaleFactor(1);
|
||||
// timer.setOverflow(CLOCK_CYCLES * 2);
|
||||
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
timer.attachInterrupt(Timer_Handler);
|
||||
timer.attachInterrupt(Timer11_Handler);
|
||||
timer.refresh();
|
||||
timerAux.setPrescaleFactor(1);
|
||||
timerAux.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
timerAux.refresh();
|
||||
|
||||
timer.resume();
|
||||
timerAux.resume();
|
||||
|
||||
interrupts();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
// Timer 2 Channel 1 controls pin D13, and Timer3 Channel 1 controls D12.
|
||||
// Enable the appropriate timer channel.
|
||||
switch (pin) {
|
||||
case 12:
|
||||
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D12);
|
||||
return true;
|
||||
case 13:
|
||||
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, D13);
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
// there's no support yet for High Accuracy, so for now return false
|
||||
// return digitalPinHasPWM(pin);
|
||||
return false;
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// Set the timer so that, at the next counter overflow, the requested
|
||||
// pin state is activated automatically before the interrupt code runs.
|
||||
// TIM2 is timer, TIM3 is timerAux.
|
||||
switch (pin) {
|
||||
case 12:
|
||||
if (high)
|
||||
TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
|
||||
else
|
||||
TIM3->CCMR1 = (TIM3->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
|
||||
break;
|
||||
case 13:
|
||||
if (high)
|
||||
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_0;
|
||||
else
|
||||
TIM2->CCMR1 = (TIM2->CCMR1 & ~TIM_CCMR1_OC1M_Msk) | TIM_CCMR1_OC1M_1;
|
||||
break;
|
||||
}
|
||||
// TODO: High Accuracy mode is not supported as yet, and may never need to be
|
||||
(void) pin;
|
||||
(void) high;
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
||||
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
||||
return;
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
@@ -163,10 +235,16 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
|
||||
#endif
|
||||
// For now, define the max of 16 ports - some variants have more, but this not **yet** supported
|
||||
#define NUM_ADC_INPUTS 16
|
||||
// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
uint8_t ADCee::highestPin = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
bool adc1configured = false;
|
||||
@@ -176,10 +254,13 @@ int16_t ADCee::ADCmax() {
|
||||
}
|
||||
|
||||
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
|
||||
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 channel (only valid for ADC1!)
|
||||
GPIO_TypeDef * gpioBase;
|
||||
|
||||
@@ -197,12 +278,20 @@ int ADCee::init(uint8_t pin) {
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||
gpioBase = GPIOC;
|
||||
break;
|
||||
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
|
||||
else
|
||||
@@ -214,14 +303,21 @@ int ADCee::init(uint8_t pin) {
|
||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = ADC1->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 and analogchans if this is the first invocation of init.
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||
}
|
||||
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
|
||||
if (id > highestPin) highestPin = id; // Store our highest pin in use
|
||||
|
||||
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -230,7 +326,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;
|
||||
@@ -245,7 +341,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;
|
||||
|
||||
@@ -256,11 +352,13 @@ void ADCee::scan() {
|
||||
// found value
|
||||
analogvals[id] = ADC1->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;
|
||||
}
|
||||
@@ -271,18 +369,20 @@ 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
|
||||
// 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;
|
||||
#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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -307,4 +407,4 @@ void ADCee::begin() {
|
||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
125
Display.cpp
125
Display.cpp
@@ -36,7 +36,7 @@
|
||||
* not held up significantly. The exception to this is when
|
||||
* the loop2() function is called with force=true, where
|
||||
* a screen update is executed to completion. This is normally
|
||||
* only noMoreRowsToDisplay during start-up.
|
||||
* only done during start-up.
|
||||
* The scroll mode is selected by defining SCROLLMODE as 0, 1 or 2
|
||||
* in the config.h.
|
||||
* #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
||||
@@ -51,11 +51,10 @@
|
||||
Display::Display(DisplayDevice *deviceDriver) {
|
||||
_deviceDriver = deviceDriver;
|
||||
// Get device dimensions in characters (e.g. 16x2).
|
||||
numCharacterColumns = _deviceDriver->getNumCols();
|
||||
numCharacterRows = _deviceDriver->getNumRows();;
|
||||
numScreenColumns = _deviceDriver->getNumCols();
|
||||
numScreenRows = _deviceDriver->getNumRows();
|
||||
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
|
||||
rowBuffer[row][0] = '\0';
|
||||
topRow = ROW_INITIAL; // loop2 will fill from row 0
|
||||
|
||||
addDisplay(0); // Add this display as display number 0
|
||||
};
|
||||
@@ -69,20 +68,19 @@ void Display::_clear() {
|
||||
_deviceDriver->clearNative();
|
||||
for (uint8_t row = 0; row < MAX_CHARACTER_ROWS; row++)
|
||||
rowBuffer[row][0] = '\0';
|
||||
topRow = ROW_INITIAL; // loop2 will fill from row 0
|
||||
}
|
||||
|
||||
void Display::_setRow(uint8_t line) {
|
||||
hotRow = line;
|
||||
hotCol = 0;
|
||||
rowBuffer[hotRow][0] = 0; // Clear existing text
|
||||
rowBuffer[hotRow][0] = '\0'; // Clear existing text
|
||||
}
|
||||
|
||||
size_t Display::_write(uint8_t b) {
|
||||
if (hotRow >= MAX_CHARACTER_ROWS || hotCol >= MAX_CHARACTER_COLS) return -1;
|
||||
rowBuffer[hotRow][hotCol] = b;
|
||||
hotCol++;
|
||||
rowBuffer[hotRow][hotCol] = 0;
|
||||
rowBuffer[hotRow][hotCol] = '\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -109,8 +107,8 @@ Display *Display::loop2(bool force) {
|
||||
return NULL;
|
||||
} else {
|
||||
// force full screen update from the beginning.
|
||||
rowFirst = ROW_INITIAL;
|
||||
rowNext = ROW_INITIAL;
|
||||
rowFirst = 0;
|
||||
rowCurrent = 0;
|
||||
bufferPointer = 0;
|
||||
noMoreRowsToDisplay = false;
|
||||
slot = 0;
|
||||
@@ -118,15 +116,20 @@ Display *Display::loop2(bool force) {
|
||||
|
||||
do {
|
||||
if (bufferPointer == 0) {
|
||||
// Find a line of data to write to the screen.
|
||||
if (rowFirst == ROW_INITIAL) rowFirst = rowNext;
|
||||
if (findNextNonBlankRow()) {
|
||||
// Search for non-blank row
|
||||
while (!noMoreRowsToDisplay) {
|
||||
if (!isCurrentRowBlank()) break;
|
||||
moveToNextRow();
|
||||
if (rowCurrent == rowFirst) noMoreRowsToDisplay = true;
|
||||
}
|
||||
|
||||
if (noMoreRowsToDisplay) {
|
||||
// No non-blank lines left, so draw blank line
|
||||
buffer[0] = '\0';
|
||||
} else {
|
||||
// Non-blank line found, so copy it (including terminator)
|
||||
for (uint8_t i = 0; i <= MAX_CHARACTER_COLS; i++)
|
||||
buffer[i] = rowBuffer[rowNext][i];
|
||||
} else {
|
||||
// No non-blank lines left, so draw a blank line
|
||||
buffer[0] = 0;
|
||||
buffer[i] = rowBuffer[rowCurrent][i];
|
||||
}
|
||||
_deviceDriver->setRowNative(slot); // Set position for display
|
||||
charIndex = 0;
|
||||
@@ -142,21 +145,49 @@ Display *Display::loop2(bool force) {
|
||||
}
|
||||
|
||||
if (++charIndex >= MAX_CHARACTER_COLS) {
|
||||
// Screen slot completed, move to next slot on screen
|
||||
// Screen slot completed, move to next nonblank row
|
||||
bufferPointer = 0;
|
||||
for (;;) {
|
||||
moveToNextRow();
|
||||
if (rowCurrent == rowFirst) {
|
||||
noMoreRowsToDisplay = true;
|
||||
break;
|
||||
}
|
||||
if (!isCurrentRowBlank()) break;
|
||||
}
|
||||
// Move to next screen slot, if available
|
||||
slot++;
|
||||
if (slot >= numCharacterRows) {
|
||||
// Last slot on screen written, reset ready for next screen update.
|
||||
#if SCROLLMODE==2
|
||||
if (!noMoreRowsToDisplay) {
|
||||
// On next refresh, restart one row on from previous start.
|
||||
rowNext = rowFirst;
|
||||
findNextNonBlankRow();
|
||||
if (slot >= numScreenRows) {
|
||||
// Last slot on screen written, so get ready for next screen update.
|
||||
#if SCROLLMODE==0
|
||||
// Scrollmode 0 scrolls continuously. If the rows fit on the screen,
|
||||
// then restart at row 0, but otherwise continue with the row
|
||||
// after the last one displayed.
|
||||
if (countNonBlankRows() <= numScreenRows)
|
||||
rowCurrent = 0;
|
||||
rowFirst = rowCurrent;
|
||||
#elif SCROLLMODE==1
|
||||
// Scrollmode 1 scrolls by page, so if the last page has just completed then
|
||||
// next time restart with row 0.
|
||||
if (noMoreRowsToDisplay)
|
||||
rowFirst = rowCurrent = 0;
|
||||
#else
|
||||
// Scrollmode 2 scrolls by row. If the rows don't fit on the screen,
|
||||
// then start one row further on next time. If they do fit, then
|
||||
// show them in order and start next page at row 0.
|
||||
if (countNonBlankRows() <= numScreenRows) {
|
||||
rowFirst = rowCurrent = 0;
|
||||
} else {
|
||||
// Find first non-blank row after the previous first row
|
||||
rowCurrent = rowFirst;
|
||||
do {
|
||||
moveToNextRow();
|
||||
} while (isCurrentRowBlank());
|
||||
rowFirst = rowCurrent;
|
||||
}
|
||||
#endif
|
||||
noMoreRowsToDisplay = false;
|
||||
slot = 0;
|
||||
rowFirst = ROW_INITIAL;
|
||||
lastScrollTime = currentMillis;
|
||||
return NULL;
|
||||
}
|
||||
@@ -167,30 +198,22 @@ Display *Display::loop2(bool force) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool Display::findNextNonBlankRow() {
|
||||
while (!noMoreRowsToDisplay) {
|
||||
if (rowNext == ROW_INITIAL)
|
||||
rowNext = 0;
|
||||
else
|
||||
rowNext = rowNext + 1;
|
||||
if (rowNext >= MAX_CHARACTER_ROWS) rowNext = ROW_INITIAL;
|
||||
#if SCROLLMODE == 1
|
||||
// Finished if we've looped back to start
|
||||
if (rowNext == ROW_INITIAL) {
|
||||
noMoreRowsToDisplay = true;
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
// Finished if we're back to the first one shown
|
||||
if (rowNext == rowFirst) {
|
||||
noMoreRowsToDisplay = true;
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
if (rowBuffer[rowNext][0] != 0) {
|
||||
// Found non-blank row
|
||||
return true;
|
||||
}
|
||||
bool Display::isCurrentRowBlank() {
|
||||
return (rowBuffer[rowCurrent][0] == '\0');
|
||||
}
|
||||
|
||||
void Display::moveToNextRow() {
|
||||
// Skip blank rows
|
||||
if (++rowCurrent >= MAX_CHARACTER_ROWS)
|
||||
rowCurrent = 0;
|
||||
}
|
||||
|
||||
uint8_t Display::countNonBlankRows() {
|
||||
uint8_t count = 0;
|
||||
for (uint8_t rowNumber=0; rowNumber<MAX_CHARACTER_ROWS; rowNumber++) {
|
||||
if (rowBuffer[rowNumber][0] != '\0')
|
||||
count++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
|
15
Display.h
15
Display.h
@@ -40,7 +40,6 @@ public:
|
||||
static const int MAX_CHARACTER_ROWS = 8;
|
||||
static const int MAX_CHARACTER_COLS = MAX_MSG_SIZE;
|
||||
static const long DISPLAY_SCROLL_TIME = 3000; // 3 seconds
|
||||
static const uint8_t ROW_INITIAL = 255;
|
||||
|
||||
private:
|
||||
DisplayDevice *_deviceDriver;
|
||||
@@ -48,16 +47,15 @@ private:
|
||||
unsigned long lastScrollTime = 0;
|
||||
uint8_t hotRow = 0;
|
||||
uint8_t hotCol = 0;
|
||||
uint8_t topRow = 0;
|
||||
uint8_t slot = 0;
|
||||
uint8_t rowFirst = ROW_INITIAL;
|
||||
uint8_t rowNext = ROW_INITIAL;
|
||||
uint8_t rowFirst = 0;
|
||||
uint8_t rowCurrent = 0;
|
||||
uint8_t charIndex = 0;
|
||||
char buffer[MAX_CHARACTER_COLS + 1];
|
||||
char* bufferPointer = 0;
|
||||
bool noMoreRowsToDisplay = false;
|
||||
uint16_t numCharacterRows;
|
||||
uint16_t numCharacterColumns = MAX_CHARACTER_COLS;
|
||||
uint16_t numScreenRows;
|
||||
uint16_t numScreenColumns = MAX_CHARACTER_COLS;
|
||||
|
||||
char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1];
|
||||
|
||||
@@ -69,7 +67,10 @@ public:
|
||||
void _refresh() override;
|
||||
void _displayLoop() override;
|
||||
Display *loop2(bool force);
|
||||
bool findNextNonBlankRow();
|
||||
bool findNonBlankRow();
|
||||
bool isCurrentRowBlank();
|
||||
void moveToNextRow();
|
||||
uint8_t countNonBlankRows();
|
||||
|
||||
};
|
||||
|
||||
|
@@ -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.
67
EXRAIL2.cpp
67
EXRAIL2.cpp
@@ -1,7 +1,8 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2020-2023 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -24,8 +25,8 @@
|
||||
F1. [DONE] DCC accessory packet opcodes (short and long form)
|
||||
F2. [DONE] ONAccessory catchers
|
||||
F3. [DONE] Turnout descriptions for Withrottle
|
||||
F4. Oled announcements (depends on HAL)
|
||||
F5. Withrottle roster info
|
||||
F4. [DONE] Oled announcements (depends on HAL)
|
||||
F5. [DONE] Withrottle roster info
|
||||
F6. Multi-occupancy semaphore
|
||||
F7. [DONE see AUTOSTART] Self starting sequences
|
||||
F8. Park/unpark
|
||||
@@ -105,12 +106,9 @@ uint16_t RMFT2::getOperand(byte n) {
|
||||
// getOperand static version, must be provided prog counter from loop etc.
|
||||
uint16_t RMFT2::getOperand(int progCounter,byte n) {
|
||||
int offset=progCounter+1+(n*3);
|
||||
if (offset&1) {
|
||||
byte lsb=GETHIGHFLASH(RouteCode,offset);
|
||||
byte msb=GETHIGHFLASH(RouteCode,offset+1);
|
||||
return msb<<8|lsb;
|
||||
}
|
||||
return GETHIGHFLASHW(RouteCode,offset);
|
||||
byte lsb=GETHIGHFLASH(RouteCode,offset);
|
||||
byte msb=GETHIGHFLASH(RouteCode,offset+1);
|
||||
return msb<<8|lsb;
|
||||
}
|
||||
|
||||
LookList::LookList(int16_t size) {
|
||||
@@ -201,7 +199,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
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;
|
||||
}
|
||||
@@ -211,7 +209,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;
|
||||
}
|
||||
@@ -243,8 +241,9 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
|
||||
case OPCODE_AUTOSTART:
|
||||
// automatically create a task from here at startup.
|
||||
// but we will do one at 0 anyway by default.
|
||||
if (progCounter>0) new RMFT2(progCounter);
|
||||
// Removed if (progCounter>0) check 4.2.31 because
|
||||
// default start it top of file is now removed. .
|
||||
new RMFT2(progCounter);
|
||||
break;
|
||||
|
||||
default: // Ignore
|
||||
@@ -255,7 +254,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
|
||||
DIAG(F("EXRAIL %db, fl=%d"),progCounter,MAX_FLAGS);
|
||||
|
||||
new RMFT2(0); // add the startup route
|
||||
// Removed for 4.2.31 new RMFT2(0); // add the startup route
|
||||
diag=saved_diag;
|
||||
}
|
||||
|
||||
@@ -267,16 +266,17 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) {
|
||||
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
|
||||
@@ -610,6 +610,7 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_SPEED:
|
||||
forward=DCC::getThrottleDirection(loco)^invert;
|
||||
driveLoco(operand);
|
||||
break;
|
||||
|
||||
@@ -704,11 +705,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);
|
||||
@@ -883,23 +884,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);
|
||||
@@ -920,6 +916,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:
|
||||
{
|
||||
@@ -1131,7 +1134,10 @@ 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::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
@@ -1239,7 +1245,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());
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -44,7 +45,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,
|
||||
@@ -76,7 +80,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,
|
||||
|
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -91,6 +92,7 @@
|
||||
#undef ONCLOSE
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONCLOCKMINS
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONTHROW
|
||||
@@ -99,7 +101,9 @@
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
#undef PRINT
|
||||
#ifndef DISABLE_PROG
|
||||
#undef POM
|
||||
#endif
|
||||
#undef POWEROFF
|
||||
#undef POWERON
|
||||
#undef READ_LOCO
|
||||
@@ -134,11 +138,13 @@
|
||||
#undef STOP
|
||||
#undef THROW
|
||||
#undef TURNOUT
|
||||
#undef TURNOUTL
|
||||
#undef UNJOIN
|
||||
#undef UNLATCH
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#undef WAITFOR
|
||||
#undef WITHROTTLE
|
||||
#undef XFOFF
|
||||
#undef XFON
|
||||
|
||||
@@ -208,6 +214,7 @@
|
||||
#define ONAMBER(signal_id)
|
||||
#define ONTIME(value)
|
||||
#define ONCLOCKTIME(hours,mins)
|
||||
#define ONCLOCKMINS(mins)
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
@@ -219,7 +226,9 @@
|
||||
#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
|
||||
@@ -254,11 +263,13 @@
|
||||
#define STOP
|
||||
#define THROW(id)
|
||||
#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)
|
||||
#define WITHROTTLE(msg)
|
||||
#define XFOFF(cab,func)
|
||||
#define XFON(cab,func)
|
||||
#endif
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -80,14 +81,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 +153,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;
|
||||
@@ -187,7 +190,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||
// 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 +201,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 +223,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
#include "myAutomation.h"
|
||||
default: break;
|
||||
}
|
||||
return F("");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Pass 8 Signal definitions
|
||||
@@ -316,6 +319,7 @@ 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 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),
|
||||
@@ -323,8 +327,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#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),
|
||||
@@ -361,10 +367,12 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
#define THROW(id) OPCODE_THROW,V(id),
|
||||
#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),
|
||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||
|
@@ -136,7 +136,7 @@ bool EthernetInterface::checkLink() {
|
||||
DIAG(F("Ethernet cable connected"));
|
||||
connected=true;
|
||||
#ifdef IP_ADDRESS
|
||||
setLocalIP(IP_ADDRESS); // for static IP, set it again
|
||||
Ethernet.setLocalIP(IP_ADDRESS); // for static IP, set it again
|
||||
#endif
|
||||
IPAddress ip = Ethernet.localIP(); // look what IP was obtained (dynamic or static)
|
||||
server = new EthernetServer(IP_PORT); // Ethernet Server listening on default port IP_PORT
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202302121935Z"
|
||||
#define GITHUB_SHA "devel-202306231154Z"
|
||||
|
@@ -72,18 +72,23 @@ static const FSH * guessI2CDeviceType(uint8_t address) {
|
||||
void I2CManagerClass::begin(void) {
|
||||
if (!_beginCompleted) {
|
||||
_beginCompleted = true;
|
||||
|
||||
// Check for short-circuit or floating lines (no pull-up) on I2C before enabling I2C
|
||||
const FSH *message = F("WARNING: Check I2C %S line for short/pullup");
|
||||
pinMode(SDA, INPUT);
|
||||
if (!digitalRead(SDA))
|
||||
DIAG(message, F("SDA"));
|
||||
pinMode(SCL, INPUT);
|
||||
if (!digitalRead(SCL))
|
||||
DIAG(message, F("SCL"));
|
||||
|
||||
// Now initialise I2C
|
||||
_initialise();
|
||||
|
||||
#if defined(I2C_USE_WIRE)
|
||||
DIAG(F("I2CManager: Using Wire library"));
|
||||
#endif
|
||||
|
||||
// Check for short-circuits on I2C
|
||||
if (!digitalRead(SDA))
|
||||
DIAG(F("WARNING: Possible short-circuit on I2C SDA line"));
|
||||
if (!digitalRead(SCL))
|
||||
DIAG(F("WARNING: Possible short-circuit on I2C SCL line"));
|
||||
|
||||
// Probe and list devices. Use standard mode
|
||||
// (clock speed 100kHz) for best device compatibility.
|
||||
_setClock(100000);
|
||||
|
18
I2CManager.h
18
I2CManager.h
@@ -84,8 +84,6 @@
|
||||
*/
|
||||
|
||||
/*
|
||||
* Future enhancement possibility:
|
||||
*
|
||||
* I2C Multiplexer (e.g. TCA9547, TCA9548)
|
||||
*
|
||||
* A multiplexer offers a way of extending the address range of I2C devices. For example, GPIO extenders use address range 0x20-0x27
|
||||
@@ -98,11 +96,6 @@
|
||||
* Thirdly, the multiplexer offers the ability to use mixed-speed devices more effectively, by allowing high-speed devices to be
|
||||
* put on a different bus to low-speed devices, enabling the software to switch the I2C speed on-the-fly between I2C transactions.
|
||||
*
|
||||
* Changes required: Increase the size of the I2CAddress field in the IODevice class from uint8_t to uint16_t.
|
||||
* The most significant byte would contain a '1' bit flag, the multiplexer number (0-7) and bus number (0-7). Then, when performing
|
||||
* an I2C operation, the I2CManager would check this byte and, if zero, do what it currently does. If the byte is non-zero, then
|
||||
* that means the device is connected via a multiplexer so the I2C transaction should be preceded by a select command issued to the
|
||||
* relevant multiplexer.
|
||||
*
|
||||
* Non-interrupting I2C:
|
||||
*
|
||||
@@ -138,13 +131,9 @@
|
||||
// may be extended to include multiple buses, and other features.
|
||||
// Uncomment to enable extended address.
|
||||
//
|
||||
// WARNING: When I2CAddress is passed to formatting commands such as DIAG, LCD etc,
|
||||
// it should be cast to (int) to ensure that the address value is passed rather than
|
||||
// the struct.
|
||||
|
||||
//#define I2C_EXTENDED_ADDRESS
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Extended I2C Address type to facilitate extended I2C addresses including
|
||||
// I2C multiplexer support.
|
||||
@@ -184,7 +173,7 @@ enum I2CSubBus : uint8_t {
|
||||
#endif
|
||||
SubBus_No, // Number of subbuses (highest + 1)
|
||||
SubBus_None = 254, // Disable all sub-buses on selected mux
|
||||
SubBus_All = 255, // Enable all sub-buses
|
||||
SubBus_All = 255, // Enable all sub-buses (not supported by some multiplexers)
|
||||
};
|
||||
|
||||
// Type to hold I2C address
|
||||
@@ -506,10 +495,12 @@ private:
|
||||
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
// Count of I2C multiplexers found when initialising. If there is only one
|
||||
// MUX then the subbus does not de-selecting after use; however, if there
|
||||
// MUX then the subbus does not need de-selecting after use; however, if there
|
||||
// are two or more, then the subbus must be deselected to avoid multiple
|
||||
// sub-bus legs on different multiplexers being accessible simultaneously.
|
||||
private:
|
||||
uint8_t _muxCount = 0;
|
||||
public:
|
||||
uint8_t getMuxCount() { return _muxCount; }
|
||||
#endif
|
||||
|
||||
@@ -522,6 +513,7 @@ private:
|
||||
// Within the queue, each request's nextRequest field points to the
|
||||
// next request, or NULL.
|
||||
// Mark volatile as they are updated by IRC and read/written elsewhere.
|
||||
private:
|
||||
I2CRB * volatile queueHead = NULL;
|
||||
I2CRB * volatile queueTail = NULL;
|
||||
|
||||
|
@@ -223,10 +223,7 @@ void I2CManagerClass::I2C_handleInterrupt() {
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
ISR(TWI_vect) {
|
||||
// pinMode(2,OUTPUT);
|
||||
// digitalWrite(2,1);
|
||||
I2CManager.handleInterrupt();
|
||||
// digitalWrite(2,0);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@@ -77,7 +77,15 @@ static uint8_t muxSelect(I2CAddress address) {
|
||||
Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+muxNo);
|
||||
uint8_t data = (subBus == SubBus_All) ? 0xff :
|
||||
(subBus == SubBus_None) ? 0x00 :
|
||||
(1 << subBus);
|
||||
#if defined(I2CMUX_PCA9547)
|
||||
0x08 | subBus;
|
||||
#elif defined(I2CMUX_PCA9542) || defined(I2CMUX_PCA9544)
|
||||
0x04 | subBus; // NB Only 2 or 4 subbuses respectively
|
||||
#else
|
||||
// Default behaviour for most MUXs is to use a mask
|
||||
// with a bit set for the subBus to be enabled
|
||||
1 << subBus;
|
||||
#endif
|
||||
Wire.write(&data, 1);
|
||||
return Wire.endTransmission(true); // have to release I2C bus for it to work
|
||||
}
|
||||
|
52
IODevice.cpp
52
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
|
||||
@@ -169,7 +185,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 +195,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 +207,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 +219,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 +229,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 +243,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 +262,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 +330,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 +345,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 +392,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 +546,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);
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
69
IODevice.h
69
IODevice.h
@@ -467,6 +467,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));
|
||||
}
|
||||
|
||||
|
240
IO_DFPlayer.h
240
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,21 +81,13 @@ private:
|
||||
HardwareSerial *_serial;
|
||||
bool _playing = false;
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Allows timeout processing
|
||||
uint8_t _lastVolumeLevel = MAXVOLUME;
|
||||
|
||||
// When two commands are sent in quick succession, the device sometimes
|
||||
// fails to execute one. A delay is required between successive commands.
|
||||
// This could be implemented by buffering commands and outputting them
|
||||
// from the loop() function, but it would somewhat complicate the
|
||||
// driver. A simpler solution is to output a number of NUL pad characters
|
||||
// between successive command strings if there isn't sufficient elapsed time
|
||||
// between them. At 9600 baud, each pad character takes approximately
|
||||
// 1ms to complete. Experiments indicate that the minimum number of pads
|
||||
// for reliable operation is 17. This gives 17.7ms between the end of one
|
||||
// command and the beginning of the next, or 28ms between successive commands
|
||||
// being completed. I've allowed 20 characters, which is almost 21ms.
|
||||
const int numPadCharacters = 20; // Number of pad characters between commands
|
||||
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
|
||||
|
||||
public:
|
||||
|
||||
@@ -113,66 +112,151 @@ 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 (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||
if (_currentVolume > _requestedVolumeLevel) {
|
||||
// Change volume before changing song if volume is reducing.
|
||||
_currentVolume = _requestedVolumeLevel;
|
||||
sendPacket(0x06, _currentVolume);
|
||||
} else if (_requestedSong > 0) {
|
||||
// Change song
|
||||
sendPacket(0x03, _requestedSong);
|
||||
_requestedSong = -1;
|
||||
} else if (_requestedSong == 0) {
|
||||
sendPacket(0x16); // Stop playing
|
||||
_requestedSong = -1;
|
||||
} else if (_currentVolume < _requestedVolumeLevel) {
|
||||
// Change volume after changing song if volume is increasing.
|
||||
_currentVolume = _requestedVolumeLevel;
|
||||
sendPacket(0x06, _currentVolume);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Play %d"), pin+1);
|
||||
#endif
|
||||
sendPacket(0x03, pin+1);
|
||||
_requestedSong = pin+1;
|
||||
_playing = true;
|
||||
} else {
|
||||
// Value 0, stop playing
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Stop"));
|
||||
#endif
|
||||
sendPacket(0x16);
|
||||
_requestedSong = 0; // No song
|
||||
_playing = false;
|
||||
}
|
||||
}
|
||||
@@ -181,16 +265,13 @@ protected:
|
||||
// Volume may be specified as second parameter to writeAnalogue.
|
||||
// If value is zero, the player stops playing.
|
||||
// WriteAnalogue on second pin sets the output volume.
|
||||
// If starting a new file and setting volume, then avoid a short burst of loud noise by
|
||||
// the following strategy:
|
||||
// - If the volume is increasing, start playing the song before setting the volume,
|
||||
// - If the volume is decreasing, decrease it and then start playing.
|
||||
//
|
||||
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.
|
||||
@@ -199,37 +280,28 @@ protected:
|
||||
if (pin == 0) {
|
||||
// Play track
|
||||
if (value > 0) {
|
||||
if (volume != 0) {
|
||||
if (volume <= _lastVolumeLevel)
|
||||
sendPacket(0x06, volume); // Set volume before starting
|
||||
sendPacket(0x03, value); // Play track
|
||||
_playing = true;
|
||||
if (volume > _lastVolumeLevel)
|
||||
sendPacket(0x06, volume); // Set volume after starting
|
||||
_lastVolumeLevel = volume;
|
||||
} else {
|
||||
// Volume not changed, just play
|
||||
sendPacket(0x03, value);
|
||||
_playing = true;
|
||||
}
|
||||
if (volume > 0)
|
||||
_requestedVolumeLevel = volume;
|
||||
_requestedSong = value;
|
||||
_playing = true;
|
||||
} else {
|
||||
sendPacket(0x16); // Stop play
|
||||
_requestedSong = 0; // stop playing
|
||||
_playing = false;
|
||||
}
|
||||
} else if (pin == 1) {
|
||||
// Set volume (0-30)
|
||||
sendPacket(0x06, value);
|
||||
_lastVolumeLevel = volume;
|
||||
_requestedVolumeLevel = value;
|
||||
}
|
||||
}
|
||||
|
||||
// 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(""));
|
||||
}
|
||||
|
||||
@@ -246,7 +318,6 @@ private:
|
||||
|
||||
void sendPacket(uint8_t command, uint16_t arg = 0)
|
||||
{
|
||||
unsigned long currentMillis = millis();
|
||||
uint8_t out[] = { 0x7E,
|
||||
0xFF,
|
||||
06,
|
||||
@@ -260,19 +331,10 @@ private:
|
||||
|
||||
setChecksum(out);
|
||||
|
||||
// Check how long since the last command was sent.
|
||||
// Each character takes approx 1ms at 9600 baud
|
||||
unsigned long minimumGap = numPadCharacters + sizeof(out);
|
||||
if (currentMillis - _commandSendTime < minimumGap) {
|
||||
// Output some pad characters to add an
|
||||
// artificial delay between commands
|
||||
for (int i=0; i<numPadCharacters; i++)
|
||||
_serial->write((uint8_t)0);
|
||||
}
|
||||
|
||||
// Now output the command
|
||||
// Output the command
|
||||
_serial->write(out, sizeof(out));
|
||||
_commandSendTime = currentMillis;
|
||||
|
||||
_commandSendTime = micros();
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
|
@@ -40,24 +40,24 @@ bool FAST_CLOCK_EXISTS = true;
|
||||
class EXFastClock : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
EXFastClock(uint8_t I2CAddress){
|
||||
_I2CAddress = I2CAddress;
|
||||
EXFastClock(I2CAddress i2cAddress){
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
static void create(uint8_t _I2CAddress) {
|
||||
static void create(I2CAddress i2cAddress) {
|
||||
|
||||
DIAG(F("Checking for Clock"));
|
||||
// Start by assuming we will find the clock
|
||||
// Check if specified I2C address is responding (blocking operation)
|
||||
// Returns I2C_STATUS_OK (0) if OK, or error code.
|
||||
uint8_t _checkforclock = I2CManager.checkAddress(_I2CAddress);
|
||||
uint8_t _checkforclock = I2CManager.checkAddress(i2cAddress);
|
||||
DIAG(F("Clock check result - %d"), _checkforclock);
|
||||
// XXXX change thistosave2 bytes
|
||||
if (_checkforclock == 0) {
|
||||
FAST_CLOCK_EXISTS = true;
|
||||
//DIAG(F("I2C Fast Clock found at %s"), _I2CAddress.toString());
|
||||
new EXFastClock(_I2CAddress);
|
||||
//DIAG(F("I2C Fast Clock found at %s"), i2cAddress.toString());
|
||||
new EXFastClock(i2cAddress);
|
||||
}
|
||||
else {
|
||||
FAST_CLOCK_EXISTS = false;
|
||||
|
@@ -34,11 +34,16 @@
|
||||
* device in use. There is no way for the device driver to sanity check pins are used for the
|
||||
* correct purpose, however the EX-IOExpander device's pin map will prevent pins being used
|
||||
* incorrectly (eg. A6/7 on Nano cannot be used for digital input/output).
|
||||
*
|
||||
* The total number of pins cannot exceed 256 because of the communications packet format.
|
||||
* The number of analogue inputs cannot exceed 16 because of a limit on the maximum
|
||||
* I2C packet size of 32 bytes (in the Wire library).
|
||||
*/
|
||||
|
||||
#ifndef IO_EX_IOEXPANDER_H
|
||||
#define IO_EX_IOEXPANDER_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
@@ -64,116 +69,209 @@ public:
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
// Constructor
|
||||
EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
// Number of pins cannot exceed 256 (1 byte) because of I2C message structure.
|
||||
if (nPins > 256) nPins = 256;
|
||||
_nPins = nPins;
|
||||
_i2cAddress = i2cAddress;
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
void _begin() {
|
||||
uint8_t status;
|
||||
// Initialise EX-IOExander device
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
_command4Buffer[0] = EXIOINIT;
|
||||
_command4Buffer[1] = _nPins;
|
||||
_command4Buffer[2] = _firstVpin & 0xFF;
|
||||
_command4Buffer[3] = _firstVpin >> 8;
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
// Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline
|
||||
I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4);
|
||||
if (_receive3Buffer[0] == EXIOPINS) {
|
||||
_numDigitalPins = _receive3Buffer[1];
|
||||
_numAnaloguePins = _receive3Buffer[2];
|
||||
_digitalPinBytes = (_numDigitalPins + 7)/8;
|
||||
_digitalInputStates=(byte*) calloc(_digitalPinBytes,1);
|
||||
_analoguePinBytes = _numAnaloguePins * 2;
|
||||
_analogueInputStates = (byte*) calloc(_analoguePinBytes, 1);
|
||||
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
|
||||
} else {
|
||||
DIAG(F("ERROR configuring EX-IOExpander device, I2C:%s"), _i2cAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}
|
||||
// NB The I2C calls here are done as blocking calls, as they're not time-critical
|
||||
// during initialisation and the reads require waiting for a response anyway.
|
||||
// Hence we can allocate I/O buffers from the stack.
|
||||
uint8_t receiveBuffer[3];
|
||||
uint8_t commandBuffer[4] = {EXIOINIT, (uint8_t)_nPins, (uint8_t)(_firstVpin & 0xFF), (uint8_t)(_firstVpin >> 8)};
|
||||
status = I2CManager.read(_I2CAddress, receiveBuffer, sizeof(receiveBuffer), commandBuffer, sizeof(commandBuffer));
|
||||
if (status == I2C_STATUS_OK) {
|
||||
if (receiveBuffer[0] == EXIOPINS) {
|
||||
_numDigitalPins = receiveBuffer[1];
|
||||
_numAnaloguePins = receiveBuffer[2];
|
||||
|
||||
// See if we already have suitable buffers assigned
|
||||
size_t digitalBytesNeeded = (_numDigitalPins + 7) / 8;
|
||||
if (_digitalPinBytes < digitalBytesNeeded) {
|
||||
// Not enough space, free any existing buffer and allocate a new one
|
||||
if (_digitalPinBytes > 0) free(_digitalInputStates);
|
||||
_digitalInputStates = (byte*) calloc(_digitalPinBytes, 1);
|
||||
_digitalPinBytes = digitalBytesNeeded;
|
||||
}
|
||||
size_t analogueBytesNeeded = _numAnaloguePins * 2;
|
||||
if (_analoguePinBytes < analogueBytesNeeded) {
|
||||
// Free any existing buffers and allocate new ones.
|
||||
if (_analoguePinBytes > 0) {
|
||||
free(_analogueInputBuffer);
|
||||
free(_analogueInputStates);
|
||||
free(_analoguePinMap);
|
||||
}
|
||||
_analogueInputStates = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||
_analogueInputBuffer = (uint8_t*) calloc(analogueBytesNeeded, 1);
|
||||
_analoguePinMap = (uint8_t*) calloc(_numAnaloguePins, 1);
|
||||
_analoguePinBytes = analogueBytesNeeded;
|
||||
}
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander I2C:%s ERROR configuring device"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// We now need to retrieve the analogue pin map
|
||||
_command1Buffer[0] = EXIOINITA;
|
||||
I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1);
|
||||
// Attempt to get version, if we don't get it, we don't care, don't go offline
|
||||
_command1Buffer[0] = EXIOVER;
|
||||
I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1);
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"),
|
||||
_I2CAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
|
||||
if (status == I2C_STATUS_OK) {
|
||||
commandBuffer[0] = EXIOINITA;
|
||||
status = I2CManager.read(_I2CAddress, _analoguePinMap, _numAnaloguePins, commandBuffer, 1);
|
||||
}
|
||||
if (status == I2C_STATUS_OK) {
|
||||
// Attempt to get version, if we don't get it, we don't care, don't go offline
|
||||
uint8_t versionBuffer[3];
|
||||
commandBuffer[0] = EXIOVER;
|
||||
if (I2CManager.read(_I2CAddress, versionBuffer, sizeof(versionBuffer), commandBuffer, 1) == I2C_STATUS_OK) {
|
||||
_majorVer = versionBuffer[0];
|
||||
_minorVer = versionBuffer[1];
|
||||
_patchVer = versionBuffer[2];
|
||||
}
|
||||
DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"),
|
||||
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer);
|
||||
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
if (status != I2C_STATUS_OK)
|
||||
reportError(status);
|
||||
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander device not found, I2C:%s"), _I2CAddress.toString());
|
||||
DIAG(F("EX-IOExpander I2C:%s device not found"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use
|
||||
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if requested.
|
||||
// Configuration isn't done frequently so we can use blocking I2C calls here, and so buffers can
|
||||
// be allocated from the stack to reduce RAM allocation.
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
|
||||
if (paramCount != 1) return false;
|
||||
int pin = vpin - _firstVpin;
|
||||
if (configType == CONFIGURE_INPUT) {
|
||||
bool pullup = params[0];
|
||||
_digitalOutBuffer[0] = EXIODPUP;
|
||||
_digitalOutBuffer[1] = pin;
|
||||
_digitalOutBuffer[2] = pullup;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
|
||||
if (_command1Buffer[0] == EXIORDY) {
|
||||
return true;
|
||||
} else {
|
||||
DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
uint8_t pullup = params[0];
|
||||
uint8_t outBuffer[] = {EXIODPUP, (uint8_t)pin, pullup};
|
||||
uint8_t responseBuffer[1];
|
||||
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer),
|
||||
outBuffer, sizeof(outBuffer));
|
||||
if (status == I2C_STATUS_OK) {
|
||||
if (responseBuffer[0] == EXIORDY) {
|
||||
return true;
|
||||
} else {
|
||||
DIAG(F("EXIOVpin %u cannot be used as a digital input pin"), (int)vpin);
|
||||
}
|
||||
} else
|
||||
reportError(status);
|
||||
} else if (configType == CONFIGURE_ANALOGINPUT) {
|
||||
// TODO: Consider moving code from _configureAnalogIn() to here and remove _configureAnalogIn
|
||||
// from IODevice class definition. Not urgent, but each virtual function defined
|
||||
// means increasing the RAM requirement of every HAL device driver, whether it's relevant
|
||||
// to the driver or not.
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Analogue input pin configuration, used to enable on EX-IOExpander device
|
||||
// Analogue input pin configuration, used to enable an EX-IOExpander device.
|
||||
// Use I2C blocking calls and allocate buffers from stack to save RAM.
|
||||
int _configureAnalogIn(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
_command2Buffer[0] = EXIOENAN;
|
||||
_command2Buffer[1] = pin;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2);
|
||||
if (_command1Buffer[0] == EXIORDY) {
|
||||
return true;
|
||||
} else {
|
||||
DIAG(F("Vpin %d cannot be used as an analogue input pin"), (int)vpin);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
uint8_t commandBuffer[] = {EXIOENAN, (uint8_t)pin};
|
||||
uint8_t responseBuffer[1];
|
||||
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, sizeof(responseBuffer),
|
||||
commandBuffer, sizeof(commandBuffer));
|
||||
if (status == I2C_STATUS_OK) {
|
||||
if (responseBuffer[0] == EXIORDY) {
|
||||
return true;
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander: Vpin %u cannot be used as an analogue input pin"), (int)vpin);
|
||||
}
|
||||
} else
|
||||
reportError(status);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
(void)currentMicros; // remove warning
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
_command1Buffer[0] = EXIORDD;
|
||||
I2CManager.read(_i2cAddress, _digitalInputStates, _digitalPinBytes, _command1Buffer, 1);
|
||||
_command1Buffer[0] = EXIORDAN;
|
||||
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1);
|
||||
if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return
|
||||
|
||||
// Request block is used for analogue and digital reads from the IOExpander, which are performed
|
||||
// on a cyclic basis. Writes are performed synchronously as and when requested.
|
||||
|
||||
if (_readState != RDS_IDLE) {
|
||||
if (_i2crb.isBusy()) return; // If I2C operation still in progress, return
|
||||
|
||||
uint8_t status = _i2crb.status;
|
||||
if (status == I2C_STATUS_OK) { // If device request ok, read input data
|
||||
|
||||
// First check if we need to process received data
|
||||
if (_readState == RDS_ANALOGUE) {
|
||||
// Read of analogue values was in progress, so process received values
|
||||
// Here we need to copy the values from input buffer to the analogue value array. We need to
|
||||
// do this to avoid tearing of the values (i.e. one byte of a two-byte value being changed
|
||||
// while the value is being read).
|
||||
memcpy(_analogueInputStates, _analogueInputBuffer, _analoguePinBytes); // Copy I2C input buffer to states
|
||||
|
||||
} else if (_readState == RDS_DIGITAL) {
|
||||
// Read of digital states was in progress, so process received values
|
||||
// The received digital states are placed directly into the digital buffer on receipt,
|
||||
// so don't need any further processing at this point (unless we want to check for
|
||||
// changes and notify them to subscribers, to avoid the need for polling - see IO_GPIOBase.h).
|
||||
}
|
||||
} else
|
||||
reportError(status, false); // report eror but don't go offline.
|
||||
|
||||
_readState = RDS_IDLE;
|
||||
}
|
||||
|
||||
// If we're not doing anything now, check to see if a new input transfer is due.
|
||||
if (_readState == RDS_IDLE) {
|
||||
if (currentMicros - _lastDigitalRead > _digitalRefresh) { // Delay for digital read refresh
|
||||
// Issue new read request for digital states. As the request is non-blocking, the buffer has to
|
||||
// be allocated from heap (object state).
|
||||
_readCommandBuffer[0] = EXIORDD;
|
||||
I2CManager.read(_I2CAddress, _digitalInputStates, (_numDigitalPins+7)/8, _readCommandBuffer, 1, &_i2crb);
|
||||
// non-blocking read
|
||||
_lastDigitalRead = currentMicros;
|
||||
_readState = RDS_DIGITAL;
|
||||
} else if (currentMicros - _lastAnalogueRead > _analogueRefresh) { // Delay for analogue read refresh
|
||||
// Issue new read for analogue input states
|
||||
_readCommandBuffer[0] = EXIORDAN;
|
||||
I2CManager.read(_I2CAddress, _analogueInputBuffer,
|
||||
_numAnaloguePins * 2, _readCommandBuffer, 1, &_i2crb);
|
||||
_lastAnalogueRead = currentMicros;
|
||||
_readState = RDS_ANALOGUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Obtain the correct analogue input value, with reference to the analogue
|
||||
// pin map.
|
||||
// Obtain the correct analogue input value
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t _pinLSBByte;
|
||||
for (uint8_t aPin = 0; aPin < _numAnaloguePins; aPin++) {
|
||||
if (_analoguePinMap[aPin] == pin) {
|
||||
_pinLSBByte = aPin * 2;
|
||||
uint8_t _pinLSBByte = aPin * 2;
|
||||
uint8_t _pinMSBByte = _pinLSBByte + 1;
|
||||
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
|
||||
}
|
||||
}
|
||||
uint8_t _pinMSBByte = _pinLSBByte + 1;
|
||||
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
|
||||
return -1; // pin not found in table
|
||||
}
|
||||
|
||||
// Obtain the correct digital input value
|
||||
@@ -185,63 +283,102 @@ private:
|
||||
return value;
|
||||
}
|
||||
|
||||
// Write digital value. We could have an output buffer of states, that is periodically
|
||||
// written to the device if there are any changes; this would reduce the I2C overhead
|
||||
// if lots of output requests are being made. We could also cache the last value
|
||||
// sent so that we don't write the same value over and over to the output.
|
||||
// However, for the time being, we just write the current value (blocking I2C) to the
|
||||
// IOExpander node. As it is a blocking request, we can use buffers allocated from
|
||||
// the stack to save RAM allocation.
|
||||
void _write(VPIN vpin, int value) override {
|
||||
uint8_t digitalOutBuffer[3];
|
||||
uint8_t responseBuffer[1];
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
_digitalOutBuffer[0] = EXIOWRD;
|
||||
_digitalOutBuffer[1] = pin;
|
||||
_digitalOutBuffer[2] = value;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _digitalOutBuffer, 3);
|
||||
if (_command1Buffer[0] != EXIORDY) {
|
||||
DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin);
|
||||
digitalOutBuffer[0] = EXIOWRD;
|
||||
digitalOutBuffer[1] = pin;
|
||||
digitalOutBuffer[2] = value;
|
||||
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, digitalOutBuffer, 3);
|
||||
if (status != I2C_STATUS_OK) {
|
||||
reportError(status);
|
||||
} else {
|
||||
if (responseBuffer[0] != EXIORDY) {
|
||||
DIAG(F("Vpin %u cannot be used as a digital output pin"), (int)vpin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write analogue (integer) value. Write the parameters (blocking I2C) to the
|
||||
// IOExpander node. As it is a blocking request, we can use buffers allocated from
|
||||
// the stack to reduce RAM allocation.
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
||||
uint8_t servoBuffer[7];
|
||||
uint8_t responseBuffer[1];
|
||||
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
#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
|
||||
_servoBuffer[0] = EXIOWRAN;
|
||||
_servoBuffer[1] = pin;
|
||||
_servoBuffer[2] = value & 0xFF;
|
||||
_servoBuffer[3] = value >> 8;
|
||||
_servoBuffer[4] = profile;
|
||||
_servoBuffer[5] = duration & 0xFF;
|
||||
_servoBuffer[6] = duration >> 8;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _servoBuffer, 7);
|
||||
if (_command1Buffer[0] != EXIORDY) {
|
||||
DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin);
|
||||
servoBuffer[0] = EXIOWRAN;
|
||||
servoBuffer[1] = pin;
|
||||
servoBuffer[2] = value & 0xFF;
|
||||
servoBuffer[3] = value >> 8;
|
||||
servoBuffer[4] = profile;
|
||||
servoBuffer[5] = duration & 0xFF;
|
||||
servoBuffer[6] = duration >> 8;
|
||||
uint8_t status = I2CManager.read(_I2CAddress, responseBuffer, 1, servoBuffer, 7);
|
||||
if (status != I2C_STATUS_OK) {
|
||||
DIAG(F("EX-IOExpander I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
} else {
|
||||
if (responseBuffer[0] != EXIORDY) {
|
||||
DIAG(F("Vpin %u cannot be used as a servo/PWM pin"), (int)vpin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display device information and status.
|
||||
void _display() override {
|
||||
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %d-%d %S"),
|
||||
_i2cAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %u-%u %S"),
|
||||
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
I2CAddress _i2cAddress;
|
||||
// Helper function for error handling
|
||||
void reportError(uint8_t status, bool fail=true) {
|
||||
DIAG(F("EX-IOExpander I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
|
||||
status, I2CManager.getErrorMessage(status));
|
||||
if (fail)
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
|
||||
uint8_t _numDigitalPins = 0;
|
||||
uint8_t _numAnaloguePins = 0;
|
||||
byte _digitalOutBuffer[3];
|
||||
uint8_t _versionBuffer[3];
|
||||
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
byte* _digitalInputStates;
|
||||
byte* _analogueInputStates;
|
||||
uint8_t _digitalPinBytes = 0;
|
||||
uint8_t _analoguePinBytes = 0;
|
||||
byte _command1Buffer[1];
|
||||
byte _command2Buffer[2];
|
||||
byte _command4Buffer[4];
|
||||
byte _receive3Buffer[3];
|
||||
byte _servoBuffer[7];
|
||||
|
||||
uint8_t* _digitalInputStates;
|
||||
uint8_t* _analogueInputStates;
|
||||
uint8_t* _analogueInputBuffer; // buffer for I2C input transfers
|
||||
uint8_t _readCommandBuffer[1];
|
||||
|
||||
uint8_t _digitalPinBytes = 0; // Size of allocated memory buffer (may be longer than needed)
|
||||
uint8_t _analoguePinBytes = 0; // Size of allocated memory buffers (may be longer than needed)
|
||||
uint8_t* _analoguePinMap;
|
||||
I2CRB _i2crb;
|
||||
|
||||
enum {RDS_IDLE, RDS_DIGITAL, RDS_ANALOGUE}; // Read operation states
|
||||
uint8_t _readState = RDS_IDLE;
|
||||
|
||||
unsigned long _lastDigitalRead = 0;
|
||||
unsigned long _lastAnalogueRead = 0;
|
||||
const unsigned long _digitalRefresh = 10000UL; // Delay refreshing digital inputs for 10ms
|
||||
const unsigned long _analogueRefresh = 50000UL; // Delay refreshing analogue inputs for 50ms
|
||||
|
||||
// EX-IOExpander protocol flags
|
||||
enum {
|
||||
|
@@ -103,7 +103,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
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);
|
||||
@@ -114,7 +114,7 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
|
||||
// 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]);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -1,141 +0,0 @@
|
||||
/*
|
||||
* © 2023, 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This device driver monitors the state of turnout objects and writes updates,
|
||||
* on change of state, to an external 24C128 (16kByte) or 24C256 (32kByte)
|
||||
* EEPROM device connected via I2C.
|
||||
*
|
||||
* When the device is restarted, it repositions the turnouts in accordance
|
||||
* with the last saved position.
|
||||
*
|
||||
* To create a device instance,
|
||||
* IO_ExternalEEPROM::create(0, 0, i2cAddress);
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_EXTERNALEEPROM_H
|
||||
#define IO_EXTERNALEEPROM_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "Turnouts.h"
|
||||
|
||||
class ExternalEEPROM : public IODevice {
|
||||
private:
|
||||
// Here we define the device-specific variables.
|
||||
int _sizeInKBytes = 128;
|
||||
Turnout *_turnout = 0;
|
||||
int _lastTurnoutHash = 0;
|
||||
I2CRB _rb;
|
||||
uint8_t _buffer[32]; // 32 is max for Wire write
|
||||
|
||||
public:
|
||||
// Static function to handle "IO_ExampleSerial::create(...)" calls.
|
||||
static void create(I2CAddress i2cAddress, int sizeInKBytes) {
|
||||
if (checkNoOverlap(0, 0, i2cAddress)) new ExternalEEPROM(i2cAddress, sizeInKBytes);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor.
|
||||
ExternalEEPROM(I2CAddress i2cAddress, int sizeInKBytes) {
|
||||
_I2CAddress = i2cAddress;
|
||||
_sizeInKBytes = sizeInKBytes;
|
||||
|
||||
// Set up I2C structures.
|
||||
_rb.setWriteParams(_I2CAddress, _buffer, 32);
|
||||
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000); // Max supported speed
|
||||
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
// Initialise or read contents of EEPROM
|
||||
// and set turnout states accordingly.
|
||||
// Read 32 bytes from address 0x0000.
|
||||
I2CManager.read(_I2CAddress, _buffer, 32, 2, 0, 0);
|
||||
// Dump data
|
||||
DIAG(F("EEPROM First 32 bytes:"));
|
||||
for (int i=0; i<32; i+=8)
|
||||
DIAG(F("%d: %x %x %x %x %x %x %x %x"),
|
||||
i, _buffer[i], _buffer[i+1], _buffer[i+2], _buffer[i+3],
|
||||
_buffer[i+4], _buffer[i+5], _buffer[i+6], _buffer[i+7]);
|
||||
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("ExternalEEPROM not found, I2C:%s"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Loop function to do background scanning of the turnouts
|
||||
void _loop(unsigned long currentMicros) {
|
||||
(void)currentMicros; // Suppress compiler warnings
|
||||
|
||||
if (_rb.isBusy()) return; // Can't do anything until previous request has completed.
|
||||
if (_rb.status == I2C_STATUS_NEGATIVE_ACKNOWLEDGE) {
|
||||
// Device not responding, probably still writing data, so requeue request
|
||||
I2CManager.queueRequest(&_rb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastTurnoutHash != Turnout::turnoutlistHash) {
|
||||
_lastTurnoutHash = Turnout::turnoutlistHash;
|
||||
// Turnout list has changed, so pointer held from last run may be invalid
|
||||
_turnout = 0; // Start at the beginning of the list again.
|
||||
//#if defined(DIAG_IO)
|
||||
DIAG(F("Turnout Hash Changed!"));
|
||||
//#endif
|
||||
}
|
||||
|
||||
// Locate next turnout, or first one if there is no current one.
|
||||
if (_turnout)
|
||||
_turnout = _turnout->next();
|
||||
else
|
||||
_turnout = Turnout::first();
|
||||
|
||||
// Retrieve turnout state
|
||||
int turnoutID = _turnout->getId();
|
||||
int turnoutState = _turnout->isThrown();
|
||||
(void)turnoutID; // Suppress compiler warning
|
||||
(void)turnoutState; // Suppress compiler warning
|
||||
|
||||
// TODO: Locate turnoutID in EEPROM (or EEPROM copy) and check if state has changed.
|
||||
// TODO: If it has, then initiate a write of the updated state to EEPROM
|
||||
|
||||
delayUntil(currentMicros+5000); // Write cycle time is 5ms max for FT24C256
|
||||
}
|
||||
|
||||
// Display information about the device.
|
||||
void _display() {
|
||||
DIAG(F("ExternalEEPROM %dkBytes I2C:%s %S"), _sizeInKBytes, _I2CAddress.toString(),
|
||||
_deviceState== DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_EXTERNALEEPROM_H
|
@@ -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(""));
|
||||
}
|
||||
|
||||
|
@@ -76,21 +76,23 @@ private:
|
||||
uint8_t *_lastRowGeneration = NULL;
|
||||
uint8_t _rowNoToScreen = 0;
|
||||
uint8_t _charPosToScreen = 0;
|
||||
bool _startAgain = false;
|
||||
DisplayInterface *_nextDisplay = NULL;
|
||||
|
||||
public:
|
||||
// Static function to handle "HALDisplay::create(...)" calls.
|
||||
static void create(I2CAddress i2cAddress, int width, int height) {
|
||||
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(0, i2cAddress, width, height);
|
||||
if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(0, i2cAddress, width, height);
|
||||
}
|
||||
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
|
||||
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(displayNo, i2cAddress, width, height);
|
||||
if (checkNoOverlap(0, 0, i2cAddress)) new HALDisplay(displayNo, i2cAddress, width, height);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor
|
||||
HALDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
|
||||
_displayDriver = new T(i2cAddress, width, height);
|
||||
if (!_displayDriver) return; // Check for memory allocation failure
|
||||
_I2CAddress = i2cAddress;
|
||||
_width = width;
|
||||
_height = height;
|
||||
@@ -101,8 +103,12 @@ protected:
|
||||
|
||||
// Allocate arrays
|
||||
_buffer = (char *)calloc(_numRows*_numCols, sizeof(char));
|
||||
if (!_buffer) return; // Check for memory allocation failure
|
||||
_rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
|
||||
if (!_rowGeneration) return; // Check for memory allocation failure
|
||||
_lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
|
||||
if (!_lastRowGeneration) return; // Check for memory allocation failure
|
||||
|
||||
// Fill buffer with spaces
|
||||
memset(_buffer, ' ', _numCols*_numRows);
|
||||
|
||||
@@ -116,7 +122,7 @@ protected:
|
||||
// Also add this display to list of display handlers
|
||||
DisplayInterface::addDisplay(displayNo);
|
||||
|
||||
// Is this the main display?
|
||||
// Is this the system display (0)?
|
||||
if (displayNo == 0) {
|
||||
// Set first two lines on screen
|
||||
this->setRow(displayNo, 0);
|
||||
@@ -135,13 +141,15 @@ protected:
|
||||
// to the screen until that row has been refreshed.
|
||||
|
||||
// First check if the OLED driver is still busy from a previous
|
||||
// call. If so, don't to anything until the next entry.
|
||||
// call. If so, don't do anything until the next entry.
|
||||
if (!_displayDriver->isBusy()) {
|
||||
// Check if we've just done the end of a row
|
||||
if (_charPosToScreen >= _numCols) {
|
||||
// Move to next line
|
||||
if (++_rowNoToScreen >= _numRows)
|
||||
if (++_rowNoToScreen >= _numRows || _startAgain) {
|
||||
_rowNoToScreen = 0; // Wrap to first row
|
||||
_startAgain = false;
|
||||
}
|
||||
|
||||
if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) {
|
||||
// Row content has changed, so start outputting it
|
||||
@@ -222,10 +230,14 @@ public:
|
||||
for (_colNo = 0; _colNo < _numCols; _colNo++)
|
||||
_buffer[_rowNo*_numCols+_colNo] = ' ';
|
||||
_colNo = 0;
|
||||
// Mark that the buffer has been touched. It will be
|
||||
// Mark that the buffer has been touched. It will start being
|
||||
// sent to the screen on the next loop entry, by which time
|
||||
// the line should have been written to the buffer.
|
||||
_rowGeneration[_rowNo]++;
|
||||
// Indicate that the output loop is to start updating the screen again from
|
||||
// row 0. Otherwise, on a full screen rewrite the bottom part may be drawn
|
||||
// before the top part!
|
||||
_startAgain = true;
|
||||
}
|
||||
|
||||
// Write one character to the screen referenced in the last setRow() call.
|
||||
|
202
IO_HCSR04.h
202
IO_HCSR04.h
@@ -30,7 +30,7 @@
|
||||
*
|
||||
* This driver polls the HC-SR04 by sending the trigger pulse and then measuring
|
||||
* the length of the received pulse. If the calculated distance is less than
|
||||
* the threshold, the output state returned by a read() call changes to 1. If
|
||||
* the threshold, the output _state returned by a read() call changes to 1. If
|
||||
* the distance is greater than the threshold plus a hysteresis margin, the
|
||||
* output changes to 0. The device also supports readAnalogue(), which returns
|
||||
* the measured distance in cm, or 32767 if the distance exceeds the
|
||||
@@ -48,6 +48,20 @@
|
||||
* Note: The timing accuracy required for measuring the pulse length means that
|
||||
* the pins have to be direct Arduino pins; GPIO pins on an IO Extender cannot
|
||||
* provide the required accuracy.
|
||||
*
|
||||
* Example configuration:
|
||||
* HCSR04::create(23000, 32, 33, 80, 85);
|
||||
*
|
||||
* Where 23000 is the VPIN allocated,
|
||||
* 32 is the pin connected to the HCSR04 trigger terminal,
|
||||
* 33 is the pin connected to the HCSR04 echo terminal,
|
||||
* 80 is the distance in cm below which pin 23000 will be active,
|
||||
* and 85 is the distance in cm above which pin 23000 will be inactive.
|
||||
*
|
||||
* Alternative configuration, which hogs the processor until the measurement is complete
|
||||
* (old behaviour, more accurate but higher impact on other CS tasks):
|
||||
* HCSR04::create(23000, 32, 33, 80, 85, HCSR04::LOOP);
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_HCSR04_H
|
||||
@@ -61,38 +75,52 @@ private:
|
||||
// pins must be arduino GPIO pins, not extender pins or HAL pins.
|
||||
int _trigPin = -1;
|
||||
int _echoPin = -1;
|
||||
// Thresholds for setting active state in cm.
|
||||
// Thresholds for setting active _state in cm.
|
||||
uint8_t _onThreshold; // cm
|
||||
uint8_t _offThreshold; // cm
|
||||
// Last measured distance in cm.
|
||||
uint16_t _distance;
|
||||
// Active=1/inactive=0 state
|
||||
// Active=1/inactive=0 _state
|
||||
uint8_t _value = 0;
|
||||
// Factor for calculating the distance (cm) from echo time (ms).
|
||||
// Factor for calculating the distance (cm) from echo time (us).
|
||||
// Based on a speed of sound of 345 metres/second.
|
||||
const uint16_t factor = 58; // ms/cm
|
||||
const uint16_t factor = 58; // us/cm
|
||||
// Limit the time spent looping by dropping out when the expected
|
||||
// worst case threshold value is greater than an arbitrary value.
|
||||
const uint16_t maxPermittedLoopTime = 10 * factor; // max in us
|
||||
unsigned long _startTime = 0;
|
||||
unsigned long _maxTime = 0;
|
||||
enum {DORMANT, MEASURING}; // _state values
|
||||
uint8_t _state = DORMANT;
|
||||
uint8_t _counter = 0;
|
||||
uint16_t _options = 0;
|
||||
|
||||
public:
|
||||
enum Options {
|
||||
LOOP = 1, // Option HCSR04::LOOP reinstates old behaviour, i.e. complete measurement in one loop entry.
|
||||
};
|
||||
|
||||
// Static create function provides alternative way to create object
|
||||
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
static void create(VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold, uint16_t options = 0) {
|
||||
if (checkNoOverlap(vpin))
|
||||
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold);
|
||||
new HCSR04(vpin, trigPin, echoPin, onThreshold, offThreshold, options);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor perfroms static initialisation of the device object
|
||||
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold) {
|
||||
// Constructor performs static initialisation of the device object
|
||||
HCSR04 (VPIN vpin, int trigPin, int echoPin, uint16_t onThreshold, uint16_t offThreshold, uint16_t options) {
|
||||
_firstVpin = vpin;
|
||||
_nPins = 1;
|
||||
_trigPin = trigPin;
|
||||
_echoPin = echoPin;
|
||||
_onThreshold = onThreshold;
|
||||
_offThreshold = offThreshold;
|
||||
_options = options;
|
||||
addDevice(this);
|
||||
}
|
||||
// _begin function called to perform dynamic initialisation of the device
|
||||
void _begin() override {
|
||||
_state = 0;
|
||||
pinMode(_trigPin, OUTPUT);
|
||||
pinMode(_echoPin, INPUT);
|
||||
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
||||
@@ -112,78 +140,104 @@ protected:
|
||||
return _distance;
|
||||
}
|
||||
|
||||
// _loop function - read HC-SR04 once every 50 milliseconds.
|
||||
// _loop function - read HC-SR04 once every 100 milliseconds.
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
read_HCSR04device();
|
||||
// Delay next loop entry until 50ms have elapsed.
|
||||
delayUntil(currentMicros + 50000UL);
|
||||
unsigned long waitTime;
|
||||
switch(_state) {
|
||||
case DORMANT: // Issue pulse
|
||||
// If receive pin is still set on from previous call, do nothing till next entry.
|
||||
if (ArduinoPins::fastReadDigital(_echoPin)) return;
|
||||
|
||||
// Send 10us pulse to trigger transmitter
|
||||
ArduinoPins::fastWriteDigital(_trigPin, 1);
|
||||
delayMicroseconds(10);
|
||||
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
||||
|
||||
// Wait, with timeout, for echo pin to become set.
|
||||
// Measured time delay is just under 500us, so
|
||||
// wait for max of 1000us.
|
||||
_startTime = micros();
|
||||
_maxTime = 1000;
|
||||
|
||||
while (!ArduinoPins::fastReadDigital(_echoPin)) {
|
||||
// Not set yet, see if we've timed out.
|
||||
waitTime = micros() - _startTime;
|
||||
if (waitTime > _maxTime) {
|
||||
// Timeout waiting for pulse start, abort the read and start again
|
||||
_state = DORMANT;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Echo pulse started, so wait for echo pin to reset, and measure length of pulse
|
||||
_startTime = micros();
|
||||
_maxTime = factor * _offThreshold;
|
||||
_state = MEASURING;
|
||||
// If maximum measurement time is high, then skip until next loop entry before
|
||||
// starting to look for pulse end.
|
||||
// This gives better accuracy at shorter distance thresholds but without extending
|
||||
// loop execution time for longer thresholds. If LOOP option is set on, then
|
||||
// the entire measurement will be done in one loop entry, i.e. the code will fall
|
||||
// through into the measuring phase.
|
||||
if (!(_options & LOOP) && _maxTime > maxPermittedLoopTime) break;
|
||||
/* fallthrough */
|
||||
|
||||
case MEASURING: // Check if echo pulse has finished
|
||||
do {
|
||||
waitTime = micros() - _startTime;
|
||||
if (!ArduinoPins::fastReadDigital(_echoPin)) {
|
||||
// Echo pulse completed; check if pulse length is below threshold and if so set value.
|
||||
if (waitTime <= factor * _onThreshold) {
|
||||
// Measured time is within the onThreshold, so value is one.
|
||||
_value = 1;
|
||||
// If the new distance value is less than the current, use it immediately.
|
||||
// But if the new distance value is longer, then it may be erroneously long
|
||||
// (because of extended loop times delays), so apply a delay to distance increases.
|
||||
uint16_t estimatedDistance = waitTime / factor;
|
||||
if (estimatedDistance < _distance)
|
||||
_distance = estimatedDistance;
|
||||
else
|
||||
_distance += 1; // Just increase distance slowly.
|
||||
_counter = 0;
|
||||
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, _distance);
|
||||
}
|
||||
_state = DORMANT;
|
||||
} else {
|
||||
// Echo pulse hasn't finished, so check if maximum time has elapsed
|
||||
// If pulse is too long then set return value to zero,
|
||||
// and finish without waiting for end of pulse.
|
||||
if (waitTime > _maxTime) {
|
||||
// Pulse length longer than maxTime, value is provisionally zero.
|
||||
// But don't change _value unless provisional value is zero for 10 consecutive measurements
|
||||
if (_value == 1) {
|
||||
if (++_counter >= 10) {
|
||||
_value = 0;
|
||||
_distance = 32767;
|
||||
_counter = 0;
|
||||
}
|
||||
}
|
||||
_state = DORMANT; // start again
|
||||
}
|
||||
}
|
||||
// If there's lots of time remaining before the expected completion time,
|
||||
// then exit and wait for next loop entry. Otherwise, loop until we finish.
|
||||
// If option LOOP is set, then we loop until finished anyway.
|
||||
uint32_t remainingTime = _maxTime - waitTime;
|
||||
if (!(_options & LOOP) && remainingTime < maxPermittedLoopTime) return;
|
||||
} while (_state == MEASURING) ;
|
||||
break;
|
||||
}
|
||||
// Datasheet recommends a wait of at least 60ms between measurement cycles
|
||||
if (_state == DORMANT)
|
||||
delayUntil(currentMicros+60000UL); // wait 60ms till next measurement
|
||||
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
private:
|
||||
// This polls the HC-SR04 device by sending a pulse and measuring the duration of
|
||||
// the pulse observed on the receive pin. In order to be kind to the rest of the CS
|
||||
// software, no interrupts are used and interrupts are not disabled. The pulse duration
|
||||
// is measured in a loop, using the micros() function. Therefore, interrupts from other
|
||||
// sources may affect the result. However, interrupts response code in CS typically takes
|
||||
// much less than the 58us frequency for the DCC interrupt, and 58us corresponds to only 1cm
|
||||
// in the HC-SR04.
|
||||
// To reduce chatter on the output, hysteresis is applied on reset: the output is set to 1 when the
|
||||
// measured distance is less than the onThreshold, and is set to 0 if the measured distance is
|
||||
// greater than the offThreshold.
|
||||
//
|
||||
void read_HCSR04device() {
|
||||
// uint16 enough to time up to 65ms
|
||||
uint16_t startTime, waitTime = 0, currentTime, maxTime;
|
||||
|
||||
// If receive pin is still set on from previous call, abort the read.
|
||||
if (ArduinoPins::fastReadDigital(_echoPin))
|
||||
return;
|
||||
|
||||
// Send 10us pulse to trigger transmitter
|
||||
ArduinoPins::fastWriteDigital(_trigPin, 1);
|
||||
delayMicroseconds(10);
|
||||
ArduinoPins::fastWriteDigital(_trigPin, 0);
|
||||
|
||||
// Wait for receive pin to be set
|
||||
startTime = currentTime = micros();
|
||||
maxTime = factor * _offThreshold * 2;
|
||||
while (!ArduinoPins::fastReadDigital(_echoPin)) {
|
||||
// lastTime = currentTime;
|
||||
currentTime = micros();
|
||||
waitTime = currentTime - startTime;
|
||||
if (waitTime > maxTime) {
|
||||
// Timeout waiting for pulse start, abort the read
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for receive pin to reset, and measure length of pulse
|
||||
startTime = currentTime = micros();
|
||||
maxTime = factor * _offThreshold;
|
||||
while (ArduinoPins::fastReadDigital(_echoPin)) {
|
||||
currentTime = micros();
|
||||
waitTime = currentTime - startTime;
|
||||
// If pulse is too long then set return value to zero,
|
||||
// and finish without waiting for end of pulse.
|
||||
if (waitTime > maxTime) {
|
||||
// Pulse length longer than maxTime, reset value.
|
||||
_value = 0;
|
||||
_distance = 32767;
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Check if pulse length is below threshold, if so set value.
|
||||
//DIAG(F("HCSR04: Pulse Len=%l Distance=%d"), waitTime, distance);
|
||||
_distance = waitTime / factor; // in centimetres
|
||||
if (_distance < _onThreshold)
|
||||
_value = 1;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif //IO_HCSR04_H
|
||||
|
@@ -45,8 +45,8 @@ void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t
|
||||
bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
|
||||
if (configType != CONFIGURE_SERVO) return false;
|
||||
if (paramCount != 5) return false;
|
||||
#if DIAG_IO >= 3
|
||||
DIAG(F("PCA9685 Configure VPIN:%d Apos:%d Ipos:%d Profile:%d Duration:%d state:%d"),
|
||||
#ifdef DIAG_IO
|
||||
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
|
||||
|
||||
@@ -117,8 +117,8 @@ void PCA9685::_begin() {
|
||||
// Device-specific write function, invoked from IODevice::write().
|
||||
// For this function, the configured profile is used.
|
||||
void PCA9685::_write(VPIN vpin, int value) {
|
||||
#if DIAG_IO >= 3
|
||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 Write VPIN:%u Value:%d"), vpin, value);
|
||||
#endif
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value) value = 1;
|
||||
@@ -144,8 +144,8 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||
// 4 (Bounce) Servo 'bounces' at extremes.
|
||||
//
|
||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
#if DIAG_IO >= 3
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||
#ifdef DIAG_IO
|
||||
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(""));
|
||||
}
|
||||
|
||||
|
@@ -120,8 +120,8 @@ private:
|
||||
//
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
|
||||
(void)param1; (void)param2; // suppress compiler warning
|
||||
#if DIAG_IO >= 3
|
||||
DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"),
|
||||
#ifdef DIAG_IO
|
||||
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,14 +134,14 @@ 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(""));
|
||||
}
|
||||
|
||||
// writeDevice (helper function) takes a pin in range 0 to _nPins-1 within the device, and a value
|
||||
// between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%.
|
||||
void writeDevice(uint8_t pin, int value) {
|
||||
#if DIAG_IO >= 3
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
|
||||
#endif
|
||||
// Wait for previous request to complete
|
||||
|
@@ -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,50 +59,79 @@
|
||||
|
||||
class RotaryEncoder : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
RotaryEncoder(VPIN firstVpin, int nPins, uint8_t I2CAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = I2CAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
if (checkNoOverlap(firstVpin, nPins, I2CAddress)) new RotaryEncoder(firstVpin, nPins, I2CAddress);
|
||||
|
||||
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
|
||||
@@ -98,28 +142,45 @@ private:
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void _writeAnalogue(VPIN vpin, int position, uint8_t profile, uint16_t duration) override {
|
||||
if (vpin == _firstVpin + 2) {
|
||||
if (position >= 0 && position <= 255) {
|
||||
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(""));
|
||||
}
|
||||
|
||||
uint8_t _I2CAddress;
|
||||
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(""));
|
||||
|
256
IO_TFTDisplay.h
256
IO_TFTDisplay.h
@@ -1,256 +0,0 @@
|
||||
/*
|
||||
* © 2023, 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This driver provides a way of driving a ST7735 TFT display through SCREEN(disp,line,"text").
|
||||
* If the line specified is off the screen then the text in the bottom line will be
|
||||
* overwritten. There is however a special case that if line 255 is specified,
|
||||
* the existing text will scroll up and the new line added to the bottom
|
||||
* line of the screen.
|
||||
*
|
||||
* To install, use the following command in myHal.cpp:
|
||||
|
||||
* TFTDisplay::create(address, width, height);
|
||||
*
|
||||
* where address is the I2C address (0x3c or 0x3d),
|
||||
* width is the width in pixels of the display, and
|
||||
* height is the height in pixels of the display.
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef IO_TFTDISPLAY_H
|
||||
#define IO_TFTDDISPLAY_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "DisplayInterface.h"
|
||||
#include "version.h"
|
||||
|
||||
|
||||
template <class T>
|
||||
class TFTDisplay : public IODevice, public DisplayInterface {
|
||||
private:
|
||||
uint8_t _displayNo = 0;
|
||||
// Here we define the device-specific variables.
|
||||
uint8_t _height; // in pixels
|
||||
uint8_t _width; // in pixels
|
||||
T *_displayDriver;
|
||||
uint8_t _rowNo = 0; // Row number being written by caller
|
||||
uint8_t _colNo = 0; // Position in line being written by caller
|
||||
uint8_t _numRows;
|
||||
uint8_t _numCols;
|
||||
char *_buffer = NULL;
|
||||
uint8_t *_rowGeneration = NULL;
|
||||
uint8_t *_lastRowGeneration = NULL;
|
||||
uint8_t _rowNoToScreen = 0;
|
||||
uint8_t _charPosToScreen = 0;
|
||||
DisplayInterface *_nextDisplay = NULL;
|
||||
uint8_t _selectedDisplayNo = 0;
|
||||
|
||||
public:
|
||||
// Static function to handle "TFTDisplay::create(...)" calls.
|
||||
static void create(I2CAddress i2cAddress, int width = 128, int height=64) {
|
||||
/* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(0, i2cAddress, width, height);
|
||||
}
|
||||
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width = 128, int height=64) {
|
||||
/* if (checkNoOverlap(i2cAddress)) */ new TFTDisplay(displayNo, i2cAddress, width, height);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor
|
||||
TFTDisplay(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
|
||||
_displayDriver = new T(i2cAddress, width, height);
|
||||
_displayNo = displayNo;
|
||||
_I2CAddress = i2cAddress;
|
||||
_width = width;
|
||||
_height = height;
|
||||
_numCols = (_width+5) / 6; // character block 6 x 8, round up
|
||||
_numRows = _height / 8; // Round down
|
||||
|
||||
_charPosToScreen = _numCols;
|
||||
|
||||
// Allocate arrays
|
||||
_buffer = (char *)calloc(_numRows*_numCols, sizeof(char));
|
||||
_rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
|
||||
_lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
|
||||
// Fill buffer with spaces
|
||||
memset(_buffer, ' ', _numCols*_numRows);
|
||||
|
||||
_displayDriver->clearNative();
|
||||
|
||||
// Is this the main display?
|
||||
if (_displayNo == 0) {
|
||||
// Set first two lines on screen
|
||||
this->setRow(0, 0);
|
||||
print(F("DCC-EX v"));
|
||||
print(F(VERSION));
|
||||
setRow(0, 1);
|
||||
print(F("Lic GPLv3"));
|
||||
}
|
||||
|
||||
// Store pointer to this object into CS display hook, so that we
|
||||
// will intercept any subsequent calls to displayHandler methods.
|
||||
// Make a note of the existing display reference, to that we can
|
||||
// pass on anything we're not interested in.
|
||||
_nextDisplay = DisplayInterface::displayHandler;
|
||||
DisplayInterface::displayHandler = this;
|
||||
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
|
||||
void screenUpdate() {
|
||||
// Loop through the buffer and if a row has changed
|
||||
// (rowGeneration[row] is changed) then start writing the
|
||||
// characters from the buffer, one character per entry,
|
||||
// to the screen until that row has been refreshed.
|
||||
|
||||
// First check if the OLED driver is still busy from a previous
|
||||
// call. If so, don't to anything until the next entry.
|
||||
if (!_displayDriver->isBusy()) {
|
||||
// Check if we've just done the end of a row or just started
|
||||
if (_charPosToScreen >= _numCols) {
|
||||
// Move to next line
|
||||
if (++_rowNoToScreen >= _numRows)
|
||||
_rowNoToScreen = 0; // Wrap to first row
|
||||
|
||||
if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) {
|
||||
// Row content has changed, so start outputting it
|
||||
_lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen];
|
||||
_displayDriver->setRowNative(_rowNoToScreen);
|
||||
_charPosToScreen = 0; // Prepare to output first character on next entry
|
||||
} else {
|
||||
// Row not changed, don't bother writing it.
|
||||
}
|
||||
} else {
|
||||
// output character at current position
|
||||
_displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// IODevice Class Member Overrides
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
// Initialise device
|
||||
if (_displayDriver->begin()) {
|
||||
|
||||
DIAG(F("TFTDisplay installed on address %s as screen %d"),
|
||||
_I2CAddress.toString(), _displayNo);
|
||||
|
||||
// Force all rows to be redrawn
|
||||
for (uint8_t row=0; row<_numRows; row++)
|
||||
_rowGeneration[row]++;
|
||||
|
||||
// Start with top line (looks better)
|
||||
_rowNoToScreen = _numRows;
|
||||
_charPosToScreen = _numCols;
|
||||
}
|
||||
}
|
||||
|
||||
void _loop(unsigned long) override {
|
||||
screenUpdate();
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// DisplayInterface functions
|
||||
//
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
public:
|
||||
void loop() override {
|
||||
screenUpdate();
|
||||
if (_nextDisplay)
|
||||
_nextDisplay->loop(); // continue to next display
|
||||
return;
|
||||
}
|
||||
|
||||
// Position on nominated line number (0 to number of lines -1)
|
||||
// Clear the line in the buffer ready for updating
|
||||
// The displayNo referenced here is remembered and any following
|
||||
// calls to write() will be directed to that display.
|
||||
void setRow(uint8_t displayNo, byte line) override {
|
||||
_selectedDisplayNo = displayNo;
|
||||
if (displayNo == _displayNo) {
|
||||
if (line == 255) {
|
||||
// LCD(255,"xxx") or SCREEN(displayNo,255, "xxx") -
|
||||
// scroll the contents of the buffer and put the new line
|
||||
// at the bottom of the screen
|
||||
for (int row=1; row<_numRows; row++) {
|
||||
strncpy(&_buffer[(row-1)*_numCols], &_buffer[row*_numCols], _numCols);
|
||||
_rowGeneration[row-1]++;
|
||||
}
|
||||
line = _numRows-1;
|
||||
} else if (line >= _numRows)
|
||||
line = _numRows - 1; // Overwrite bottom line.
|
||||
|
||||
_rowNo = line;
|
||||
// Fill line with blanks
|
||||
for (_colNo = 0; _colNo < _numCols; _colNo++)
|
||||
_buffer[_rowNo*_numCols+_colNo] = ' ';
|
||||
_colNo = 0;
|
||||
// Mark that the buffer has been touched. It will be
|
||||
// sent to the screen on the next loop entry, by which time
|
||||
// the line should have been written to the buffer.
|
||||
_rowGeneration[_rowNo]++;
|
||||
|
||||
}
|
||||
if (_nextDisplay)
|
||||
_nextDisplay->setRow(displayNo, line); // Pass to next display
|
||||
|
||||
}
|
||||
|
||||
// Write one character to the screen referenced in the last setRow() call.
|
||||
size_t write(uint8_t c) override {
|
||||
if (_selectedDisplayNo == _displayNo) {
|
||||
// Write character to buffer (if there's space)
|
||||
if (_colNo < _numCols) {
|
||||
_buffer[_rowNo*_numCols+_colNo++] = c;
|
||||
}
|
||||
}
|
||||
if (_nextDisplay)
|
||||
_nextDisplay->write(c);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write blanks to all of the screen (blocks until complete)
|
||||
void clear (uint8_t displayNo) override {
|
||||
if (displayNo == _displayNo) {
|
||||
// Clear buffer
|
||||
for (_rowNo = 0; _rowNo < _numRows; _rowNo++) {
|
||||
setRow(displayNo, _rowNo);
|
||||
}
|
||||
_rowNo = 0;
|
||||
}
|
||||
if (_nextDisplay)
|
||||
_nextDisplay->clear(displayNo); // Pass to next display
|
||||
}
|
||||
|
||||
// Display information about the device.
|
||||
void _display() {
|
||||
DIAG(F("TFTDisplay %d Configured addr %s"), _displayNo, _I2CAddress.toString());
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_TFTDDISPLAY_H
|
134
IO_TouchKeypad.h
Normal file
134
IO_TouchKeypad.h
Normal file
@@ -0,0 +1,134 @@
|
||||
/*
|
||||
* © 2023, 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Driver for capacitative touch-pad based on the TTP229-B chip with serial
|
||||
* (not I2C) output. The touchpad has 16 separate pads in a 4x4 matrix,
|
||||
* numbered 1-16. The communications with the pad are via a clock signal sent
|
||||
* from the controller to the device, and a data signal sent back by the device.
|
||||
* The pins clockPin and dataPin must be local pins, not external (GPIO Expander)
|
||||
* pins.
|
||||
*
|
||||
* To use,
|
||||
* TouchKeypad::create(firstVpin, 16, clockPin, dataPin);
|
||||
*
|
||||
* NOTE: Most of these keypads ship with only 8 pads enabled. To enable all
|
||||
* sixteen pads, locate the area of the board labelled P1 (four pairs of
|
||||
* holes labelled 1 to 4 from the left); solder a jumper link between the pair
|
||||
* labelled 3 (connected to pin TP2 on the chip). When this link is connected,
|
||||
* the pins OUT1 to OUT8 are not used but all sixteen touch pads are operational.
|
||||
*
|
||||
* TODO: Allow a list of datapins to be provided so that multiple keypads can
|
||||
* be read simultaneously by the one device driver and the one shared clock signal.
|
||||
* As it stands, we can configure multiple driver instances, one for each keypad,
|
||||
* and it will work fine. The clock will be driven to all devices but only one
|
||||
* driver will be reading the responses from its corresponding device at a time.
|
||||
*/
|
||||
|
||||
#ifndef IO_TOUCHKEYPAD_H
|
||||
#define IO_TOUCHKEYPAD_H
|
||||
|
||||
#include "IODevice.h"
|
||||
|
||||
class TouchKeypad : public IODevice {
|
||||
private:
|
||||
// Here we define the device-specific variables.
|
||||
uint16_t _inputStates = 0;
|
||||
VPIN _clockPin;
|
||||
VPIN _dataPin;
|
||||
|
||||
public:
|
||||
// Static function to handle create calls.
|
||||
static void create(VPIN firstVpin, int nPins, VPIN clockPin, VPIN dataPin) {
|
||||
if (checkNoOverlap(firstVpin,nPins)) new TouchKeypad(firstVpin, nPins, clockPin, dataPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor.
|
||||
TouchKeypad(VPIN firstVpin, int nPins, VPIN clockPin, VPIN dataPin) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = (nPins > 16) ? 16 : nPins; // Maximum of 16 pads per device
|
||||
_clockPin = clockPin;
|
||||
_dataPin = dataPin;
|
||||
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
// Set clock pin as output, initially high, and data pin as input.
|
||||
// Enable pullup on the input so that the default (not connected) state is
|
||||
// 'keypad not pressed'.
|
||||
ArduinoPins::fastWriteDigital(_clockPin, 1);
|
||||
pinMode(_clockPin, OUTPUT);
|
||||
pinMode(_dataPin, INPUT_PULLUP); // Force defined state when no connection
|
||||
}
|
||||
|
||||
// Device-specific read function.
|
||||
int _read(VPIN vpin) {
|
||||
if (vpin < _firstVpin || vpin >= _firstVpin + _nPins) return 0;
|
||||
|
||||
// Return a value for the specified vpin.
|
||||
return _inputStates & (1<<(vpin-_firstVpin)) ? 1 : 0;
|
||||
}
|
||||
|
||||
// Loop function to do background scanning of the keyboard.
|
||||
// The TTP229 device requires clock pulses to be sent to it,
|
||||
// and the data bits can be read on the rising edge of the clock.
|
||||
// By default the clock and data are inverted (active-low).
|
||||
// A gap of more than 2ms is advised between successive read
|
||||
// cycles, we wait for 100ms between reads of the keyboard as this
|
||||
// provide a good enough response time.
|
||||
// Maximum clock frequency is 512kHz, so put a 1us delay
|
||||
// between clock transitions.
|
||||
//
|
||||
void _loop(unsigned long currentMicros) {
|
||||
|
||||
// Clock 16 bits from the device
|
||||
uint16_t data = 0, maskBit = 0x01;
|
||||
for (uint8_t pad=0; pad<16; pad++) {
|
||||
ArduinoPins::fastWriteDigital(_clockPin, 0);
|
||||
delayMicroseconds(1);
|
||||
ArduinoPins::fastWriteDigital(_clockPin, 1);
|
||||
data |= (ArduinoPins::fastReadDigital(_dataPin) ? 0 : maskBit);
|
||||
maskBit <<= 1;
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
_inputStates = data;
|
||||
#ifdef DIAG_IO
|
||||
static uint16_t lastData = 0;
|
||||
if (data != lastData) DIAG(F("KeyPad: %x"), data);
|
||||
lastData = data;
|
||||
#endif
|
||||
delayUntil(currentMicros + 100000); // read every 100ms
|
||||
}
|
||||
|
||||
// Display information about the device, and perhaps its current condition (e.g. active, disabled etc).
|
||||
void _display() {
|
||||
DIAG(F("TouchKeypad Configured on Vpins:%u-%u SCL=%d SDO=%d"), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _clockPin, _dataPin);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_TOUCHKEYPAD_H
|
@@ -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(""));
|
||||
}
|
||||
|
136
IO_Wire.h
136
IO_Wire.h
@@ -1,136 +0,0 @@
|
||||
/*
|
||||
* © 2023, 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The purpose of this module is to provide an interface to the DCC
|
||||
* I2CManager that is compatible with code written for the Arduino
|
||||
* 'Wire' interface.
|
||||
*
|
||||
* To use it, just replace
|
||||
* #include "Wire.h" or #include <Wire.h>
|
||||
* with
|
||||
* #include "IO_Wire.h"
|
||||
*
|
||||
* Note that the CS only supports I2C master mode, so the calls related to
|
||||
* slave mode are not implemented here.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_WIRE
|
||||
#define IO_WIRE
|
||||
|
||||
#include "IODevice.h"
|
||||
|
||||
#ifndef I2C_USE_WIRE
|
||||
|
||||
class IO_Wire : public IODevice, public Stream {
|
||||
public:
|
||||
IO_Wire() {
|
||||
addDevice(this);
|
||||
};
|
||||
void begin() {
|
||||
I2CManager.begin();
|
||||
}
|
||||
void setClock(uint32_t speed) {
|
||||
I2CManager.setClock(speed);
|
||||
}
|
||||
void beginTransmission(uint8_t address) {
|
||||
i2cAddress = address;
|
||||
outputLength = 0;
|
||||
}
|
||||
size_t write(byte value) override {
|
||||
if (outputLength < sizeof(outputBuffer)) {
|
||||
outputBuffer[outputLength++] = value;
|
||||
return 1;
|
||||
} else
|
||||
return 0;
|
||||
}
|
||||
size_t write(const uint8_t *buffer, size_t size) override {
|
||||
for (size_t i=0; i<size; i++) {
|
||||
if (!write(buffer[i])) return i;
|
||||
}
|
||||
return size;
|
||||
}
|
||||
uint8_t endTransmission(bool) {
|
||||
// As this software doesn't run in a multi-master environment, there
|
||||
// is no advantage to holding the bus between transactions. Therefore,
|
||||
// for simplicity, a stop condition is always sent.
|
||||
return I2CManager.write(i2cAddress, outputBuffer, outputLength);
|
||||
}
|
||||
uint8_t requestFrom(uint8_t address, uint8_t readSize, uint8_t sendStop) {
|
||||
(void)sendStop; // suppress compiler warning
|
||||
uint8_t status = I2CManager.read(address, inputBuffer, readSize);
|
||||
inputPos = 0;
|
||||
inputLength = readSize;
|
||||
return status;
|
||||
}
|
||||
uint8_t requestFrom(uint8_t address, uint8_t quantity)
|
||||
{
|
||||
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
|
||||
}
|
||||
uint8_t requestFrom(int address, int quantity)
|
||||
{
|
||||
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)true);
|
||||
}
|
||||
uint8_t requestFrom(int address, int quantity, int sendStop)
|
||||
{
|
||||
return requestFrom((uint8_t)address, (uint8_t)quantity, (uint8_t)sendStop);
|
||||
}
|
||||
int read() override {
|
||||
if (inputPos < inputLength)
|
||||
return inputBuffer[inputPos++];
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
int available() override {
|
||||
return (inputPos < inputLength);
|
||||
}
|
||||
int peek() override {
|
||||
if (inputPos < inputLength)
|
||||
return inputBuffer[inputPos];
|
||||
else
|
||||
return -1;
|
||||
}
|
||||
uint8_t endTransmission() {
|
||||
return endTransmission(true);
|
||||
}
|
||||
|
||||
static IO_Wire Wire();
|
||||
|
||||
protected:
|
||||
void _begin() { }
|
||||
void _display() {
|
||||
DIAG(F("I2CManager Wire Interface"));
|
||||
}
|
||||
|
||||
private:
|
||||
uint8_t outputBuffer[32];
|
||||
uint8_t outputLength = 0;
|
||||
uint8_t inputBuffer[32];
|
||||
uint8_t inputLength = 0;
|
||||
uint8_t inputPos = 0;
|
||||
uint8_t i2cAddress;
|
||||
};
|
||||
|
||||
static IO_Wire Wire;
|
||||
|
||||
#else
|
||||
#include <Wire.h>
|
||||
#endif
|
||||
#endif
|
@@ -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);
|
||||
|
232
MotorDriver.cpp
232
MotorDriver.cpp
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
* © 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
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -27,19 +27,16 @@
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.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;
|
||||
@@ -75,29 +72,63 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
dualSignal=true;
|
||||
getFastPin(F("SIG2"),signalPin2,fastSignalPin2);
|
||||
pinMode(signalPin2, OUTPUT);
|
||||
|
||||
fastSignalPin2.shadowinout = NULL;
|
||||
if (HAVE_PORTA(fastSignalPin2.inout == &PORTA)) {
|
||||
DIAG(F("Found PORTA pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTA;
|
||||
}
|
||||
if (HAVE_PORTB(fastSignalPin2.inout == &PORTB)) {
|
||||
DIAG(F("Found PORTB pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTB;
|
||||
}
|
||||
if (HAVE_PORTC(fastSignalPin2.inout == &PORTC)) {
|
||||
DIAG(F("Found PORTC pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTC;
|
||||
}
|
||||
}
|
||||
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) {
|
||||
senseOffset = ADCee::init(currentPin);
|
||||
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
|
||||
@@ -119,21 +150,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("CurrentPin=A%d, Offset=%d, TripValue=%d"),
|
||||
currentPin-A0, senseOffset,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() {
|
||||
@@ -142,8 +168,15 @@ bool MotorDriver::isPWMCapable() {
|
||||
|
||||
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
if (powerMode == mode) return;
|
||||
bool on=mode==POWERMODE::ON;
|
||||
if (on) {
|
||||
// when switching a track On, we need to check the crrentOffset with the pin OFF
|
||||
if (powerMode==POWERMODE::OFF && currentPin!=UNUSED_PIN) {
|
||||
senseOffset = ADCee::read(currentPin);
|
||||
DIAG(F("Track %c sensOffset=%d"),trackLetter,senseOffset);
|
||||
}
|
||||
|
||||
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
||||
if (isProgTrack)
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
@@ -189,12 +222,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
|
||||
@@ -234,7 +272,7 @@ void MotorDriver::startCurrentFromHW() {
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
247, 262, 294, 330,
|
||||
249, 392, 440, 494,
|
||||
349, 392, 440, 494,
|
||||
523, 587, 659, 698,
|
||||
494, 440, 392, 249,
|
||||
330, 284, 262, 247,
|
||||
@@ -249,6 +287,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
#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
|
||||
TCCR5B = (TCCR5B & B11111000) | B00000100; // same for timer 5 which is like timer 4
|
||||
#endif
|
||||
// spedcoode is a dcc speed & direction
|
||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||
@@ -262,7 +301,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
f = taurustones[ (tSpeed-2)/2 ] ;
|
||||
}
|
||||
}
|
||||
DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, f); // set DC PWM frequency to 100Hz XXX May move to setup
|
||||
}
|
||||
#endif
|
||||
if (tSpeed <= 1) brake = 255;
|
||||
@@ -271,7 +310,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
|
||||
@@ -330,63 +369,112 @@ void MotorDriver::getFastPin(const FSH* type,int pin, bool input, FASTPIN & res
|
||||
}
|
||||
|
||||
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;
|
||||
if (overloadNow) {
|
||||
// reset overload condition as we have just turned off power
|
||||
// DIAG(F("OVERLOAD POFF OFF"));
|
||||
overloadNow=false;
|
||||
setLastPowerChange();
|
||||
}
|
||||
if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) {
|
||||
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_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');
|
||||
// We have a fault pin condition to take care of
|
||||
if (!overloadNow) {
|
||||
// turn on overload condition as fault pin has gone active
|
||||
// DIAG(F("OVERLOAD FPIN ON"));
|
||||
overloadNow=true;
|
||||
setLastPowerChangeOverload();
|
||||
}
|
||||
lastCurrent = -lastCurrent;
|
||||
{
|
||||
if (lastCurrent < tripValue) {
|
||||
if (power_sample_overload_wait <= (POWER_SAMPLE_OVERLOAD_WAIT * 10) && // almost virgin
|
||||
microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_LOW) {
|
||||
// Ignore 50ms fault pin if no current
|
||||
DIAG(F("TRACK %c FAULT PIN (50ms ignore)"), trackno + 'A');
|
||||
break;
|
||||
}
|
||||
lastCurrent = tripValue; // exaggerate so condition below (*) is true
|
||||
} else {
|
||||
DIAG(F("TRACK %c FAULT PIN ACTIVE - OVERLOAD"), trackno + 'A');
|
||||
if (lastCurrent < tripValue) {
|
||||
lastCurrent = tripValue; // exaggerate
|
||||
}
|
||||
if (power_sample_overload_wait <= POWER_SAMPLE_OVERLOAD_WAIT && // virgin
|
||||
microsSinceLastPowerChange() < POWER_SAMPLE_IGNORE_FAULT_HIGH) {
|
||||
// Ignore 5ms fault pin if we see current
|
||||
DIAG(F("TRACK %c FAULT PIN (5ms ignore)"), trackno + 'A');
|
||||
break;
|
||||
}
|
||||
}
|
||||
DIAG(F("TRACK %c FAULT PIN"), trackno + 'A');
|
||||
}
|
||||
}
|
||||
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;
|
||||
// // //
|
||||
// above we looked at fault pin, below we look at current
|
||||
// // //
|
||||
if (lastCurrent < tripValue) { // see above (*)
|
||||
if (overloadNow) {
|
||||
// current is below trip value, turn off overload condition
|
||||
// DIAG(F("OVERLOAD PON OFF"));
|
||||
overloadNow=false;
|
||||
setLastPowerChange();
|
||||
}
|
||||
if (microsSinceLastPowerChange() > POWER_SAMPLE_ALL_GOOD) {
|
||||
power_sample_overload_wait = POWER_SAMPLE_OVERLOAD_WAIT;
|
||||
}
|
||||
} else {
|
||||
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;
|
||||
// too much current
|
||||
if (!overloadNow) {
|
||||
// current is over trip value, turn on overload condition
|
||||
// DIAG(F("OVERLOAD PON ON"));
|
||||
overloadNow=true;
|
||||
setLastPowerChange();
|
||||
}
|
||||
unsigned long uSecs = microsSinceLastPowerChange();
|
||||
if (power_sample_overload_wait > POWER_SAMPLE_OVERLOAD_WAIT || // not virgin
|
||||
uSecs > POWER_SAMPLE_OFF_DELAY) {
|
||||
// Overload has existed longer than delay (typ. 10ms)
|
||||
setPower(POWERMODE::OVERLOAD);
|
||||
if (overloadNow) {
|
||||
// the setPower just turned off, so overload is now gone
|
||||
// DIAG(F("OVERLOAD PON OFF"));
|
||||
overloadNow=false;
|
||||
setLastPowerChangeOverload();
|
||||
}
|
||||
unsigned int mA=raw2mA(lastCurrent);
|
||||
unsigned int maxmA=raw2mA(tripValue);
|
||||
DIAG(F("TRACK %c POWER OVERLOAD %4dmA (max %4dmA) detected after %4M. Pause %4M"),
|
||||
trackno + 'A', mA, maxmA, uSecs, power_sample_overload_wait);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case POWERMODE::OVERLOAD:
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
case POWERMODE::OVERLOAD:
|
||||
{
|
||||
// Try setting it back on after the OVERLOAD_WAIT
|
||||
unsigned long mslpc = (commonFaultPin ? (micros() - globalOverloadStart) : microsSinceLastPowerChange());
|
||||
if (mslpc > power_sample_overload_wait) {
|
||||
// adjust next wait time
|
||||
power_sample_overload_wait *= 2;
|
||||
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||
// power on test
|
||||
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.
|
||||
// here we change power but not the overloadNow as that was
|
||||
// already changed to false when we entered POWERMODE::OVERLOAD
|
||||
// so we need to set the lastPowerChange anyway.
|
||||
overloadNow=false;
|
||||
setLastPowerChange();
|
||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@@ -74,8 +74,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:
|
||||
@@ -111,8 +112,8 @@ enum class POWERMODE : byte { OFF, ON, OVERLOAD };
|
||||
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
|
||||
@@ -174,7 +175,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;
|
||||
}
|
||||
@@ -182,11 +186,36 @@ class MotorDriver {
|
||||
isProgTrack = on;
|
||||
}
|
||||
void checkPowerOverload(bool useProgLimit, byte trackno);
|
||||
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() {
|
||||
unsigned long now = micros();
|
||||
unsigned long diff = now - lastPowerChange;
|
||||
if (diff > (1UL << (7 *sizeof(unsigned long)))) // 2^(4*7)us = 268.4 seconds
|
||||
lastPowerChange = now - 30000000UL; // 30 seconds ago
|
||||
return diff;
|
||||
};
|
||||
inline void setLastPowerChange() {
|
||||
lastPowerChange = micros();
|
||||
};
|
||||
// as setLastPowerChange but sets the global timestamp as well which
|
||||
// is only used to sync power restore in case of common Fault pin.
|
||||
inline void setLastPowerChangeOverload() {
|
||||
if (commonFaultPin)
|
||||
globalOverloadStart = lastPowerChange = micros();
|
||||
else
|
||||
setLastPowerChange();
|
||||
};
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
bool sampleCurrentFromHW();
|
||||
void startCurrentFromHW();
|
||||
#endif
|
||||
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) {
|
||||
@@ -198,6 +227,7 @@ class MotorDriver {
|
||||
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
|
||||
@@ -211,8 +241,10 @@ class MotorDriver {
|
||||
int rawCurrentTripValue;
|
||||
// current sampling
|
||||
POWERMODE powerMode;
|
||||
unsigned long lastSampleTaken;
|
||||
unsigned int sampleDelay;
|
||||
bool overloadNow = false;
|
||||
unsigned long lastPowerChange; // timestamp in microseconds
|
||||
// used to sync restore time when common Fault pin detected
|
||||
static unsigned long globalOverloadStart; // timestamp in microseconds
|
||||
int progTripValue;
|
||||
int lastCurrent;
|
||||
#ifdef ANALOG_READ_INTERRUPT
|
||||
@@ -222,10 +254,19 @@ 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 = 100UL;
|
||||
// Time after we consider all faults old and forgotten
|
||||
static const unsigned long POWER_SAMPLE_ALL_GOOD = 5000000UL;
|
||||
// How long to ignore fault pin if current is under limit
|
||||
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_LOW = 50000UL;
|
||||
// How long to ignore fault pin if current is higher than limit
|
||||
static const unsigned long POWER_SAMPLE_IGNORE_FAULT_HIGH = 5000UL;
|
||||
// How long to wait between overcurrent and turning off
|
||||
static const unsigned long POWER_SAMPLE_OFF_DELAY = 10000UL;
|
||||
// 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.
|
||||
|
@@ -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
|
||||
|
197
Release_Notes/CommandRef.md
Normal file
197
Release_Notes/CommandRef.md
Normal file
@@ -0,0 +1,197 @@
|
||||
This file is being used to consolidate the command reference information.
|
||||
|
||||
General points:
|
||||
- Commands below have a single character opcode and parameters.
|
||||
Even <JA> is actually read as <J A>
|
||||
- Keyword parameters are shown in upper case but may be entered in mixed case.
|
||||
- value parameters are decimal numeric (unless otherwise noted)
|
||||
- [something] indicates its optional.
|
||||
- Not all commands have a response, and broadcasts mean that not all responses come from the last commands that you have issued.
|
||||
|
||||
Startup status
|
||||
<s> Return status like
|
||||
<iDCC-EX V-4.2.22 / MEGA / STANDARD_MOTOR_SHIELD G-devel-202302281422Z>
|
||||
also returns defined turnout list:
|
||||
<H id 1|0> 1=thrown
|
||||
|
||||
Track power management. After power commands a power state is broadcast to all throttles.
|
||||
|
||||
<1> Power on all
|
||||
<1 MAIN|PROG|JOIN> Power on MAIN or PROG track
|
||||
<1 JOIN> Power on MAIN and PROG track but send main track data on both.
|
||||
<0> Power off all tracks
|
||||
<0 MAIN|PROG> Power off main or prog track
|
||||
|
||||
Basic manual loco control
|
||||
<t locoid speed direction> Throttle loco.
|
||||
speed in JMRI-form (-1=ESTOP, 0=STOP, 1..126 = DCC speeds 2..127)
|
||||
direction 1=forward, 0=reverse
|
||||
For response see broadcast <l>
|
||||
|
||||
<F locoid function 1|0> Set loco function 1=ON, 0-OFF
|
||||
For response see broadcast <l>
|
||||
|
||||
<!> emergency stop all locos
|
||||
<T id 0|1|T|C> Control turnout id, 0=C=Closed, 1=T=Thrown
|
||||
response broadcast <H id 0|1>
|
||||
|
||||
|
||||
DCC accessory control
|
||||
<a address subaddress activate [onoff]>
|
||||
<a linearaddress activate>
|
||||
|
||||
|
||||
Turnout definition
|
||||
Note: Turnouts are best defined in myAutomation.h where a turnout description can also be provided ( refer to EXRAIL documentation) or by using these commands in a mySetup.h file.
|
||||
|
||||
<T id SERVO vpin thrown closed profile>
|
||||
<T id VPIN vpin>
|
||||
<T id DCC addr subaddr>
|
||||
<T id DCC linearaddr>
|
||||
Valid commands respond with <O>
|
||||
|
||||
Direct pin manipulation (replaces <Z commands, no predefinition required)
|
||||
<z vpin> Set pin HIGH
|
||||
<z -vpin> Set pin LOW
|
||||
<z vpin value> Set pin analog value
|
||||
<z vpin value profile> Set pin analog with profile
|
||||
<z vpin value profile duration> set pin analog with profile and value
|
||||
|
||||
|
||||
Sensors (Used by JMRI, not required by EXRAIL)
|
||||
<S id vpin pullup> define a sensor to be monitored.
|
||||
Responses <Q id> and <q id> as sensor changes
|
||||
|
||||
Decoder programming - main track
|
||||
<w cab cv value> POM write value to cv on loco
|
||||
<b cab cv bit value> POM write bit to cv on loco
|
||||
|
||||
Decoder Programming - prog track
|
||||
<W cabid> Clear consist and write new cab id (includes long/short settings)
|
||||
Responds <W cabid> or <W -1> for error
|
||||
<W cv value> Write value to cv
|
||||
|
||||
<V cv predictedValue> Read cv value, much faster if prediction is correct.
|
||||
<V cv bit predictedValue> Read CV bit
|
||||
|
||||
<R> Read drive-away loco id. (May be a consist id)
|
||||
<D ACK ON|OFF>
|
||||
<D ACK LIMIT|MIN|MAX|RETRY value>
|
||||
<D PROGBOOST>
|
||||
|
||||
Advanced DCC control
|
||||
<M packet.... >
|
||||
<P packet ...>
|
||||
<f map1 map2 [map3]>
|
||||
<#>
|
||||
<->
|
||||
<- cabid>
|
||||
<D CABS>
|
||||
<D SPEED28>
|
||||
<D SPEED128>
|
||||
|
||||
|
||||
EEPROM commands
|
||||
These commands exist for
|
||||
backwards JMRI compatibility.
|
||||
You are strongly discouraged from maintaining your configuration settings in EEPROM.
|
||||
<E>
|
||||
<e>
|
||||
<D EEPROM>
|
||||
<T>
|
||||
<T id>
|
||||
<S>
|
||||
<S id>
|
||||
<Z>
|
||||
<Z id>
|
||||
|
||||
Diagnostic commands
|
||||
<D CMD ON|OFF>
|
||||
<D WIFI ON|OFF>
|
||||
<D ETHERNET ON|OFF>
|
||||
<D WIT ON|OFF>
|
||||
<D LCN ON|OFF>
|
||||
<D EXRAIL ON|OFF>
|
||||
<D RESET>
|
||||
<D SERVO|ANOUT vpin position [profile]>
|
||||
<D ANIN vpin>
|
||||
<D HAL SHOW>
|
||||
<D HAL RESET>
|
||||
<+ cmd>
|
||||
<+>
|
||||
<Q>
|
||||
|
||||
User defined filter commands
|
||||
<U ....>
|
||||
<u ....>
|
||||
|
||||
Track Management
|
||||
<=>
|
||||
<= track DCC|PROG|OFF>
|
||||
<= track DC|DCX cabid>
|
||||
<JG>
|
||||
<JI>
|
||||
|
||||
|
||||
Turntable interface
|
||||
<D TT vpin steps [activity]>
|
||||
|
||||
Fast clock interface
|
||||
<JC>
|
||||
<JC mins rate>
|
||||
|
||||
|
||||
Advanced Throttle access to features
|
||||
<t cab>
|
||||
<JA>
|
||||
<JA id>
|
||||
<JR>
|
||||
<JR id>
|
||||
<JT>
|
||||
<JT id>
|
||||
|
||||
*******************
|
||||
EXRAIL Commands
|
||||
*******************
|
||||
|
||||
</>
|
||||
</PAUSE>
|
||||
</RESUME>
|
||||
</START cab sequence>
|
||||
</START sequence>
|
||||
</KILL taskid>
|
||||
</KILL ALL>
|
||||
</RESERVE|FREE blockid>
|
||||
</LATCH|UNLATCH latchid>
|
||||
</RED|AMBER|GREEN signalid>
|
||||
|
||||
Obsolete commands/formats
|
||||
<c>
|
||||
<t ignored cab speed direction>
|
||||
<T id vpin thrown closed>
|
||||
<T id addr subaddr>
|
||||
<B cv bit value obsolete obsolete>
|
||||
<R cv obsolete obsolete>
|
||||
<W cv value obsolete obsolete>
|
||||
<R cv> V command is much faster if prediction is correct.
|
||||
<B cv bit value> V command is much faster if prediction is correct.
|
||||
<Z id vpin active> (use <z) Define an output pin that JMRI can set by id
|
||||
<Z id activate> (use <z) Activate an output pin by id
|
||||
|
||||
|
||||
Broadcast responses
|
||||
Note: broadcasts are sent to all throttles when appropriate (usually because something has changed)
|
||||
|
||||
<p0>
|
||||
<p1>
|
||||
<p1 MAIN|PROG|JOIN>
|
||||
|
||||
<l cab slot dccspeed functionmap>
|
||||
<H id 1|0>
|
||||
<jC mmmm speed>
|
||||
|
||||
Diagnostic responses
|
||||
These are not meant to be software readable. They contain diagnostic information for programmers to identify issues.
|
||||
<X>
|
||||
<* ... *>
|
||||
|
@@ -236,11 +236,6 @@ void SSD1306AsciiWire::setRowNative(uint8_t line) {
|
||||
size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
|
||||
const uint8_t* base = m_font;
|
||||
|
||||
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
|
||||
return 0;
|
||||
// Check if character would be partly or wholly off the display
|
||||
if (m_col + fontWidth > m_displayWidth)
|
||||
return 0;
|
||||
#if defined(NOLOWERCASE)
|
||||
// Adjust if lowercase is missing
|
||||
if (ch >= 'a') {
|
||||
@@ -250,6 +245,12 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
|
||||
ch -= 26; // Allow for missing lowercase letters
|
||||
}
|
||||
#endif
|
||||
if (ch < m_fontFirstChar || ch >= (m_fontFirstChar + m_fontCharCount))
|
||||
return 0;
|
||||
// Check if character would be partly or wholly off the display
|
||||
if (m_col + fontWidth > m_displayWidth)
|
||||
return 0;
|
||||
|
||||
ch -= m_fontFirstChar;
|
||||
base += fontWidth * ch;
|
||||
// Before using buffer, wait for last request to complete
|
||||
@@ -406,8 +407,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x38, 0x44, 0xc6, 0x44, 0x20, 0x00, // cent 0x9b
|
||||
0x44, 0x6e, 0x59, 0x49, 0x62, 0x00, // £ 0x9c
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
@@ -425,27 +426,27 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x10, 0x28, 0x54, 0x28, 0x44, 0x00, // <<
|
||||
0x44, 0x28, 0x54, 0x28, 0x10, 0x00, // >>
|
||||
// Extended characters 176-180
|
||||
0x92, 0x00, 0x49, 0x00, 0x24, 0x00, // Light grey 0xb0
|
||||
0xcc, 0x55, 0xcc, 0x55, 0xcc, 0x55, // Mid grey 0xb1
|
||||
0x6a, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2
|
||||
0xaa, 0x44, 0xaa, 0x11, 0xaa, 0x55, // Mid grey 0xb1
|
||||
0x6d, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2
|
||||
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, // Vertical line 0xb3
|
||||
0x08, 0x08, 0x08, 0xff, 0x00, 0x00, // Vertical line with left spur 0xb4
|
||||
|
||||
0x14, 0x14, 0xfe, 0x00, 0xff, 0x00, // Vertical line with double left spur 0xb9
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented Double vertical line with single left spur
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x14, 0x14, 0x14, 0xff, 0x00, 0x00, // Vertical line with double left spur 0xb5
|
||||
0x08, 0x08, 0xff, 0x00, 0xff, 0x00, // Double vertical line with single left spur
|
||||
0x08, 0x08, 0xf8, 0x08, 0xf8, 0x00, // Top right corner, single horiz, double vert
|
||||
0x14, 0x14, 0x14, 0xfc, 0x00, 0x00, // Top right corner, double horiz, single vert
|
||||
|
||||
// Extended characters 185-190
|
||||
0x28, 0x28, 0xef, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9
|
||||
0x14, 0x14, 0xf7, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9
|
||||
0x00, 0x00, 0xff, 0x00, 0xff, 0x00, // Double vertical line 0xba
|
||||
0x14, 0x14, 0xf4, 0x04, 0xfc, 0x00, // Double top right corner 0xbb
|
||||
0x14, 0x14, 0x17, 0x10, 0x1f, 0x00, // Double bottom right corner 0xbc
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbd
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbe
|
||||
0x08, 0x08, 0x0f, 0x08, 0x0f, 0x00, // Bottom right corner, single horiz, double vert 0xbd
|
||||
0x14, 0x14, 0x14, 0x1f, 0x00, 0x00, // Bottom right corner, double horiz, single vert 0xbe
|
||||
|
||||
// Extended characters 191-199
|
||||
0x08, 0x08, 0x08, 0xf8, 0x00, 0x00, // Top right corner 0xbf
|
||||
@@ -455,8 +456,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
|
||||
0x00, 0x00, 0x00, 0xff, 0x08, 0x08, // Vertical line with right spur 0xc3
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, 0x08, // Horizontal line 0xc4
|
||||
0x08, 0x08, 0x08, 0xff, 0x08, 0x08, // Cross 0xc5
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0xff, 0x14, 0x14, // Vertical line double right spur 0xc6
|
||||
0x00, 0x00, 0xff, 0x00, 0xff, 0x08, // Double vertical line single right spur 0xc7
|
||||
|
||||
// Extended characters 200-206
|
||||
0x00, 0x00, 0x1f, 0x10, 0x17, 0x14, // Double bottom left corner 0xc8
|
||||
@@ -467,16 +468,16 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, // Double horizontal line 0xcd
|
||||
0x14, 0x14, 0xf7, 0x00, 0xf7, 0x14, // Double cross 0xce
|
||||
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xd0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x14, 0x14, 0x14, 0x17, 0x14, 0x14, // Double horizontal line single upward spur 0xcf
|
||||
0x08, 0x08, 0x0f, 0x08, 0x0f, 0x08, // Horiz single line with double upward spur 0xd0
|
||||
0x14, 0x14, 0x14, 0xf4, 0x14, 0x14, // Horiz double line with single downward spur 0xd1
|
||||
0x08, 0x08, 0xf8, 0x08, 0xf8, 0x08, // Horiz single line with double downward spur 0xd2
|
||||
0x00, 0x00, 0x0f, 0x08, 0x0f, 0x08, // Bottom left corner, double vert single horiz 0xd3
|
||||
0x00, 0x00, 0x00, 0x1f, 0x14, 0x14, // Bottom left corner, single vert double horiz 0xd4
|
||||
0x00, 0x00, 0x00, 0xfc, 0x14, 0x14, // Top left corner, single vert double horiz 0xd5
|
||||
0x00, 0x00, 0xf8, 0x08, 0xf8, 0x08, // Top left corner, double vert single horiz 0xd6
|
||||
0x08, 0x08, 0xff, 0x00, 0xff, 0x08, // Cross, double vert single horiz 0xd7
|
||||
0x14, 0x14, 0x14, 0xf7, 0x14, 0x14, // Cross, single vert double horiz 0xd8
|
||||
|
||||
// Extended characters 217-223
|
||||
0x08, 0x08, 0x08, 0x0f, 0x00, 0x00, // Bottom right corner 0xd9
|
||||
@@ -487,10 +488,10 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
|
||||
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, // Right half block 0xde
|
||||
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // Top half block 0xdf
|
||||
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xe0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0xf0, 0xf0, 0xf0, 0x00, 0x00, 0x00, // Bottom Left block 0xe0
|
||||
0x00, 0x00, 0x00, 0xf0, 0xf0, 0xf0, // Bottom Right block
|
||||
0x0f, 0x0f, 0x0f, 0x00, 0x00, 0x00, // Top left block
|
||||
0x00, 0x00, 0x00, 0x0f, 0x0f, 0x0f, // Top right block 0xe3
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
@@ -511,9 +512,8 @@ const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
// Extended character 248
|
||||
0x00, 0x06, 0x09, 0x09, 0x06, 0x00, // degree symbol 0xf8
|
||||
0x00, 0x06, 0x09, 0x09, 0x06, 0x00 // degree symbol 0xf8
|
||||
#endif
|
||||
0x00
|
||||
};
|
||||
|
||||
const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6;
|
||||
|
517
ST7735-TFT.h
517
ST7735-TFT.h
@@ -1,517 +0,0 @@
|
||||
/* Tiny TFT Graphics Library v5 - see http://www.technoblogy.com/show?3WAI
|
||||
David Johnson-Davies - www.technoblogy.com - 26th October 2022
|
||||
|
||||
CC BY 4.0
|
||||
Licensed under a Creative Commons Attribution 4.0 International license:
|
||||
http://creativecommons.org/licenses/by/4.0/
|
||||
*/
|
||||
|
||||
#include "FSH.h"
|
||||
#include "DisplayInterface.h"
|
||||
|
||||
|
||||
#if defined(MEGATINYCORE)
|
||||
// ATtiny402/412 PORTA positions. Change these for the chip you're using
|
||||
int const dc = 7;
|
||||
int const mosi = 1;
|
||||
int const sck = 3;
|
||||
int const cs = 6;
|
||||
|
||||
// ATtiny 0-, 1-, and 2-series port manipulations - assumes all pins in same port
|
||||
#define PORT_TOGGLE(x) PORTA.OUTTGL = (x)
|
||||
#define PORT_LOW(x) PORTA.OUTCLR = (x)
|
||||
#define PORT_HIGH(x) PORTA.OUTSET = (x)
|
||||
#define PORT_OUTPUT(x) PORTA.DIRSET = (x)
|
||||
|
||||
#else
|
||||
// ATtiny45/85 PORTB positions. Change these for the chip you're using
|
||||
int const dc = 0;
|
||||
int const mosi = 1;
|
||||
int const sck = 2;
|
||||
int const cs = 3;
|
||||
|
||||
// Classic ATtiny port manipulations - assumes all pins in same port
|
||||
#define PORT_TOGGLE(x) PINB = (x)
|
||||
#define PORT_LOW(x) PORTB = PORTB & ~((x));
|
||||
#define PORT_HIGH(x) PORTB = PORTB | ((x))
|
||||
#define PORT_OUTPUT(x) DDRB = (x)
|
||||
|
||||
#endif
|
||||
|
||||
// Display parameters - uncomment the line for the one you want to use
|
||||
|
||||
// Adafruit 1.44" 128x128 display
|
||||
// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1;
|
||||
|
||||
// AliExpress 1.44" 128x128 display
|
||||
// int const xsize = 128, ysize = 128, xoff = 2, yoff = 1, invert = 0, rotate = 3, bgr = 1;
|
||||
|
||||
// Adafruit 0.96" 160x80 display
|
||||
// int const xsize = 160, ysize = 80, xoff = 0, yoff = 24, invert = 0, rotate = 6, bgr = 0;
|
||||
|
||||
// AliExpress 0.96" 160x80 display
|
||||
// int const xsize = 160, ysize = 80, xoff = 1, yoff = 26, invert = 1, rotate = 0, bgr = 1;
|
||||
|
||||
// Adafruit 1.8" 160x128 display
|
||||
// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1;
|
||||
|
||||
// AliExpress 1.8" 160x128 display (red PCB)
|
||||
int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 0, bgr = 1;
|
||||
|
||||
// AliExpress 1.8" 160x128 display (blue PCB)
|
||||
// int const xsize = 160, ysize = 128, xoff = 0, yoff = 0, invert = 0, rotate = 6, bgr = 0;
|
||||
|
||||
// Adafruit 1.14" 240x135 display
|
||||
// int const xsize = 240, ysize = 135, xoff = 40, yoff = 53, invert = 1, rotate = 6, bgr = 0;
|
||||
|
||||
// AliExpress 1.14" 240x135 display
|
||||
// int const xsize = 240, ysize = 135, xoff = 40, yoff = 52, invert = 1, rotate = 0, bgr = 0;
|
||||
|
||||
// Adafruit 1.3" 240x240 display
|
||||
// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0;
|
||||
|
||||
// Adafruit 1.54" 240x240 display
|
||||
// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0;
|
||||
|
||||
// AliExpress 1.54" 240x240 display
|
||||
// int const xsize = 240, ysize = 240, xoff = 0, yoff = 80, invert = 1, rotate = 5, bgr = 0;
|
||||
|
||||
// Adafruit 1.9" 320x170 display
|
||||
// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0;
|
||||
|
||||
// AliExpress 1.9" 320x170 display
|
||||
// int const xsize = 320, ysize = 170, xoff = 0, yoff = 35, invert = 1, rotate = 0, bgr = 0;
|
||||
|
||||
// Adafruit 1.47" 320x172 rounded rectangle display
|
||||
// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0;
|
||||
|
||||
// AliExpress 1.47" 320x172 rounded rectangle display
|
||||
// int const xsize = 320, ysize = 172, xoff = 0, yoff = 34, invert = 1, rotate = 0, bgr = 0;
|
||||
|
||||
// Adafruit 2.0" 320x240 display
|
||||
// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 6, bgr = 0;
|
||||
|
||||
// AliExpress 2.0" 320x240 display
|
||||
// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 1, rotate = 0, bgr = 0;
|
||||
|
||||
// Adafruit 2.2" 320x240 display
|
||||
// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 4, bgr = 1;
|
||||
|
||||
// AliExpress 2.4" 320x240 display
|
||||
// int const xsize = 320, ysize = 240, xoff = 0, yoff = 0, invert = 0, rotate = 2, bgr = 1;
|
||||
|
||||
// Character set for text - stored in program memory
|
||||
const uint8_t CharMap[96][6] FLASH = {
|
||||
{ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 },
|
||||
{ 0x00, 0x00, 0x5F, 0x00, 0x00, 0x00 },
|
||||
{ 0x00, 0x07, 0x00, 0x07, 0x00, 0x00 },
|
||||
{ 0x14, 0x7F, 0x14, 0x7F, 0x14, 0x00 },
|
||||
{ 0x24, 0x2A, 0x7F, 0x2A, 0x12, 0x00 },
|
||||
{ 0x23, 0x13, 0x08, 0x64, 0x62, 0x00 },
|
||||
{ 0x36, 0x49, 0x56, 0x20, 0x50, 0x00 },
|
||||
{ 0x00, 0x08, 0x07, 0x03, 0x00, 0x00 },
|
||||
{ 0x00, 0x1C, 0x22, 0x41, 0x00, 0x00 },
|
||||
{ 0x00, 0x41, 0x22, 0x1C, 0x00, 0x00 },
|
||||
{ 0x2A, 0x1C, 0x7F, 0x1C, 0x2A, 0x00 },
|
||||
{ 0x08, 0x08, 0x3E, 0x08, 0x08, 0x00 },
|
||||
{ 0x00, 0x80, 0x70, 0x30, 0x00, 0x00 },
|
||||
{ 0x08, 0x08, 0x08, 0x08, 0x08, 0x00 },
|
||||
{ 0x00, 0x00, 0x60, 0x60, 0x00, 0x00 },
|
||||
{ 0x20, 0x10, 0x08, 0x04, 0x02, 0x00 },
|
||||
{ 0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00 },
|
||||
{ 0x00, 0x42, 0x7F, 0x40, 0x00, 0x00 },
|
||||
{ 0x72, 0x49, 0x49, 0x49, 0x46, 0x00 },
|
||||
{ 0x21, 0x41, 0x49, 0x4D, 0x33, 0x00 },
|
||||
{ 0x18, 0x14, 0x12, 0x7F, 0x10, 0x00 },
|
||||
{ 0x27, 0x45, 0x45, 0x45, 0x39, 0x00 },
|
||||
{ 0x3C, 0x4A, 0x49, 0x49, 0x31, 0x00 },
|
||||
{ 0x41, 0x21, 0x11, 0x09, 0x07, 0x00 },
|
||||
{ 0x36, 0x49, 0x49, 0x49, 0x36, 0x00 },
|
||||
{ 0x46, 0x49, 0x49, 0x29, 0x1E, 0x00 },
|
||||
{ 0x00, 0x00, 0x14, 0x00, 0x00, 0x00 },
|
||||
{ 0x00, 0x40, 0x34, 0x00, 0x00, 0x00 },
|
||||
{ 0x00, 0x08, 0x14, 0x22, 0x41, 0x00 },
|
||||
{ 0x14, 0x14, 0x14, 0x14, 0x14, 0x00 },
|
||||
{ 0x00, 0x41, 0x22, 0x14, 0x08, 0x00 },
|
||||
{ 0x02, 0x01, 0x59, 0x09, 0x06, 0x00 },
|
||||
{ 0x3E, 0x41, 0x5D, 0x59, 0x4E, 0x00 },
|
||||
{ 0x7C, 0x12, 0x11, 0x12, 0x7C, 0x00 },
|
||||
{ 0x7F, 0x49, 0x49, 0x49, 0x36, 0x00 },
|
||||
{ 0x3E, 0x41, 0x41, 0x41, 0x22, 0x00 },
|
||||
{ 0x7F, 0x41, 0x41, 0x41, 0x3E, 0x00 },
|
||||
{ 0x7F, 0x49, 0x49, 0x49, 0x41, 0x00 },
|
||||
{ 0x7F, 0x09, 0x09, 0x09, 0x01, 0x00 },
|
||||
{ 0x3E, 0x41, 0x41, 0x51, 0x73, 0x00 },
|
||||
{ 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00 },
|
||||
{ 0x00, 0x41, 0x7F, 0x41, 0x00, 0x00 },
|
||||
{ 0x20, 0x40, 0x41, 0x3F, 0x01, 0x00 },
|
||||
{ 0x7F, 0x08, 0x14, 0x22, 0x41, 0x00 },
|
||||
{ 0x7F, 0x40, 0x40, 0x40, 0x40, 0x00 },
|
||||
{ 0x7F, 0x02, 0x1C, 0x02, 0x7F, 0x00 },
|
||||
{ 0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00 },
|
||||
{ 0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00 },
|
||||
{ 0x7F, 0x09, 0x09, 0x09, 0x06, 0x00 },
|
||||
{ 0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00 },
|
||||
{ 0x7F, 0x09, 0x19, 0x29, 0x46, 0x00 },
|
||||
{ 0x26, 0x49, 0x49, 0x49, 0x32, 0x00 },
|
||||
{ 0x03, 0x01, 0x7F, 0x01, 0x03, 0x00 },
|
||||
{ 0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00 },
|
||||
{ 0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00 },
|
||||
{ 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00 },
|
||||
{ 0x63, 0x14, 0x08, 0x14, 0x63, 0x00 },
|
||||
{ 0x03, 0x04, 0x78, 0x04, 0x03, 0x00 },
|
||||
{ 0x61, 0x59, 0x49, 0x4D, 0x43, 0x00 },
|
||||
{ 0x00, 0x7F, 0x41, 0x41, 0x41, 0x00 },
|
||||
{ 0x02, 0x04, 0x08, 0x10, 0x20, 0x00 },
|
||||
{ 0x00, 0x41, 0x41, 0x41, 0x7F, 0x00 },
|
||||
{ 0x04, 0x02, 0x01, 0x02, 0x04, 0x00 },
|
||||
{ 0x40, 0x40, 0x40, 0x40, 0x40, 0x00 },
|
||||
{ 0x00, 0x03, 0x07, 0x08, 0x00, 0x00 },
|
||||
{ 0x20, 0x54, 0x54, 0x78, 0x40, 0x00 },
|
||||
{ 0x7F, 0x28, 0x44, 0x44, 0x38, 0x00 },
|
||||
{ 0x38, 0x44, 0x44, 0x44, 0x28, 0x00 },
|
||||
{ 0x38, 0x44, 0x44, 0x28, 0x7F, 0x00 },
|
||||
{ 0x38, 0x54, 0x54, 0x54, 0x18, 0x00 },
|
||||
{ 0x00, 0x08, 0x7E, 0x09, 0x02, 0x00 },
|
||||
{ 0x18, 0xA4, 0xA4, 0x9C, 0x78, 0x00 },
|
||||
{ 0x7F, 0x08, 0x04, 0x04, 0x78, 0x00 },
|
||||
{ 0x00, 0x44, 0x7D, 0x40, 0x00, 0x00 },
|
||||
{ 0x20, 0x40, 0x40, 0x3D, 0x00, 0x00 },
|
||||
{ 0x7F, 0x10, 0x28, 0x44, 0x00, 0x00 },
|
||||
{ 0x00, 0x41, 0x7F, 0x40, 0x00, 0x00 },
|
||||
{ 0x7C, 0x04, 0x78, 0x04, 0x78, 0x00 },
|
||||
{ 0x7C, 0x08, 0x04, 0x04, 0x78, 0x00 },
|
||||
{ 0x38, 0x44, 0x44, 0x44, 0x38, 0x00 },
|
||||
{ 0xFC, 0x18, 0x24, 0x24, 0x18, 0x00 },
|
||||
{ 0x18, 0x24, 0x24, 0x18, 0xFC, 0x00 },
|
||||
{ 0x7C, 0x08, 0x04, 0x04, 0x08, 0x00 },
|
||||
{ 0x48, 0x54, 0x54, 0x54, 0x24, 0x00 },
|
||||
{ 0x04, 0x04, 0x3F, 0x44, 0x24, 0x00 },
|
||||
{ 0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00 },
|
||||
{ 0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00 },
|
||||
{ 0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00 },
|
||||
{ 0x44, 0x28, 0x10, 0x28, 0x44, 0x00 },
|
||||
{ 0x4C, 0x90, 0x90, 0x90, 0x7C, 0x00 },
|
||||
{ 0x44, 0x64, 0x54, 0x4C, 0x44, 0x00 },
|
||||
{ 0x00, 0x08, 0x36, 0x41, 0x00, 0x00 },
|
||||
{ 0x00, 0x00, 0x77, 0x00, 0x00, 0x00 },
|
||||
{ 0x00, 0x41, 0x36, 0x08, 0x00, 0x00 },
|
||||
{ 0x00, 0x06, 0x09, 0x06, 0x00, 0x00 }, // degree symbol = '~'
|
||||
{ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00 }
|
||||
};
|
||||
|
||||
// TFT colour display **********************************************
|
||||
|
||||
int const CASET = 0x2A; // Define column address
|
||||
int const RASET = 0x2B; // Define row address
|
||||
int const RAMWR = 0x2C; // Write to display RAM
|
||||
|
||||
int const White = 0xFFFF;
|
||||
int const Black = 0;
|
||||
|
||||
// Current plot position and colours
|
||||
int xpos, ypos;
|
||||
int fore = White;
|
||||
int back = Black;
|
||||
int scale = 1; // Text scale
|
||||
|
||||
// Send a byte to the display
|
||||
|
||||
void Data (uint8_t d) {
|
||||
for (uint8_t bit = 0x80; bit; bit >>= 1) {
|
||||
PORT_TOGGLE(1<<sck);
|
||||
if (d & bit) PORT_HIGH(1<<mosi); else PORT_LOW(1<<mosi);
|
||||
PORT_TOGGLE(1<<sck);
|
||||
}
|
||||
}
|
||||
|
||||
// Send a command to the display
|
||||
void Command (uint8_t c) {
|
||||
PORT_TOGGLE(1<<dc);
|
||||
Data(c);
|
||||
PORT_TOGGLE(1<<dc);
|
||||
}
|
||||
|
||||
// Send a command followed by two data words
|
||||
void Command2 (uint8_t c, uint16_t d1, uint16_t d2) {
|
||||
PORT_TOGGLE(1<<dc);
|
||||
Data(c);
|
||||
PORT_TOGGLE(1<<dc);
|
||||
Data(d1>>8); Data(d1); Data(d2>>8); Data(d2);
|
||||
}
|
||||
|
||||
void InitDisplay () {
|
||||
PORT_OUTPUT(1<<dc | 1<<cs | 1<<mosi | 1<<sck); // All outputs
|
||||
PORT_HIGH(1<<dc | 1<<cs | 1<<sck); // Outputs high
|
||||
PORT_TOGGLE(1<<cs);
|
||||
Command(0x01); // Software reset
|
||||
delay(250); // delay 250 ms
|
||||
Command(0x36); Data(rotate<<5 | bgr<<3); // Set orientation and rgb/bgr
|
||||
Command(0x3A); Data(0x55); // Set color mode - 16-bit color
|
||||
Command(0x20+invert); // Invert
|
||||
Command(0x11); // Out of sleep mode
|
||||
delay(150);
|
||||
PORT_TOGGLE(1<<cs);
|
||||
}
|
||||
|
||||
void DisplayOn () {
|
||||
PORT_TOGGLE(1<<cs);
|
||||
Command(0x29); // Display on
|
||||
delay(150);
|
||||
PORT_TOGGLE(1<<cs);
|
||||
}
|
||||
|
||||
void ClearDisplay () {
|
||||
PORT_TOGGLE(1<<cs);
|
||||
Command2(CASET, yoff, yoff + ysize - 1);
|
||||
Command2(RASET, xoff, xoff + xsize - 1);
|
||||
Command(0x3A); Data(0x03); // 12-bit colour
|
||||
Command(RAMWR); // Leaves mosi low
|
||||
for (int i=0; i<xsize*4; i++) {
|
||||
for (int j=0; j<ysize*3; j++) {
|
||||
PORT_TOGGLE(1<<sck);
|
||||
PORT_TOGGLE(1<<sck);
|
||||
}
|
||||
}
|
||||
Command(0x3A); Data(0x05); // Back to 16-bit colour
|
||||
PORT_TOGGLE(1<<cs);
|
||||
}
|
||||
|
||||
unsigned int Colour (int r, int g, int b) {
|
||||
return (r & 0xf8)<<8 | (g & 0xfc)<<3 | b>>3;
|
||||
}
|
||||
|
||||
// Move current plot position to x,y
|
||||
void MoveTo (int x, int y) {
|
||||
xpos = x; ypos = y;
|
||||
}
|
||||
|
||||
// Plot point at x,y
|
||||
void PlotPoint (int x, int y) {
|
||||
PORT_TOGGLE(1<<cs);
|
||||
Command2(CASET, yoff+y, yoff+y);
|
||||
Command2(RASET, xoff+x, xoff+x);
|
||||
Command(RAMWR); Data(fore>>8); Data(fore & 0xff);
|
||||
PORT_TOGGLE(1<<cs);
|
||||
}
|
||||
|
||||
// Draw a line to x,y
|
||||
void DrawTo (int x, int y) {
|
||||
int sx, sy, e2, err;
|
||||
int dx = abs(x - xpos);
|
||||
int dy = abs(y - ypos);
|
||||
if (xpos < x) sx = 1; else sx = -1;
|
||||
if (ypos < y) sy = 1; else sy = -1;
|
||||
err = dx - dy;
|
||||
for (;;) {
|
||||
PlotPoint(xpos, ypos);
|
||||
if (xpos==x && ypos==y) return;
|
||||
e2 = err<<1;
|
||||
if (e2 > -dy) { err = err - dy; xpos = xpos + sx; }
|
||||
if (e2 < dx) { err = err + dx; ypos = ypos + sy; }
|
||||
}
|
||||
}
|
||||
|
||||
void FillRect (int w, int h) {
|
||||
PORT_TOGGLE(1<<cs);
|
||||
Command2(CASET, ypos+yoff, ypos+yoff+h-1);
|
||||
Command2(RASET, xpos+xoff, xpos+xoff+w-1);
|
||||
Command(RAMWR);
|
||||
uint8_t hi = fore>>8;
|
||||
uint8_t lo = fore & 0xff;
|
||||
for (int i=0; i<w; i++) {
|
||||
for (int j=0; j<h; j++) {
|
||||
Data(hi); Data(lo);
|
||||
}
|
||||
}
|
||||
PORT_TOGGLE(1<<cs);
|
||||
}
|
||||
|
||||
void DrawRect (int w, int h) {
|
||||
int x1 = xpos, y1 = ypos;
|
||||
FillRect(w-1, 1); MoveTo(x1, y1+1);
|
||||
FillRect(1, h-1); MoveTo(x1+1, y1+h-1);
|
||||
FillRect(w-1, 1); MoveTo(x1+w-1, y1);
|
||||
FillRect(1, h-1);
|
||||
xpos = x1; ypos = y1;
|
||||
}
|
||||
|
||||
void FillCircle (int radius) {
|
||||
int x1 = xpos, y1 = ypos, dx = 1, dy = 1;
|
||||
int x = radius - 1, y = 0;
|
||||
int err = dx - (radius<<1);
|
||||
while (x >= y) {
|
||||
MoveTo(x1-x, y1+y); FillRect(x<<1, 1);
|
||||
MoveTo(x1-y, y1+x); FillRect(y<<1, 1);
|
||||
MoveTo(x1-y, y1-x); FillRect(y<<1, 1);
|
||||
MoveTo(x1-x, y1-y); FillRect(x<<1, 1);
|
||||
if (err > 0) {
|
||||
x = x - 1; dx = dx + 2;
|
||||
err = err - (radius<<1) + dx;
|
||||
} else {
|
||||
y = y + 1; err = err + dy;
|
||||
dy = dy + 2;
|
||||
}
|
||||
}
|
||||
xpos = x1; ypos = y1;
|
||||
}
|
||||
|
||||
void DrawCircle (int radius) {
|
||||
int x1 = xpos, y1 = ypos, dx = 1, dy = 1;
|
||||
int x = radius - 1, y = 0;
|
||||
int err = dx - (radius<<1);
|
||||
while (x >= y) {
|
||||
PlotPoint(x1-x, y1+y); PlotPoint(x1+x, y1+y);
|
||||
PlotPoint(x1-y, y1+x); PlotPoint(x1+y, y1+x);
|
||||
PlotPoint(x1-y, y1-x); PlotPoint(x1+y, y1-x);
|
||||
PlotPoint(x1-x, y1-y); PlotPoint(x1+x, y1-y);
|
||||
if (err > 0) {
|
||||
x = x - 1; dx = dx + 2;
|
||||
err = err - (radius<<1) + dx;
|
||||
} else {
|
||||
y = y + 1; err = err + dy;
|
||||
dy = dy + 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Plot an ASCII character with bottom left corner at x,y
|
||||
void PlotChar (char c) {
|
||||
int colour;
|
||||
PORT_TOGGLE(1<<cs);
|
||||
Command2(CASET, yoff+ypos, yoff+ypos+8*scale-1);
|
||||
Command2(RASET, xoff+xpos, xoff+xpos+6*scale-1);
|
||||
Command(RAMWR);
|
||||
for (int xx=0; xx<6; xx++) {
|
||||
int bits = pgm_read_byte(&CharMap[c-32][xx]);
|
||||
for (int xr=0; xr<scale; xr++) {
|
||||
for (int yy=0; yy<8; yy++) {
|
||||
if (bits>>(7-yy) & 1) colour = fore; else colour = back;
|
||||
for (int yr=0; yr<scale; yr++) {
|
||||
Data(colour>>8); Data(colour & 0xFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PORT_TOGGLE(1<<cs);
|
||||
xpos = xpos + 6*scale;
|
||||
}
|
||||
|
||||
// Plot text starting at the current plot position
|
||||
void PlotText(PGM_P p) {
|
||||
while (1) {
|
||||
char c = pgm_read_byte(p++);
|
||||
if (c == 0) return;
|
||||
PlotChar(c);
|
||||
}
|
||||
}
|
||||
|
||||
void PlotInt(int n) {
|
||||
bool lead = false;
|
||||
for (int d=10000; d>0; d = d/10) {
|
||||
char j = (n/d) % 10;
|
||||
if (j!=0 || lead || d==1) { PlotChar(j + '0'); lead = true; }
|
||||
}
|
||||
}
|
||||
|
||||
void TestChart () {
|
||||
DrawRect(xsize, ysize);
|
||||
scale = 8;
|
||||
fore = Colour(255, 0, 0);
|
||||
MoveTo((xsize-40)/2, (ysize-64)/2); PlotChar('F');
|
||||
scale = 1;
|
||||
}
|
||||
|
||||
// Demos **********************************************
|
||||
|
||||
void BarChart () {
|
||||
int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11;
|
||||
MoveTo(x0+(w-x1-90)/2+x1, y0+h-8); PlotText(PSTR("Sensor Readings"));
|
||||
// Horizontal axis
|
||||
int xinc = (w-x1)/20;
|
||||
MoveTo(x0+x1, y0+y1); DrawTo(x0+w-1, y0+y1);
|
||||
for (int i=0; i<=20; i=i+4) {
|
||||
int mark = x1+i*xinc;
|
||||
MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2);
|
||||
// Draw histogram
|
||||
if (i != 20) {
|
||||
int bar = xinc*4/3;
|
||||
for (int b=2; b>=0; b--) {
|
||||
fore = Colour(255, 127*b, 0); // Red, Orange, Yellow
|
||||
MoveTo(x0+mark+bar*b-b+1, y0+y1+1); FillRect(bar, 5+random(h-y1-20));
|
||||
}
|
||||
fore = White;
|
||||
}
|
||||
if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11);
|
||||
PlotInt(i);
|
||||
}
|
||||
// Vertical axis
|
||||
int yinc = (h-y1)/20;
|
||||
MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1);
|
||||
for (int i=0; i<=20; i=i+5) {
|
||||
int mark = y1+i*yinc;
|
||||
MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark);
|
||||
if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4);
|
||||
PlotInt(i);
|
||||
}
|
||||
}
|
||||
|
||||
void Waterfall () {
|
||||
int x0 = 0, y0 = 0, w = xsize, h = ysize, x1 = 15, y1 = 11;
|
||||
int factor = 5160/h*10;
|
||||
MoveTo(x0+(w-x1-60)/2+x1, y0+h-8); PlotText(PSTR("Luminance"));
|
||||
// Horizontal axis
|
||||
int xinc = (w-x1-15)/30;
|
||||
MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+xinc*20, y0+y1);
|
||||
for (int i=0; i<=20; i=i+5) {
|
||||
int mark = x1+i*xinc;
|
||||
MoveTo(x0+mark, y0+y1); DrawTo(x0+mark, y0+y1-2);
|
||||
if (i > 9) MoveTo(x0+mark-7, y0+y1-11); else MoveTo(x0+mark-3, y0+y1-11);
|
||||
PlotInt(i);
|
||||
}
|
||||
// Vertical axis
|
||||
int yinc = (h-y1)/20;
|
||||
MoveTo(x0+x1, y0+y1); DrawTo(x0+x1, y0+h-1);
|
||||
for (int i=0; i<=20; i=i+5) {
|
||||
int mark = y1+i*yinc;
|
||||
MoveTo(x0+x1, y0+mark); DrawTo(x0+x1-2, y0+mark);
|
||||
if (i > 9) MoveTo(x0+x1-15, y0+mark-4); else MoveTo(x0+x1-9, y0+mark-4);
|
||||
PlotInt(i);
|
||||
}
|
||||
// Diagonal axis
|
||||
yinc = xinc/2;
|
||||
// MoveTo(x0+x1, y0+y1); DrawTo(x0+x1+10*xinc, y0+y1+10*xinc);
|
||||
MoveTo(x0+x1+20*xinc, y0+y1); DrawTo(x0+x1+30*xinc, y0+y1+10*xinc);
|
||||
for (int i=0; i<=20; i=i+5) {
|
||||
MoveTo(x0+x1+20*xinc+i*xinc/2, y0+y1+i*xinc/2);
|
||||
DrawTo(x0+x1+20*xinc+i*xinc/2+3, y0+y1+i*xinc/2);
|
||||
MoveTo(x0+x1+20*xinc+i*xinc/2+6, y0+y1+i*xinc/2-4); PlotInt(i);
|
||||
}
|
||||
// Plot data
|
||||
for (int y=20; y>=0; y--) {
|
||||
for (int i=0; i<=20; i++) {
|
||||
int fn0 = 180-(i-10)*(i-10)-(y-10)*(y-10);
|
||||
int fn1 = 180-(i+1-10)*(i+1-10)-(y-10)*(y-10);
|
||||
fore = Colour(255, 255, 0);
|
||||
MoveTo(x0+x1+y*yinc+i*xinc, y0+y1+y*yinc+fn0*fn0/factor);
|
||||
DrawTo(x0+x1+y*yinc+(i+1)*xinc, y0+y1+y*yinc+fn1*fn1/factor);
|
||||
fore = White;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Setup **********************************************
|
||||
|
||||
void setup() {
|
||||
InitDisplay();
|
||||
ClearDisplay();
|
||||
DisplayOn();
|
||||
MoveTo(0,0);
|
||||
// TestChart();
|
||||
}
|
||||
|
||||
void loop () {
|
||||
BarChart();
|
||||
// Waterfall();
|
||||
for (;;);
|
||||
}
|
@@ -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 '-':
|
||||
|
134
TrackManager.cpp
134
TrackManager.cpp
@@ -25,6 +25,7 @@
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#include"CommandDistributor.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||
|
||||
@@ -32,8 +33,9 @@
|
||||
FOR_EACH_TRACK(t) \
|
||||
if (trackMode[t]==findmode) \
|
||||
track[t]->function;
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
const int16_t HASH_KEYWORD_PROG = -29718;
|
||||
#endif
|
||||
const int16_t HASH_KEYWORD_MAIN = 11339;
|
||||
const int16_t HASH_KEYWORD_OFF = 22479;
|
||||
const int16_t HASH_KEYWORD_DC = 2183;
|
||||
@@ -115,12 +117,24 @@ 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) {
|
||||
@@ -128,6 +142,7 @@ void TrackManager::addTrack(byte t, MotorDriver* driver) {
|
||||
track[t]=driver;
|
||||
if (driver) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
track[t]->setTrackLetter('A'+t);
|
||||
lastTrack=t;
|
||||
}
|
||||
}
|
||||
@@ -196,13 +211,18 @@ 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) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
trackMode[t]=TRACK_MODE_OFF;
|
||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||
streamTrackState(NULL,t);
|
||||
}
|
||||
track[trackToSet]->makeProgTrack(true); // set for prog track special handling
|
||||
} else {
|
||||
@@ -210,7 +230,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
}
|
||||
trackMode[trackToSet]=mode;
|
||||
trackDCAddr[trackToSet]=dcAddr;
|
||||
|
||||
streamTrackState(NULL,trackToSet);
|
||||
|
||||
// When a track is switched, we must clear any side effects of its previous
|
||||
// state, otherwise trains run away or just dont move.
|
||||
|
||||
@@ -290,36 +311,7 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
|
||||
if (params==0) { // <=> List track assignments
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]!=NULL) {
|
||||
StringFormatter::send(stream,F("<= %c "),'A'+t);
|
||||
switch(trackMode[t]) {
|
||||
case TRACK_MODE_MAIN:
|
||||
StringFormatter::send(stream,F("MAIN"));
|
||||
if (track[t]->trackPWM)
|
||||
StringFormatter::send(stream,F("+"));
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
StringFormatter::send(stream,F("PROG"));
|
||||
if (track[t]->trackPWM)
|
||||
StringFormatter::send(stream,F("+"));
|
||||
break;
|
||||
case TRACK_MODE_OFF:
|
||||
StringFormatter::send(stream,F("OFF"));
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
StringFormatter::send(stream,F("EXT"));
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
StringFormatter::send(stream,F("DC %d"),trackDCAddr[t]);
|
||||
break;
|
||||
case TRACK_MODE_DCX:
|
||||
StringFormatter::send(stream,F("DCX %d"),trackDCAddr[t]);
|
||||
break;
|
||||
default:
|
||||
break; // unknown, dont care
|
||||
}
|
||||
StringFormatter::send(stream,F(">\n"));
|
||||
}
|
||||
streamTrackState(stream,t);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -331,8 +323,10 @@ 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);
|
||||
@@ -349,11 +343,45 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
return false;
|
||||
}
|
||||
|
||||
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]) {
|
||||
case TRACK_MODE_MAIN:
|
||||
format=F("<= %c MAIN>\n");
|
||||
break;
|
||||
#ifndef DISABLE_PROG
|
||||
case TRACK_MODE_PROG:
|
||||
format=F("<= %c PROG>\n");
|
||||
break;
|
||||
#endif
|
||||
case TRACK_MODE_OFF:
|
||||
format=F("<= %c OFF>\n");
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
format=F("<= %c EXT>\n");
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
format=F("<= %c DC %d>\n");
|
||||
break;
|
||||
case TRACK_MODE_DCX:
|
||||
format=F("<= %c DCX %d>\n");
|
||||
break;
|
||||
default:
|
||||
break; // unknown, dont care
|
||||
}
|
||||
if (stream) StringFormatter::send(stream,format,'A'+t,trackDCAddr[t]);
|
||||
else CommandDistributor::broadcastTrackState(format,'A'+t,trackDCAddr[t]);
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -423,7 +451,35 @@ POWERMODE TrackManager::getProgPower() {
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
|
||||
void TrackManager::reportObsoleteCurrent(Print* stream) {
|
||||
// This function is for backward JMRI compatibility only
|
||||
// It reports the first track only, as main, regardless of track settings.
|
||||
// <c MeterName value C/V unit min max res warn>
|
||||
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
|
||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
|
||||
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
||||
}
|
||||
|
||||
void TrackManager::reportCurrent(Print* stream) {
|
||||
StringFormatter::send(stream,F("<jI"));
|
||||
FOR_EACH_TRACK(t) {
|
||||
StringFormatter::send(stream, F(" %d"),
|
||||
(track[t]->getPower()==POWERMODE::OVERLOAD) ? -1 :
|
||||
track[t]->raw2mA(track[t]->getCurrentRaw(false)));
|
||||
}
|
||||
StringFormatter::send(stream,F(">\n"));
|
||||
}
|
||||
|
||||
void TrackManager::reportGauges(Print* stream) {
|
||||
StringFormatter::send(stream,F("<jG"));
|
||||
FOR_EACH_TRACK(t) {
|
||||
StringFormatter::send(stream, F(" %d"),
|
||||
track[t]->raw2mA(track[t]->getRawCurrentTripValue()));
|
||||
}
|
||||
StringFormatter::send(stream,F(">\n"));
|
||||
}
|
||||
|
||||
void TrackManager::setJoinRelayPin(byte joinRelayPin) {
|
||||
joinRelay=joinRelayPin;
|
||||
if (joinRelay!=UNUSED_PIN) {
|
||||
|
@@ -77,10 +77,22 @@ class TrackManager {
|
||||
static bool isJoined() { return progTrackSyncMain;}
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void sampleCurrent();
|
||||
static void reportGauges(Print* stream);
|
||||
static void reportCurrent(Print* stream);
|
||||
static void reportObsoleteCurrent(Print* stream);
|
||||
static void streamTrackState(Print* stream, 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;
|
||||
@@ -88,7 +100,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
|
||||
|
46
Turnouts.cpp
46
Turnouts.cpp
@@ -110,49 +110,40 @@
|
||||
/* static */ bool Turnout::setClosedStateOnly(uint16_t id, bool closeFlag) {
|
||||
Turnout *tt = get(id);
|
||||
if (!tt) return false;
|
||||
tt->_turnoutData.closed = closeFlag;
|
||||
|
||||
// I know it says setClosedStateOnly, but we need to tell others
|
||||
// that the state has changed too.
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
RMFT2::turnoutEvent(id, closeFlag);
|
||||
#endif
|
||||
|
||||
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||
// that the state has changed too. But we only broadcast if there
|
||||
// really has been a change.
|
||||
if (tt->_turnoutData.closed != closeFlag) {
|
||||
tt->_turnoutData.closed = closeFlag;
|
||||
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||
}
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
RMFT2::turnoutEvent(id, closeFlag);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
#define DIAG_IO
|
||||
// Static setClosed function is invoked from close(), throw() etc. to perform the
|
||||
// common parts of the turnout operation. Code which is specific to a turnout
|
||||
// type should be placed in the virtual function setClosedInternal(bool) which is
|
||||
// called from here.
|
||||
/* static */ bool Turnout::setClosed(uint16_t id, bool closeFlag) {
|
||||
#if defined(DIAG_IO)
|
||||
if (closeFlag)
|
||||
DIAG(F("Turnout::close(%d)"), id);
|
||||
else
|
||||
DIAG(F("Turnout::throw(%d)"), id);
|
||||
#endif
|
||||
#if defined(DIAG_IO)
|
||||
DIAG(F("Turnout(%d,%c)"), id, closeFlag ? 'c':'t');
|
||||
#endif
|
||||
Turnout *tt = Turnout::get(id);
|
||||
if (!tt) return false;
|
||||
bool ok = tt->setClosedInternal(closeFlag);
|
||||
|
||||
if (ok) {
|
||||
|
||||
tt->setClosedStateOnly(id, closeFlag);
|
||||
#ifndef DISABLE_EEPROM
|
||||
// Write byte containing new closed/thrown state to EEPROM if required. Note that eepromAddress
|
||||
// is always zero for LCN turnouts.
|
||||
if (EEStore::eeStore->data.nTurnouts > 0 && tt->_eepromAddress > 0)
|
||||
EEPROM.put(tt->_eepromAddress, tt->_turnoutData.flags);
|
||||
#endif
|
||||
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
RMFT2::turnoutEvent(id, closeFlag);
|
||||
#endif
|
||||
|
||||
// Send message to JMRI etc.
|
||||
CommandDistributor::broadcastTurnout(id, closeFlag);
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
@@ -259,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
|
||||
@@ -298,7 +290,6 @@
|
||||
#ifndef IO_NO_HAL
|
||||
IODevice::writeAnalogue(_servoTurnoutData.vpin,
|
||||
close ? _servoTurnoutData.closedPosition : _servoTurnoutData.thrownPosition, _servoTurnoutData.profile);
|
||||
_turnoutData.closed = close;
|
||||
#else
|
||||
(void)close; // avoid compiler warnings
|
||||
#endif
|
||||
@@ -396,7 +387,6 @@
|
||||
// and Close writes a 0.
|
||||
// RCN-213 specifies that Throw is 0 and Close is 1.
|
||||
DCC::setAccessory(_dccTurnoutData.address, _dccTurnoutData.subAddress, close ^ !rcn213Compliant);
|
||||
_turnoutData.closed = close;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -472,7 +462,6 @@
|
||||
|
||||
bool VpinTurnout::setClosedInternal(bool close) {
|
||||
IODevice::write(_vpinTurnoutData.vpin, close);
|
||||
_turnoutData.closed = close;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -523,7 +512,10 @@
|
||||
bool LCNTurnout::setClosedInternal(bool close) {
|
||||
// Assume that the LCN command still uses 1 for throw and 0 for close...
|
||||
LCN::send('T', _turnoutData.id, !close);
|
||||
// The _turnoutData.closed flag should be updated by a message from the LCN master, later.
|
||||
// The _turnoutData.closed flag should be updated by a message from the LCN master.
|
||||
// but in this implementation it is updated in setClosedStateOnly() instead.
|
||||
// If the LCN master updates this, setClosedStateOnly() and all setClosedInternal()
|
||||
// have to be updated accordingly so that the closed flag is only set once.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -20,6 +20,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,6 +106,12 @@ 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,
|
||||
@@ -176,12 +183,14 @@ bool WifiESP::setup(const char *SSid,
|
||||
}
|
||||
if (!haveSSID) {
|
||||
// prepare all strings
|
||||
String strSSID("DCC_");
|
||||
String strSSID("DCCEX_");
|
||||
String strPass("PASS_");
|
||||
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);
|
||||
|
||||
@@ -209,6 +218,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
|
||||
|
@@ -52,10 +52,30 @@ 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_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_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#define NUM_SERIAL 2
|
||||
#define SERIAL1 Serial6
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef NUM_SERIAL
|
||||
#define NUM_SERIAL 1
|
||||
#define SERIAL1 Serial1
|
||||
#endif
|
||||
|
||||
bool WifiInterface::setup(long serial_link_speed,
|
||||
@@ -76,13 +96,16 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
(void) port;
|
||||
(void) channel;
|
||||
#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);
|
||||
#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)
|
||||
{
|
||||
@@ -90,12 +113,15 @@ bool WifiInterface::setup(long serial_link_speed,
|
||||
wifiUp = setup(Serial2, wifiESSID, wifiPassword, hostname, port, channel);
|
||||
}
|
||||
#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);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -125,17 +151,18 @@ wifiSerialState WifiInterface::setup(Stream & setupStream, const FSH* SSid, con
|
||||
wifiState = setup2( SSid, password, hostname, port, channel);
|
||||
|
||||
if (wifiState == WIFI_NOAT) {
|
||||
DIAG(F("++ Wifi Setup NO AT ++"));
|
||||
return wifiState;
|
||||
LCD(4, F("WiFi no AT chip"));
|
||||
return wifiState;
|
||||
}
|
||||
|
||||
if (wifiState == WIFI_CONNECTED) {
|
||||
StringFormatter::send(wifiStream, F("ATE0\r\n")); // turn off the echo
|
||||
checkForOK(200, true);
|
||||
checkForOK(200, true);
|
||||
DIAG(F("WiFi CONNECTED"));
|
||||
// LCD already shows IP
|
||||
} else {
|
||||
LCD(4,F("WiFi DISCON."));
|
||||
}
|
||||
|
||||
|
||||
DIAG(F("++ Wifi Setup %S ++"), wifiState == WIFI_CONNECTED ? F("CONNECTED") : F("DISCONNECTED"));
|
||||
return wifiState;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2021 Harald Barth
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
*
|
||||
@@ -27,6 +27,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,15 +44,15 @@ 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
|
||||
//
|
||||
@@ -125,10 +135,10 @@ The configuration file for DCC-EX Command Station
|
||||
// define LCD_DRIVER for I2C address 0x27, 16 cols, 2 rows
|
||||
// #define LCD_DRIVER 0x27,16,2
|
||||
|
||||
//OR define OLED_DRIVER width,height in pixels (address auto detected)
|
||||
//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
|
||||
// #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 +151,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 +159,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,8 +245,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
|
||||
|
||||
// FastClock Enabler
|
||||
// To build the FastClock code into the CS please uncomment the line below
|
||||
//#define USEFASTCLOCK
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@@ -148,7 +148,6 @@
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
|
||||
|
||||
/* TODO when ready
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
#define ARDUINO_TYPE "RP2040"
|
||||
|
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)
|
@@ -69,10 +69,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
|
||||
|
@@ -23,7 +23,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// This is the startup sequence, AKA SEQUENCE(0)
|
||||
// This is the startup sequence,
|
||||
AUTOSTART
|
||||
POWERON // turn on track power
|
||||
SENDLOCO(3,1) // send loco 3 off along route 1
|
||||
SENDLOCO(10,2) // send loco 10 off along route 2
|
||||
DONE // This just ends the startup thread, leaving 2 others running.
|
||||
|
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
|
@@ -17,9 +17,11 @@
|
||||
|
||||
// Include devices you need.
|
||||
#include "IODevice.h"
|
||||
#include "IO_HCSR04.h" // Ultrasonic range sensor
|
||||
#include "IO_VL53L0X.h" // Laser time-of-flight sensor
|
||||
#include "IO_DFPlayer.h" // MP3 sound player
|
||||
//#include "IO_HALDisplay.h" // Auxiliary display devices (LCD/OLED)
|
||||
//#include "IO_HCSR04.h" // Ultrasonic range sensor
|
||||
//#include "IO_VL53L0X.h" // Laser time-of-flight sensor
|
||||
//#include "IO_DFPlayer.h" // MP3 sound player
|
||||
//#include "IO_TouchKeypad.h // Touch keypad with 16 keys
|
||||
//#include "IO_EXTurntable.h" // Turntable-EX turntable controller
|
||||
//#include "IO_EXFastClock.h" // FastClock driver
|
||||
|
||||
@@ -31,6 +33,61 @@
|
||||
|
||||
void halSetup() {
|
||||
|
||||
//=======================================================================
|
||||
// The following directives define auxiliary display devices.
|
||||
// These can be defined in addition to the system display (display
|
||||
// number 0) that is defined in config.h.
|
||||
// A write to a line which is beyond the length of the screen will overwrite
|
||||
// the bottom line, unless the line number is 255 in which case the
|
||||
// screen contents will scroll up before the text is written to the
|
||||
// bottom line.
|
||||
//=======================================================================
|
||||
//
|
||||
// Create a 128x32 OLED display device as display number 1
|
||||
// (line 0 is written by EX-RAIL 'SCREEN(1, 0, "text")').
|
||||
|
||||
//HALDisplay<OLED>::create(1, 0x3d, 128, 32);
|
||||
|
||||
// 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);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// User Add-ins
|
||||
//=======================================================================
|
||||
// User add-ins can be created when you want to do something that
|
||||
// can't be done in EX-RAIL but does not merit a HAL driver. The
|
||||
// user add-in is a C++ function that is executed periodically by the
|
||||
// HAL subsystem.
|
||||
|
||||
// Example: The function will be executed once per second and will display,
|
||||
// on screen #3, the first eight entries (assuming an 8-line display)
|
||||
// from the loco speed table.
|
||||
|
||||
// Put the following block of code in myHal.cpp OUTSIDE of the
|
||||
// halSetup() function:
|
||||
//
|
||||
// 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);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Put the following line INSIDE the halSetup() function:
|
||||
//
|
||||
// UserAddin::create(updateLocoScreen, 1000);
|
||||
//
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines a PCA9685 PWM Servo driver module.
|
||||
//=======================================================================
|
||||
@@ -156,12 +213,12 @@ void halSetup() {
|
||||
// With these parameters, up to 10 files may be played on pins 10000-10009.
|
||||
// Play is started from EX-RAIL with SET(10000) for first mp3 file, SET(10001)
|
||||
// for second file, etc. Play may also be initiated by writing an analogue
|
||||
// value to the first pin, e.g. SERVO(10000,23,0) will play the 23rd mp3 file.
|
||||
// SERVO(10000,23,30) will do the same thing, as well as setting the volume to
|
||||
// value to the first pin, e.g. ANOUT(10000,23,0,0) will play the 23rd mp3 file.
|
||||
// ANOUT(10000,23,30,0) will do the same thing, as well as setting the volume to
|
||||
// 30 (maximum value).
|
||||
// Play is stopped by RESET(10000) (or any other allocated VPIN).
|
||||
// Volume may also be set by writing an analogue value to the second pin for the player,
|
||||
// e.g. SERVO(10001,30,0) sets volume to maximum (30).
|
||||
// e.g. ANOUT(10001,30,0,0) sets volume to maximum (30).
|
||||
// The EX-RAIL script may check for completion of play by calling WAITFOR(pin), which will only proceed to the
|
||||
// following line when the player is no longer busy.
|
||||
// E.g.
|
||||
@@ -170,12 +227,27 @@ void halSetup() {
|
||||
// SET(10003) // Play fourth MP3 file
|
||||
// LCD(4, "Playing") // Display message on LCD/OLED
|
||||
// WAITFOR(10003) // Wait for playing to finish
|
||||
// LCD(4, " ") // Clear LCD/OLED line
|
||||
// LCD(4, "") // Clear LCD/OLED line
|
||||
// FOLLOW(1) // Go back to start
|
||||
|
||||
// DFPlayer::create(10000, 10, Serial1);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// 16-pad capacitative touch key pad based on TP229 IC.
|
||||
//=======================================================================
|
||||
// Parameters below:
|
||||
// 11000 = first VPIN allocated
|
||||
// 16 = number of VPINs allocated
|
||||
// 25 = local GPIO pin number for clock signal
|
||||
// 24 = local GPIO pin number for data signal
|
||||
//
|
||||
// Pressing the key pads numbered 1-16 cause each of the nominated digital VPINs
|
||||
// (11000-11015 in this case) to be activated.
|
||||
|
||||
// TouchKeypad::create(11000, 16, 25, 24);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-Turntable turntable instance.
|
||||
//=======================================================================
|
||||
|
@@ -20,11 +20,11 @@ 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 = .
|
||||
|
||||
@@ -53,7 +53,7 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:Arduino M0]
|
||||
[env:Arduino-M0]
|
||||
platform = atmelsam
|
||||
board = mzeroUSB
|
||||
framework = arduino
|
||||
@@ -173,6 +173,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 +190,11 @@ 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 ; -DDIAG_LOOPTIMES ; -DDIAG_IO
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Teensy3.2]
|
||||
[env:Teensy3_2]
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
framework = arduino
|
||||
@@ -200,7 +202,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 +210,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 +218,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,7 +226,7 @@ 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
|
||||
|
63
version.h
63
version.h
@@ -4,7 +4,64 @@
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.18"
|
||||
#define VERSION "4.2.60"
|
||||
// 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.
|
||||
// 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.
|
||||
// - Update IO_DFPlayer to work with MP3-TF-16P rev3.
|
||||
// 4.2.27 - Bugfix LCD showed random characters in SCROLLMODE 2
|
||||
// 4.2.26 - EX-IOExpander device driver enhancements
|
||||
// - Enhance I2C error checking
|
||||
// - Introduce delays to _loop to allow room for other I2C device comms
|
||||
// - Improve analogue read reliability
|
||||
// 4.2.25 - Bugfix SAMD21 Exrail odd byte boundary
|
||||
// 4.2.24 - Bugfix Ethernet shield: Static IP now possible
|
||||
// 4.2.23 - Bugfix signalpin2 was not set up in shadow port
|
||||
// 4.2.22 - Implement broadcast of Track Manager changes
|
||||
// 4.2.21 - Implement non-blocking I2C for EX-IOExpander device driver
|
||||
// 4.2.20 - <JG> & <JI> commands for multi-track gauges
|
||||
// - Reinstate <c> but remember its a bit useless when TM involved.
|
||||
// 4.2.19 - Bugfix for analog reading of track current sensor offset.
|
||||
// 4.2.18 - I2C Multiplexer support through Extended Addresses,
|
||||
// added for Wire, 4209 and AVR I2C drivers.
|
||||
// - I2C retries when an operation fails.
|
||||
@@ -40,6 +97,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