mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 18:33:44 +02:00
Compare commits
296 Commits
devel-adct
...
v4.2.24-De
Author | SHA1 | Date | |
---|---|---|---|
|
881463ada9 | ||
|
45cf610028 | ||
|
471b8ac8e1 | ||
|
3ae1859ec7 | ||
|
679e5885c4 | ||
|
59c6c1e5af | ||
|
f94a5f971e | ||
|
1d29436008 | ||
|
bec8aea5a5 | ||
|
4f23dbc984 | ||
|
46070e2999 | ||
|
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 | ||
|
b7483d99e9 | ||
|
ce3885b125 | ||
|
c52b4a60a5 | ||
|
ef85d5eaba | ||
|
f281938606 | ||
|
a405d36523 | ||
|
8e8ae90030 | ||
|
c3675367ed | ||
|
4deb323802 | ||
|
4eb277f19e | ||
|
c2e8557c4c | ||
|
8e90bb6996 | ||
|
034bb6b675 | ||
|
43b1e8db21 | ||
|
a36dccfad0 | ||
|
33229b4847 | ||
|
173676287c | ||
|
9797a0fd2d | ||
|
10cd580061 | ||
|
d6c8595f8a | ||
|
6b863ea483 | ||
|
8ed3bbd845 | ||
|
d0445f157c | ||
|
0118aa037d | ||
|
21c82b37b0 | ||
|
67bd886a98 | ||
|
3292c93192 | ||
|
2ada89f918 | ||
|
f1f1be8ad9 | ||
|
0b0aba7aef | ||
|
9c95eb6905 | ||
|
ec73ac69d9 | ||
|
47cda83210 | ||
|
35f3cca9b3 | ||
|
65f7a4917f | ||
|
8be9d9e0b0 | ||
|
a9971968c0 | ||
|
e498915b28 | ||
|
c315895cd9 | ||
|
1cfe5a1e46 | ||
|
ad4cedfccf | ||
|
98697427a3 | ||
|
f5b5809ba5 | ||
|
0b307a67e4 | ||
|
553a94bf67 | ||
|
18b148ed1f | ||
|
1ffb3a9836 | ||
|
f358880f30 | ||
|
5f9705d1b7 | ||
|
7e2487ffbb | ||
|
fd07402aec | ||
|
d8d785877e | ||
|
3b82a94d83 | ||
|
acadf241e6 | ||
|
8cc5f7ddf4 | ||
|
f1c17c3606 | ||
|
d36ac7dcfd | ||
|
6b67760db1 | ||
|
6874ddca9b | ||
|
06827a42b7 | ||
|
f59fe6e83b | ||
|
c768bdc361 | ||
|
ad97260055 | ||
|
938b4cfbd6 | ||
|
2a3d48dc00 | ||
|
5efb0c5013 | ||
|
e53ed7b46d | ||
|
4d31cd64a5 | ||
|
6031a0fb7f | ||
|
d375723a13 | ||
|
fa38583772 | ||
|
984ef6fead | ||
|
cf2817d7c4 | ||
|
0c2f8428df | ||
|
53215b496e | ||
|
d41b5e0938 | ||
|
d8cbdb24e1 | ||
|
93ac1b6d61 | ||
|
8083bd1b3b | ||
|
9e0e110b5d | ||
|
7de46a0c17 | ||
|
9dd9990979 | ||
|
dd0ee8b50a | ||
|
ad4a0a9592 | ||
|
deb49f2943 | ||
|
5cb216dd79 | ||
|
afc94a75bb | ||
|
2848ba616b | ||
|
3d480ee9ef | ||
|
d7f92d7b88 | ||
|
3fbcd6f300 | ||
|
a0f0b860eb | ||
|
efb2666060 | ||
|
73a7d3e0ca | ||
|
47cb43d1e9 | ||
|
e2e9b7bae7 | ||
|
57292c2250 | ||
|
31ce2e3fef | ||
|
d8881deb6a | ||
|
5439dd3158 | ||
|
aad0d28d1f | ||
|
19070d33ba | ||
|
7b79680de2 | ||
|
0c88c74706 | ||
|
cb287f23a4 | ||
|
261ccf2f3b | ||
|
278a9c52a6 | ||
|
9435869ee3 | ||
|
d5a394d4e6 | ||
|
c870940dde | ||
|
754639c7e3 | ||
|
a478ad7112 | ||
|
13bd6ef9eb | ||
|
45657881eb | ||
|
a590245e93 | ||
|
6f5680fce0 | ||
|
2f46a8e083 | ||
|
bd62939713 | ||
|
abe79b854e | ||
|
27ddc7b30b | ||
|
81559998ec | ||
|
847ced2f49 | ||
|
49713badb2 | ||
|
be88344407 | ||
|
ec83a345dc | ||
|
4e32c707b9 | ||
|
73e1dfc192 | ||
|
1ae74d9487 | ||
|
a7366b42c1 | ||
|
84431d1841 | ||
|
e12c5292fa | ||
|
e76197faa9 | ||
|
bdc8aec9a6 | ||
|
052256e2ed | ||
|
ba9b363058 | ||
|
bdffd36820 | ||
|
4d350040ba | ||
|
1073e142e6 | ||
|
a18c06d021 | ||
|
291a331f3e | ||
|
77b20e6a16 | ||
|
1d27eb67e4 | ||
|
7f19a92d2a | ||
|
28caa9e8d3 | ||
|
95945eab4c | ||
|
638682f05c | ||
|
ffb08523da | ||
|
d8a1bcaf34 | ||
|
212bf8d80e | ||
|
a17c02444d | ||
|
290d878063 | ||
|
2a7588b1b5 | ||
|
be33bafa66 | ||
|
6cc66e26c1 | ||
|
c91d66549c | ||
|
9e5d780c14 | ||
|
2c0886bc2f | ||
|
762742b4af | ||
|
88b572a148 | ||
|
fcf16c1367 | ||
|
c69b8d85c8 | ||
|
006c85e6ae | ||
|
d0ce59b19f | ||
|
a3d4255fee | ||
|
e8e00f69d6 | ||
|
10c8915d33 | ||
|
4f233de726 | ||
|
682c47f7dd | ||
|
4acf46db54 | ||
|
20b3e9064c | ||
|
459904e5dd | ||
|
878549d538 | ||
|
7f4e3d9cea | ||
|
f2aeb4069f | ||
|
aaf25d5426 | ||
|
705617239f | ||
|
bfbc45674f | ||
|
e079a9e395 | ||
|
fb9170ab8b | ||
|
286bdc3c4d | ||
|
cd46d3c9e0 | ||
|
fb36bd1380 | ||
|
b62c4da04d | ||
|
ccf463b507 | ||
|
abf62dfd85 | ||
|
8fac20a451 | ||
|
7c25f22939 | ||
|
0c054c4d42 | ||
|
538519dd9d | ||
|
6e69df2da8 | ||
|
3c5b7bbcfe | ||
|
79437bbf37 | ||
|
1be382a6ed | ||
|
1f433d0c17 | ||
|
046e62a8b3 | ||
|
a2c7c7d12a | ||
|
9b36bdcf46 | ||
|
a8646a2f32 | ||
|
22e20f9092 | ||
|
873d470f86 | ||
|
ff7260b9bc | ||
|
de4954ca3e | ||
|
c26f53e1fa | ||
|
e48a40fafb | ||
|
5c120efa16 | ||
|
9abcfb9e4f | ||
|
e01893bcf1 | ||
|
6eff836473 | ||
|
402e16727c | ||
|
658fca2601 | ||
|
3fccf6a484 | ||
|
ace9c1642a | ||
|
ec4dfb8c1e | ||
|
6482a421b4 | ||
|
d02c6b1f61 | ||
|
94c8dafeb2 | ||
|
322cb3db54 | ||
|
ffdf023de6 | ||
|
8f32ae712f | ||
|
eea1396997 | ||
|
b1bd28273d | ||
|
0be25f6e7f | ||
|
71ce913712 | ||
|
70845b4932 | ||
|
c44fb0ac44 | ||
|
1c7103c21e | ||
|
5170147e3e | ||
|
2ad08029a4 | ||
|
25b3250345 | ||
|
3973996344 | ||
|
943494385f | ||
|
c8fea3a4a7 | ||
|
1d61a8f3f9 | ||
|
a480a5a3d2 | ||
|
070daa37dc | ||
|
75f1a8f43a | ||
|
ad294ea17e | ||
|
8ecb408da7 | ||
|
3862f7250d | ||
|
785b515f9e | ||
|
9699a44081 | ||
|
cb9a8bb7a6 | ||
|
1d5897d2d2 | ||
|
7bc0433197 | ||
|
9104956009 | ||
|
af4d8d4075 | ||
|
06945bb114 | ||
|
2d27cb052d | ||
|
8cbcf5df32 | ||
|
13368c319a | ||
|
cb1fc75077 | ||
|
b671d70dfe | ||
|
45b36c48cb | ||
|
d95096ded8 | ||
|
a6ae1a48a2 | ||
|
913f0a0c86 | ||
|
984fd2fa08 | ||
|
d062de2eb8 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -19,3 +19,4 @@ my*.h
|
||||
!my*.example.h
|
||||
.vscode/extensions.json
|
||||
.vscode/extensions.json
|
||||
compile_commands.json
|
||||
|
@@ -29,6 +29,11 @@
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCC.h"
|
||||
#include "TrackManager.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
// variables to hold clock time
|
||||
int16_t lastclocktime;
|
||||
int8_t lastclockrate;
|
||||
|
||||
|
||||
#if WIFI_ON || ETHERNET_ON || defined(SERIAL1_COMMANDS) || defined(SERIAL2_COMMANDS) || defined(SERIAL3_COMMANDS)
|
||||
@@ -43,7 +48,7 @@ template<typename... Targs> void CommandDistributor::broadcastReply(clientType t
|
||||
// on a single USB connection config, write direct to Serial and ignore flush/shove
|
||||
template<typename... Targs> void CommandDistributor::broadcastReply(clientType type, Targs... msg){
|
||||
(void)type; //shut up compiler warning
|
||||
StringFormatter::send(&Serial, msg...);
|
||||
StringFormatter::send(&USB_SERIAL, msg...);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -97,7 +102,7 @@ void CommandDistributor::parse(byte clientId,byte * buffer, RingStream * stream
|
||||
}
|
||||
|
||||
void CommandDistributor::forget(byte clientId) {
|
||||
// keep for later if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
|
||||
if (clients[clientId]==WITHROTTLE_TYPE) WiThrottle::forget(clientId);
|
||||
clients[clientId]=NONE_TYPE;
|
||||
}
|
||||
#endif
|
||||
@@ -155,6 +160,47 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
||||
// The JMRI clock command is of the form : PFT65871<;>4
|
||||
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
|
||||
// The string below contains serial and Withrottle protocols which should
|
||||
// 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);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::setClockTime(int16_t clocktime, int8_t clockrate, byte opt) {
|
||||
// opt - case 1 save the latest time if changed
|
||||
// case 2 broadcast the time when requested
|
||||
// case 3 display latest time
|
||||
switch (opt)
|
||||
{
|
||||
case 1:
|
||||
if (clocktime != lastclocktime){
|
||||
// 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);
|
||||
// Now tell everyone else what the time is.
|
||||
CommandDistributor::broadcastClockTime(clocktime, clockrate);
|
||||
lastclocktime = clocktime;
|
||||
lastclockrate = clockrate;
|
||||
}
|
||||
return;
|
||||
|
||||
case 2:
|
||||
CommandDistributor::broadcastClockTime(lastclocktime, lastclockrate);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int16_t CommandDistributor::retClockTime() {
|
||||
return lastclocktime;
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -187,3 +233,7 @@ void CommandDistributor::broadcastText(const FSH * msg) {
|
||||
broadcastReply(WITHROTTLE_TYPE, F("Hm%S\n"), msg);
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr);
|
||||
}
|
||||
|
@@ -25,6 +25,7 @@
|
||||
#include "RingStream.h"
|
||||
#include "StringBuffer.h"
|
||||
#include "defines.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
#if WIFI_ON | ETHERNET_ON
|
||||
// Command Distributor must handle a RingStream of clients
|
||||
@@ -45,10 +46,15 @@ public :
|
||||
static void broadcastLoco(byte slot);
|
||||
static void broadcastSensor(int16_t id, bool value);
|
||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||
static void broadcastClockTime(int16_t time, int8_t rate);
|
||||
static void setClockTime(int16_t time, int8_t rate, byte opt);
|
||||
static int16_t retClockTime();
|
||||
static void broadcastPower();
|
||||
static void broadcastText(const FSH * msg);
|
||||
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
|
||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||
static void forget(byte clientId);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -49,6 +49,7 @@
|
||||
*/
|
||||
|
||||
#include "DCCEX.h"
|
||||
#include "Display_Implementation.h"
|
||||
|
||||
#ifdef CPU_TYPE_ERROR
|
||||
#error CANNOT COMPILE - DCC++ EX ONLY WORKS WITH THE ARCHITECTURES LISTED IN defines.h
|
||||
@@ -74,11 +75,11 @@ void setup()
|
||||
|
||||
DIAG(F("License GPLv3 fsf.org (c) dcc-ex.com"));
|
||||
|
||||
CONDITIONAL_LCD_START {
|
||||
// This block is still executed for DIAGS if LCD not in use
|
||||
LCD(0,F("DCC++ EX v%S"),F(VERSION));
|
||||
DISPLAY_START (
|
||||
// This block is still executed for DIAGS if display not in use
|
||||
LCD(0,F("DCC-EX v%S"),F(VERSION));
|
||||
LCD(1,F("Lic GPLv3"));
|
||||
}
|
||||
);
|
||||
|
||||
// Responsibility 2: Start all the communications before the DCC engine
|
||||
// Start the WiFi interface on a MEGA, Uno cannot currently handle WiFi
|
||||
@@ -99,6 +100,9 @@ void setup()
|
||||
// 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
|
||||
@@ -157,7 +161,8 @@ void loop()
|
||||
LCN::loop();
|
||||
#endif
|
||||
|
||||
LCDDisplay::loop(); // ignored if LCD not in use
|
||||
// Display refresh
|
||||
DisplayInterface::loop();
|
||||
|
||||
// Handle/update IO devices.
|
||||
IODevice::loop();
|
||||
|
8
DCC.cpp
8
DCC.cpp
@@ -576,9 +576,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
|
||||
|
2
DCCEX.h
2
DCCEX.h
@@ -40,7 +40,7 @@
|
||||
#if ETHERNET_ON == true
|
||||
#include "EthernetInterface.h"
|
||||
#endif
|
||||
#include "LCD_Implementation.h"
|
||||
#include "Display_Implementation.h"
|
||||
#include "LCN.h"
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
|
@@ -41,6 +41,14 @@
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
// This macro can't be created easily as a portable function because the
|
||||
// flashlist requires a far pointer for high flash access.
|
||||
#define SENDFLASHLIST(stream,flashList) \
|
||||
for (int16_t i=0;;i+=sizeof(flashList[0])) { \
|
||||
int16_t value=GETHIGHFLASHW(flashList,i); \
|
||||
if (value==0) break; \
|
||||
StringFormatter::send(stream,F(" %d"),value); \
|
||||
}
|
||||
|
||||
|
||||
// These keywords are used in the <1> command. The number is what you get if you use the keyword as a parameter.
|
||||
@@ -71,6 +79,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';
|
||||
@@ -182,9 +192,9 @@ void DCCEXParser::setAtCommandCallback(AT_COMMAND_CALLBACK callback)
|
||||
// Parse an F() string
|
||||
void DCCEXParser::parse(const FSH * cmd) {
|
||||
DIAG(F("SETUP(\"%S\")"),cmd);
|
||||
int size=strlen_P((char *)cmd)+1;
|
||||
int size=STRLEN_P((char *)cmd)+1;
|
||||
char buffer[size];
|
||||
strcpy_P(buffer,(char *)cmd);
|
||||
STRCPY_P(buffer,(char *)cmd);
|
||||
parse(&USB_SERIAL,(byte *)buffer,NULL);
|
||||
}
|
||||
|
||||
@@ -493,8 +503,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);
|
||||
@@ -502,6 +514,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
Output::printAll(stream); //send all Output states
|
||||
Sensor::printAll(stream); //send all Sensor states
|
||||
@@ -562,15 +575,35 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case 'J' : // throttle info access
|
||||
{
|
||||
if ((params<1) | (params>2)) break; // <J>
|
||||
if ((params<1) | (params>3)) break; // <J>
|
||||
//if ((params<1) | (params>2)) break; // <J>
|
||||
int16_t id=(params==2)?p[1]:0;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_C: // <JC mmmm nn> sets time and speed
|
||||
if (params==1) { // <JC> returns latest time
|
||||
int16_t x = CommandDistributor::retClockTime();
|
||||
StringFormatter::send(stream, F("<jC %d>\n"), x);
|
||||
return;
|
||||
}
|
||||
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"));
|
||||
if (params==1) {// <JA>
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
sendFlashList(stream,RMFT2::routeIdList);
|
||||
sendFlashList(stream,RMFT2::automationIdList);
|
||||
SENDFLASHLIST(stream,RMFT2::routeIdList)
|
||||
SENDFLASHLIST(stream,RMFT2::automationIdList)
|
||||
#endif
|
||||
}
|
||||
else { // <JA id>
|
||||
@@ -589,7 +622,9 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||
StringFormatter::send(stream, F("<jR"));
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
if (params==1) sendFlashList(stream,RMFT2::rosterIdList);
|
||||
if (params==1) {
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
}
|
||||
else StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, RMFT2::getRosterName(id), RMFT2::getRosterFunctions(id));
|
||||
#endif
|
||||
@@ -606,14 +641,17 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
else { // <JT id>
|
||||
Turnout * t=Turnout::get(id);
|
||||
if (!t || t->isHidden()) StringFormatter::send(stream, F(" %d X"),id);
|
||||
else StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
else {
|
||||
const FSH *tdesc = NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getTurnoutDescription(id)
|
||||
#else
|
||||
F("")
|
||||
#endif
|
||||
);
|
||||
tdesc = RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
if (tdesc == NULL)
|
||||
tdesc = F("");
|
||||
StringFormatter::send(stream, F(" %d %c \"%S\""),
|
||||
id,t->isThrown()?'T':'C',
|
||||
tdesc);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
@@ -634,14 +672,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
StringFormatter::send(stream, F("<X>\n"));
|
||||
}
|
||||
|
||||
void DCCEXParser::sendFlashList(Print * stream,const int16_t flashList[]) {
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t value=GETFLASHW(flashList+i);
|
||||
if (value==0) return;
|
||||
StringFormatter::send(stream,F(" %d"),value);
|
||||
}
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseZ(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
|
||||
@@ -928,10 +958,12 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
DIAG(F("VPIN=%d value=%d"), p[1], IODevice::readAnalogue(p[1]));
|
||||
break;
|
||||
|
||||
#if !defined(IO_MINIMAL_HAL)
|
||||
#if !defined(IO_NO_HAL)
|
||||
case HASH_KEYWORD_HAL:
|
||||
if (p[1] == HASH_KEYWORD_SHOW)
|
||||
IODevice::DumpAll();
|
||||
else if (p[1] == HASH_KEYWORD_RESET)
|
||||
IODevice::reset();
|
||||
break;
|
||||
#endif
|
||||
|
||||
|
@@ -113,17 +113,19 @@ 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();
|
||||
// begin is called for any setup that must be done before
|
||||
// scan can be called.
|
||||
static void begin();
|
||||
// bit array of used pins (max 16)
|
||||
static uint16_t usedpins;
|
||||
// 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;
|
||||
};
|
||||
|
@@ -88,7 +88,10 @@ void DCCTimer::clearPWM() {
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
for (byte i=0; i<6; i++) {
|
||||
mac[i]=boot_signature_byte_get(0x0E + i);
|
||||
// take the fist 3 and last 3 of the serial.
|
||||
// the first 5 of 8 are at 0x0E to 0x013
|
||||
// the last 3 of 8 are at 0x15 to 0x017
|
||||
mac[i]=boot_signature_byte_get(0x0E + i + (i>2? 4 : 0));
|
||||
}
|
||||
mac[0] &= 0xFE;
|
||||
mac[0] |= 0x02;
|
||||
@@ -120,13 +123,14 @@ void DCCTimer::reset() {
|
||||
}
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
#define NUM_ADC_INPUTS 15
|
||||
#define NUM_ADC_INPUTS 16
|
||||
#else
|
||||
#define NUM_ADC_INPUTS 7
|
||||
#define NUM_ADC_INPUTS 8
|
||||
#endif
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
bool ADCusesHighPort = false;
|
||||
byte *ADCee::idarr = NULL;
|
||||
static bool ADCusesHighPort = false;
|
||||
|
||||
/*
|
||||
* Register a new pin to be scanned
|
||||
@@ -135,16 +139,28 @@ bool ADCusesHighPort = false;
|
||||
*/
|
||||
int ADCee::init(uint8_t pin) {
|
||||
uint8_t id = pin - A0;
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
byte n;
|
||||
if (id >= NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
if (id > 7)
|
||||
ADCusesHighPort = true;
|
||||
pinMode(pin, INPUT);
|
||||
int value = analogRead(pin);
|
||||
if (analogvals == NULL)
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogvals[id] = value;
|
||||
usedpins |= (1<<id);
|
||||
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;
|
||||
}
|
||||
}
|
||||
return value;
|
||||
}
|
||||
int16_t ADCee::ADCmax() {
|
||||
@@ -156,11 +172,13 @@ int16_t ADCee::ADCmax() {
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
(void)fromISR; // AVR does ignore this arg
|
||||
uint8_t id = pin - A0;
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
int a;
|
||||
// we do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
return analogvals[id];
|
||||
noInterrupts();
|
||||
a = analogvals[id];
|
||||
interrupts();
|
||||
return a;
|
||||
}
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
@@ -168,8 +186,7 @@ int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
#pragma GCC push_options
|
||||
#pragma GCC optimize ("-O3")
|
||||
void ADCee::scan() {
|
||||
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 byte num = 0; // index into id array
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
@@ -181,45 +198,26 @@ void ADCee::scan() {
|
||||
low = ADCL; //must read low before high
|
||||
high = ADCH;
|
||||
bitSet(ADCSRA, ADIF);
|
||||
analogvals[id] = (high << 8) | low;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
analogvals[idarr[num]] = (high << 8) | low;
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
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
|
||||
// cycle around in-use analogue pins
|
||||
num++;
|
||||
if (idarr[num] == 255)
|
||||
num = 0;
|
||||
// start new ADC aquire on id
|
||||
#if defined(ADCSRB) && defined(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)|(id & 0x07); //select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA,ADSC); // start conversion
|
||||
// for scope debug TrackManager::track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
return;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
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);
|
||||
}
|
||||
#endif
|
||||
ADMUX = (1 << REFS0) | (idarr[num] & 0x07); // select AVCC as reference and set MUX
|
||||
bitSet(ADCSRA, ADSC); // start conversion
|
||||
waiting = true;
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
@@ -233,4 +231,4 @@ void ADCee::begin() {
|
||||
//bitSet(ADCSRA, ADSC); //do not start the ADC yet. Done when we have set the MUX
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
||||
#endif
|
@@ -168,23 +168,6 @@ int ADCee::init(uint8_t pin) {
|
||||
if (id > NUM_ADC_INPUTS)
|
||||
return -1023;
|
||||
|
||||
// Dummy read using Arduino library
|
||||
analogReadResolution(12);
|
||||
value = analogRead(pin);
|
||||
|
||||
// Reconfigure ADC
|
||||
ADC->CTRLA.bit.ENABLE = 0; // disable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
ADC->CTRLB.reg &= 0b1111100011001111; // mask PRESCALER and RESSEL bits
|
||||
ADC->CTRLB.reg |= ADC_CTRLB_PRESCALER_DIV64 | // divide Clock by 16
|
||||
ADC_CTRLB_RESSEL_12BIT; // Result 12 bits, 10 bits possible
|
||||
ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1 | // take 1 sample at a time
|
||||
ADC_AVGCTRL_ADJRES(0x00ul); // adjusting result by 0
|
||||
ADC->SAMPCTRL.reg = 0x00ul; // sampling Time Length = 0
|
||||
ADC->CTRLA.bit.ENABLE = 1; // enable ADC
|
||||
while( ADC->STATUS.bit.SYNCBUSY == 1 ); // wait for synchronization
|
||||
|
||||
// Permanently configure SAMD IO MUX for that pin
|
||||
pinPeripheral(pin, PIO_ANALOG);
|
||||
ADC->INPUTCTRL.bit.MUXPOS = g_APinDescription[pin].ulADCChannelNumber; // Selection for the positive ADC input
|
||||
@@ -205,9 +188,11 @@ int ADCee::init(uint8_t pin) {
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
|
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2023 Neil McKechnie
|
||||
* © 2022 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Harald Barth
|
||||
@@ -28,35 +29,38 @@
|
||||
// This is to avoid repetition and duplication.
|
||||
#ifdef ARDUINO_ARCH_STM32
|
||||
|
||||
#include "FSH.h" //PMA temp debug
|
||||
#include "DIAG.h" //PMA temp debug
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#define STM32F411RE // PMA - ideally this ought to be derived from within the STM32 support somehow
|
||||
|
||||
#if defined(STM32F411RE)
|
||||
// STM32F411RE doesn't have Serial1 defined by default
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
// Nucleo-64 boards don't have Serial1 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 STM32F411RE. 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 F411RE)
|
||||
// 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(STM32F446ZE)
|
||||
// STM32F446ZE doesn't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F446ZE
|
||||
#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
|
||||
// Serial2 is defined to use USART2 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-64. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
|
||||
#else
|
||||
#warning Serial1 not defined
|
||||
#endif
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
// Let's use STM32's timer #11 until disabused of this notion
|
||||
// Timer #11 is used for "servo" library, but as DCC-EX is not using
|
||||
// this libary, we should be free and clear.
|
||||
HardwareTimer timer(TIM11);
|
||||
// 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);
|
||||
|
||||
// Timer IRQ handler
|
||||
void Timer11_Handler() {
|
||||
void Timer_Handler() {
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
@@ -66,38 +70,65 @@ 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(Timer11_Handler);
|
||||
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) {
|
||||
//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;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCTimer::setPWM(byte pin, bool high) {
|
||||
// TODO: High Accuracy mode is not supported as yet, and may never need to be
|
||||
(void) pin;
|
||||
(void) high;
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
void DCCTimer::clearPWM() {
|
||||
return;
|
||||
timer.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
||||
timerAux.setMode(1, TIMER_OUTPUT_COMPARE_INACTIVE, NC);
|
||||
}
|
||||
|
||||
void DCCTimer::getSimulatedMacAddress(byte mac[6]) {
|
||||
volatile uint32_t *serno1 = (volatile uint32_t *)0x0080A00C;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x0080A040;
|
||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x0080A044;
|
||||
// volatile uint32_t *serno4 = (volatile uint32_t *)0x0080A048;
|
||||
volatile uint32_t *serno1 = (volatile uint32_t *)0x1FFF7A10;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
|
||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
|
||||
|
||||
volatile uint32_t m1 = *serno1;
|
||||
volatile uint32_t m2 = *serno2;
|
||||
@@ -132,31 +163,148 @@ 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!
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
bool adc1configured = false;
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
return analogRead(pin);
|
||||
uint id = pin - A0;
|
||||
int value = 0;
|
||||
PinName stmpin = digitalPin[analogInputPin[id]];
|
||||
uint32_t stmgpio = stmpin / 16; // 16-bits per GPIO port group on STM32
|
||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
||||
GPIO_TypeDef * gpioBase;
|
||||
|
||||
// Port config - find which port we're on and power it up
|
||||
switch(stmgpio) {
|
||||
case 0x00:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
|
||||
gpioBase = GPIOA;
|
||||
break;
|
||||
case 0x01:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOBEN; //Power up PORTB
|
||||
gpioBase = GPIOB;
|
||||
break;
|
||||
case 0x02:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||
gpioBase = GPIOC;
|
||||
break;
|
||||
}
|
||||
|
||||
// Set pin mux mode to analog input
|
||||
gpioBase->MODER |= (0b011 << (stmpin << 1)); // Set pin mux to analog mode
|
||||
|
||||
// Set the sampling rate for that analog input
|
||||
if (adcchan < 10)
|
||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
else
|
||||
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
|
||||
// Read the inital ADC value for this analog input
|
||||
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = ADC1->DR; // Read value from register
|
||||
|
||||
if (analogvals == NULL)
|
||||
{
|
||||
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
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* Read function ADCee::read(pin) to get value instead of analogRead(pin)
|
||||
*/
|
||||
int ADCee::read(uint8_t pin, bool fromISR) {
|
||||
int current;
|
||||
if (!fromISR) noInterrupts();
|
||||
current = analogRead(pin);
|
||||
if (!fromISR) interrupts();
|
||||
return current;
|
||||
uint8_t id = pin - A0;
|
||||
// Was this pin initialised yet?
|
||||
if ((usedpins & (1<<id) ) == 0)
|
||||
return -1023;
|
||||
// We do not need to check (analogvals == NULL)
|
||||
// because usedpins would still be 0 in that case
|
||||
return analogvals[id];
|
||||
}
|
||||
|
||||
/*
|
||||
* Scan function that is called from interrupt
|
||||
*/
|
||||
#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 uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
|
||||
if (waiting) {
|
||||
// look if we have a result
|
||||
if (!(ADC1->SR & (1 << 1)))
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
analogvals[id] = ADC1->DR;
|
||||
// advance at least one track
|
||||
// for scope debug TrackManager::track[1]->setBrake(0);
|
||||
waiting = false;
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
if (!waiting) {
|
||||
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
|
||||
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;
|
||||
}
|
||||
id++;
|
||||
mask = mask << 1;
|
||||
if (id == NUM_ADC_INPUTS+1) {
|
||||
id = 0;
|
||||
mask = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#pragma GCC pop_options
|
||||
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
//ADC1 config sequence
|
||||
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
|
||||
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
|
||||
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
|
||||
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
|
||||
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC1->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
@@ -62,7 +62,6 @@ const bool signalTransform[]={
|
||||
/* WAVE_PENDING (should not happen) -> */ LOW};
|
||||
|
||||
void DCCWaveform::begin() {
|
||||
ADCee::begin();
|
||||
DCCTimer::begin(DCCWaveform::interruptHandler);
|
||||
}
|
||||
|
||||
|
1
DIAG.h
1
DIAG.h
@@ -24,4 +24,5 @@
|
||||
#include "StringFormatter.h"
|
||||
#define DIAG StringFormatter::diag
|
||||
#define LCD StringFormatter::lcd
|
||||
#define SCREEN StringFormatter::lcd2
|
||||
#endif
|
||||
|
196
Display.cpp
Normal file
196
Display.cpp
Normal file
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. 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/>.
|
||||
*/
|
||||
|
||||
// CAUTION: the device dependent parts of this class are created in the .ini
|
||||
// using LCD_Implementation.h
|
||||
|
||||
/* The strategy for drawing the screen is as follows.
|
||||
* 1) There are up to eight rows of text to be displayed.
|
||||
* 2) Blank rows of text are ignored.
|
||||
* 3) If there are more non-blank rows than screen lines,
|
||||
* then all of the rows are displayed, with the rest of the
|
||||
* screen being blank.
|
||||
* 4) If there are fewer non-blank rows than screen lines,
|
||||
* then a scrolling strategy is adopted so that, on each screen
|
||||
* refresh, a different subset of the rows is presented.
|
||||
* 5) On each entry into loop2(), a single operation is sent to the
|
||||
* screen; this may be a position command or a character for
|
||||
* display. This spreads the onerous work of updating the screen
|
||||
* and ensures that other loop() functions in the application are
|
||||
* 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.
|
||||
* 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),
|
||||
* #define SCROLLMODE 1 is by page (alternate between pages),
|
||||
* #define SCROLLMODE 2 is by row (move up 1 row at a time).
|
||||
|
||||
*/
|
||||
|
||||
#include "Display.h"
|
||||
|
||||
// Constructor - allocates device driver.
|
||||
Display::Display(DisplayDevice *deviceDriver) {
|
||||
_deviceDriver = deviceDriver;
|
||||
// Get device dimensions in characters (e.g. 16x2).
|
||||
numCharacterColumns = _deviceDriver->getNumCols();
|
||||
numCharacterRows = _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
|
||||
};
|
||||
|
||||
void Display::begin() {
|
||||
_deviceDriver->begin();
|
||||
_deviceDriver->clearNative();
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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;
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Refresh screen completely (will block until complete). Used
|
||||
// during start-up.
|
||||
void Display::_refresh() {
|
||||
loop2(true);
|
||||
}
|
||||
|
||||
// On normal loop entries, loop will only make one output request on each
|
||||
// entry, to avoid blocking while waiting for the I2C.
|
||||
void Display::_displayLoop() {
|
||||
// If output device is busy, don't do anything on this loop
|
||||
// This avoids blocking while waiting for the device to complete.
|
||||
if (!_deviceDriver->isBusy()) loop2(false);
|
||||
}
|
||||
|
||||
Display *Display::loop2(bool force) {
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if (!force) {
|
||||
// See if we're in the time between updates
|
||||
if ((currentMillis - lastScrollTime) < DISPLAY_SCROLL_TIME)
|
||||
return NULL;
|
||||
} else {
|
||||
// force full screen update from the beginning.
|
||||
rowFirst = ROW_INITIAL;
|
||||
rowNext = ROW_INITIAL;
|
||||
bufferPointer = 0;
|
||||
noMoreRowsToDisplay = false;
|
||||
slot = 0;
|
||||
}
|
||||
|
||||
do {
|
||||
if (bufferPointer == 0) {
|
||||
// Find a line of data to write to the screen.
|
||||
if (rowFirst == ROW_INITIAL) rowFirst = rowNext;
|
||||
if (findNextNonBlankRow()) {
|
||||
// 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;
|
||||
}
|
||||
_deviceDriver->setRowNative(slot); // Set position for display
|
||||
charIndex = 0;
|
||||
bufferPointer = &buffer[0];
|
||||
} else {
|
||||
// Write next character, or a space to erase current position.
|
||||
char ch = *bufferPointer;
|
||||
if (ch) {
|
||||
_deviceDriver->writeNative(ch);
|
||||
bufferPointer++;
|
||||
} else {
|
||||
_deviceDriver->writeNative(' ');
|
||||
}
|
||||
|
||||
if (++charIndex >= MAX_CHARACTER_COLS) {
|
||||
// Screen slot completed, move to next slot on screen
|
||||
bufferPointer = 0;
|
||||
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();
|
||||
}
|
||||
#endif
|
||||
noMoreRowsToDisplay = false;
|
||||
slot = 0;
|
||||
rowFirst = ROW_INITIAL;
|
||||
lastScrollTime = currentMillis;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (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;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
76
Display.h
Normal file
76
Display.h
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef Display_h
|
||||
#define Display_h
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "DisplayInterface.h"
|
||||
|
||||
// Allow maximum message length to be overridden from config.h
|
||||
#if !defined(MAX_MSG_SIZE)
|
||||
#define MAX_MSG_SIZE 20
|
||||
#endif
|
||||
|
||||
// Set default scroll mode (overridable in config.h)
|
||||
#if !defined(SCROLLMODE)
|
||||
#define SCROLLMODE 1
|
||||
#endif
|
||||
|
||||
// This class is created in Display_Implementation.h
|
||||
|
||||
class Display : public DisplayInterface {
|
||||
public:
|
||||
Display(DisplayDevice *deviceDriver);
|
||||
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;
|
||||
|
||||
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 charIndex = 0;
|
||||
char buffer[MAX_CHARACTER_COLS + 1];
|
||||
char* bufferPointer = 0;
|
||||
bool noMoreRowsToDisplay = false;
|
||||
uint16_t numCharacterRows;
|
||||
uint16_t numCharacterColumns = MAX_CHARACTER_COLS;
|
||||
|
||||
char rowBuffer[MAX_CHARACTER_ROWS][MAX_CHARACTER_COLS+1];
|
||||
|
||||
public:
|
||||
void begin() override;
|
||||
void _clear() override;
|
||||
void _setRow(uint8_t line) override;
|
||||
size_t _write(uint8_t b) override;
|
||||
void _refresh() override;
|
||||
void _displayLoop() override;
|
||||
Display *loop2(bool force);
|
||||
bool findNextNonBlankRow();
|
||||
|
||||
};
|
||||
|
||||
#endif
|
@@ -21,4 +21,7 @@
|
||||
|
||||
#include "DisplayInterface.h"
|
||||
|
||||
DisplayInterface *DisplayInterface::lcdDisplay = 0;
|
||||
// Install null display driver initially - will be replaced if required.
|
||||
DisplayInterface *DisplayInterface::_displayHandler = new DisplayInterface();
|
||||
|
||||
uint8_t DisplayInterface::_selectedDisplayNo = 255;
|
||||
|
@@ -25,13 +25,75 @@
|
||||
|
||||
// Definition of base class for displays. The base class does nothing.
|
||||
class DisplayInterface : public Print {
|
||||
public:
|
||||
virtual DisplayInterface* loop2(bool force) { (void)force; return NULL; };
|
||||
virtual void setRow(byte line) { (void)line; };
|
||||
virtual void clear() {};
|
||||
virtual size_t write(uint8_t c) { (void)c; return 0; };
|
||||
protected:
|
||||
static DisplayInterface *_displayHandler;
|
||||
static uint8_t _selectedDisplayNo; // Nothing selected.
|
||||
DisplayInterface *_nextHandler = NULL;
|
||||
uint8_t _displayNo = 0;
|
||||
|
||||
static DisplayInterface *lcdDisplay;
|
||||
public:
|
||||
// Add display object to list of displays
|
||||
void addDisplay(uint8_t displayNo) {
|
||||
_nextHandler = _displayHandler;
|
||||
_displayHandler = this;
|
||||
_displayNo = displayNo;
|
||||
}
|
||||
static DisplayInterface *getDisplayHandler() {
|
||||
return _displayHandler;
|
||||
}
|
||||
uint8_t getDisplayNo() {
|
||||
return _displayNo;
|
||||
}
|
||||
|
||||
// The next functions are to provide compatibility with calls to the LCD function
|
||||
// which does not specify a display number. These always apply to display '0'.
|
||||
static void refresh() { refresh(0); };
|
||||
static void setRow(uint8_t line) { setRow(0, line); };
|
||||
static void clear() { clear(0); };
|
||||
|
||||
// Additional functions to support multiple displays. These perform a
|
||||
// multicast to all displays that match the selected displayNo.
|
||||
// Display number zero is the default one.
|
||||
static void setRow(uint8_t displayNo, uint8_t line) {
|
||||
_selectedDisplayNo = displayNo;
|
||||
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler) {
|
||||
if (displayNo == p->_displayNo) p->_setRow(line);
|
||||
}
|
||||
}
|
||||
size_t write (uint8_t c) override {
|
||||
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
|
||||
if (_selectedDisplayNo == p->_displayNo) p->_write(c);
|
||||
return _displayHandler ? 1 : 0;
|
||||
}
|
||||
static void clear(uint8_t displayNo) {
|
||||
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
|
||||
if (displayNo == p->_displayNo) p->_clear();
|
||||
}
|
||||
static void refresh(uint8_t displayNo) {
|
||||
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
|
||||
if (displayNo == p->_displayNo) p->_refresh();
|
||||
}
|
||||
static void loop() {
|
||||
for (DisplayInterface *p = _displayHandler; p!=0; p=p->_nextHandler)
|
||||
p->_displayLoop();
|
||||
};
|
||||
// The following are overridden within the specific device class
|
||||
virtual void begin() {};
|
||||
virtual size_t _write(uint8_t c) { (void)c; return 0; };
|
||||
virtual void _setRow(uint8_t line) { (void)line; }
|
||||
virtual void _clear() {}
|
||||
virtual void _refresh() {}
|
||||
virtual void _displayLoop() {}
|
||||
};
|
||||
|
||||
class DisplayDevice {
|
||||
public:
|
||||
virtual bool begin() { return true; }
|
||||
virtual void clearNative() = 0;
|
||||
virtual void setRowNative(uint8_t line) = 0;
|
||||
virtual size_t writeNative(uint8_t c) = 0;
|
||||
virtual bool isBusy() = 0;
|
||||
virtual uint16_t getNumRows() = 0;
|
||||
virtual uint16_t getNumCols() = 0;
|
||||
};
|
||||
#endif
|
||||
|
@@ -27,27 +27,34 @@
|
||||
|
||||
#ifndef LCD_Implementation_h
|
||||
#define LCD_Implementation_h
|
||||
#include "LCDDisplay.h"
|
||||
#include "DisplayInterface.h"
|
||||
#include "SSD1306Ascii.h"
|
||||
#include "LiquidCrystal_I2C.h"
|
||||
|
||||
|
||||
// Implement the LCDDisplay shim class as a singleton.
|
||||
// The DisplayInterface class implements a displayy handler with no code (null device);
|
||||
// The LCDDisplay class sub-classes DisplayInterface to provide the common display code;
|
||||
// Then LCDDisplay class is subclassed to the specific device type classes:
|
||||
// Implement the Display shim class as a singleton.
|
||||
// The DisplayInterface class implements a display handler with no code (null device);
|
||||
// The Display class sub-classes DisplayInterface to provide the common display code;
|
||||
// Then Display class talks to the specific device type classes:
|
||||
// SSD1306AsciiWire for I2C OLED driver with SSD1306 or SH1106 controllers;
|
||||
// LiquidCrystal_I2C for I2C LCD driver for HD44780 with PCF8574 'backpack'.
|
||||
|
||||
#if defined(OLED_DRIVER)
|
||||
#define CONDITIONAL_LCD_START for (DisplayInterface * dummy=new SSD1306AsciiWire(OLED_DRIVER);dummy!=NULL; dummy=dummy->loop2(true))
|
||||
#define DISPLAY_START(xxx) { \
|
||||
DisplayInterface *t = new Display(new SSD1306AsciiWire(OLED_DRIVER)); \
|
||||
t->begin(); \
|
||||
xxx; \
|
||||
t->refresh(); \
|
||||
}
|
||||
|
||||
#elif defined(LCD_DRIVER)
|
||||
#define CONDITIONAL_LCD_START for (DisplayInterface * dummy=new LiquidCrystal_I2C(LCD_DRIVER);dummy!=NULL; dummy=dummy->loop2(true))
|
||||
|
||||
#define DISPLAY_START(xxx) { \
|
||||
DisplayInterface *t = new Display(new LiquidCrystal_I2C(LCD_DRIVER)); \
|
||||
t->begin(); \
|
||||
xxx; \
|
||||
t->refresh();}
|
||||
#else
|
||||
// Create null display handler just in case someone calls lcdDisplay->something without checking if lcdDisplay is NULL!
|
||||
#define CONDITIONAL_LCD_START { new DisplayInterface(); }
|
||||
#endif
|
||||
#define DISPLAY_START(xxx) {}
|
||||
|
||||
#endif
|
||||
#endif // LCD_Implementation_h
|
254
EXRAIL2.cpp
254
EXRAIL2.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -41,6 +41,7 @@
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "DCC.h"
|
||||
#include "DCCWaveform.h"
|
||||
@@ -90,15 +91,27 @@ LookList * RMFT2::onDeactivateLookup=NULL;
|
||||
LookList * RMFT2::onRedLookup=NULL;
|
||||
LookList * RMFT2::onAmberLookup=NULL;
|
||||
LookList * RMFT2::onGreenLookup=NULL;
|
||||
#define GET_OPCODE GETFLASH(RMFT2::RouteCode+progCounter)
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
#define GET_OPERAND(n) GETFLASHW(RMFT2::RouteCode+progCounter+1+(n*3))
|
||||
#else
|
||||
#define GET_OPERAND(n) GETOPW(RMFT2::RouteCode+progCounter+1+(n*3))
|
||||
#define GETOPW(A) (((uint32_t)A)%2 ? GETFLASH((const byte *)A) | (GETFLASH(1+(const byte *)A)<<8) : GETFLASHW(A))
|
||||
#endif
|
||||
LookList * RMFT2::onChangeLookup=NULL;
|
||||
LookList * RMFT2::onClockLookup=NULL;
|
||||
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
|
||||
// getOperand instance version, uses progCounter from instance.
|
||||
uint16_t RMFT2::getOperand(byte n) {
|
||||
return getOperand(progCounter,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);
|
||||
}
|
||||
|
||||
LookList::LookList(int16_t size) {
|
||||
m_size=size;
|
||||
@@ -139,12 +152,17 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
for (progCounter=0;; SKIPOP) {
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(GET_OPERAND(0),progCounter);
|
||||
if (opcode==op1 || opcode==op2 || opcode==op3) list->add(getOperand(progCounter,0),progCounter);
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::begin() {
|
||||
|
||||
DIAG(F("EXRAIL RoutCode at =%P"),RouteCode);
|
||||
|
||||
bool saved_diag=diag;
|
||||
diag=true;
|
||||
DCCEXParser::setRMFTFilter(RMFT2::ComandFilter);
|
||||
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
|
||||
|
||||
@@ -157,11 +175,14 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
onRedLookup=LookListLoader(OPCODE_ONRED);
|
||||
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
|
||||
onClockLookup=LookListLoader(OPCODE_ONTIME);
|
||||
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
// add sequences onRoutines to the lookups
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
|
||||
}
|
||||
@@ -170,7 +191,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
for (progCounter=0;; SKIPOP){
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
VPIN operand=GET_OPERAND(0);
|
||||
VPIN operand=getOperand(progCounter,0);
|
||||
|
||||
switch (opcode) {
|
||||
case OPCODE_AT:
|
||||
@@ -180,6 +201,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);
|
||||
IODevice::configureInput((VPIN)pin,true);
|
||||
break;
|
||||
}
|
||||
@@ -189,31 +211,32 @@ 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);
|
||||
IODevice::configureAnalogIn((VPIN)operand);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_TURNOUT: {
|
||||
VPIN id=operand;
|
||||
int addr=GET_OPERAND(1);
|
||||
byte subAddr=GET_OPERAND(2);
|
||||
int addr=getOperand(progCounter,1);
|
||||
byte subAddr=getOperand(progCounter,2);
|
||||
setTurnoutHiddenState(DCCTurnout::create(id,addr,subAddr));
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_SERVOTURNOUT: {
|
||||
VPIN id=operand;
|
||||
VPIN pin=GET_OPERAND(1);
|
||||
int activeAngle=GET_OPERAND(2);
|
||||
int inactiveAngle=GET_OPERAND(3);
|
||||
int profile=GET_OPERAND(4);
|
||||
VPIN pin=getOperand(progCounter,1);
|
||||
int activeAngle=getOperand(progCounter,2);
|
||||
int inactiveAngle=getOperand(progCounter,3);
|
||||
int profile=getOperand(progCounter,4);
|
||||
setTurnoutHiddenState(ServoTurnout::create(id,pin,activeAngle,inactiveAngle,profile));
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_PINTURNOUT: {
|
||||
VPIN id=operand;
|
||||
VPIN pin=GET_OPERAND(1);
|
||||
VPIN pin=getOperand(progCounter,1);
|
||||
setTurnoutHiddenState(VpinTurnout::create(id,pin));
|
||||
break;
|
||||
}
|
||||
@@ -233,20 +256,22 @@ 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
|
||||
diag=saved_diag;
|
||||
}
|
||||
|
||||
void RMFT2::setTurnoutHiddenState(Turnout * t) {
|
||||
// turnout descriptions are in low flash F strings
|
||||
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
|
||||
}
|
||||
|
||||
char RMFT2::getRouteType(int16_t id) {
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t rid= GETFLASHW(routeIdList+i);
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(routeIdList,i);
|
||||
if (rid==id) return 'R';
|
||||
if (rid==0) break;
|
||||
}
|
||||
for (int16_t i=0;;i++) {
|
||||
int16_t rid= GETFLASHW(automationIdList+i);
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(automationIdList,i);
|
||||
if (rid==id) return 'A';
|
||||
if (rid==0) break;
|
||||
}
|
||||
@@ -308,7 +333,7 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
// do the signals
|
||||
// flags[n] represents the state of the nth signal in the table
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigslot*4);
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
@@ -555,7 +580,7 @@ void RMFT2::loop2() {
|
||||
if (delayTime!=0 && millis()-delayStart < delayTime) return;
|
||||
|
||||
byte opcode = GET_OPCODE;
|
||||
int16_t operand = GET_OPERAND(0);
|
||||
int16_t operand = getOperand(0);
|
||||
|
||||
// skipIf will get set to indicate a failing IF condition
|
||||
bool skipIf=false;
|
||||
@@ -621,13 +646,13 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_ATGTE: // wait for analog sensor>= value
|
||||
timeoutFlag=false;
|
||||
if (IODevice::readAnalogue(operand) >= (int)(GET_OPERAND(1))) break;
|
||||
if (IODevice::readAnalogue(operand) >= (int)(getOperand(1))) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
case OPCODE_ATLT: // wait for analog sensor < value
|
||||
timeoutFlag=false;
|
||||
if (IODevice::readAnalogue(operand) < (int)(GET_OPERAND(1))) break;
|
||||
if (IODevice::readAnalogue(operand) < (int)(getOperand(1))) break;
|
||||
delayMe(50);
|
||||
return;
|
||||
|
||||
@@ -638,7 +663,7 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_ATTIMEOUT2:
|
||||
if (readSensor(operand)) break; // success without timeout
|
||||
if (millis()-timeoutStart > 100*GET_OPERAND(1)) {
|
||||
if (millis()-timeoutStart > 100*getOperand(1)) {
|
||||
timeoutFlag=true;
|
||||
break; // and drop through
|
||||
}
|
||||
@@ -681,7 +706,7 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_POM:
|
||||
if (loco) DCC::writeCVByteMain(loco, operand, GET_OPERAND(1));
|
||||
if (loco) DCC::writeCVByteMain(loco, operand, getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_POWEROFF:
|
||||
@@ -715,19 +740,27 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_IFGTE: // do next operand if sensor>= value
|
||||
skipIf=IODevice::readAnalogue(operand)<(int)(GET_OPERAND(1));
|
||||
skipIf=IODevice::readAnalogue(operand)<(int)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFLT: // do next operand if sensor< value
|
||||
skipIf=IODevice::readAnalogue(operand)>=(int)(GET_OPERAND(1));
|
||||
skipIf=IODevice::readAnalogue(operand)>=(int)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFLOCO: // do if the loco is the active one
|
||||
skipIf=loco!=(uint16_t)operand; // bad luck if someone enters negative loco numbers into EXRAIL
|
||||
break;
|
||||
|
||||
case OPCODE_IFNOT: // do next operand if sensor not set
|
||||
skipIf=readSensor(operand);
|
||||
break;
|
||||
|
||||
case OPCODE_IFRE: // do next operand if rotary encoder != position
|
||||
skipIf=IODevice::readAnalogue(operand)!=(int)(getOperand(1));
|
||||
break;
|
||||
|
||||
case OPCODE_IFRANDOM: // do block on random percentage
|
||||
skipIf=(int16_t)(micros()%100) >= operand;
|
||||
skipIf=(uint8_t)micros() >= operand * 255/100;
|
||||
break;
|
||||
|
||||
case OPCODE_IFRESERVE: // do block if we successfully RERSERVE
|
||||
@@ -802,11 +835,11 @@ void RMFT2::loop2() {
|
||||
}
|
||||
|
||||
case OPCODE_XFON:
|
||||
DCC::setFn(operand,GET_OPERAND(1),true);
|
||||
DCC::setFn(operand,getOperand(1),true);
|
||||
break;
|
||||
|
||||
case OPCODE_XFOFF:
|
||||
DCC::setFn(operand,GET_OPERAND(1),false);
|
||||
DCC::setFn(operand,getOperand(1),false);
|
||||
break;
|
||||
|
||||
case OPCODE_DCCACTIVATE: {
|
||||
@@ -898,7 +931,7 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_SENDLOCO: // cab, route
|
||||
{
|
||||
int newPc=sequenceLookup->find(GET_OPERAND(1));
|
||||
int newPc=sequenceLookup->find(getOperand(1));
|
||||
if (newPc<0) break;
|
||||
RMFT2* newtask=new RMFT2(newPc); // create new task
|
||||
newtask->loco=operand;
|
||||
@@ -916,7 +949,7 @@ void RMFT2::loop2() {
|
||||
|
||||
|
||||
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
|
||||
IODevice::writeAnalogue(operand,GET_OPERAND(1),GET_OPERAND(2),GET_OPERAND(3));
|
||||
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
|
||||
break;
|
||||
|
||||
case OPCODE_WAITFOR: // OPCODE_SERVO,V(pin)
|
||||
@@ -948,6 +981,8 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONRED:
|
||||
case OPCODE_ONAMBER:
|
||||
case OPCODE_ONGREEN:
|
||||
case OPCODE_ONCHANGE:
|
||||
case OPCODE_ONTIME:
|
||||
|
||||
break;
|
||||
|
||||
@@ -965,7 +1000,7 @@ void RMFT2::delayMe(long delay) {
|
||||
delayStart=millis();
|
||||
}
|
||||
|
||||
boolean RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
|
||||
bool RMFT2::setFlag(VPIN id,byte onMask, byte offMask) {
|
||||
if (FLAGOVERFLOW(id)) return false; // Outside range limit
|
||||
byte f=flags[id];
|
||||
f &= ~offMask;
|
||||
@@ -986,8 +1021,8 @@ void RMFT2::kill(const FSH * reason, int operand) {
|
||||
}
|
||||
|
||||
int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
for (int sigpos=0;;sigpos+=4) {
|
||||
int16_t sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
int16_t sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) { // end of signal list
|
||||
DIAG(F("EXRAIL Signal %d not defined"), id);
|
||||
return -1;
|
||||
@@ -997,9 +1032,10 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
// but for a servo signal it will also have SERVO_SIGNAL_FLAG set.
|
||||
|
||||
if ((sigid & SIGNAL_ID_MASK)!= id) continue; // keep looking
|
||||
return sigpos/4; // relative slot in signals table
|
||||
return sigslot; // relative slot in signals table
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
|
||||
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
|
||||
|
||||
@@ -1016,11 +1052,11 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
setFlag(sigslot,rag,SIGNAL_MASK);
|
||||
|
||||
// Correct signal definition found, get the rag values
|
||||
int16_t sigpos=sigslot*4;
|
||||
VPIN sigid=GETFLASHW(RMFT2::SignalDefinitions+sigpos);
|
||||
VPIN redpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+1);
|
||||
VPIN amberpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+2);
|
||||
VPIN greenpin=GETFLASHW(RMFT2::SignalDefinitions+sigpos+3);
|
||||
int16_t sigpos=sigslot*8;
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos);
|
||||
VPIN redpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+2);
|
||||
VPIN amberpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+4);
|
||||
VPIN greenpin=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigpos+6);
|
||||
if (diag) DIAG(F("signal %d %d %d %d %d"),sigid,id,redpin,amberpin,greenpin);
|
||||
|
||||
VPIN sigtype=sigid & ~SIGNAL_ID_MASK;
|
||||
@@ -1048,11 +1084,23 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
|
||||
// Manage invert (HIGH on) pins
|
||||
bool aHigh=sigid & ACTIVE_HIGH_SIGNAL_FLAG;
|
||||
|
||||
|
||||
// set the three pins
|
||||
if (redpin) IODevice::write(redpin,(rag==SIGNAL_RED || rag==SIMAMBER)^aHigh);
|
||||
if (amberpin) IODevice::write(amberpin,(rag==SIGNAL_AMBER)^aHigh);
|
||||
if (greenpin) IODevice::write(greenpin,(rag==SIGNAL_GREEN || rag==SIMAMBER)^aHigh);
|
||||
if (redpin) {
|
||||
bool redval=(rag==SIGNAL_RED || rag==SIMAMBER);
|
||||
if (!aHigh) redval=!redval;
|
||||
IODevice::write(redpin,redval);
|
||||
}
|
||||
if (amberpin) {
|
||||
bool amberval=(rag==SIGNAL_AMBER);
|
||||
if (!aHigh) amberval=!amberval;
|
||||
IODevice::write(amberpin,amberval);
|
||||
}
|
||||
if (greenpin) {
|
||||
bool greenval=(rag==SIGNAL_GREEN || rag==SIMAMBER);
|
||||
if (!aHigh) greenval=!greenval;
|
||||
IODevice::write(greenpin,greenval);
|
||||
}
|
||||
}
|
||||
|
||||
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
|
||||
@@ -1073,7 +1121,19 @@ void RMFT2::activateEvent(int16_t addr, bool activate) {
|
||||
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
|
||||
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
|
||||
}
|
||||
|
||||
|
||||
void RMFT2::changeEvent(int16_t vpin, bool change) {
|
||||
// Hunt for an ONCHANGE for this sensor
|
||||
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
int pc= handlers->find(id);
|
||||
if (pc<0) return;
|
||||
@@ -1096,3 +1156,97 @@ void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
void RMFT2::printMessage2(const FSH * msg) {
|
||||
DIAG(F("EXRAIL(%d) %S"),loco,msg);
|
||||
}
|
||||
static StringBuffer * buffer=NULL;
|
||||
/* thrungeString is used to stream a HIGHFLASH string to a suitable Serial
|
||||
and handle the oddities like LCD, BROADCAST and PARSE */
|
||||
void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
//DIAG(F("thrunge addr=%l mode=%d id=%d"), strfar,mode,id);
|
||||
Print * stream=NULL;
|
||||
// Find out where the string is going
|
||||
switch (mode) {
|
||||
case thrunge_print:
|
||||
StringFormatter::send(&USB_SERIAL,F("<* EXRAIL(%d) "),loco);
|
||||
stream=&USB_SERIAL;
|
||||
break;
|
||||
|
||||
case thrunge_serial: stream=&USB_SERIAL; break;
|
||||
case thrunge_serial1:
|
||||
#ifdef SERIAL1_COMMANDS
|
||||
stream=&Serial1;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial2:
|
||||
#ifdef SERIAL2_COMMANDS
|
||||
stream=&Serial2;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial3:
|
||||
#ifdef SERIAL3_COMMANDS
|
||||
stream=&Serial3;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial4:
|
||||
#ifdef SERIAL4_COMMANDS
|
||||
stream=&Serial4;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial5:
|
||||
#ifdef SERIAL5_COMMANDS
|
||||
stream=&Serial5;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_serial6:
|
||||
#ifdef SERIAL6_COMMANDS
|
||||
stream=&Serial6;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_lcn:
|
||||
#if defined(LCN_SERIAL)
|
||||
stream=&LCN_SERIAL;
|
||||
#endif
|
||||
break;
|
||||
case thrunge_parse:
|
||||
case thrunge_broadcast:
|
||||
case thrunge_lcd:
|
||||
default: // thrunge_lcd+1, ...
|
||||
if (!buffer) buffer=new StringBuffer();
|
||||
buffer->flush();
|
||||
stream=buffer;
|
||||
break;
|
||||
}
|
||||
if (!stream) return;
|
||||
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
// if mega stream it out
|
||||
for (;;strfar++) {
|
||||
char c=pgm_read_byte_far(strfar);
|
||||
if (c=='\0') break;
|
||||
stream->write(c);
|
||||
}
|
||||
#else
|
||||
// UNO/NANO CPUs dont have high memory
|
||||
// 32 bit cpus dont care anyway
|
||||
stream->print((FSH *)strfar);
|
||||
#endif
|
||||
|
||||
// and decide what to do next
|
||||
switch (mode) {
|
||||
case thrunge_print:
|
||||
StringFormatter::send(&USB_SERIAL,F(" *>\n"));
|
||||
break;
|
||||
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
|
||||
case thrunge_parse:
|
||||
DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL);
|
||||
break;
|
||||
case thrunge_broadcast:
|
||||
// TODO CommandDistributor::broadcastText(buffer->getString());
|
||||
break;
|
||||
case thrunge_lcd:
|
||||
LCD(id,F("%s"),buffer->getString());
|
||||
break;
|
||||
default: // thrunge_lcd+1, ...
|
||||
if (mode > thrunge_lcd)
|
||||
SCREEN(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
37
EXRAIL2.h
37
EXRAIL2.h
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -54,6 +55,9 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
OPCODE_ONCHANGE,
|
||||
OPCODE_ONCLOCKTIME,
|
||||
OPCODE_ONTIME,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -64,9 +68,21 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_IFTIMEOUT,
|
||||
OPCODE_IF,OPCODE_IFNOT,
|
||||
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||
OPCODE_IFRE,
|
||||
OPCODE_IFLOCO
|
||||
};
|
||||
|
||||
// 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_serial1, thrunge_serial2, thrunge_serial3,
|
||||
thrunge_serial4, thrunge_serial5, thrunge_serial6,
|
||||
thrunge_lcn,
|
||||
thrunge_lcd, // Must be last!!
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Flag bits for status of hardware and TPL
|
||||
@@ -107,16 +123,17 @@ class LookList {
|
||||
static void createNewTask(int route, uint16_t cab);
|
||||
static void turnoutEvent(int16_t id, bool closed);
|
||||
static void activateEvent(int16_t addr, bool active);
|
||||
static void changeEvent(int16_t id, bool change);
|
||||
static void clockEvent(int16_t clocktime, bool change);
|
||||
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
||||
static const int16_t SIGNAL_ID_MASK=0x0FFF;
|
||||
|
||||
// Throttle Info Access functions built by exrail macros
|
||||
static const byte rosterNameCount;
|
||||
static const int16_t FLASH routeIdList[];
|
||||
static const int16_t FLASH automationIdList[];
|
||||
static const int16_t FLASH rosterIdList[];
|
||||
static const int16_t HIGHFLASH routeIdList[];
|
||||
static const int16_t HIGHFLASH automationIdList[];
|
||||
static const int16_t HIGHFLASH rosterIdList[];
|
||||
static const FSH * getRouteDescription(int16_t id);
|
||||
static char getRouteType(int16_t id);
|
||||
static const FSH * getTurnoutDescription(int16_t id);
|
||||
@@ -137,6 +154,7 @@ private:
|
||||
static LookList* LookListLoader(OPCODE op1,
|
||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
||||
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
|
||||
static uint16_t getOperand(int progCounter,byte n);
|
||||
static RMFT2 * loopTask;
|
||||
static RMFT2 * pausingTask;
|
||||
void delayMe(long millisecs);
|
||||
@@ -148,10 +166,12 @@ private:
|
||||
void kill(const FSH * reason=NULL,int operand=0);
|
||||
void printMessage(uint16_t id); // Built by RMFTMacros.h
|
||||
void printMessage2(const FSH * msg);
|
||||
void thrungeString(uint32_t strfar, thrunger mode, byte id=0);
|
||||
uint16_t getOperand(byte n);
|
||||
|
||||
static bool diag;
|
||||
static const FLASH byte RouteCode[];
|
||||
static const FLASH int16_t SignalDefinitions[];
|
||||
static const HIGHFLASH byte RouteCode[];
|
||||
static const HIGHFLASH int16_t SignalDefinitions[];
|
||||
static byte flags[MAX_FLAGS];
|
||||
static LookList * sequenceLookup;
|
||||
static LookList * onThrowLookup;
|
||||
@@ -161,7 +181,8 @@ private:
|
||||
static LookList * onRedLookup;
|
||||
static LookList * onAmberLookup;
|
||||
static LookList * onGreenLookup;
|
||||
|
||||
static LookList * onChangeLookup;
|
||||
static LookList * onClockLookup;
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021-2022 Chris Harlow
|
||||
* © 2020,2021 Chris Harlow. All rights reserved.
|
||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||
* © 2023 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -28,6 +28,7 @@
|
||||
#undef AFTER
|
||||
#undef ALIAS
|
||||
#undef AMBER
|
||||
#undef ANOUT
|
||||
#undef AT
|
||||
#undef ATGTE
|
||||
#undef ATLT
|
||||
@@ -65,6 +66,7 @@
|
||||
#undef IFCLOSED
|
||||
#undef IFGREEN
|
||||
#undef IFGTE
|
||||
#undef IFLOCO
|
||||
#undef IFLT
|
||||
#undef IFNOT
|
||||
#undef IFRANDOM
|
||||
@@ -72,11 +74,13 @@
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef IFRE
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JOIN
|
||||
#undef KILLALL
|
||||
#undef LATCH
|
||||
#undef LCD
|
||||
#undef SCREEN
|
||||
#undef LCN
|
||||
#undef MOVETT
|
||||
#undef ONACTIVATE
|
||||
@@ -85,9 +89,12 @@
|
||||
#undef ONDEACTIVATE
|
||||
#undef ONDEACTIVATEL
|
||||
#undef ONCLOSE
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONTHROW
|
||||
#undef ONCHANGE
|
||||
#undef PARSE
|
||||
#undef PAUSE
|
||||
#undef PIN_TURNOUT
|
||||
@@ -110,6 +117,9 @@
|
||||
#undef SERIAL1
|
||||
#undef SERIAL2
|
||||
#undef SERIAL3
|
||||
#undef SERIAL4
|
||||
#undef SERIAL5
|
||||
#undef SERIAL6
|
||||
#undef SERVO
|
||||
#undef SERVO2
|
||||
#undef SERVO_TURNOUT
|
||||
@@ -138,6 +148,7 @@
|
||||
#define AFTER(sensor_id)
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id)
|
||||
#define ANOUT(vpin,value,param1,param2)
|
||||
#define AT(sensor_id)
|
||||
#define ATGTE(sensor_id,value)
|
||||
#define ATLT(sensor_id,value)
|
||||
@@ -175,6 +186,7 @@
|
||||
#define IFCLOSED(turnout_id)
|
||||
#define IFGREEN(signal_id)
|
||||
#define IFGTE(sensor_id,value)
|
||||
#define IFLOCO(loco_id)
|
||||
#define IFLT(sensor_id,value)
|
||||
#define IFNOT(sensor_id)
|
||||
#define IFRANDOM(percent)
|
||||
@@ -182,22 +194,27 @@
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define IFRE(sensor_id,value)
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
#define LATCH(sensor_id)
|
||||
#define LCD(row,msg)
|
||||
#define LCD(row,msg)
|
||||
#define SCREEN(display,row,msg)
|
||||
#define LCN(msg)
|
||||
#define MOVETT(id,steps,activity)
|
||||
#define ONACTIVATE(addr,subaddr)
|
||||
#define ONACTIVATEL(linear)
|
||||
#define ONAMBER(signal_id)
|
||||
#define ONTIME(value)
|
||||
#define ONCLOCKTIME(hours,mins)
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
#define ONGREEN(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONTHROW(turnout_id)
|
||||
#define ONCHANGE(sensor_id)
|
||||
#define PAUSE
|
||||
#define PIN_TURNOUT(id,pin,description...)
|
||||
#define PRINT(msg)
|
||||
@@ -220,6 +237,9 @@
|
||||
#define SERIAL1(msg)
|
||||
#define SERIAL2(msg)
|
||||
#define SERIAL3(msg)
|
||||
#define SERIAL4(msg)
|
||||
#define SERIAL5(msg)
|
||||
#define SERIAL6(msg)
|
||||
#define SERVO(id,position,profile)
|
||||
#define SERVO2(id,position,duration)
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -44,7 +45,7 @@
|
||||
// Descriptive texts for routes and animations are created in a sepaerate function which
|
||||
// can be called to emit a list of routes/automatuions in a form suitable for Withrottle.
|
||||
|
||||
// PRINT(msg) and LCD(row,msg) is implemented in a separate pass to create
|
||||
// PRINT(msg), LCD(row,msg) and SCREEN(display,row,msg) are implemented in a separate pass to create
|
||||
// a getMessageText(id) function.
|
||||
|
||||
// CAUTION: The macros below are multiple passed over myAutomation.h
|
||||
@@ -55,6 +56,10 @@
|
||||
// helper macro for turnout description as HIDDEN
|
||||
#define HIDDEN "\x01"
|
||||
|
||||
// helper macro to strip leading zeros off time inputs
|
||||
// (10#mins)%100)
|
||||
#define STRIP_ZERO(value) 10##value%100
|
||||
|
||||
// Pass 1 Implements aliases
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ALIAS
|
||||
@@ -73,14 +78,14 @@ void exrailHalSetup() {
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
#define ROUTE(id, description) id,
|
||||
const int16_t FLASH RMFT2::routeIdList[]= {
|
||||
const int16_t HIGHFLASH RMFT2::routeIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
// Pass 2a create throttle automation list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef AUTOMATION
|
||||
#define AUTOMATION(id, description) id,
|
||||
const int16_t FLASH RMFT2::automationIdList[]= {
|
||||
const int16_t HIGHFLASH RMFT2::automationIdList[]= {
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
|
||||
@@ -100,30 +105,63 @@ const FSH * RMFT2::getRouteDescription(int16_t id) {
|
||||
// Pass 4... Create Text sending functions
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
const int StringMacroTracker1=__COUNTER__;
|
||||
#define THRUNGE(msg,mode) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
static const char HIGHFLASH thrunge[]=msg;\
|
||||
strfar=(uint32_t)GETFARPTR(thrunge);\
|
||||
tmode=mode;\
|
||||
break;\
|
||||
}
|
||||
#undef BROADCAST
|
||||
#define BROADCAST(msg) case (__COUNTER__ - StringMacroTracker1) : CommandDistributor::broadcastText(F(msg));break;
|
||||
#define BROADCAST(msg) THRUNGE(msg,thrunge_broadcast)
|
||||
#undef PARSE
|
||||
#define PARSE(msg) case (__COUNTER__ - StringMacroTracker1) : DCCEXParser::parse(F(msg));break;
|
||||
#define PARSE(msg) THRUNGE(msg,thrunge_parse)
|
||||
#undef PRINT
|
||||
#define PRINT(msg) case (__COUNTER__ - StringMacroTracker1) : printMessage2(F(msg));break;
|
||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||
#undef LCN
|
||||
#define LCN(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&LCN_SERIAL,F(msg));break;
|
||||
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
|
||||
#undef SERIAL
|
||||
#define SERIAL(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial,F(msg));break;
|
||||
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
||||
#undef SERIAL1
|
||||
#define SERIAL1(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial1,F(msg));break;
|
||||
#define SERIAL1(msg) THRUNGE(msg,thrunge_serial1)
|
||||
#undef SERIAL2
|
||||
#define SERIAL2(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial2,F(msg));break;
|
||||
#define SERIAL2(msg) THRUNGE(msg,thrunge_serial2)
|
||||
#undef SERIAL3
|
||||
#define SERIAL3(msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::send(&Serial3,F(msg));break;
|
||||
#define SERIAL3(msg) THRUNGE(msg,thrunge_serial3)
|
||||
#undef SERIAL4
|
||||
#define SERIAL4(msg) THRUNGE(msg,thrunge_serial4)
|
||||
#undef SERIAL5
|
||||
#define SERIAL5(msg) THRUNGE(msg,thrunge_serial5)
|
||||
#undef SERIAL6
|
||||
#define SERIAL6(msg) THRUNGE(msg,thrunge_serial6)
|
||||
#undef LCD
|
||||
#define LCD(id,msg) case (__COUNTER__ - StringMacroTracker1) : StringFormatter::lcd(id,F(msg));break;
|
||||
#define LCD(id,msg) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
static const char HIGHFLASH thrunge[]=msg;\
|
||||
strfar=(uint32_t)GETFARPTR(thrunge);\
|
||||
tmode=thrunge_lcd; \
|
||||
lcdid=id;\
|
||||
break;\
|
||||
}
|
||||
#undef SCREEN
|
||||
#define SCREEN(display,id,msg) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
static const char HIGHFLASH thrunge[]=msg;\
|
||||
strfar=(uint32_t)GETFARPTR(thrunge);\
|
||||
tmode=(thrunger)(thrunge_lcd+display); \
|
||||
lcdid=id;\
|
||||
break;\
|
||||
}
|
||||
|
||||
void RMFT2::printMessage(uint16_t id) {
|
||||
thrunger tmode;
|
||||
uint32_t strfar=0;
|
||||
byte lcdid=0;
|
||||
switch(id) {
|
||||
#include "myAutomation.h"
|
||||
default: break ;
|
||||
}
|
||||
if (strfar) thrungeString(strfar,tmode,lcdid);
|
||||
}
|
||||
|
||||
|
||||
@@ -158,7 +196,7 @@ const byte RMFT2::rosterNameCount=0
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
#define ROSTER(cabid,name,funcmap...) cabid,
|
||||
const int16_t FLASH RMFT2::rosterIdList[]={
|
||||
const int16_t HIGHFLASH RMFT2::rosterIdList[]={
|
||||
#include "myAutomation.h"
|
||||
0};
|
||||
|
||||
@@ -198,7 +236,7 @@ const FSH * RMFT2::getRosterFunctions(int16_t id) {
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) id,0,0,0,
|
||||
|
||||
const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#include "myAutomation.h"
|
||||
0,0,0,0 };
|
||||
|
||||
@@ -217,6 +255,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||
#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2),
|
||||
#define AT(sensor_id) OPCODE_AT,V(sensor_id),
|
||||
#define ATGTE(sensor_id,value) OPCODE_ATGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define ATLT(sensor_id,value) OPCODE_ATLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
@@ -254,6 +293,7 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define IFCLOSED(turnout_id) OPCODE_IFCLOSED,V(turnout_id),
|
||||
#define IFGREEN(signal_id) OPCODE_IFGREEN,V(signal_id),
|
||||
#define IFGTE(sensor_id,value) OPCODE_IFGTE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFLOCO(loco_id) OPCODE_IFLOCO,V(loco_id),
|
||||
#define IFLT(sensor_id,value) OPCODE_IFLT,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define IFNOT(sensor_id) OPCODE_IFNOT,V(sensor_id),
|
||||
#define IFRANDOM(percent) OPCODE_IFRANDOM,V(percent),
|
||||
@@ -261,22 +301,27 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||
#define LCD(id,msg) PRINT(msg)
|
||||
#define SCREEN(display,id,msg) PRINT(msg)
|
||||
#define LCN(msg) PRINT(msg)
|
||||
#define MOVETT(id,steps,activity) OPCODE_SERVO,V(id),OPCODE_PAD,V(steps),OPCODE_PAD,V(EXTurntable::activity),OPCODE_PAD,V(0),
|
||||
#define ONACTIVATE(addr,subaddr) OPCODE_ONACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
||||
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(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),
|
||||
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
|
||||
#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 POM(cv,value) OPCODE_POM,V(cv),OPCODE_PAD,V(value),
|
||||
@@ -299,6 +344,9 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define SERIAL1(msg) PRINT(msg)
|
||||
#define SERIAL2(msg) PRINT(msg)
|
||||
#define SERIAL3(msg) PRINT(msg)
|
||||
#define SERIAL4(msg) PRINT(msg)
|
||||
#define SERIAL5(msg) PRINT(msg)
|
||||
#define SERIAL6(msg) PRINT(msg)
|
||||
#define SERVO(id,position,profile) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::profile),OPCODE_PAD,V(0),
|
||||
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
@@ -323,13 +371,15 @@ const FLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
|
||||
// Build RouteCode
|
||||
const int StringMacroTracker2=__COUNTER__;
|
||||
const FLASH byte RMFT2::RouteCode[] = {
|
||||
const HIGHFLASH byte RMFT2::RouteCode[] = {
|
||||
#include "myAutomation.h"
|
||||
OPCODE_ENDTASK,0,0,OPCODE_ENDEXRAIL,0,0 };
|
||||
|
||||
// Restore normal code LCD & SERIAL macro
|
||||
#undef LCD
|
||||
#define LCD StringFormatter::lcd
|
||||
#undef SCREEN
|
||||
#define SCREEN StringFormatter::lcd2
|
||||
#undef SERIAL
|
||||
#define SERIAL 0x0
|
||||
#endif
|
||||
|
@@ -60,14 +60,14 @@ EthernetInterface::EthernetInterface()
|
||||
connected=false;
|
||||
|
||||
#ifdef IP_ADDRESS
|
||||
if (Ethernet.begin(mac, IP_ADDRESS) == 0)
|
||||
Ethernet.begin(mac, IP_ADDRESS);
|
||||
#else
|
||||
if (Ethernet.begin(mac) == 0)
|
||||
#endif
|
||||
{
|
||||
DIAG(F("Ethernet.begin FAILED"));
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
if (Ethernet.hardwareStatus() == EthernetNoHardware) {
|
||||
DIAG(F("Ethernet shield not found or W5100"));
|
||||
}
|
||||
@@ -135,7 +135,10 @@ bool EthernetInterface::checkLink() {
|
||||
if(!connected) {
|
||||
DIAG(F("Ethernet cable connected"));
|
||||
connected=true;
|
||||
IPAddress ip = Ethernet.localIP(); // reassign the obtained ip address
|
||||
#ifdef IP_ADDRESS
|
||||
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
|
||||
server->begin();
|
||||
LCD(4,F("IP: %d.%d.%d.%d"), ip[0], ip[1], ip[2], ip[3]);
|
||||
|
74
FSH.h
74
FSH.h
@@ -34,42 +34,60 @@
|
||||
* PROGMEM use FLASH instead
|
||||
* pgm_read_byte_near use GETFLASH instead.
|
||||
* pgm_read_word_near use GETFLASHW instead.
|
||||
*
|
||||
* Also:
|
||||
* HIGHFLASH - PROGMEM forced to end of link so needs far pointers.
|
||||
* GETHIGHFLASH,GETHIGHFLASHW to access them
|
||||
*
|
||||
*/
|
||||
#include <Arduino.h>
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
// AVR devices have flash memory mapped differently
|
||||
// progmem can be accessed by _near functions or _far
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define FLASH PROGMEM
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
#define STRCPY_P strcpy_P
|
||||
#define STRCMP_P strcmp_P
|
||||
#define STRNCPY_P strncpy_P
|
||||
#define STRNCMP_P strncmp_P
|
||||
#define STRLEN_P strlen_P
|
||||
|
||||
#if defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#if defined(ARDUINO_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
// AVR_MEGA memory deliberately placed at end of link may need _far functions
|
||||
#define HIGHFLASH __attribute__((section(".fini2")))
|
||||
#define GETFARPTR(data) pgm_get_far_address(data)
|
||||
#define GETHIGHFLASH(data,offset) pgm_read_byte_far(GETFARPTR(data)+offset)
|
||||
#define GETHIGHFLASHW(data,offset) pgm_read_word_far(GETFARPTR(data)+offset)
|
||||
#else
|
||||
// AVR_UNO/NANO runtime does not support _far functions so just use _near equivalent
|
||||
// as there is no progmem above 32kb anyway.
|
||||
#define HIGHFLASH PROGMEM
|
||||
#define GETFARPTR(data) ((uint32_t)(data))
|
||||
#define GETHIGHFLASH(data,offset) pgm_read_byte_near(GETFARPTR(data)+(offset))
|
||||
#define GETHIGHFLASHW(data,offset) pgm_read_word_near(GETFARPTR(data)+(offset))
|
||||
#endif
|
||||
|
||||
#else
|
||||
// Non-AVR Flat-memory devices have no need of this support so can be remapped to normal memory access
|
||||
#ifdef F
|
||||
#undef F
|
||||
#endif
|
||||
#define F(str) (str)
|
||||
typedef char FSH;
|
||||
#define GETFLASH(addr) (*(const unsigned char *)(addr))
|
||||
#define GETFLASHW(addr) (*(const unsigned short *)(addr))
|
||||
#define FLASH
|
||||
#define strlen_P strlen
|
||||
#define strcpy_P strcpy
|
||||
|
||||
#elif defined(ARDUINO_ARCH_SAMD) || defined(ARDUINO_ARCH_STM32)
|
||||
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte(addr)
|
||||
// pgm_read_word is buggy if addr is odd but here
|
||||
// we do only read well aligned addrs, the others are
|
||||
// taken care about in the GET_OPERAND(n) macro in EXRAIL2.cpp.
|
||||
#define GETFLASHW(addr) pgm_read_word(addr)
|
||||
#ifdef FLASH
|
||||
#undef FLASH
|
||||
#endif
|
||||
#define FLASH PROGMEM
|
||||
|
||||
#else // AVR and AVR compat here
|
||||
|
||||
typedef __FlashStringHelper FSH;
|
||||
#define GETFLASH(addr) pgm_read_byte_near(addr)
|
||||
#define GETFLASHW(addr) pgm_read_word_near(addr)
|
||||
#define FLASH PROGMEM
|
||||
|
||||
#endif // flash stuff
|
||||
#endif // FSH
|
||||
#define F(str) (str)
|
||||
typedef char FSH;
|
||||
#define FLASH
|
||||
#define HIGHFLASH
|
||||
#define GETFARPTR(data) ((uint32_t)(data))
|
||||
#define GETFLASH(addr) (*(const byte *)(addr))
|
||||
#define GETHIGHFLASH(data,offset) (*(const byte *)(GETFARPTR(data)+offset))
|
||||
#define GETHIGHFLASHW(data,offset) (*(const uint16_t *)(GETFARPTR(data)+offset))
|
||||
#define STRCPY_P strcpy
|
||||
#define STRCMP_P strcmp
|
||||
#define STRNCPY_P strncpy
|
||||
#define STRNCMP_P strncmp
|
||||
#define STRLEN_P strlen
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202211181919Z"
|
||||
#define GITHUB_SHA "devel-202303101548Z"
|
||||
|
202
I2CManager.cpp
202
I2CManager.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -35,28 +35,125 @@
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#include "I2CManager_NonBlocking.h"
|
||||
#include "I2CManager_SAMD.h" // SAMD21 for now... SAMD51 as well later
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#include "I2CManager_NonBlocking.h"
|
||||
#include "I2CManager_STM32.h" // STM32F411RE for now... more later
|
||||
#else
|
||||
#define I2C_USE_WIRE
|
||||
#include "I2CManager_Wire.h" // Other platforms
|
||||
#endif
|
||||
|
||||
|
||||
// Helper function for listing device types
|
||||
static const FSH * guessI2CDeviceType(uint8_t address) {
|
||||
if (address >= 0x20 && address <= 0x26)
|
||||
return F("GPIO Expander");
|
||||
else if (address == 0x27)
|
||||
return F("GPIO Expander or LCD Display");
|
||||
else if (address == 0x29)
|
||||
return F("Time-of-flight sensor");
|
||||
else if (address >= 0x3c && address <= 0x3d)
|
||||
return F("OLED Display");
|
||||
else if (address >= 0x48 && address <= 0x4f)
|
||||
return F("Analogue Inputs or PWM");
|
||||
else if (address >= 0x40 && address <= 0x4f)
|
||||
return F("PWM");
|
||||
else if (address >= 0x50 && address <= 0x5f)
|
||||
return F("EEPROM");
|
||||
else if (address == 0x68)
|
||||
return F("Real-time clock");
|
||||
else if (address >= 0x70 && address <= 0x77)
|
||||
return F("I2C Mux");
|
||||
else
|
||||
return F("?");
|
||||
}
|
||||
|
||||
// If not already initialised, initialise I2C
|
||||
void I2CManagerClass::begin(void) {
|
||||
//setTimeout(25000); // 25 millisecond timeout
|
||||
if (!_beginCompleted) {
|
||||
_beginCompleted = true;
|
||||
|
||||
// Check for short-circuit or floating lines (no pull-up) on I2C before enabling I2C
|
||||
const FSH *message = F("WARNING: Possible short-circuit or inadequate pullup on I2C %S line");
|
||||
pinMode(SDA, INPUT);
|
||||
if (!digitalRead(SDA))
|
||||
DIAG(message, F("SDA"));
|
||||
pinMode(SCL, INPUT);
|
||||
if (!digitalRead(SCL))
|
||||
DIAG(message, F("SCL"));
|
||||
|
||||
// Now initialise I2C
|
||||
_initialise();
|
||||
|
||||
// Probe and list devices.
|
||||
#if defined(I2C_USE_WIRE)
|
||||
DIAG(F("I2CManager: Using Wire library"));
|
||||
#endif
|
||||
|
||||
// Probe and list devices. Use standard mode
|
||||
// (clock speed 100kHz) for best device compatibility.
|
||||
_setClock(100000);
|
||||
unsigned long originalTimeout = _timeout;
|
||||
setTimeout(1000); // use 1ms timeout for probes
|
||||
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
// First count the multiplexers and switch off all subbuses
|
||||
_muxCount = 0;
|
||||
for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) {
|
||||
if (I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None})==I2C_STATUS_OK)
|
||||
_muxCount++;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Enumerate devices that are visible
|
||||
bool found = false;
|
||||
for (byte addr=1; addr<127; addr++) {
|
||||
for (uint8_t addr=0x08; addr<0x78; addr++) {
|
||||
if (exists(addr)) {
|
||||
found = true;
|
||||
DIAG(F("I2C Device found at x%x"), addr);
|
||||
DIAG(F("I2C Device found at 0x%x, %S?"), addr, guessI2CDeviceType(addr));
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
// Enumerate all I2C devices that are connected via multiplexer,
|
||||
// i.e. that respond when only one multiplexer has one subBus enabled
|
||||
// and the device doesn't respond when the mux subBus is disabled.
|
||||
// If any probes time out, then assume that the subbus is dead and
|
||||
// don't do any more on that subbus.
|
||||
for (uint8_t muxNo=I2CMux_0; muxNo <= I2CMux_7; muxNo++) {
|
||||
uint8_t muxAddr = I2C_MUX_BASE_ADDRESS + muxNo;
|
||||
if (exists(muxAddr)) {
|
||||
// Select Mux Subbus
|
||||
for (uint8_t subBus=0; subBus<=SubBus_No; subBus++) {
|
||||
muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus});
|
||||
for (uint8_t addr=0x08; addr<0x78; addr++) {
|
||||
uint8_t status = checkAddress(addr);
|
||||
if (status == I2C_STATUS_OK) {
|
||||
// De-select subbus
|
||||
muxSelectSubBus({(I2CMux)muxNo, SubBus_None});
|
||||
if (!exists(addr)) {
|
||||
// Device responds when subbus selected but not when
|
||||
// subbus disabled - ergo it must be on subbus!
|
||||
found = true;
|
||||
DIAG(F("I2C Device found at {I2CMux_%d,SubBus_%d,0x%x}, %S?"),
|
||||
muxNo, subBus, addr, guessI2CDeviceType(addr));
|
||||
}
|
||||
// Re-select subbus
|
||||
muxSelectSubBus({(I2CMux)muxNo, (I2CSubBus)subBus});
|
||||
} else if (status == I2C_STATUS_TIMEOUT) {
|
||||
// Bus stuck, skip to next one.
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Deselect all subBuses for this mux. Otherwise its devices will continue to
|
||||
// respond when other muxes are being probed.
|
||||
I2CManager.muxSelectSubBus({(I2CMux)muxNo, SubBus_None}); // Deselect Mux
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (!found) DIAG(F("No I2C Devices found"));
|
||||
_setClock(_clockSpeed);
|
||||
setTimeout(originalTimeout); // set timeout back to original
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,31 +162,35 @@ void I2CManagerClass::begin(void) {
|
||||
void I2CManagerClass::setClock(uint32_t speed) {
|
||||
if (speed < _clockSpeed && !_clockSpeedFixed) {
|
||||
_clockSpeed = speed;
|
||||
DIAG(F("I2C clock speed set to %l Hz"), _clockSpeed);
|
||||
}
|
||||
_setClock(_clockSpeed);
|
||||
}
|
||||
|
||||
// Force clock speed to that specified. It can then only
|
||||
// be overridden by calling Wire.setClock directly.
|
||||
// Force clock speed to that specified.
|
||||
void I2CManagerClass::forceClock(uint32_t speed) {
|
||||
if (!_clockSpeedFixed) {
|
||||
_clockSpeed = speed;
|
||||
_clockSpeedFixed = true;
|
||||
_setClock(_clockSpeed);
|
||||
}
|
||||
_clockSpeed = speed;
|
||||
_clockSpeedFixed = true;
|
||||
_setClock(_clockSpeed);
|
||||
DIAG(F("I2C clock speed forced to %l Hz"), _clockSpeed);
|
||||
}
|
||||
|
||||
// Check if specified I2C address is responding (blocking operation)
|
||||
// Returns I2C_STATUS_OK (0) if OK, or error code.
|
||||
uint8_t I2CManagerClass::checkAddress(uint8_t address) {
|
||||
return write(address, NULL, 0);
|
||||
// Suppress retries. If it doesn't respond first time it's out of the running.
|
||||
uint8_t I2CManagerClass::checkAddress(I2CAddress address) {
|
||||
I2CRB rb;
|
||||
rb.setWriteParams(address, NULL, 0);
|
||||
rb.suppressRetries(true);
|
||||
queueRequest(&rb);
|
||||
return rb.wait();
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Write a transmission to I2C using a list of data (blocking operation)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::write(uint8_t address, uint8_t nBytes, ...) {
|
||||
uint8_t I2CManagerClass::write(I2CAddress address, uint8_t nBytes, ...) {
|
||||
uint8_t buffer[nBytes];
|
||||
va_list args;
|
||||
va_start(args, nBytes);
|
||||
@@ -102,7 +203,7 @@ uint8_t I2CManagerClass::write(uint8_t address, uint8_t nBytes, ...) {
|
||||
/***************************************************************************
|
||||
* Initiate a write to an I2C device (blocking operation)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) {
|
||||
uint8_t I2CManagerClass::write(I2CAddress i2cAddress, const uint8_t writeBuffer[], uint8_t writeLen) {
|
||||
I2CRB req;
|
||||
uint8_t status = write(i2cAddress, writeBuffer, writeLen, &req);
|
||||
return finishRB(&req, status);
|
||||
@@ -111,7 +212,7 @@ uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t writeBuffer[],
|
||||
/***************************************************************************
|
||||
* Initiate a write from PROGMEM (flash) to an I2C device (blocking operation)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * data, uint8_t dataLen) {
|
||||
uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * data, uint8_t dataLen) {
|
||||
I2CRB req;
|
||||
uint8_t status = write_P(i2cAddress, data, dataLen, &req);
|
||||
return finishRB(&req, status);
|
||||
@@ -120,7 +221,7 @@ uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * data, uint8
|
||||
/***************************************************************************
|
||||
* Initiate a write (optional) followed by a read from the I2C device (blocking operation)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen,
|
||||
uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
|
||||
const uint8_t *writeBuffer, uint8_t writeLen)
|
||||
{
|
||||
I2CRB req;
|
||||
@@ -131,7 +232,7 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r
|
||||
/***************************************************************************
|
||||
* Overload of read() to allow command to be specified as a series of bytes (blocking operation)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t writeSize, ...) {
|
||||
va_list args;
|
||||
// Copy the series of bytes into an array.
|
||||
@@ -162,7 +263,7 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) {
|
||||
case I2C_STATUS_NEGATIVE_ACKNOWLEDGE: return F("No response from device (address NAK)");
|
||||
case I2C_STATUS_TRANSMIT_ERROR: return F("Transmit error (data NAK)");
|
||||
case I2C_STATUS_OTHER_TWI_ERROR: return F("Other Wire/TWI error");
|
||||
case I2C_STATUS_TIMEOUT: return F("Timeout");
|
||||
case I2C_STATUS_TIMEOUT: return F("I2C bus timeout");
|
||||
case I2C_STATUS_ARBITRATION_LOST: return F("Arbitration lost");
|
||||
case I2C_STATUS_BUS_ERROR: return F("I2C bus error");
|
||||
case I2C_STATUS_UNEXPECTED_ERROR: return F("Unexpected error");
|
||||
@@ -176,46 +277,40 @@ const FSH *I2CManagerClass::getErrorMessage(uint8_t status) {
|
||||
***************************************************************************/
|
||||
I2CManagerClass I2CManager = I2CManagerClass();
|
||||
|
||||
// Buffer for conversion of I2CAddress to char*.
|
||||
/* static */ char I2CAddress::addressBuffer[30];
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Helper functions associated with I2C Request Block
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/***************************************************************************
|
||||
* Block waiting for request block to complete, and return completion status.
|
||||
* Since such a loop could potentially last for ever if the RB status doesn't
|
||||
* change, we set a high limit (1sec, 1000ms) on the wait time and, if it
|
||||
* hasn't changed by that time we assume it's not going to, and just return
|
||||
* a timeout status. This means that CS will not lock up.
|
||||
* Block waiting for request to complete, and return completion status.
|
||||
* Timeout monitoring is performed in the I2CManager.loop() function.
|
||||
***************************************************************************/
|
||||
uint8_t I2CRB::wait() {
|
||||
unsigned long waitStart = millis();
|
||||
do {
|
||||
while (status==I2C_STATUS_PENDING) {
|
||||
I2CManager.loop();
|
||||
// Rather than looping indefinitely, let's set a very high timeout (1s).
|
||||
if ((millis() - waitStart) > 1000UL) {
|
||||
DIAG(F("I2C TIMEOUT I2C:x%x I2CRB:x%x"), i2cAddress, this);
|
||||
status = I2C_STATUS_TIMEOUT;
|
||||
// Note that, although the timeout is posted, the request may yet complete.
|
||||
// TODO: Ideally we would like to cancel the request.
|
||||
return status;
|
||||
}
|
||||
} while (status==I2C_STATUS_PENDING);
|
||||
};
|
||||
return status;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Check whether request is still in progress.
|
||||
* Timeout monitoring is performed in the I2CManager.loop() function.
|
||||
***************************************************************************/
|
||||
bool I2CRB::isBusy() {
|
||||
I2CManager.loop();
|
||||
return (status==I2C_STATUS_PENDING);
|
||||
if (status==I2C_STATUS_PENDING) {
|
||||
I2CManager.loop();
|
||||
return true;
|
||||
} else
|
||||
return false;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Helper functions to fill the I2CRequest structure with parameters.
|
||||
***************************************************************************/
|
||||
void I2CRB::setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen) {
|
||||
void I2CRB::setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen) {
|
||||
this->i2cAddress = i2cAddress;
|
||||
this->writeLen = 0;
|
||||
this->readBuffer = readBuffer;
|
||||
@@ -224,7 +319,7 @@ void I2CRB::setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readL
|
||||
this->status = I2C_STATUS_OK;
|
||||
}
|
||||
|
||||
void I2CRB::setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen,
|
||||
void I2CRB::setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
|
||||
const uint8_t *writeBuffer, uint8_t writeLen) {
|
||||
this->i2cAddress = i2cAddress;
|
||||
this->writeBuffer = writeBuffer;
|
||||
@@ -235,7 +330,7 @@ void I2CRB::setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t re
|
||||
this->status = I2C_STATUS_OK;
|
||||
}
|
||||
|
||||
void I2CRB::setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) {
|
||||
void I2CRB::setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen) {
|
||||
this->i2cAddress = i2cAddress;
|
||||
this->writeBuffer = writeBuffer;
|
||||
this->writeLen = writeLen;
|
||||
@@ -244,3 +339,28 @@ void I2CRB::setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8
|
||||
this->status = I2C_STATUS_OK;
|
||||
}
|
||||
|
||||
void I2CRB::suppressRetries(bool suppress) {
|
||||
if (suppress)
|
||||
this->operation |= OPERATION_NORETRY;
|
||||
else
|
||||
this->operation &= ~OPERATION_NORETRY;
|
||||
}
|
||||
|
||||
|
||||
// Helper function for converting a uint8_t to four characters (e.g. 0x23).
|
||||
void I2CAddress::toHex(const uint8_t value, char *buffer) {
|
||||
char *ptr = buffer;
|
||||
// Just display hex value, two digits.
|
||||
*ptr++ = '0';
|
||||
*ptr++ = 'x';
|
||||
uint8_t bits = (value >> 4) & 0xf;
|
||||
*ptr++ = bits > 9 ? bits-10+'a' : bits+'0';
|
||||
bits = value & 0xf;
|
||||
*ptr++ = bits > 9 ? bits-10+'a' : bits+'0';
|
||||
}
|
||||
|
||||
#if !defined(I2C_EXTENDED_ADDRESS)
|
||||
|
||||
/* static */ bool I2CAddress::_addressWarningDone = false;
|
||||
|
||||
#endif
|
384
I2CManager.h
384
I2CManager.h
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -23,13 +23,15 @@
|
||||
|
||||
#include <inttypes.h>
|
||||
#include "FSH.h"
|
||||
#include "defines.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
/*
|
||||
* Manager for I2C communications. For portability, it allows use
|
||||
* of the Wire class, but also has a native implementation for AVR
|
||||
* which supports non-blocking queued I/O requests.
|
||||
*
|
||||
* Helps to avoid calling Wire.begin() multiple times (which is not)
|
||||
* Helps to avoid calling Wire.begin() multiple times (which is not
|
||||
* entirely benign as it reinitialises).
|
||||
*
|
||||
* Also helps to avoid the Wire clock from being set, by another device
|
||||
@@ -76,12 +78,12 @@
|
||||
* Timeout monitoring is possible, but requires that the following call is made
|
||||
* reasonably frequently in the program's loop() function:
|
||||
* I2CManager.loop();
|
||||
* So that the application doesn't need to do this explicitly, this call is performed
|
||||
* from the I2CRB::isBusy() or I2CRB::wait() functions.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* 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
|
||||
@@ -94,15 +96,10 @@
|
||||
* 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:
|
||||
*
|
||||
* I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state
|
||||
* Non-blocking I2C may be operated without interrupts (undefine I2C_USE_INTERRUPTS). Instead, the I2C state
|
||||
* machine handler, currently invoked from the interrupt service routine, is invoked from the loop() function.
|
||||
* The speed at which I2C operations can be performed then becomes highly dependent on the frequency that
|
||||
* the loop() function is called, and may be adequate under some circumstances.
|
||||
@@ -111,6 +108,13 @@
|
||||
*
|
||||
*/
|
||||
|
||||
// Maximum number of retries on an I2C operation.
|
||||
// A value of zero will disable retries.
|
||||
// Maximum value is 254 (unsigned byte counter)
|
||||
// Note that timeout failures are not retried, but any timeout
|
||||
// configured applies to each try separately.
|
||||
#define MAX_I2C_RETRIES 2
|
||||
|
||||
// Add following line to config.h to enable Wire library instead of native I2C drivers
|
||||
//#define I2C_USE_WIRE
|
||||
|
||||
@@ -122,6 +126,233 @@
|
||||
#define I2C_USE_INTERRUPTS
|
||||
#endif
|
||||
|
||||
// I2C Extended Address support I2C Multiplexers and allows various properties to be
|
||||
// associated with an I2C address such as the MUX and SubBus. In the future, this
|
||||
// may be extended to include multiple buses, and other features.
|
||||
// Uncomment to enable extended address.
|
||||
//
|
||||
|
||||
//#define I2C_EXTENDED_ADDRESS
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Extended I2C Address type to facilitate extended I2C addresses including
|
||||
// I2C multiplexer support.
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Currently only one bus supported, and one instance of I2CManager to handle it.
|
||||
enum I2CBus : uint8_t {
|
||||
I2CBus_0 = 0,
|
||||
};
|
||||
|
||||
// Currently I2CAddress supports one I2C bus, with up to eight
|
||||
// multipexers (MUX) attached. Each MUX can have up to eight sub-buses.
|
||||
enum I2CMux : uint8_t {
|
||||
I2CMux_0 = 0,
|
||||
I2CMux_1 = 1,
|
||||
I2CMux_2 = 2,
|
||||
I2CMux_3 = 3,
|
||||
I2CMux_4 = 4,
|
||||
I2CMux_5 = 5,
|
||||
I2CMux_6 = 6,
|
||||
I2CMux_7 = 7,
|
||||
I2CMux_None = 255, // Address doesn't need mux switching
|
||||
};
|
||||
|
||||
enum I2CSubBus : uint8_t {
|
||||
SubBus_0 = 0, // Enable individual sub-buses...
|
||||
SubBus_1 = 1,
|
||||
#if !defined(I2CMUX_PCA9542)
|
||||
SubBus_2 = 2,
|
||||
SubBus_3 = 3,
|
||||
#if !defined(I2CMUX_PCA9544)
|
||||
SubBus_4 = 4,
|
||||
SubBus_5 = 5,
|
||||
SubBus_6 = 6,
|
||||
SubBus_7 = 7,
|
||||
#endif
|
||||
#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 (not supported by some multiplexers)
|
||||
};
|
||||
|
||||
// Type to hold I2C address
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
|
||||
// First MUX address (they range between 0x70-0x77).
|
||||
#define I2C_MUX_BASE_ADDRESS 0x70
|
||||
|
||||
// Currently I2C address supports one I2C bus, with up to eight
|
||||
// multiplexers (MUX) attached. Each MUX can have up to eight sub-buses.
|
||||
// This structure could be extended in the future (if there is a need)
|
||||
// to support 10-bit I2C addresses, different I2C clock speed for each
|
||||
// sub-bus, multiple I2C buses, and other features not yet thought of.
|
||||
struct I2CAddress {
|
||||
private:
|
||||
// Fields
|
||||
I2CBus _busNumber;
|
||||
I2CMux _muxNumber;
|
||||
I2CSubBus _subBus;
|
||||
uint8_t _deviceAddress;
|
||||
static char addressBuffer[];
|
||||
public:
|
||||
// Constructors
|
||||
// For I2CAddress "{I2CBus_0, Mux_0, SubBus_0, 0x23}" syntax.
|
||||
I2CAddress(const I2CBus busNumber, const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) {
|
||||
_busNumber = busNumber;
|
||||
_muxNumber = muxNumber;
|
||||
_subBus = subBus;
|
||||
_deviceAddress = deviceAddress;
|
||||
}
|
||||
|
||||
// Basic constructor
|
||||
I2CAddress() : I2CAddress(I2CMux_None, SubBus_None, 0) {}
|
||||
|
||||
// For I2CAddress "{Mux_0, SubBus_0, 0x23}" syntax.
|
||||
I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus, const uint8_t deviceAddress) :
|
||||
I2CAddress(I2CBus_0, muxNumber, subBus, deviceAddress) {}
|
||||
|
||||
// For I2CAddress in form "{SubBus_0, 0x23}" - assume Mux0 (0x70)
|
||||
I2CAddress(I2CSubBus subBus, uint8_t deviceAddress) :
|
||||
I2CAddress(I2CMux_0, subBus, deviceAddress) {}
|
||||
|
||||
// Conversion from uint8_t to I2CAddress
|
||||
// For I2CAddress in form "0x23"
|
||||
// (device assumed to be on the main I2C bus).
|
||||
I2CAddress(const uint8_t deviceAddress) :
|
||||
I2CAddress(I2CMux_None, SubBus_None, deviceAddress) {}
|
||||
|
||||
// Conversion from uint8_t to I2CAddress
|
||||
// For I2CAddress in form "{I2CBus_1, 0x23}"
|
||||
// (device not connected via multiplexer).
|
||||
I2CAddress(const I2CBus bus, const uint8_t deviceAddress) :
|
||||
I2CAddress(bus, I2CMux_None, SubBus_None, deviceAddress) {}
|
||||
|
||||
// For I2CAddress in form "{I2CMux_0, SubBus_0}" (mux selector)
|
||||
I2CAddress(const I2CMux muxNumber, const I2CSubBus subBus) :
|
||||
I2CAddress(muxNumber, subBus, 0x00) {}
|
||||
|
||||
// For I2CAddress in form "{i2cAddress, deviceAddress}"
|
||||
// where deviceAddress is to be on the same subbus as i2cAddress.
|
||||
I2CAddress(I2CAddress firstAddress, uint8_t newDeviceAddress) :
|
||||
I2CAddress(firstAddress._muxNumber, firstAddress._subBus, newDeviceAddress) {}
|
||||
|
||||
// Conversion operator from I2CAddress to uint8_t
|
||||
// For "uint8_t address = i2cAddress;" syntax
|
||||
// (device assumed to be on the main I2C bus or on a currently selected subbus.
|
||||
operator uint8_t () const { return _deviceAddress; }
|
||||
|
||||
// Conversion from I2CAddress to char* (uses static storage so only
|
||||
// one conversion can be done at a time). So don't call it twice in a
|
||||
// single DIAG statement for example.
|
||||
const char* toString() {
|
||||
char *ptr = addressBuffer;
|
||||
if (_muxNumber != I2CMux_None) {
|
||||
strcpy_P(ptr, (const char*)F("{I2CMux_"));
|
||||
ptr += 8;
|
||||
*ptr++ = '0' + _muxNumber;
|
||||
strcpy_P(ptr, (const char*)F(",Subbus_"));
|
||||
ptr += 8;
|
||||
if (_subBus == SubBus_None) {
|
||||
strcpy_P(ptr, (const char*)F("None"));
|
||||
ptr += 4;
|
||||
} else if (_subBus == SubBus_All) {
|
||||
strcpy_P(ptr, (const char*)F("All"));
|
||||
ptr += 3;
|
||||
} else
|
||||
*ptr++ = '0' + _subBus;
|
||||
*ptr++ = ',';
|
||||
}
|
||||
toHex(_deviceAddress, ptr);
|
||||
ptr += 4;
|
||||
if (_muxNumber != I2CMux_None)
|
||||
*ptr++ = '}';
|
||||
*ptr = 0; // terminate string
|
||||
return addressBuffer;
|
||||
}
|
||||
|
||||
// Comparison operator
|
||||
int operator == (I2CAddress &a) const {
|
||||
if (_deviceAddress != a._deviceAddress)
|
||||
return false; // Different device address so no match
|
||||
if (_muxNumber == I2CMux_None || a._muxNumber == I2CMux_None)
|
||||
return true; // Same device address, one or other on main bus
|
||||
if (_subBus == SubBus_None || a._subBus == SubBus_None)
|
||||
return true; // Same device address, one or other on main bus
|
||||
if (_muxNumber != a._muxNumber)
|
||||
return false; // Connected to a subbus on a different mux
|
||||
if (_subBus != a._subBus)
|
||||
return false; // different subbus
|
||||
return true; // Same address on same mux and same subbus
|
||||
}
|
||||
// Field accessors
|
||||
I2CMux muxNumber() { return _muxNumber; }
|
||||
I2CSubBus subBus() { return _subBus; }
|
||||
uint8_t deviceAddress() { return _deviceAddress; }
|
||||
|
||||
private:
|
||||
// Helper function for converting byte to four-character hex string (e.g. 0x23).
|
||||
void toHex(const uint8_t value, char *buffer);
|
||||
};
|
||||
|
||||
#else
|
||||
struct I2CAddress {
|
||||
private:
|
||||
uint8_t _deviceAddress;
|
||||
static char addressBuffer[];
|
||||
public:
|
||||
// Constructors
|
||||
I2CAddress(const uint8_t deviceAddress) {
|
||||
_deviceAddress = deviceAddress;
|
||||
}
|
||||
I2CAddress(I2CMux, I2CSubBus, const uint8_t deviceAddress) {
|
||||
addressWarning();
|
||||
_deviceAddress = deviceAddress;
|
||||
}
|
||||
I2CAddress(I2CSubBus, const uint8_t deviceAddress) {
|
||||
addressWarning();
|
||||
_deviceAddress = deviceAddress;
|
||||
}
|
||||
|
||||
// Basic constructor
|
||||
I2CAddress() : I2CAddress(0) {}
|
||||
|
||||
// Conversion operator from I2CAddress to uint8_t
|
||||
// For "uint8_t address = i2cAddress;" syntax
|
||||
operator uint8_t () const { return _deviceAddress; }
|
||||
|
||||
// Conversion from I2CAddress to char* (uses static storage so only
|
||||
// one conversion can be done at a time). So don't call it twice in a
|
||||
// single DIAG statement for example.
|
||||
const char* toString () {
|
||||
char *ptr = addressBuffer;
|
||||
// Just display hex value, two digits.
|
||||
toHex(_deviceAddress, ptr);
|
||||
ptr += 4;
|
||||
*ptr = 0; // terminate string
|
||||
return addressBuffer;
|
||||
}
|
||||
|
||||
// Comparison operator
|
||||
int operator == (I2CAddress &a) const {
|
||||
if (_deviceAddress != a._deviceAddress)
|
||||
return false; // Different device address so no match
|
||||
return true; // Same address on same mux and same subbus
|
||||
}
|
||||
private:
|
||||
// Helper function for converting byte to four-character hex string (e.g. 0x23).
|
||||
void toHex(const uint8_t value, char *buffer);
|
||||
void addressWarning() {
|
||||
if (!_addressWarningDone) {
|
||||
DIAG(F("WARNIING: Extended I2C address used but not supported in this configuration"));
|
||||
_addressWarningDone = true;
|
||||
}
|
||||
}
|
||||
static bool _addressWarningDone;
|
||||
};
|
||||
#endif // I2C_EXTENDED_ADDRESS
|
||||
|
||||
|
||||
// Status codes for I2CRB structures.
|
||||
enum : uint8_t {
|
||||
// Codes used by Wire and by native drivers
|
||||
@@ -144,6 +375,7 @@ enum : uint8_t {
|
||||
I2C_STATE_ACTIVE=253,
|
||||
I2C_STATE_FREE=254,
|
||||
I2C_STATE_CLOSING=255,
|
||||
I2C_STATE_COMPLETED=252,
|
||||
};
|
||||
|
||||
typedef enum : uint8_t
|
||||
@@ -152,6 +384,8 @@ typedef enum : uint8_t
|
||||
OPERATION_REQUEST = 2,
|
||||
OPERATION_SEND = 3,
|
||||
OPERATION_SEND_P = 4,
|
||||
OPERATION_NORETRY = 0x80, // OR with operation to suppress retries.
|
||||
OPERATION_MASK = 0x7f, // mask for extracting the operation code
|
||||
} OperationEnum;
|
||||
|
||||
|
||||
@@ -170,18 +404,19 @@ public:
|
||||
uint8_t wait();
|
||||
bool isBusy();
|
||||
|
||||
void setReadParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen);
|
||||
void setRequestParams(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen);
|
||||
void setWriteParams(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen);
|
||||
void setReadParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen);
|
||||
void setRequestParams(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen, const uint8_t *writeBuffer, uint8_t writeLen);
|
||||
void setWriteParams(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen);
|
||||
void suppressRetries(bool suppress);
|
||||
|
||||
uint8_t writeLen;
|
||||
uint8_t readLen;
|
||||
uint8_t operation;
|
||||
uint8_t i2cAddress;
|
||||
I2CAddress i2cAddress;
|
||||
uint8_t *readBuffer;
|
||||
const uint8_t *writeBuffer;
|
||||
#if !defined(I2C_USE_WIRE)
|
||||
I2CRB *nextRequest;
|
||||
I2CRB *nextRequest; // Used by non-blocking devices for I2CRB queue management.
|
||||
#endif
|
||||
};
|
||||
|
||||
@@ -195,26 +430,33 @@ public:
|
||||
void setClock(uint32_t speed);
|
||||
// Force clock speed
|
||||
void forceClock(uint32_t speed);
|
||||
// setTimeout sets the timout value for I2C transactions (milliseconds).
|
||||
void setTimeout(unsigned long);
|
||||
// Check if specified I2C address is responding.
|
||||
uint8_t checkAddress(uint8_t address);
|
||||
inline bool exists(uint8_t address) {
|
||||
uint8_t checkAddress(I2CAddress address);
|
||||
inline bool exists(I2CAddress address) {
|
||||
return checkAddress(address)==I2C_STATUS_OK;
|
||||
}
|
||||
// Select/deselect Mux Sub-Bus (if using legacy addresses, just checks address)
|
||||
// E.g. muxSelectSubBus({I2CMux_0, SubBus_3});
|
||||
uint8_t muxSelectSubBus(I2CAddress address) {
|
||||
return checkAddress(address);
|
||||
}
|
||||
// Write a complete transmission to I2C from an array in RAM
|
||||
uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size);
|
||||
uint8_t write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb);
|
||||
uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size);
|
||||
uint8_t write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb);
|
||||
// Write a complete transmission to I2C from an array in Flash
|
||||
uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size);
|
||||
uint8_t write_P(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb);
|
||||
uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size);
|
||||
uint8_t write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb);
|
||||
// Write a transmission to I2C from a list of bytes.
|
||||
uint8_t write(uint8_t address, uint8_t nBytes, ...);
|
||||
uint8_t write(I2CAddress address, uint8_t nBytes, ...);
|
||||
// Write a command from an array in RAM and read response
|
||||
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
|
||||
const uint8_t writeBuffer[]=NULL, uint8_t writeSize=0);
|
||||
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
|
||||
const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb);
|
||||
// Write a command from an arbitrary list of bytes and read response
|
||||
uint8_t read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t writeSize, ...);
|
||||
void queueRequest(I2CRB *req);
|
||||
|
||||
@@ -231,18 +473,37 @@ public:
|
||||
private:
|
||||
bool _beginCompleted = false;
|
||||
bool _clockSpeedFixed = false;
|
||||
#if defined(__arm__)
|
||||
uint32_t _clockSpeed = 32000000L; // 3.2MHz max on SAMD and STM32
|
||||
#else
|
||||
uint32_t _clockSpeed = 400000L; // 400kHz max on Arduino.
|
||||
#endif
|
||||
|
||||
uint8_t retryCounter; // Count of retries
|
||||
// Clock speed must be no higher than 400kHz on AVR. Higher is possible on 4809, SAMD
|
||||
// and STM32 but most popular I2C devices are 400kHz so in practice the higher speeds
|
||||
// will not be useful. The speed can be overridden by I2CManager::forceClock().
|
||||
uint32_t _clockSpeed = I2C_FREQ;
|
||||
// Default timeout 100ms on I2C request block completion.
|
||||
// A full 32-byte transmission takes about 8ms at 100kHz,
|
||||
// so this value allows lots of headroom.
|
||||
// It can be modified by calling I2CManager.setTimeout() function.
|
||||
// When retries are enabled, the timeout applies to each
|
||||
// try, and failure from timeout does not get retried.
|
||||
// A value of 0 means disable timeout monitoring.
|
||||
unsigned long _timeout = 100000UL;
|
||||
|
||||
// Finish off request block by waiting for completion and posting status.
|
||||
uint8_t finishRB(I2CRB *rb, uint8_t status);
|
||||
|
||||
void _initialise();
|
||||
void _setClock(unsigned long);
|
||||
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
// Count of I2C multiplexers found when initialising. If there is only one
|
||||
// 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
|
||||
|
||||
#if !defined(I2C_USE_WIRE)
|
||||
// I2CRB structs are queued on the following two links.
|
||||
// If there are no requests, both are NULL.
|
||||
@@ -252,42 +513,57 @@ 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.
|
||||
static I2CRB * volatile queueHead;
|
||||
static I2CRB * volatile queueTail;
|
||||
static volatile uint8_t state;
|
||||
private:
|
||||
I2CRB * volatile queueHead = NULL;
|
||||
I2CRB * volatile queueTail = NULL;
|
||||
|
||||
static I2CRB * volatile currentRequest;
|
||||
static volatile uint8_t txCount;
|
||||
static volatile uint8_t rxCount;
|
||||
static volatile uint8_t bytesToSend;
|
||||
static volatile uint8_t bytesToReceive;
|
||||
static volatile uint8_t operation;
|
||||
static volatile unsigned long startTime;
|
||||
// State is set to I2C_STATE_FREE when the interrupt handler has finished
|
||||
// the current request and is ready to complete.
|
||||
uint8_t state = I2C_STATE_FREE;
|
||||
|
||||
// CompletionStatus may be set by the interrupt handler at any time but is
|
||||
// not written to the I2CRB until the state is I2C_STATE_FREE.
|
||||
uint8_t completionStatus = I2C_STATUS_OK;
|
||||
uint8_t overallStatus = I2C_STATUS_OK;
|
||||
|
||||
I2CRB * currentRequest = NULL;
|
||||
uint8_t txCount = 0;
|
||||
uint8_t rxCount = 0;
|
||||
uint8_t bytesToSend = 0;
|
||||
uint8_t bytesToReceive = 0;
|
||||
uint8_t operation = 0;
|
||||
unsigned long startTime = 0;
|
||||
uint8_t muxPhase = 0;
|
||||
uint8_t muxAddress = 0;
|
||||
uint8_t muxData[1];
|
||||
uint8_t deviceAddress;
|
||||
const uint8_t *sendBuffer;
|
||||
uint8_t *receiveBuffer;
|
||||
|
||||
volatile uint32_t pendingClockSpeed = 0;
|
||||
|
||||
static unsigned long timeout; // Transaction timeout in microseconds. 0=disabled.
|
||||
|
||||
void startTransaction();
|
||||
|
||||
// Low-level hardware manipulation functions.
|
||||
static void I2C_init();
|
||||
static void I2C_setClock(unsigned long i2cClockSpeed);
|
||||
static void I2C_handleInterrupt();
|
||||
static void I2C_sendStart();
|
||||
static void I2C_sendStop();
|
||||
static void I2C_close();
|
||||
void I2C_init();
|
||||
void I2C_setClock(unsigned long i2cClockSpeed);
|
||||
void I2C_handleInterrupt();
|
||||
void I2C_sendStart();
|
||||
void I2C_sendStop();
|
||||
void I2C_close();
|
||||
|
||||
public:
|
||||
// setTimeout sets the timout value for I2C transactions.
|
||||
// TODO: Get I2C timeout working before uncommenting the code below.
|
||||
void setTimeout(unsigned long value) { (void)value; /* timeout = value; */ };
|
||||
|
||||
// handleInterrupt needs to be public to be called from the ISR function!
|
||||
static void handleInterrupt();
|
||||
void handleInterrupt();
|
||||
#endif
|
||||
|
||||
|
||||
};
|
||||
|
||||
// Pointer to class instance (Note: if there is more than one bus, each will have
|
||||
// its own instance of I2CManager, selected by the queueRequest function from
|
||||
// the I2CBus field within the request block's I2CAddress).
|
||||
extern I2CManagerClass I2CManager;
|
||||
|
||||
|
||||
#endif
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "I2CManager.h"
|
||||
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
|
||||
|
||||
#include <avr/io.h>
|
||||
#include <avr/interrupt.h>
|
||||
@@ -94,12 +95,13 @@ void I2CManagerClass::I2C_init()
|
||||
* Initiate a start bit for transmission.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStart() {
|
||||
bytesToSend = currentRequest->writeLen;
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
// We may have initiated a stop bit before this without waiting for it.
|
||||
// Wait for stop bit to be sent before sending start.
|
||||
while (TWCR & (1<<TWSTO)) {}
|
||||
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start
|
||||
rxCount = 0;
|
||||
txCount = 0;
|
||||
// We may have already triggered a stop bit in the same run as this. To avoid
|
||||
// clearing that bit before the stop bit has been sent, we can either wait for
|
||||
// it to complete or we can OR the bit onto the existing bits.
|
||||
TWCR |= (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start
|
||||
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -107,7 +109,7 @@ void I2CManagerClass::I2C_sendStart() {
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStop() {
|
||||
TWDR = 0xff; // Default condition = SDA released
|
||||
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
|
||||
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWSTO); // Send Stop
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -115,9 +117,8 @@ void I2CManagerClass::I2C_sendStop() {
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_close() {
|
||||
// disable TWI
|
||||
I2C_sendStop();
|
||||
while (TWCR & (1<<TWSTO)) {}
|
||||
TWCR = (1<<TWINT); // clear any interrupt and stop twi.
|
||||
delayMicroseconds(10); // Wait for things to stabilise (hopefully)
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -125,37 +126,51 @@ void I2CManagerClass::I2C_close() {
|
||||
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
|
||||
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
|
||||
***************************************************************************/
|
||||
|
||||
void I2CManagerClass::I2C_handleInterrupt() {
|
||||
if (!(TWCR & (1<<TWINT))) return; // Nothing to do.
|
||||
|
||||
uint8_t twsr = TWSR & 0xF8;
|
||||
|
||||
|
||||
// Main I2C interrupt handler, used for the device communications.
|
||||
// The following variables are used:
|
||||
// bytesToSend, bytesToReceive (R/W)
|
||||
// txCount, rxCount (W)
|
||||
// deviceAddress (R)
|
||||
// sendBuffer, receiveBuffer (R)
|
||||
// operation (R)
|
||||
// state, completionStatus (W)
|
||||
//
|
||||
// Cases are ordered so that the most frequently used ones are tested first.
|
||||
switch (twsr) {
|
||||
case TWI_MTX_DATA_ACK: // Data byte has been transmitted and ACK received
|
||||
case TWI_MTX_ADR_ACK: // SLA+W has been transmitted and ACK received
|
||||
if (bytesToSend) { // Send first.
|
||||
if (operation == OPERATION_SEND_P)
|
||||
TWDR = GETFLASH(currentRequest->writeBuffer + (txCount++));
|
||||
TWDR = GETFLASH(sendBuffer + (txCount++));
|
||||
else
|
||||
TWDR = currentRequest->writeBuffer[txCount++];
|
||||
TWDR = sendBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
|
||||
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT);
|
||||
} else if (bytesToReceive) { // All sent, anything to receive?
|
||||
while (TWCR & (1<<TWSTO)) {} // Wait for stop to be sent
|
||||
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA)|(1<<TWSTA); // Send Start
|
||||
} else { // Nothing left to send or receive
|
||||
TWDR = 0xff; // Default condition = SDA released
|
||||
// Don't need to wait for stop, as the interface won't send the start until
|
||||
// any in-progress stop condition from previous interrupts has been sent.
|
||||
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWSTA); // Send Start
|
||||
} else {
|
||||
// Nothing left to send or receive
|
||||
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
|
||||
state = I2C_STATUS_OK;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
break;
|
||||
|
||||
case TWI_MRX_DATA_ACK: // Data byte has been received and ACK transmitted
|
||||
if (bytesToReceive > 0) {
|
||||
currentRequest->readBuffer[rxCount++] = TWDR;
|
||||
receiveBuffer[rxCount++] = TWDR;
|
||||
bytesToReceive--;
|
||||
}
|
||||
/* fallthrough */
|
||||
|
||||
case TWI_MRX_ADR_ACK: // SLA+R has been sent and ACK received
|
||||
if (bytesToReceive <= 1) {
|
||||
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT); // Send NACK after next reception
|
||||
@@ -164,45 +179,51 @@ void I2CManagerClass::I2C_handleInterrupt() {
|
||||
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
|
||||
}
|
||||
break;
|
||||
|
||||
case TWI_MRX_DATA_NACK: // Data byte has been received and NACK transmitted
|
||||
if (bytesToReceive > 0) {
|
||||
currentRequest->readBuffer[rxCount++] = TWDR;
|
||||
receiveBuffer[rxCount++] = TWDR;
|
||||
bytesToReceive--;
|
||||
}
|
||||
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
|
||||
state = I2C_STATUS_OK;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
break;
|
||||
|
||||
case TWI_START: // START has been transmitted
|
||||
case TWI_REP_START: // Repeated START has been transmitted
|
||||
// Set up address and R/W
|
||||
if (operation == OPERATION_READ || (operation==OPERATION_REQUEST && !bytesToSend))
|
||||
TWDR = (currentRequest->i2cAddress << 1) | 1; // SLA+R
|
||||
TWDR = (deviceAddress << 1) | 1; // SLA+R
|
||||
else
|
||||
TWDR = (currentRequest->i2cAddress << 1) | 0; // SLA+W
|
||||
TWDR = (deviceAddress << 1) | 0; // SLA+W
|
||||
TWCR = (1<<TWEN)|ENABLE_TWI_INTERRUPT|(1<<TWINT)|(1<<TWEA);
|
||||
break;
|
||||
|
||||
case TWI_MTX_ADR_NACK: // SLA+W has been transmitted and NACK received
|
||||
case TWI_MRX_ADR_NACK: // SLA+R has been transmitted and NACK received
|
||||
case TWI_MTX_DATA_NACK: // Data byte has been transmitted and NACK received
|
||||
TWDR = 0xff; // Default condition = SDA released
|
||||
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
|
||||
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
break;
|
||||
|
||||
case TWI_ARB_LOST: // Arbitration lost
|
||||
// Restart transaction from start.
|
||||
I2C_sendStart();
|
||||
break;
|
||||
|
||||
case TWI_BUS_ERROR: // Bus error due to an illegal START or STOP condition
|
||||
default:
|
||||
TWDR = 0xff; // Default condition = SDA released
|
||||
TWCR = (1<<TWEN)|(1<<TWINT)|(1<<TWEA)|(1<<TWSTO); // Send Stop
|
||||
state = I2C_STATUS_TRANSMIT_ERROR;
|
||||
completionStatus = I2C_STATUS_TRANSMIT_ERROR;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
ISR(TWI_vect) {
|
||||
I2CManagerClass::handleInterrupt();
|
||||
I2CManager.handleInterrupt();
|
||||
}
|
||||
#endif
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -28,21 +28,21 @@
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_setClock(unsigned long i2cClockSpeed) {
|
||||
uint16_t t_rise;
|
||||
if (i2cClockSpeed < 200000) {
|
||||
i2cClockSpeed = 100000;
|
||||
if (i2cClockSpeed < 200000)
|
||||
t_rise = 1000;
|
||||
} else if (i2cClockSpeed < 800000) {
|
||||
i2cClockSpeed = 400000;
|
||||
else if (i2cClockSpeed < 800000)
|
||||
t_rise = 300;
|
||||
} else if (i2cClockSpeed < 1200000) {
|
||||
i2cClockSpeed = 1000000;
|
||||
else
|
||||
t_rise = 120;
|
||||
} else {
|
||||
i2cClockSpeed = 100000;
|
||||
t_rise = 1000;
|
||||
}
|
||||
|
||||
if (t_rise == 120)
|
||||
TWI0.CTRLA |= TWI_FMPEN_bm;
|
||||
else
|
||||
TWI0.CTRLA &= ~TWI_FMPEN_bm;
|
||||
|
||||
uint32_t baud = (F_CPU_CORRECTED / i2cClockSpeed - F_CPU_CORRECTED / 1000 / 1000
|
||||
* t_rise / 1000 - 10) / 2;
|
||||
if (baud > 255) baud = 255; // ~30kHz
|
||||
TWI0.MBAUD = (uint8_t)baud;
|
||||
}
|
||||
|
||||
@@ -54,13 +54,13 @@ void I2CManagerClass::I2C_init()
|
||||
pinMode(PIN_WIRE_SDA, INPUT_PULLUP);
|
||||
pinMode(PIN_WIRE_SCL, INPUT_PULLUP);
|
||||
PORTMUX.TWISPIROUTEA |= TWI_MUX;
|
||||
I2C_setClock(I2C_FREQ);
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
TWI0.MCTRLA = TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm;
|
||||
#else
|
||||
TWI0.MCTRLA = TWI_ENABLE_bm;
|
||||
#endif
|
||||
I2C_setClock(I2C_FREQ);
|
||||
TWI0.MSTATUS = TWI_BUSSTATE_IDLE_gc;
|
||||
}
|
||||
|
||||
@@ -68,8 +68,8 @@ void I2CManagerClass::I2C_init()
|
||||
* Initiate a start bit for transmission, followed by address and R/W
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStart() {
|
||||
bytesToSend = currentRequest->writeLen;
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
txCount = 0;
|
||||
rxCount = 0;
|
||||
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||
@@ -89,7 +89,10 @@ void I2CManagerClass::I2C_sendStop() {
|
||||
* Close I2C down
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_close() {
|
||||
I2C_sendStop();
|
||||
|
||||
TWI0.MCTRLA &= ~(TWI_RIEN_bm | TWI_WIEN_bm | TWI_ENABLE_bm); // Switch off I2C
|
||||
TWI0.MSTATUS = TWI_BUSSTATE_UNKNOWN_gc;
|
||||
delayMicroseconds(10); // Wait for things to stabilise (hopefully)
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -105,46 +108,42 @@ void I2CManagerClass::I2C_handleInterrupt() {
|
||||
I2C_sendStart(); // Reinitiate request
|
||||
} else if (currentStatus & TWI_BUSERR_bm) {
|
||||
// Bus error
|
||||
state = I2C_STATUS_BUS_ERROR;
|
||||
completionStatus = I2C_STATUS_BUS_ERROR;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
TWI0.MSTATUS = currentStatus; // clear all flags
|
||||
} else if (currentStatus & TWI_WIF_bm) {
|
||||
// Master write completed
|
||||
if (currentStatus & TWI_RXACK_bm) {
|
||||
// Nacked, send stop.
|
||||
TWI0.MCTRLB = TWI_MCMD_STOP_gc;
|
||||
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
|
||||
} else if (bytesToSend) {
|
||||
// Acked, so send next byte
|
||||
if (currentRequest->operation == OPERATION_SEND_P)
|
||||
TWI0.MDATA = GETFLASH(currentRequest->writeBuffer + (txCount++));
|
||||
else
|
||||
TWI0.MDATA = currentRequest->writeBuffer[txCount++];
|
||||
// Acked, so send next byte (don't need to use GETFLASH)
|
||||
TWI0.MDATA = sendBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
} else if (bytesToReceive) {
|
||||
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
|
||||
TWI0.MADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
|
||||
TWI0.MADDR = (deviceAddress << 1) | 1;
|
||||
} else {
|
||||
// No more data to send/receive. Initiate a STOP condition.
|
||||
TWI0.MCTRLB = TWI_MCMD_STOP_gc;
|
||||
state = I2C_STATUS_OK; // Done
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
} else if (currentStatus & TWI_RIF_bm) {
|
||||
// Master read completed without errors
|
||||
if (bytesToReceive) {
|
||||
currentRequest->readBuffer[rxCount++] = TWI0.MDATA; // Store received byte
|
||||
receiveBuffer[rxCount++] = TWI0.MDATA; // Store received byte
|
||||
bytesToReceive--;
|
||||
} else {
|
||||
// Buffer full, issue nack/stop
|
||||
TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc;
|
||||
state = I2C_STATUS_OK;
|
||||
}
|
||||
}
|
||||
if (bytesToReceive) {
|
||||
// More bytes to receive, issue ack and start another read
|
||||
TWI0.MCTRLB = TWI_MCMD_RECVTRANS_gc;
|
||||
} else {
|
||||
// Transaction finished, issue NACK and STOP.
|
||||
TWI0.MCTRLB = TWI_ACKACT_bm | TWI_MCMD_STOP_gc;
|
||||
state = I2C_STATUS_OK;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -154,7 +153,7 @@ void I2CManagerClass::I2C_handleInterrupt() {
|
||||
* Interrupt handler.
|
||||
***************************************************************************/
|
||||
ISR(TWI0_TWIM_vect) {
|
||||
I2CManagerClass::handleInterrupt();
|
||||
I2CManager.handleInterrupt();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -24,58 +24,62 @@
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "I2CManager.h"
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// atomic.h isn't available on SAMD, and likely others too...
|
||||
#if defined(__AVR__)
|
||||
#include <util/atomic.h>
|
||||
#elif defined(__arm__)
|
||||
// Helper assembly language functions
|
||||
static __inline__ uint8_t my_iSeiRetVal(void)
|
||||
{
|
||||
__asm__ __volatile__ ("cpsie i" ::);
|
||||
return 1;
|
||||
|
||||
// Support for atomic isolation (i.e. a block with interrupts disabled).
|
||||
// E.g.
|
||||
// ATOMIC_BLOCK() {
|
||||
// doSomethingWithInterruptsDisabled();
|
||||
// }
|
||||
// This has the advantage over simple noInterrupts/Interrupts that the
|
||||
// original interrupt state is restored when the block finishes.
|
||||
//
|
||||
// (This should really be defined in an include file somewhere more global, so
|
||||
// it can replace use of noInterrupts/interrupts in other parts of DCC-EX.
|
||||
//
|
||||
static inline uint8_t _deferInterrupts(void) {
|
||||
noInterrupts();
|
||||
return 1;
|
||||
}
|
||||
|
||||
static __inline__ uint8_t my_iCliRetVal(void)
|
||||
{
|
||||
__asm__ __volatile__ ("cpsid i" ::);
|
||||
return 1;
|
||||
static inline void _conditionalEnableInterrupts(bool *wasEnabled) {
|
||||
if (*wasEnabled) interrupts();
|
||||
}
|
||||
#define ATOMIC_BLOCK(x) \
|
||||
for (bool _int_saved __attribute__((__cleanup__(_conditionalEnableInterrupts))) \
|
||||
=_getInterruptState(),_ToDo=_deferInterrupts(); _ToDo; _ToDo=0)
|
||||
|
||||
static __inline__ void my_iRestore(const uint32_t *__s)
|
||||
{
|
||||
uint32_t res = *__s;
|
||||
__asm__ __volatile__ ("MSR primask, %0" : : "r" (res) );
|
||||
}
|
||||
|
||||
static __inline__ uint32_t my_iGetIReg( void )
|
||||
{
|
||||
uint32_t reg;
|
||||
__asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) );
|
||||
return reg;
|
||||
}
|
||||
// Macros for atomic isolation
|
||||
#define MY_ATOMIC_RESTORESTATE uint32_t _sa_saved \
|
||||
__attribute__((__cleanup__(my_iRestore))) = my_iGetIReg()
|
||||
|
||||
#define ATOMIC() \
|
||||
for ( MY_ATOMIC_RESTORESTATE, _done = my_iCliRetVal(); \
|
||||
_done; _done = 0 )
|
||||
|
||||
#define ATOMIC_BLOCK(x) ATOMIC()
|
||||
#define ATOMIC_RESTORESTATE
|
||||
#endif
|
||||
#if defined(__AVR__) // Nano, Uno, Mega2580, NanoEvery, etc.
|
||||
static inline bool _getInterruptState(void) {
|
||||
return bitRead(SREG, SREG_I); // true if enabled, false if disabled
|
||||
}
|
||||
#elif defined(__arm__) // STM32, SAMD, Teensy
|
||||
static inline bool _getInterruptState( void ) {
|
||||
uint32_t reg;
|
||||
__asm__ __volatile__ ("MRS %0, primask" : "=r" (reg) );
|
||||
return !(reg & 1); // true if interrupts enabled, false otherwise
|
||||
}
|
||||
#else
|
||||
#define ATOMIC_BLOCK(x)
|
||||
#define ATOMIC_RESTORESTATE
|
||||
#warning "ATOMIC_BLOCK() not defined for this target type, I2C interrupts disabled"
|
||||
#define ATOMIC_BLOCK(x) // expand to nothing.
|
||||
#ifdef I2C_USE_INTERRUPTS
|
||||
#undef I2C_USE_INTERRUPTS
|
||||
#endif
|
||||
#endif
|
||||
|
||||
|
||||
// This module is only compiled if I2C_USE_WIRE is not defined, so undefine it here
|
||||
// to get intellisense to work correctly.
|
||||
#if defined(I2C_USE_WIRE)
|
||||
#undef I2C_USE_WIRE
|
||||
#endif
|
||||
|
||||
enum MuxPhase: uint8_t {
|
||||
MuxPhase_OFF = 0,
|
||||
MuxPhase_PROLOG,
|
||||
MuxPhase_PAYLOAD,
|
||||
MuxPhase_EPILOG,
|
||||
} ;
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Initialise the I2CManagerAsync class.
|
||||
***************************************************************************/
|
||||
@@ -84,31 +88,82 @@ void I2CManagerClass::_initialise()
|
||||
queueHead = queueTail = NULL;
|
||||
state = I2C_STATE_FREE;
|
||||
I2C_init();
|
||||
_setClock(_clockSpeed);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Set I2C clock speed. Normally 100000 (Standard) or 400000 (Fast)
|
||||
* on Arduino. Mega4809 supports 1000000 (Fast+) too.
|
||||
* This function saves the desired clock speed and the startTransaction
|
||||
* function acts on it before a new transaction, to avoid speed changes
|
||||
* during an I2C transaction.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) {
|
||||
I2C_setClock(i2cClockSpeed);
|
||||
pendingClockSpeed = i2cClockSpeed;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Helper function to start operations, if the I2C interface is free and
|
||||
* Start an I2C transaction, if the I2C interface is free and
|
||||
* there is a queued request to be processed.
|
||||
* If there's an I2C clock speed change pending, then implement it before
|
||||
* starting the operation.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::startTransaction() {
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
void I2CManagerClass::startTransaction() {
|
||||
ATOMIC_BLOCK() {
|
||||
if ((state == I2C_STATE_FREE) && (queueHead != NULL)) {
|
||||
state = I2C_STATE_ACTIVE;
|
||||
completionStatus = I2C_STATUS_OK;
|
||||
// Check for pending clock speed change
|
||||
if (pendingClockSpeed) {
|
||||
// We're about to start a new I2C transaction, so set clock now.
|
||||
I2C_setClock(pendingClockSpeed);
|
||||
pendingClockSpeed = 0;
|
||||
}
|
||||
startTime = micros();
|
||||
currentRequest = queueHead;
|
||||
rxCount = txCount = 0;
|
||||
// Copy key fields to static data for speed.
|
||||
operation = currentRequest->operation;
|
||||
|
||||
// Start the I2C process going.
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
I2CMux muxNumber = currentRequest->i2cAddress.muxNumber();
|
||||
if (muxNumber != I2CMux_None) {
|
||||
muxPhase = MuxPhase_PROLOG;
|
||||
uint8_t subBus = currentRequest->i2cAddress.subBus();
|
||||
muxData[0] = (subBus == SubBus_All) ? 0xff :
|
||||
(subBus == SubBus_None) ? 0x00 :
|
||||
#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
|
||||
deviceAddress = I2C_MUX_BASE_ADDRESS + muxNumber;
|
||||
sendBuffer = &muxData[0];
|
||||
bytesToSend = 1;
|
||||
bytesToReceive = 0;
|
||||
operation = OPERATION_SEND;
|
||||
} else {
|
||||
// Send/receive payload for device only.
|
||||
muxPhase = MuxPhase_OFF;
|
||||
deviceAddress = currentRequest->i2cAddress;
|
||||
sendBuffer = currentRequest->writeBuffer;
|
||||
bytesToSend = currentRequest->writeLen;
|
||||
receiveBuffer = currentRequest->readBuffer;
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
operation = currentRequest->operation & OPERATION_MASK;
|
||||
}
|
||||
#else
|
||||
deviceAddress = currentRequest->i2cAddress;
|
||||
sendBuffer = currentRequest->writeBuffer;
|
||||
bytesToSend = currentRequest->writeLen;
|
||||
receiveBuffer = currentRequest->readBuffer;
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
operation = currentRequest->operation & OPERATION_MASK;
|
||||
#endif
|
||||
I2C_sendStart();
|
||||
startTime = micros();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,8 +174,7 @@ void I2CManagerClass::startTransaction() {
|
||||
void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
req->status = I2C_STATUS_PENDING;
|
||||
req->nextRequest = NULL;
|
||||
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
ATOMIC_BLOCK() {
|
||||
if (!queueTail)
|
||||
queueHead = queueTail = req; // Only item on queue
|
||||
else
|
||||
@@ -133,7 +187,7 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
/***************************************************************************
|
||||
* Initiate a write to an I2C device (non-blocking operation)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req) {
|
||||
uint8_t I2CManagerClass::write(I2CAddress i2cAddress, const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req) {
|
||||
// Make sure previous request has completed.
|
||||
req->wait();
|
||||
req->setWriteParams(i2cAddress, writeBuffer, writeLen);
|
||||
@@ -144,7 +198,7 @@ uint8_t I2CManagerClass::write(uint8_t i2cAddress, const uint8_t *writeBuffer, u
|
||||
/***************************************************************************
|
||||
* Initiate a write from PROGMEM (flash) to an I2C device (non-blocking operation)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) {
|
||||
uint8_t I2CManagerClass::write_P(I2CAddress i2cAddress, const uint8_t * writeBuffer, uint8_t writeLen, I2CRB *req) {
|
||||
// Make sure previous request has completed.
|
||||
req->wait();
|
||||
req->setWriteParams(i2cAddress, writeBuffer, writeLen);
|
||||
@@ -157,7 +211,7 @@ uint8_t I2CManagerClass::write_P(uint8_t i2cAddress, const uint8_t * writeBuffer
|
||||
* Initiate a read from the I2C device, optionally preceded by a write
|
||||
* (non-blocking operation)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t readLen,
|
||||
uint8_t I2CManagerClass::read(I2CAddress i2cAddress, uint8_t *readBuffer, uint8_t readLen,
|
||||
const uint8_t *writeBuffer, uint8_t writeLen, I2CRB *req)
|
||||
{
|
||||
// Make sure previous request has completed.
|
||||
@@ -167,31 +221,54 @@ uint8_t I2CManagerClass::read(uint8_t i2cAddress, uint8_t *readBuffer, uint8_t r
|
||||
return I2C_STATUS_OK;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Set I2C timeout value in microseconds. The timeout applies to the entire
|
||||
* I2CRB request, e.g. where a write+read is performed, the timer is not
|
||||
* reset before the read.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::setTimeout(unsigned long value) {
|
||||
_timeout = value;
|
||||
};
|
||||
|
||||
/***************************************************************************
|
||||
* checkForTimeout() function, called from isBusy() and wait() to cancel
|
||||
* requests that are taking too long to complete.
|
||||
* This function doesn't fully work as intended so is not currently called.
|
||||
* Instead we check for an I2C hang-up and report an error from
|
||||
* I2CRB::wait(), but we aren't able to recover from the hang-up. Such faults
|
||||
* requests that are taking too long to complete. Such faults
|
||||
* may be caused by an I2C wire short for example.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::checkForTimeout() {
|
||||
unsigned long currentMicros = micros();
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
ATOMIC_BLOCK() {
|
||||
I2CRB *t = queueHead;
|
||||
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && timeout > 0) {
|
||||
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
|
||||
// Check for timeout
|
||||
if (currentMicros - startTime > timeout) {
|
||||
unsigned long elapsed = micros() - startTime;
|
||||
if (elapsed > _timeout) {
|
||||
#ifdef DIAG_IO
|
||||
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
|
||||
#endif
|
||||
// Excessive time. Dequeue request
|
||||
queueHead = t->nextRequest;
|
||||
if (!queueHead) queueTail = NULL;
|
||||
currentRequest = NULL;
|
||||
bytesToReceive = bytesToSend = 0;
|
||||
// Post request as timed out.
|
||||
t->status = I2C_STATUS_TIMEOUT;
|
||||
// Reset TWI interface so it is able to continue
|
||||
// Try close and init, not entirely satisfactory but sort of works...
|
||||
I2C_close(); // Shutdown and restart twi interface
|
||||
|
||||
// If SDA is stuck low, issue up to 9 clock pulses to attempt to free it.
|
||||
pinMode(SCL, INPUT_PULLUP);
|
||||
pinMode(SDA, INPUT_PULLUP);
|
||||
for (int i=0; !digitalRead(SDA) && i<9; i++) {
|
||||
digitalWrite(SCL, 0);
|
||||
pinMode(SCL, OUTPUT); // Force clock low
|
||||
delayMicroseconds(10); // ... for 5us
|
||||
pinMode(SCL, INPUT_PULLUP); // ... then high
|
||||
delayMicroseconds(10); // ... for 5us (100kHz Clock)
|
||||
}
|
||||
// Whether that's succeeded or not, now try reinitialising.
|
||||
I2C_init();
|
||||
_setClock(_clockSpeed);
|
||||
state = I2C_STATE_FREE;
|
||||
|
||||
// Initiate next queued request if any.
|
||||
@@ -208,10 +285,8 @@ void I2CManagerClass::loop() {
|
||||
#if !defined(I2C_USE_INTERRUPTS)
|
||||
handleInterrupt();
|
||||
#endif
|
||||
// Timeout is now reported in I2CRB::wait(), not here.
|
||||
// I've left the code, commented out, as a reminder to look at this again
|
||||
// in the future.
|
||||
//checkForTimeout();
|
||||
// Call function to monitor for stuck I2C operations.
|
||||
checkForTimeout();
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -223,43 +298,85 @@ void I2CManagerClass::handleInterrupt() {
|
||||
// Update hardware state machine
|
||||
I2C_handleInterrupt();
|
||||
|
||||
// Enable interrupts to minimise effect on other interrupt code
|
||||
interrupts();
|
||||
|
||||
// Check if current request has completed. If there's a current request
|
||||
// and state isn't active then state contains the completion status of the request.
|
||||
if (state != I2C_STATE_ACTIVE && currentRequest != NULL) {
|
||||
// Remove completed request from head of queue
|
||||
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
|
||||
if (state == I2C_STATE_COMPLETED && currentRequest != NULL) {
|
||||
// Operation has completed.
|
||||
if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|
||||
|| currentRequest->operation & OPERATION_NORETRY)
|
||||
{
|
||||
// Status is OK, or has failed and retry count exceeded, or retries disabled.
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
if (muxPhase == MuxPhase_PROLOG ) {
|
||||
overallStatus = completionStatus;
|
||||
uint8_t rbAddress = currentRequest->i2cAddress.deviceAddress();
|
||||
if (completionStatus == I2C_STATUS_OK && rbAddress != 0) {
|
||||
// Mux request OK, start handling application request.
|
||||
muxPhase = MuxPhase_PAYLOAD;
|
||||
deviceAddress = rbAddress;
|
||||
sendBuffer = currentRequest->writeBuffer;
|
||||
bytesToSend = currentRequest->writeLen;
|
||||
receiveBuffer = currentRequest->readBuffer;
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
operation = currentRequest->operation & OPERATION_MASK;
|
||||
state = I2C_STATE_ACTIVE;
|
||||
I2C_sendStart();
|
||||
return;
|
||||
}
|
||||
} else if (muxPhase == MuxPhase_PAYLOAD) {
|
||||
// Application request completed, now send epilogue to mux
|
||||
overallStatus = completionStatus;
|
||||
currentRequest->nBytes = rxCount; // Save number of bytes read into rb
|
||||
if (_muxCount == 1) {
|
||||
// Only one MUX, don't need to deselect subbus
|
||||
muxPhase = MuxPhase_OFF;
|
||||
} else {
|
||||
muxPhase = MuxPhase_EPILOG;
|
||||
deviceAddress = I2C_MUX_BASE_ADDRESS + currentRequest->i2cAddress.muxNumber();
|
||||
muxData[0] = 0x00;
|
||||
sendBuffer = &muxData[0];
|
||||
bytesToSend = 1;
|
||||
bytesToReceive = 0;
|
||||
operation = OPERATION_SEND;
|
||||
state = I2C_STATE_ACTIVE;
|
||||
I2C_sendStart();
|
||||
return;
|
||||
}
|
||||
} else if (muxPhase == MuxPhase_EPILOG) {
|
||||
// Epilog finished, ignore completionStatus
|
||||
muxPhase = MuxPhase_OFF;
|
||||
} else
|
||||
overallStatus = completionStatus;
|
||||
#else
|
||||
overallStatus = completionStatus;
|
||||
currentRequest->nBytes = rxCount;
|
||||
#endif
|
||||
|
||||
// Remove completed request from head of queue
|
||||
I2CRB * t = queueHead;
|
||||
if (t == queueHead) {
|
||||
if (t == currentRequest) {
|
||||
queueHead = t->nextRequest;
|
||||
if (!queueHead) queueTail = queueHead;
|
||||
t->nBytes = rxCount;
|
||||
t->status = state;
|
||||
t->status = overallStatus;
|
||||
|
||||
// I2C state machine is now free for next request
|
||||
currentRequest = NULL;
|
||||
state = I2C_STATE_FREE;
|
||||
|
||||
// Start next request (if any)
|
||||
I2CManager.startTransaction();
|
||||
}
|
||||
retryCounter = 0;
|
||||
} else {
|
||||
// Status is failed and retry permitted.
|
||||
// Retry previous request.
|
||||
state = I2C_STATE_FREE;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == I2C_STATE_FREE && queueHead != NULL) {
|
||||
// Allow any pending interrupts before starting the next request.
|
||||
//interrupts();
|
||||
// Start next request
|
||||
I2CManager.startTransaction();
|
||||
}
|
||||
}
|
||||
|
||||
// Fields in I2CManager class specific to Non-blocking implementation.
|
||||
I2CRB * volatile I2CManagerClass::queueHead = NULL;
|
||||
I2CRB * volatile I2CManagerClass::queueTail = NULL;
|
||||
I2CRB * volatile I2CManagerClass::currentRequest = NULL;
|
||||
volatile uint8_t I2CManagerClass::state = I2C_STATE_FREE;
|
||||
volatile uint8_t I2CManagerClass::txCount;
|
||||
volatile uint8_t I2CManagerClass::rxCount;
|
||||
volatile uint8_t I2CManagerClass::operation;
|
||||
volatile uint8_t I2CManagerClass::bytesToSend;
|
||||
volatile uint8_t I2CManagerClass::bytesToReceive;
|
||||
volatile unsigned long I2CManagerClass::startTime;
|
||||
unsigned long I2CManagerClass::timeout = 0;
|
||||
|
||||
#endif
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2021, Neil McKechnie
|
||||
* © 2023, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -38,7 +38,7 @@
|
||||
***************************************************************************/
|
||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_SAMD_ZERO)
|
||||
void SERCOM3_Handler() {
|
||||
I2CManagerClass::handleInterrupt();
|
||||
I2CManager.handleInterrupt();
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -46,14 +46,16 @@ void SERCOM3_Handler() {
|
||||
Sercom *s = SERCOM3;
|
||||
|
||||
/***************************************************************************
|
||||
* Set I2C clock speed register.
|
||||
* Set I2C clock speed register. This should only be called outside of
|
||||
* a transmission. The I2CManagerClass::_setClock() function ensures
|
||||
* that it is only called at the beginning of an I2C transaction.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
|
||||
// Calculate a rise time appropriate to the requested bus speed
|
||||
int t_rise;
|
||||
if (i2cClockSpeed < 200000L) {
|
||||
i2cClockSpeed = 100000L;
|
||||
i2cClockSpeed = 100000L; // NB: this overrides a "force clock" of lower than 100KHz!
|
||||
t_rise = 1000;
|
||||
} else if (i2cClockSpeed < 800000L) {
|
||||
i2cClockSpeed = 400000L;
|
||||
@@ -66,6 +68,9 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
t_rise = 1000;
|
||||
}
|
||||
|
||||
// Wait while the bus is busy
|
||||
while (s->I2CM.STATUS.bit.BUSSTATE != 0x1);
|
||||
|
||||
// Disable the I2C master mode and wait for sync
|
||||
s->I2CM.CTRLA.bit.ENABLE = 0 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0);
|
||||
@@ -80,8 +85,6 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
// Setting bus idle mode and wait for sync
|
||||
s->I2CM.STATUS.bit.BUSSTATE = 1 ;
|
||||
while (s->I2CM.SYNCBUSY.bit.SYSOP != 0);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -107,8 +110,8 @@ void I2CManagerClass::I2C_init()
|
||||
s->I2CM.CTRLA.reg = SERCOM_I2CM_CTRLA_MODE( I2C_MASTER_OPERATION )/* |
|
||||
SERCOM_I2CM_CTRLA_SCLSM*/ ;
|
||||
|
||||
// Enable Smart mode and Quick Command
|
||||
s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN | SERCOM_I2CM_CTRLB_QCEN;
|
||||
// Enable Smart mode (but not Quick Command)
|
||||
s->I2CM.CTRLB.reg = SERCOM_I2CM_CTRLB_SMEN;
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// Setting NVIC
|
||||
@@ -141,30 +144,33 @@ void I2CManagerClass::I2C_init()
|
||||
PORT->Group[g_APinDescription[PIN_WIRE_SCL].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SCL].ulPin].reg =
|
||||
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
|
||||
PORT->Group[g_APinDescription[PIN_WIRE_SDA].ulPort].PINCFG[g_APinDescription[PIN_WIRE_SDA].ulPin].reg =
|
||||
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
|
||||
PORT_PINCFG_DRVSTR | PORT_PINCFG_PULLEN | PORT_PINCFG_PMUXEN;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a start bit for transmission.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStart() {
|
||||
bytesToSend = currentRequest->writeLen;
|
||||
bytesToReceive = currentRequest->readLen;
|
||||
|
||||
// We may have initiated a stop bit before this without waiting for it.
|
||||
// Wait for stop bit to be sent before sending start.
|
||||
while (s->I2CM.STATUS.bit.BUSSTATE == 0x2);
|
||||
// Set counters here in case this is a retry.
|
||||
txCount = 0;
|
||||
rxCount = 0;
|
||||
|
||||
// On a single-master I2C bus, the start bit won't be sent until the bus
|
||||
// state goes to IDLE so we can request it without waiting. On a
|
||||
// multi-master bus, the bus may be BUSY under control of another master,
|
||||
// in which case we can avoid some arbitration failures by waiting until
|
||||
// the bus state is IDLE. We don't do that here.
|
||||
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||
{
|
||||
// Send start and address with read/write flag or'd in
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
// Send start and address with read flag (1) or'd in
|
||||
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
|
||||
}
|
||||
else {
|
||||
// Wait while the I2C bus is BUSY
|
||||
while (s->I2CM.STATUS.bit.BUSSTATE != 0x1);
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1ul) | 0;
|
||||
// Send start and address with write flag (0) or'd in
|
||||
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1ul) | 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,6 +186,13 @@ void I2CManagerClass::I2C_sendStop() {
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_close() {
|
||||
I2C_sendStop();
|
||||
// Disable the I2C master mode and wait for sync
|
||||
s->I2CM.CTRLA.bit.ENABLE = 0 ;
|
||||
// Wait for up to 500us only.
|
||||
unsigned long startTime = micros();
|
||||
while (s->I2CM.SYNCBUSY.bit.ENABLE != 0) {
|
||||
if (micros() - startTime >= 500UL) break;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -194,49 +207,39 @@ void I2CManagerClass::I2C_handleInterrupt() {
|
||||
I2C_sendStart(); // Reinitiate request
|
||||
} else if (s->I2CM.STATUS.bit.BUSERR) {
|
||||
// Bus error
|
||||
state = I2C_STATUS_BUS_ERROR;
|
||||
completionStatus = I2C_STATUS_BUS_ERROR;
|
||||
state = I2C_STATE_COMPLETED; // Completed with error
|
||||
} else if (s->I2CM.INTFLAG.bit.MB) {
|
||||
// Master write completed
|
||||
if (s->I2CM.STATUS.bit.RXNACK) {
|
||||
// Nacked, send stop.
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
state = I2C_STATE_COMPLETED; // Completed with error
|
||||
} else if (bytesToSend) {
|
||||
// Acked, so send next byte
|
||||
if (currentRequest->operation == OPERATION_SEND_P)
|
||||
s->I2CM.DATA.bit.DATA = GETFLASH(currentRequest->writeBuffer + (txCount++));
|
||||
else
|
||||
s->I2CM.DATA.bit.DATA = currentRequest->writeBuffer[txCount++];
|
||||
s->I2CM.DATA.bit.DATA = sendBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
} else if (bytesToReceive) {
|
||||
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
|
||||
s->I2CM.ADDR.bit.ADDR = (currentRequest->i2cAddress << 1) | 1;
|
||||
s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
|
||||
} else {
|
||||
// No more data to send/receive. Initiate a STOP condition.
|
||||
// No more data to send/receive. Initiate a STOP condition
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK; // Done
|
||||
state = I2C_STATE_COMPLETED; // Completed OK
|
||||
}
|
||||
} else if (s->I2CM.INTFLAG.bit.SB) {
|
||||
// Master read completed without errors
|
||||
if (bytesToReceive) {
|
||||
currentRequest->readBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
|
||||
if (bytesToReceive == 1) {
|
||||
s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
|
||||
I2C_sendStop(); // send stop
|
||||
receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
|
||||
bytesToReceive = 0;
|
||||
state = I2C_STATE_COMPLETED; // Completed OK
|
||||
} else if (bytesToReceive) {
|
||||
s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
|
||||
receiveBuffer[rxCount++] = s->I2CM.DATA.bit.DATA; // Store received byte
|
||||
bytesToReceive--;
|
||||
} else {
|
||||
// Buffer full, issue nack/stop
|
||||
s->I2CM.CTRLB.bit.ACKACT = 1;
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK;
|
||||
}
|
||||
if (bytesToReceive) {
|
||||
// PMA - I think Smart Mode means we have nothing to do...
|
||||
// More bytes to receive, issue ack and start another read
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transaction finished, issue NACK and STOP.
|
||||
s->I2CM.CTRLB.bit.ACKACT = 1;
|
||||
I2C_sendStop();
|
||||
state = I2C_STATUS_OK;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
312
I2CManager_STM32.h
Normal file
312
I2CManager_STM32.h
Normal file
@@ -0,0 +1,312 @@
|
||||
/*
|
||||
* © 2022-23 Paul M Antoine
|
||||
* © 2023, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef I2CMANAGER_STM32_H
|
||||
#define I2CMANAGER_STM32_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "I2CManager.h"
|
||||
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
|
||||
|
||||
//#include <avr/io.h>
|
||||
//#include <avr/interrupt.h>
|
||||
#include <wiring_private.h>
|
||||
|
||||
/***************************************************************************
|
||||
* Interrupt handler.
|
||||
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
|
||||
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
|
||||
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
|
||||
* bus on the SAMD architecture
|
||||
***************************************************************************/
|
||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
|
||||
void I2C1_IRQHandler() {
|
||||
I2CManager.handleInterrupt();
|
||||
}
|
||||
#endif
|
||||
|
||||
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants
|
||||
I2C_TypeDef *s = I2C1;
|
||||
#define I2C_IRQn I2C1_EV_IRQn
|
||||
#define I2C_BUSFREQ 16
|
||||
|
||||
// I2C SR1 Status Register #1 bit definitions for convenience
|
||||
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
|
||||
// #define I2C_SR1_TIMEOUT (1<<14) // Timeout of Tlow error
|
||||
// #define I2C_SR1_PECERR (1<<12) // PEC error in reception
|
||||
// #define I2C_SR1_OVR (1<<11) // Overrun/Underrun error
|
||||
// #define I2C_SR1_AF (1<<10) // Acknowledge failure
|
||||
// #define I2C_SR1_ARLO (1<<9) // Arbitration lost (master mode)
|
||||
// #define I2C_SR1_BERR (1<<8) // Bus error (misplaced start or stop condition)
|
||||
// #define I2C_SR1_TxE (1<<7) // Data register empty on transmit
|
||||
// #define I2C_SR1_RxNE (1<<6) // Data register not empty on receive
|
||||
// #define I2C_SR1_STOPF (1<<4) // Stop detection (slave mode)
|
||||
// #define I2C_SR1_ADD10 (1<<3) // 10 bit header sent
|
||||
// #define I2C_SR1_BTF (1<<2) // Byte transfer finished - data transfer done
|
||||
// #define I2C_SR1_ADDR (1<<1) // Address sent (master) or matched (slave)
|
||||
// #define I2C_SR1_SB (1<<0) // Start bit (master mode) 1=start condition generated
|
||||
|
||||
// I2C CR1 Control Register #1 bit definitions for convenience
|
||||
// #define I2C_CR1_SWRST (1<<15) // Software reset - places peripheral under reset
|
||||
// #define I2C_CR1_ALERT (1<<13) // SMBus alert assertion
|
||||
// #define I2C_CR1_PEC (1<<12) // Packet Error Checking transfer in progress
|
||||
// #define I2C_CR1_POS (1<<11) // Acknowledge/PEC Postion (for data reception in PEC mode)
|
||||
// #define I2C_CR1_ACK (1<<10) // Acknowledge enable - ACK returned after byte is received (address or data)
|
||||
// #define I2C_CR1_STOP (1<<9) // STOP generated
|
||||
// #define I2C_CR1_START (1<<8) // START generated
|
||||
// #define I2C_CR1_NOSTRETCH (1<<7) // Clock stretching disable (slave mode)
|
||||
// #define I2C_CR1_ENGC (1<<6) // General call (broadcast) enable (address 00h is ACKed)
|
||||
// #define I2C_CR1_ENPEC (1<<5) // PEC Enable
|
||||
// #define I2C_CR1_ENARP (1<<4) // ARP enable (SMBus)
|
||||
// #define I2C_CR1_SMBTYPE (1<<3) // SMBus type, 1=host, 0=device
|
||||
// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C
|
||||
// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable
|
||||
|
||||
/***************************************************************************
|
||||
* Set I2C clock speed register. This should only be called outside of
|
||||
* a transmission. The I2CManagerClass::_setClock() function ensures
|
||||
* that it is only called at the beginning of an I2C transaction.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
|
||||
// Calculate a rise time appropriate to the requested bus speed
|
||||
// Use 10x the rise time spec to enable integer divide of 62.5ns clock period
|
||||
uint16_t t_rise;
|
||||
uint32_t ccr_freq;
|
||||
if (i2cClockSpeed < 200000L) {
|
||||
// i2cClockSpeed = 100000L;
|
||||
t_rise = 0x11; // (1000ns /62.5ns) + 1;
|
||||
}
|
||||
else if (i2cClockSpeed < 800000L)
|
||||
{
|
||||
i2cClockSpeed = 400000L;
|
||||
t_rise = 0x06; // (300ns / 62.5ns) + 1;
|
||||
// } else if (i2cClockSpeed < 1200000L) {
|
||||
// i2cClockSpeed = 1000000L;
|
||||
// t_rise = 120;
|
||||
}
|
||||
else
|
||||
{
|
||||
i2cClockSpeed = 100000L;
|
||||
t_rise = 0x11; // (1000ns /62.5ns) + 1;
|
||||
}
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 &= ~(I2C_CR1_PE); // Enable I2C
|
||||
// Software reset the I2C peripheral
|
||||
// s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
// Release reset
|
||||
// s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
|
||||
// Calculate baudrate - using a rise time appropriate for the speed
|
||||
ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2;
|
||||
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
// Bit 14: Duty, fast mode duty cycle
|
||||
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
|
||||
s->CCR = (uint16_t)ccr_freq;
|
||||
|
||||
// Configure the rise time register
|
||||
s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initialise I2C registers.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_init()
|
||||
{
|
||||
//Setting up the clocks
|
||||
RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK
|
||||
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
|
||||
// Standard I2C pins are SCL on PB8 and SDA on PB9
|
||||
// Bits (17:16)= 1:0 --> Alternate Function for Pin PB8;
|
||||
// Bits (19:18)= 1:0 --> Alternate Function for Pin PB9
|
||||
GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function
|
||||
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
|
||||
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
|
||||
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
|
||||
// Alt Function High register routing pins PB8 and PB9 for I2C1:
|
||||
// Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8
|
||||
// Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9
|
||||
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
|
||||
|
||||
// Software reset the I2C peripheral
|
||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
|
||||
// Program the peripheral input clock in CR2 Register in order to generate correct timings
|
||||
s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// Setting NVIC
|
||||
NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities
|
||||
NVIC_EnableIRQ(I2C_IRQn);
|
||||
|
||||
// CR2 Interrupt Settings
|
||||
// Bit 15-13: reserved
|
||||
// Bit 12: LAST - DMA last transfer
|
||||
// Bit 11: DMAEN - DMA enable
|
||||
// Bit 10: ITBUFEN - Buffer interrupt enable
|
||||
// Bit 9: ITEVTEN - Event interrupt enable
|
||||
// Bit 8: ITERREN - Error interrupt enable
|
||||
// Bit 7-6: reserved
|
||||
// Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz)
|
||||
// s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts
|
||||
s->CR2 |= 0x0300; // Enable Event and Error interrupts
|
||||
#endif
|
||||
|
||||
// Calculate baudrate and set default rate for now
|
||||
// Configure the Clock Control Register for 100KHz SCL frequency
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
// Bit 14: Duty, fast mode duty cycle
|
||||
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
|
||||
s->CCR = 0x0050;
|
||||
|
||||
// Configure the rise time register - max allowed in 1000ns
|
||||
s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
// Setting bus idle mode and wait for sync
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a start bit for transmission.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStart() {
|
||||
|
||||
// Set counters here in case this is a retry.
|
||||
rxCount = txCount = 0;
|
||||
uint8_t temp;
|
||||
|
||||
// On a single-master I2C bus, the start bit won't be sent until the bus
|
||||
// state goes to IDLE so we can request it without waiting. On a
|
||||
// multi-master bus, the bus may be BUSY under control of another master,
|
||||
// in which case we can avoid some arbitration failures by waiting until
|
||||
// the bus state is IDLE. We don't do that here.
|
||||
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||
{
|
||||
// Send start for read operation
|
||||
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
|
||||
s->CR1 |= I2C_CR1_START; // Generate START
|
||||
// Send address with read flag (1) or'd in
|
||||
s->DR = (deviceAddress << 1) | 1; // send the address
|
||||
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
|
||||
// Special case for 1 byte reads!
|
||||
if (bytesToReceive == 1)
|
||||
{
|
||||
s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit
|
||||
temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
|
||||
s->CR1 |= I2C_CR1_STOP; // Stop I2C
|
||||
}
|
||||
else
|
||||
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
|
||||
}
|
||||
else {
|
||||
// Send start for write operation
|
||||
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
|
||||
s->CR1 |= I2C_CR1_START; // Generate START
|
||||
// Send address with write flag (0) or'd in
|
||||
s->DR = (deviceAddress << 1) | 0; // send the address
|
||||
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
|
||||
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a stop bit for transmission (does not interrupt)
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStop() {
|
||||
s->CR1 |= I2C_CR1_STOP; // Stop I2C
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Close I2C down
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_close() {
|
||||
I2C_sendStop();
|
||||
// Disable the I2C master mode and wait for sync
|
||||
s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral
|
||||
// Should never happen, but wait for up to 500us only.
|
||||
unsigned long startTime = micros();
|
||||
while ((s->CR1 && I2C_CR1_PE) != 0) {
|
||||
if (micros() - startTime >= 500UL) break;
|
||||
}
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Main state machine for I2C, called from interrupt handler or,
|
||||
* if I2C_USE_INTERRUPTS isn't defined, from the I2CManagerClass::loop() function
|
||||
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_handleInterrupt() {
|
||||
|
||||
if (s->SR1 && I2C_SR1_ARLO) {
|
||||
// Arbitration lost, restart
|
||||
I2C_sendStart(); // Reinitiate request
|
||||
} else if (s->SR1 && I2C_SR1_BERR) {
|
||||
// Bus error
|
||||
completionStatus = I2C_STATUS_BUS_ERROR;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} else if (s->SR1 && I2C_SR1_TXE) {
|
||||
// Master write completed
|
||||
if (s->SR1 && (1<<10)) {
|
||||
// Nacked, send stop.
|
||||
I2C_sendStop();
|
||||
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} else if (bytesToSend) {
|
||||
// Acked, so send next byte
|
||||
s->DR = sendBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
} else if (bytesToReceive) {
|
||||
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
|
||||
// s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
|
||||
} else {
|
||||
// Check both TxE/BTF == 1 before generating stop
|
||||
while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE
|
||||
while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF
|
||||
// No more data to send/receive. Initiate a STOP condition and finish
|
||||
I2C_sendStop();
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
} else if (s->SR1 && I2C_SR1_RXNE) {
|
||||
// Master read completed without errors
|
||||
if (bytesToReceive == 1) {
|
||||
// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
|
||||
I2C_sendStop(); // send stop
|
||||
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
||||
bytesToReceive = 0;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} else if (bytesToReceive) {
|
||||
// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
|
||||
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
||||
bytesToReceive--;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif /* I2CMANAGER_STM32_H */
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
@@ -30,11 +30,19 @@
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
|
||||
// Older versions of Wire don't have setWireTimeout function. AVR does.
|
||||
#ifdef ARDUINO_ARCH_AVR
|
||||
#define WIRE_HAS_TIMEOUT
|
||||
#endif
|
||||
|
||||
/***************************************************************************
|
||||
* Initialise I2C interface software
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::_initialise() {
|
||||
Wire.begin();
|
||||
#if defined(WIRE_HAS_TIMEOUT)
|
||||
Wire.setWireTimeout(_timeout, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -45,20 +53,85 @@ void I2CManagerClass::_setClock(unsigned long i2cClockSpeed) {
|
||||
Wire.setClock(i2cClockSpeed);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Set I2C timeout value in microseconds. The timeout applies to each
|
||||
* Wire call separately, i.e. in a write+read, the timer is reset before the
|
||||
* read is started.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::setTimeout(unsigned long value) {
|
||||
_timeout = value;
|
||||
#if defined(WIRE_HAS_TIMEOUT)
|
||||
Wire.setWireTimeout(value, true);
|
||||
#endif
|
||||
}
|
||||
|
||||
/********************************************************
|
||||
* Helper function for I2C Multiplexer operations
|
||||
********************************************************/
|
||||
#ifdef I2C_EXTENDED_ADDRESS
|
||||
static uint8_t muxSelect(I2CAddress address) {
|
||||
// Select MUX sub bus.
|
||||
I2CMux muxNo = address.muxNumber();
|
||||
I2CSubBus subBus = address.subBus();
|
||||
if (muxNo != I2CMux_None) {
|
||||
Wire.beginTransmission(I2C_MUX_BASE_ADDRESS+muxNo);
|
||||
uint8_t data = (subBus == SubBus_All) ? 0xff :
|
||||
(subBus == SubBus_None) ? 0x00 :
|
||||
#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
|
||||
}
|
||||
return I2C_STATUS_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a write to an I2C device (blocking operation on Wire)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::write(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
|
||||
Wire.beginTransmission(address);
|
||||
if (size > 0) Wire.write(buffer, size);
|
||||
rb->status = Wire.endTransmission();
|
||||
uint8_t I2CManagerClass::write(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
|
||||
uint8_t status, muxStatus;
|
||||
uint8_t retryCount = 0;
|
||||
// If request fails, retry up to the defined limit, unless the NORETRY flag is set
|
||||
// in the request block.
|
||||
do {
|
||||
status = muxStatus = I2C_STATUS_OK;
|
||||
#ifdef I2C_EXTENDED_ADDRESS
|
||||
if (address.muxNumber() != I2CMux_None)
|
||||
muxStatus = muxSelect(address);
|
||||
#endif
|
||||
// Only send new transaction if address is non-zero.
|
||||
if (muxStatus == I2C_STATUS_OK && address != 0) {
|
||||
Wire.beginTransmission(address);
|
||||
if (size > 0) Wire.write(buffer, size);
|
||||
status = Wire.endTransmission();
|
||||
}
|
||||
#ifdef I2C_EXTENDED_ADDRESS
|
||||
// Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected
|
||||
if (_muxCount > 1 && muxStatus == I2C_STATUS_OK
|
||||
&& address.deviceAddress() != 0 && address.muxNumber() != I2CMux_None) {
|
||||
muxSelect({address.muxNumber(), SubBus_None});
|
||||
}
|
||||
if (muxStatus != I2C_STATUS_OK) status = muxStatus;
|
||||
#endif
|
||||
} while (!(status == I2C_STATUS_OK
|
||||
|| ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY));
|
||||
rb->status = status;
|
||||
return I2C_STATUS_OK;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a write from PROGMEM (flash) to an I2C device (blocking operation on Wire)
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
|
||||
uint8_t I2CManagerClass::write_P(I2CAddress address, const uint8_t buffer[], uint8_t size, I2CRB *rb) {
|
||||
uint8_t ramBuffer[size];
|
||||
const uint8_t *p1 = buffer;
|
||||
for (uint8_t i=0; i<size; i++)
|
||||
@@ -70,27 +143,64 @@ uint8_t I2CManagerClass::write_P(uint8_t address, const uint8_t buffer[], uint8_
|
||||
* Initiate a write (optional) followed by a read from the I2C device (blocking operation on Wire)
|
||||
* If fewer than the number of requested bytes are received, status is I2C_STATUS_TRUNCATED.
|
||||
***************************************************************************/
|
||||
uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t readSize,
|
||||
uint8_t I2CManagerClass::read(I2CAddress address, uint8_t readBuffer[], uint8_t readSize,
|
||||
const uint8_t writeBuffer[], uint8_t writeSize, I2CRB *rb)
|
||||
{
|
||||
uint8_t status = I2C_STATUS_OK;
|
||||
uint8_t status, muxStatus;
|
||||
uint8_t nBytes = 0;
|
||||
if (writeSize > 0) {
|
||||
Wire.beginTransmission(address);
|
||||
Wire.write(writeBuffer, writeSize);
|
||||
status = Wire.endTransmission(false); // Don't free bus yet
|
||||
}
|
||||
if (status == I2C_STATUS_OK) {
|
||||
Wire.requestFrom(address, (size_t)readSize);
|
||||
while (Wire.available() && nBytes < readSize)
|
||||
readBuffer[nBytes++] = Wire.read();
|
||||
if (nBytes < readSize) status = I2C_STATUS_TRUNCATED;
|
||||
}
|
||||
uint8_t retryCount = 0;
|
||||
// If request fails, retry up to the defined limit, unless the NORETRY flag is set
|
||||
// in the request block.
|
||||
do {
|
||||
status = muxStatus = I2C_STATUS_OK;
|
||||
#ifdef I2C_EXTENDED_ADDRESS
|
||||
if (address.muxNumber() != I2CMux_None) {
|
||||
muxStatus = muxSelect(address);
|
||||
}
|
||||
#endif
|
||||
// Only start new transaction if address is non-zero.
|
||||
if (muxStatus == I2C_STATUS_OK && address != 0) {
|
||||
if (writeSize > 0) {
|
||||
Wire.beginTransmission(address);
|
||||
Wire.write(writeBuffer, writeSize);
|
||||
status = Wire.endTransmission(false); // Don't free bus yet
|
||||
}
|
||||
if (status == I2C_STATUS_OK) {
|
||||
#ifdef WIRE_HAS_TIMEOUT
|
||||
Wire.clearWireTimeoutFlag();
|
||||
Wire.requestFrom(address, (size_t)readSize);
|
||||
if (!Wire.getWireTimeoutFlag()) {
|
||||
while (Wire.available() && nBytes < readSize)
|
||||
readBuffer[nBytes++] = Wire.read();
|
||||
if (nBytes < readSize) status = I2C_STATUS_TRUNCATED;
|
||||
} else {
|
||||
status = I2C_STATUS_TIMEOUT;
|
||||
}
|
||||
#else
|
||||
Wire.requestFrom(address, (size_t)readSize);
|
||||
while (Wire.available() && nBytes < readSize)
|
||||
readBuffer[nBytes++] = Wire.read();
|
||||
if (nBytes < readSize) status = I2C_STATUS_TRUNCATED;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#ifdef I2C_EXTENDED_ADDRESS
|
||||
// Deselect MUX if there's more than one MUX present, to avoid having multiple ones selected
|
||||
if (_muxCount > 1 && muxStatus == I2C_STATUS_OK && address != 0 && address.muxNumber() != I2CMux_None) {
|
||||
muxSelect({address.muxNumber(), SubBus_None});
|
||||
}
|
||||
if (muxStatus != I2C_STATUS_OK) status = muxStatus;
|
||||
#endif
|
||||
|
||||
} while (!((status == I2C_STATUS_OK)
|
||||
|| ++retryCount > MAX_I2C_RETRIES || rb->operation & OPERATION_NORETRY));
|
||||
|
||||
rb->nBytes = nBytes;
|
||||
rb->status = status;
|
||||
return I2C_STATUS_OK;
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Function to queue a request block and initiate operations.
|
||||
*
|
||||
@@ -100,7 +210,7 @@ uint8_t I2CManagerClass::read(uint8_t address, uint8_t readBuffer[], uint8_t rea
|
||||
* the non-blocking version.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
switch (req->operation) {
|
||||
switch (req->operation & OPERATION_MASK) {
|
||||
case OPERATION_READ:
|
||||
read(req->i2cAddress, req->readBuffer, req->readLen, NULL, 0, req);
|
||||
break;
|
||||
@@ -121,8 +231,4 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::loop() {}
|
||||
|
||||
// Loop function
|
||||
void I2CManagerClass::checkForTimeout() {}
|
||||
|
||||
|
||||
#endif
|
120
IODevice.cpp
120
IODevice.cpp
@@ -48,12 +48,14 @@ extern __attribute__((weak)) void exrailHalSetup();
|
||||
// Create any standard device instances that may be required, such as the Arduino pins
|
||||
// and PCA9685.
|
||||
void IODevice::begin() {
|
||||
// Initialise the IO subsystem defaults
|
||||
ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access
|
||||
|
||||
// Call user's halSetup() function (if defined in the build in myHal.cpp).
|
||||
// The contents will depend on the user's system hardware configuration.
|
||||
// The myHal.cpp file is a standard C++ module so has access to all of the DCC++EX APIs.
|
||||
|
||||
// This is done first so that the following defaults will detect an overlap and not
|
||||
// create something that conflicts with the users vpin definitions.
|
||||
// This is done early so that the subsequent defaults will detect an overlap and not
|
||||
// create something that conflicts with the user's vpin definitions.
|
||||
if (halSetup)
|
||||
halSetup();
|
||||
|
||||
@@ -61,8 +63,6 @@ void IODevice::begin() {
|
||||
if (exrailHalSetup)
|
||||
exrailHalSetup();
|
||||
|
||||
// Initialise the IO subsystem defaults
|
||||
ArduinoPins::create(2, NUM_DIGITAL_PINS-2); // Reserve pins for direct access
|
||||
// Predefine two PCA9685 modules 0x40-0x41
|
||||
// Allocates 32 pins 100-131
|
||||
PCA9685::create(100, 16, 0x40);
|
||||
@@ -72,12 +72,18 @@ void IODevice::begin() {
|
||||
// Allocates 32 pins 164-195
|
||||
MCP23017::create(164, 16, 0x20);
|
||||
MCP23017::create(180, 16, 0x21);
|
||||
}
|
||||
|
||||
// Call the begin() methods of each configured device in turn
|
||||
for (IODevice *dev=_firstDevice; dev!=NULL; dev = dev->_nextDevice) {
|
||||
// reset() function to reinitialise all devices
|
||||
void IODevice::reset() {
|
||||
unsigned long currentMicros = micros();
|
||||
for (IODevice *dev = _firstDevice; dev != NULL; dev = dev->_nextDevice) {
|
||||
dev->_deviceState = DEVSTATE_DORMANT;
|
||||
// First ensure that _loop isn't delaying
|
||||
dev->delayUntil(currentMicros);
|
||||
// Then invoke _begin to restart driver
|
||||
dev->_begin();
|
||||
}
|
||||
_initPhase = false;
|
||||
}
|
||||
|
||||
// Overarching static loop() method for the IODevice subsystem. Works through the
|
||||
@@ -109,18 +115,19 @@ void IODevice::loop() {
|
||||
|
||||
// Report loop time if diags enabled
|
||||
#if defined(DIAG_LOOPTIMES)
|
||||
unsigned long diagMicros = micros();
|
||||
static unsigned long lastMicros = 0;
|
||||
// Measure time since loop() method started.
|
||||
unsigned long halElapsed = micros() - currentMicros;
|
||||
// Measure time between loop() method entries.
|
||||
unsigned long elapsed = currentMicros - lastMicros;
|
||||
// Measure time since HAL's loop() method started.
|
||||
unsigned long halElapsed = diagMicros - currentMicros;
|
||||
// Measure time between loop() method entries (excluding this diagnostic).
|
||||
unsigned long elapsed = diagMicros - lastMicros;
|
||||
static unsigned long maxElapsed = 0, maxHalElapsed = 0;
|
||||
static unsigned long lastOutputTime = 0;
|
||||
static unsigned long halTotal = 0, total = 0;
|
||||
static unsigned long count = 0;
|
||||
const unsigned long interval = (unsigned long)5 * 1000 * 1000; // 5 seconds in microsec
|
||||
|
||||
// Ignore long loop counts while message is still outputting
|
||||
// Ignore long loop counts while message is still outputting (~3 milliseconds)
|
||||
if (currentMicros - lastOutputTime > 3000UL) {
|
||||
if (elapsed > maxElapsed) maxElapsed = elapsed;
|
||||
if (halElapsed > maxHalElapsed) maxHalElapsed = halElapsed;
|
||||
@@ -128,14 +135,16 @@ void IODevice::loop() {
|
||||
total += elapsed;
|
||||
count++;
|
||||
}
|
||||
if (currentMicros - lastOutputTime > interval) {
|
||||
if (diagMicros - lastOutputTime > interval) {
|
||||
if (lastOutputTime > 0)
|
||||
DIAG(F("Loop Total:%lus (%lus max) HAL:%lus (%lus max)"),
|
||||
total/count, maxElapsed, halTotal/count, maxHalElapsed);
|
||||
maxElapsed = maxHalElapsed = total = halTotal = count = 0;
|
||||
lastOutputTime = currentMicros;
|
||||
lastOutputTime = diagMicros;
|
||||
}
|
||||
lastMicros = currentMicros;
|
||||
// Read microsecond count after calculations, so they aren't
|
||||
// included in the overall timings.
|
||||
lastMicros = micros();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -258,25 +267,27 @@ void IODevice::setGPIOInterruptPin(int16_t pinNumber) {
|
||||
_gpioInterruptPin = pinNumber;
|
||||
}
|
||||
|
||||
// Private helper function to add a device to the chain of devices.
|
||||
void IODevice::addDevice(IODevice *newDevice) {
|
||||
// Link new object to the end of the chain. Thereby, the first devices to be declared/created
|
||||
// will be located faster by findDevice than those which are created later.
|
||||
// Ideally declare/create the digital IO pins first, then servos, then more esoteric devices.
|
||||
IODevice *lastDevice;
|
||||
if (_firstDevice == 0)
|
||||
// Helper function to add a new device to the device chain. If
|
||||
// slaveDevice is NULL then the device is added to the end of the chain.
|
||||
// Otherwise, the chain is searched for slaveDevice and the new device linked
|
||||
// in front of it (to support filter devices that share the same VPIN range
|
||||
// as the devices they control). If slaveDevice isn't found, then the
|
||||
// device is linked to the end of the chain.
|
||||
void IODevice::addDevice(IODevice *newDevice, IODevice *slaveDevice /* = NULL */) {
|
||||
if (slaveDevice == _firstDevice) {
|
||||
newDevice->_nextDevice = _firstDevice;
|
||||
_firstDevice = newDevice;
|
||||
else {
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice)
|
||||
lastDevice = dev;
|
||||
lastDevice->_nextDevice = newDevice;
|
||||
} else {
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
if (dev->_nextDevice == slaveDevice || dev->_nextDevice == NULL) {
|
||||
// Link new device between dev and slaveDevice (or at end of chain)
|
||||
newDevice->_nextDevice = dev->_nextDevice;
|
||||
dev->_nextDevice = newDevice;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
newDevice->_nextDevice = 0;
|
||||
|
||||
// If the IODevice::begin() method has already been called, initialise device here. If not,
|
||||
// the device's _begin() method will be called by IODevice::begin().
|
||||
if (!_initPhase)
|
||||
newDevice->_begin();
|
||||
newDevice->_begin();
|
||||
}
|
||||
|
||||
// Private helper function to locate a device by VPIN. Returns NULL if not found.
|
||||
@@ -290,28 +301,40 @@ IODevice *IODevice::findDevice(VPIN vpin) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Instance helper function for filter devices (layered over others). Looks for
|
||||
// a device that is further down the chain than the current device.
|
||||
IODevice *IODevice::findDeviceFollowing(VPIN vpin) {
|
||||
for (IODevice *dev = _nextDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
VPIN firstVpin = dev->_firstVpin;
|
||||
if (vpin >= firstVpin && vpin < firstVpin+dev->_nPins)
|
||||
return dev;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Private helper function to check for vpin overlap. Run during setup only.
|
||||
// returns true if pins DONT overlap with existing device
|
||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress) {
|
||||
bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, I2CAddress i2cAddress) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("Check no overlap %d %d 0x%x"), firstPin,nPins,i2cAddress);
|
||||
DIAG(F("Check no overlap %d %d %s"), firstPin,nPins,i2cAddress.toString());
|
||||
#endif
|
||||
VPIN lastPin=firstPin+nPins-1;
|
||||
for (IODevice *dev = _firstDevice; dev != 0; dev = dev->_nextDevice) {
|
||||
|
||||
// check for pin range overlaps (verbose but compiler will fix that)
|
||||
VPIN firstDevPin=dev->_firstVpin;
|
||||
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."),
|
||||
firstPin, lastPin);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (nPins > 0 && dev->_nPins > 0) {
|
||||
// check for pin range overlaps (verbose but compiler will fix that)
|
||||
VPIN firstDevPin=dev->_firstVpin;
|
||||
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."),
|
||||
firstPin, lastPin);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// Check for overlapping I2C address
|
||||
if (i2cAddress && dev->_I2CAddress==i2cAddress) {
|
||||
DIAG(F("WARNING HAL Overlap. i2c Addr 0x%x ignored."),i2cAddress);
|
||||
DIAG(F("WARNING HAL Overlap. i2c Addr %s ignored."),i2cAddress.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -326,15 +349,12 @@ bool IODevice::checkNoOverlap(VPIN firstPin, uint8_t nPins, uint8_t i2cAddress)
|
||||
// Chain of callback blocks (identifying registered callback functions for state changes)
|
||||
IONotifyCallback *IONotifyCallback::first = 0;
|
||||
|
||||
// Start of chain of devices.
|
||||
// Start and end of chain of devices.
|
||||
IODevice *IODevice::_firstDevice = 0;
|
||||
|
||||
// Reference to next device to be called on _loop() method.
|
||||
IODevice *IODevice::_nextLoopDevice = 0;
|
||||
|
||||
// Flag which is reset when IODevice::begin has been called.
|
||||
bool IODevice::_initPhase = true;
|
||||
|
||||
|
||||
//==================================================================================================================
|
||||
// Instance members
|
||||
|
134
IODevice.h
134
IODevice.h
@@ -1,4 +1,5 @@
|
||||
/*
|
||||
* © 2023, Paul Antoine, Discord user @ADUBOURG
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
@@ -93,6 +94,8 @@ public:
|
||||
CONFIGURE_INPUT = 1,
|
||||
CONFIGURE_SERVO = 2,
|
||||
CONFIGURE_OUTPUT = 3,
|
||||
CONFIGURE_ANALOGOUTPUT = 4,
|
||||
CONFIGURE_ANALOGINPUT = 5,
|
||||
} ConfigTypeEnum;
|
||||
|
||||
typedef enum : uint8_t {
|
||||
@@ -110,6 +113,10 @@ public:
|
||||
// Also, the _begin method of any existing instances is called from here.
|
||||
static void begin();
|
||||
|
||||
// reset function to invoke all driver's _begin() methods again, to
|
||||
// reset the state of the devices and reinitialise.
|
||||
static void reset();
|
||||
|
||||
// configure is used invoke an IODevice instance's _configure method
|
||||
static bool configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]);
|
||||
|
||||
@@ -161,25 +168,12 @@ public:
|
||||
// once the GPIO port concerned has been read.
|
||||
void setGPIOInterruptPin(int16_t pinNumber);
|
||||
|
||||
|
||||
protected:
|
||||
|
||||
// Constructor
|
||||
IODevice(VPIN firstVpin=0, int nPins=0) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_nextEntryTime = 0;
|
||||
_I2CAddress=0;
|
||||
}
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, I2CAddress i2cAddress=0);
|
||||
|
||||
// Method to perform initialisation of the device (optionally implemented within device class)
|
||||
virtual void _begin() {}
|
||||
|
||||
// Method to configure device (optionally implemented within device class)
|
||||
virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
|
||||
(void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning.
|
||||
return false;
|
||||
};
|
||||
// Method used by IODevice filters to locate slave pins that may be overlayed by their own
|
||||
// pin range.
|
||||
IODevice *findDeviceFollowing(VPIN vpin);
|
||||
|
||||
// Method to write new state (optionally implemented within device class)
|
||||
virtual void _write(VPIN vpin, int value) {
|
||||
@@ -187,7 +181,7 @@ protected:
|
||||
};
|
||||
|
||||
// Method to write an 'analogue' value (optionally implemented within device class)
|
||||
virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) {
|
||||
virtual void _writeAnalogue(VPIN vpin, int value, uint8_t param1=0, uint16_t param2=0) {
|
||||
(void)vpin; (void)value; (void) param1; (void)param2;
|
||||
};
|
||||
|
||||
@@ -202,6 +196,29 @@ protected:
|
||||
(void)vpin;
|
||||
return 0;
|
||||
};
|
||||
|
||||
protected:
|
||||
|
||||
// Constructor
|
||||
IODevice(VPIN firstVpin=0, int nPins=0) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_nextEntryTime = 0;
|
||||
_I2CAddress=0;
|
||||
}
|
||||
|
||||
// Method to perform initialisation of the device (optionally implemented within device class)
|
||||
virtual void _begin() {}
|
||||
|
||||
// Method to check whether the vpin corresponds to this device
|
||||
bool owns(VPIN vpin);
|
||||
|
||||
// Method to configure device (optionally implemented within device class)
|
||||
virtual bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
|
||||
(void)vpin; (void)configType; (void)paramCount; (void)params; // Suppress compiler warning.
|
||||
return false;
|
||||
};
|
||||
|
||||
virtual int _configureAnalogIn(VPIN vpin) {
|
||||
(void)vpin;
|
||||
return 0;
|
||||
@@ -226,7 +243,7 @@ protected:
|
||||
// Common object fields.
|
||||
VPIN _firstVpin;
|
||||
int _nPins;
|
||||
uint8_t _I2CAddress;
|
||||
I2CAddress _I2CAddress;
|
||||
// Flag whether the device supports callbacks.
|
||||
bool _hasCallback = false;
|
||||
|
||||
@@ -234,26 +251,21 @@ protected:
|
||||
// pin low if an input changes state.
|
||||
int16_t _gpioInterruptPin = -1;
|
||||
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
|
||||
|
||||
// Static support function for subclass creation
|
||||
static void addDevice(IODevice *newDevice);
|
||||
static void addDevice(IODevice *newDevice, IODevice *slaveDevice = NULL);
|
||||
|
||||
// Method to find device handling Vpin
|
||||
static IODevice *findDevice(VPIN vpin);
|
||||
|
||||
// Current state of device
|
||||
DeviceStateEnum _deviceState = DEVSTATE_DORMANT;
|
||||
|
||||
private:
|
||||
// Method to check whether the vpin corresponds to this device
|
||||
bool owns(VPIN vpin);
|
||||
// Method to find device handling Vpin
|
||||
static IODevice *findDevice(VPIN vpin);
|
||||
IODevice *_nextDevice = 0;
|
||||
unsigned long _nextEntryTime;
|
||||
static IODevice *_firstDevice;
|
||||
|
||||
static IODevice *_nextLoopDevice;
|
||||
static bool _initPhase;
|
||||
};
|
||||
|
||||
|
||||
@@ -264,7 +276,7 @@ private:
|
||||
|
||||
class PCA9685 : public IODevice {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress);
|
||||
static void create(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50);
|
||||
enum ProfileType : uint8_t {
|
||||
Instant = 0, // Moves immediately between positions (if duration not specified)
|
||||
UseDuration = 0, // Use specified duration
|
||||
@@ -277,7 +289,7 @@ public:
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
PCA9685(VPIN vpin, int nPins, uint8_t I2CAddress);
|
||||
PCA9685(VPIN vpin, int nPins, I2CAddress i2cAddress, uint16_t frequency);
|
||||
// Device-specific initialisation
|
||||
void _begin() override;
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override;
|
||||
@@ -307,13 +319,14 @@ private:
|
||||
struct ServoData *_servoData [16];
|
||||
|
||||
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
|
||||
static const byte FLASH _bounceProfile[30];
|
||||
static const uint8_t FLASH _bounceProfile[30];
|
||||
|
||||
const unsigned int refreshInterval = 50; // refresh every 50ms
|
||||
|
||||
// structures for setting up non-blocking writes to servo controller
|
||||
I2CRB requestBlock;
|
||||
uint8_t outputBuffer[5];
|
||||
uint8_t prescaler; // clock prescaler for setting PWM frequency
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -377,9 +390,9 @@ private:
|
||||
|
||||
class EXTurntable : public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t I2CAddress);
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress I2CAddress);
|
||||
// Constructor
|
||||
EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress);
|
||||
EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress);
|
||||
enum ActivityNumber : uint8_t {
|
||||
Turn = 0, // Rotate turntable, maintain phase
|
||||
Turn_PInvert = 1, // Rotate turntable, invert phase
|
||||
@@ -405,8 +418,61 @@ private:
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
||||
// IODevice framework for invoking user-written functions.
|
||||
// To use, define a function that you want to be regularly
|
||||
// invoked, and then create an instance of UserAddin.
|
||||
// For example, you can show the status, on screen 3, of the first eight
|
||||
// locos in the speed table:
|
||||
//
|
||||
// void updateLocoScreen() {
|
||||
// for (int i=0; i<8; i++) {
|
||||
// if (DCC::speedTable[i].loco > 0) {
|
||||
// int speed = DCC::speedTable[i].speedCode;
|
||||
// SCREEN(3, i, F("Loco:%4d %3d %c"), DCC::speedTable[i].loco,
|
||||
// speed & 0x7f, speed & 0x80 ? 'R' : 'F');
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// void halSetup() {
|
||||
// ...
|
||||
// UserAddin(updateLocoScreen, 1000); // Update every 1000ms
|
||||
// ...
|
||||
// }
|
||||
//
|
||||
class UserAddin : public IODevice {
|
||||
private:
|
||||
void (*_invokeUserFunction)();
|
||||
int _delay; // milliseconds
|
||||
public:
|
||||
UserAddin(void (*func)(), int delay) {
|
||||
_invokeUserFunction = func;
|
||||
_delay = delay;
|
||||
addDevice(this);
|
||||
}
|
||||
// userFunction has no return value, no parameter. delay is in milliseconds.
|
||||
static void create(void (*userFunction)(), int delay) {
|
||||
new UserAddin(userFunction, delay);
|
||||
}
|
||||
protected:
|
||||
void _begin() { _display(); }
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
_invokeUserFunction();
|
||||
// _loop won't be called again until _delay ms have elapsed.
|
||||
delayUntil(currentMicros + _delay * 1000UL);
|
||||
}
|
||||
void _display() override {
|
||||
DIAG(F("UserAddin run every %dms"), _delay);
|
||||
}
|
||||
};
|
||||
|
||||
#include "IO_MCP23008.h"
|
||||
#include "IO_MCP23017.h"
|
||||
#include "IO_PCF8574.h"
|
||||
#include "IO_PCF8575.h"
|
||||
#include "IO_duinoNodes.h"
|
||||
#include "IO_EXIOExpander.h"
|
||||
|
||||
|
||||
#endif // iodevice_h
|
||||
|
@@ -59,28 +59,33 @@
|
||||
**********************************************************************************************/
|
||||
class ADS111x: public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
if (checkNoOverlap(firstVpin,nPins,i2cAddress)) new ADS111x(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
private:
|
||||
ADS111x(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
ADS111x(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins,4);
|
||||
_i2cAddress = i2cAddress;
|
||||
_nPins = (nPins > 4) ? 4 : nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
_currentPin = 0;
|
||||
for (int8_t i=0; i<_nPins; i++)
|
||||
_value[i] = -1;
|
||||
addDevice(this);
|
||||
}
|
||||
void _begin() {
|
||||
// Initialise I2C
|
||||
I2CManager.begin();
|
||||
// ADS111x support high-speed I2C (4.3MHz) but that requires special
|
||||
// processing. So stick to fast mode (400kHz maximum).
|
||||
I2CManager.setClock(400000);
|
||||
// Initialise ADS device
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
_nextState = STATE_STARTSCAN;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("ADS111x device not found, I2C:%x"), _i2cAddress);
|
||||
DIAG(F("ADS111x device not found, I2C:%s"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -98,7 +103,7 @@ private:
|
||||
_outBuffer[1] = 0xC0 + (_currentPin << 4); // Trigger single-shot, channel n
|
||||
_outBuffer[2] = 0xA3; // 250 samples/sec, comparator off
|
||||
// Write command, without waiting for completion.
|
||||
I2CManager.write(_i2cAddress, _outBuffer, 3, &_i2crb);
|
||||
I2CManager.write(_I2CAddress, _outBuffer, 3, &_i2crb);
|
||||
|
||||
delayUntil(currentMicros + scanInterval);
|
||||
_nextState = STATE_STARTREAD;
|
||||
@@ -107,7 +112,7 @@ private:
|
||||
case STATE_STARTREAD:
|
||||
// Reading the pin value
|
||||
_outBuffer[0] = 0x00; // Conversion register address
|
||||
I2CManager.read(_i2cAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register
|
||||
I2CManager.read(_I2CAddress, _inBuffer, 2, _outBuffer, 1, &_i2crb); // Read register
|
||||
_nextState = STATE_GETVALUE;
|
||||
break;
|
||||
|
||||
@@ -126,7 +131,7 @@ private:
|
||||
break;
|
||||
}
|
||||
} else { // error status
|
||||
DIAG(F("ADS111x I2C:x%d Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status));
|
||||
DIAG(F("ADS111x I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
@@ -137,7 +142,7 @@ private:
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("ADS111x I2C:x%x Configured on Vpins:%d-%d %S"), _i2cAddress, _firstVpin, _firstVpin+_nPins-1,
|
||||
DIAG(F("ADS111x I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
@@ -154,7 +159,6 @@ private:
|
||||
STATE_GETVALUE,
|
||||
};
|
||||
uint16_t _value[4];
|
||||
uint8_t _i2cAddress;
|
||||
uint8_t _outBuffer[3];
|
||||
uint8_t _inBuffer[2];
|
||||
uint8_t _currentPin; // ADC pin currently being scanned
|
||||
|
@@ -70,10 +70,12 @@
|
||||
|
||||
class DFPlayer : public IODevice {
|
||||
private:
|
||||
const uint8_t MAXVOLUME=30;
|
||||
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.
|
||||
@@ -179,41 +181,45 @@ 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 {
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: VPIN:%d FileNo:%d Volume:%d"), vpin, value, volume);
|
||||
#endif
|
||||
|
||||
// Validate parameter.
|
||||
volume = min((uint8_t)30,volume);
|
||||
if (volume > MAXVOLUME) volume = MAXVOLUME;
|
||||
|
||||
if (pin == 0) {
|
||||
// Play track
|
||||
if (value > 0) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Play %d"), value);
|
||||
#endif
|
||||
sendPacket(0x03, value); // Play track
|
||||
_playing = true;
|
||||
if (volume > 0) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Volume %d"), volume);
|
||||
#endif
|
||||
sendPacket(0x06, volume); // Set volume
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Stop"));
|
||||
#endif
|
||||
sendPacket(0x16); // Stop play
|
||||
_playing = false;
|
||||
}
|
||||
} else if (pin == 1) {
|
||||
// Set volume (0-30)
|
||||
if (value > 30) value = 30;
|
||||
else if (value < 0) value = 0;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("DFPlayer: Volume %d"), value);
|
||||
#endif
|
||||
sendPacket(0x06, value);
|
||||
sendPacket(0x06, value);
|
||||
_lastVolumeLevel = volume;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -261,7 +267,7 @@ private:
|
||||
// Output some pad characters to add an
|
||||
// artificial delay between commands
|
||||
for (int i=0; i<numPadCharacters; i++)
|
||||
_serial->write(0);
|
||||
_serial->write((uint8_t)0);
|
||||
}
|
||||
|
||||
// Now output the command
|
||||
|
125
IO_EXFastclock.h
Normal file
125
IO_EXFastclock.h
Normal file
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* © 2022, Colin Murdoch. 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_EXFastclock device driver is used to interface the standalone fast clock and receive time data.
|
||||
*
|
||||
* The EX-fastClock code lives in a separate repo (https://github.com/DCC-EX/EX-Fastclock) and contains the clock logic.
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_EXFastclock_h
|
||||
#define IO_EXFastclock_h
|
||||
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "CommandDistributor.h"
|
||||
|
||||
bool FAST_CLOCK_EXISTS = true;
|
||||
|
||||
class EXFastClock : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
EXFastClock(I2CAddress i2cAddress){
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
else {
|
||||
FAST_CLOCK_EXISTS = false;
|
||||
//DIAG(F("No Fast Clock found"));
|
||||
LCD(6,F("CLOCK NOT FOUND"));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
|
||||
// Initialisation of Fastclock
|
||||
void _begin() override {
|
||||
|
||||
if (FAST_CLOCK_EXISTS == true) {
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
//LCD(6,F("CLOCK NOT FOUND"));
|
||||
DIAG(F("Fast Clock Not Found at address %s"), _I2CAddress.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Processing loop to obtain clock time
|
||||
|
||||
void _loop(unsigned long currentMicros) override{
|
||||
|
||||
if (FAST_CLOCK_EXISTS==true) {
|
||||
uint8_t readBuffer[3];
|
||||
byte a,b;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
I2CManager.read(_I2CAddress, readBuffer, 3);
|
||||
// XXXX change this to save a few bytes
|
||||
a = readBuffer[0];
|
||||
b = readBuffer[1];
|
||||
//_clocktime = (a << 8) + b;
|
||||
//_clockrate = readBuffer[2];
|
||||
|
||||
CommandDistributor::setClockTime(((a << 8) + b), readBuffer[2], 1);
|
||||
//setClockTime(int16_t clocktime, int8_t clockrate, byte opt);
|
||||
|
||||
// As the minimum clock increment is 2 seconds delay a bit - say 1 sec.
|
||||
// Clock interval is 60/ clockspeed i.e 60/b seconds
|
||||
delayUntil(currentMicros + ((60/b) * 1000000));
|
||||
|
||||
#endif
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// Display EX-FastClock device driver info.
|
||||
void _display() override {
|
||||
DIAG(F("FastCLock on I2C:%s - %S"), _I2CAddress.toString(), (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
264
IO_EXIOExpander.h
Normal file
264
IO_EXIOExpander.h
Normal file
@@ -0,0 +1,264 @@
|
||||
/*
|
||||
* © 2022, Peter Cole. All rights reserved.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_EXIOExpander.h device driver integrates with one or more EX-IOExpander devices.
|
||||
* This device driver will configure the device on startup, along with
|
||||
* interacting with the device for all input/output duties.
|
||||
*
|
||||
* To create EX-IOExpander devices, these are defined in myHal.cpp:
|
||||
* (Note the device driver is included by default)
|
||||
*
|
||||
* void halSetup() {
|
||||
* // EXIOExpander::create(vpin, num_vpins, i2c_address);
|
||||
* EXIOExpander::create(800, 18, 0x65);
|
||||
* }
|
||||
*
|
||||
* All pins on an EX-IOExpander device are allocated according to the pin map for the specific
|
||||
* 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).
|
||||
*/
|
||||
|
||||
#ifndef IO_EX_IOEXPANDER_H
|
||||
#define IO_EX_IOEXPANDER_H
|
||||
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for EX-IOExpander.
|
||||
*/
|
||||
class EXIOExpander : public IODevice {
|
||||
public:
|
||||
|
||||
enum ProfileType : uint8_t {
|
||||
Instant = 0, // Moves immediately between positions (if duration not specified)
|
||||
UseDuration = 0, // Use specified duration
|
||||
Fast = 1, // Takes around 500ms end-to-end
|
||||
Medium = 2, // 1 second end-to-end
|
||||
Slow = 3, // 2 seconds end-to-end
|
||||
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
|
||||
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
|
||||
};
|
||||
|
||||
static void create(VPIN vpin, int nPins, I2CAddress i2cAddress) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new EXIOExpander(vpin, nPins, i2cAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
EXIOExpander(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_i2cAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
void _begin() {
|
||||
// Initialise EX-IOExander device
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
_command4Buffer[0] = EXIOINIT;
|
||||
_command4Buffer[1] = _nPins;
|
||||
_command4Buffer[2] = _firstVpin & 0xFF;
|
||||
_command4Buffer[3] = _firstVpin >> 8;
|
||||
// Send config, if EXIOPINS returned, we're good, setup pin buffers, otherwise go offline
|
||||
I2CManager.read(_i2cAddress, _receive3Buffer, 3, _command4Buffer, 4, &_i2crb);
|
||||
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;
|
||||
}
|
||||
// We now need to retrieve the analogue pin map
|
||||
_command1Buffer[0] = EXIOINITA;
|
||||
I2CManager.read(_i2cAddress, _analoguePinMap, _numAnaloguePins, _command1Buffer, 1, &_i2crb);
|
||||
// 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, &_i2crb);
|
||||
_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]);
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("EX-IOExpander device not found, I2C:%s"), _i2cAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if in use
|
||||
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, &_i2crb);
|
||||
if (_command1Buffer[0] == EXIORDY) {
|
||||
return true;
|
||||
} else {
|
||||
DIAG(F("Vpin %d cannot be used as a digital input pin"), (int)vpin);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Analogue input pin configuration, used to enable on EX-IOExpander device
|
||||
int _configureAnalogIn(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
_command2Buffer[0] = EXIOENAN;
|
||||
_command2Buffer[1] = pin;
|
||||
I2CManager.read(_i2cAddress, _command1Buffer, 1, _command2Buffer, 2, &_i2crb);
|
||||
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;
|
||||
}
|
||||
|
||||
// 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, &_i2crb);
|
||||
_command1Buffer[0] = EXIORDAN;
|
||||
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1, &_i2crb);
|
||||
}
|
||||
|
||||
// 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 _pinMSBByte = _pinLSBByte + 1;
|
||||
return (_analogueInputStates[_pinMSBByte] << 8) + _analogueInputStates[_pinLSBByte];
|
||||
}
|
||||
|
||||
// Obtain the correct digital input value
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
int pin = vpin - _firstVpin;
|
||||
uint8_t pinByte = pin / 8;
|
||||
bool value = bitRead(_digitalInputStates[pinByte], pin - pinByte * 8);
|
||||
return value;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
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, &_i2crb);
|
||||
if (_command1Buffer[0] != EXIORDY) {
|
||||
DIAG(F("Vpin %d cannot be used as a digital output pin"), (int)vpin);
|
||||
}
|
||||
}
|
||||
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) override {
|
||||
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"),
|
||||
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, &_i2crb);
|
||||
if (_command1Buffer[0] != EXIORDY) {
|
||||
DIAG(F("Vpin %d cannot be used as a servo/PWM pin"), (int)vpin);
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("EX-IOExpander I2C:%s v%d.%d.%d Vpins %d-%d %S"),
|
||||
_i2cAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
I2CAddress _i2cAddress;
|
||||
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* _analoguePinMap;
|
||||
I2CRB _i2crb;
|
||||
|
||||
// EX-IOExpander protocol flags
|
||||
enum {
|
||||
EXIOINIT = 0xE0, // Flag to initialise setup procedure
|
||||
EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup
|
||||
EXIODPUP = 0xE2, // Flag we're sending digital pin pullup configuration
|
||||
EXIOVER = 0xE3, // Flag to get version
|
||||
EXIORDAN = 0xE4, // Flag to read an analogue input
|
||||
EXIOWRD = 0xE5, // Flag for digital write
|
||||
EXIORDD = 0xE6, // Flag to read digital input
|
||||
EXIOENAN = 0xE7, // Flag to enable an analogue pin
|
||||
EXIOINITA = 0xE8, // Flag we're receiving analogue pin mappings
|
||||
EXIOPINS = 0xE9, // Flag we're receiving pin counts for buffers
|
||||
EXIOWRAN = 0xEA, // Flag we're sending an analogue write (PWM)
|
||||
EXIOERR = 0xEF, // Flag we've received an error
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
@@ -35,19 +35,19 @@
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
void EXTurntable::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||
new EXTurntable(firstVpin, nPins, I2CAddress);
|
||||
}
|
||||
|
||||
// Constructor
|
||||
EXTurntable::EXTurntable(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = I2CAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Initialisation of TurntableEX
|
||||
// Initialisation of EXTurntable
|
||||
void EXTurntable::_begin() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
@@ -103,10 +103,10 @@ 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("TurntableEX WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
|
||||
DIAG(F("EX-Turntable WriteAnalogue Vpin:%d Value:%d Activity:%d Duration:%d"),
|
||||
vpin, value, activity, duration);
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress, stepsMSB, stepsLSB, activity);
|
||||
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
||||
#endif
|
||||
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
I2CManager.write(_I2CAddress, 3, 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("TurntableEX I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
DIAG(F("EX-Turntable I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "IO_ExampleSerial.h"
|
||||
#include "FSH.h"
|
||||
|
||||
// Constructor
|
||||
IO_ExampleSerial::IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t));
|
||||
_baud = baud;
|
||||
|
||||
// Save reference to serial port driver
|
||||
_serial = serial;
|
||||
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Static create method for one module.
|
||||
void IO_ExampleSerial::create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
|
||||
if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud);
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
void IO_ExampleSerial::_begin() {
|
||||
_serial->begin(_baud);
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
|
||||
// Send a few # characters to the output
|
||||
for (uint8_t i=0; i<3; i++)
|
||||
_serial->write('#');
|
||||
}
|
||||
|
||||
// Device-specific write function. Write a string in the form "#Wm,n#"
|
||||
// where m is the vpin number, and n is the value.
|
||||
void IO_ExampleSerial::_write(VPIN vpin, int value) {
|
||||
int pin = vpin -_firstVpin;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("IO_ExampleSerial::_write Pin:%d Value:%d"), (int)vpin, value);
|
||||
#endif
|
||||
// Send a command string over the serial line
|
||||
_serial->print('#');
|
||||
_serial->print('W');
|
||||
_serial->print(pin);
|
||||
_serial->print(',');
|
||||
_serial->print(value);
|
||||
_serial->println('#');
|
||||
DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value);
|
||||
}
|
||||
|
||||
// Device-specific read function.
|
||||
int IO_ExampleSerial::_read(VPIN vpin) {
|
||||
|
||||
// Return a value for the specified vpin.
|
||||
int result = _pinValues[vpin-_firstVpin];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Loop function to do background scanning of the input port. State
|
||||
// machine parses the incoming command as it is received. Command
|
||||
// is in the form "#Nm,n#" where m is the index and n is the value.
|
||||
void IO_ExampleSerial::_loop(unsigned long currentMicros) {
|
||||
(void)currentMicros; // Suppress compiler warnings
|
||||
if (_serial->available()) {
|
||||
// Input data available to read. Read a character.
|
||||
char c = _serial->read();
|
||||
switch (_inputState) {
|
||||
case 0: // Waiting for start of command
|
||||
if (c == '#') // Start of command received.
|
||||
_inputState = 1;
|
||||
break;
|
||||
case 1: // Expecting command character
|
||||
if (c == 'N') { // 'Notify' character received
|
||||
_inputState = 2;
|
||||
_inputValue = _inputIndex = 0;
|
||||
} else
|
||||
_inputState = 0; // Unexpected char, reset
|
||||
break;
|
||||
case 2: // reading first parameter (index)
|
||||
if (isdigit(c))
|
||||
_inputIndex = _inputIndex * 10 + (c-'0');
|
||||
else if (c==',')
|
||||
_inputState = 3;
|
||||
else
|
||||
_inputState = 0; // Unexpected char, reset
|
||||
break;
|
||||
case 3: // reading reading second parameter (value)
|
||||
if (isdigit(c))
|
||||
_inputValue = _inputValue * 10 - (c-'0');
|
||||
else if (c=='#') { // End of command
|
||||
// Complete command received, do something with it.
|
||||
DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue);
|
||||
if (_inputIndex < _nPins) { // Store value
|
||||
_pinValues[_inputIndex] = _inputValue;
|
||||
}
|
||||
_inputState = 0; // Done, start again.
|
||||
} else
|
||||
_inputState = 0; // Unexpected char, reset
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void IO_ExampleSerial::_display() {
|
||||
DIAG(F("IO_ExampleSerial Configured on VPins:%d-%d"), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1);
|
||||
}
|
||||
|
@@ -35,24 +35,131 @@
|
||||
#include "IODevice.h"
|
||||
|
||||
class IO_ExampleSerial : public IODevice {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
|
||||
|
||||
protected:
|
||||
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud);
|
||||
void _begin() override;
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
void _write(VPIN vpin, int value) override;
|
||||
int _read(VPIN vpin) override;
|
||||
void _display() override;
|
||||
|
||||
private:
|
||||
// Here we define the device-specific variables.
|
||||
HardwareSerial *_serial;
|
||||
uint8_t _inputState = 0;
|
||||
int _inputIndex = 0;
|
||||
int _inputValue = 0;
|
||||
uint16_t *_pinValues; // Pointer to block of memory containing pin values
|
||||
unsigned long _baud;
|
||||
|
||||
public:
|
||||
// Static function to handle "IO_ExampleSerial::create(...)" calls.
|
||||
static void create(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
|
||||
if (checkNoOverlap(firstVpin,nPins)) new IO_ExampleSerial(firstVpin, nPins, serial, baud);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor. This should initialise variables etc. but not call other objects yet
|
||||
// (e.g. Serial, I2CManager, and other parts of the CS functionality).
|
||||
// defer those until the _begin() function. The 'addDevice' call is required unless
|
||||
// the device is not to be added (e.g. because of incorrect parameters).
|
||||
IO_ExampleSerial(VPIN firstVpin, int nPins, HardwareSerial *serial, unsigned long baud) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_pinValues = (uint16_t *)calloc(_nPins, sizeof(uint16_t));
|
||||
_baud = baud;
|
||||
|
||||
// Save reference to serial port driver
|
||||
_serial = serial;
|
||||
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
_serial->begin(_baud);
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
|
||||
// Send a few # characters to the output
|
||||
for (uint8_t i=0; i<3; i++)
|
||||
_serial->write('#');
|
||||
}
|
||||
|
||||
// Device-specific write function. Write a string in the form "#Wm,n#"
|
||||
// where m is the vpin number, and n is the value.
|
||||
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);
|
||||
#endif
|
||||
// Send a command string over the serial line
|
||||
_serial->print('#');
|
||||
_serial->print('W');
|
||||
_serial->print(pin);
|
||||
_serial->print(',');
|
||||
_serial->print(value);
|
||||
_serial->println('#');
|
||||
DIAG(F("ExampleSerial Sent command, p1=%d, p2=%d"), vpin, value);
|
||||
}
|
||||
|
||||
// Device-specific read function.
|
||||
int _read(VPIN vpin) {
|
||||
|
||||
// Return a value for the specified vpin.
|
||||
int result = _pinValues[vpin-_firstVpin];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Loop function to do background scanning of the input port. State
|
||||
// machine parses the incoming command as it is received. Command
|
||||
// is in the form "#Nm,n#" where m is the index and n is the value.
|
||||
void _loop(unsigned long currentMicros) {
|
||||
(void)currentMicros; // Suppress compiler warnings
|
||||
if (_serial->available()) {
|
||||
// Input data available to read. Read a character.
|
||||
char c = _serial->read();
|
||||
switch (_inputState) {
|
||||
case 0: // Waiting for start of command
|
||||
if (c == '#') // Start of command received.
|
||||
_inputState = 1;
|
||||
break;
|
||||
case 1: // Expecting command character
|
||||
if (c == 'N') { // 'Notify' character received
|
||||
_inputState = 2;
|
||||
_inputValue = _inputIndex = 0;
|
||||
} else
|
||||
_inputState = 0; // Unexpected char, reset
|
||||
break;
|
||||
case 2: // reading first parameter (index)
|
||||
if (isdigit(c))
|
||||
_inputIndex = _inputIndex * 10 + (c-'0');
|
||||
else if (c==',')
|
||||
_inputState = 3;
|
||||
else
|
||||
_inputState = 0; // Unexpected char, reset
|
||||
break;
|
||||
case 3: // reading reading second parameter (value)
|
||||
if (isdigit(c))
|
||||
_inputValue = _inputValue * 10 - (c-'0');
|
||||
else if (c=='#') { // End of command
|
||||
// Complete command received, do something with it.
|
||||
DIAG(F("ExampleSerial Received command, p1=%d, p2=%d"), _inputIndex, _inputValue);
|
||||
if (_inputIndex >= 0 && _inputIndex < _nPins) { // Store value
|
||||
_pinValues[_inputIndex] = _inputValue;
|
||||
}
|
||||
_inputState = 0; // Done, start again.
|
||||
} else
|
||||
_inputState = 0; // Unexpected char, reset
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
(int)_firstVpin+_nPins-1);
|
||||
for (int i=0; i<_nPins; i++)
|
||||
DIAG(F(" VPin %2d: %d"), _firstVpin+i, _pinValues[i]);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_EXAMPLESERIAL_H
|
@@ -34,7 +34,7 @@ class GPIOBase : public IODevice {
|
||||
|
||||
protected:
|
||||
// Constructor
|
||||
GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin);
|
||||
GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin);
|
||||
// Device-specific initialisation
|
||||
void _begin() override;
|
||||
// Device-specific pin configuration function.
|
||||
@@ -49,13 +49,13 @@ protected:
|
||||
// Data fields
|
||||
|
||||
// Allocate enough space for all input pins
|
||||
T _portInputState;
|
||||
T _portOutputState;
|
||||
T _portMode;
|
||||
T _portPullup;
|
||||
T _portInUse;
|
||||
// Interval between refreshes of each input port
|
||||
static const int _portTickTime = 4000;
|
||||
T _portInputState; // 1=high (inactive), 0=low (activated)
|
||||
T _portOutputState; // 1 =high, 0=low
|
||||
T _portMode; // 0=input, 1=output
|
||||
T _portPullup; // 0=nopullup, 1=pullup
|
||||
T _portInUse; // 0=not in use, 1=in use
|
||||
// Target interval between refreshes of each input port
|
||||
static const int _portTickTime = 4000; // 4ms
|
||||
|
||||
// Virtual functions for interfacing with I2C GPIO Device
|
||||
virtual void _writeGpioPort() = 0;
|
||||
@@ -69,10 +69,6 @@ protected:
|
||||
|
||||
I2CRB requestBlock;
|
||||
FSH *_deviceName;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
// workaround: Has somehow no min function for all types
|
||||
static inline T min(T a, int b) { return a < b ? a : b; };
|
||||
#endif
|
||||
};
|
||||
|
||||
// Because class GPIOBase is a template, the implementation (below) must be contained within the same
|
||||
@@ -80,15 +76,21 @@ protected:
|
||||
|
||||
// Constructor
|
||||
template <class T>
|
||||
GPIOBase<T>::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin) :
|
||||
GPIOBase<T>::GPIOBase(FSH *deviceName, VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin) :
|
||||
IODevice(firstVpin, nPins)
|
||||
{
|
||||
if (_nPins > (int)sizeof(T)*8) _nPins = sizeof(T)*8; // Ensure nPins is consistent with the number of bits in T
|
||||
_deviceName = deviceName;
|
||||
_I2CAddress = I2CAddress;
|
||||
_I2CAddress = i2cAddress;
|
||||
_gpioInterruptPin = interruptPin;
|
||||
_hasCallback = true;
|
||||
// Add device to list of devices.
|
||||
addDevice(this);
|
||||
|
||||
_portMode = 0; // default to input mode
|
||||
_portPullup = -1; // default to pullup enabled
|
||||
_portInputState = -1; // default to all inputs high (inactive)
|
||||
_portInUse = 0; // No ports in use initially.
|
||||
}
|
||||
|
||||
template <class T>
|
||||
@@ -103,21 +105,16 @@ void GPIOBase<T>::_begin() {
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
_portMode = 0; // default to input mode
|
||||
_portPullup = -1; // default to pullup enabled
|
||||
_portInputState = -1;
|
||||
_portInUse = 0;
|
||||
_setupDevice();
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
} else {
|
||||
DIAG(F("%S I2C:x%x Device not detected"), _deviceName, _I2CAddress);
|
||||
DIAG(F("%S I2C:%s Device not detected"), _deviceName, _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Configuration parameters for inputs:
|
||||
// params[0]: enable pullup
|
||||
// params[1]: invert input (optional)
|
||||
template <class T>
|
||||
bool GPIOBase<T>::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
|
||||
if (configType != CONFIGURE_INPUT) return false;
|
||||
@@ -125,7 +122,7 @@ bool GPIOBase<T>::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCoun
|
||||
bool pullup = params[0];
|
||||
int pin = vpin - _firstVpin;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("%S I2C:x%x Config Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, pullup);
|
||||
DIAG(F("%S I2C:%s Config Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, pullup);
|
||||
#endif
|
||||
uint16_t mask = 1 << pin;
|
||||
if (pullup)
|
||||
@@ -155,7 +152,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
} else {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
DIAG(F("%S I2C:x%x Error:%d %S"), _deviceName, _I2CAddress, status,
|
||||
DIAG(F("%S I2C:%s Error:%d %S"), _deviceName, _I2CAddress.toString(), status,
|
||||
I2CManager.getErrorMessage(status));
|
||||
}
|
||||
_processCompletion(status);
|
||||
@@ -178,7 +175,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
|
||||
|
||||
#ifdef DIAG_IO
|
||||
if (differences)
|
||||
DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState);
|
||||
DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -199,7 +196,7 @@ void GPIOBase<T>::_loop(unsigned long currentMicros) {
|
||||
|
||||
template <class T>
|
||||
void GPIOBase<T>::_display() {
|
||||
DIAG(F("%S I2C:x%x Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress,
|
||||
DIAG(F("%S I2C:%s Configured on Vpins:%d-%d %S"), _deviceName, _I2CAddress.toString(),
|
||||
_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
@@ -208,7 +205,7 @@ void GPIOBase<T>::_write(VPIN vpin, int value) {
|
||||
int pin = vpin - _firstVpin;
|
||||
T mask = 1 << pin;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("%S I2C:x%x Write Pin:%d Val:%d"), _deviceName, _I2CAddress, pin, value);
|
||||
DIAG(F("%S I2C:%s Write Pin:%d Val:%d"), _deviceName, _I2CAddress.toString(), pin, value);
|
||||
#endif
|
||||
|
||||
// Set port mode output if currently not output mode
|
||||
@@ -244,7 +241,7 @@ int GPIOBase<T>::_read(VPIN vpin) {
|
||||
// Set unused pin and write mode pin value to 1
|
||||
_portInputState |= ~_portInUse | _portMode;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("%S I2C:x%x PortStates:%x"), _deviceName, _I2CAddress, _portInputState);
|
||||
DIAG(F("%S I2C:%s PortStates:%x"), _deviceName, _I2CAddress.toString(), _portInputState);
|
||||
#endif
|
||||
}
|
||||
return (_portInputState & mask) ? 0 : 1; // Invert state (5v=0, 0v=1)
|
||||
|
262
IO_HALDisplay.h
Normal file
262
IO_HALDisplay.h
Normal file
@@ -0,0 +1,262 @@
|
||||
/*
|
||||
* © 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 more immediate interface into the OLED display
|
||||
* than the one installed through the config.h file. When an LCD(...) call
|
||||
* is made, the text is output immediately to the specified display line,
|
||||
* without waiting for the next 2.5 second refresh. However, 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:
|
||||
*
|
||||
* HALDisplay<OLED>::create(address, width, height);
|
||||
*
|
||||
* where address is the I2C address of the OLED display (0x3c or 0x3d),
|
||||
* width is the width in pixels, and height is the height in pixels.
|
||||
*
|
||||
* Valid width and height are 128x32 (SSD1306 controller),
|
||||
* 128x64 (SSD1306) and 132x64 (SH1106). The driver uses
|
||||
* a 5x7 character set in a 6x8 pixel cell.
|
||||
*
|
||||
* OR
|
||||
*
|
||||
* HALDisplay<LiquidCrystal>::create(address, width, height);
|
||||
*
|
||||
* where address is the I2C address of the LCD display (0x27 typically),
|
||||
* width is the width in characters (16 or 20 typically),
|
||||
* and height is the height in characters (2 or 4 typically).
|
||||
*/
|
||||
|
||||
|
||||
#ifndef IO_HALDisplay_H
|
||||
#define IO_HALDisplay_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "DisplayInterface.h"
|
||||
#include "SSD1306Ascii.h"
|
||||
#include "LiquidCrystal_I2C.h"
|
||||
#include "version.h"
|
||||
|
||||
typedef SSD1306AsciiWire OLED;
|
||||
typedef LiquidCrystal_I2C LiquidCrystal;
|
||||
|
||||
template <class T>
|
||||
class HALDisplay : public IODevice, public DisplayInterface {
|
||||
private:
|
||||
// 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;
|
||||
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(0, 0, i2cAddress)) new HALDisplay(0, i2cAddress, width, height);
|
||||
}
|
||||
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int 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;
|
||||
_numCols = _displayDriver->getNumCols();
|
||||
_numRows = _displayDriver->getNumRows();
|
||||
|
||||
_charPosToScreen = _numCols;
|
||||
|
||||
// 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);
|
||||
|
||||
_displayDriver->clearNative();
|
||||
|
||||
// Add device to list of HAL devices (not necessary but allows
|
||||
// status to be displayed using <D HAL SHOW> and device to be
|
||||
// reinitialised using <D HAL RESET>).
|
||||
IODevice::addDevice(this);
|
||||
|
||||
// Also add this display to list of display handlers
|
||||
DisplayInterface::addDisplay(displayNo);
|
||||
|
||||
// Is this the system display (0)?
|
||||
if (displayNo == 0) {
|
||||
// Set first two lines on screen
|
||||
this->setRow(displayNo, 0);
|
||||
print(F("DCC-EX v"));
|
||||
print(F(VERSION));
|
||||
setRow(displayNo, 1);
|
||||
print(F("Lic GPLv3"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 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 || _startAgain) {
|
||||
_rowNoToScreen = 0; // Wrap to first row
|
||||
_startAgain = false;
|
||||
}
|
||||
|
||||
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()) {
|
||||
|
||||
_display();
|
||||
|
||||
// Force all rows to be redrawn
|
||||
for (uint8_t row=0; row<_numRows; row++)
|
||||
_rowGeneration[row]++;
|
||||
|
||||
// Start with top line (looks better).
|
||||
// The numbers will wrap round on the first loop2 entry.
|
||||
_rowNoToScreen = _numRows;
|
||||
_charPosToScreen = _numCols;
|
||||
}
|
||||
}
|
||||
|
||||
void _loop(unsigned long) override {
|
||||
screenUpdate();
|
||||
}
|
||||
|
||||
// Display information about the device.
|
||||
void _display() {
|
||||
DIAG(F("HALDisplay %d configured on addr %s"), _displayNo, _I2CAddress.toString());
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// DisplayInterface functions
|
||||
//
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
public:
|
||||
void _displayLoop() override {
|
||||
screenUpdate();
|
||||
}
|
||||
|
||||
// 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(byte line) override {
|
||||
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 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.
|
||||
virtual size_t _write(uint8_t c) override {
|
||||
// Write character to buffer (if there's space)
|
||||
if (_colNo < _numCols) {
|
||||
_buffer[_rowNo*_numCols+_colNo++] = c;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Write blanks to all of the screen buffer
|
||||
void _clear() {
|
||||
// Clear buffer
|
||||
memset(_buffer, ' ', _numCols*_numRows);
|
||||
_colNo = 0;
|
||||
_rowNo = 0;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_HALDisplay_H
|
@@ -25,14 +25,14 @@
|
||||
|
||||
class MCP23008 : public GPIOBase<uint8_t> {
|
||||
public:
|
||||
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new MCP23008(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new MCP23008(firstVpin, nPins, i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
MCP23008(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin) {
|
||||
MCP23008(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("MCP23008"), firstVpin, nPins, i2cAddress, interruptPin) {
|
||||
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
@@ -60,7 +60,7 @@ private:
|
||||
if (immediate) {
|
||||
uint8_t buffer;
|
||||
I2CManager.read(_I2CAddress, &buffer, 1, 1, REG_GPIO);
|
||||
_portInputState = buffer;
|
||||
_portInputState = buffer | _portMode;
|
||||
} else {
|
||||
// Queue new request
|
||||
requestBlock.wait(); // Wait for preceding operation to complete
|
||||
@@ -71,7 +71,7 @@ private:
|
||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||
void _processCompletion(uint8_t status) override {
|
||||
if (status == I2C_STATUS_OK)
|
||||
_portInputState = inputBuffer[0];
|
||||
_portInputState = inputBuffer[0] | _portMode;
|
||||
else
|
||||
_portInputState = 0xff;
|
||||
}
|
||||
|
@@ -30,14 +30,14 @@
|
||||
|
||||
class MCP23017 : public GPIOBase<uint16_t> {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(vpin, nPins, I2CAddress)) new MCP23017(vpin, min(nPins,16), I2CAddress, interruptPin);
|
||||
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new MCP23017(vpin, nPins, i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
MCP23017(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("MCP23017"), vpin, nPins, I2CAddress, interruptPin)
|
||||
MCP23017(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("MCP23017"), vpin, nPins, i2cAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
@@ -65,7 +65,7 @@ private:
|
||||
if (immediate) {
|
||||
uint8_t buffer[2];
|
||||
I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA);
|
||||
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0];
|
||||
_portInputState = ((uint16_t)buffer[1]<<8) | buffer[0] | _portMode;
|
||||
} else {
|
||||
// Queue new request
|
||||
requestBlock.wait(); // Wait for preceding operation to complete
|
||||
@@ -76,7 +76,7 @@ private:
|
||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||
void _processCompletion(uint8_t status) override {
|
||||
if (status == I2C_STATUS_OK)
|
||||
_portInputState = ((uint16_t)inputBuffer[1]<<8) | inputBuffer[0];
|
||||
_portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode;
|
||||
else
|
||||
_portInputState = 0xffff;
|
||||
}
|
||||
|
@@ -31,15 +31,14 @@ static const byte MODE1_AI=0x20; /**< Auto-Increment enabled */
|
||||
static const byte MODE1_RESTART=0x80; /**< Restart enabled */
|
||||
|
||||
static const float FREQUENCY_OSCILLATOR=25000000.0; /** Accurate enough for our purposes */
|
||||
static const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
|
||||
static const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
|
||||
|
||||
// Predeclare helper function
|
||||
static void writeRegister(byte address, byte reg, byte value);
|
||||
|
||||
// Create device driver instance.
|
||||
void PCA9685::create(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCA9685(firstVpin, nPins, I2CAddress);
|
||||
void PCA9685::create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
|
||||
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new PCA9685(firstVpin, nPins, i2cAddress, frequency);
|
||||
}
|
||||
|
||||
// Configure a port on the PCA9685.
|
||||
@@ -73,10 +72,14 @@ bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, i
|
||||
}
|
||||
|
||||
// Constructor
|
||||
PCA9685::PCA9685(VPIN firstVpin, int nPins, uint8_t I2CAddress) {
|
||||
PCA9685::PCA9685(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins, 16);
|
||||
_I2CAddress = I2CAddress;
|
||||
_nPins = (nPins > 16) ? 16 : nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
// Calculate prescaler value for PWM clock
|
||||
if (frequency > 1526) frequency = 1526;
|
||||
else if (frequency < 24) frequency = 24;
|
||||
prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency;
|
||||
// To save RAM, space for servo configuration is not allocated unless a pin is used.
|
||||
// Initialise the pointers to NULL.
|
||||
for (int i=0; i<_nPins; i++)
|
||||
@@ -98,7 +101,7 @@ void PCA9685::_begin() {
|
||||
// Initialise I/O module here.
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
|
||||
writeRegister(_I2CAddress, PCA9685_PRESCALE, PRESCALE_50HZ); // 50Hz clock, 20ms pulse period.
|
||||
writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler);
|
||||
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
|
||||
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI);
|
||||
// In theory, we should wait 500us before sending any other commands to each device, to allow
|
||||
@@ -239,13 +242,13 @@ void PCA9685::updatePosition(uint8_t pin) {
|
||||
// between 0 and 4095 for the PWM mark-to-period ratio, with 4095 being 100%.
|
||||
void PCA9685::writeDevice(uint8_t pin, int value) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685 I2C:x%x WriteDevice Pin:%d Value:%d"), _I2CAddress, pin, value);
|
||||
DIAG(F("PCA9685 I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
|
||||
#endif
|
||||
// Wait for previous request to complete
|
||||
uint8_t status = requestBlock.wait();
|
||||
if (status != I2C_STATUS_OK) {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
DIAG(F("PCA9685 I2C:x%x failed %S"), _I2CAddress, I2CManager.getErrorMessage(status));
|
||||
DIAG(F("PCA9685 I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
} else {
|
||||
// Set up new request.
|
||||
outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin;
|
||||
@@ -259,7 +262,7 @@ void PCA9685::writeDevice(uint8_t pin, int value) {
|
||||
|
||||
// Display details of this device.
|
||||
void PCA9685::_display() {
|
||||
DIAG(F("PCA9685 I2C:x%x Configured on Vpins:%d-%d %S"), _I2CAddress, (int)_firstVpin,
|
||||
DIAG(F("PCA9685 I2C:%s Configured on Vpins:%d-%d %S"), _I2CAddress.toString(), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
@@ -272,5 +275,5 @@ static void writeRegister(byte address, byte reg, byte value) {
|
||||
// The profile below is in the range 0-100% and should be combined with the desired limits
|
||||
// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here,
|
||||
// i.e. the bounce is the same on the down action as on the up action. First entry isn't used.
|
||||
const byte FLASH PCA9685::_bounceProfile[30] =
|
||||
const uint8_t FLASH PCA9685::_bounceProfile[30] =
|
||||
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};
|
||||
|
170
IO_PCA9685pwm.h
Normal file
170
IO_PCA9685pwm.h
Normal file
@@ -0,0 +1,170 @@
|
||||
/*
|
||||
* © 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 performs the basic interface between the HAL and an
|
||||
* I2C-connected PCA9685 16-channel PWM module. When requested, it
|
||||
* commands the device to set the PWM mark-to-period ratio accordingly.
|
||||
* The call to IODevice::writeAnalogue(vpin, value) specifies the
|
||||
* desired value in the range 0-4095 (0=0% and 4095=100%).
|
||||
*
|
||||
* This driver can be used for simple servo control by writing values between
|
||||
* about 102 and 450 (extremes of movement for 9g micro servos) or 150 to 250
|
||||
* for a more restricted range (corresponding to 1.5ms to 2.5ms pulse length).
|
||||
* A value of zero will switch off the servo. To create the device, use
|
||||
* the following syntax:
|
||||
*
|
||||
* PCA9685_basic::create(vpin, npins, i2caddress);
|
||||
*
|
||||
* For LED control, a value of 0 is fully off, and 4095 is fully on. It is
|
||||
* recommended, to reduce flicker of LEDs, that the frequency be configured
|
||||
* to a value higher than the default of 50Hz. To do this, create the device
|
||||
* as follows, for a frequency of 200Hz.:
|
||||
*
|
||||
* PCA9685_basic::create(vpin, npins, i2caddress, 200);
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef PCA9685_BASIC_H
|
||||
#define PCA9685_BASIC_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
/*
|
||||
* IODevice subclass for PCA9685 16-channel PWM module.
|
||||
*/
|
||||
|
||||
class PCA9685pwm : public IODevice {
|
||||
public:
|
||||
// Create device driver instance.
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency = 50) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCA9685pwm(firstVpin, nPins, i2cAddress, frequency);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
// structures for setting up non-blocking writes to PWM controller
|
||||
I2CRB requestBlock;
|
||||
uint8_t outputBuffer[5];
|
||||
uint16_t prescaler;
|
||||
|
||||
// REGISTER ADDRESSES
|
||||
const uint8_t PCA9685_MODE1=0x00; // Mode Register
|
||||
const uint8_t PCA9685_FIRST_SERVO=0x06; /** low uint8_t first PWM register ON*/
|
||||
const uint8_t PCA9685_PRESCALE=0xFE; /** Prescale register for PWM output frequency */
|
||||
// MODE1 bits
|
||||
const uint8_t MODE1_SLEEP=0x10; /**< Low power mode. Oscillator off */
|
||||
const uint8_t MODE1_AI=0x20; /**< Auto-Increment enabled */
|
||||
const uint8_t MODE1_RESTART=0x80; /**< Restart enabled */
|
||||
|
||||
const uint32_t FREQUENCY_OSCILLATOR=25000000; /** Accurate enough for our purposes */
|
||||
const uint8_t PRESCALE_50HZ = (uint8_t)(((FREQUENCY_OSCILLATOR / (50.0 * 4096.0)) + 0.5) - 1);
|
||||
const uint32_t MAX_I2C_SPEED = 1000000L; // PCA9685 rated up to 1MHz I2C clock speed
|
||||
|
||||
// Constructor
|
||||
PCA9685pwm(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t frequency) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = (nPins>16) ? 16 : nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
if (frequency > 1526) frequency = 1526;
|
||||
else if (frequency < 24) frequency = 24;
|
||||
prescaler = FREQUENCY_OSCILLATOR / 4096 / frequency;
|
||||
addDevice(this);
|
||||
|
||||
// Initialise structure used for setting pulse rate
|
||||
requestBlock.setWriteParams(_I2CAddress, outputBuffer, sizeof(outputBuffer));
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000); // Nominally able to run up to 1MHz on I2C
|
||||
// In reality, other devices including the Arduino will limit
|
||||
// the clock speed to a lower rate.
|
||||
|
||||
// Initialise I/O module here.
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_SLEEP | MODE1_AI);
|
||||
writeRegister(_I2CAddress, PCA9685_PRESCALE, prescaler);
|
||||
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_AI);
|
||||
writeRegister(_I2CAddress, PCA9685_MODE1, MODE1_RESTART | MODE1_AI);
|
||||
// In theory, we should wait 500us before sending any other commands to each device, to allow
|
||||
// the PWM oscillator to get running. However, we don't do any specific wait, as there's
|
||||
// plenty of other stuff to do before we will send a command.
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
} else
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
|
||||
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
|
||||
//
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override {
|
||||
(void)param1; (void)param2; // suppress compiler warning
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685pwm WriteAnalogue Vpin:%d Value:%d %S"),
|
||||
vpin, value, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value > 4095) value = 4095;
|
||||
else if (value < 0) value = 0;
|
||||
|
||||
writeDevice(pin, value);
|
||||
}
|
||||
|
||||
// Display details of this device.
|
||||
void _display() override {
|
||||
DIAG(F("PCA9685pwm I2C:%s Configured on Vpins:%d-%d %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) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("PCA9685pwm I2C:%s WriteDevice Pin:%d Value:%d"), _I2CAddress.toString(), pin, value);
|
||||
#endif
|
||||
// Wait for previous request to complete
|
||||
uint8_t status = requestBlock.wait();
|
||||
if (status != I2C_STATUS_OK) {
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
DIAG(F("PCA9685pwm I2C:%s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
} else {
|
||||
// Set up new request.
|
||||
outputBuffer[0] = PCA9685_FIRST_SERVO + 4 * pin;
|
||||
outputBuffer[1] = 0;
|
||||
outputBuffer[2] = (value == 4095 ? 0x10 : 0); // 4095=full on
|
||||
outputBuffer[3] = value & 0xff;
|
||||
outputBuffer[4] = value >> 8;
|
||||
I2CManager.queueRequest(&requestBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// Internal helper function for this device
|
||||
static void writeRegister(I2CAddress address, uint8_t reg, uint8_t value) {
|
||||
I2CManager.write(address, 2, reg, value);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
#endif
|
21
IO_PCF8574.h
21
IO_PCF8574.h
@@ -43,20 +43,22 @@
|
||||
|
||||
class PCF8574 : public GPIOBase<uint8_t> {
|
||||
public:
|
||||
static void create(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins,I2CAddress)) new PCF8574(firstVpin, nPins, I2CAddress, interruptPin);
|
||||
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8574(firstVpin, nPins, i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
PCF8574(VPIN firstVpin, uint8_t nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, min(nPins, (uint8_t)8), I2CAddress, interruptPin)
|
||||
PCF8574(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint8_t>((FSH *)F("PCF8574"), firstVpin, nPins, i2cAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setReadParams(_I2CAddress, inputBuffer, 1);
|
||||
}
|
||||
|
||||
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
|
||||
// The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'.
|
||||
// The pin state is driven '1' if the pin is an input, or if it is an output set to 1.
|
||||
// Unused pins are driven '0'.
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode);
|
||||
I2CManager.write(_I2CAddress, 1, (_portOutputState | ~_portMode) & _portInUse);
|
||||
}
|
||||
|
||||
// The PCF8574 handles inputs by applying a weak pull-up when output is driven to '1'.
|
||||
@@ -64,9 +66,8 @@ private:
|
||||
// and enable pull-up.
|
||||
void _writePullups() override { }
|
||||
|
||||
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
|
||||
void _writePortModes() override {
|
||||
I2CManager.write(_I2CAddress, 1, _portOutputState | ~_portMode);
|
||||
_writeGpioPort();
|
||||
}
|
||||
|
||||
// In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly.
|
||||
@@ -76,7 +77,7 @@ private:
|
||||
if (immediate) {
|
||||
uint8_t buffer[1];
|
||||
I2CManager.read(_I2CAddress, buffer, 1);
|
||||
_portInputState = buffer[0];
|
||||
_portInputState = buffer[0] | _portMode;
|
||||
} else {
|
||||
requestBlock.wait(); // Wait for preceding operation to complete
|
||||
// Issue new request to read GPIO register
|
||||
@@ -87,7 +88,7 @@ private:
|
||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||
void _processCompletion(uint8_t status) override {
|
||||
if (status == I2C_STATUS_OK)
|
||||
_portInputState = inputBuffer[0];
|
||||
_portInputState = inputBuffer[0] | _portMode;
|
||||
else
|
||||
_portInputState = 0xff;
|
||||
}
|
||||
|
109
IO_PCF8575.h
Normal file
109
IO_PCF8575.h
Normal file
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* © 2023, Paul Antoine, and Discord user @ADUBOURG
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The PCF8575 is a simple device; it only has one register. The device
|
||||
* input/output mode and pullup are configured through this, and the
|
||||
* output state is written and the input state read through it too.
|
||||
*
|
||||
* This is accomplished by having a weak resistor in series with the output,
|
||||
* and a read-back of the other end of the resistor. As an output, the
|
||||
* pin state is set to 1 or 0, and the output voltage goes to +5V or 0V
|
||||
* (through the weak resistor).
|
||||
*
|
||||
* In order to use the pin as an input, the output is written as
|
||||
* a '1' in order to pull up the resistor. Therefore the input will be
|
||||
* 1 unless the pin is pulled down externally, in which case it will be 0.
|
||||
*
|
||||
* As a consequence of this approach, it is not possible to use the device for
|
||||
* inputs without pullups.
|
||||
*/
|
||||
|
||||
#ifndef IO_PCF8575_H
|
||||
#define IO_PCF8575_H
|
||||
|
||||
#include "IO_GPIOBase.h"
|
||||
#include "FSH.h"
|
||||
|
||||
class PCF8575 : public GPIOBase<uint16_t> {
|
||||
public:
|
||||
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new PCF8575(firstVpin, nPins, i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
PCF8575(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("PCF8575"), firstVpin, nPins, i2cAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setReadParams(_I2CAddress, inputBuffer, sizeof(inputBuffer));
|
||||
}
|
||||
|
||||
// The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'.
|
||||
// The pin state is driven '1' if the pin is an input, or if it is an output set to 1.
|
||||
// Unused pins are driven '0'.
|
||||
void _writeGpioPort() override {
|
||||
uint16_t bits = (_portOutputState | ~_portMode) & _portInUse;
|
||||
I2CManager.write(_I2CAddress, 2, bits, bits>>8);
|
||||
}
|
||||
|
||||
// The PCF8575 handles inputs by applying a weak pull-up when output is driven to '1'.
|
||||
// Therefore, writing '1' in _writePortModes is enough to set the module to input mode
|
||||
// and enable pull-up.
|
||||
void _writePullups() override { }
|
||||
|
||||
// The pin state is '1' if the pin is an input or if it is an output set to 1. Zero otherwise.
|
||||
void _writePortModes() override {
|
||||
_writeGpioPort();
|
||||
}
|
||||
|
||||
// In immediate mode, _readGpioPort reads the device GPIO port and updates _portInputState accordingly.
|
||||
// When not in immediate mode, it initiates a request using the request block and returns.
|
||||
// When the request completes, _processCompletion finishes the operation.
|
||||
void _readGpioPort(bool immediate) override {
|
||||
if (immediate) {
|
||||
uint8_t buffer[2];
|
||||
I2CManager.read(_I2CAddress, buffer, 2);
|
||||
_portInputState = (((uint16_t)buffer[1]<<8) | buffer[0]) | _portMode;
|
||||
} else {
|
||||
requestBlock.wait(); // Wait for preceding operation to complete
|
||||
// Issue new request to read GPIO register
|
||||
I2CManager.queueRequest(&requestBlock);
|
||||
}
|
||||
}
|
||||
|
||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||
void _processCompletion(uint8_t status) override {
|
||||
if (status == I2C_STATUS_OK)
|
||||
_portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode;
|
||||
else
|
||||
_portInputState = 0xffff;
|
||||
}
|
||||
|
||||
// Set up device ports
|
||||
void _setupDevice() override {
|
||||
_writePortModes();
|
||||
_writeGpioPort();
|
||||
_writePullups();
|
||||
}
|
||||
|
||||
uint8_t inputBuffer[2];
|
||||
};
|
||||
|
||||
#endif
|
126
IO_RotaryEncoder.h
Normal file
126
IO_RotaryEncoder.h
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* © 2022, Peter Cole. All rights reserved.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* The IO_RotaryEncoder device driver is used to receive positions from a rotary encoder connected to an Arduino via I2C.
|
||||
*
|
||||
* There is separate code required for the Arduino the rotary encoder is connected to, which is located here:
|
||||
* https://github.com/peteGSX-Projects/dcc-ex-rotary-encoder
|
||||
*
|
||||
* This device driver receives the rotary encoder position when the rotary encoder button is pushed, and these positions
|
||||
* can be tested in EX-RAIL with:
|
||||
* 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.
|
||||
* A SET(vpin) will flag that a turntable (or anything else) is in motion, and a RESET(vpin) that the motion has finished.
|
||||
*
|
||||
* Refer to the documentation for further information including the valid activities and examples.
|
||||
*/
|
||||
|
||||
#ifndef IO_ROTARYENCODER_H
|
||||
#define IO_ROTARYENCODER_H
|
||||
|
||||
#include "EXRAIL2.h"
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
class RotaryEncoder : public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
RotaryEncoder(VPIN firstVpin, int nPins, I2CAddress i2cAddress){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new RotaryEncoder(firstVpin, nPins, i2cAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
// Initiate the device
|
||||
void _begin() {
|
||||
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);
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
_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 (_position != _previousPosition) {
|
||||
_previousPosition = _position;
|
||||
RMFT2::changeEvent(_firstVpin,1);
|
||||
} else {
|
||||
RMFT2::changeEvent(_firstVpin,0);
|
||||
}
|
||||
#endif
|
||||
delayUntil(currentMicros + 100000);
|
||||
}
|
||||
|
||||
// Device specific read function
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
return _position;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (vpin == _firstVpin + 1) {
|
||||
byte _feedbackBuffer[2] = {RE_OP, value};
|
||||
I2CManager.write(_I2CAddress, _feedbackBuffer, 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,
|
||||
(int)_firstVpin, _firstVpin+_nPins-1, (_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
int8_t _position;
|
||||
int8_t _previousPosition = 0;
|
||||
uint8_t _versionBuffer[3];
|
||||
uint8_t _buffer[1];
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
|
||||
enum {
|
||||
RE_VER = 0xA0, // Flag to retrieve rotary encoder version from the device
|
||||
RE_OP = 0xA1, // Flag for normal operation
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif
|
33
IO_Servo.cpp
Normal file
33
IO_Servo.cpp
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* © 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/>.
|
||||
*/
|
||||
|
||||
#include "IO_Servo.h"
|
||||
#include "FSH.h"
|
||||
|
||||
// Profile for a bouncing signal or turnout
|
||||
// The profile below is in the range 0-100% and should be combined with the desired limits
|
||||
// of the servo set by _activePosition and _inactivePosition. The profile is symmetrical here,
|
||||
// i.e. the bounce is the same on the down action as on the up action. First entry isn't used.
|
||||
//
|
||||
// Note: This has been put into its own .CPP file to ensure that duplicates aren't created
|
||||
// if the IO_Servo.h library is #include'd in multiple source files.
|
||||
//
|
||||
const uint8_t FLASH Servo::_bounceProfile[30] =
|
||||
{0,2,3,7,13,33,50,83,100,83,75,70,65,60,60,65,74,84,100,83,75,70,70,72,75,80,87,92,97,100};
|
||||
|
298
IO_Servo.h
Normal file
298
IO_Servo.h
Normal file
@@ -0,0 +1,298 @@
|
||||
/*
|
||||
* © 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 is a layered device which is designed to sit on top of another
|
||||
* device. The underlying device class is expected to accept writeAnalogue calls
|
||||
* which will normally cause some physical movement of something. The device may be a servo,
|
||||
* a motor or some other kind of positioner, and the something might be a turnout,
|
||||
* a semaphore signal or something else. One user has used this capability for
|
||||
* moving a figure along the platform on their layout!
|
||||
*
|
||||
* Example of use:
|
||||
* In myHal.cpp,
|
||||
*
|
||||
* #include "IO_Servo.h"
|
||||
* ...
|
||||
* PCA9685::create(100,16,0x40); // First create the hardware interface device
|
||||
* Servo::create(300,16,100); // Then create the higher level device which
|
||||
* // references pins 100-115 or a subset of them.
|
||||
*
|
||||
* Then any reference to pins 300-315 will cause the servo driver to send output
|
||||
* PWM commands to the corresponding PCA9685 driver pins 100-115. The PCA9685 driver may
|
||||
* be substituted with any other driver which provides analogue output
|
||||
* capability, e.g. EX-IOExpander devices, as long as they are capable of interpreting
|
||||
* the writeAnalogue() function calls.
|
||||
*/
|
||||
|
||||
#include "IODevice.h"
|
||||
|
||||
#ifndef IO_SERVO_H
|
||||
#define IO_SERVO_H
|
||||
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
class Servo : IODevice {
|
||||
|
||||
public:
|
||||
enum ProfileType : uint8_t {
|
||||
Instant = 0, // Moves immediately between positions (if duration not specified)
|
||||
UseDuration = 0, // Use specified duration
|
||||
Fast = 1, // Takes around 500ms end-to-end
|
||||
Medium = 2, // 1 second end-to-end
|
||||
Slow = 3, // 2 seconds end-to-end
|
||||
Bounce = 4, // For semaphores/turnouts with a bit of bounce!!
|
||||
NoPowerOff = 0x80, // Flag to be ORed in to suppress power off after move.
|
||||
};
|
||||
|
||||
// Create device driver instance.
|
||||
static void create(VPIN firstVpin, int nPins, VPIN firstSlavePin=VPIN_NONE) {
|
||||
new Servo(firstVpin, nPins, firstSlavePin);
|
||||
}
|
||||
|
||||
private:
|
||||
VPIN _firstSlavePin;
|
||||
IODevice *_slaveDevice = NULL;
|
||||
|
||||
struct ServoData {
|
||||
uint16_t activePosition : 12; // Config parameter
|
||||
uint16_t inactivePosition : 12; // Config parameter
|
||||
uint16_t currentPosition : 12;
|
||||
uint16_t fromPosition : 12;
|
||||
uint16_t toPosition : 12;
|
||||
uint8_t profile; // Config parameter
|
||||
uint16_t stepNumber; // Index of current step (starting from 0)
|
||||
uint16_t numSteps; // Number of steps in animation, or 0 if none in progress.
|
||||
uint8_t currentProfile; // profile being used for current animation.
|
||||
uint16_t duration; // time (tenths of a second) for animation to complete.
|
||||
}; // 14 bytes per element, i.e. per pin in use
|
||||
|
||||
struct ServoData *_servoData [16];
|
||||
|
||||
static const uint8_t _catchupSteps = 5; // number of steps to wait before switching servo off
|
||||
static const uint8_t FLASH _bounceProfile[30];
|
||||
|
||||
const unsigned int refreshInterval = 50; // refresh every 50ms
|
||||
|
||||
|
||||
// Configure a port on the Servo.
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||
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"),
|
||||
vpin, params[0], params[1], params[2], params[3], params[4]);
|
||||
#endif
|
||||
|
||||
int8_t pin = vpin - _firstVpin;
|
||||
struct ServoData *s = _servoData[pin];
|
||||
if (s == NULL) {
|
||||
_servoData[pin] = (struct ServoData *)calloc(1, sizeof(struct ServoData));
|
||||
s = _servoData[pin];
|
||||
if (!s) return false; // Check for failed memory allocation
|
||||
}
|
||||
|
||||
s->activePosition = params[0];
|
||||
s->inactivePosition = params[1];
|
||||
s->profile = params[2];
|
||||
s->duration = params[3];
|
||||
int state = params[4];
|
||||
|
||||
if (state != -1) {
|
||||
// Position servo to initial state
|
||||
writeAnalogue(vpin, state ? s->activePosition : s->inactivePosition);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Constructor
|
||||
Servo(VPIN firstVpin, int nPins, VPIN firstSlavePin = VPIN_NONE) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = (nPins > 16) ? 16 : nPins;
|
||||
if (firstSlavePin == VPIN_NONE)
|
||||
_firstSlavePin = firstVpin;
|
||||
else
|
||||
_firstSlavePin = firstSlavePin;
|
||||
|
||||
// To save RAM, space for servo configuration is not allocated unless a pin is used.
|
||||
// Initialise the pointers to NULL.
|
||||
for (int i=0; i<_nPins; i++)
|
||||
_servoData[i] = NULL;
|
||||
|
||||
// Get reference to slave device.
|
||||
_slaveDevice = findDevice(_firstSlavePin);
|
||||
if (!_slaveDevice) {
|
||||
DIAG(F("Servo: Slave device not found on pins %d-%d"),
|
||||
_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"),
|
||||
_firstSlavePin, _firstSlavePin+_nPins-1);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
|
||||
addDevice(this, _slaveDevice); // Link device ahead of slave device to intercept requests
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
|
||||
// Device-specific write function, invoked from IODevice::write().
|
||||
// For this function, the configured profile is used.
|
||||
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);
|
||||
#endif
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value) value = 1;
|
||||
|
||||
struct ServoData *s = _servoData[pin];
|
||||
if (s != NULL) {
|
||||
// Use configured parameters
|
||||
writeAnalogue(vpin, value ? s->activePosition : s->inactivePosition, s->profile, s->duration);
|
||||
} else {
|
||||
/* simulate digital pin on PWM */
|
||||
writeAnalogue(vpin, value ? 4095 : 0, Instant | NoPowerOff, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// Device-specific writeAnalogue function, invoked from IODevice::writeAnalogue().
|
||||
// Profile is as follows:
|
||||
// Bit 7: 0=Set output to 0% to power off servo motor when finished
|
||||
// 1=Keep output at final position (better with LEDs, which will stay lit)
|
||||
// Bits 6-0: 0 Use specified duration (defaults to 0 deciseconds)
|
||||
// 1 (Fast) Move servo in 0.5 seconds
|
||||
// 2 (Medium) Move servo in 1.0 seconds
|
||||
// 3 (Slow) Move servo in 2.0 seconds
|
||||
// 4 (Bounce) Servo 'bounces' at extremes.
|
||||
// Duration is in deciseconds (tenths of a second) and defaults to 0.
|
||||
//
|
||||
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"),
|
||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
int pin = vpin - _firstVpin;
|
||||
if (value > 4095) value = 4095;
|
||||
else if (value < 0) value = 0;
|
||||
|
||||
struct ServoData *s = _servoData[pin];
|
||||
if (s == NULL) {
|
||||
// Servo pin not configured, so configure now using defaults
|
||||
s = _servoData[pin] = (struct ServoData *) calloc(sizeof(struct ServoData), 1);
|
||||
if (s == NULL) return; // Check for memory allocation failure
|
||||
s->activePosition = 4095;
|
||||
s->inactivePosition = 0;
|
||||
s->currentPosition = value;
|
||||
s->profile = Instant | NoPowerOff; // Use instant profile (but not this time)
|
||||
}
|
||||
|
||||
// Animated profile. Initiate the appropriate action.
|
||||
s->currentProfile = profile;
|
||||
uint8_t profileValue = profile & ~NoPowerOff; // Mask off 'don't-power-off' bit.
|
||||
s->numSteps = profileValue==Fast ? 10 : // 0.5 seconds
|
||||
profileValue==Medium ? 20 : // 1.0 seconds
|
||||
profileValue==Slow ? 40 : // 2.0 seconds
|
||||
profileValue==Bounce ? sizeof(_bounceProfile)-1 : // ~ 1.5 seconds
|
||||
duration * 2 + 1; // Convert from deciseconds (100ms) to refresh cycles (50ms)
|
||||
s->stepNumber = 0;
|
||||
s->toPosition = value;
|
||||
s->fromPosition = s->currentPosition;
|
||||
}
|
||||
|
||||
// _read returns true if the device is currently in executing an animation,
|
||||
// changing the output over a period of time.
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
int pin = vpin - _firstVpin;
|
||||
struct ServoData *s = _servoData[pin];
|
||||
if (s == NULL)
|
||||
return false; // No structure means no animation!
|
||||
else
|
||||
return (s->stepNumber < s->numSteps);
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
for (int pin=0; pin<_nPins; pin++) {
|
||||
updatePosition(pin);
|
||||
}
|
||||
delayUntil(currentMicros + refreshInterval * 1000UL);
|
||||
}
|
||||
|
||||
// Private function to reposition servo
|
||||
// TODO: Could calculate step number from elapsed time, to allow for erratic loop timing.
|
||||
void updatePosition(uint8_t pin) {
|
||||
struct ServoData *s = _servoData[pin];
|
||||
if (s == NULL) return; // No pin configuration/state data
|
||||
|
||||
if (s->numSteps == 0) return; // No animation in progress
|
||||
|
||||
if (s->stepNumber == 0 && s->fromPosition == s->toPosition) {
|
||||
// Go straight to end of sequence, output final position.
|
||||
s->stepNumber = s->numSteps-1;
|
||||
}
|
||||
|
||||
if (s->stepNumber < s->numSteps) {
|
||||
// Animation in progress, reposition servo
|
||||
s->stepNumber++;
|
||||
if ((s->currentProfile & ~NoPowerOff) == Bounce) {
|
||||
// Retrieve step positions from array in flash
|
||||
uint8_t profileValue = GETFLASH(&_bounceProfile[s->stepNumber]);
|
||||
s->currentPosition = map(profileValue, 0, 100, s->fromPosition, s->toPosition);
|
||||
} else {
|
||||
// All other profiles - calculate step by linear interpolation between from and to positions.
|
||||
s->currentPosition = map(s->stepNumber, 0, s->numSteps, s->fromPosition, s->toPosition);
|
||||
}
|
||||
// Send servo command to output driver
|
||||
_slaveDevice->_writeAnalogue(_firstSlavePin+pin, s->currentPosition);
|
||||
} else if (s->stepNumber < s->numSteps + _catchupSteps) {
|
||||
// We've finished animation, wait a little to allow servo to catch up
|
||||
s->stepNumber++;
|
||||
} else if (s->stepNumber == s->numSteps + _catchupSteps
|
||||
&& s->currentPosition != 0) {
|
||||
#ifdef IO_SWITCH_OFF_SERVO
|
||||
if ((s->currentProfile & NoPowerOff) == 0) {
|
||||
// Wait has finished, so switch off output driver to avoid servo buzz.
|
||||
_slaveDevice->_writeAnalogue(_firstSlavePin+pin, 0);
|
||||
}
|
||||
#endif
|
||||
s->numSteps = 0; // Done now.
|
||||
}
|
||||
}
|
||||
|
||||
// Display details of this device.
|
||||
void _display() override {
|
||||
DIAG(F("Servo Configured on Vpins:%d-%d, slave pins:%d-%d %S"),
|
||||
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
||||
(int)_firstSlavePin, (int)_firstSlavePin+_nPins-1,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
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:%d-%d SCL=%d SDO=%d"), (int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _clockPin, _dataPin);
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_TOUCHKEYPAD_H
|
176
IO_VL53L0X.h
176
IO_VL53L0X.h
@@ -42,14 +42,17 @@
|
||||
* If you have more than one module, then you will need to specify a digital VPIN (Arduino
|
||||
* digital output or I/O extender pin) which you connect to the module's XSHUT pin. Now,
|
||||
* when the device driver starts, the XSHUT pin is set LOW to turn the module off. Once
|
||||
* all VL53L0X modules are turned off, the driver works through each module in turn by
|
||||
* setting XSHUT to HIGH to turn the module on,, then writing the module's desired I2C address.
|
||||
* all VL53L0X modules are turned off, the driver works through each module in turn,
|
||||
* setting XSHUT to HIGH to turn that module on, then writing that module's desired I2C address.
|
||||
* In this way, many VL53L0X modules can be connected to the one I2C bus, each one
|
||||
* using a distinct I2C address.
|
||||
* using a distinct I2C address. The process is described in ST Microelectronics application
|
||||
* note AN4846.
|
||||
*
|
||||
* WARNING: If the device's XSHUT pin is not connected, then it is very prone to noise,
|
||||
* and the device may even reset when handled. If you're not using XSHUT, then it's
|
||||
* best to tie it to +5V.
|
||||
* WARNING: If the device's XSHUT pin is not connected, then it may be prone to noise,
|
||||
* and the device may reset spontaneously or when handled and the device will stop responding
|
||||
* on its allocated address. If you're not using XSHUT, then tie it to +5V via a resistor
|
||||
* (should be tied to +2.8V strictly). Some manufacturers (Adafruit and Polulu for example)
|
||||
* include a pull-up on the module, but others don't.
|
||||
*
|
||||
* The driver is configured as follows:
|
||||
*
|
||||
@@ -70,7 +73,8 @@
|
||||
* lowThreshold is the distance at which the digital vpin state is set to 1 (in mm),
|
||||
* highThreshold is the distance at which the digital vpin state is set to 0 (in mm),
|
||||
* and xshutPin is the VPIN number corresponding to a digital output that is connected to the
|
||||
* XSHUT terminal on the module.
|
||||
* XSHUT terminal on the module. The digital output may be an Arduino pin or an
|
||||
* I/O extender pin.
|
||||
*
|
||||
* Example:
|
||||
* In mySetup function within mySetup.cpp:
|
||||
@@ -93,7 +97,6 @@
|
||||
|
||||
class VL53L0X : public IODevice {
|
||||
private:
|
||||
uint8_t _i2cAddress;
|
||||
uint16_t _ambient;
|
||||
uint16_t _distance;
|
||||
uint16_t _signal;
|
||||
@@ -101,20 +104,23 @@ private:
|
||||
uint16_t _offThreshold;
|
||||
VPIN _xshutPin;
|
||||
bool _value;
|
||||
uint8_t _nextState = 0;
|
||||
uint8_t _nextState = STATE_INIT;
|
||||
I2CRB _rb;
|
||||
uint8_t _inBuffer[12];
|
||||
uint8_t _outBuffer[2];
|
||||
static bool _addressConfigInProgress;
|
||||
|
||||
// State machine states.
|
||||
enum : uint8_t {
|
||||
STATE_INIT = 0,
|
||||
STATE_CONFIGUREADDRESS = 1,
|
||||
STATE_SKIP = 2,
|
||||
STATE_CONFIGUREDEVICE = 3,
|
||||
STATE_INITIATESCAN = 4,
|
||||
STATE_CHECKSTATUS = 5,
|
||||
STATE_GETRESULTS = 6,
|
||||
STATE_DECODERESULTS = 7,
|
||||
STATE_INIT,
|
||||
STATE_RESTARTMODULE,
|
||||
STATE_CONFIGUREADDRESS,
|
||||
STATE_CONFIGUREDEVICE,
|
||||
STATE_INITIATESCAN,
|
||||
STATE_CHECKSTATUS,
|
||||
STATE_GETRESULTS,
|
||||
STATE_DECODERESULTS,
|
||||
STATE_FAILED,
|
||||
};
|
||||
|
||||
// Register addresses
|
||||
@@ -129,15 +135,15 @@ private:
|
||||
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
if (checkNoOverlap(firstVpin, nPins,i2cAddress)) new VL53L0X(firstVpin, nPins, i2cAddress, onThreshold, offThreshold, xshutPin);
|
||||
}
|
||||
|
||||
protected:
|
||||
VL53L0X(VPIN firstVpin, int nPins, uint8_t i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
VL53L0X(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint16_t onThreshold, uint16_t offThreshold, VPIN xshutPin = VPIN_NONE) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins, 3);
|
||||
_i2cAddress = i2cAddress;
|
||||
_nPins = (nPins > 3) ? 3 : nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
_onThreshold = onThreshold;
|
||||
_offThreshold = offThreshold;
|
||||
_xshutPin = xshutPin;
|
||||
@@ -145,81 +151,108 @@ protected:
|
||||
addDevice(this);
|
||||
}
|
||||
void _begin() override {
|
||||
if (_xshutPin == VPIN_NONE) {
|
||||
// Check if device is already responding on the nominated address.
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
// Yes, it's already on this address, so skip the address initialisation.
|
||||
_nextState = STATE_CONFIGUREDEVICE;
|
||||
} else {
|
||||
_nextState = STATE_INIT;
|
||||
}
|
||||
}
|
||||
// If there's only one device, then the XSHUT pin need not be connected. However,
|
||||
// the device will not respond on its default address if it has
|
||||
// already been changed. Therefore, we skip the address configuration if the
|
||||
// desired address is already responding on the I2C bus.
|
||||
_nextState = STATE_INIT;
|
||||
_addressConfigInProgress = false;
|
||||
}
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
uint8_t status;
|
||||
switch (_nextState) {
|
||||
case STATE_INIT:
|
||||
// On first entry to loop, reset this module by pulling XSHUT low. All modules
|
||||
// will be reset in turn.
|
||||
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0);
|
||||
_nextState = STATE_CONFIGUREADDRESS;
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
// Device already present on the nominated address, so skip the address initialisation.
|
||||
_nextState = STATE_CONFIGUREDEVICE;
|
||||
} else {
|
||||
// On first entry to loop, reset this module by pulling XSHUT low. Each module
|
||||
// will be addressed in turn, until all are in the reset state.
|
||||
// If no XSHUT pin is configured, then only one device is supported.
|
||||
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 0);
|
||||
_nextState = STATE_RESTARTMODULE;
|
||||
delayUntil(currentMicros+10000);
|
||||
}
|
||||
break;
|
||||
case STATE_RESTARTMODULE:
|
||||
// On second entry, set XSHUT pin high to allow this module to restart.
|
||||
// I've observed that the device tends to randomly reset if the XSHUT
|
||||
// pin is set high from a 5V arduino, even through a pullup resistor.
|
||||
// Assume that there will be a pull-up on the XSHUT pin to +2.8V as
|
||||
// recommended in the device datasheet. Then we only need to
|
||||
// turn our output pin high-impedence (by making it an input) and the
|
||||
// on-board pullup will do its job.
|
||||
// Ensure XSHUT is set for only one module at a time by using a
|
||||
// shared flag accessible to all device instances.
|
||||
if (!_addressConfigInProgress) {
|
||||
_addressConfigInProgress = true;
|
||||
// Configure XSHUT pin (if connected) to bring the module out of sleep mode.
|
||||
if (_xshutPin != VPIN_NONE) IODevice::configureInput(_xshutPin, false);
|
||||
// Allow the module time to restart
|
||||
delayUntil(currentMicros+10000);
|
||||
_nextState = STATE_CONFIGUREADDRESS;
|
||||
}
|
||||
break;
|
||||
case STATE_CONFIGUREADDRESS:
|
||||
// On second entry, set XSHUT pin high to allow the module to restart.
|
||||
// On the module, there is a diode in series with the XSHUT pin to
|
||||
// protect the low-voltage pin against +5V.
|
||||
if (_xshutPin != VPIN_NONE) IODevice::write(_xshutPin, 1);
|
||||
// Allow the module time to restart
|
||||
delay(10);
|
||||
// Then write the desired I2C address to the device, while this is the only
|
||||
// module responding to the default address.
|
||||
I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _i2cAddress);
|
||||
_nextState = STATE_SKIP;
|
||||
break;
|
||||
case STATE_SKIP:
|
||||
// Do nothing on the third entry.
|
||||
{
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
// Add subbus reference for desired address to the device default address.
|
||||
I2CAddress defaultAddress = {_I2CAddress, VL53L0X_I2C_DEFAULT_ADDRESS};
|
||||
status = I2CManager.write(defaultAddress, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress.deviceAddress());
|
||||
#else
|
||||
status = I2CManager.write(VL53L0X_I2C_DEFAULT_ADDRESS, 2, VL53L0X_REG_I2C_SLAVE_DEVICE_ADDRESS, _I2CAddress);
|
||||
#endif
|
||||
if (status != I2C_STATUS_OK) {
|
||||
reportError(status);
|
||||
}
|
||||
}
|
||||
delayUntil(currentMicros+10000);
|
||||
_nextState = STATE_CONFIGUREDEVICE;
|
||||
break;
|
||||
case STATE_CONFIGUREDEVICE:
|
||||
// On next entry, check if device address has been set.
|
||||
if (I2CManager.exists(_i2cAddress)) {
|
||||
// Allow next VL53L0X device to be configured
|
||||
_addressConfigInProgress = false;
|
||||
// Now check if device address has been set.
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
// Set 2.8V mode
|
||||
write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
|
||||
status = write_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV,
|
||||
read_reg(VL53L0X_CONFIG_PAD_SCL_SDA__EXTSUP_HV) | 0x01);
|
||||
if (status != I2C_STATUS_OK) {
|
||||
reportError(status);
|
||||
} else
|
||||
_nextState = STATE_INITIATESCAN;
|
||||
} else {
|
||||
DIAG(F("VL53L0X I2C:x%x device not responding"), _i2cAddress);
|
||||
DIAG(F("VL53L0X I2C:%s device not responding"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_nextState = STATE_FAILED;
|
||||
}
|
||||
_nextState = STATE_INITIATESCAN;
|
||||
break;
|
||||
case STATE_INITIATESCAN:
|
||||
// Not scanning, so initiate a scan
|
||||
_outBuffer[0] = VL53L0X_REG_SYSRANGE_START;
|
||||
_outBuffer[1] = 0x01;
|
||||
I2CManager.write(_i2cAddress, _outBuffer, 2, &_rb);
|
||||
I2CManager.write(_I2CAddress, _outBuffer, 2, &_rb);
|
||||
_nextState = STATE_CHECKSTATUS;
|
||||
break;
|
||||
case STATE_CHECKSTATUS:
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // try next time
|
||||
if (status != I2C_STATUS_OK) {
|
||||
DIAG(F("VL53L0X I2C:x%x Error:%d %S"), _i2cAddress, status, I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_value = false;
|
||||
reportError(status);
|
||||
} else
|
||||
_nextState = 2;
|
||||
_nextState = STATE_GETRESULTS;
|
||||
delayUntil(currentMicros + 95000); // wait for 95 ms before checking.
|
||||
_nextState = STATE_GETRESULTS;
|
||||
break;
|
||||
case STATE_GETRESULTS:
|
||||
// Ranging completed. Request results
|
||||
_outBuffer[0] = VL53L0X_REG_RESULT_RANGE_STATUS;
|
||||
I2CManager.read(_i2cAddress, _inBuffer, 12, _outBuffer, 1, &_rb);
|
||||
_nextState = 3;
|
||||
I2CManager.read(_I2CAddress, _inBuffer, 12, _outBuffer, 1, &_rb);
|
||||
delayUntil(currentMicros + 5000); // Allow 5ms to get data
|
||||
_nextState = STATE_DECODERESULTS;
|
||||
break;
|
||||
@@ -240,15 +273,28 @@ protected:
|
||||
else if (_distance > _offThreshold)
|
||||
_value = false;
|
||||
}
|
||||
// Completed. Restart scan on next loop entry.
|
||||
_nextState = STATE_INITIATESCAN;
|
||||
} else {
|
||||
reportError(status);
|
||||
}
|
||||
// Completed. Restart scan on next loop entry.
|
||||
_nextState = STATE_INITIATESCAN;
|
||||
break;
|
||||
case STATE_FAILED:
|
||||
// Do nothing.
|
||||
delayUntil(currentMicros+1000000UL);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Function to report a failed I2C operation.
|
||||
void reportError(uint8_t status) {
|
||||
DIAG(F("VL53L0X I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_value = false;
|
||||
}
|
||||
|
||||
// For analogue read, first pin returns distance, second pin is signal strength, and third is ambient level.
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
@@ -273,8 +319,8 @@ protected:
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("VL53L0X I2C:x%x Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"),
|
||||
_i2cAddress, _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
|
||||
DIAG(F("VL53L0X I2C:%s Configured on Vpins:%d-%d On:%dmm Off:%dmm %S"),
|
||||
_I2CAddress.toString(), _firstVpin, _firstVpin+_nPins-1, _onThreshold, _offThreshold,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
@@ -288,13 +334,15 @@ private:
|
||||
uint8_t outBuffer[2];
|
||||
outBuffer[0] = reg;
|
||||
outBuffer[1] = data;
|
||||
return I2CManager.write(_i2cAddress, outBuffer, 2);
|
||||
return I2CManager.write(_I2CAddress, outBuffer, 2);
|
||||
}
|
||||
uint8_t read_reg(uint8_t reg) {
|
||||
// read byte from register and return value
|
||||
I2CManager.read(_i2cAddress, _inBuffer, 1, ®, 1);
|
||||
I2CManager.read(_I2CAddress, _inBuffer, 1, ®, 1);
|
||||
return _inBuffer[0];
|
||||
}
|
||||
};
|
||||
|
||||
bool VL53L0X::_addressConfigInProgress = false;
|
||||
|
||||
#endif // IO_VL53L0X_h
|
||||
|
172
IO_duinoNodes.h
Normal file
172
IO_duinoNodes.h
Normal file
@@ -0,0 +1,172 @@
|
||||
/*
|
||||
* © 2022, Chris Harlow. All rights reserved.
|
||||
* Based on original by: Robin Simonds, Beagle Bay Inc
|
||||
*
|
||||
* This file is part of DCC-EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef IO_duinoNodes_h
|
||||
#define IO_duinoNodes_h
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
#define DN_PIN_MASK(bit) (0x80>>(bit%8))
|
||||
#define DN_GET_BIT(x) (_pinValues[(x)/8] & DN_PIN_MASK((x)) )
|
||||
#define DN_SET_BIT(x) _pinValues[(x)/8] |= DN_PIN_MASK((x))
|
||||
#define DN_CLR_BIT(x) _pinValues[(x)/8] &= ~DN_PIN_MASK((x))
|
||||
|
||||
|
||||
|
||||
class IO_duinoNodes : public IODevice {
|
||||
|
||||
public:
|
||||
IO_duinoNodes(VPIN firstVpin, int nPins,
|
||||
byte clockPin, byte latchPin, byte dataPin,
|
||||
const byte* pinmap) :
|
||||
IODevice(firstVpin, nPins) {
|
||||
|
||||
_latchPin=latchPin;
|
||||
_clockPin=clockPin;
|
||||
_dataPin=dataPin;
|
||||
_pinMap=pinmap;
|
||||
_nShiftBytes=(nPins+7)/8; // rounded up to multiples of 8 bits
|
||||
_pinValues=(byte*) calloc(_nShiftBytes,1);
|
||||
// Connect to HAL so my _write, _read and _loop will be called as required.
|
||||
IODevice::addDevice(this);
|
||||
}
|
||||
|
||||
// Called by HAL to start handling this device
|
||||
void _begin() override {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
pinMode(_latchPin,OUTPUT);
|
||||
pinMode(_clockPin,OUTPUT);
|
||||
pinMode(_dataPin,_pinMap?INPUT_PULLUP:OUTPUT);
|
||||
_display();
|
||||
}
|
||||
|
||||
// loop called by HAL supervisor
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
if (_pinMap) _loopInput(currentMicros);
|
||||
else if (_xmitPending) _loopOutput();
|
||||
}
|
||||
|
||||
void _loopInput(unsigned long currentMicros) {
|
||||
|
||||
if (currentMicros-_prevMicros < POLL_MICROS) return; // Nothing to do
|
||||
_prevMicros=currentMicros;
|
||||
|
||||
//set latch to HIGH to freeze & store parallel data
|
||||
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
//set latch to LOW to enable the data to be transmitted serially
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
|
||||
// stream in the bitmap using mapping order provided at constructor
|
||||
for (int xmitByte=0;xmitByte<_nShiftBytes; xmitByte++) {
|
||||
byte newByte=0;
|
||||
for (int xmitBit=0;xmitBit<8; xmitBit++) {
|
||||
ArduinoPins::fastWriteDigital(_clockPin, LOW);
|
||||
delayMicroseconds(1);
|
||||
bool data = ArduinoPins::fastReadDigital(_dataPin);
|
||||
byte map=_pinMap[xmitBit];
|
||||
if (data) newByte |= map;
|
||||
else newByte &= ~map;
|
||||
ArduinoPins::fastWriteDigital(_clockPin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
_pinValues[xmitByte]=newByte;
|
||||
// DIAG(F("DIN %x=%x"),xmitByte, newByte);
|
||||
}
|
||||
}
|
||||
|
||||
void _loopOutput() {
|
||||
// stream out the bitmap (highest pin first)
|
||||
_xmitPending=false;
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
|
||||
ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit));
|
||||
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
|
||||
ArduinoPins::fastWriteDigital(_clockPin,LOW);
|
||||
}
|
||||
ArduinoPins::fastWriteDigital(_latchPin, HIGH);
|
||||
}
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
int pin=vpin - _firstVpin;
|
||||
bool b=DN_GET_BIT(pin);
|
||||
return b?1:0;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
bool oldval=DN_GET_BIT(pin);
|
||||
bool newval=value!=0;
|
||||
if (newval==oldval) return; // no change
|
||||
if (newval) DN_SET_BIT(pin);
|
||||
else DN_CLR_BIT(pin);
|
||||
_xmitPending=true; // shift register will be sent on next _loop()
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("IO_duinoNodes %SPUT Configured on VPins:%d-%d shift=%d"),
|
||||
_pinMap?F("IN"):F("OUT"),
|
||||
(int)_firstVpin,
|
||||
(int)_firstVpin+_nPins-1, _nShiftBytes*8);
|
||||
}
|
||||
|
||||
private:
|
||||
static const unsigned long POLL_MICROS=100000; // 10 / S
|
||||
unsigned long _prevMicros;
|
||||
int _nShiftBytes=0;
|
||||
VPIN _latchPin,_clockPin,_dataPin;
|
||||
byte* _pinValues;
|
||||
bool _xmitPending; // Only relevant in output mode
|
||||
const byte* _pinMap; // NULL in output mode
|
||||
};
|
||||
|
||||
class IO_DNIN8 {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
// input arrives as board pin 0,7,6,5,1,2,3,4
|
||||
static const byte pinmap[8]={0x80,0x01,0x02,0x04,0x40,0x20,0x10,0x08};
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class IO_DNIN8K {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
// input arrives as board pin 0, 1, 2, 3, 4, 5, 6, 7
|
||||
static const byte pinmap[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,pinmap);
|
||||
}
|
||||
};
|
||||
|
||||
class IO_DNOU8 {
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, byte clockPin, byte latchPin, byte dataPin )
|
||||
{
|
||||
if (IODevice::checkNoOverlap(firstVpin,nPins))
|
||||
new IO_duinoNodes( firstVpin, nPins, clockPin, latchPin, dataPin,NULL);
|
||||
}
|
||||
|
||||
};
|
||||
#endif
|
167
LCDDisplay.cpp
167
LCDDisplay.cpp
@@ -1,167 +0,0 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. 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/>.
|
||||
*/
|
||||
|
||||
// CAUTION: the device dependent parts of this class are created in the .ini
|
||||
// using LCD_Implementation.h
|
||||
|
||||
/* The strategy for drawing the screen is as follows.
|
||||
* 1) There are up to eight rows of text to be displayed.
|
||||
* 2) Blank rows of text are ignored.
|
||||
* 3) If there are more non-blank rows than screen lines,
|
||||
* then all of the rows are displayed, with the rest of the
|
||||
* screen being blank.
|
||||
* 4) If there are fewer non-blank rows than screen lines,
|
||||
* then a scrolling strategy is adopted so that, on each screen
|
||||
* refresh, a different subset of the rows is presented.
|
||||
* 5) On each entry into loop2(), a single operation is sent to the
|
||||
* screen; this may be a position command or a character for
|
||||
* display. This spreads the onerous work of updating the screen
|
||||
* and ensures that other loop() functions in the application are
|
||||
* 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 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),
|
||||
* #define SCROLLMODE 1 is by page (alternate between pages),
|
||||
* #define SCROLLMODE 2 is by row (move up 1 row at a time).
|
||||
|
||||
*/
|
||||
|
||||
#include "LCDDisplay.h"
|
||||
|
||||
void LCDDisplay::clear() {
|
||||
clearNative();
|
||||
for (byte row = 0; row < MAX_LCD_ROWS; row++) rowBuffer[row][0] = '\0';
|
||||
topRow = -1; // loop2 will fill from row 0
|
||||
}
|
||||
|
||||
void LCDDisplay::setRow(byte line) {
|
||||
hotRow = line;
|
||||
hotCol = 0;
|
||||
}
|
||||
|
||||
size_t LCDDisplay::write(uint8_t b) {
|
||||
if (hotRow >= MAX_LCD_ROWS || hotCol >= MAX_LCD_COLS) return -1;
|
||||
rowBuffer[hotRow][hotCol] = b;
|
||||
hotCol++;
|
||||
rowBuffer[hotRow][hotCol] = 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void LCDDisplay::loop() {
|
||||
if (!lcdDisplay) return;
|
||||
lcdDisplay->loop2(false);
|
||||
}
|
||||
|
||||
LCDDisplay *LCDDisplay::loop2(bool force) {
|
||||
if (!lcdDisplay) return NULL;
|
||||
|
||||
// If output device is busy, don't do anything on this loop
|
||||
// This avoids blocking while waiting for the device to complete.
|
||||
if (isBusy()) return NULL;
|
||||
|
||||
unsigned long currentMillis = millis();
|
||||
|
||||
if (!force) {
|
||||
// See if we're in the time between updates
|
||||
if ((currentMillis - lastScrollTime) < LCD_SCROLL_TIME)
|
||||
return NULL;
|
||||
} else {
|
||||
// force full screen update from the beginning.
|
||||
rowFirst = -1;
|
||||
rowNext = 0;
|
||||
bufferPointer = 0;
|
||||
done = false;
|
||||
slot = 0;
|
||||
}
|
||||
|
||||
do {
|
||||
if (bufferPointer == 0) {
|
||||
// Find a line of data to write to the screen.
|
||||
if (rowFirst < 0) rowFirst = rowNext;
|
||||
skipBlankRows();
|
||||
if (!done) {
|
||||
// Non-blank line found, so copy it.
|
||||
for (uint8_t i = 0; i < sizeof(buffer); i++)
|
||||
buffer[i] = rowBuffer[rowNext][i];
|
||||
} else
|
||||
buffer[0] = '\0'; // Empty line
|
||||
setRowNative(slot); // Set position for display
|
||||
charIndex = 0;
|
||||
bufferPointer = &buffer[0];
|
||||
|
||||
} else {
|
||||
|
||||
// Write next character, or a space to erase current position.
|
||||
char ch = *bufferPointer;
|
||||
if (ch) {
|
||||
writeNative(ch);
|
||||
bufferPointer++;
|
||||
} else
|
||||
writeNative(' ');
|
||||
|
||||
if (++charIndex >= MAX_LCD_COLS) {
|
||||
// Screen slot completed, move to next slot on screen
|
||||
slot++;
|
||||
bufferPointer = 0;
|
||||
if (!done) {
|
||||
moveToNextRow();
|
||||
skipBlankRows();
|
||||
}
|
||||
}
|
||||
|
||||
if (slot >= lcdRows) {
|
||||
// Last slot finished, reset ready for next screen update.
|
||||
#if SCROLLMODE==2
|
||||
if (!done) {
|
||||
// On next refresh, restart one row on from previous start.
|
||||
rowNext = rowFirst;
|
||||
moveToNextRow();
|
||||
skipBlankRows();
|
||||
}
|
||||
#endif
|
||||
done = false;
|
||||
slot = 0;
|
||||
rowFirst = -1;
|
||||
lastScrollTime = currentMillis;
|
||||
return NULL;
|
||||
}
|
||||
}
|
||||
} while (force);
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
void LCDDisplay::moveToNextRow() {
|
||||
rowNext = (rowNext + 1) % MAX_LCD_ROWS;
|
||||
#if SCROLLMODE == 1
|
||||
// Finished if we've looped back to row 0
|
||||
if (rowNext == 0) done = true;
|
||||
#else
|
||||
// Finished if we're back to the first one shown
|
||||
if (rowNext == rowFirst) done = true;
|
||||
#endif
|
||||
}
|
||||
|
||||
void LCDDisplay::skipBlankRows() {
|
||||
while (!done && rowBuffer[rowNext][0] == 0)
|
||||
moveToNextRow();
|
||||
}
|
81
LCDDisplay.h
81
LCDDisplay.h
@@ -1,81 +0,0 @@
|
||||
/*
|
||||
* © 2021, Chris Harlow, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
#ifndef LCDDisplay_h
|
||||
#define LCDDisplay_h
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "DisplayInterface.h"
|
||||
|
||||
// Allow maximum message length to be overridden from config.h
|
||||
#if !defined(MAX_MSG_SIZE)
|
||||
#define MAX_MSG_SIZE 20
|
||||
#endif
|
||||
|
||||
// Set default scroll mode (overridable in config.h)
|
||||
#if !defined(SCROLLMODE)
|
||||
#define SCROLLMODE 1
|
||||
#endif
|
||||
|
||||
// This class is created in LCDisplay_Implementation.h
|
||||
|
||||
class LCDDisplay : public DisplayInterface {
|
||||
public:
|
||||
LCDDisplay() {};
|
||||
static const int MAX_LCD_ROWS = 8;
|
||||
static const int MAX_LCD_COLS = MAX_MSG_SIZE;
|
||||
static const long LCD_SCROLL_TIME = 3000; // 3 seconds
|
||||
|
||||
// Internally handled functions
|
||||
static void loop();
|
||||
LCDDisplay* loop2(bool force) override;
|
||||
void setRow(byte line) override;
|
||||
void clear() override;
|
||||
|
||||
size_t write(uint8_t b) override;
|
||||
|
||||
protected:
|
||||
uint8_t lcdRows;
|
||||
uint8_t lcdCols;
|
||||
|
||||
private:
|
||||
void moveToNextRow();
|
||||
void skipBlankRows();
|
||||
|
||||
// Relay functions to the live driver in the subclass
|
||||
virtual void clearNative() = 0;
|
||||
virtual void setRowNative(byte line) = 0;
|
||||
virtual size_t writeNative(uint8_t b) = 0;
|
||||
virtual bool isBusy() = 0;
|
||||
|
||||
unsigned long lastScrollTime = 0;
|
||||
int8_t hotRow = 0;
|
||||
int8_t hotCol = 0;
|
||||
int8_t topRow = 0;
|
||||
int8_t slot = 0;
|
||||
int8_t rowFirst = -1;
|
||||
int8_t rowNext = 0;
|
||||
int8_t charIndex = 0;
|
||||
char buffer[MAX_LCD_COLS + 1];
|
||||
char* bufferPointer = 0;
|
||||
bool done = false;
|
||||
|
||||
char rowBuffer[MAX_LCD_ROWS][MAX_LCD_COLS + 1];
|
||||
};
|
||||
|
||||
#endif
|
2
LCN.cpp
2
LCN.cpp
@@ -43,7 +43,7 @@ void LCN::loop() {
|
||||
|
||||
while (stream->available()) {
|
||||
int ch = stream->read();
|
||||
if (ch >= 0 && ch <= '9') { // accumulate id value
|
||||
if (ch >= '0' && ch <= '9') { // accumulate id value
|
||||
id = 10 * id + ch - '0';
|
||||
}
|
||||
else if (ch == 't' || ch == 'T') { // Turnout opcodes
|
||||
|
@@ -41,27 +41,28 @@
|
||||
// can't assume that its in that state when a sketch starts (and the
|
||||
// LiquidCrystal constructor is called).
|
||||
|
||||
LiquidCrystal_I2C::LiquidCrystal_I2C(uint8_t lcd_Addr, uint8_t lcd_cols,
|
||||
LiquidCrystal_I2C::LiquidCrystal_I2C(I2CAddress lcd_Addr, uint8_t lcd_cols,
|
||||
uint8_t lcd_rows) {
|
||||
_Addr = lcd_Addr;
|
||||
lcdRows = lcd_rows;
|
||||
lcdCols = lcd_cols;
|
||||
|
||||
lcdRows = lcd_rows; // Number of character rows (typically 2 or 4).
|
||||
lcdCols = lcd_cols; // Number of character columns (typically 16 or 20)
|
||||
_backlightval = 0;
|
||||
}
|
||||
|
||||
bool LiquidCrystal_I2C::begin() {
|
||||
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(100000L); // PCF8574 is spec'd to 100kHz.
|
||||
|
||||
if (I2CManager.exists(lcd_Addr)) {
|
||||
DIAG(F("%dx%d LCD configured on I2C:x%x"), (int)lcd_cols, (int)lcd_rows, (int)lcd_Addr);
|
||||
if (I2CManager.exists(_Addr)) {
|
||||
DIAG(F("%dx%d LCD configured on I2C:%s"), (int)lcdCols, (int)lcdRows, _Addr.toString());
|
||||
_displayfunction = LCD_4BITMODE | LCD_1LINE | LCD_5x8DOTS;
|
||||
begin();
|
||||
backlight();
|
||||
lcdDisplay = this;
|
||||
} else {
|
||||
DIAG(F("LCD not found on I2C:%s"), _Addr.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::begin() {
|
||||
if (lcdRows > 1) {
|
||||
_displayfunction |= LCD_2LINE;
|
||||
}
|
||||
@@ -79,15 +80,15 @@ void LiquidCrystal_I2C::begin() {
|
||||
|
||||
// we start in 8bit mode, try to set 4 bit mode
|
||||
write4bits(0x03);
|
||||
delayMicroseconds(4500); // wait min 4.1ms
|
||||
delayMicroseconds(5000); // wait min 4.1ms
|
||||
|
||||
// second try
|
||||
write4bits(0x03);
|
||||
delayMicroseconds(4500); // wait min 4.1ms
|
||||
delayMicroseconds(5000); // wait min 4.1ms
|
||||
|
||||
// third go!
|
||||
write4bits(0x03);
|
||||
delayMicroseconds(150);
|
||||
delayMicroseconds(5000);
|
||||
|
||||
// finally, set to 4-bit interface
|
||||
write4bits(0x02);
|
||||
@@ -99,26 +100,23 @@ void LiquidCrystal_I2C::begin() {
|
||||
_displaycontrol = LCD_DISPLAYON | LCD_CURSOROFF | LCD_BLINKOFF;
|
||||
display();
|
||||
|
||||
// clear it off
|
||||
clear();
|
||||
|
||||
// Initialize to default text direction (for roman languages)
|
||||
_displaymode = LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT;
|
||||
|
||||
// set the entry mode
|
||||
command(LCD_ENTRYMODESET | _displaymode);
|
||||
|
||||
setRowNative(0);
|
||||
return true;
|
||||
}
|
||||
|
||||
/********** high level commands, for the user! */
|
||||
void LiquidCrystal_I2C::clearNative() {
|
||||
command(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
|
||||
delayMicroseconds(2000); // this command takes 1.52ms
|
||||
delayMicroseconds(2000); // this command takes 1.52ms but allow plenty
|
||||
}
|
||||
|
||||
void LiquidCrystal_I2C::setRowNative(byte row) {
|
||||
int row_offsets[] = {0x00, 0x40, 0x14, 0x54};
|
||||
uint8_t row_offsets[] = {0x00, 0x40, 0x14, 0x54};
|
||||
if (row >= lcdRows) {
|
||||
row = lcdRows - 1; // we count rows starting w/0
|
||||
}
|
||||
@@ -146,6 +144,10 @@ size_t LiquidCrystal_I2C::writeNative(uint8_t value) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
bool LiquidCrystal_I2C::isBusy() {
|
||||
return rb.isBusy();
|
||||
}
|
||||
|
||||
/*********** mid level commands, for sending data/cmds */
|
||||
|
||||
inline void LiquidCrystal_I2C::command(uint8_t value) {
|
||||
@@ -192,11 +194,12 @@ void LiquidCrystal_I2C::send(uint8_t value, uint8_t mode) {
|
||||
uint8_t lownib = ((value & 0x0f) << BACKPACK_DATA_BITS) | mode;
|
||||
// Send both nibbles
|
||||
uint8_t len = 0;
|
||||
rb.wait();
|
||||
outputBuffer[len++] = highnib|En;
|
||||
outputBuffer[len++] = highnib;
|
||||
outputBuffer[len++] = lownib|En;
|
||||
outputBuffer[len++] = lownib;
|
||||
I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously
|
||||
I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously
|
||||
}
|
||||
|
||||
// write 4 data bits to the HD44780 LCD controller.
|
||||
@@ -206,14 +209,16 @@ void LiquidCrystal_I2C::write4bits(uint8_t value) {
|
||||
// I2C clock cycle time of 2.5us at 400kHz. Data is clocked in to the
|
||||
// HD44780 on the trailing edge of the Enable pin.
|
||||
uint8_t len = 0;
|
||||
rb.wait();
|
||||
outputBuffer[len++] = _data|En;
|
||||
outputBuffer[len++] = _data;
|
||||
I2CManager.write(_Addr, outputBuffer, len); // Write command synchronously
|
||||
I2CManager.write(_Addr, outputBuffer, len, &rb); // Write command asynchronously
|
||||
}
|
||||
|
||||
// write a byte to the PCF8574 I2C interface. We don't need to set
|
||||
// the enable pin for this.
|
||||
void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
|
||||
rb.wait();
|
||||
outputBuffer[0] = value | _backlightval;
|
||||
I2CManager.write(_Addr, outputBuffer, 1); // Write command synchronously
|
||||
I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously
|
||||
}
|
@@ -22,7 +22,7 @@
|
||||
#define LiquidCrystal_I2C_h
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "LCDDisplay.h"
|
||||
#include "Display.h"
|
||||
#include "I2CManager.h"
|
||||
|
||||
// commands
|
||||
@@ -62,33 +62,38 @@
|
||||
#define Rw (1 << BACKPACK_Rw_BIT) // Read/Write bit
|
||||
#define Rs (1 << BACKPACK_Rs_BIT) // Register select bit
|
||||
|
||||
class LiquidCrystal_I2C : public LCDDisplay {
|
||||
class LiquidCrystal_I2C : public DisplayDevice {
|
||||
public:
|
||||
LiquidCrystal_I2C(uint8_t lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
|
||||
void begin();
|
||||
LiquidCrystal_I2C(I2CAddress lcd_Addr,uint8_t lcd_cols,uint8_t lcd_rows);
|
||||
bool begin() override;
|
||||
void clearNative() override;
|
||||
void setRowNative(byte line) override;
|
||||
size_t writeNative(uint8_t c) override;
|
||||
// I/O is synchronous, so if this is called we're not busy!
|
||||
bool isBusy() override;
|
||||
|
||||
void display();
|
||||
void noBacklight();
|
||||
void backlight();
|
||||
|
||||
void command(uint8_t);
|
||||
uint16_t getNumCols() { return lcdCols; }
|
||||
uint16_t getNumRows() { return lcdRows; }
|
||||
|
||||
|
||||
private:
|
||||
void send(uint8_t, uint8_t);
|
||||
void write4bits(uint8_t);
|
||||
void expanderWrite(uint8_t);
|
||||
uint8_t _Addr;
|
||||
uint8_t lcdCols=0, lcdRows=0;
|
||||
I2CAddress _Addr;
|
||||
uint8_t _displayfunction;
|
||||
uint8_t _displaycontrol;
|
||||
uint8_t _displaymode;
|
||||
uint8_t _backlightval;
|
||||
uint8_t _backlightval = 0;
|
||||
|
||||
uint8_t outputBuffer[4];
|
||||
// I/O is synchronous, so if this is called we're not busy!
|
||||
bool isBusy() override { return false; }
|
||||
I2CRB rb;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@@ -75,6 +75,23 @@ 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;
|
||||
|
||||
@@ -90,9 +107,8 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
else brakePin=UNUSED_PIN;
|
||||
|
||||
currentPin=current_pin;
|
||||
if (currentPin!=UNUSED_PIN) {
|
||||
senseOffset = ADCee::init(currentPin);
|
||||
}
|
||||
if (currentPin!=UNUSED_PIN) ADCee::init(currentPin);
|
||||
senseOffset=0; // value can not be obtained until waveform is activated
|
||||
|
||||
faultPin=fault_pin;
|
||||
if (faultPin != UNUSED_PIN) {
|
||||
@@ -121,8 +137,8 @@ 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"));
|
||||
else {
|
||||
DIAG(F("CurrentPin=A%d, Offset=%d, TripValue=%d"),
|
||||
currentPin-A0, senseOffset,rawCurrentTripValue);
|
||||
DIAG(F("CurrentPin=A%d, TripValue=%d"),
|
||||
currentPin-A0, rawCurrentTripValue);
|
||||
|
||||
// self testing diagnostic for the non-float converters... may be removed when happy
|
||||
// DIAG(F("senseFactorInternal=%d raw2mA(1000)=%d mA2Raw(1000)=%d"),
|
||||
@@ -144,16 +160,18 @@ bool MotorDriver::isPWMCapable() {
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::ON;
|
||||
if (on) {
|
||||
noInterrupts();
|
||||
// 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("CurrentPin A%d sensOffset=%d"),currentPin-A0,senseOffset);
|
||||
}
|
||||
|
||||
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
||||
interrupts();
|
||||
if (isProgTrack)
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
}
|
||||
else {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? HIGH : LOW);
|
||||
interrupts();
|
||||
}
|
||||
powerMode=mode;
|
||||
}
|
||||
@@ -238,7 +256,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,
|
||||
|
@@ -182,7 +182,7 @@
|
||||
#define STACKED_MOTOR_SHIELD F("STACKED_MOTOR_SHIELD"),\
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver( 2, 10, UNUSED_PIN, 7, A3, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver( 5, 4, UNUSED_PIN, 6, A4, 2.99, 1500, UNUSED_PIN)
|
||||
new MotorDriver( 2, 10, UNUSED_PIN, 7, A4, 2.99, 1500, UNUSED_PIN), \
|
||||
new MotorDriver( 5, 4, UNUSED_PIN, 6, A5, 2.99, 1500, UNUSED_PIN)
|
||||
//
|
||||
#endif
|
||||
|
@@ -485,10 +485,10 @@
|
||||
<text x="32.86" y="598.05" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Accessories <v:newlineChar/><tspan
|
||||
x="30.42" dy="1.2em" class="st5">(</tspan>Output.cpp)</text> </g>
|
||||
</a>
|
||||
<a xlink:href="https://github.com/DCC-EX/CommandStation-EX/blob/master/LCDDisplay.cpp">
|
||||
<a xlink:href="https://github.com/DCC-EX/CommandStation-EX/blob/master/Display.cpp">
|
||||
<g id="shape14-81" v:mID="14" v:groupContext="shape" v:layerMember="0" transform="translate(288,-116.156)">
|
||||
<title>Process.14</title>
|
||||
<desc>Other Utilities (LCDDisplay.cpp)</desc>
|
||||
<desc>Other Utilities (Display.cpp)</desc>
|
||||
<v:custProps>
|
||||
<v:cp v:nameU="Cost" v:lbl="Cost" v:prompt="" v:type="7" v:format="@" v:sortKey="" v:invis="false"
|
||||
v:ask="false" v:langID="1033" v:cal="0"/>
|
||||
@@ -522,7 +522,7 @@
|
||||
</g>
|
||||
<rect x="0" y="580.5" width="90" height="31.5" rx="13.5" ry="13.5" class="st3"/>
|
||||
<text x="19.29" y="593.55" class="st4" v:langID="1033"><v:paragraph v:horizAlign="1"/><v:tabList/>Other Utilities<v:newlineChar/><tspan
|
||||
x="14.29" dy="1.2em" class="st5">(</tspan>LCDDisplay.cpp)</text> </g>
|
||||
x="14.29" dy="1.2em" class="st5">(</tspan>Display.cpp)</text> </g>
|
||||
</a>
|
||||
<g id="shape3-88" v:mID="3" v:groupContext="shape" v:layerMember="1" transform="translate(108,-443.812)">
|
||||
<title>Dynamic connector</title>
|
||||
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
171
Release_Notes/CommandRef.md
Normal file
171
Release_Notes/CommandRef.md
Normal file
@@ -0,0 +1,171 @@
|
||||
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 numeric.
|
||||
- [something] indicates its optional.
|
||||
- Not all commands have a response, and not all responses come from the last commands that you have issued.
|
||||
|
||||
Startup status
|
||||
<s>
|
||||
|
||||
Track power management
|
||||
<1>
|
||||
<1 MAIN|PROG|JOIN>
|
||||
<0>
|
||||
<0 MAIN|PROG>
|
||||
|
||||
Basic manual control
|
||||
<t cab speed direction>
|
||||
<F cab function 1|0>
|
||||
<!>
|
||||
<T id 0|1|T|C>
|
||||
|
||||
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>
|
||||
|
||||
|
||||
Outputs
|
||||
<Z id activate>
|
||||
<Z id vpin iflag>
|
||||
|
||||
Sensors
|
||||
<S id vpin pullup>
|
||||
|
||||
Decoder programming
|
||||
<w cab cv value>
|
||||
<b cab cv bit value>
|
||||
<W cabid>
|
||||
<W cv value>
|
||||
<V cv value>
|
||||
<V cv bit value>
|
||||
<R>
|
||||
<R cv>
|
||||
<B cv bit value>
|
||||
<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>
|
||||
|
||||
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>
|
||||
<* ... *>
|
||||
|
39
Release_Notes/duinoNodes.md
Normal file
39
Release_Notes/duinoNodes.md
Normal file
@@ -0,0 +1,39 @@
|
||||
Using Lew's Duino Gear boards:
|
||||
|
||||
1. DNIN8 Input
|
||||
This is a shift-register implementation of a digital input collector.
|
||||
Multiple DNIN8 may be connected in sequence but it is IMPORTANT that the software
|
||||
configuratuion correctly represents the number of boards connected otherwise the results will be meaningless.
|
||||
|
||||
Use in myAnimation.h
|
||||
|
||||
HAL(IO_DNIN8, firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
e.g.
|
||||
HAL(IO_DNIN8, 400, 16, 40, 42, 44)
|
||||
|
||||
OR Use in myHal.cpp
|
||||
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
|
||||
|
||||
|
||||
This will create virtaul pins 400-415 using two DNIN8 boards connected in sequence.
|
||||
Vpins 400-407 will be on the first board (closest to the CS) and 408-415 on the second.
|
||||
|
||||
Note: 16 pins uses two boards. You may specify a non-multiple-of-8 pins but this will be rounded up to a multiple of 8 and you must connect ONLY the number of boards that this takes.
|
||||
|
||||
This example uses Arduino GPIO pins 40,42,44 as these are conveniently side-by-side on a Mega which is easier when you are using a 3 strand cable.
|
||||
|
||||
The DNIN8K module works the same but you must use DNIN8K in the HAL setup instead of DNIN8. NO you cant mix 8 and 8k versions in the same string of boards but you can create another string of boards.
|
||||
|
||||
|
||||
DNOU8 works the same way,
|
||||
Use in myAnimation.h
|
||||
|
||||
HAL(IO_DNOU8, firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
e.g.
|
||||
HAL(IO_DNIN8, 450, 16, 45, 47, 49)
|
||||
|
||||
OR Use in myHal.cpp
|
||||
IO_DNIN8::create( firstVpin, numPins, clockPin, latchPin, dataPin)
|
||||
|
||||
This creates a string of input pins 450-465. Note the clock/latch/data pins must be different to any DNIN8/k pins.
|
@@ -65,6 +65,13 @@ int RingStream::availableForWrite() {
|
||||
}
|
||||
|
||||
size_t RingStream::printFlash(const FSH * flashBuffer) {
|
||||
// This function does not work on a 32 bit processor where the runtime
|
||||
// sometimes misrepresents the pointer size in uintptr_t.
|
||||
// In any case its not really necessary in a 32 bit processor because
|
||||
// we have adequate ram.
|
||||
if (sizeof(void*)>2) return print(flashBuffer);
|
||||
|
||||
|
||||
// We are about to add a PROGMEM string to the buffer.
|
||||
// To save RAM we can insert a marker and the
|
||||
// progmem address into the buffer instead.
|
||||
@@ -76,7 +83,7 @@ size_t RingStream::printFlash(const FSH * flashBuffer) {
|
||||
|
||||
// Establish the actual length of the progmem string.
|
||||
char * flash=(char *)flashBuffer;
|
||||
int16_t plength=strlen_P(flash);
|
||||
int16_t plength=STRLEN_P(flash);
|
||||
if (plength==0) return 0; // just ignore empty string
|
||||
|
||||
// Retain the buffer count as it will be modified by the marker+address insert
|
||||
@@ -107,8 +114,11 @@ int RingStream::read() {
|
||||
if ((_pos_read==_pos_write) && !_overflow) return -1; // empty
|
||||
byte b=readRawByte();
|
||||
if (b!=FLASH_INSERT_MARKER) return b;
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// Detected a flash insert
|
||||
if (sizeof(void*)>2) {
|
||||
DIAG(F("Detected invalid flash insert marker at pos %d"),_pos_read);
|
||||
return '?';
|
||||
}
|
||||
// read address bytes LSB first (size depends on CPU)
|
||||
uintptr_t iFlash=0;
|
||||
for (byte f=0; f<sizeof(iFlash); f++) {
|
||||
@@ -120,10 +130,6 @@ int RingStream::read() {
|
||||
_flashInsert=reinterpret_cast<char * >( iFlash);
|
||||
// and try again... so will read the first byte of the insert.
|
||||
return read();
|
||||
#else
|
||||
DIAG(F("Detected flash insert marker at pos %d but there should not be one"),_pos_read);
|
||||
return '\0';
|
||||
#endif
|
||||
}
|
||||
|
||||
byte RingStream::readRawByte() {
|
||||
@@ -189,12 +195,6 @@ bool RingStream::commit() {
|
||||
_mark++;
|
||||
if (_mark==_len) _mark=0;
|
||||
_buffer[_mark]=lowByte(_count);
|
||||
// Enable this for debugging only, it requires A LOT of RAM
|
||||
//{ char s[_count+2];
|
||||
// strncpy(s, (const char*)&(_buffer[_mark+1]), _count);
|
||||
// s[_count]=0;
|
||||
// DIAG(F("RS commit count=%d core %d \"%s\""), _count, xPortGetCoreID(), s);
|
||||
//}
|
||||
_ringClient = NO_CLIENT;
|
||||
return true; // commit worked
|
||||
}
|
||||
|
@@ -27,7 +27,7 @@ class RingStream : public Print {
|
||||
|
||||
public:
|
||||
RingStream( const uint16_t len);
|
||||
static const int THIS_IS_A_RINGSTREAM=77;
|
||||
static const int THIS_IS_A_RINGSTREAM=777;
|
||||
virtual size_t write(uint8_t b);
|
||||
|
||||
// This availableForWrite function is subverted from its original intention so that a caller
|
||||
|
444
SSD1306Ascii.cpp
444
SSD1306Ascii.cpp
@@ -143,56 +143,69 @@ const uint8_t FLASH SSD1306AsciiWire::SH1106_132x64init[] = {
|
||||
// SSD1306AsciiWire Method Definitions
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Constructor
|
||||
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height) {
|
||||
// Auto-detect address
|
||||
SSD1306AsciiWire::SSD1306AsciiWire(int width, int height)
|
||||
: SSD1306AsciiWire(0, width, height) { }
|
||||
|
||||
// Constructor with explicit address
|
||||
SSD1306AsciiWire::SSD1306AsciiWire(I2CAddress address, int width, int height) {
|
||||
m_i2cAddr = address;
|
||||
m_displayWidth = width;
|
||||
m_displayHeight = height;
|
||||
// Set size in characters in base class
|
||||
lcdRows = height / 8;
|
||||
lcdCols = width / 6;
|
||||
// Set size in characters
|
||||
m_charsPerColumn = m_displayHeight / fontHeight;
|
||||
m_charsPerRow = (m_displayWidth+fontWidth-1) / fontWidth; // Round up
|
||||
}
|
||||
|
||||
bool SSD1306AsciiWire::begin() {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(400000L); // Set max supported I2C speede
|
||||
|
||||
if (m_i2cAddr == 0) {
|
||||
// Probe for I2C device on 0x3c and 0x3d.
|
||||
for (uint8_t address = 0x3c; address <= 0x3d; address++) {
|
||||
if (I2CManager.exists(address)) {
|
||||
m_i2cAddr = address;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_i2cAddr == 0)
|
||||
DIAG(F("OLED display not found"));
|
||||
}
|
||||
|
||||
m_col = 0;
|
||||
m_row = 0;
|
||||
m_colOffset = 0;
|
||||
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(400000L); // Set max supported I2C speed
|
||||
for (byte address = 0x3c; address <= 0x3d; address++) {
|
||||
if (I2CManager.exists(address)) {
|
||||
m_i2cAddr = address;
|
||||
if (m_displayWidth==132 && m_displayHeight==64) {
|
||||
// SH1106 display. This uses 128x64 centered within a 132x64 OLED.
|
||||
m_colOffset = 2;
|
||||
I2CManager.write_P(address, SH1106_132x64init, sizeof(SH1106_132x64init));
|
||||
} else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) {
|
||||
// SSD1306 128x64 or 128x32
|
||||
I2CManager.write_P(address, Adafruit128xXXinit, sizeof(Adafruit128xXXinit));
|
||||
if (m_displayHeight == 32)
|
||||
I2CManager.write(address, 5, 0, // Set command mode
|
||||
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
|
||||
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
|
||||
} else {
|
||||
DIAG(F("OLED configuration option not recognised"));
|
||||
return;
|
||||
}
|
||||
// Device found
|
||||
DIAG(F("%dx%d OLED display configured on I2C:x%x"), width, height, address);
|
||||
// Set singleton address
|
||||
lcdDisplay = this;
|
||||
clear();
|
||||
return;
|
||||
}
|
||||
if (m_displayWidth==132 && m_displayHeight==64) {
|
||||
// SH1106 display. This uses 128x64 centered within a 132x64 OLED.
|
||||
m_colOffset = 2;
|
||||
I2CManager.write_P(m_i2cAddr, SH1106_132x64init, sizeof(SH1106_132x64init));
|
||||
} else if (m_displayWidth==128 && (m_displayHeight==64 || m_displayHeight==32)) {
|
||||
// SSD1306 or SSD1309 128x64 or 128x32
|
||||
I2CManager.write_P(m_i2cAddr, Adafruit128xXXinit, sizeof(Adafruit128xXXinit));
|
||||
if (m_displayHeight == 32)
|
||||
I2CManager.write(m_i2cAddr, 5, 0, // Set command mode
|
||||
SSD1306_SETMULTIPLEX, 0x1F, // ratio 32
|
||||
SSD1306_SETCOMPINS, 0x02); // sequential COM pins, disable remap
|
||||
} else {
|
||||
DIAG(F("OLED configuration option not recognised"));
|
||||
return false;
|
||||
}
|
||||
DIAG(F("OLED display not found"));
|
||||
// Device found
|
||||
DIAG(F("%dx%d OLED display configured on I2C:%s"), m_displayWidth, m_displayHeight, m_i2cAddr.toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Clear screen by writing blank pixels. */
|
||||
void SSD1306AsciiWire::clearNative() {
|
||||
const int maxBytes = sizeof(blankPixels); // max number of bytes sendable over Wire
|
||||
const int maxBytes = sizeof(blankPixels) - 1; // max number of pixel columns (bytes) per transmission
|
||||
for (uint8_t r = 0; r <= m_displayHeight/8 - 1; r++) {
|
||||
setRowNative(r); // Position at start of row to be erased
|
||||
for (uint8_t c = 0; c <= m_displayWidth - 1; c += maxBytes-1) {
|
||||
uint8_t len = min(m_displayWidth-c, maxBytes-1) + 1;
|
||||
I2CManager.write_P(m_i2cAddr, blankPixels, len); // Write a number of blank columns
|
||||
for (uint8_t c = 0; c < m_displayWidth; c += maxBytes) {
|
||||
uint8_t len = m_displayWidth-c; // Number of pixel columns remaining
|
||||
if (len > maxBytes) len = maxBytes;
|
||||
I2CManager.write_P(m_i2cAddr, blankPixels, len+1); // Write command + 'len' blank columns
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,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') {
|
||||
@@ -237,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
|
||||
@@ -245,127 +259,261 @@ size_t SSD1306AsciiWire::writeNative(uint8_t ch) {
|
||||
outputBuffer[0] = 0x40; // set SSD1306 controller to data mode
|
||||
uint8_t bufferPos = 1;
|
||||
// Copy character pixel columns
|
||||
for (uint8_t i = 0; i < fontWidth; i++)
|
||||
outputBuffer[bufferPos++] = GETFLASH(base++);
|
||||
// Add blank pixels between letters
|
||||
for (uint8_t i = 0; i < letterSpacing; i++)
|
||||
outputBuffer[bufferPos++] = 0;
|
||||
for (uint8_t i = 0; i < fontWidth; i++) {
|
||||
if (m_col++ < m_displayWidth)
|
||||
outputBuffer[bufferPos++] = GETFLASH(base++);
|
||||
}
|
||||
|
||||
// Write the data to I2C display
|
||||
I2CManager.write(m_i2cAddr, outputBuffer, bufferPos, &requestBlock);
|
||||
m_col += fontWidth + letterSpacing;
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// Font characters, 5x7 pixels, 0x61 characters starting at 0x20.
|
||||
// Font characters, 6x8 pixels, starting at 0x20.
|
||||
// Lower case characters optionally omitted.
|
||||
const uint8_t FLASH SSD1306AsciiWire::System5x7[] = {
|
||||
const uint8_t FLASH SSD1306AsciiWire::System6x8[] = {
|
||||
|
||||
// Fixed width; char width table not used !!!!
|
||||
// or with lowercase character omitted.
|
||||
|
||||
// font data
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, // (space)
|
||||
0x00, 0x00, 0x5F, 0x00, 0x00, // !
|
||||
0x00, 0x07, 0x00, 0x07, 0x00, // "
|
||||
0x14, 0x7F, 0x14, 0x7F, 0x14, // #
|
||||
0x24, 0x2A, 0x7F, 0x2A, 0x12, // $
|
||||
0x23, 0x13, 0x08, 0x64, 0x62, // %
|
||||
0x36, 0x49, 0x55, 0x22, 0x50, // &
|
||||
0x00, 0x05, 0x03, 0x00, 0x00, // '
|
||||
0x00, 0x1C, 0x22, 0x41, 0x00, // (
|
||||
0x00, 0x41, 0x22, 0x1C, 0x00, // )
|
||||
0x08, 0x2A, 0x1C, 0x2A, 0x08, // *
|
||||
0x08, 0x08, 0x3E, 0x08, 0x08, // +
|
||||
0x00, 0x50, 0x30, 0x00, 0x00, // ,
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, // -
|
||||
0x00, 0x60, 0x60, 0x00, 0x00, // .
|
||||
0x20, 0x10, 0x08, 0x04, 0x02, // /
|
||||
0x3E, 0x51, 0x49, 0x45, 0x3E, // 0
|
||||
0x00, 0x42, 0x7F, 0x40, 0x00, // 1
|
||||
0x42, 0x61, 0x51, 0x49, 0x46, // 2
|
||||
0x21, 0x41, 0x45, 0x4B, 0x31, // 3
|
||||
0x18, 0x14, 0x12, 0x7F, 0x10, // 4
|
||||
0x27, 0x45, 0x45, 0x45, 0x39, // 5
|
||||
0x3C, 0x4A, 0x49, 0x49, 0x30, // 6
|
||||
0x01, 0x71, 0x09, 0x05, 0x03, // 7
|
||||
0x36, 0x49, 0x49, 0x49, 0x36, // 8
|
||||
0x06, 0x49, 0x49, 0x29, 0x1E, // 9
|
||||
0x00, 0x36, 0x36, 0x00, 0x00, // :
|
||||
0x00, 0x56, 0x36, 0x00, 0x00, // ;
|
||||
0x00, 0x08, 0x14, 0x22, 0x41, // <
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, // =
|
||||
0x41, 0x22, 0x14, 0x08, 0x00, // >
|
||||
0x02, 0x01, 0x51, 0x09, 0x06, // ?
|
||||
0x32, 0x49, 0x79, 0x41, 0x3E, // @
|
||||
0x7E, 0x11, 0x11, 0x11, 0x7E, // A
|
||||
0x7F, 0x49, 0x49, 0x49, 0x36, // B
|
||||
0x3E, 0x41, 0x41, 0x41, 0x22, // C
|
||||
0x7F, 0x41, 0x41, 0x22, 0x1C, // D
|
||||
0x7F, 0x49, 0x49, 0x49, 0x41, // E
|
||||
0x7F, 0x09, 0x09, 0x01, 0x01, // F
|
||||
0x3E, 0x41, 0x41, 0x51, 0x32, // G
|
||||
0x7F, 0x08, 0x08, 0x08, 0x7F, // H
|
||||
0x00, 0x41, 0x7F, 0x41, 0x00, // I
|
||||
0x20, 0x40, 0x41, 0x3F, 0x01, // J
|
||||
0x7F, 0x08, 0x14, 0x22, 0x41, // K
|
||||
0x7F, 0x40, 0x40, 0x40, 0x40, // L
|
||||
0x7F, 0x02, 0x04, 0x02, 0x7F, // M
|
||||
0x7F, 0x04, 0x08, 0x10, 0x7F, // N
|
||||
0x3E, 0x41, 0x41, 0x41, 0x3E, // O
|
||||
0x7F, 0x09, 0x09, 0x09, 0x06, // P
|
||||
0x3E, 0x41, 0x51, 0x21, 0x5E, // Q
|
||||
0x7F, 0x09, 0x19, 0x29, 0x46, // R
|
||||
0x46, 0x49, 0x49, 0x49, 0x31, // S
|
||||
0x01, 0x01, 0x7F, 0x01, 0x01, // T
|
||||
0x3F, 0x40, 0x40, 0x40, 0x3F, // U
|
||||
0x1F, 0x20, 0x40, 0x20, 0x1F, // V
|
||||
0x7F, 0x20, 0x18, 0x20, 0x7F, // W
|
||||
0x63, 0x14, 0x08, 0x14, 0x63, // X
|
||||
0x03, 0x04, 0x78, 0x04, 0x03, // Y
|
||||
0x61, 0x51, 0x49, 0x45, 0x43, // Z
|
||||
0x00, 0x00, 0x7F, 0x41, 0x41, // [
|
||||
0x02, 0x04, 0x08, 0x10, 0x20, // "\"
|
||||
0x41, 0x41, 0x7F, 0x00, 0x00, // ]
|
||||
0x04, 0x02, 0x01, 0x02, 0x04, // ^
|
||||
0x40, 0x40, 0x40, 0x40, 0x40, // _
|
||||
0x00, 0x01, 0x02, 0x04, 0x00, // `
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // (space) (20)
|
||||
0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, // ! (21)
|
||||
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, 0x55, 0x22, 0x50, 0x00, // &
|
||||
0x00, 0x05, 0x03, 0x00, 0x00, 0x00, // '
|
||||
0x00, 0x1C, 0x22, 0x41, 0x00, 0x00, // (
|
||||
0x00, 0x41, 0x22, 0x1C, 0x00, 0x00, // )
|
||||
0x08, 0x2A, 0x1C, 0x2A, 0x08, 0x00, // *
|
||||
0x08, 0x08, 0x3E, 0x08, 0x08, 0x00, // +
|
||||
0x00, 0x50, 0x30, 0x00, 0x00, 0x00, // ,
|
||||
0x08, 0x08, 0x08, 0x08, 0x08, 0x00, // -
|
||||
0x00, 0x60, 0x60, 0x00, 0x00, 0x00, // .
|
||||
0x20, 0x10, 0x08, 0x04, 0x02, 0x00, // / (47)
|
||||
0x3E, 0x51, 0x49, 0x45, 0x3E, 0x00, // 0 (48)
|
||||
0x00, 0x42, 0x7F, 0x40, 0x00, 0x00, // 1
|
||||
0x42, 0x61, 0x51, 0x49, 0x46, 0x00, // 2
|
||||
0x21, 0x41, 0x45, 0x4B, 0x31, 0x00, // 3
|
||||
0x18, 0x14, 0x12, 0x7F, 0x10, 0x00, // 4
|
||||
0x27, 0x45, 0x45, 0x45, 0x39, 0x00, // 5
|
||||
0x3C, 0x4A, 0x49, 0x49, 0x30, 0x00, // 6
|
||||
0x01, 0x71, 0x09, 0x05, 0x03, 0x00, // 7
|
||||
0x36, 0x49, 0x49, 0x49, 0x36, 0x00, // 8
|
||||
0x06, 0x49, 0x49, 0x29, 0x1E, 0x00, // 9 (57)
|
||||
0x00, 0x36, 0x36, 0x00, 0x00, 0x00, // :
|
||||
0x00, 0x56, 0x36, 0x00, 0x00, 0x00, // ;
|
||||
0x00, 0x08, 0x14, 0x22, 0x41, 0x00, // <
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x00, // =
|
||||
0x41, 0x22, 0x14, 0x08, 0x00, 0x00, // >
|
||||
0x02, 0x01, 0x51, 0x09, 0x06, 0x00, // ?
|
||||
0x32, 0x49, 0x79, 0x41, 0x3E, 0x00, // @ (64)
|
||||
0x7E, 0x11, 0x11, 0x11, 0x7E, 0x00, // A (65)
|
||||
0x7F, 0x49, 0x49, 0x49, 0x36, 0x00, // B
|
||||
0x3E, 0x41, 0x41, 0x41, 0x22, 0x00, // C
|
||||
0x7F, 0x41, 0x41, 0x22, 0x1C, 0x00, // D
|
||||
0x7F, 0x49, 0x49, 0x49, 0x41, 0x00, // E
|
||||
0x7F, 0x09, 0x09, 0x01, 0x01, 0x00, // F
|
||||
0x3E, 0x41, 0x41, 0x51, 0x32, 0x00, // G
|
||||
0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, // H
|
||||
0x00, 0x41, 0x7F, 0x41, 0x00, 0x00, // I
|
||||
0x20, 0x40, 0x41, 0x3F, 0x01, 0x00, // J
|
||||
0x7F, 0x08, 0x14, 0x22, 0x41, 0x00, // K
|
||||
0x7F, 0x40, 0x40, 0x40, 0x40, 0x00, // L
|
||||
0x7F, 0x02, 0x04, 0x02, 0x7F, 0x00, // M
|
||||
0x7F, 0x04, 0x08, 0x10, 0x7F, 0x00, // N
|
||||
0x3E, 0x41, 0x41, 0x41, 0x3E, 0x00, // O
|
||||
0x7F, 0x09, 0x09, 0x09, 0x06, 0x00, // P
|
||||
0x3E, 0x41, 0x51, 0x21, 0x5E, 0x00, // Q
|
||||
0x7F, 0x09, 0x19, 0x29, 0x46, 0x00, // R
|
||||
0x46, 0x49, 0x49, 0x49, 0x31, 0x00, // S
|
||||
0x01, 0x01, 0x7F, 0x01, 0x01, 0x00, // T
|
||||
0x3F, 0x40, 0x40, 0x40, 0x3F, 0x00, // U
|
||||
0x1F, 0x20, 0x40, 0x20, 0x1F, 0x00, // V
|
||||
0x7F, 0x20, 0x18, 0x20, 0x7F, 0x00, // W
|
||||
0x63, 0x14, 0x08, 0x14, 0x63, 0x00, // X
|
||||
0x03, 0x04, 0x78, 0x04, 0x03, 0x00, // Y
|
||||
0x61, 0x51, 0x49, 0x45, 0x43, 0x00, // Z (90)
|
||||
0x00, 0x00, 0x7F, 0x41, 0x41, 0x00, // [
|
||||
0x02, 0x04, 0x08, 0x10, 0x20, 0x00, // "\"
|
||||
0x41, 0x41, 0x7F, 0x00, 0x00, 0x00, // ]
|
||||
0x04, 0x02, 0x01, 0x02, 0x04, 0x00, // ^
|
||||
0x40, 0x40, 0x40, 0x40, 0x40, 0x00, // _
|
||||
0x00, 0x01, 0x02, 0x04, 0x00, 0x00, // ' (96)
|
||||
#ifndef NOLOWERCASE
|
||||
0x20, 0x54, 0x54, 0x54, 0x78, // a
|
||||
0x7F, 0x48, 0x44, 0x44, 0x38, // b
|
||||
0x38, 0x44, 0x44, 0x44, 0x20, // c
|
||||
0x38, 0x44, 0x44, 0x48, 0x7F, // d
|
||||
0x38, 0x54, 0x54, 0x54, 0x18, // e
|
||||
0x08, 0x7E, 0x09, 0x01, 0x02, // f
|
||||
0x08, 0x14, 0x54, 0x54, 0x3C, // g
|
||||
0x7F, 0x08, 0x04, 0x04, 0x78, // h
|
||||
0x00, 0x44, 0x7D, 0x40, 0x00, // i
|
||||
0x20, 0x40, 0x44, 0x3D, 0x00, // j
|
||||
0x00, 0x7F, 0x10, 0x28, 0x44, // k
|
||||
0x00, 0x41, 0x7F, 0x40, 0x00, // l
|
||||
0x7C, 0x04, 0x18, 0x04, 0x78, // m
|
||||
0x7C, 0x08, 0x04, 0x04, 0x78, // n
|
||||
0x38, 0x44, 0x44, 0x44, 0x38, // o
|
||||
0x7C, 0x14, 0x14, 0x14, 0x08, // p
|
||||
0x08, 0x14, 0x14, 0x18, 0x7C, // q
|
||||
0x7C, 0x08, 0x04, 0x04, 0x08, // r
|
||||
0x48, 0x54, 0x54, 0x54, 0x20, // s
|
||||
0x04, 0x3F, 0x44, 0x40, 0x20, // t
|
||||
0x3C, 0x40, 0x40, 0x20, 0x7C, // u
|
||||
0x1C, 0x20, 0x40, 0x20, 0x1C, // v
|
||||
0x3C, 0x40, 0x30, 0x40, 0x3C, // w
|
||||
0x44, 0x28, 0x10, 0x28, 0x44, // x
|
||||
0x0C, 0x50, 0x50, 0x50, 0x3C, // y
|
||||
0x44, 0x64, 0x54, 0x4C, 0x44, // z
|
||||
0x20, 0x54, 0x54, 0x54, 0x78, 0x00, // a (97)
|
||||
0x7F, 0x48, 0x44, 0x44, 0x38, 0x00, // b
|
||||
0x38, 0x44, 0x44, 0x44, 0x20, 0x00, // c
|
||||
0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, // d
|
||||
0x38, 0x54, 0x54, 0x54, 0x18, 0x00, // e
|
||||
0x08, 0x7E, 0x09, 0x01, 0x02, 0x00, // f
|
||||
0x08, 0x14, 0x54, 0x54, 0x3C, 0x00, // g
|
||||
0x7F, 0x08, 0x04, 0x04, 0x78, 0x00, // h
|
||||
0x00, 0x44, 0x7D, 0x40, 0x00, 0x00, // i
|
||||
0x20, 0x40, 0x44, 0x3D, 0x00, 0x00, // j
|
||||
0x00, 0x7F, 0x10, 0x28, 0x44, 0x00, // k
|
||||
0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, // l
|
||||
0x7C, 0x04, 0x18, 0x04, 0x78, 0x00, // m
|
||||
0x7C, 0x08, 0x04, 0x04, 0x78, 0x00, // n
|
||||
0x38, 0x44, 0x44, 0x44, 0x38, 0x00, // o
|
||||
0x7C, 0x14, 0x14, 0x14, 0x08, 0x00, // p
|
||||
0x08, 0x14, 0x14, 0x18, 0x7C, 0x00, // q
|
||||
0x7C, 0x08, 0x04, 0x04, 0x08, 0x00, // r
|
||||
0x48, 0x54, 0x54, 0x54, 0x20, 0x00, // s
|
||||
0x04, 0x3F, 0x44, 0x40, 0x20, 0x00, // t
|
||||
0x3C, 0x40, 0x40, 0x20, 0x7C, 0x00, // u
|
||||
0x1C, 0x20, 0x40, 0x20, 0x1C, 0x00, // v
|
||||
0x3C, 0x40, 0x30, 0x40, 0x3C, 0x00, // w
|
||||
0x44, 0x28, 0x10, 0x28, 0x44, 0x00, // x
|
||||
0x0C, 0x50, 0x50, 0x50, 0x3C, 0x00, // y
|
||||
0x44, 0x64, 0x54, 0x4C, 0x44, 0x00, // z (122)
|
||||
#endif
|
||||
0x00, 0x08, 0x36, 0x41, 0x00, // {
|
||||
0x00, 0x00, 0x7F, 0x00, 0x00, // |
|
||||
0x00, 0x41, 0x36, 0x08, 0x00, // }
|
||||
0x08, 0x08, 0x2A, 0x1C, 0x08, // ->
|
||||
0x08, 0x1C, 0x2A, 0x08, 0x08, // <-
|
||||
0x00, 0x06, 0x09, 0x09, 0x06 // degree symbol
|
||||
0x00, 0x08, 0x36, 0x41, 0x00, 0x00, // { (123)
|
||||
0x00, 0x00, 0x7F, 0x00, 0x00, 0x00, // |
|
||||
0x00, 0x41, 0x36, 0x08, 0x00, 0x00, // }
|
||||
0x08, 0x08, 0x2A, 0x1C, 0x08, 0x00, // ->
|
||||
0x08, 0x1C, 0x2A, 0x08, 0x08, 0x00, // <- (127)
|
||||
#ifndef NO_EXTENDED_CHARACTERS
|
||||
// Extended characters - based on "DOS Western Europe" characters
|
||||
// International characters not yet implemented.
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0x80
|
||||
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
|
||||
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 0x90
|
||||
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
|
||||
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
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xa0
|
||||
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
|
||||
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
|
||||
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, 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
|
||||
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
|
||||
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
|
||||
0x00, 0x00, 0x00, 0x0f, 0x08, 0x08, // Bottom left corner 0xc0
|
||||
0x08, 0x08, 0x08, 0x0f, 0x08, 0x08, // Horizontal line with upward spur 0xc1
|
||||
0x08, 0x08, 0x08, 0xf8, 0x08, 0x08, // Horizontal line with downward spur 0xc2
|
||||
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, 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
|
||||
0x00, 0x00, 0xfc, 0x04, 0xf4, 0x14, // Double top left corner 0xc9
|
||||
0x14, 0x14, 0x17, 0x10, 0x17, 0x14, // Double horizontal with double upward spur 0xca
|
||||
0x14, 0x14, 0xf4, 0x04, 0xf4, 0x14, // Double horizontal with double downward spur 0xcb
|
||||
0x00, 0x00, 0xff, 0x00, 0xf7, 0x14, // Double vertical line with double right spur 0xcc
|
||||
0x14, 0x14, 0x14, 0x14, 0x14, 0x14, // Double horizontal line 0xcd
|
||||
0x14, 0x14, 0xf7, 0x00, 0xf7, 0x14, // Double cross 0xce
|
||||
|
||||
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
|
||||
0x00, 0x00, 0x00, 0xf8, 0x08, 0x08, // Top left corner 0xda
|
||||
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // Solid block 0xdb
|
||||
0xf0, 0xf0, 0xf0, 0xf0, 0xf0, 0xf0, // Bottom half block 0xdc
|
||||
0xff, 0xff, 0xff, 0x00, 0x00, 0x00, // Left half block 0xdd
|
||||
0x00, 0x00, 0x00, 0xff, 0xff, 0xff, // Right half block 0xde
|
||||
0x0f, 0x0f, 0x0f, 0x0f, 0x0f, 0x0f, // Top half block 0xdf
|
||||
|
||||
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
|
||||
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
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xf0
|
||||
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
|
||||
// Extended character 248
|
||||
0x00, 0x06, 0x09, 0x09, 0x06, 0x00 // degree symbol 0xf8
|
||||
#endif
|
||||
};
|
||||
|
||||
const uint8_t SSD1306AsciiWire::m_fontCharCount = sizeof(System6x8) / 6;
|
||||
|
@@ -23,24 +23,28 @@
|
||||
|
||||
#include "Arduino.h"
|
||||
#include "FSH.h"
|
||||
#include "LCDDisplay.h"
|
||||
#include "Display.h"
|
||||
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "DisplayInterface.h"
|
||||
|
||||
// Uncomment to remove lower-case letters to save 108 bytes of flash
|
||||
//#define NOLOWERCASE
|
||||
|
||||
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constructor
|
||||
class SSD1306AsciiWire : public LCDDisplay {
|
||||
class SSD1306AsciiWire : public DisplayDevice {
|
||||
public:
|
||||
|
||||
// Constructor
|
||||
SSD1306AsciiWire(int width, int height);
|
||||
// Constructors
|
||||
SSD1306AsciiWire(int width, int height); // Auto-detects I2C address
|
||||
SSD1306AsciiWire(I2CAddress address, int width, int height);
|
||||
|
||||
// Initialize the display controller.
|
||||
void begin(uint8_t i2cAddr);
|
||||
bool begin();
|
||||
|
||||
// Clear the display and set the cursor to (0, 0).
|
||||
void clearNative() override;
|
||||
@@ -52,6 +56,8 @@ class SSD1306AsciiWire : public LCDDisplay {
|
||||
size_t writeNative(uint8_t c) override;
|
||||
|
||||
bool isBusy() override { return requestBlock.isBusy(); }
|
||||
uint16_t getNumCols() { return m_charsPerRow; }
|
||||
uint16_t getNumRows() { return m_charsPerColumn; }
|
||||
|
||||
private:
|
||||
// Cursor column.
|
||||
@@ -62,26 +68,31 @@ class SSD1306AsciiWire : public LCDDisplay {
|
||||
uint8_t m_displayWidth;
|
||||
// Display height.
|
||||
uint8_t m_displayHeight;
|
||||
// Display width in characters
|
||||
uint8_t m_charsPerRow;
|
||||
// Display height in characters
|
||||
uint8_t m_charsPerColumn;
|
||||
// Column offset RAM to SEG.
|
||||
uint8_t m_colOffset = 0;
|
||||
// Current font.
|
||||
const uint8_t* const m_font = System5x7;
|
||||
const uint8_t* const m_font = System6x8;
|
||||
// Flag to prevent calling begin() twice
|
||||
uint8_t m_initialised = false;
|
||||
|
||||
// Only fixed size 5x7 fonts in a 6x8 cell are supported.
|
||||
static const uint8_t fontWidth = 5;
|
||||
static const uint8_t fontHeight = 7;
|
||||
static const uint8_t letterSpacing = 1;
|
||||
// Only fixed size 6x8 fonts in a 6x8 cell are supported.
|
||||
static const uint8_t fontWidth = 6;
|
||||
static const uint8_t fontHeight = 8;
|
||||
static const uint8_t m_fontFirstChar = 0x20;
|
||||
static const uint8_t m_fontCharCount = 0x61;
|
||||
static const uint8_t m_fontCharCount;
|
||||
|
||||
uint8_t m_i2cAddr;
|
||||
I2CAddress m_i2cAddr = 0;
|
||||
|
||||
I2CRB requestBlock;
|
||||
uint8_t outputBuffer[fontWidth+letterSpacing+1];
|
||||
uint8_t outputBuffer[fontWidth+1];
|
||||
|
||||
static const uint8_t blankPixels[];
|
||||
|
||||
static const uint8_t System5x7[];
|
||||
static const uint8_t System6x8[];
|
||||
static const uint8_t FLASH Adafruit128xXXinit[];
|
||||
static const uint8_t FLASH SH1106_132x64init[];
|
||||
};
|
||||
|
@@ -32,7 +32,7 @@ class StringBuffer : public Print {
|
||||
private:
|
||||
static const int buffer_max=64; // enough for long text msgs to throttles
|
||||
int16_t _pos_write;
|
||||
char _buffer[buffer_max+1];
|
||||
char _buffer[buffer_max+2];
|
||||
};
|
||||
|
||||
#endif
|
@@ -18,7 +18,7 @@
|
||||
*/
|
||||
#include "StringFormatter.h"
|
||||
#include <stdarg.h>
|
||||
#include "LCDDisplay.h"
|
||||
#include "DisplayInterface.h"
|
||||
|
||||
bool Diag::ACK=false;
|
||||
bool Diag::CMD=false;
|
||||
@@ -45,10 +45,17 @@ void StringFormatter::lcd(byte row, const FSH* input...) {
|
||||
send2(&USB_SERIAL,input,args);
|
||||
send(&USB_SERIAL,F(" *>\n"));
|
||||
|
||||
if (!LCDDisplay::lcdDisplay) return;
|
||||
LCDDisplay::lcdDisplay->setRow(row);
|
||||
DisplayInterface::setRow(row);
|
||||
va_start(args, input);
|
||||
send2(LCDDisplay::lcdDisplay,input,args);
|
||||
send2(DisplayInterface::getDisplayHandler(),input,args);
|
||||
}
|
||||
|
||||
void StringFormatter::lcd2(uint8_t display, byte row, const FSH* input...) {
|
||||
va_list args;
|
||||
|
||||
DisplayInterface::setRow(display, row);
|
||||
va_start(args, input);
|
||||
send2(DisplayInterface::getDisplayHandler(),input,args);
|
||||
}
|
||||
|
||||
void StringFormatter::send(Print * stream, const FSH* input...) {
|
||||
@@ -91,9 +98,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
{
|
||||
const FSH* flash= (const FSH*)va_arg(args, char*);
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// On ESP32 the reading flashstring from rinstream code
|
||||
// crashes, so don't use the flashstream hack on ESP32
|
||||
#if WIFI_ON | ETHERNET_ON
|
||||
// RingStream has special logic to handle flash strings
|
||||
// but is not implemented unless wifi or ethernet are enabled.
|
||||
@@ -101,17 +105,18 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
if (stream->availableForWrite()==RingStream::THIS_IS_A_RINGSTREAM)
|
||||
((RingStream *)stream)->printFlash(flash);
|
||||
else
|
||||
#endif
|
||||
#endif
|
||||
stream->print(flash);
|
||||
break;
|
||||
}
|
||||
case 'P': stream->print((uint32_t)va_arg(args, void*), HEX); break;
|
||||
case 'd': printPadded(stream,va_arg(args, int), formatWidth, formatLeft); break;
|
||||
case 'u': printPadded(stream,va_arg(args, unsigned int), formatWidth, formatLeft); break;
|
||||
case 'l': printPadded(stream,va_arg(args, long), formatWidth, formatLeft); break;
|
||||
case 'b': stream->print(va_arg(args, int), BIN); break;
|
||||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
||||
case 'x': stream->print(va_arg(args, int), HEX); 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 'f': stream->print(va_arg(args, double), 2); break;
|
||||
//format width prefix
|
||||
case '-':
|
||||
@@ -168,8 +173,8 @@ void StringFormatter::printEscape(Print * stream, char c) {
|
||||
case '\r': stream->print(F("\\r")); break;
|
||||
case '\0': stream->print(F("\\0")); return;
|
||||
case '\t': stream->print(F("\\t")); break;
|
||||
case '\\': stream->print(F("\\")); break;
|
||||
default: stream->print(c);
|
||||
case '\\': stream->print(F("\\\\")); break;
|
||||
default: stream->write(c);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -21,7 +21,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "FSH.h"
|
||||
#include "RingStream.h"
|
||||
#include "LCDDisplay.h"
|
||||
#include "Display.h"
|
||||
class Diag {
|
||||
public:
|
||||
static bool ACK;
|
||||
@@ -46,6 +46,7 @@ class StringFormatter
|
||||
// DIAG support
|
||||
static void diag( const FSH* input...);
|
||||
static void lcd(byte row, const FSH* input...);
|
||||
static void lcd2(uint8_t display, byte row, const FSH* input...);
|
||||
static void printEscapes(char * input);
|
||||
static void printEscape( char c);
|
||||
|
||||
|
@@ -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++)
|
||||
|
||||
@@ -203,6 +204,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
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 +212,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 +293,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;
|
||||
}
|
||||
|
||||
@@ -349,6 +323,36 @@ 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;
|
||||
case TRACK_MODE_PROG:
|
||||
format=F("<= %c PROG>\n");
|
||||
break;
|
||||
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() {
|
||||
@@ -423,7 +427,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,6 +77,11 @@ 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
|
||||
|
@@ -207,7 +207,7 @@
|
||||
}
|
||||
|
||||
#ifdef EESTOREDEBUG
|
||||
printAll(&Serial);
|
||||
printAll(&USB_SERIAL);
|
||||
#endif
|
||||
return tt;
|
||||
}
|
||||
|
339
WiThrottle.cpp
339
WiThrottle.cpp
@@ -63,69 +63,6 @@
|
||||
|
||||
WiThrottle * WiThrottle::firstThrottle=NULL;
|
||||
|
||||
static uint8_t xstrncmp(const char *s1, const char *s2, uint8_t n) {
|
||||
if (n == 0)
|
||||
return 0;
|
||||
do {
|
||||
if (*s1 != *s2++)
|
||||
return 1;
|
||||
if (*s1++ == 0)
|
||||
break;
|
||||
} while (--n != 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
void WiThrottle::findUniqThrottle(int id, char *u) {
|
||||
WiThrottle *wtmyid = NULL;
|
||||
WiThrottle *wtmyuniq = NULL;
|
||||
|
||||
// search 1, look for clientid match
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
|
||||
if (wt->clientid == id) {
|
||||
if (xstrncmp(u, wt->uniq, 16) == 0) // should be most common case
|
||||
return;
|
||||
wtmyid = wt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// search 2, look for string match
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle){
|
||||
if (xstrncmp(u, wt->uniq, 16) == 0) {
|
||||
wtmyuniq = wt;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// analyse result of the two for loops:
|
||||
if (wtmyid == NULL) { // should not happen
|
||||
DIAG(F("Did not find my own wiThrottle handle"));
|
||||
return;
|
||||
}
|
||||
// wtmyuniq == wtmyid has already returned in for loop 1
|
||||
if (wtmyuniq == NULL) { // register uniq in the found id
|
||||
strncpy(wtmyid->uniq, u, 16);
|
||||
wtmyid->uniq[16] = '\0';
|
||||
if (Diag::WITHROTTLE) DIAG(F("Client %d registered as %s"),wtmyid->clientid, wtmyid->uniq);
|
||||
return;
|
||||
}
|
||||
// if we get here wtmyid and wtmyuniq point on objects but differnet ones
|
||||
// so we need to do the copy (all other options covered above)
|
||||
for(int n=0; n < MAX_MY_LOCO; n++)
|
||||
wtmyid->myLocos[n] = wtmyuniq->myLocos[n];
|
||||
wtmyid->heartBeatEnable = wtmyuniq->heartBeatEnable;
|
||||
wtmyid->heartBeat = wtmyuniq->heartBeat;
|
||||
wtmyid->initSent = wtmyuniq->initSent;
|
||||
wtmyid->exRailSent = wtmyuniq->exRailSent;
|
||||
wtmyid->mostRecentCab = wtmyuniq->mostRecentCab;
|
||||
wtmyid->turnoutListHash = wtmyuniq->turnoutListHash;
|
||||
wtmyid->lastPowerState = wtmyuniq->lastPowerState;
|
||||
strncpy(wtmyid->uniq, u, 16);
|
||||
wtmyid->uniq[16] = '\0';
|
||||
if (Diag::WITHROTTLE)
|
||||
DIAG(F("New client %d replaces old client %d as %s"), wtmyid->clientid, wtmyuniq->clientid, wtmyid->uniq);
|
||||
forget(wtmyuniq->clientid); // do not use wtmyid after this
|
||||
}
|
||||
|
||||
WiThrottle* WiThrottle::getThrottle( int wifiClient) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->clientid==wifiClient) return wt;
|
||||
@@ -135,6 +72,7 @@ WiThrottle* WiThrottle::getThrottle( int wifiClient) {
|
||||
void WiThrottle::forget( byte clientId) {
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
if (wt->clientid==clientId) {
|
||||
DIAG(F("Withrottle client %d dropped"),clientId);
|
||||
delete wt;
|
||||
break;
|
||||
}
|
||||
@@ -159,10 +97,7 @@ WiThrottle::WiThrottle( int wificlientid) {
|
||||
nextThrottle=firstThrottle;
|
||||
firstThrottle= this;
|
||||
clientid=wificlientid;
|
||||
initSent=false; // prevent sending heartbeats before connection completed
|
||||
heartBeatEnable=false; // until client turns it on
|
||||
turnoutListHash = -1; // make sure turnout list is sent once
|
||||
exRailSent=false;
|
||||
mostRecentCab=0;
|
||||
for (int loco=0;loco<MAX_MY_LOCO; loco++) myLocos[loco].throttle='\0';
|
||||
}
|
||||
@@ -187,47 +122,17 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
|
||||
heartBeat=millis();
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d)<-[%e]"),millis(),clientid,cmd);
|
||||
|
||||
// On first few commands, send turnout, roster and routes
|
||||
if (introSent) {
|
||||
if (!turnoutsSent) sendTurnouts(stream);
|
||||
else if(!rosterSent) sendRoster(stream);
|
||||
else if (!routesSent) sendRoutes(stream);
|
||||
else if (!heartrateSent) {
|
||||
heartrateSent=true;
|
||||
// allow heartbeat to slow down once all metadata sent
|
||||
StringFormatter::send(stream,F("*%d\nHMConnected\n"),HEARTBEAT_SECONDS);
|
||||
|
||||
if (initSent) {
|
||||
// Send turnout list if changed since last sent (will replace list on client)
|
||||
if (turnoutListHash != Turnout::turnoutlistHash) {
|
||||
StringFormatter::send(stream,F("PTL"));
|
||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||
if (tt->isHidden()) continue;
|
||||
int id=tt->getId();
|
||||
const FSH * tdesc=NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc=RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||
if (tdesc==NULL) // turnout with no description
|
||||
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
|
||||
else
|
||||
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
turnoutListHash = Turnout::turnoutlistHash; // keep a copy of hash for later comparison
|
||||
}
|
||||
|
||||
else if (!exRailSent) {
|
||||
// Send EX-RAIL routes list if not already sent (but not at same time as turnouts above)
|
||||
exRailSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
for (byte pass=0;pass<2;pass++) {
|
||||
// first pass automations, second pass routes.
|
||||
for (int ix=0;;ix++) {
|
||||
int16_t id=GETFLASHW((pass?RMFT2::automationIdList:RMFT2::routeIdList)+ix);
|
||||
if (id==0) break;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[%c%d}|{%S}|{%c"),
|
||||
pass?'A':'R',id,desc, pass?'4':'2');
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#endif
|
||||
// allow heartbeat to slow down once all metadata sent
|
||||
StringFormatter::send(stream,F("*%d\n"),HEARTBEAT_SECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -283,32 +188,14 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
}
|
||||
break;
|
||||
case 'N': // Heartbeat (2), only send if connection completed by 'HU' message
|
||||
StringFormatter::send(stream, F("*%d\n"), initSent ? HEARTBEAT_SECONDS : HEARTBEAT_SECONDS/2); // return timeout value
|
||||
StringFormatter::send(stream, F("*%d\n"), heartrateSent ? HEARTBEAT_SECONDS : HEARTBEAT_PRELOAD); // return timeout value
|
||||
break;
|
||||
case 'M': // multithrottle
|
||||
multithrottle(stream, cmd);
|
||||
break;
|
||||
case 'H': // send initial connection info after receiving "HU" message
|
||||
if (cmd[1] == 'U') {
|
||||
WiThrottle::findUniqThrottle(clientid, (char *)cmd+2);
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("RL%d"), RMFT2::rosterNameCount);
|
||||
for (int16_t r=0;r<RMFT2::rosterNameCount;r++) {
|
||||
int16_t cabid=GETFLASHW(RMFT2::rosterIdList+r);
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
stream->write('\n'); // end roster
|
||||
#endif
|
||||
|
||||
|
||||
// set heartbeat to 5 seconds because we need to sync the metadata (1 second is too short!)
|
||||
StringFormatter::send(stream,F("*%d\n"), HEARTBEAT_SECONDS/2);
|
||||
initSent = true;
|
||||
if (cmd[1] == 'U') {
|
||||
sendIntro(stream);
|
||||
}
|
||||
break;
|
||||
case 'Q': //
|
||||
@@ -317,7 +204,7 @@ void WiThrottle::parse(RingStream * stream, byte * cmdx) {
|
||||
StringFormatter::send(stream, F("M%c-%c%d<;>\n"), myLocos[loco].throttle, LorS(myLocos[loco].cab), myLocos[loco].cab);
|
||||
}
|
||||
}
|
||||
if (Diag::WITHROTTLE) DIAG(F("%l WiThrottle(%d) Quit"),millis(),clientid);
|
||||
if (Diag::WITHROTTLE) DIAG(F("WiThrottle(%d) Quit"),clientid);
|
||||
delete this;
|
||||
break;
|
||||
}
|
||||
@@ -378,65 +265,17 @@ void WiThrottle::multithrottle(RingStream * stream, byte * cmd){
|
||||
}
|
||||
//use first empty "slot" on this client's list, will be added to DCC registration list
|
||||
for (int loco=0;loco<MAX_MY_LOCO;loco++) {
|
||||
if (myLocos[loco].throttle=='\0') {
|
||||
myLocos[loco].throttle=throttleChar;
|
||||
myLocos[loco].cab=locoid;
|
||||
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
|
||||
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
|
||||
mostRecentCab=locoid;
|
||||
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
||||
int fkeys=29;
|
||||
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 presets as above
|
||||
}
|
||||
else if (GETFLASH(functionNames)=='\0') {
|
||||
// "" = Roster but no functions given
|
||||
fkeys=0;
|
||||
}
|
||||
else {
|
||||
// we have function names...
|
||||
// scan names list emitting names, counting functions and
|
||||
// flagging non-toggling things like horn.
|
||||
myLocos[loco].functionToggles =0;
|
||||
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), throttleChar,cmd[3],locoid);
|
||||
fkeys=0;
|
||||
bool firstchar=true;
|
||||
for (int fx=0;;fx++) {
|
||||
char c=GETFLASH(functionNames+fx);
|
||||
if (c=='\0') {
|
||||
fkeys++;
|
||||
break;
|
||||
}
|
||||
if (c=='/') {
|
||||
fkeys++;
|
||||
StringFormatter::send(stream,F("]\\["));
|
||||
firstchar=true;
|
||||
}
|
||||
else if (firstchar && c=='*') {
|
||||
myLocos[loco].functionToggles |= 1UL<<fkeys;
|
||||
firstchar=false;
|
||||
}
|
||||
else {
|
||||
firstchar=false;
|
||||
stream->write(c);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),throttleChar,cmd[3],locoid,fstate,fKey);
|
||||
}
|
||||
//speed and direction will be published at next broadcast cycle
|
||||
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
||||
return;
|
||||
if (myLocos[loco].throttle=='\0') {
|
||||
myLocos[loco].throttle=throttleChar;
|
||||
myLocos[loco].cab=locoid;
|
||||
myLocos[loco].functionMap=DCC::getFunctionMap(locoid);
|
||||
myLocos[loco].broadcastPending=true; // means speed/dir will be sent later
|
||||
mostRecentCab=locoid;
|
||||
StringFormatter::send(stream, F("M%c+%c%d<;>\n"), throttleChar, cmd[3] ,locoid); //tell client to add loco
|
||||
sendFunctions(stream,loco);
|
||||
//speed and direction will be published at next broadcast cycle
|
||||
StringFormatter::send(stream, F("M%cA%c%d<;>s1\n"), throttleChar, cmd[3], locoid); //default speed step 128
|
||||
return;
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream, F("HMMax locos (%d) exceeded, %d not added!\n"), MAX_MY_LOCO ,locoid);
|
||||
@@ -540,8 +379,6 @@ void WiThrottle::loop(RingStream * stream) {
|
||||
// for each WiThrottle, check the heartbeat and broadcast needed
|
||||
for (WiThrottle* wt=firstThrottle; wt!=NULL ; wt=wt->nextThrottle)
|
||||
wt->checkHeartbeat(stream);
|
||||
|
||||
|
||||
}
|
||||
|
||||
void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||
@@ -555,8 +392,8 @@ void WiThrottle::checkHeartbeat(RingStream * stream) {
|
||||
heartBeat=millis(); // We have just stopped everyting, we don't need to do that again at next loop.
|
||||
}
|
||||
}
|
||||
//haba no, not necessary the only throttle and it may come back
|
||||
//delete this;
|
||||
// if it does come back, the throttle should re-acquire
|
||||
delete this;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -656,5 +493,123 @@ void WiThrottle::getLocoCallback(int16_t locoid) {
|
||||
DIAG(F("LocoCallback commit success"));
|
||||
stashStream->commit();
|
||||
CommandDistributor::broadcastPower();
|
||||
|
||||
}
|
||||
|
||||
void WiThrottle::sendIntro(Print* stream) {
|
||||
introSent=true;
|
||||
StringFormatter::send(stream,F("VN2.0\nHTDCC-EX\nRL0\n"));
|
||||
StringFormatter::send(stream,F("HtDCC-EX v%S, %S, %S, %S\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
StringFormatter::send(stream,F("PTT]\\[Turnouts}|{Turnout]\\[THROW}|{2]\\[CLOSE}|{4\n"));
|
||||
StringFormatter::send(stream,F("PPA%x\n"),TrackManager::getMainPower()==POWERMODE::ON);
|
||||
// set heartbeat to 2 seconds because we need to sync the metadata (1 second is too short!)
|
||||
StringFormatter::send(stream,F("*%d\nHMConnecting..\n"), HEARTBEAT_PRELOAD);
|
||||
}
|
||||
|
||||
void WiThrottle::sendTurnouts(Print* stream) {
|
||||
turnoutsSent=true;
|
||||
StringFormatter::send(stream,F("PTL"));
|
||||
for(Turnout *tt=Turnout::first();tt!=NULL;tt=tt->next()){
|
||||
if (tt->isHidden()) continue;
|
||||
int id=tt->getId();
|
||||
const FSH * tdesc=NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tdesc=RMFT2::getTurnoutDescription(id);
|
||||
#endif
|
||||
char tchar=Turnout::isClosed(id)?'2':'4';
|
||||
if (tdesc==NULL) // turnout with no description
|
||||
StringFormatter::send(stream,F("]\\[%d}|{T%d}|{T%c"), id,id,tchar);
|
||||
else
|
||||
StringFormatter::send(stream,F("]\\[%d}|{%S}|{%c"), id,tdesc,tchar);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
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++) {
|
||||
int16_t cabid=GETHIGHFLASHW(RMFT2::rosterIdList,r*2);
|
||||
StringFormatter::send(stream,F("]\\[%S}|{%d}|{%c"),
|
||||
RMFT2::getRosterName(cabid),cabid,cabid<128?'S':'L');
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#else
|
||||
(void)stream; // remove warning
|
||||
#endif
|
||||
}
|
||||
void WiThrottle::sendRoutes(Print* stream) {
|
||||
routesSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
// first pass automations
|
||||
for (int ix=0;;ix+=2) {
|
||||
int16_t id =GETHIGHFLASHW(RMFT2::automationIdList,ix);
|
||||
if (id==0) 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;
|
||||
const FSH * desc=RMFT2::getRouteDescription(id);
|
||||
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
#else
|
||||
(void)stream; // remove warning
|
||||
#endif
|
||||
}
|
||||
|
||||
void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
int16_t locoid=myLocos[loco].cab;
|
||||
int fkeys=29;
|
||||
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
|
||||
}
|
||||
else if (GETFLASH(functionNames)=='\0') {
|
||||
// "" = Roster but no functions given
|
||||
fkeys=0;
|
||||
}
|
||||
else {
|
||||
// we have function names...
|
||||
// scan names list emitting names, counting functions and
|
||||
// flagging non-toggling things like horn.
|
||||
myLocos[loco].functionToggles =0;
|
||||
StringFormatter::send(stream, F("M%cL%c%d<;>]\\["), myLocos[loco].throttle,LorS(locoid),locoid);
|
||||
fkeys=0;
|
||||
bool firstchar=true;
|
||||
for (int fx=0;;fx++) {
|
||||
char c=GETFLASH(functionNames+fx);
|
||||
if (c=='\0') {
|
||||
fkeys++;
|
||||
break;
|
||||
}
|
||||
if (c=='/') {
|
||||
fkeys++;
|
||||
StringFormatter::send(stream,F("]\\["));
|
||||
firstchar=true;
|
||||
}
|
||||
else if (firstchar && c=='*') {
|
||||
myLocos[loco].functionToggles |= 1UL<<fkeys;
|
||||
firstchar=false;
|
||||
}
|
||||
else {
|
||||
firstchar=false;
|
||||
stream->write(c);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
for(int fKey=0; fKey<fkeys; fKey++) {
|
||||
int fstate=DCC::getFn(locoid,fKey);
|
||||
if (fstate>=0) StringFormatter::send(stream,F("M%cA%c%d<;>F%d%d\n"),myLocos[loco].throttle,LorS(locoid),locoid,fstate,fKey);
|
||||
}
|
||||
}
|
||||
|
16
WiThrottle.h
16
WiThrottle.h
@@ -45,7 +45,8 @@ class WiThrottle {
|
||||
~WiThrottle();
|
||||
|
||||
static const int MAX_MY_LOCO=10; // maximum number of locos assigned to a single client
|
||||
static const int HEARTBEAT_SECONDS=10; // heartbeat at 4secs to provide messaging transport
|
||||
static const int HEARTBEAT_SECONDS=10; // heartbeat at 10 secs to provide messaging transport
|
||||
static const int HEARTBEAT_PRELOAD=2; // request fast callback when connecting multiple messages
|
||||
static const int ESTOP_SECONDS=20; // eStop if no incoming messages for more than 8secs
|
||||
static WiThrottle* firstThrottle;
|
||||
static int getInt(byte * cmd);
|
||||
@@ -61,10 +62,12 @@ class WiThrottle {
|
||||
MYLOCO myLocos[MAX_MY_LOCO];
|
||||
bool heartBeatEnable;
|
||||
unsigned long heartBeat;
|
||||
bool initSent; // valid connection established
|
||||
bool exRailSent; // valid connection established
|
||||
bool introSent=false;
|
||||
bool turnoutsSent=false;
|
||||
bool rosterSent=false;
|
||||
bool routesSent=false;
|
||||
bool heartrateSent=false;
|
||||
uint16_t mostRecentCab;
|
||||
int turnoutListHash; // used to check for changes to turnout list
|
||||
bool lastPowerState; // last power state sent to this client
|
||||
|
||||
int DCCToWiTSpeed(int DCCSpeed);
|
||||
@@ -74,6 +77,11 @@ class WiThrottle {
|
||||
void accessory(RingStream *, byte* cmd);
|
||||
void checkHeartbeat(RingStream * stream);
|
||||
void markForBroadcast2(int cab);
|
||||
void sendIntro(Print * stream);
|
||||
void sendTurnouts(Print * stream);
|
||||
void sendRoster(Print * stream);
|
||||
void sendRoutes(Print * stream);
|
||||
void sendFunctions(Print* stream, byte loco);
|
||||
// callback stuff to support prog track acquire
|
||||
static RingStream * stashStream;
|
||||
static WiThrottle * stashInstance;
|
||||
|
@@ -66,7 +66,7 @@ void WifiInboundHandler::loop1() {
|
||||
}
|
||||
|
||||
|
||||
if (pendingCipsend) {
|
||||
if (pendingCipsend && millis()-lastCIPSEND > CIPSENDgap) {
|
||||
if (Diag::WIFI) DIAG( F("WiFi: [[CIPSEND=%d,%d]]"), clientPendingCIPSEND, currentReplySize);
|
||||
StringFormatter::send(wifiStream, F("AT+CIPSEND=%d,%d\r\n"), clientPendingCIPSEND, currentReplySize);
|
||||
pendingCipsend=false;
|
||||
@@ -131,11 +131,13 @@ WifiInboundHandler::INBOUND_STATE WifiInboundHandler::loop2() {
|
||||
|
||||
if (ch=='S') { // SEND OK probably
|
||||
loopState=SKIPTOEND;
|
||||
lastCIPSEND=0; // no need to wait next time
|
||||
break;
|
||||
}
|
||||
|
||||
if (ch=='b') { // This is a busy indicator... probabaly must restart a CIPSEND
|
||||
pendingCipsend=(clientPendingCIPSEND>=0);
|
||||
if (pendingCipsend) lastCIPSEND=millis(); // forces a gap to next CIPSEND
|
||||
loopState=SKIPTOEND;
|
||||
break;
|
||||
}
|
||||
|
@@ -68,7 +68,9 @@ class WifiInboundHandler {
|
||||
Stream * wifiStream;
|
||||
|
||||
static const int INBOUND_RING = 512;
|
||||
static const int OUTBOUND_RING = 2048;
|
||||
static const int OUTBOUND_RING = sizeof(void*)==2?2048:8192;
|
||||
|
||||
static const int CIPSENDgap=100; // millis() between retries of cipsend.
|
||||
|
||||
RingStream * inboundRing;
|
||||
RingStream * outboundRing;
|
||||
@@ -79,5 +81,7 @@ class WifiInboundHandler {
|
||||
int clientPendingCIPSEND=-1;
|
||||
int currentReplySize;
|
||||
bool pendingCipsend;
|
||||
uint32_t lastCIPSEND=0; // millis() of previous cipsend
|
||||
|
||||
};
|
||||
#endif
|
||||
|
@@ -184,8 +184,8 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
checkForOK(1000, true); // Not always OK, sometimes "no change"
|
||||
|
||||
const char *yourNetwork = "Your network ";
|
||||
if (strncmp_P(yourNetwork, (const char*)SSid, 13) == 0 || strncmp_P("", (const char*)SSid, 13) == 0) {
|
||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
if (STRNCMP_P(yourNetwork, (const char*)SSid, 13) == 0 || STRNCMP_P("", (const char*)SSid, 13) == 0) {
|
||||
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
// If the source code looks unconfigured, check if the
|
||||
// ESP8266 is preconfigured in station mode.
|
||||
// We check the first 13 chars of the SSid and the password
|
||||
@@ -258,7 +258,7 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
|
||||
i=0;
|
||||
do {
|
||||
if (strncmp_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
if (STRNCMP_P(yourNetwork, (const char*)password, 13) == 0) {
|
||||
// unconfigured
|
||||
StringFormatter::send(wifiStream, F("AT+CWSAP%s=\"DCCEX_%s\",\"PASS_%s\",%d,4\r\n"),
|
||||
oldCmd ? "" : "_CUR", macTail, macTail, channel);
|
||||
@@ -344,11 +344,10 @@ void WifiInterface::ATCommand(HardwareSerial * stream,const byte * command) {
|
||||
while (wifiStream->available()) stream->write(wifiStream->read());
|
||||
if (stream->available()) {
|
||||
int cx=stream->read();
|
||||
// A newline followed by !!! is an exit
|
||||
// A newline followed by ! is an exit
|
||||
if (cx=='\n' || cx=='\r') startOfLine=true;
|
||||
else if (startOfLine && cx=='!') break;
|
||||
else startOfLine=false;
|
||||
stream->write(cx);
|
||||
wifiStream->write(cx);
|
||||
}
|
||||
}
|
||||
@@ -377,11 +376,12 @@ bool WifiInterface::checkForOK( const unsigned int timeout, const FSH * waitfor,
|
||||
char *locator = (char *)waitfor;
|
||||
DIAG(F("Wifi Check: [%E]"), waitfor);
|
||||
while ( millis() - startTime < timeout) {
|
||||
while (wifiStream->available()) {
|
||||
int ch = wifiStream->read();
|
||||
int nextchar;
|
||||
while (wifiStream->available() && (nextchar = wifiStream->read()) > -1) {
|
||||
char ch = (char)nextchar;
|
||||
if (echo) {
|
||||
if (escapeEcho) StringFormatter::printEscape( ch); /// THIS IS A DIAG IN DISGUISE
|
||||
else USB_SERIAL.print((char)ch);
|
||||
else USB_SERIAL.print(ch);
|
||||
}
|
||||
if (ch != GETFLASH(locator)) locator = (char *)waitfor;
|
||||
if (ch == GETFLASH(locator)) {
|
||||
|
@@ -125,10 +125,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 128,32,0x3c
|
||||
|
||||
// Define scroll mode as 0, 1 or 2
|
||||
// * #define SCROLLMODE 0 is scroll continuous (fill screen if poss),
|
||||
@@ -224,4 +224,5 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
181
defines.h
181
defines.h
@@ -43,110 +43,125 @@
|
||||
#undef USB_SERIAL // Teensy has this defined by default...
|
||||
#define USB_SERIAL Serial
|
||||
|
||||
// Include extended addresses unless specifically excluded
|
||||
#define I2C_EXTENDED_ADDRESS
|
||||
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#define ARDUINO_TYPE "UNO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#define NO_EXTENDED_CHARACTERS
|
||||
#undef I2C_EXTENDED_ADDRESS
|
||||
#elif defined(ARDUINO_AVR_NANO)
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#define ARDUINO_TYPE "NANO"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#define NO_EXTENDED_CHARACTERS
|
||||
#undef I2C_EXTENDED_ADDRESS
|
||||
#elif defined(ARDUINO_AVR_MEGA)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_AVR_MEGA2560)
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#define ARDUINO_TYPE "MEGA"
|
||||
#elif defined(ARDUINO_ARCH_MEGAAVR)
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#define ARDUINO_TYPE "MEGAAVR"
|
||||
#undef HAS_ENOUGH_MEMORY
|
||||
#define NO_EXTENDED_CHARACTERS
|
||||
#undef I2C_EXTENDED_ADDRESS
|
||||
#elif defined(ARDUINO_TEENSY31)
|
||||
#define ARDUINO_TYPE "TEENSY3132"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#define ARDUINO_TYPE "TEENSY3132"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY35)
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
// Teensy support for I2C is awaiting development
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#define ARDUINO_TYPE "TEENSY35"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
// Teensy support for I2C is awaiting development
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY36)
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#define ARDUINO_TYPE "TEENSY36"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY40)
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#define ARDUINO_TYPE "TEENSY40"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
#elif defined(ARDUINO_TEENSY41)
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#define ARDUINO_TYPE "TEENSY41"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// Teensy support for native I2C is awaiting development
|
||||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_ESP8266)
|
||||
#define ARDUINO_TYPE "ESP8266"
|
||||
#warning "ESP8266 platform untested, you are on your own"
|
||||
#define ARDUINO_TYPE "ESP8266"
|
||||
#warning "ESP8266 platform untested, you are on your own"
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#define ARDUINO_TYPE "SAMD21"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
// SAMD no EEPROM by default
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
#define ARDUINO_TYPE "SAMD21"
|
||||
#undef USB_SERIAL
|
||||
#define USB_SERIAL SerialUSB
|
||||
// SAMD no EEPROM by default
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
#define ARDUINO_TYPE "STM32"
|
||||
// STM32 no EEPROM by default
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// STM32 support for native I2C is awaiting development
|
||||
#ifndef I2C_NO_INTERRUPTS
|
||||
#define I2C_NO_INTERRUPTS
|
||||
#endif
|
||||
#define ARDUINO_TYPE "STM32"
|
||||
// STM32 no EEPROM by default
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// STM32 support for native I2C is awaiting development
|
||||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
|
||||
|
||||
/* TODO when ready
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
#define ARDUINO_TYPE "RP2040"
|
||||
#define ARDUINO_TYPE "RP2040"
|
||||
*/
|
||||
|
||||
#else
|
||||
#define CPU_TYPE_ERROR
|
||||
#define CPU_TYPE_ERROR
|
||||
#endif
|
||||
|
||||
// replace board type if provided by compiler
|
||||
#ifdef BOARD_NAME
|
||||
#undef ARDUINO_TYPE
|
||||
#define ARDUINO_TYPE BOARD_NAME
|
||||
#endif
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
|
34
installer.sh
34
installer.sh
@@ -37,6 +37,10 @@ function need () {
|
||||
|
||||
|
||||
need git
|
||||
if test -d `basename "$DCCEXGITURL"` ; then
|
||||
: assume we are almost there
|
||||
cd `basename "$DCCEXGITURL"` || exit 255
|
||||
fi
|
||||
if test -d .git ; then
|
||||
: assume we are right here
|
||||
git pull
|
||||
@@ -44,6 +48,21 @@ else
|
||||
git clone "$DCCEXGITURL"
|
||||
cd `basename "$DCCEXGITURL"` || exit 255
|
||||
fi
|
||||
|
||||
# prepare versions
|
||||
VERSIONS=/tmp/versions.$$
|
||||
git tag --sort=v:refname | grep Prod | tail -1 > $VERSIONS
|
||||
echo master >> $VERSIONS
|
||||
git tag --sort=v:refname | grep Devel | tail -1 >> $VERSIONS
|
||||
echo devel >> $VERSIONS
|
||||
|
||||
# ask user what version to use
|
||||
echo "What version to use? (give line number) If in doubt, use 1"
|
||||
cat -n $VERSIONS
|
||||
echo -n "> "
|
||||
LINE=`awk 'BEGIN {getline A < "/dev/tty"} ; A == NR {print}' $VERSIONS`
|
||||
git checkout $LINE
|
||||
|
||||
if test -f config.h ; then
|
||||
: all well
|
||||
else
|
||||
@@ -63,7 +82,14 @@ $ACLI core update-index || exit 255
|
||||
|
||||
# Board discovery
|
||||
BOARDS=/tmp/boards.$$
|
||||
$ACLI board list | grep serial > $BOARDS
|
||||
$ACLI board list > /dev/null # download missing components
|
||||
$ACLI board list | grep serial > $BOARDS # real run
|
||||
if test -s $BOARDS ; then
|
||||
: all well
|
||||
else
|
||||
echo "$ACLI: No boards found"
|
||||
exit 255
|
||||
fi
|
||||
if test x`< $BOARDS wc -l` = 'x1' ; then
|
||||
LINE=`cat $BOARDS`
|
||||
else
|
||||
@@ -96,6 +122,6 @@ echo FQBN is $FQBN
|
||||
|
||||
# Install phase
|
||||
$ACLI core install `echo $FQBN | sed 's,:[^:]*$,,1'` # remove last component to get package
|
||||
$ACLI board attach -p $PORT --fqbn $FQBN $PWD
|
||||
$ACLI compile --fqbn $FQBN $PWD
|
||||
$ACLI upload -v -t -p $PORT $PWD
|
||||
$ACLI board attach -p $PORT --fqbn $FQBN "$PWD"
|
||||
$ACLI compile --fqbn $FQBN "$PWD"
|
||||
$ACLI upload -v -t -p $PORT "$PWD"
|
||||
|
@@ -17,10 +17,13 @@
|
||||
|
||||
// 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
|
||||
|
||||
//==========================================================================
|
||||
// The function halSetup() is invoked from CS if it exists within the build.
|
||||
@@ -30,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.
|
||||
//=======================================================================
|
||||
@@ -87,6 +145,21 @@ void halSetup() {
|
||||
//PCF8574::create(200, 8, 0x23, 40);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines a PCF8575 16-port I2C GPIO Extender module.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// First Vpin=200
|
||||
// Number of VPINs=16 (numbered 200-215)
|
||||
// I2C address of module=0x23
|
||||
|
||||
//PCF8575::create(200, 16, 0x23);
|
||||
|
||||
|
||||
// Alternative form using INT pin (see above)
|
||||
|
||||
//PCF8575::create(200, 16, 0x23, 40);
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an HCSR04 ultrasonic ranging module.
|
||||
//=======================================================================
|
||||
@@ -140,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.
|
||||
@@ -154,12 +227,84 @@ 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.
|
||||
//=======================================================================
|
||||
// EXTurntable::create(VPIN, Number of VPINs, I2C Address)
|
||||
//
|
||||
// The parameters are:
|
||||
// VPIN=600
|
||||
// Number of VPINs=1 (Note there is no reason to change this)
|
||||
// I2C address=0x60
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-Turntable code, and 0x60 is the default.
|
||||
|
||||
//EXTurntable::create(600, 1, 0x60);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-IOExpander instance.
|
||||
//=======================================================================
|
||||
// EXIOExpander::create(VPIN, Number of VPINs, I2C Address)
|
||||
//
|
||||
// The parameters are:
|
||||
// VPIN=an available Vpin
|
||||
// Number of VPINs=pin count (must match device in use as per documentation)
|
||||
// I2C address=an available I2C address (default 0x65)
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-IOExpander code, and 0x65 is the default.
|
||||
// The example is for an Arduino Nano.
|
||||
|
||||
//EXIOExpander::create(800, 18, 0x65);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines a rotary encoder instance.
|
||||
//=======================================================================
|
||||
// The parameters are:
|
||||
// firstVpin = First available Vpin to allocate
|
||||
// numPins= Number of Vpins to allocate, can be either 1 or 2
|
||||
// i2cAddress = Available I2C address (default 0x70)
|
||||
|
||||
//RotaryEncoder::create(firstVpin, numPins, i2cAddress);
|
||||
//RotaryEncoder::create(700, 1, 0x70);
|
||||
//RotaryEncoder::create(701, 2, 0x71);
|
||||
|
||||
//=======================================================================
|
||||
// The following directive defines an EX-FastClock instance.
|
||||
//=======================================================================
|
||||
// EXFastCLock::create(I2C Address)
|
||||
//
|
||||
// The parameters are:
|
||||
//
|
||||
// I2C address=0x55 (decimal 85)
|
||||
//
|
||||
// Note that the I2C address is defined in the EX-FastClock code, and 0x55 is the default.
|
||||
|
||||
|
||||
// EXFastClock::create(0x55);
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
|
@@ -19,6 +19,7 @@ default_envs =
|
||||
samd21-zero-usb
|
||||
ESP32
|
||||
Nucleo-F411RE
|
||||
Nucleo-F446RE
|
||||
Teensy3.2
|
||||
Teensy3.5
|
||||
Teensy3.6
|
||||
@@ -29,6 +30,8 @@ include_dir = .
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
monitor_filters = time
|
||||
; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0
|
||||
|
||||
[env:samd21-dev-usb]
|
||||
platform = atmelsam
|
||||
@@ -50,18 +53,14 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17
|
||||
|
||||
; Firebox disabled for now
|
||||
; [env:samc21-firebox]
|
||||
; platform = atmelsam
|
||||
; board = firebox
|
||||
; framework = arduino
|
||||
; upload_protocol = atmel-ice
|
||||
; lib_deps =
|
||||
; ${env.lib_deps}
|
||||
; SparkFun External EEPROM Arduino Library
|
||||
;monitor_speed = 115200
|
||||
;monitor_echo = yes
|
||||
;build_flags = -std=c++17
|
||||
[env:Arduino M0]
|
||||
platform = atmelsam
|
||||
board = mzeroUSB
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
|
||||
|
||||
[env:mega2560-debug]
|
||||
platform = atmelavr
|
||||
@@ -73,7 +72,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -DDIAG_IO -DDIAG_LOOPTIMES
|
||||
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
|
||||
|
||||
[env:mega2560-no-HAL]
|
||||
platform = atmelavr
|
||||
@@ -85,7 +84,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -DIO_NO_HAL
|
||||
build_flags = -DIO_NO_HAL
|
||||
|
||||
[env:mega2560-I2C-wire]
|
||||
platform = atmelavr
|
||||
@@ -109,10 +108,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
; Example, but v12 does generate bigger binaries
|
||||
; platform_packages = toolchain-atmelavr@symlink:///opt/avr-gcc-12.1.0-x64-linux
|
||||
; Should make binaries smaller
|
||||
build_flags = -mcall-prologues
|
||||
build_flags = ; -DDIAG_LOOPTIMES
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@@ -148,7 +144,7 @@ lib_deps =
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
upload_speed = 19200
|
||||
build_flags = -DDIAG_IO
|
||||
build_flags =
|
||||
|
||||
[env:uno]
|
||||
platform = atmelavr
|
||||
@@ -160,7 +156,6 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
; Should make binaries smaller
|
||||
build_flags = -mcall-prologues
|
||||
|
||||
[env:nano]
|
||||
@@ -184,7 +179,16 @@ platform = ststm32
|
||||
board = nucleo_f411re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Nucleo-F446RE]
|
||||
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
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
@@ -192,7 +196,7 @@ monitor_echo = yes
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
@@ -200,7 +204,7 @@ lib_ignore = NativeEthernet
|
||||
platform = teensy
|
||||
board = teensy35
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
@@ -208,7 +212,7 @@ lib_ignore = NativeEthernet
|
||||
platform = teensy
|
||||
board = teensy36
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
@@ -216,7 +220,7 @@ lib_ignore = NativeEthernet
|
||||
platform = teensy
|
||||
board = teensy40
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore = NativeEthernet
|
||||
|
||||
@@ -224,6 +228,7 @@ lib_ignore = NativeEthernet
|
||||
platform = teensy
|
||||
board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -DDISABLE_EEPROM -Os -g2
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
lib_ignore =
|
||||
|
||||
|
50
version.h
50
version.h
@@ -4,7 +4,55 @@
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.6"
|
||||
#define VERSION "4.2.24"
|
||||
// 4.3.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.
|
||||
// - I2C timeout handling and recovery completed.
|
||||
// - I2C SAMD Driver Read code completed.
|
||||
// - PCF8575 I2C GPIO driver added.
|
||||
// - EX-RAIL ANOUT function for triggering analogue
|
||||
// HAL drivers (e.g. analogue outputs, DFPlayer, PWM).
|
||||
// - EX-RAIL SCREEN function for writing to screens other
|
||||
// than the primary one.
|
||||
// - Installable HALDisplay Driver, with support
|
||||
// for multiple displays.
|
||||
// - Layered HAL Drivers PCA9685pwm and Servo added for
|
||||
// native PWM on PCA9685 module and
|
||||
// for animations of servo movement via PCA9685pwm.
|
||||
// This is intended to support EXIOExpander and also
|
||||
// replace the existing PCA9685 driver.
|
||||
// - Add <D HAL RESET> to reinitialise failed drivers.
|
||||
// - Add UserAddin facility to allow a user-written C++ function to be
|
||||
// declared in myHal.cpp, to be called at a user-specified frequency.
|
||||
// - Add ability to configure clock speed of PCA9685 drivers
|
||||
// (to allow flicker-free LED control).
|
||||
// - Improve stability of VL53L0X driver when XSHUT pin connected.
|
||||
// - Enable DCC high accuracy mode for STM32 on standard motor shield (pins D12/D13).
|
||||
// - Incorporate improvements to ADC scanning performance (courtesy of HABA).
|
||||
// 4.2.17 LCN bugfix
|
||||
// 4.2.16 Move EX-IOExpander servo support to the EX-IOExpander software
|
||||
// 4.2.15 Add basic experimental PWM support to EX-IOExpander
|
||||
// EX-IOExpander 0.0.14 minimum required
|
||||
// 4.2.14 STM32F4xx fast ADC read implementation
|
||||
// 4.2.13 Broadcast power for <s> again
|
||||
// 4.2.12 Bugfix for issue #299 TurnoutDescription NULL
|
||||
// 4.2.11 Exrail IFLOCO feature added
|
||||
// 4.2.10 SIGNAL/SIGNALH bug fix as they were inverted
|
||||
// IO_EXIOExpander.h input speed optimisation
|
||||
// 4.2.9 duinoNodes support
|
||||
// 4.2.8 HIGHMEM (EXRAIL support beyond 64kb)
|
||||
// Withrottle connect/disconnect improvements
|
||||
// Report BOARD_TYPE if provided by compiler
|
||||
// 4.2.7 FIX: Static IP addr
|
||||
// FIX: Reuse WiThrottle list entries
|
||||
// 4.2.6 FIX: Remove RAM thief
|
||||
// FIX: ADC port 8-15 fix
|
||||
// 4.2.5 Make GETFLASHW code more universal
|
||||
|
Reference in New Issue
Block a user