mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 18:33:44 +02:00
Compare commits
253 Commits
v4.2.9-Dev
...
v4.2.18-de
Author | SHA1 | Date | |
---|---|---|---|
|
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 | ||
|
71ce913712 | ||
|
70845b4932 | ||
|
c44fb0ac44 | ||
|
1c7103c21e | ||
|
5170147e3e | ||
|
2ad08029a4 | ||
|
25b3250345 | ||
|
3973996344 | ||
|
943494385f | ||
|
c8fea3a4a7 | ||
|
a480a5a3d2 | ||
|
070daa37dc | ||
|
75f1a8f43a | ||
|
8ecb408da7 | ||
|
3862f7250d | ||
|
785b515f9e | ||
|
9699a44081 | ||
|
cb9a8bb7a6 | ||
|
1d5897d2d2 | ||
|
7bc0433197 | ||
|
9104956009 | ||
|
af4d8d4075 | ||
|
06945bb114 | ||
|
2d27cb052d | ||
|
8cbcf5df32 |
@@ -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
|
||||
|
||||
@@ -155,6 +160,50 @@ 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){
|
||||
if (Diag::CMD) {
|
||||
DIAG(F("Clock Command Received"));
|
||||
DIAG(F("Received Clock Time is: %d at rate: %d"), clocktime, clockrate);
|
||||
}
|
||||
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);
|
||||
|
@@ -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,14 @@ 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);
|
||||
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();
|
||||
|
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"
|
||||
|
@@ -190,9 +190,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);
|
||||
}
|
||||
|
||||
@@ -510,6 +510,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
|
||||
@@ -570,9 +571,19 @@ 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_A: // <JA> returns automations/routes
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
if (params==1) {// <JA>
|
||||
@@ -616,14 +627,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;
|
||||
@@ -930,10 +944,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;
|
||||
};
|
||||
|
@@ -123,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
|
||||
@@ -138,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() {
|
||||
@@ -159,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
|
||||
@@ -171,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) {
|
||||
@@ -184,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
|
||||
@@ -236,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
|
||||
@@ -31,14 +32,18 @@
|
||||
#include "DCCTimer.h"
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F411RE)
|
||||
// STM32F411RE doesn't have Serial1 defined by default
|
||||
// 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(ARDUINO_BLAH_F412ZG) || defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_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
|
||||
@@ -46,13 +51,16 @@ HardwareSerial Serial1(PG9, PG14); // Rx=PG9, Tx=PG14 -- D0, D1 - F412ZG/F446ZE
|
||||
#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();
|
||||
}
|
||||
|
||||
@@ -62,37 +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 *)0x1FFF7A10;
|
||||
volatile uint32_t *serno2 = (volatile uint32_t *)0x1FFF7A14;
|
||||
volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
|
||||
// volatile uint32_t *serno3 = (volatile uint32_t *)0x1FFF7A18;
|
||||
|
||||
volatile uint32_t m1 = *serno1;
|
||||
volatile uint32_t m2 = *serno2;
|
||||
@@ -127,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;
|
||||
// Start of chain of display handlers.
|
||||
DisplayInterface *DisplayInterface::_displayHandler = NULL;
|
||||
|
||||
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
|
70
EXRAIL2.cpp
70
EXRAIL2.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2022 Harald Barth
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2022 Chris Harlow
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -91,6 +91,8 @@ LookList * RMFT2::onDeactivateLookup=NULL;
|
||||
LookList * RMFT2::onRedLookup=NULL;
|
||||
LookList * RMFT2::onAmberLookup=NULL;
|
||||
LookList * RMFT2::onGreenLookup=NULL;
|
||||
LookList * RMFT2::onChangeLookup=NULL;
|
||||
LookList * RMFT2::onClockLookup=NULL;
|
||||
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
@@ -173,6 +175,9 @@ 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
|
||||
@@ -742,9 +747,17 @@ void RMFT2::loop2() {
|
||||
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=(uint8_t)micros() >= operand * 255/100;
|
||||
@@ -968,6 +981,8 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONRED:
|
||||
case OPCODE_ONAMBER:
|
||||
case OPCODE_ONGREEN:
|
||||
case OPCODE_ONCHANGE:
|
||||
case OPCODE_ONTIME:
|
||||
|
||||
break;
|
||||
|
||||
@@ -1069,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) {
|
||||
@@ -1094,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;
|
||||
@@ -1126,11 +1165,11 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
// Find out where the string is going
|
||||
switch (mode) {
|
||||
case thrunge_print:
|
||||
StringFormatter::send(&Serial,F("<* EXRAIL(%d) "),loco);
|
||||
stream=&Serial;
|
||||
StringFormatter::send(&USB_SERIAL,F("<* EXRAIL(%d) "),loco);
|
||||
stream=&USB_SERIAL;
|
||||
break;
|
||||
|
||||
case thrunge_serial: stream=&Serial; break;
|
||||
case thrunge_serial: stream=&USB_SERIAL; break;
|
||||
case thrunge_serial1:
|
||||
#ifdef SERIAL1_COMMANDS
|
||||
stream=&Serial1;
|
||||
@@ -1161,7 +1200,6 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
stream=&Serial6;
|
||||
#endif
|
||||
break;
|
||||
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
|
||||
case thrunge_lcn:
|
||||
#if defined(LCN_SERIAL)
|
||||
stream=&LCN_SERIAL;
|
||||
@@ -1170,6 +1208,7 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
case thrunge_parse:
|
||||
case thrunge_broadcast:
|
||||
case thrunge_lcd:
|
||||
default: // thrunge_lcd+1, ...
|
||||
if (!buffer) buffer=new StringBuffer();
|
||||
buffer->flush();
|
||||
stream=buffer;
|
||||
@@ -1193,11 +1232,11 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
// and decide what to do next
|
||||
switch (mode) {
|
||||
case thrunge_print:
|
||||
StringFormatter::send(&Serial,F(" *>\n"));
|
||||
StringFormatter::send(&USB_SERIAL,F(" *>\n"));
|
||||
break;
|
||||
// TODO more serials for SAMx case thrunge_serial4: stream=&Serial4; break;
|
||||
case thrunge_parse:
|
||||
DCCEXParser::parseOne(&Serial,(byte*)buffer->getString(),NULL);
|
||||
DCCEXParser::parseOne(&USB_SERIAL,(byte*)buffer->getString(),NULL);
|
||||
break;
|
||||
case thrunge_broadcast:
|
||||
// TODO CommandDistributor::broadcastText(buffer->getString());
|
||||
@@ -1205,8 +1244,9 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
case thrunge_lcd:
|
||||
LCD(id,F("%s"),buffer->getString());
|
||||
break;
|
||||
|
||||
default: break;
|
||||
default: // thrunge_lcd+1, ...
|
||||
if (mode > thrunge_lcd)
|
||||
SCREEN(mode-thrunge_lcd, id, F("%s"),buffer->getString()); // print to other display
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
18
EXRAIL2.h
18
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,14 +68,20 @@ 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_lcd, thrunge_lcn};
|
||||
thrunge_lcn,
|
||||
thrunge_lcd, // Must be last!!
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -113,6 +123,8 @@ 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;
|
||||
@@ -169,6 +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
|
||||
@@ -141,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)
|
||||
@@ -178,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)
|
||||
@@ -185,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)
|
||||
|
@@ -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
|
||||
@@ -137,6 +142,15 @@ const int StringMacroTracker1=__COUNTER__;
|
||||
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) {
|
||||
@@ -241,6 +255,7 @@ const HIGHFLASH 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),
|
||||
@@ -278,6 +293,7 @@ const HIGHFLASH 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),
|
||||
@@ -285,22 +301,27 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#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),
|
||||
@@ -357,6 +378,8 @@ const HIGHFLASH byte RMFT2::RouteCode[] = {
|
||||
// Restore normal code LCD & SERIAL macro
|
||||
#undef LCD
|
||||
#define LCD StringFormatter::lcd
|
||||
#undef SCREEN
|
||||
#define SCREEN StringFormatter::lcd2
|
||||
#undef SERIAL
|
||||
#define SERIAL 0x0
|
||||
#endif
|
||||
|
11
FSH.h
11
FSH.h
@@ -47,7 +47,11 @@
|
||||
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_AVR_MEGA) || defined(ARDUINO_AVR_MEGA2560)
|
||||
// AVR_MEGA memory deliberately placed at end of link may need _far functions
|
||||
@@ -80,5 +84,10 @@ typedef char FSH;
|
||||
#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-202212051450Z"
|
||||
#define GITHUB_SHA "devel-202302121935Z"
|
||||
|
197
I2CManager.cpp
197
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,120 @@
|
||||
#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;
|
||||
_initialise();
|
||||
|
||||
// Probe and list devices.
|
||||
#if defined(I2C_USE_WIRE)
|
||||
DIAG(F("I2CManager: Using Wire library"));
|
||||
#endif
|
||||
|
||||
// Check for short-circuits on I2C
|
||||
if (!digitalRead(SDA))
|
||||
DIAG(F("WARNING: Possible short-circuit on I2C SDA line"));
|
||||
if (!digitalRead(SCL))
|
||||
DIAG(F("WARNING: Possible short-circuit on I2C SCL line"));
|
||||
|
||||
// Probe and list devices. Use standard mode
|
||||
// (clock speed 100kHz) for best device compatibility.
|
||||
_setClock(100000);
|
||||
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 +157,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 +198,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 +207,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 +216,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 +227,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 +258,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 +272,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 +314,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 +325,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 +334,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
|
381
I2CManager.h
381
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,6 +78,8 @@
|
||||
* 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.
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -102,7 +106,7 @@
|
||||
*
|
||||
* 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 +115,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 +133,237 @@
|
||||
#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.
|
||||
//
|
||||
// WARNING: When I2CAddress is passed to formatting commands such as DIAG, LCD etc,
|
||||
// it should be cast to (int) to ensure that the address value is passed rather than
|
||||
// the struct.
|
||||
|
||||
//#define I2C_EXTENDED_ADDRESS
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
// Extended I2C Address type to facilitate extended I2C addresses including
|
||||
// I2C multiplexer support.
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
// 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 +386,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 +395,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 +415,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 +441,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 +484,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 +524,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,77 @@ 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 :
|
||||
(1 << subBus);
|
||||
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 +135,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 +202,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 +223,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
|
||||
|
132
IODevice.h
132
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,27 +168,12 @@ public:
|
||||
// once the GPIO port concerned has been read.
|
||||
void setGPIOInterruptPin(int16_t pinNumber);
|
||||
|
||||
// Method to check if pins will overlap before creating new device.
|
||||
static bool checkNoOverlap(VPIN firstPin, uint8_t nPins=1, uint8_t i2cAddress=0);
|
||||
|
||||
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) {
|
||||
@@ -189,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;
|
||||
};
|
||||
|
||||
@@ -204,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;
|
||||
@@ -228,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;
|
||||
|
||||
@@ -237,22 +252,20 @@ protected:
|
||||
int16_t _gpioInterruptPin = -1;
|
||||
|
||||
// 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;
|
||||
};
|
||||
|
||||
|
||||
@@ -263,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
|
||||
@@ -276,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;
|
||||
@@ -306,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
|
||||
};
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -376,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
|
||||
@@ -404,9 +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
|
263
IO_EXIOExpander.h
Normal file
263
IO_EXIOExpander.h
Normal file
@@ -0,0 +1,263 @@
|
||||
/*
|
||||
* © 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);
|
||||
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);
|
||||
// Attempt to get version, if we don't get it, we don't care, don't go offline
|
||||
_command1Buffer[0] = EXIOVER;
|
||||
I2CManager.read(_i2cAddress, _versionBuffer, 3, _command1Buffer, 1);
|
||||
_majorVer = _versionBuffer[0];
|
||||
_minorVer = _versionBuffer[1];
|
||||
_patchVer = _versionBuffer[2];
|
||||
DIAG(F("EX-IOExpander device found, I2C:%s, Version v%d.%d.%d"),
|
||||
_I2CAddress.toString(), _versionBuffer[0], _versionBuffer[1], _versionBuffer[2]);
|
||||
#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);
|
||||
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);
|
||||
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);
|
||||
_command1Buffer[0] = EXIORDAN;
|
||||
I2CManager.read(_i2cAddress, _analogueInputStates, _analoguePinBytes, _command1Buffer, 1);
|
||||
}
|
||||
|
||||
// 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);
|
||||
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);
|
||||
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;
|
||||
|
||||
// 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
|
141
IO_ExternalEEPROM.h
Normal file
141
IO_ExternalEEPROM.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* This device driver monitors the state of turnout objects and writes updates,
|
||||
* on change of state, to an external 24C128 (16kByte) or 24C256 (32kByte)
|
||||
* EEPROM device connected via I2C.
|
||||
*
|
||||
* When the device is restarted, it repositions the turnouts in accordance
|
||||
* with the last saved position.
|
||||
*
|
||||
* To create a device instance,
|
||||
* IO_ExternalEEPROM::create(0, 0, i2cAddress);
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_EXTERNALEEPROM_H
|
||||
#define IO_EXTERNALEEPROM_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "Turnouts.h"
|
||||
|
||||
class ExternalEEPROM : public IODevice {
|
||||
private:
|
||||
// Here we define the device-specific variables.
|
||||
int _sizeInKBytes = 128;
|
||||
Turnout *_turnout = 0;
|
||||
int _lastTurnoutHash = 0;
|
||||
I2CRB _rb;
|
||||
uint8_t _buffer[32]; // 32 is max for Wire write
|
||||
|
||||
public:
|
||||
// Static function to handle "IO_ExampleSerial::create(...)" calls.
|
||||
static void create(I2CAddress i2cAddress, int sizeInKBytes) {
|
||||
if (checkNoOverlap(0, 0, i2cAddress)) new ExternalEEPROM(i2cAddress, sizeInKBytes);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Constructor.
|
||||
ExternalEEPROM(I2CAddress i2cAddress, int sizeInKBytes) {
|
||||
_I2CAddress = i2cAddress;
|
||||
_sizeInKBytes = sizeInKBytes;
|
||||
|
||||
// Set up I2C structures.
|
||||
_rb.setWriteParams(_I2CAddress, _buffer, 32);
|
||||
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000); // Max supported speed
|
||||
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
// Initialise or read contents of EEPROM
|
||||
// and set turnout states accordingly.
|
||||
// Read 32 bytes from address 0x0000.
|
||||
I2CManager.read(_I2CAddress, _buffer, 32, 2, 0, 0);
|
||||
// Dump data
|
||||
DIAG(F("EEPROM First 32 bytes:"));
|
||||
for (int i=0; i<32; i+=8)
|
||||
DIAG(F("%d: %x %x %x %x %x %x %x %x"),
|
||||
i, _buffer[i], _buffer[i+1], _buffer[i+2], _buffer[i+3],
|
||||
_buffer[i+4], _buffer[i+5], _buffer[i+6], _buffer[i+7]);
|
||||
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("ExternalEEPROM not found, I2C:%s"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// Loop function to do background scanning of the turnouts
|
||||
void _loop(unsigned long currentMicros) {
|
||||
(void)currentMicros; // Suppress compiler warnings
|
||||
|
||||
if (_rb.isBusy()) return; // Can't do anything until previous request has completed.
|
||||
if (_rb.status == I2C_STATUS_NEGATIVE_ACKNOWLEDGE) {
|
||||
// Device not responding, probably still writing data, so requeue request
|
||||
I2CManager.queueRequest(&_rb);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_lastTurnoutHash != Turnout::turnoutlistHash) {
|
||||
_lastTurnoutHash = Turnout::turnoutlistHash;
|
||||
// Turnout list has changed, so pointer held from last run may be invalid
|
||||
_turnout = 0; // Start at the beginning of the list again.
|
||||
//#if defined(DIAG_IO)
|
||||
DIAG(F("Turnout Hash Changed!"));
|
||||
//#endif
|
||||
}
|
||||
|
||||
// Locate next turnout, or first one if there is no current one.
|
||||
if (_turnout)
|
||||
_turnout = _turnout->next();
|
||||
else
|
||||
_turnout = Turnout::first();
|
||||
|
||||
// Retrieve turnout state
|
||||
int turnoutID = _turnout->getId();
|
||||
int turnoutState = _turnout->isThrown();
|
||||
(void)turnoutID; // Suppress compiler warning
|
||||
(void)turnoutState; // Suppress compiler warning
|
||||
|
||||
// TODO: Locate turnoutID in EEPROM (or EEPROM copy) and check if state has changed.
|
||||
// TODO: If it has, then initiate a write of the updated state to EEPROM
|
||||
|
||||
delayUntil(currentMicros+5000); // Write cycle time is 5ms max for FT24C256
|
||||
}
|
||||
|
||||
// Display information about the device.
|
||||
void _display() {
|
||||
DIAG(F("ExternalEEPROM %dkBytes I2C:%s %S"), _sizeInKBytes, _I2CAddress.toString(),
|
||||
_deviceState== DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_EXTERNALEEPROM_H
|
@@ -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)
|
||||
|
250
IO_HALDisplay.h
Normal file
250
IO_HALDisplay.h
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* © 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;
|
||||
DisplayInterface *_nextDisplay = NULL;
|
||||
|
||||
public:
|
||||
// Static function to handle "HALDisplay::create(...)" calls.
|
||||
static void create(I2CAddress i2cAddress, int width, int height) {
|
||||
/* if (checkNoOverlap(i2cAddress)) */ new HALDisplay(0, i2cAddress, width, height);
|
||||
}
|
||||
static void create(uint8_t displayNo, I2CAddress i2cAddress, int width, int height) {
|
||||
/* if (checkNoOverlap(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);
|
||||
_I2CAddress = i2cAddress;
|
||||
_width = width;
|
||||
_height = height;
|
||||
_numCols = _displayDriver->getNumCols();
|
||||
_numRows = _displayDriver->getNumRows();
|
||||
|
||||
_charPosToScreen = _numCols;
|
||||
|
||||
// Allocate arrays
|
||||
_buffer = (char *)calloc(_numRows*_numCols, sizeof(char));
|
||||
_rowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
|
||||
_lastRowGeneration = (uint8_t *)calloc(_numRows, sizeof(uint8_t));
|
||||
// Fill buffer with spaces
|
||||
memset(_buffer, ' ', _numCols*_numRows);
|
||||
|
||||
_displayDriver->clearNative();
|
||||
|
||||
// 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 main display?
|
||||
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 to 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)
|
||||
_rowNoToScreen = 0; // Wrap to first row
|
||||
|
||||
if (_rowGeneration[_rowNoToScreen] != _lastRowGeneration[_rowNoToScreen]) {
|
||||
// Row content has changed, so start outputting it
|
||||
_lastRowGeneration[_rowNoToScreen] = _rowGeneration[_rowNoToScreen];
|
||||
_displayDriver->setRowNative(_rowNoToScreen);
|
||||
_charPosToScreen = 0; // Prepare to output first character on next entry
|
||||
} else {
|
||||
// Row not changed, don't bother writing it.
|
||||
}
|
||||
} else {
|
||||
// output character at current position
|
||||
_displayDriver->writeNative(_buffer[_rowNoToScreen*_numCols+_charPosToScreen++]);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////
|
||||
// IODevice Class Member Overrides
|
||||
/////////////////////////////////////////////////
|
||||
|
||||
// Device-specific initialisation
|
||||
void _begin() override {
|
||||
// Initialise device
|
||||
if (_displayDriver->begin()) {
|
||||
|
||||
_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 be
|
||||
// sent to the screen on the next loop entry, by which time
|
||||
// the line should have been written to the buffer.
|
||||
_rowGeneration[_rowNo]++;
|
||||
}
|
||||
|
||||
// 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,22 +31,21 @@ 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.
|
||||
bool PCA9685::_configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) {
|
||||
if (configType != CONFIGURE_SERVO) return false;
|
||||
if (paramCount != 5) return false;
|
||||
#ifdef DIAG_IO
|
||||
#if DIAG_IO >= 3
|
||||
DIAG(F("PCA9685 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
|
||||
@@ -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
|
||||
@@ -114,7 +117,7 @@ void PCA9685::_begin() {
|
||||
// Device-specific write function, invoked from IODevice::write().
|
||||
// For this function, the configured profile is used.
|
||||
void PCA9685::_write(VPIN vpin, int value) {
|
||||
#ifdef DIAG_IO
|
||||
#if DIAG_IO >= 3
|
||||
DIAG(F("PCA9685 Write Vpin:%d Value:%d"), vpin, value);
|
||||
#endif
|
||||
int pin = vpin - _firstVpin;
|
||||
@@ -141,7 +144,7 @@ void PCA9685::_write(VPIN vpin, int value) {
|
||||
// 4 (Bounce) Servo 'bounces' at extremes.
|
||||
//
|
||||
void PCA9685::_writeAnalogue(VPIN vpin, int value, uint8_t profile, uint16_t duration) {
|
||||
#ifdef DIAG_IO
|
||||
#if DIAG_IO >= 3
|
||||
DIAG(F("PCA9685 WriteAnalogue Vpin:%d Value:%d Profile:%d Duration:%d %S"),
|
||||
vpin, value, profile, duration, _deviceState == DEVSTATE_FAILED?F("DEVSTATE_FAILED"):F(""));
|
||||
#endif
|
||||
@@ -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
|
||||
#if DIAG_IO >= 3
|
||||
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) {
|
||||
#if DIAG_IO >= 3
|
||||
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
|
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
|
||||
|
@@ -23,11 +23,10 @@
|
||||
#include "defines.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
#define PIN_MASK(bit) (0x80>>(bit%8))
|
||||
#define GET_BIT(x) (_pinValues[(x)/8] & PIN_MASK((x)) )
|
||||
#define SET_BIT(x) _pinValues[(x)/8] |= PIN_MASK((x))
|
||||
#define CLR_BIT(x) _pinValues[(x)/8] &= ~PIN_MASK((x))
|
||||
#define DIAG_IO
|
||||
#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))
|
||||
|
||||
|
||||
|
||||
@@ -98,7 +97,7 @@ void _loopOutput() {
|
||||
_xmitPending=false;
|
||||
ArduinoPins::fastWriteDigital(_latchPin, LOW);
|
||||
for (int xmitBit=_nShiftBytes*8 -1; xmitBit>=0; xmitBit--) {
|
||||
ArduinoPins::fastWriteDigital(_dataPin,GET_BIT(xmitBit));
|
||||
ArduinoPins::fastWriteDigital(_dataPin,DN_GET_BIT(xmitBit));
|
||||
ArduinoPins::fastWriteDigital(_clockPin,HIGH);
|
||||
ArduinoPins::fastWriteDigital(_clockPin,LOW);
|
||||
}
|
||||
@@ -107,17 +106,17 @@ void _loopOutput() {
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
int pin=vpin - _firstVpin;
|
||||
bool b=GET_BIT(pin);
|
||||
bool b=DN_GET_BIT(pin);
|
||||
return b?1:0;
|
||||
}
|
||||
|
||||
void _write(VPIN vpin, int value) override {
|
||||
int pin = vpin - _firstVpin;
|
||||
bool oldval=GET_BIT(pin);
|
||||
bool oldval=DN_GET_BIT(pin);
|
||||
bool newval=value!=0;
|
||||
if (newval==oldval) return; // no change
|
||||
if (newval) SET_BIT(pin);
|
||||
else CLR_BIT(pin);
|
||||
if (newval) DN_SET_BIT(pin);
|
||||
else DN_CLR_BIT(pin);
|
||||
_xmitPending=true; // shift register will be sent on next _loop()
|
||||
}
|
||||
|
||||
|
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
|
||||
|
@@ -144,16 +144,12 @@ bool MotorDriver::isPWMCapable() {
|
||||
void MotorDriver::setPower(POWERMODE mode) {
|
||||
bool on=mode==POWERMODE::ON;
|
||||
if (on) {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? LOW : HIGH);
|
||||
interrupts();
|
||||
if (isProgTrack)
|
||||
DCCWaveform::progTrack.clearResets();
|
||||
}
|
||||
else {
|
||||
noInterrupts();
|
||||
IODevice::write(powerPin,invertPower ? HIGH : LOW);
|
||||
interrupts();
|
||||
}
|
||||
powerMode=mode;
|
||||
}
|
||||
|
@@ -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 |
@@ -83,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
|
||||
|
434
SSD1306Ascii.cpp
434
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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,127 +258,262 @@ 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
|
||||
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 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
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
// Extended characters 176-180
|
||||
0x92, 0x00, 0x49, 0x00, 0x24, 0x00, // Light grey 0xb0
|
||||
0xcc, 0x55, 0xcc, 0x55, 0xcc, 0x55, // Mid grey 0xb1
|
||||
0x6a, 0xff, 0xb6, 0xff, 0xdb, 0xff, // Dark grey 0xb2
|
||||
0x00, 0x00, 0x00, 0xff, 0x00, 0x00, // Vertical line 0xb3
|
||||
0x08, 0x08, 0x08, 0xff, 0x00, 0x00, // Vertical line with left spur 0xb4
|
||||
|
||||
0x14, 0x14, 0xfe, 0x00, 0xff, 0x00, // Vertical line with double left spur 0xb9
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented Double vertical line with single left spur
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
|
||||
// Extended characters 185-190
|
||||
0x28, 0x28, 0xef, 0x00, 0xff, 0x00, // Double vertical line with double left spur 0xb9
|
||||
0x00, 0x00, 0xff, 0x00, 0xff, 0x00, // Double vertical line 0xba
|
||||
0x14, 0x14, 0xf4, 0x04, 0xfc, 0x00, // Double top right corner 0xbb
|
||||
0x14, 0x14, 0x17, 0x10, 0x1f, 0x00, // Double bottom right corner 0xbc
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbd
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xbe
|
||||
|
||||
// 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, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
|
||||
// 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
|
||||
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xd0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
|
||||
// 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
|
||||
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented 0xe0
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Not implemented
|
||||
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
|
||||
0x00
|
||||
};
|
||||
|
||||
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[];
|
||||
};
|
||||
|
@@ -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...) {
|
||||
@@ -108,7 +115,8 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
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 '-':
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -207,7 +207,7 @@
|
||||
}
|
||||
|
||||
#ifdef EESTOREDEBUG
|
||||
printAll(&Serial);
|
||||
printAll(&USB_SERIAL);
|
||||
#endif
|
||||
return tt;
|
||||
}
|
||||
|
@@ -525,7 +525,7 @@ void WiThrottle::sendTurnouts(Print* stream) {
|
||||
}
|
||||
void WiThrottle::sendRoster(Print* stream) {
|
||||
rosterSent=true;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
#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);
|
||||
@@ -533,11 +533,13 @@ void WiThrottle::sendRoster(Print* stream) {
|
||||
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
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
StringFormatter::send(stream,F("PRT]\\[Routes}|{Route]\\[Set}|{2]\\[Handoff}|{4\nPRL"));
|
||||
// first pass automations
|
||||
for (int ix=0;;ix+=2) {
|
||||
@@ -554,7 +556,8 @@ void WiThrottle::sendRoutes(Print* stream) {
|
||||
StringFormatter::send(stream,F("]\\[R%d}|{%S}|{2"),id,desc);
|
||||
}
|
||||
StringFormatter::send(stream,F("\n"));
|
||||
|
||||
#else
|
||||
(void)stream; // remove warning
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -609,4 +612,4 @@ void WiThrottle::sendFunctions(Print* stream, byte loco) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -224,4 +224,8 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
|
||||
// FastClock Enabler
|
||||
// To build the FastClock code into the CS please uncomment the line below
|
||||
//#define USEFASTCLOCK
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
175
defines.h
175
defines.h
@@ -43,110 +43,119 @@
|
||||
#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
|
||||
|
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"
|
||||
|
@@ -20,7 +20,8 @@
|
||||
#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_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.
|
||||
@@ -87,6 +88,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 +156,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 +170,69 @@ 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);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// 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 -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
|
||||
|
||||
@@ -226,4 +230,5 @@ board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
lib_ignore =
|
||||
|
||||
|
37
version.h
37
version.h
@@ -4,7 +4,42 @@
|
||||
#include "StringFormatter.h"
|
||||
|
||||
|
||||
#define VERSION "4.2.9pre1"
|
||||
#define VERSION "4.2.18"
|
||||
// 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
|
||||
|
Reference in New Issue
Block a user