mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-03-15 18:43:06 +01:00
Compare commits
22 Commits
ffcbc6b70e
...
e9c33ce2a2
Author | SHA1 | Date | |
---|---|---|---|
|
e9c33ce2a2 | ||
|
14724aeb2a | ||
|
8f48e2ed94 | ||
|
6710c47f03 | ||
|
420d14567d | ||
|
953b8054f5 | ||
|
8081bfdf1e | ||
|
03bd1e897a | ||
|
d8f6d91408 | ||
|
dbb15c6aaa | ||
|
614802c756 | ||
|
5efe385f2e | ||
|
c50f3e016c | ||
|
535dcabcec | ||
|
1d18d5dea5 | ||
|
21c01ab69a | ||
|
fa00e9e11b | ||
|
f5014f5595 | ||
|
33c8ed19a9 | ||
|
0e99ad143b | ||
|
01533e2cd2 | ||
|
07ab7286ba |
95
CamParser.cpp
Normal file
95
CamParser.cpp
Normal file
@ -0,0 +1,95 @@
|
||||
|
||||
//sensorCAM parser.cpp version 3.03 Sep 2024
|
||||
#include "CamParser.h"
|
||||
#include "FSH.h"
|
||||
#include "IO_EXSensorCAM.h"
|
||||
|
||||
#ifndef SENSORCAM_VPIN //define CAM vpin (700?) in config.h
|
||||
#define SENSORCAM_VPIN 0
|
||||
#endif
|
||||
#define CAM_VPIN SENSORCAM_VPIN
|
||||
#ifndef SENSORCAM2_VPIN
|
||||
#define SENSORCAM2_VPIN CAM_VPIN
|
||||
#endif
|
||||
#ifndef SENSORCAM3_VPIN
|
||||
#define SENSORCAM3_VPIN 0
|
||||
#endif
|
||||
const int CAMVPINS[] = {CAM_VPIN,SENSORCAM_VPIN,SENSORCAM2_VPIN,SENSORCAM3_VPIN};
|
||||
const int16_t ver=30177;
|
||||
const int16_t ve =2899;
|
||||
|
||||
VPIN EXSensorCAM::CAMBaseVpin = CAM_VPIN;
|
||||
|
||||
bool CamParser::parseN(Print * stream, byte paramCount, int16_t p[]) {
|
||||
(void)stream; // probably unused parameter
|
||||
VPIN vpin=EXSensorCAM::CAMBaseVpin; //use current CAM selection
|
||||
|
||||
if (paramCount==0) {
|
||||
DIAG(F("vpin:%d EXSensorCAMs defined at Vpins #1@ %d #2@ %d #3@ %d"),vpin,CAMVPINS[1],CAMVPINS[2],CAMVPINS[3]);
|
||||
return true;
|
||||
}
|
||||
uint8_t camop=p[0]; // cam oprerator
|
||||
int param1=0;
|
||||
int16_t param3=9999; // =0 could invoke parameter changes. & -1 gives later errors
|
||||
|
||||
if(camop=='C'){
|
||||
if(p[1]>=100) EXSensorCAM::CAMBaseVpin=p[1];
|
||||
if(p[1]<4) EXSensorCAM::CAMBaseVpin=CAMVPINS[p[1]];
|
||||
DIAG(F("CAM base Vpin: %c %d "),p[0],EXSensorCAM::CAMBaseVpin);
|
||||
return true;
|
||||
}
|
||||
if (camop<100) { //switch CAM# if p[1] dictates
|
||||
if(p[1]>=100 && p[1]<400) { //limits to CAM# 1 to 3 for now
|
||||
vpin=CAMVPINS[p[1]/100];
|
||||
EXSensorCAM::CAMBaseVpin=vpin;
|
||||
DIAG(F("switching to CAM %d baseVpin:%d"),p[1]/100,vpin);
|
||||
p[1]=p[1]%100; //strip off CAM #
|
||||
}
|
||||
}
|
||||
if (EXSensorCAM::CAMBaseVpin==0) return false; // no cam defined
|
||||
|
||||
|
||||
// send UPPER case to sensorCAM to flag binary data from a DCCEX-CS parser
|
||||
switch(paramCount) {
|
||||
case 1: //<N ver> produces '^'
|
||||
if((p[0] == ve) || (p[0] == ver) || (p[0] == 'V')) camop='^';
|
||||
if (STRCHR_P((const char *)F("EFGMQRVW^"),camop) == nullptr) return false;
|
||||
if (camop=='Q') param3=10; //<NQ> for activation state of all 10 banks of sensors
|
||||
if (camop=='F') camop=']'; //<NF> for Reset/Finish webCAM.
|
||||
break; // F Coded as ']' else conflicts with <Nf %%>
|
||||
|
||||
case 2: //<N camop p1>
|
||||
if (STRCHR_P((const char *)F("ABFILMNOPQRSTUV"),camop)==nullptr) return false;
|
||||
param1=p[1];
|
||||
break;
|
||||
|
||||
case 3: //<N vpin rowY colx > or <N cmd p1 p2>
|
||||
camop=p[0];
|
||||
if (p[0]>=100) { //vpin - i.e. NOT 'A' through 'Z'
|
||||
if (p[1]>236 || p[1]<0) return false; //row
|
||||
if (p[2]>316 || p[2]<0) return false; //column
|
||||
camop=0x80; // special 'a' case for IO_SensorCAM
|
||||
vpin = p[0];
|
||||
}else if (STRCHR_P((const char *)F("IJMNT"),camop) == nullptr) return false;
|
||||
param1 = p[1];
|
||||
param3 = p[2];
|
||||
break;
|
||||
|
||||
case 4: //<N a id row col>
|
||||
if (camop!='A') return false; //must start with 'a'
|
||||
if (p[3]>316 || p[3]<0) return false;
|
||||
if (p[2]>236 || p[2]<0) return false;
|
||||
if (p[1]>97 || p[1]<0) return false; //treat as bsNo.
|
||||
vpin = vpin + (p[1]/10)*8 + p[1]%10; //translate p[1]
|
||||
camop=0x80; // special 'a' case for IO_SensorCAM
|
||||
param1=p[2]; // row
|
||||
param3=p[3]; // col
|
||||
break;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
DIAG(F("CamParser: %d %c %d %d"),vpin,camop,param1,param3);
|
||||
IODevice::writeAnalogue(vpin,param1,camop,param3);
|
||||
return true;
|
||||
}
|
12
CamParser.h
Normal file
12
CamParser.h
Normal file
@ -0,0 +1,12 @@
|
||||
#ifndef CamParser_H
|
||||
#define CamParser_H
|
||||
#include <Arduino.h>
|
||||
#include "IODevice.h"
|
||||
|
||||
class CamParser {
|
||||
public:
|
||||
static bool parseN(Print * stream, byte paramCount, int16_t p[]);
|
||||
};
|
||||
|
||||
|
||||
#endif
|
@ -377,4 +377,3 @@ void CommandDistributor::setVirtualLCDSerial(Print * stream) {
|
||||
Print* CommandDistributor::virtualLCDSerial=&USB_SERIAL;
|
||||
byte CommandDistributor::virtualLCDClient=0xFF;
|
||||
byte CommandDistributor::rememberVLCDClient=0;
|
||||
|
||||
|
@ -483,4 +483,3 @@ void DCCACK::checkAck(byte sentResetsSincePacket) {
|
||||
}
|
||||
ackPulseStart=0; // We have detected a too-short or too-long pulse so ignore and wait for next leading edge
|
||||
}
|
||||
|
||||
|
@ -117,6 +117,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
#include "Turntables.h"
|
||||
#include "version.h"
|
||||
#include "KeywordHasher.h"
|
||||
#include "CamParser.h"
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
#include "WifiESP32.h"
|
||||
#endif
|
||||
@ -457,7 +458,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
else IODevice::write(-p[0],LOW);
|
||||
return;
|
||||
}
|
||||
if (params>=2 && params<=4) { // <z vpin ana;og profile duration>
|
||||
if (params>=2 && params<=4) { // <z vpin analog profile duration>
|
||||
// unused params default to 0
|
||||
IODevice::writeAnalogue(p[0],p[1],p[2],p[3]);
|
||||
return;
|
||||
@ -577,7 +578,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
{
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
|
||||
TrackManager::setTrackPower(TRACK_ALL, POWERMODE::ON);
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]=="MAIN"_hk) { // <1 MAIN>
|
||||
@ -610,7 +611,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
|
||||
TrackManager::setTrackPower(TRACK_ALL, POWERMODE::OFF);
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]=="MAIN"_hk) { // <0 MAIN>
|
||||
@ -876,7 +877,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
return;
|
||||
break;
|
||||
#endif
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
case 'N': // <N commands for SensorCam
|
||||
if (CamParser::parseN(stream,params,p)) return;
|
||||
break;
|
||||
#endif
|
||||
case '/': // implemented in EXRAIL parser
|
||||
case 'L': // LCC interface implemented in EXRAIL parser
|
||||
break; // Will <X> if not intercepted by EXRAIL
|
||||
|
19
DCCRMT.cpp
19
DCCRMT.cpp
@ -17,6 +17,25 @@
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* RMT has "channels" which us FIFO RAM where you place what you want to send
|
||||
* or receive. Channels can be merged to get more words per channel.
|
||||
*
|
||||
* WROOM: 8 channels total of 512 words, 64 words per channel. We use currently
|
||||
* channel 0+1 for 128 words for DCC MAIN and 2+3 for DCC PROG.
|
||||
*
|
||||
* S3: 8 channels total of 384 words. 4 channels dedicated for TX and 4 channels
|
||||
* dedicated for RX. 48 words per channel. So for TX there are 4 channels and we
|
||||
* could use them with 96 words for MAIN and PROG if DCC data does fit in there.
|
||||
*
|
||||
* C3: 4 channels total of 192 words. As we do not use RX we can use all for TX
|
||||
* so the situation is the same as for the -S3
|
||||
*
|
||||
* C6, H2: 4 channels total of 192 words. 2 channels dedictaed for TX and
|
||||
* 2 channels dedicated for RX. Half RMT capacity compared to the C3.
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#include "defines.h"
|
||||
#include "DIAG.h"
|
||||
|
@ -197,6 +197,8 @@ void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t f) {
|
||||
}
|
||||
void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t fbits) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
(void)fbits;
|
||||
(void) pin;
|
||||
// Not worth doin something here as:
|
||||
// If we are on pin 9 or 10 we are on Timer1 and we can not touch Timer1 as that is our DCC source.
|
||||
// If we are on pin 5 or 6 we are on Timer 0 ad we can not touch Timer0 as that is millis() etc.
|
||||
|
@ -324,4 +324,3 @@ void ADCee::begin() {
|
||||
}
|
||||
|
||||
#endif //ESP32
|
||||
|
||||
|
@ -70,9 +70,9 @@ HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5 - F446RE
|
||||
defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||
HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5
|
||||
#if !defined(ARDUINO_NUCLEO_F412ZG)
|
||||
HardwareSerial Serial2(PD6, PD5); // Rx=PD6, Tx=PD5 -- UART5
|
||||
HardwareSerial Serial2(PD6, PD5); // Rx=PD6, Tx=PD5 -- UART2
|
||||
#if !defined(ARDUINO_NUCLEO_F412ZG) // F412ZG does not have UART5
|
||||
HardwareSerial Serial5(PD2, PC12); // Rx=PD2, Tx=PC12 -- UART5
|
||||
#endif
|
||||
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
|
||||
// via the debugger on the Nucleo-144. It is therefore unavailable for other DCC-EX uses like WiFi, DFPlayer, etc.
|
||||
@ -328,7 +328,7 @@ void DCCTimer::DCCEXanalogWriteFrequencyInternal(uint8_t pin, uint32_t frequency
|
||||
if (pin_timer[pin] != NULL)
|
||||
{
|
||||
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
|
||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d, frequency %d"), pin, pin_channel[pin], frequency);
|
||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer Channel %d, frequency %d"), pin, pin_channel[pin], frequency);
|
||||
}
|
||||
else
|
||||
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
|
||||
|
15
EXRAIL2.cpp
15
EXRAIL2.cpp
@ -312,7 +312,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
case OPCODE_EXTTTURNTABLE: {
|
||||
VPIN id=operand;
|
||||
VPIN pin=getOperand(progCounter,1);
|
||||
int home=getOperand(progCounter,3);
|
||||
int home=getOperand(progCounter,2);
|
||||
setTurntableHiddenState(EXTTTurntable::create(id,pin));
|
||||
Turntable *tto=Turntable::get(id);
|
||||
tto->addPosition(0,0,home);
|
||||
@ -677,13 +677,14 @@ void RMFT2::loop2() {
|
||||
break;
|
||||
|
||||
case OPCODE_SET:
|
||||
killBlinkOnVpin(operand);
|
||||
IODevice::write(operand,true);
|
||||
break;
|
||||
|
||||
case OPCODE_RESET:
|
||||
killBlinkOnVpin(operand);
|
||||
IODevice::write(operand,false);
|
||||
{
|
||||
auto count=getOperand(1);
|
||||
for (uint16_t i=0;i<count;i++) {
|
||||
killBlinkOnVpin(operand+i);
|
||||
IODevice::write(operand+i,opcode==OPCODE_SET);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_BLINK:
|
||||
|
@ -219,7 +219,7 @@
|
||||
#define CONFIGURE_SERVO(vpin,pos1,pos2,profile)
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DCCX_SIGNAL(id,redAspect,amberAspect,greenAspect)
|
||||
#define DCC_TURNTABLE(id,home,description)
|
||||
#define DCC_TURNTABLE(id,home,description...)
|
||||
#define DEACTIVATE(addr,subaddr)
|
||||
#define DEACTIVATEL(addr)
|
||||
#define DELAY(mindelay)
|
||||
@ -233,7 +233,7 @@
|
||||
#define ENDTASK
|
||||
#define ESTOP
|
||||
#define EXRAIL
|
||||
#define EXTT_TURNTABLE(id,vpin,home,description)
|
||||
#define EXTT_TURNTABLE(id,vpin,home,description...)
|
||||
#define FADE(pin,value,ms)
|
||||
#define FOFF(func)
|
||||
#define FOLLOW(route)
|
||||
@ -309,7 +309,7 @@
|
||||
#define READ_LOCO
|
||||
#define RED(signal_id)
|
||||
#define RESERVE(blockid)
|
||||
#define RESET(pin)
|
||||
#define RESET(pin,count...)
|
||||
#define RESUME
|
||||
#define RETURN
|
||||
#define REV(speed)
|
||||
@ -335,7 +335,7 @@
|
||||
#define SERVO2(id,position,duration)
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||
#define SET(pin)
|
||||
#define SET(pin,count...)
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SET_POWER(track,onoff)
|
||||
#define SETLOCO(loco)
|
||||
|
@ -363,4 +363,3 @@ bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -63,6 +63,10 @@
|
||||
// playing sounds with IO_I2CDFPlayer
|
||||
#define PLAYSOUND ANOUT
|
||||
|
||||
// SEG7 is a helper to create ANOUT from a 7-segment request
|
||||
#define SEG7(vpin,value,format) \
|
||||
ANOUT(vpin,(value & 0xFFFF),TM1638::DF_##format,((uint32_t)value)>>16)
|
||||
|
||||
// helper macro to strip leading zeros off time inputs
|
||||
// (10#mins)%100)
|
||||
#define STRIP_ZERO(value) 10##value%100
|
||||
@ -603,7 +607,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define READ_LOCO OPCODE_READ_LOCO1,0,0,OPCODE_READ_LOCO2,0,0,
|
||||
#define RED(signal_id) OPCODE_RED,V(signal_id),
|
||||
#define RESERVE(blockid) OPCODE_RESERVE,V(blockid),
|
||||
#define RESET(pin) OPCODE_RESET,V(pin),
|
||||
#define RESET(pin,count...) OPCODE_RESET,V(pin),OPCODE_PAD,V(#count[0] ? count+0: 1),
|
||||
#define RESUME OPCODE_RESUME,0,0,
|
||||
#define RETURN OPCODE_RETURN,0,0,
|
||||
#define REV(speed) OPCODE_REV,V(speed),
|
||||
@ -631,7 +635,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define SERVO2(id,position,ms) OPCODE_SERVO,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(PCA9685::Instant),OPCODE_PAD,V(ms/100L),
|
||||
#define SERVO_SIGNAL(vpin,redpos,amberpos,greenpos)
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...) OPCODE_SERVOTURNOUT,V(id),OPCODE_PAD,V(pin),OPCODE_PAD,V(activeAngle),OPCODE_PAD,V(inactiveAngle),OPCODE_PAD,V(PCA9685::ProfileType::profile),
|
||||
#define SET(pin) OPCODE_SET,V(pin),
|
||||
#define SET(pin,count...) OPCODE_SET,V(pin),OPCODE_PAD,V(#count[0] ? count+0: 1),
|
||||
#define SET_TRACK(track,mode) OPCODE_SET_TRACK,V(TRACK_MODE_##mode <<8 | TRACK_NUMBER_##track),
|
||||
#define SET_POWER(track,onoff) OPCODE_SET_POWER,V(TRACK_POWER_##onoff),OPCODE_PAD, V(TRACK_NUMBER_##track),
|
||||
#define SETLOCO(loco) OPCODE_SETLOCO,V(loco),
|
||||
|
@ -47,4 +47,4 @@ class EXRAILSensor {
|
||||
bool onChange;
|
||||
byte latchDelay;
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
@ -1,8 +1,10 @@
|
||||
/*
|
||||
* © 2024 Morten "Doc" Nielsen
|
||||
* © 2023-2024 Paul M. Antoine
|
||||
* © 2022 Bruno Sanches
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020-2024 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* All rights reserved.
|
||||
*
|
||||
@ -36,9 +38,14 @@
|
||||
MDNS mdns(udp);
|
||||
#endif
|
||||
|
||||
|
||||
//extern void looptimer(unsigned long timeout, const FSH* message);
|
||||
#define looptimer(a,b)
|
||||
|
||||
bool EthernetInterface::connected=false;
|
||||
EthernetServer * EthernetInterface::server= nullptr;
|
||||
EthernetClient EthernetInterface::clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
bool EthernetInterface::inUse[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
uint8_t EthernetInterface::buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
|
||||
RingStream * EthernetInterface::outboundRing = nullptr;
|
||||
|
||||
@ -47,12 +54,13 @@ RingStream * EthernetInterface::outboundRing = nullptr;
|
||||
*
|
||||
*/
|
||||
|
||||
void EthernetInterface::setup() // STM32 VERSION
|
||||
void EthernetInterface::setup()
|
||||
{
|
||||
DIAG(F("Ethernet begin"
|
||||
DIAG(F("Ethernet starting"
|
||||
#ifdef DO_MDNS
|
||||
" with mDNS"
|
||||
" (with mDNS)"
|
||||
#endif
|
||||
" Please be patient, especially if no cable is connected!"
|
||||
));
|
||||
|
||||
#ifdef STM32_ETHERNET
|
||||
@ -116,6 +124,47 @@ void EthernetInterface::setup() // STM32 VERSION
|
||||
connected=true;
|
||||
}
|
||||
|
||||
#if defined (STM32_ETHERNET)
|
||||
void EthernetInterface::acceptClient() { // STM32 version
|
||||
auto client=server->available();
|
||||
if (!client) return;
|
||||
// check for existing client
|
||||
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||
if (inUse[socket] && client == clients[socket]) return;
|
||||
|
||||
// new client
|
||||
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||
{
|
||||
if (!inUse[socket])
|
||||
{
|
||||
clients[socket] = client;
|
||||
inUse[socket]=true;
|
||||
if (Diag::ETHERNET)
|
||||
DIAG(F("Ethernet: New client socket %d"), socket);
|
||||
return;
|
||||
}
|
||||
}
|
||||
DIAG(F("Ethernet OVERFLOW"));
|
||||
}
|
||||
#else
|
||||
void EthernetInterface::acceptClient() { // non-STM32 version
|
||||
auto client=server->accept();
|
||||
if (!client) return;
|
||||
auto socket=client.getSocketNumber();
|
||||
clients[socket]=client;
|
||||
inUse[socket]=true;
|
||||
if (Diag::ETHERNET)
|
||||
DIAG(F("Ethernet: New client socket %d"), socket);
|
||||
}
|
||||
#endif
|
||||
|
||||
void EthernetInterface::dropClient(byte socket)
|
||||
{
|
||||
clients[socket].stop();
|
||||
inUse[socket]=false;
|
||||
CommandDistributor::forget(socket);
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: Disconnect %d "), socket);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Main loop for the EthernetInterface
|
||||
@ -124,7 +173,8 @@ void EthernetInterface::setup() // STM32 VERSION
|
||||
void EthernetInterface::loop()
|
||||
{
|
||||
if (!connected) return;
|
||||
|
||||
looptimer(5000, F("E.loop"));
|
||||
|
||||
static bool warnedAboutLink=false;
|
||||
if (Ethernet.linkStatus() == LinkOFF){
|
||||
if (warnedAboutLink) return;
|
||||
@ -132,7 +182,8 @@ void EthernetInterface::loop()
|
||||
warnedAboutLink=true;
|
||||
return;
|
||||
}
|
||||
|
||||
looptimer(5000, F("E.loop warn"));
|
||||
|
||||
// link status must be ok here
|
||||
if (warnedAboutLink) {
|
||||
DIAG(F("Ethernet link RESTORED"));
|
||||
@ -142,6 +193,8 @@ void EthernetInterface::loop()
|
||||
#ifdef DO_MDNS
|
||||
// Always do this because we don't want traffic to intefere with being found!
|
||||
mdns.run();
|
||||
looptimer(5000, F("E.mdns"));
|
||||
|
||||
#endif
|
||||
|
||||
//
|
||||
@ -161,50 +214,22 @@ void EthernetInterface::loop()
|
||||
//DIAG(F("maintained"));
|
||||
break;
|
||||
}
|
||||
looptimer(5000, F("E.maintain"));
|
||||
|
||||
// get client from the server
|
||||
acceptClient();
|
||||
|
||||
// get client from the server
|
||||
#if defined (STM32_ETHERNET)
|
||||
// STM32Ethernet doesn't use accept(), just available()
|
||||
auto client = server->available();
|
||||
if (client) {
|
||||
// check for new client
|
||||
byte socket;
|
||||
bool sockfound = false;
|
||||
for (socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||
{
|
||||
if (client == clients[socket])
|
||||
{
|
||||
sockfound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!sockfound)
|
||||
{ // new client
|
||||
for (socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||
{
|
||||
if (!clients[socket])
|
||||
{
|
||||
clients[socket] = client;
|
||||
sockfound=true;
|
||||
if (Diag::ETHERNET)
|
||||
DIAG(F("Ethernet: New client socket %d"), socket);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!sockfound) DIAG(F("new Ethernet OVERFLOW"));
|
||||
}
|
||||
|
||||
#else
|
||||
auto client = server->accept();
|
||||
if (client) clients[client.getSocketNumber()]=client;
|
||||
#endif
|
||||
|
||||
// handle disconnected sockets because STM32 library doesnt
|
||||
// do the read==0 response.
|
||||
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||
{
|
||||
if (inUse[socket] && !clients[socket].connected()) dropClient(socket);
|
||||
}
|
||||
|
||||
// check for incoming data from all possible clients
|
||||
for (byte socket = 0; socket < MAX_SOCK_NUM; socket++)
|
||||
{
|
||||
if (!clients[socket]) continue; // socket is not in use
|
||||
if (!inUse[socket]) continue; // socket is not in use
|
||||
|
||||
// read any bytes from this client
|
||||
auto count = clients[socket].read(buffer, MAX_ETH_BUFFER);
|
||||
@ -216,14 +241,13 @@ void EthernetInterface::loop()
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet s=%d, c=%d b=:%e"), socket, count, buffer);
|
||||
// execute with data going directly back
|
||||
CommandDistributor::parse(socket,buffer,outboundRing);
|
||||
//looptimer(5000, F("Ethloop2 parse"));
|
||||
return; // limit the amount of processing that takes place within 1 loop() cycle.
|
||||
}
|
||||
|
||||
// count=0 The client has disconnected
|
||||
clients[socket].stop();
|
||||
CommandDistributor::forget(socket);
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet: disconnect %d "), socket);
|
||||
}
|
||||
dropClient(socket);
|
||||
}
|
||||
|
||||
WiThrottle::loop(outboundRing);
|
||||
|
||||
@ -245,9 +269,11 @@ void EthernetInterface::loop()
|
||||
tmpbuf[i] = outboundRing->read();
|
||||
}
|
||||
tmpbuf[count]=0;
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet reply s=%d, c=%d, b:%e"),
|
||||
if (inUse[socketOut]) {
|
||||
if (Diag::ETHERNET) DIAG(F("Ethernet reply s=%d, c=%d, b:%e"),
|
||||
socketOut,count,tmpbuf);
|
||||
clients[socketOut].write(tmpbuf,count);
|
||||
clients[socketOut].write(tmpbuf,count);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
/*
|
||||
* © 2023-2024 Paul M. Antoine
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2020-2022 Harald Barth
|
||||
* © 2020-2024 Chris Harlow
|
||||
* © 2020 Gregor Baues
|
||||
* All rights reserved.
|
||||
*
|
||||
@ -35,6 +37,7 @@
|
||||
#if defined (ARDUINO_TEENSY41)
|
||||
#include <NativeEthernet.h> //TEENSY Ethernet Treiber
|
||||
#include <NativeEthernetUdp.h>
|
||||
#define MAX_SOCK_NUM 4
|
||||
#elif defined (ARDUINO_NUCLEO_F429ZI) || defined (ARDUINO_NUCLEO_F439ZI) || defined (ARDUINO_NUCLEO_F4X9ZI)
|
||||
#include <LwIP.h>
|
||||
// #include "STM32lwipopts.h"
|
||||
@ -67,8 +70,12 @@ class EthernetInterface {
|
||||
static bool connected;
|
||||
static EthernetServer * server;
|
||||
static EthernetClient clients[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
static bool inUse[MAX_SOCK_NUM]; // accept up to MAX_SOCK_NUM client connections at the same time; This depends on the chipset used on the Shield
|
||||
static uint8_t buffer[MAX_ETH_BUFFER+1]; // buffer used by TCP for the recv
|
||||
static RingStream * outboundRing;
|
||||
static void acceptClient();
|
||||
static void dropClient(byte socketnum);
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202409221903Z"
|
||||
#define GITHUB_SHA "devel-202411091200Z"
|
||||
|
@ -384,4 +384,4 @@ void I2CManagerClass::handleInterrupt() {
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022-23 Paul M Antoine
|
||||
* © 2022-24 Paul M Antoine
|
||||
* © 2023, Neil McKechnie
|
||||
* All rights reserved.
|
||||
*
|
||||
@ -38,8 +38,9 @@
|
||||
*****************************************************************************/
|
||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
|
||||
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) \
|
||||
|| defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) \
|
||||
|| defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
|| defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F446ZE) \
|
||||
|| defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI)
|
||||
|
||||
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely all Nucleo-64
|
||||
// and Nucleo-144 variants
|
||||
I2C_TypeDef *s = I2C1;
|
||||
@ -184,7 +185,7 @@ void I2CManagerClass::I2C_init()
|
||||
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
|
||||
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
|
||||
GPIOB->PUPDR &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all PUPDR bits for PB8 and PB9
|
||||
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
|
||||
// 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
|
||||
|
@ -231,4 +231,4 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::loop() {}
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -627,4 +627,3 @@ bool ArduinoPins::fastReadDigital(uint8_t pin) {
|
||||
#endif
|
||||
return result;
|
||||
}
|
||||
|
||||
|
@ -570,6 +570,7 @@ protected:
|
||||
#include "IO_EncoderThrottle.h"
|
||||
#include "IO_TCA8418.h"
|
||||
#include "IO_NeoPixel.h"
|
||||
|
||||
#include "IO_TM1638.h"
|
||||
#include "IO_EXSensorCAM.h"
|
||||
|
||||
#endif // iodevice_h
|
||||
|
@ -166,4 +166,4 @@ private:
|
||||
uint8_t _nextState;
|
||||
};
|
||||
|
||||
#endif // io_analogueinputs_h
|
||||
#endif // io_analogueinputs_h
|
||||
|
@ -65,4 +65,3 @@ void DCCAccessoryDecoder::_display() {
|
||||
DIAG(F("DCCAccessoryDecoder Configured on Vpins:%u-%u Addresses %d/%d-%d/%d)"), _firstVpin, _firstVpin+_nPins-1,
|
||||
ADDRESS(_packedAddress), SUBADDRESS(_packedAddress), ADDRESS(endAddress), SUBADDRESS(endAddress));
|
||||
}
|
||||
|
||||
|
425
IO_EXSensorCAM.h
Normal file
425
IO_EXSensorCAM.h
Normal file
@ -0,0 +1,425 @@
|
||||
/* 2024/08/14
|
||||
* © 2024, Barry Daniel ESP32-CAM revision
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
#define driverVer 305
|
||||
// v305 less debug & alpha ordered switch
|
||||
// v304 static oldb0; t(##[,%%];
|
||||
// v303 zipped with CS 5.2.76 and uploaded to repo (with debug)
|
||||
// v302 SEND=StringFormatter::send, remove Sp(), add 'q', memcpy( .8) -> .7);
|
||||
// v301 improved 'f','p'&'q' code and driver version calc. Correct bsNo calc. for 'a'
|
||||
// v300 stripped & revised without expander functionality. Needs sensorCAM.h v300 AND CamParser.cpp
|
||||
// v222 uses '@'for EXIORDD read. handles <NB $> and <NN $ ##>
|
||||
// v216 includes 'j' command and uses CamParser rather than myFilter.h Incompatible with v203 senorCAM
|
||||
// v203 added pvtThreshold to 'i' output
|
||||
// v201 deleted code for compatibility with CAM pre v171. Needs CAM ver201 with o06 only
|
||||
// v200 rewrite reduces need for double reads of ESP32 slave CAM. Deleted ESP32CAP.
|
||||
// Inompatible with pre-v170 sensorCAM, unless set S06 to 0 and S07 to 1 (o06 & l07 say)
|
||||
/*
|
||||
* The IO_EXSensorCAM.h device driver can integrate with the sensorCAM device.
|
||||
* It is modelled on the IO_EXIOExpander.h device driver to include specific needs of the ESP32 sensorCAM
|
||||
* This device driver will configure the device on startup, along with CamParser.cpp
|
||||
* interacting with the sensorCAM device for all input/output duties.
|
||||
*
|
||||
* #include "CamParser.h" in DCCEXParser.cpp
|
||||
* #include "IO_EXSensorCAM.h" in IODevice.h
|
||||
* To create EX-SensorCAM devices, define them in myHal.cpp: with
|
||||
* EXSensorCAM::create(baseVpin,num_vpins,i2c_address) or
|
||||
* alternatively use HAL(EXSensorCAM baseVpin numpins i2c_address) in myAutomation.h
|
||||
* also #define SENSORCAM_VPIN baseVpin in config.h
|
||||
*
|
||||
* void halSetup() {
|
||||
* // EXSensorCAM::create(vpin, num_vpins, i2c_address);
|
||||
* EXSensorCAM::create(700, 80, 0x11);
|
||||
* }
|
||||
*
|
||||
* I2C packet size of 32 bytes (in the Wire library).
|
||||
*/
|
||||
# define DIGITALREFRESH 20000UL // min uSec delay between digital reads of digitalInputStates
|
||||
#ifndef IO_EX_EXSENSORCAM_H
|
||||
#define IO_EX_EXSENSORCAM_H
|
||||
#define SEND StringFormatter::send
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
#include "CamParser.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for EX-SensorCAM.
|
||||
*/
|
||||
class EXSensorCAM : public IODevice {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, I2CAddress i2cAddress) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress))
|
||||
new EXSensorCAM(vpin, nPins, i2cAddress);
|
||||
}
|
||||
|
||||
static VPIN CAMBaseVpin;
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
EXSensorCAM(VPIN firstVpin, int nPins, I2CAddress i2cAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
// Number of pins cannot exceed 255 (1 byte) because of I2C message structure.
|
||||
if (nPins > 80) nPins = 80;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
addDevice(this);
|
||||
}
|
||||
//*************************
|
||||
void _begin() {
|
||||
uint8_t status;
|
||||
// Initialise EX-SensorCAM device
|
||||
I2CManager.begin();
|
||||
if (!I2CManager.exists(_I2CAddress)) {
|
||||
DIAG(F("EX-SensorCAM I2C:%s device not found"), _I2CAddress.toString());
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
return;
|
||||
}else {
|
||||
uint8_t commandBuffer[4]={EXIOINIT,(uint8_t)_nPins,(uint8_t)(_firstVpin & 0xFF),(uint8_t)(_firstVpin>>8)};
|
||||
status = I2CManager.read(_I2CAddress,_inputBuf,sizeof(_inputBuf),commandBuffer,sizeof(commandBuffer));
|
||||
//EXIOINIT needed to trigger and send firstVpin to CAM
|
||||
|
||||
if (status == I2C_STATUS_OK) {
|
||||
// Attempt to get version, non-blocking results in poor placement of response. Can be blocking here!
|
||||
commandBuffer[0] = '^'; //new version code
|
||||
|
||||
status = I2CManager.read(_I2CAddress, _inputBuf, sizeof(_inputBuf), commandBuffer, 1);
|
||||
// for ESP32 CAM, read again for good immediate response version data
|
||||
status = I2CManager.read(_I2CAddress, _inputBuf, sizeof(_inputBuf), commandBuffer, 1);
|
||||
|
||||
if (status == I2C_STATUS_OK) {
|
||||
_majorVer= _inputBuf[1]/10;
|
||||
_minorVer= _inputBuf[1]%10;
|
||||
_patchVer= _inputBuf[2];
|
||||
DIAG(F("EX-SensorCAM device found, I2C:%s, Version v%d.%d.%d"),
|
||||
_I2CAddress.toString(),_majorVer, _minorVer,_patchVer);
|
||||
}
|
||||
}
|
||||
if (status != I2C_STATUS_OK)
|
||||
reportError(status);
|
||||
}
|
||||
}
|
||||
//*************************
|
||||
// Digital input pin configuration, used to enable on EX-IOExpander device and set pullups if requested.
|
||||
// Configuration isn't done frequently so we can use blocking I2C calls here, and so buffers can
|
||||
// be allocated from the stack to reduce RAM allocation.
|
||||
bool _configure(VPIN vpin, ConfigTypeEnum configType, int paramCount, int params[]) override {
|
||||
(void)configType; (void)params; // unused
|
||||
if(_verPrint) DIAG(F("_configure() driver IO_EXSensorCAM v0.%d.%d vpin: %d "), driverVer/100,driverVer%100,vpin);
|
||||
_verPrint=false; //only give driver versions once
|
||||
if (paramCount != 1) return false;
|
||||
return true; //at least confirm that CAM is (always) configured (no vpin check!)
|
||||
}
|
||||
//*************************
|
||||
// Analogue input pin configuration, used to enable an EX-IOExpander device.
|
||||
int _configureAnalogIn(VPIN vpin) override {
|
||||
DIAG(F("_configureAnalogIn() IO_EXSensorCAM vpin %d"),vpin);
|
||||
return true; // NOTE: use of EXRAIL IFGTE() etc use "analog" reads.
|
||||
}
|
||||
//*************************
|
||||
// Main loop, collect both digital and "analog" pin states continuously (faster sensor/input reads)
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
// Request block is used for "analogue" (cmd. data) and digital reads from the sensorCAM, which
|
||||
// are performed on a cyclic basis. Writes are performed synchronously as and when requested.
|
||||
if (_readState != RDS_IDLE) { //expecting a return packet
|
||||
if (_i2crb.isBusy()) return; // If I2C operation still in progress, return
|
||||
uint8_t status = _i2crb.status;
|
||||
if (status == I2C_STATUS_OK) { // If device request ok, read input data
|
||||
//apparently the above checks do not guarantee a good packet! error rate about 1 pkt per 1000
|
||||
//there should be a packet in _CAMresponseBuff[32]
|
||||
if ((_CAMresponseBuff[0] & 0x60) >= 0x60) { //Buff[0] seems to have ascii cmd header (bit6 high) (o06)
|
||||
int error = processIncomingPkt( _CAMresponseBuff, _CAMresponseBuff[0]); // '~' 'i' 'm' 'n' 't' etc
|
||||
if (error>0) DIAG(F("CAM packet header(0x%x) not recognised"),_CAMresponseBuff[0]);
|
||||
}else{ // Header not valid - typically replaced by bank 0 data! To avoid any bad responses set S06 to 0
|
||||
// Versions of sensorCAM.h after v300 should return header for '@' of '`'(0x60) (not 0xE6)
|
||||
// followed by digitalInputStates sensor state array
|
||||
}
|
||||
}else reportError(status, false); // report i2c eror but don't go offline.
|
||||
_readState = RDS_IDLE;
|
||||
}
|
||||
|
||||
// If we're not doing anything now, check to see if a new state table transfer, or for 't' repeat, is due.
|
||||
if (_readState == RDS_IDLE) { //check if time for digitalRefresh
|
||||
if ( currentMicros - _lastDigitalRead > _digitalRefresh) {
|
||||
// Issue new read request for digital states.
|
||||
|
||||
_readCommandBuffer[0] = '@'; //start new read of digitalInputStates Table // non-blocking read
|
||||
I2CManager.read(_I2CAddress,_CAMresponseBuff, 32,_readCommandBuffer, 1, &_i2crb);
|
||||
_lastDigitalRead = currentMicros;
|
||||
_readState = RDS_DIGITAL;
|
||||
|
||||
}else{ //slip in a repeat <NT n> if pending
|
||||
if (currentMicros - _lasttStateRead > _tStateRefresh) // Delay for "analog" command repetitions
|
||||
if (_savedCmd[2]>1) { //repeat a 't' command
|
||||
for (int i=0;i<7;i++) _readCommandBuffer[i] =_savedCmd[i];
|
||||
int errors = ioESP32(_I2CAddress, _CAMresponseBuff, 32, _readCommandBuffer, 7);
|
||||
_lasttStateRead = currentMicros;
|
||||
_savedCmd[2] -= 1; //decrement repeats
|
||||
if (errors==0) return;
|
||||
DIAG(F("ioESP32 error %d header 0x%x"),errors,_CAMresponseBuff[0]);
|
||||
_readState = RDS_TSTATE; //this should stop further cmd requests until packet read (or timeout)
|
||||
}
|
||||
} //end repeat 't'
|
||||
}
|
||||
}
|
||||
//*************************
|
||||
// Obtain the bank of 8 sensors as an "analog" value
|
||||
// can be used to track the position through a sequential sensor bank
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
return _digitalInputStates[(vpin - _firstVpin) / 8];
|
||||
}
|
||||
//*************************
|
||||
// Obtain the correct digital sensor input value
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
int pin = vpin - _firstVpin;
|
||||
return bitRead(_digitalInputStates[pin / 8], pin % 8);
|
||||
}
|
||||
//*************************
|
||||
// Write digital value.
|
||||
void _write(VPIN vpin, int value) override {
|
||||
DIAG(F("**_write() vpin %d = %d"),vpin,value);
|
||||
return ;
|
||||
}
|
||||
//*************************
|
||||
// i2cAddr of ESP32 CAM
|
||||
// rBuf buffer for return packet
|
||||
// inbytes number of bytes to request from CAM
|
||||
// outBuff holds outbytes to be sent to CAM
|
||||
int ioESP32(uint8_t i2cAddr,uint8_t *rBuf,int inbytes,uint8_t *outBuff,int outbytes) {
|
||||
uint8_t status = _i2crb.status;
|
||||
|
||||
while( _i2crb.status != I2C_STATUS_OK){status = _i2crb.status;} //wait until bus free
|
||||
|
||||
status = I2CManager.read(i2cAddr, rBuf, inbytes, outBuff, outbytes);
|
||||
|
||||
if (status != I2C_STATUS_OK){
|
||||
DIAG(F("EX-SensorCAM I2C:%s Error:%d %S"), _I2CAddress.toString(), status, I2CManager.getErrorMessage(status));
|
||||
reportError(status); return status;
|
||||
}
|
||||
return 0; // 0 for no error != 0 for error number.
|
||||
}
|
||||
//*************************
|
||||
//function to interpret packet from sensorCAM.ino
|
||||
//i2cAddr to identify CAM# (if # >1)
|
||||
//rBuf contains packet of up to 32 bytes usually with (ascii) cmd header in rBuf[0]
|
||||
//sensorCmd command header byte from CAM (in rBuf[0]?)
|
||||
int processIncomingPkt(uint8_t *rBuf,uint8_t sensorCmd) {
|
||||
//static uint8_t oldb0; //for debug only
|
||||
int k;
|
||||
int b;
|
||||
char str[] = "11111111";
|
||||
// if (sensorCmd <= '~') DIAG(F("processIncomingPkt %c %d %d %d"),rBuf[0],rBuf[1],rBuf[2],rBuf[3]);
|
||||
switch (sensorCmd){
|
||||
case '`': //response to request for digitalInputStates[] table '@'=>'`'
|
||||
memcpy(_digitalInputStates, rBuf+1, digitalBytesNeeded);
|
||||
// if ( _digitalInputStates[0]!=oldb0) { oldb0=_digitalInputStates[0]; //debug
|
||||
// for (k=0;k<5;k++) {Serial.print(" ");Serial.print(_digitalInputStates[k],HEX);}
|
||||
// }
|
||||
break;
|
||||
|
||||
case EXIORDY: //some commands give back acknowledgement only
|
||||
break;
|
||||
|
||||
case CAMERR: //cmd format error code from CAM
|
||||
DIAG(F("CAM cmd error 0xFE 0x%x"),rBuf[1]);
|
||||
break;
|
||||
|
||||
case '~': //information from '^' version request <N v[er]>
|
||||
DIAG(F("EX-SensorCAM device found, I2C:%s,CAM Version v%d.%d.%d vpins %u-%u"),
|
||||
_I2CAddress.toString(), rBuf[1]/10, rBuf[1]%10, rBuf[2],(int) _firstVpin, (int) _firstVpin +_nPins-1);
|
||||
DIAG(F("IO_EXSensorCAM driver v0.%d.%d vpin: %d "), driverVer/100,driverVer%100,_firstVpin);
|
||||
break;
|
||||
|
||||
case 'f':
|
||||
DIAG(F("(f %%%%) frame header 'f' for bsNo %d/%d - showing Quarter sample (1 row) only"), rBuf[1]/8,rBuf[1]%8);
|
||||
SEND(&USB_SERIAL,F("<n row: %d Ref bytes: "),rBuf[2]);
|
||||
for(k=3;k<15;k++)
|
||||
SEND(&USB_SERIAL,F("%x%x%s"), rBuf[k]>>4, rBuf[k]&15, k%3==2 ? " " : " ");
|
||||
Serial.print(" latest grab: ");
|
||||
for(k=16;k<28;k++)
|
||||
SEND(&USB_SERIAL,F("%x%x%s"), rBuf[k]>>4, rBuf[k]&15, (k%3==0) ? " " : " ");
|
||||
Serial.print(" n>\n");
|
||||
break;
|
||||
|
||||
case 'i': //information from i%%
|
||||
k=256*rBuf[5]+rBuf[4];
|
||||
DIAG(F("(i%%%%[,$$]) Info: Sensor 0%o(%d) enabled:%d status:%d row=%d x=%d Twin=0%o pvtThreshold=%d A~%d")
|
||||
,rBuf[1],rBuf[1],rBuf[3],rBuf[2],rBuf[6],k,rBuf[7],rBuf[9],int(rBuf[8])*16);
|
||||
break;
|
||||
|
||||
case 'm':
|
||||
DIAG(F("(m$[,##]) Min/max: $ frames min2flip (trip) %d, maxSensors 0%o, minSensors 0%o, nLED %d,"
|
||||
" threshold %d, TWOIMAGE_MAXBS 0%o"),rBuf[1],rBuf[3],rBuf[2],rBuf[4],rBuf[5],rBuf[6]);
|
||||
break;
|
||||
|
||||
case 'n':
|
||||
DIAG(F("(n$[,##]) Nominate: $ nLED %d, ## minSensors 0%o (maxSensors 0%o threshold %d)")
|
||||
,rBuf[4],rBuf[2],rBuf[3],rBuf[5]);
|
||||
break;
|
||||
|
||||
case 'p':
|
||||
b=rBuf[1]-2;
|
||||
if(b<4) { Serial.print("<n (p%%) Bank empty n>\n"); break; }
|
||||
SEND(&USB_SERIAL,F("<n (p%%) Bank: %d "),(0x7F&rBuf[2])/8);
|
||||
for (int j=2; j<b; j+=3)
|
||||
SEND(&USB_SERIAL,F(" S[%d%d]: r=%d x=%d"),0x7F&rBuf[j]/8,0x7F&rBuf[j]%8,rBuf[j+1],rBuf[j+2]+2*(rBuf[j]&0x80));
|
||||
Serial.print(" n>\n");
|
||||
break;
|
||||
|
||||
case 'q':
|
||||
for (int i =0; i<8; i++) str[i] = ((rBuf[2] << i) & 0x80 ? '1' : '0');
|
||||
DIAG(F("(q $) Query bank %c ENABLED sensors(S%c7-%c0): %s "), rBuf[1], rBuf[1], rBuf[1], str);
|
||||
break;
|
||||
|
||||
case 't': //threshold etc. from t## //bad pkt if 't' FF's
|
||||
if(rBuf[1]==0xFF) {Serial.println("<n bad CAM 't' packet: 74 FF n>");_savedCmd[2] +=1; return 0;}
|
||||
SEND(&USB_SERIAL,F("<n (t[##[,%%%%]]) Threshold:%d sensor S00:-%d"),rBuf[1],min(rBuf[2]&0x7F,99));
|
||||
if(rBuf[2]>127) Serial.print("##* ");
|
||||
else{
|
||||
if(rBuf[2]>rBuf[1]) Serial.print("-?* ");
|
||||
else Serial.print("--* ");
|
||||
}
|
||||
for(int i=3;i<31;i+=2){
|
||||
uint8_t valu=rBuf[i]; //get bsn
|
||||
if(valu==80) break; //80 = end flag
|
||||
else{
|
||||
SEND(&USB_SERIAL,F("%d%d:"), (valu&0x7F)/8,(valu&0x7F)%8);
|
||||
if(valu>=128) Serial.print("?-");
|
||||
else {if(rBuf[i+1]>=128) Serial.print("oo");else Serial.print("--");}
|
||||
valu=rBuf[i+1];
|
||||
SEND(&USB_SERIAL,F("%d%s"),min(valu&0x7F,99),(valu<128) ? "--* ":"##* ");
|
||||
}
|
||||
}
|
||||
Serial.print(" >\n");
|
||||
break;
|
||||
|
||||
default: //header not a recognised cmd character
|
||||
DIAG(F("CAM packet header not valid (0x%x) (0x%x) (0x%x)"),rBuf[0],rBuf[1],rBuf[2]);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
//*************************
|
||||
// Write (analogue) 8bit (command) values. Write the parameters to the sensorCAM
|
||||
void _writeAnalogue(VPIN vpin, int param1, uint8_t camop, uint16_t param3) override {
|
||||
uint8_t outputBuffer[7];
|
||||
int errors=0;
|
||||
outputBuffer[0] = camop;
|
||||
int pin = vpin - _firstVpin;
|
||||
|
||||
if(camop >= 0x80) { //case "a" (4p) also (3p) e.g. <N 713 210 310>
|
||||
camop=param1; //put row (0-236) in expected place
|
||||
param1=param3; //put column in expected place
|
||||
outputBuffer[0] = 'A';
|
||||
pin = (pin/8)*10 + pin%8; //restore bsNo. as integer
|
||||
}
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
|
||||
outputBuffer[1] = pin; //vpin => bsn
|
||||
outputBuffer[2] = param1 & 0xFF;
|
||||
outputBuffer[3] = param1 >> 8;
|
||||
outputBuffer[4] = camop; //command code
|
||||
outputBuffer[5] = param3 & 0xFF;
|
||||
outputBuffer[6] = param3 >> 8;
|
||||
|
||||
int count=param1+1;
|
||||
if(camop=='Q'){
|
||||
if(param3<=10) {count=param3; camop='B';}
|
||||
//if(param1<10) outputBuffer[2] = param1*10;
|
||||
}
|
||||
if(camop=='B'){ //then 'b'(b%) cmd - can totally deal with that here. (but can't do b%,# (brightSF))
|
||||
if(param1>97) return;
|
||||
if(param1>9) param1 = param1/10; //accept a bsNo
|
||||
for(int bnk=param1;bnk<count;bnk++) {
|
||||
uint8_t b=_digitalInputStates[bnk];
|
||||
char str[] = "11111111";
|
||||
for (int i=0;i<8;i++) if(((b<<i)&0x80) == 0) str[i]='0';
|
||||
DIAG(F("(b $) Bank: %d activated byte: 0x%x%x (sensors S%d7->%d0) %s"), bnk,b>>4,b&15,bnk,bnk,str );
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (outputBuffer[4]=='T') { //then 't' cmd
|
||||
if(param1<31) { //repeated calls if param < 31
|
||||
//for (int i=0;i<7;i++) _savedCmd[i]=outputBuffer[i];
|
||||
memcpy( _savedCmd, outputBuffer, 7);
|
||||
}else _savedCmd[2] = 0; //no repeats if ##>30
|
||||
}else _savedCmd[2] = 0; //no repeats unless 't'
|
||||
|
||||
_lasttStateRead = micros(); //don't repeat until _tStateRefresh mSec
|
||||
|
||||
errors = ioESP32(_I2CAddress, _CAMresponseBuff, 32 , outputBuffer, 7); //send to esp32-CAM
|
||||
if (errors==0) return;
|
||||
else { // if (_CAMresponseBuff[0] != EXIORDY) //can't be sure what is inBuff[0] !
|
||||
DIAG(F("ioESP32 i2c error %d header 0x%x"),errors,_CAMresponseBuff[0]);
|
||||
}
|
||||
}
|
||||
//*************************
|
||||
// Display device information and status.
|
||||
void _display() override {
|
||||
DIAG(F("EX-SensorCAM I2C:%s v%d.%d.%d Vpins %u-%u %S"),
|
||||
_I2CAddress.toString(), _majorVer, _minorVer, _patchVer,
|
||||
(int)_firstVpin, (int)_firstVpin+_nPins-1,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
//*************************
|
||||
// Helper function for error handling
|
||||
void reportError(uint8_t status, bool fail=true) {
|
||||
DIAG(F("EX-SensorCAM I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
|
||||
status, I2CManager.getErrorMessage(status));
|
||||
if (fail) _deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
//*************************
|
||||
uint8_t _numDigitalPins = 80;
|
||||
size_t digitalBytesNeeded=10;
|
||||
uint8_t _CAMresponseBuff[34];
|
||||
|
||||
uint8_t _majorVer = 0;
|
||||
uint8_t _minorVer = 0;
|
||||
uint8_t _patchVer = 0;
|
||||
|
||||
uint8_t _digitalInputStates[10];
|
||||
I2CRB _i2crb;
|
||||
uint8_t _inputBuf[12];
|
||||
byte _outputBuffer[8];
|
||||
|
||||
bool _verPrint=true;
|
||||
|
||||
uint8_t _readCommandBuffer[8];
|
||||
uint8_t _savedCmd[8]; //for repeat 't' command
|
||||
//uint8_t _digitalPinBytes = 10; // Size of allocated memory buffer (may be longer than needed)
|
||||
|
||||
enum {RDS_IDLE, RDS_DIGITAL, RDS_TSTATE}; // Read operation states
|
||||
uint8_t _readState = RDS_IDLE;
|
||||
//uint8_t cmdBuffer[7]={0,0,0,0,0,0,0};
|
||||
unsigned long _lastDigitalRead = 0;
|
||||
unsigned long _lasttStateRead = 0;
|
||||
unsigned long _digitalRefresh = DIGITALREFRESH; // Delay refreshing digital inputs for 10ms
|
||||
const unsigned long _tStateRefresh = 120000UL; // Delay refreshing repeat "tState" inputs
|
||||
|
||||
enum {
|
||||
EXIOINIT = 0xE0, // Flag to initialise setup procedure
|
||||
EXIORDY = 0xE1, // Flag we have completed setup procedure, also for EX-IO to ACK setup
|
||||
CAMERR = 0xFE
|
||||
};
|
||||
};
|
||||
#endif
|
@ -141,4 +141,3 @@ const byte _DIR_MASK = 0x30;
|
||||
void EncoderThrottle::_display() {
|
||||
DIAG(F("DRIVE vpin %d loco %d notch %d"),_firstVpin,_locoid,_notch);
|
||||
}
|
||||
|
||||
|
@ -162,4 +162,4 @@ protected:
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_EXAMPLESERIAL_H
|
||||
#endif // IO_EXAMPLESERIAL_H
|
||||
|
@ -262,4 +262,4 @@ public:
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_HALDisplay_H
|
||||
#endif // IO_HALDisplay_H
|
||||
|
@ -98,4 +98,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -108,4 +108,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -129,7 +129,7 @@ class NeoPixel : public IODevice {
|
||||
public:
|
||||
|
||||
static void create(VPIN vpin, int nPins, uint16_t mode=(NEO_GRB | NEO_KHZ800), I2CAddress i2cAddress=0x60) {
|
||||
if (checkNoOverlap(vpin, nPins, mode, i2cAddress)) new NeoPixel(vpin, nPins, mode, i2cAddress);
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new NeoPixel(vpin, nPins, mode, i2cAddress);
|
||||
}
|
||||
|
||||
private:
|
||||
@ -206,6 +206,7 @@ private:
|
||||
|
||||
// loop called by HAL supervisor
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
(void)currentMicros;
|
||||
if (!_showPendimg) return;
|
||||
byte showBuffer[]={SEESAW_NEOPIXEL_BASE,SEESAW_NEOPIXEL_SHOW};
|
||||
I2CManager.write(_I2CAddress,showBuffer,sizeof(showBuffer));
|
||||
@ -291,7 +292,7 @@ private:
|
||||
}
|
||||
|
||||
|
||||
void transmit(uint16_t pixel, bool show=true) {
|
||||
void transmit(uint16_t pixel) {
|
||||
byte buffer[]={SEESAW_NEOPIXEL_BASE,SEESAW_NEOPIXEL_BUF,0x00,0x00,0x00,0x00,0x00};
|
||||
uint16_t offset= pixel * _bytesPerPixel;
|
||||
buffer[2]=(byte)(offset>>8);
|
||||
|
@ -167,4 +167,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -101,4 +101,4 @@ private:
|
||||
uint8_t inputBuffer[1];
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -106,4 +106,4 @@ private:
|
||||
uint8_t inputBuffer[2];
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -30,4 +30,3 @@
|
||||
//
|
||||
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};
|
||||
|
||||
|
@ -295,4 +295,4 @@ private:
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
461
IO_TCA8418.h
461
IO_TCA8418.h
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2023, Paul M. Antoine
|
||||
* © 2023-2024, Paul M. Antoine
|
||||
* © 2021, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC-EX API
|
||||
@ -21,164 +21,351 @@
|
||||
#ifndef io_tca8418_h
|
||||
#define io_tca8418_h
|
||||
|
||||
#include "IO_GPIOBase.h"
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "FSH.h"
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for TCA8418 80-key keypad encoder, which we'll treat as 64 of the possible
|
||||
* 80 inputs for now, in an 8x8 matrix only, although the datasheet says:
|
||||
* IODevice subclass for TCA8418 80-key keypad encoder, which we'll treat as 80 available VPINs where
|
||||
* key down == 1 and key up == 0 by configuring just as an 8x10 keyboard matrix. Users can opt to use
|
||||
* up to all 80 of the available VPINs for now, allowing memory to be saved if not all events are required.
|
||||
*
|
||||
* The datasheet says:
|
||||
*
|
||||
* The TCA8418 can be configured to support many different configurations of keypad setups.
|
||||
* All 18 GPIOs for the rows and columns can be used to support up to 80 keys in an 8x10 key pad
|
||||
* array. Another option is that all 18 GPIOs be used for GPIs to read 18 buttons which are
|
||||
* not connected in an array. Any combination in between is also acceptable (for example, a
|
||||
* 3x4 keypad matrix and using the remaining 11 GPIOs as a combination of inputs and outputs).
|
||||
*
|
||||
* With an 8x10 key event matrix, the events are numbered as such:
|
||||
*
|
||||
* C0 C1 C2 C3 C4 C5 C6 C7 C8 C9
|
||||
* ========================================
|
||||
* R0| 0 1 2 3 4 5 6 7 8 9
|
||||
* R1| 10 11 12 13 14 15 16 17 18 19
|
||||
* R2| 20 21 22 23 24 25 26 27 28 29
|
||||
* R3| 30 31 32 33 34 35 36 37 38 39
|
||||
* R4| 40 41 42 43 44 45 46 47 48 49
|
||||
* R5| 50 51 52 53 54 55 56 57 58 59
|
||||
* R6| 60 61 62 63 64 65 66 67 68 69
|
||||
* R7| 70 71 72 73 74 75 76 77 78 79
|
||||
*
|
||||
* So if you start with VPIN 300, R0/C0 will be 300, and R7/C9 will be 379.
|
||||
*
|
||||
* HAL declaration for myAutomation.h is:
|
||||
* HAL(TCA8418, firstVpin, numPins, I2CAddress, interruptPin)
|
||||
*
|
||||
* Where numPins can be 1-80, and interruptPin can be any spare Arduino pin.
|
||||
*
|
||||
* Configure using the following on the main I2C bus:
|
||||
* HAL(TCA8418, 300, 80, 0x34)
|
||||
*
|
||||
* Use something like this on a multiplexor, and with up to 8 of the 8-way multiplexors you could have 64 different TCA8418 boards:
|
||||
* HAL(TCA8418, 400, 80, {SubBus_1, 0x34})
|
||||
*
|
||||
* And if needing an Interrupt pin to speed up operations:
|
||||
* HAL(TCA8418, 300, 80, 0x34, D21)
|
||||
*
|
||||
* Note that using an interrupt pin speeds up button press acquisition considerably (less than a millisecond vs 10-100),
|
||||
* but even with interrupts enabled the code presently checks every 100ms in case the interrupt pin becomes disconnected.
|
||||
* Use any available Arduino pin for interrupt monitoring.
|
||||
*/
|
||||
|
||||
class TCA8418 : public GPIOBase<uint64_t> {
|
||||
class TCA8418 : public IODevice {
|
||||
public:
|
||||
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress))
|
||||
// temporarily use the simple 18-pin GPIO mode - we'll switch to 8x8 matrix once this works
|
||||
new TCA8418(vpin, (nPins = (nPins > 18) ? 18 : nPins), i2cAddress, interruptPin);
|
||||
|
||||
static void create(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress))
|
||||
new TCA8418(firstVpin, (nPins = (nPins > 80) ? 80 : nPins), i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
uint8_t* _digitalInputStates = NULL; // Array of pin states
|
||||
uint8_t _digitalPinBytes = 0; // Number of bytes in pin state array
|
||||
|
||||
uint8_t _numKeyEvents = 0; // Number of outsanding key events waiting for us
|
||||
|
||||
unsigned long _lastEventRead = 0;
|
||||
unsigned long _eventRefresh = 10000UL; // Delay refreshing events for 10ms
|
||||
const unsigned long _eventRefreshSlow = 100000UL; // Delay refreshing events for 100ms
|
||||
bool _gpioInterruptsEnabled = false;
|
||||
|
||||
uint8_t _inputBuffer[1];
|
||||
uint8_t _commandBuffer[1];
|
||||
I2CRB _i2crb;
|
||||
|
||||
enum {RDS_IDLE, RDS_EVENT, RDS_KEYCODE}; // Read operation states
|
||||
uint8_t _readState = RDS_IDLE;
|
||||
|
||||
// Constructor
|
||||
TCA8418(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint64_t>((FSH *)F("TCA8418"), vpin, nPins, i2cAddress, interruptPin)
|
||||
{
|
||||
uint8_t receiveBuffer[1];
|
||||
uint8_t commandBuffer[1];
|
||||
uint8_t status;
|
||||
|
||||
commandBuffer[0] = REG_INT_STAT; // Check interrupt status
|
||||
status = I2CManager.read(_I2CAddress, receiveBuffer, sizeof(receiveBuffer), commandBuffer, sizeof(commandBuffer));
|
||||
if (status == I2C_STATUS_OK) {
|
||||
DIAG(F("TCA8418 Interrupt status was: %x"), receiveBuffer[0]);
|
||||
TCA8418(VPIN firstVpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (nPins > 0)
|
||||
{
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
_gpioInterruptPin = interruptPin;
|
||||
addDevice(this);
|
||||
}
|
||||
else
|
||||
DIAG(F("TCA8418 Interrupt status failed to read!"));
|
||||
// requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
// outputBuffer, sizeof(outputBuffer));
|
||||
// outputBuffer[0] = REG_GPIOA;
|
||||
}
|
||||
void _writeGpioPort() override {
|
||||
// I2CManager.write(_I2CAddress, 3, REG_GPIOA, _portOutputState, _portOutputState>>8);
|
||||
}
|
||||
void _writePullups() override {
|
||||
// Set pullups only for in-use pins. This prevents pullup being set for a pin that
|
||||
// is intended for use as an output but hasn't been written to yet.
|
||||
uint32_t temp = _portPullup & _portInUse;
|
||||
(void)temp; // Chris did this so he could see warnings that mattered
|
||||
// I2CManager.write(_I2CAddress, 3, REG_GPPUA, temp, temp>>8);
|
||||
}
|
||||
void _writePortModes() override {
|
||||
// Write 0 to each GPIO_DIRn for in-use pins that are inputs, 1 for outputs
|
||||
uint64_t temp = _portMode & _portInUse;
|
||||
DIAG(F("TCA8418 writing Port Mode: %x, to GPIO_DIRs"), temp);
|
||||
DIAG(F("TCA8418 writing Port Mode: %x, to GPIO_DIR1"), (temp&0xFF));
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR1, (temp&0xFF));
|
||||
DIAG(F("TCA8418 writing Port Mode: %x, to GPIO_DIR2"), ((temp&0xFF00)>>8));
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR2, ((temp&0xFF00)>>8));
|
||||
DIAG(F("TCA8418 writing Port Mode: %x, to GPIO_DIR3"), (temp&0x30000)>>16);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR3, ((temp&0x30000)>>16));
|
||||
|
||||
// Enable interrupt for in-use pins which are inputs (_portMode=0)
|
||||
// TCA8418 has interrupt enables per pin, but must be configured for low->high
|
||||
// or high->low... unlike the MCP23017
|
||||
temp = ~_portMode & _portInUse;
|
||||
DIAG(F("TCA8418 writing interrupt Port Mode: %x, to GPIO_INT_ENs"), temp);
|
||||
DIAG(F("TCA8418 writing interrupt Port Mode: %x, to GPIO_INT_EN1"), (temp&0xFF));
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN1, (temp&0xFF));
|
||||
DIAG(F("TCA8418 writing interrupt Port Mode: %x, to GPIO_INT_EN2"), ((temp&0xFF00)>>8));
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN2, ((temp&0xFF00)>>8));
|
||||
DIAG(F("TCA8418 writing interrupt Port Mode: %x, to GPIO_INT_EN3"), (temp&0x30000)>>16);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN3, ((temp&0x30000)>>16));
|
||||
// I2CManager.write(_I2CAddress, 3, REG_INTCONA, 0x00, 0x00);
|
||||
// I2CManager.write(_I2CAddress, 3, REG_GPINTENA, temp, temp>>8);
|
||||
}
|
||||
void _readGpioPort(bool immediate) override {
|
||||
// if (immediate) {
|
||||
// uint8_t buffer[2];
|
||||
// I2CManager.read(_I2CAddress, buffer, 2, 1, REG_GPIOA);
|
||||
// _portInputState = ((uint16_t)buffer[1]<<8) | buffer[0] | _portMode;
|
||||
// } else {
|
||||
// // Queue new request
|
||||
// requestBlock.wait(); // Wait for preceding operation to complete
|
||||
// // Issue new request to read GPIO register
|
||||
// I2CManager.queueRequest(&requestBlock);
|
||||
// }
|
||||
}
|
||||
// This function is invoked when an I/O operation on the requestBlock completes.
|
||||
void _processCompletion(uint8_t status) override {
|
||||
// if (status == I2C_STATUS_OK)
|
||||
// _portInputState = (((uint16_t)inputBuffer[1]<<8) | inputBuffer[0]) | _portMode;
|
||||
// else
|
||||
// _portInputState = 0xffff;
|
||||
}
|
||||
|
||||
void _setupDevice() override {
|
||||
DIAG(F("TCA8418 setupDevice() called"));
|
||||
// IOCON is set MIRROR=1, ODR=1 (open drain shared interrupt pin)
|
||||
// I2CManager.write(_I2CAddress, 2, REG_IOCON, 0x44);
|
||||
_writePortModes();
|
||||
_writePullups();
|
||||
_writeGpioPort();
|
||||
void _begin() {
|
||||
|
||||
I2CManager.begin();
|
||||
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
// Default all GPIO pins to INPUT
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR_1, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR_2, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_DIR_3, 0x00);
|
||||
|
||||
// Remove all GPIO pins from events
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPI_EM_1, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPI_EM_2, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPI_EM_3, 0x00);
|
||||
|
||||
// Set all pins to FALLING interrupts
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_LVL_1, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_LVL_2, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_LVL_3, 0x00);
|
||||
|
||||
// Remove all GPIO pins from interrupts
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN_1, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN_2, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_GPIO_INT_EN_3, 0x00);
|
||||
|
||||
// Set up an 8 x 10 matrix by writing 0xFF to all the row and column configs
|
||||
// Row config is maximum of 8, and in REG_KP_GPIO_1
|
||||
I2CManager.write(_I2CAddress, 2, REG_KP_GPIO_1, 0xFF);
|
||||
// Column config is maximum of 10, lower 8 bits in REG_KP_GPIO_2, upper in REG_KP_GPIO_3
|
||||
// Set first 8 columns
|
||||
I2CManager.write(_I2CAddress, 2, REG_KP_GPIO_2, 0xFF);
|
||||
// Turn on cols 9/10
|
||||
I2CManager.write(_I2CAddress, 2, REG_KP_GPIO_3, 0x03);
|
||||
|
||||
// // Set all pins to Enable Debounce
|
||||
I2CManager.write(_I2CAddress, 2, REG_DEBOUNCE_DIS_1, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_DEBOUNCE_DIS_2, 0x00);
|
||||
I2CManager.write(_I2CAddress, 2, REG_DEBOUNCE_DIS_3, 0x00);
|
||||
|
||||
// Let's assume an 8x10 matrix for now, and configure
|
||||
_digitalPinBytes = (_nPins + 7) / 8;
|
||||
if ((_digitalInputStates = (byte *)calloc(_digitalPinBytes, 1)) == NULL) {
|
||||
DIAG(F("TCA8418 I2C: Unable to alloc %d bytes"), _digitalPinBytes);
|
||||
return;
|
||||
}
|
||||
|
||||
// Configure pin used for GPIO extender notification of change (if allocated)
|
||||
// and configure TCA8418 to produce key event interrupts
|
||||
if (_gpioInterruptPin >= 0) {
|
||||
DIAG(F("TCA8418 I2C: interrupt pin configured on %d"), _gpioInterruptPin);
|
||||
_gpioInterruptsEnabled = true;
|
||||
_eventRefresh = _eventRefreshSlow; // Switch to slower manual refreshes in case the INT pin isn't connected!
|
||||
pinMode(_gpioInterruptPin, INPUT_PULLUP);
|
||||
I2CManager.write(_I2CAddress, 2, REG_CFG, REG_CFG_KE_IEN);
|
||||
// Clear any pending interrupts
|
||||
I2CManager.write(_I2CAddress, 2, REG_INT_STAT, REG_STAT_K_INT);
|
||||
}
|
||||
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
enum
|
||||
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED)
|
||||
return 0;
|
||||
int pin = vpin - _firstVpin;
|
||||
bool result = _digitalInputStates[pin / 8] & (1 << (pin % 8));
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
// Main loop, collect both digital and analogue pin states continuously (faster sensor/input reads)
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return; // If device failed, return
|
||||
|
||||
// Request block is used for key event reads from the TCA8418, which are performed
|
||||
// on a cyclic basis.
|
||||
|
||||
if (_readState != RDS_IDLE) {
|
||||
if (_i2crb.isBusy()) return; // If I2C operation still in progress, return
|
||||
|
||||
uint8_t status = _i2crb.status;
|
||||
if (status == I2C_STATUS_OK) { // If device request ok, read input data
|
||||
|
||||
// First check if we have any key events waiting
|
||||
if (_readState == RDS_EVENT) {
|
||||
if ((_numKeyEvents = (_inputBuffer[0] & 0x0F)) != 0) {
|
||||
// We could read each key event waiting in a synchronous loop, which may prove preferable
|
||||
// but for now, schedule an async read of the first key event in the queue
|
||||
_commandBuffer[0] = REG_KEY_EVENT_A;
|
||||
I2CManager.read(_I2CAddress, _inputBuffer, 1, _commandBuffer, 1, &_i2crb); // non-blocking read
|
||||
_readState = RDS_KEYCODE; // Shift to reading key events!
|
||||
}
|
||||
else // We found no key events waiting, return to IDLE
|
||||
_readState = RDS_IDLE;
|
||||
}
|
||||
else {
|
||||
// RDS_KEYCODE
|
||||
uint8_t key = _inputBuffer[0] & 0x7F;
|
||||
bool keyDown = _inputBuffer[0] & 0x80;
|
||||
// Check for just keypad events
|
||||
key--; // R0/C0 is key #1, so subtract 1 to create an array offset
|
||||
// We only want to record key events we're configured for, as we have calloc'd an
|
||||
// appropriately sized _digitalInputStates array!
|
||||
if (key < _nPins) {
|
||||
if (keyDown)
|
||||
_digitalInputStates[key / 8] |= (1 << (key % 8));
|
||||
else
|
||||
_digitalInputStates[key / 8] &= ~(1 << (key % 8));
|
||||
}
|
||||
else
|
||||
DIAG(F("TCA8418 I2C: key event %d discarded, outside Vpin range"), key);
|
||||
_numKeyEvents--; // One less key event to get
|
||||
if (_numKeyEvents != 0)
|
||||
{
|
||||
// DIAG(F("TCA8418 I2C: more keys in read event queue, # waiting is: %x"), _numKeyEvents);
|
||||
// We could read each key event waiting in a synchronous loop, which may prove preferable
|
||||
// but for now, schedule an async read of the first key event in the queue
|
||||
_commandBuffer[0] = REG_KEY_EVENT_A;
|
||||
I2CManager.read(_I2CAddress, _inputBuffer, 1, _commandBuffer, 1, &_i2crb); // non-blocking read
|
||||
}
|
||||
else {
|
||||
// DIAG(F("TCA8418 I2C: no more keys in read event queue"));
|
||||
// Clear any pending interrupts
|
||||
I2CManager.write(_I2CAddress, 2, REG_INT_STAT, REG_STAT_K_INT);
|
||||
_readState = RDS_IDLE; // Shift to IDLE
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else
|
||||
reportError(status, false); // report eror but don't go offline.
|
||||
}
|
||||
|
||||
// If we're not doing anything now, check to see if we have an interrupt pin configured and it is low,
|
||||
// or if our timer has elapsed and we should check anyway in case the interrupt pin is disconnected.
|
||||
if (_readState == RDS_IDLE) {
|
||||
if ((_gpioInterruptsEnabled && !digitalRead(_gpioInterruptPin)) ||
|
||||
((currentMicros - _lastEventRead) > _eventRefresh))
|
||||
{
|
||||
_commandBuffer[0] = REG_KEY_LCK_EC;
|
||||
I2CManager.read(_I2CAddress, _inputBuffer, 1, _commandBuffer, 1, &_i2crb); // non-blocking read
|
||||
_lastEventRead = currentMicros;
|
||||
_readState = RDS_EVENT; // Shift to looking for key events!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Display device information and status
|
||||
void _display() override {
|
||||
DIAG(F("TCA8418 I2C:%s Vpins %u-%u%S"),
|
||||
_I2CAddress.toString(),
|
||||
_firstVpin, (_firstVpin+_nPins-1),
|
||||
_deviceState == DEVSTATE_FAILED ? F(" OFFLINE") : F(""));
|
||||
if (_gpioInterruptsEnabled)
|
||||
DIAG(F("TCA8418 I2C:Interrupt on pin %d"), _gpioInterruptPin);
|
||||
}
|
||||
|
||||
// Helper function for error handling
|
||||
void reportError(uint8_t status, bool fail=true) {
|
||||
DIAG(F("TCA8418 I2C:%s Error:%d (%S)"), _I2CAddress.toString(),
|
||||
status, I2CManager.getErrorMessage(status));
|
||||
if (fail)
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
}
|
||||
|
||||
enum tca8418_registers
|
||||
{
|
||||
REG_FIRST_RESERVED = 0x00,
|
||||
REG_CFG = 0x01,
|
||||
REG_INT_STAT = 0x02,
|
||||
REG_KEY_LCK_EC = 0x03,
|
||||
REG_KEY_EVENT_A = 0x04,
|
||||
REG_KEY_EVENT_B = 0x05,
|
||||
REG_KEY_EVENT_C = 0x06,
|
||||
REG_KEY_EVENT_D = 0x07,
|
||||
REG_KEY_EVENT_E = 0x08,
|
||||
REG_KEY_EVENT_F = 0x09,
|
||||
REG_KEY_EVENT_G = 0x0A,
|
||||
REG_KEY_EVENT_H = 0x0B,
|
||||
REG_KEY_EVENT_I = 0x0C,
|
||||
REG_KEY_EVENT_J = 0x0D,
|
||||
REG_KP_LCK_TIMER = 0x0E,
|
||||
REG_UNLOCK1 = 0x0F,
|
||||
REG_UNLOCK2 = 0x10,
|
||||
REG_GPIO_INT_STAT1 = 0x11,
|
||||
REG_GPIO_INT_STAT2 = 0x12,
|
||||
REG_GPIO_INT_STAT3 = 0x13,
|
||||
REG_GPIO_DAT_STAT1 = 0x14,
|
||||
REG_GPIO_DAT_STAT2 = 0x15,
|
||||
REG_GPIO_DAT_STAT3 = 0x16,
|
||||
REG_GPIO_DAT_OUT1 = 0x17,
|
||||
REG_GPIO_DAT_OUT2 = 0x18,
|
||||
REG_GPIO_DAT_OUT3 = 0x19,
|
||||
REG_GPIO_INT_EN1 = 0x1A,
|
||||
REG_GPIO_INT_EN2 = 0x1B,
|
||||
REG_GPIO_INT_EN3 = 0x1C,
|
||||
REG_KP_GPIO1 = 0x1D,
|
||||
REG_KP_GPIO2 = 0x1E,
|
||||
REG_KP_GPIO3 = 0x1F,
|
||||
REG_GPI_EM1 = 0x20,
|
||||
REG_GPI_EM2 = 0x21,
|
||||
REG_GPI_EM3 = 0x22,
|
||||
REG_GPIO_DIR1 = 0x23,
|
||||
REG_GPIO_DIR2 = 0x24,
|
||||
REG_GPIO_DIR3 = 0x25,
|
||||
REG_GPIO_INT_LVL1 = 0x26,
|
||||
REG_GPIO_INT_LVL2 = 0x27,
|
||||
REG_GPIO_INT_LVL3 = 0x28,
|
||||
REG_DEBOUNCE_DIS1 = 0x29,
|
||||
REG_DEBOUNCE_DIS2 = 0x2A,
|
||||
REG_DEBOUNCE_DIS3 = 0x2B,
|
||||
REG_GPIO_PULL1 = 0x2C,
|
||||
REG_GPIO_PULL2 = 0x2D,
|
||||
REG_GPIO_PULL3 = 0x2E,
|
||||
REG_LAST_RESERVED = 0x2F,
|
||||
// REG_RESERVED = 0x00
|
||||
REG_CFG = 0x01, // Configuration register
|
||||
REG_INT_STAT = 0x02, // Interrupt status
|
||||
REG_KEY_LCK_EC = 0x03, // Key lock and event counter
|
||||
REG_KEY_EVENT_A = 0x04, // Key event register A
|
||||
REG_KEY_EVENT_B = 0x05, // Key event register B
|
||||
REG_KEY_EVENT_C = 0x06, // Key event register C
|
||||
REG_KEY_EVENT_D = 0x07, // Key event register D
|
||||
REG_KEY_EVENT_E = 0x08, // Key event register E
|
||||
REG_KEY_EVENT_F = 0x09, // Key event register F
|
||||
REG_KEY_EVENT_G = 0x0A, // Key event register G
|
||||
REG_KEY_EVENT_H = 0x0B, // Key event register H
|
||||
REG_KEY_EVENT_I = 0x0C, // Key event register I
|
||||
REG_KEY_EVENT_J = 0x0D, // Key event register J
|
||||
REG_KP_LCK_TIMER = 0x0E, // Keypad lock1 to lock2 timer
|
||||
REG_UNLOCK_1 = 0x0F, // Unlock register 1
|
||||
REG_UNLOCK_2 = 0x10, // Unlock register 2
|
||||
REG_GPIO_INT_STAT_1 = 0x11, // GPIO interrupt status 1
|
||||
REG_GPIO_INT_STAT_2 = 0x12, // GPIO interrupt status 2
|
||||
REG_GPIO_INT_STAT_3 = 0x13, // GPIO interrupt status 3
|
||||
REG_GPIO_DAT_STAT_1 = 0x14, // GPIO data status 1
|
||||
REG_GPIO_DAT_STAT_2 = 0x15, // GPIO data status 2
|
||||
REG_GPIO_DAT_STAT_3 = 0x16, // GPIO data status 3
|
||||
REG_GPIO_DAT_OUT_1 = 0x17, // GPIO data out 1
|
||||
REG_GPIO_DAT_OUT_2 = 0x18, // GPIO data out 2
|
||||
REG_GPIO_DAT_OUT_3 = 0x19, // GPIO data out 3
|
||||
REG_GPIO_INT_EN_1 = 0x1A, // GPIO interrupt enable 1
|
||||
REG_GPIO_INT_EN_2 = 0x1B, // GPIO interrupt enable 2
|
||||
REG_GPIO_INT_EN_3 = 0x1C, // GPIO interrupt enable 3
|
||||
REG_KP_GPIO_1 = 0x1D, // Keypad/GPIO select 1
|
||||
REG_KP_GPIO_2 = 0x1E, // Keypad/GPIO select 2
|
||||
REG_KP_GPIO_3 = 0x1F, // Keypad/GPIO select 3
|
||||
REG_GPI_EM_1 = 0x20, // GPI event mode 1
|
||||
REG_GPI_EM_2 = 0x21, // GPI event mode 2
|
||||
REG_GPI_EM_3 = 0x22, // GPI event mode 3
|
||||
REG_GPIO_DIR_1 = 0x23, // GPIO data direction 1
|
||||
REG_GPIO_DIR_2 = 0x24, // GPIO data direction 2
|
||||
REG_GPIO_DIR_3 = 0x25, // GPIO data direction 3
|
||||
REG_GPIO_INT_LVL_1 = 0x26, // GPIO edge/level detect 1
|
||||
REG_GPIO_INT_LVL_2 = 0x27, // GPIO edge/level detect 2
|
||||
REG_GPIO_INT_LVL_3 = 0x28, // GPIO edge/level detect 3
|
||||
REG_DEBOUNCE_DIS_1 = 0x29, // Debounce disable 1
|
||||
REG_DEBOUNCE_DIS_2 = 0x2A, // Debounce disable 2
|
||||
REG_DEBOUNCE_DIS_3 = 0x2B, // Debounce disable 3
|
||||
REG_GPIO_PULL_1 = 0x2C, // GPIO pull-up disable 1
|
||||
REG_GPIO_PULL_2 = 0x2D, // GPIO pull-up disable 2
|
||||
REG_GPIO_PULL_3 = 0x2E, // GPIO pull-up disable 3
|
||||
// REG_RESERVED = 0x2F
|
||||
};
|
||||
|
||||
enum tca8418_config_reg_fields
|
||||
{
|
||||
// Config Register #1 fields
|
||||
REG_CFG_AI = 0x80, // Auto-increment for read/write
|
||||
REG_CFG_GPI_E_CGF = 0x40, // Event mode config
|
||||
REG_CFG_OVR_FLOW_M = 0x20, // Overflow mode enable
|
||||
REG_CFG_INT_CFG = 0x10, // Interrupt config
|
||||
REG_CFG_OVR_FLOW_IEN = 0x08, // Overflow interrupt enable
|
||||
REG_CFG_K_LCK_IEN = 0x04, // Keypad lock interrupt enable
|
||||
REG_CFG_GPI_IEN = 0x02, // GPI interrupt enable
|
||||
REG_CFG_KE_IEN = 0x01, // Key events interrupt enable
|
||||
};
|
||||
|
||||
enum tca8418_int_status_fields
|
||||
{
|
||||
// Interrupt Status Register #2 fields
|
||||
REG_STAT_CAD_INT = 0x10, // Ctrl-alt-del seq status
|
||||
REG_STAT_OVR_FLOW_INT = 0x08, // Overflow interrupt status
|
||||
REG_STAT_K_LCK_INT = 0x04, // Key lock interrupt status
|
||||
REG_STAT_GPI_INT = 0x02, // GPI interrupt status
|
||||
REG_STAT_K_INT = 0x01, // Key events interrupt status
|
||||
};
|
||||
|
||||
enum tca8418_lock_ec_fields
|
||||
{
|
||||
// Key Lock Event Count Register #3
|
||||
REG_LCK_EC_K_LCK_EN = 0x40, // Key lock enable
|
||||
REG_LCK_EC_LCK_2 = 0x20, // Keypad lock status 2
|
||||
REG_LCK_EC_LCK_1 = 0x10, // Keypad lock status 1
|
||||
REG_LCK_EC_KLEC_3 = 0x08, // Key event count bit 3
|
||||
REG_LCK_EC_KLEC_2 = 0x04, // Key event count bit 2
|
||||
REG_LCK_EC_KLEC_1 = 0x02, // Key event count bit 1
|
||||
REG_LCK_EC_KLEC_0 = 0x01, // Key event count bit 0
|
||||
};
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
215
IO_TM1638.cpp
Normal file
215
IO_TM1638.cpp
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
* © 2024, Chris Harlow. 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/>.
|
||||
*/
|
||||
|
||||
/* Credit to https://github.com/dvarrel/TM1638 for the basic formulae.*/
|
||||
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "IODevice.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
|
||||
const uint8_t HIGHFLASH _digits[16]={
|
||||
0b00111111,0b00000110,0b01011011,0b01001111,
|
||||
0b01100110,0b01101101,0b01111101,0b00000111,
|
||||
0b01111111,0b01101111,0b01110111,0b01111100,
|
||||
0b00111001,0b01011110,0b01111001,0b01110001
|
||||
};
|
||||
|
||||
// Constructor
|
||||
TM1638::TM1638(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = 8;
|
||||
_clk_pin = clk_pin;
|
||||
_stb_pin = stb_pin;
|
||||
_dio_pin = dio_pin;
|
||||
pinMode(clk_pin,OUTPUT);
|
||||
pinMode(stb_pin,OUTPUT);
|
||||
pinMode(dio_pin,OUTPUT);
|
||||
_pulse = PULSE1_16;
|
||||
|
||||
_buttons=0;
|
||||
_leds=0;
|
||||
_lastLoop=micros();
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
|
||||
void TM1638::create(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin) {
|
||||
if (checkNoOverlap(firstVpin,8))
|
||||
new TM1638(firstVpin, clk_pin,dio_pin,stb_pin);
|
||||
}
|
||||
|
||||
void TM1638::_begin() {
|
||||
displayClear();
|
||||
test();
|
||||
_display();
|
||||
}
|
||||
|
||||
|
||||
void TM1638::_loop(unsigned long currentMicros) {
|
||||
if (currentMicros - _lastLoop > (1000000UL/LoopHz)) {
|
||||
_buttons=getButtons();// Read the buttons
|
||||
_lastLoop=currentMicros;
|
||||
}
|
||||
}
|
||||
|
||||
void TM1638::_display() {
|
||||
DIAG(F("TM1638 Configured on Vpins:%u-%u"), _firstVpin, _firstVpin+_nPins-1);
|
||||
}
|
||||
|
||||
// digital read gets button state
|
||||
int TM1638::_read(VPIN vpin) {
|
||||
byte pin=vpin - _firstVpin;
|
||||
bool result=bitRead(_buttons,pin);
|
||||
// DIAG(F("TM1638 read (%d) buttons %x = %d"),pin,_buttons,result);
|
||||
return result;
|
||||
}
|
||||
|
||||
// digital write sets led state
|
||||
void TM1638::_write(VPIN vpin, int value) {
|
||||
// TODO.. skip if no state change
|
||||
writeLed(vpin - _firstVpin + 1,value!=0);
|
||||
}
|
||||
|
||||
// Analog write sets digit displays
|
||||
|
||||
void TM1638::_writeAnalogue(VPIN vpin, int lowBytes, uint8_t mode, uint16_t highBytes) {
|
||||
// mode is in DataFormat defined above.
|
||||
byte formatLength=mode & 0x0F; // last 4 bits
|
||||
byte formatType=mode & 0xF0; //
|
||||
int8_t leftDigit=vpin-_firstVpin; // 0..7 from left
|
||||
int8_t rightDigit=leftDigit+formatLength-1; // 0..7 from left
|
||||
|
||||
// loading is done right to left startDigit first
|
||||
int8_t startDigit=7-rightDigit; // reverse as 7 on left
|
||||
int8_t lastDigit=7-leftDigit; // reverse as 7 on left
|
||||
uint32_t value=highBytes;
|
||||
value<<=16;
|
||||
value |= (uint16_t)lowBytes;
|
||||
|
||||
//DIAG(F("TM1638 fl=%d ft=%x sd=%d ld=%d v=%l vx=%X"),
|
||||
// formatLength,formatType,startDigit,lastDigit,value,value);
|
||||
while(startDigit<=lastDigit) {
|
||||
switch (formatType) {
|
||||
case _DF_DECIMAL:// decimal (leading zeros)
|
||||
displayDig(startDigit,GETHIGHFLASH(_digits,(value%10)));
|
||||
value=value/10;
|
||||
break;
|
||||
case _DF_HEX:// HEX (leading zeros)
|
||||
displayDig(startDigit,GETHIGHFLASH(_digits,(value & 0x0F)));
|
||||
value>>=4;
|
||||
break;
|
||||
case _DF_RAW:// Raw 7-segment pattern
|
||||
displayDig(startDigit,value & 0xFF);
|
||||
value>>=8;
|
||||
break;
|
||||
default:
|
||||
DIAG(F("TM1368 invalid mode 0x%x"),mode);
|
||||
return;
|
||||
}
|
||||
startDigit++;
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t TM1638::getButtons(){
|
||||
ArduinoPins::fastWriteDigital(_stb_pin, LOW);
|
||||
writeData(INSTRUCTION_READ_KEY);
|
||||
pinMode(_dio_pin, INPUT);
|
||||
ArduinoPins::fastWriteDigital(_clk_pin, LOW);
|
||||
uint8_t buttons=0;
|
||||
for (uint8_t eachByte=0; eachByte<4;eachByte++) {
|
||||
uint8_t value = 0;
|
||||
for (uint8_t eachBit = 0; eachBit < 8; eachBit++) {
|
||||
ArduinoPins::fastWriteDigital(_clk_pin, HIGH);
|
||||
value |= ArduinoPins::fastReadDigital(_dio_pin) << eachBit;
|
||||
ArduinoPins::fastWriteDigital(_clk_pin, LOW);
|
||||
}
|
||||
buttons |= value << eachByte;
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
pinMode(_dio_pin, OUTPUT);
|
||||
ArduinoPins::fastWriteDigital(_stb_pin, HIGH);
|
||||
return buttons;
|
||||
}
|
||||
|
||||
|
||||
void TM1638::displayDig(uint8_t digitId, uint8_t pgfedcba){
|
||||
if (digitId>7) return;
|
||||
setDataInstruction(DISPLAY_TURN_ON | _pulse);
|
||||
setDataInstruction(INSTRUCTION_WRITE_DATA| INSTRUCTION_ADDRESS_FIXED);
|
||||
writeDataAt(FIRST_DISPLAY_ADDRESS+14-(digitId*2), pgfedcba);
|
||||
}
|
||||
|
||||
void TM1638::displayClear(){
|
||||
setDataInstruction(DISPLAY_TURN_ON | _pulse);
|
||||
setDataInstruction(INSTRUCTION_WRITE_DATA | INSTRUCTION_ADDRESS_FIXED);
|
||||
for (uint8_t i=0;i<15;i+=2){
|
||||
writeDataAt(FIRST_DISPLAY_ADDRESS+i,0x00);
|
||||
}
|
||||
}
|
||||
|
||||
void TM1638::writeLed(uint8_t num,bool state){
|
||||
if ((num<1) | (num>8)) return;
|
||||
setDataInstruction(DISPLAY_TURN_ON | _pulse);
|
||||
setDataInstruction(INSTRUCTION_WRITE_DATA | INSTRUCTION_ADDRESS_FIXED);
|
||||
writeDataAt(FIRST_DISPLAY_ADDRESS + (num*2-1), state);
|
||||
}
|
||||
|
||||
|
||||
void TM1638::writeData(uint8_t data){
|
||||
for (uint8_t i = 0; i < 8; i++) {
|
||||
ArduinoPins::fastWriteDigital(_dio_pin, data & 1);
|
||||
data >>= 1;
|
||||
ArduinoPins::fastWriteDigital(_clk_pin, HIGH);
|
||||
ArduinoPins::fastWriteDigital(_clk_pin, LOW);
|
||||
}
|
||||
}
|
||||
|
||||
void TM1638::writeDataAt(uint8_t displayAddress, uint8_t data){
|
||||
ArduinoPins::fastWriteDigital(_stb_pin, LOW);
|
||||
writeData(displayAddress);
|
||||
writeData(data);
|
||||
ArduinoPins::fastWriteDigital(_stb_pin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
|
||||
void TM1638::setDataInstruction(uint8_t dataInstruction){
|
||||
ArduinoPins::fastWriteDigital(_stb_pin, LOW);
|
||||
writeData(dataInstruction);
|
||||
ArduinoPins::fastWriteDigital(_stb_pin, HIGH);
|
||||
delayMicroseconds(1);
|
||||
}
|
||||
|
||||
void TM1638::test(){
|
||||
DIAG(F("TM1638 test"));
|
||||
uint8_t val=0;
|
||||
for(uint8_t i=0;i<5;i++){
|
||||
setDataInstruction(DISPLAY_TURN_ON | _pulse);
|
||||
setDataInstruction(INSTRUCTION_WRITE_DATA| INSTRUCTION_ADDRESS_AUTO);
|
||||
ArduinoPins::fastWriteDigital(_stb_pin, LOW);
|
||||
writeData(FIRST_DISPLAY_ADDRESS);
|
||||
for(uint8_t i=0;i<16;i++)
|
||||
writeData(val);
|
||||
ArduinoPins::fastWriteDigital(_stb_pin, HIGH);
|
||||
delay(1000);
|
||||
val = ~val;
|
||||
}
|
||||
|
||||
}
|
134
IO_TM1638.h
Normal file
134
IO_TM1638.h
Normal file
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* © 2024, Chris Harlow. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef IO_TM1638_h
|
||||
#define IO_TM1638_h
|
||||
#include <Arduino.h>
|
||||
#include "IODevice.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
class TM1638 : public IODevice {
|
||||
private:
|
||||
|
||||
uint8_t _buttons;
|
||||
uint8_t _leds;
|
||||
unsigned long _lastLoop;
|
||||
static const int LoopHz=20;
|
||||
|
||||
static const byte
|
||||
INSTRUCTION_WRITE_DATA=0x40,
|
||||
INSTRUCTION_READ_KEY=0x42,
|
||||
INSTRUCTION_ADDRESS_AUTO=0x40,
|
||||
INSTRUCTION_ADDRESS_FIXED=0x44,
|
||||
INSTRUCTION_NORMAL_MODE=0x40,
|
||||
INSTRUCTION_TEST_MODE=0x48,
|
||||
|
||||
FIRST_DISPLAY_ADDRESS=0xC0,
|
||||
|
||||
DISPLAY_TURN_OFF=0x80,
|
||||
DISPLAY_TURN_ON=0x88;
|
||||
|
||||
|
||||
uint8_t _clk_pin;
|
||||
uint8_t _stb_pin;
|
||||
uint8_t _dio_pin;
|
||||
uint8_t _pulse;
|
||||
bool _isOn;
|
||||
|
||||
|
||||
// Constructor
|
||||
TM1638(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin);
|
||||
|
||||
public:
|
||||
enum DigitFormat : byte {
|
||||
// last 4 bits are length.
|
||||
// DF_1.. DF_8 decimal
|
||||
DF_1=0x01,DF_2=0x02,DF_3=0x03,DF_4=0x04,
|
||||
DF_5=0x05,DF_6=0x06,DF_7=0x07,DF_8=0x08,
|
||||
// DF_1X.. DF_8X HEX
|
||||
DF_1X=0x11,DF_2X=0x12,DF_3X=0x13,DF_4X=0x14,
|
||||
DF_5X=0x15,DF_6X=0x16,DF_7X=0x17,DF_8X=0x18,
|
||||
// DF_1R .. DF_4R raw 7 segmnent data
|
||||
// only 4 because HAL analogWrite only passes 4 bytes
|
||||
DF_1R=0x21,DF_2R=0x22,DF_3R=0x23,DF_4R=0x24,
|
||||
|
||||
// bits of data conversion type (ored with length)
|
||||
_DF_DECIMAL=0x00,// right adjusted decimal unsigned leading zeros
|
||||
_DF_HEX=0x10, // right adjusted hex leading zeros
|
||||
_DF_RAW=0x20 // bytes are raw 7-segment pattern (max length 4)
|
||||
};
|
||||
|
||||
static void create(VPIN firstVpin, byte clk_pin,byte dio_pin,byte stb_pin);
|
||||
|
||||
// Functions overridden in IODevice
|
||||
void _begin();
|
||||
void _loop(unsigned long currentMicros) override ;
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t param1, uint16_t param2) override;
|
||||
void _display() override ;
|
||||
int _read(VPIN pin) override;
|
||||
void _write(VPIN pin,int value) override;
|
||||
|
||||
// Device driving functions
|
||||
private:
|
||||
enum pulse_t {
|
||||
PULSE1_16,
|
||||
PULSE2_16,
|
||||
PULSE4_16,
|
||||
PULSE10_16,
|
||||
PULSE11_16,
|
||||
PULSE12_16,
|
||||
PULSE13_16,
|
||||
PULSE14_16
|
||||
};
|
||||
|
||||
/**
|
||||
* @fn getButtons
|
||||
* @return state of 8 buttons
|
||||
*/
|
||||
uint8_t getButtons();
|
||||
|
||||
/**
|
||||
* @fn writeLed
|
||||
* @brief put led ON or OFF
|
||||
* @param num num of led(1-8)
|
||||
* @param state (true or false)
|
||||
*/
|
||||
void writeLed(uint8_t num, bool state);
|
||||
|
||||
|
||||
/**
|
||||
* @fn displayDig
|
||||
* @brief set 7 segment display + dot
|
||||
* @param digitId num of digit(0-7)
|
||||
* @param val value 8 bits
|
||||
*/
|
||||
void displayDig(uint8_t digitId, uint8_t pgfedcba);
|
||||
|
||||
/**
|
||||
* @fn displayClear
|
||||
* @brief switch off all leds and segment display
|
||||
*/
|
||||
void displayClear();
|
||||
void test();
|
||||
void writeData(uint8_t data);
|
||||
void writeDataAt(uint8_t displayAddress, uint8_t data);
|
||||
void setDisplayMode(uint8_t displayMode);
|
||||
void setDataInstruction(uint8_t dataInstruction);
|
||||
};
|
||||
#endif
|
@ -131,4 +131,4 @@ protected:
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_TOUCHKEYPAD_H
|
||||
#endif // IO_TOUCHKEYPAD_H
|
||||
|
@ -170,4 +170,4 @@ public:
|
||||
}
|
||||
|
||||
};
|
||||
#endif
|
||||
#endif
|
||||
|
@ -95,4 +95,4 @@ private:
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -54,4 +54,43 @@ static_assert("MAIN"_hk == 11339,"Keyword hasher error");
|
||||
static_assert("SLOW"_hk == -17209,"Keyword hasher error");
|
||||
static_assert("SPEED28"_hk == -17064,"Keyword hasher error");
|
||||
static_assert("SPEED128"_hk == 25816,"Keyword hasher error");
|
||||
#endif
|
||||
|
||||
// Compile time converter from "abcd"_s7 to the 7 segment nearest equivalent
|
||||
|
||||
constexpr uint8_t seg7Digits[]={
|
||||
0b00111111,0b00000110,0b01011011,0b01001111, // 0..3
|
||||
0b01100110,0b01101101,0b01111101,0b00000111, // 4..7
|
||||
0b01111111,0b01101111 // 8..9
|
||||
};
|
||||
|
||||
constexpr uint8_t seg7Letters[]={
|
||||
0b01110111,0b01111100,0b00111001,0b01011110, // ABCD
|
||||
0b01111001,0b01110001,0b00111101,0b01110110, // EFGH
|
||||
0b00000100,0b00011110,0b01110010,0b00111000, //IJKL
|
||||
0b01010101,0b01010100,0b01011100,0b01110011, // MNOP
|
||||
0b10111111,0b01010000,0b01101101,0b01111000, // QRST
|
||||
0b00111110,0b00011100,0b01101010,0b01001001, //UVWX
|
||||
0b01100110,0b01011011 //YZ
|
||||
};
|
||||
constexpr uint8_t seg7Space=0b00000000;
|
||||
constexpr uint8_t seg7Minus=0b01000000;
|
||||
constexpr uint8_t seg7Equals=0b01001000;
|
||||
|
||||
|
||||
constexpr uint32_t CompiletimeSeg7(const char * sv, uint32_t running, size_t rlen) {
|
||||
return (*sv==0 || rlen==0) ? running << (8*rlen) : CompiletimeSeg7(sv+1,
|
||||
(*sv >= '0' && *sv <= '9') ? (running<<8) | seg7Digits[*sv-'0'] :
|
||||
(*sv >= 'A' && *sv <= 'Z') ? (running<<8) | seg7Letters[*sv-'A'] :
|
||||
(*sv >= 'a' && *sv <= 'z') ? (running<<8) | seg7Letters[*sv-'a'] :
|
||||
(*sv == '-') ? (running<<8) | seg7Minus :
|
||||
(*sv == '=') ? (running<<8) | seg7Equals :
|
||||
(running<<8) | seg7Space,
|
||||
rlen-1
|
||||
); //
|
||||
}
|
||||
|
||||
constexpr uint32_t operator""_s7(const char * keyword, size_t len)
|
||||
{
|
||||
return CompiletimeSeg7(keyword,0*len,4);
|
||||
}
|
||||
#endif
|
||||
|
@ -221,4 +221,4 @@ void LiquidCrystal_I2C::expanderWrite(uint8_t value) {
|
||||
rb.wait();
|
||||
outputBuffer[0] = value | _backlightval;
|
||||
I2CManager.write(_Addr, outputBuffer, 1, &rb); // Write command asynchronously
|
||||
}
|
||||
}
|
||||
|
@ -576,7 +576,7 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
DIAG(F("TRACK %c ALERT FAULT"), trackno + 'A');
|
||||
}
|
||||
setPower(POWERMODE::ALERT);
|
||||
if ((trackMode & TRACK_MODE_AUTOINV) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){
|
||||
if ((trackMode & TRACK_MODIFIER_AUTO) && (trackMode & (TRACK_MODE_MAIN|TRACK_MODE_EXT|TRACK_MODE_BOOST))){
|
||||
DIAG(F("TRACK %c INVERT"), trackno + 'A');
|
||||
invertOutput();
|
||||
}
|
||||
|
@ -29,21 +29,33 @@
|
||||
#include <wiring_private.h>
|
||||
|
||||
// use powers of two so we can do logical and/or on the track modes in if clauses.
|
||||
// RACK_MODE_DCX is (TRACK_MODE_DC|TRACK_MODE_INV)
|
||||
// For example TRACK_MODE_DC_INV is (TRACK_MODE_DC|TRACK_MODIFIER_INV)
|
||||
template<class T> inline T operator~ (T a) { return (T)~(int)a; }
|
||||
template<class T> inline T operator| (T a, T b) { return (T)((int)a | (int)b); }
|
||||
template<class T> inline T operator& (T a, T b) { return (T)((int)a & (int)b); }
|
||||
template<class T> inline T operator^ (T a, T b) { return (T)((int)a ^ (int)b); }
|
||||
enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16,
|
||||
enum TRACK_MODE : byte {
|
||||
// main modes
|
||||
TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PROG = 4,
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_EXT = 16,
|
||||
// modifiers
|
||||
TRACK_MODIFIER_INV = 64, TRACK_MODIFIER_AUTO = 128,
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
TRACK_MODE_BOOST = 32,
|
||||
TRACK_MODE_BOOST = 32,
|
||||
TRACK_MODE_BOOST_INV = TRACK_MODE_BOOST|TRACK_MODIFIER_INV,
|
||||
TRACK_MODE_BOOST_AUTO = TRACK_MODE_BOOST|TRACK_MODIFIER_AUTO,
|
||||
#else
|
||||
TRACK_MODE_BOOST = 0,
|
||||
TRACK_MODE_BOOST = 0,
|
||||
TRACK_MODE_BOOST_INV = 0,
|
||||
TRACK_MODE_BOOST_AUTO = 0,
|
||||
#endif
|
||||
TRACK_MODE_ALL = TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_EXT|TRACK_MODE_BOOST,
|
||||
TRACK_MODE_INV = 64,
|
||||
TRACK_MODE_DCX = TRACK_MODE_DC|TRACK_MODE_INV, TRACK_MODE_AUTOINV = 128};
|
||||
// derived modes; TRACK_ALL is calles that so it does not match TRACK_MODE_*
|
||||
TRACK_ALL = TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_EXT|TRACK_MODE_BOOST,
|
||||
TRACK_MODE_MAIN_INV = TRACK_MODE_MAIN|TRACK_MODIFIER_INV,
|
||||
TRACK_MODE_MAIN_AUTO = TRACK_MODE_MAIN|TRACK_MODIFIER_AUTO,
|
||||
TRACK_MODE_DC_INV = TRACK_MODE_DC|TRACK_MODIFIER_INV,
|
||||
TRACK_MODE_DCX = TRACK_MODE_DC_INV // DCX is other name for historical reasons
|
||||
};
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
@ -273,7 +285,7 @@ class MotorDriver {
|
||||
#endif
|
||||
inline void setMode(TRACK_MODE m) {
|
||||
trackMode = m;
|
||||
invertOutput(trackMode & TRACK_MODE_INV);
|
||||
invertOutput(trackMode & TRACK_MODIFIER_INV);
|
||||
};
|
||||
inline void invertOutput() { // toggles output inversion
|
||||
invertPhase = !invertPhase;
|
||||
|
@ -75,11 +75,19 @@
|
||||
#define SAMD_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
|
||||
#define STM32_STANDARD_MOTOR_SHIELD STANDARD_MOTOR_SHIELD
|
||||
|
||||
#if defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI)
|
||||
// EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC
|
||||
// The Ethernet capable STM32 models cannot use Channel B BRAKE on D8, and must use the ALT pin of D6,
|
||||
// AND cannot use Channel B PWN on D11, but must use the ALT pin of D5
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \
|
||||
new MotorDriver( 5, 13, UNUSED_PIN, 6, A1, 1.27, 5000, A5)
|
||||
#else
|
||||
// EX 8874 based shield connected to a 3V3 system with 12-bit (4096) ADC
|
||||
#define EX8874_SHIELD F("EX8874"), \
|
||||
new MotorDriver( 3, 12, UNUSED_PIN, 9, A0, 1.27, 5000, A4), \
|
||||
new MotorDriver(11, 13, UNUSED_PIN, 8, A1, 1.27, 5000, A5)
|
||||
|
||||
#endif
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
// STANDARD shield on an ESPDUINO-32 (ESP32 in Uno form factor). The shield must be eiter the
|
||||
|
44
Release_Notes/TCA8418.md
Normal file
44
Release_Notes/TCA8418.md
Normal file
@ -0,0 +1,44 @@
|
||||
## TCA8418 ##
|
||||
|
||||
The TCA8418 IC from Texas Instruments is a low cost and very capable GPIO and keyboard scanner. Used as a keyboard scanner, it has 8 rows of 10 columns of IO pins which allow encoding of up to 80 buttons. The IC is available on an Adafruit board with Qwiic I2C interconnect called the "Adafruit TCA8418 Keypad Matrix and GPIO Expander Breakout" and available here for the modest sum of $US6 or so: https://www.adafruit.com/product/4918
|
||||
|
||||
The great advantage of this IC is that the keyboard scanning is done continuously, and it has a 10-element event queue, so even if you don't get to the interrupt immediately, keypress and release events will be held for you. Since it's I2C its very easy to use with any DCC-EX command station.
|
||||
|
||||
The TCA8418 driver presently configures the IC in the full 8x10 keyboard scanning mode, and then maps each key down/key up event to the state of a single vpin for extremely easy use from within EX-RAIL and JMRI as each key looks like an individual sensor.
|
||||
|
||||
This is ideal for mimic panels where you may need a lot of buttons, but with this board you can use just 18 wires to handle as many as 80 buttons.
|
||||
|
||||
By adding a simple HAL statement to myAutomation.h it creates between 1 and 80 buttons it will report back.
|
||||
|
||||
`HAL(TCA8418, firstVpin, numPins, I2CAddress, interruptPin)`
|
||||
|
||||
For example:
|
||||
|
||||
`HAL(TCA8418, 300, 80, 0x34)`
|
||||
|
||||
Creates VPINs 300-379 which you can monitor with EX-RAIL, JMRI sensors etc.
|
||||
|
||||
With an 8x10 key event matrix, the events are numbered using the Rn row pins and Cn column pins as such:
|
||||
|
||||
C0 C1 C2 C3 C4 C5 C6 C7 C8 C9
|
||||
========================================
|
||||
R0| 0 1 2 3 4 5 6 7 8 9
|
||||
R1| 10 11 12 13 14 15 16 17 18 19
|
||||
R2| 20 21 22 23 24 25 26 27 28 29
|
||||
R3| 30 31 32 33 34 35 36 37 38 39
|
||||
R4| 40 41 42 43 44 45 46 47 48 49
|
||||
R5| 50 51 52 53 54 55 56 57 58 59
|
||||
R6| 60 61 62 63 64 65 66 67 68 69
|
||||
R7| 70 71 72 73 74 75 76 77 78 79
|
||||
|
||||
So if you start with the first pin definition being VPIN 300, R0/C0 will be 300 + 0, and R7/C9 will be 300+79 or 379.
|
||||
|
||||
Use something like this on a multiplexor, and with up to 8 of the 8-way multiplexors you could have 64 different TCA8418 boards:
|
||||
|
||||
`HAL(TCA8418, 400, 80, {SubBus_1, 0x34})`
|
||||
|
||||
And if needing an Interrupt pin to speed up operations:
|
||||
`HAL(TCA8418, 300, 80, 0x34, 21)`
|
||||
|
||||
Note that using an interrupt pin speeds up button press acquisition considerably (less than a millisecond vs 10-100), but even with interrupts enabled the code presently checks every 100ms in case the interrupt pin becomes disconnected. Use any available Arduino pin for interrupt monitoring.
|
||||
|
84
Release_Notes/TM1638.md
Normal file
84
Release_Notes/TM1638.md
Normal file
@ -0,0 +1,84 @@
|
||||
## TM1638 ##
|
||||
|
||||
The TM1638 board provides a very cheap way of implementing 8 buttons, 8 leds and an 8 digit 7segment display in a package requiring just 5 Dupont wires (vcc, gnd + 3 GPIO pins) from the command station without soldering.
|
||||
|
||||
|
||||
This is ideal for prototyping and testing, simulating sensors and signals, displaying states etc. For a built layout, this could provide a control for things that are not particularly suited to throttle 'route' buttons, perhaps lineside automations or fiddle yard lane selection.
|
||||
|
||||
By adding a simple HAL statement to myAutomation.h it creates 8 buttons/sensors and 8 leds.
|
||||
|
||||
`HAL(TM1638,500,29,31,33)`
|
||||
Creates VPINs 500-507 And desscribes the GPIO pins used to connect the clk,dio,stb pins on the TM1638 board.
|
||||
|
||||
Setting each of the VPINs will control the associated LED (using for example SET, RESET or BLINK in Exrail or `<z 500> <z -501> from a command).
|
||||
|
||||
Unlike most pins, you can also read the same pin number and get the button state, using Exrail IF/AT/ONBUTTON etc.
|
||||
|
||||
For example:
|
||||
`
|
||||
HAL(TM1638,500,29,31,33)
|
||||
`
|
||||
All the folowing examples assume you are using VPIN 500 as the first, leftmost, led/button on the TM1638 board.
|
||||
|
||||
|
||||
`ONBUTTON(500)
|
||||
SET(500) // light the first led
|
||||
BLINK(501,500,500) // blink the second led
|
||||
SETLOCO(3) FWD(50) // set a loco going
|
||||
AT(501) STOP // press second button to stop
|
||||
RESET(500) RESET(501) // turn leds off
|
||||
DONE
|
||||
`
|
||||
|
||||
Buttons behave like any other sensor, so using `<S 500 500 1>` will cause the command station to issue `<Q 500>` and `<q 500>` messages when the first button is pressed or released.
|
||||
|
||||
Exrail `JMRI_SENSOR(500,8)` will create `<S` commands for all 8 buttons.
|
||||
|
||||
## Using the 7 Segment display ##
|
||||
|
||||
The 8 digit display can be treated as 8 separate digits (left most being the same VPIN as the leftmost button and led) or be written to in sections of any length. Writing uses the existing analogue interface to the common HAL but is awkward to use directly. To make this easier from Exrail, a SEG7 macro provides a remapping to the ANOUT facility that makes more sense.
|
||||
|
||||
SEG7(vpin,value,format)
|
||||
|
||||
The vpin determins which digit to start writing at.
|
||||
The value can be a 32bit unsigned integer but is interpreted differentlky according to the format.
|
||||
|
||||
Format values:
|
||||
1..8 give the length (number of display digits) to fill, and defaults to decimal number with leading zeros.
|
||||
|
||||
1X..8X give the length but display in hex.
|
||||
|
||||
1R..4R treats each byte of the value as raw 7-segment patterns so that it can write letters and symbols using any compination of the 7segments and deciml point.
|
||||
|
||||
There is a useful description here:
|
||||
https://jetpackacademy.com/wp-content/uploads/2018/06/TM1638_cheat_sheet_download.pdf
|
||||
|
||||
|
||||
e.g. SEG7(500,3,4)
|
||||
writes 0003 to first 4 digits of the display
|
||||
SEG7(504,0xcafe,4X)
|
||||
writes CAFE to the last 4 digits
|
||||
SEG7(500,0xdeadbeef,8X)
|
||||
writes dEAdbEEF to all 8 digits.
|
||||
|
||||
Writing raw segment patters requires knowledge of the bit pattern to segment relationship:
|
||||
` 0
|
||||
== 0 ==
|
||||
5| | 1
|
||||
== 6 ==
|
||||
4 | | 2
|
||||
== 3 ==
|
||||
7=decimal point
|
||||
|
||||
Thus Letter A is segments 6 5 4 2 1 0, in bits that is (0 bit on right)
|
||||
0b01110111 or 0x77
|
||||
This is not easy to do my hand and thus a new string type suffix has been introduced to make simple text messages. Note that the HAL interface only has width for 32 bits which is only 4 symbols so writing 8 digits requires two calls.
|
||||
|
||||
e.g. SEG7(500,"Hell"_s7,4R) SEG7(504,"o"_s7,4R)
|
||||
DELAY(1000)
|
||||
SEG7(500,"Worl"_s7,4R) SEG7(504,"d"_s7,4R)
|
||||
|
||||
Note that some letters like k,m,v,x do not have particularly readable 7-segment representations.
|
||||
|
||||
Credit to https://github.com/dvarrel/TM1638 for the basic formulae.
|
||||
|
@ -41,5 +41,3 @@ size_t StringBuffer::write(uint8_t b) {
|
||||
_buffer[_pos_write]='\0';
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
||||
|
@ -35,4 +35,4 @@ class StringBuffer : public Print {
|
||||
char _buffer[buffer_max+2];
|
||||
};
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@ -151,8 +151,8 @@ void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTD(shadowPORTD=PORTD);
|
||||
HAVE_PORTE(shadowPORTE=PORTE);
|
||||
HAVE_PORTF(shadowPORTF=PORTF);
|
||||
HAVE_PORTG(shadowPORTF=PORTG);
|
||||
HAVE_PORTH(shadowPORTF=PORTH);
|
||||
HAVE_PORTG(shadowPORTG=PORTG);
|
||||
HAVE_PORTH(shadowPORTH=PORTH);
|
||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
@ -160,8 +160,8 @@ void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTD(PORTD=shadowPORTD);
|
||||
HAVE_PORTE(PORTE=shadowPORTE);
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
HAVE_PORTG(shadowPORTF=PORTG);
|
||||
HAVE_PORTH(shadowPORTF=PORTH);
|
||||
HAVE_PORTG(PORTG=shadowPORTG);
|
||||
HAVE_PORTH(PORTH=shadowPORTH);
|
||||
}
|
||||
|
||||
// setPROGSignal(), called from interrupt context
|
||||
@ -173,8 +173,8 @@ void TrackManager::setPROGSignal( bool on) {
|
||||
HAVE_PORTD(shadowPORTD=PORTD);
|
||||
HAVE_PORTE(shadowPORTE=PORTE);
|
||||
HAVE_PORTF(shadowPORTF=PORTF);
|
||||
HAVE_PORTG(shadowPORTF=PORTG);
|
||||
HAVE_PORTH(shadowPORTF=PORTH);
|
||||
HAVE_PORTG(shadowPORTG=PORTG);
|
||||
HAVE_PORTH(shadowPORTH=PORTH);
|
||||
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
@ -182,8 +182,8 @@ void TrackManager::setPROGSignal( bool on) {
|
||||
HAVE_PORTD(PORTD=shadowPORTD);
|
||||
HAVE_PORTE(PORTE=shadowPORTE);
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
HAVE_PORTG(shadowPORTF=PORTG);
|
||||
HAVE_PORTH(shadowPORTF=PORTH);
|
||||
HAVE_PORTG(PORTG=shadowPORTG);
|
||||
HAVE_PORTH(PORTH=shadowPORTH);
|
||||
}
|
||||
|
||||
// setDCSignal(), called from normal context
|
||||
@ -387,11 +387,15 @@ bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||
if (params>1 && (p[0]<0 || p[0]>=MAX_TRACKS))
|
||||
return false;
|
||||
|
||||
if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN>
|
||||
if (params==2 && p[1]=="MAIN"_hk) // <= id MAIN>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN);
|
||||
if (params==2 && p[1]=="MAIN_INV"_hk) // <= id MAIN_INV>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN_INV);
|
||||
if (params==2 && p[1]=="MAIN_AUTO"_hk) // <= id MAIN_AUTO>
|
||||
return setTrackMode(p[0],TRACK_MODE_MAIN_AUTO);
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
if (params==2 && p[1]=="PROG"_hk) // <= id PROG>
|
||||
if (params==2 && p[1]=="PROG"_hk) // <= id PROG>
|
||||
return setTrackMode(p[0],TRACK_MODE_PROG);
|
||||
#endif
|
||||
|
||||
@ -402,20 +406,27 @@ bool TrackManager::parseEqualSign(Print *stream, int16_t params, int16_t p[])
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (TRACK_MODE_BOOST != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
|
||||
params==2 && p[1]=="BOOST"_hk) // <= id BOOST>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST);
|
||||
if (TRACK_MODE_BOOST_INV != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST_INV"_hk) // <= id BOOST_INV>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST_INV);
|
||||
if (TRACK_MODE_BOOST_AUTO != 0 && // compile time optimization
|
||||
params==2 && p[1]=="BOOST_AUTO"_hk) // <= id BOOST_AUTO>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST_AUTO);
|
||||
#endif
|
||||
if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV);
|
||||
if (params==2 && p[1]=="AUTO"_hk) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_AUTO);
|
||||
|
||||
if (params==2 && p[1]=="INV"_hk) // <= id INV>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV);
|
||||
if (params==2 && p[1]=="INV"_hk) // <= id INV>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODIFIER_INV);
|
||||
|
||||
if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab>
|
||||
if (params==3 && p[1]=="DC"_hk && p[2]>0) // <= id DC cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
||||
|
||||
if (params==3 && p[1]=="DCX"_hk && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]);
|
||||
if (params==3 && (p[1]=="DC_INV"_hk || // <= id DC_INV cab>
|
||||
p[1]=="DCX"_hk) && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC_INV,p[2]);
|
||||
|
||||
return false;
|
||||
}
|
||||
@ -424,9 +435,9 @@ const FSH* TrackManager::getModeName(TRACK_MODE tm) {
|
||||
const FSH *modename=F("---");
|
||||
|
||||
if (tm & TRACK_MODE_MAIN) {
|
||||
if(tm & TRACK_MODE_AUTOINV)
|
||||
if(tm & TRACK_MODIFIER_AUTO)
|
||||
modename=F("MAIN A");
|
||||
else if (tm & TRACK_MODE_INV)
|
||||
else if (tm & TRACK_MODIFIER_INV)
|
||||
modename=F("MAIN I>\n");
|
||||
else
|
||||
modename=F("MAIN");
|
||||
@ -440,15 +451,15 @@ const FSH* TrackManager::getModeName(TRACK_MODE tm) {
|
||||
else if(tm & TRACK_MODE_EXT)
|
||||
modename=F("EXT");
|
||||
else if(tm & TRACK_MODE_BOOST) {
|
||||
if(tm & TRACK_MODE_AUTOINV)
|
||||
if(tm & TRACK_MODIFIER_AUTO)
|
||||
modename=F("BOOST A");
|
||||
else if (tm & TRACK_MODE_INV)
|
||||
else if (tm & TRACK_MODIFIER_INV)
|
||||
modename=F("BOOST I");
|
||||
else
|
||||
modename=F("BOOST");
|
||||
}
|
||||
else if (tm & TRACK_MODE_DC) {
|
||||
if (tm & TRACK_MODE_INV)
|
||||
if (tm & TRACK_MODIFIER_INV)
|
||||
modename=F("DCX");
|
||||
else
|
||||
modename=F("DC");
|
||||
@ -686,4 +697,3 @@ TRACK_MODE TrackManager::getMode(byte t) {
|
||||
int16_t TrackManager::returnDCAddr(byte t) {
|
||||
return (trackDCAddr[t]);
|
||||
}
|
||||
|
||||
|
@ -527,4 +527,3 @@
|
||||
StringFormatter::send(stream, F("<H %d LCN %d>\n"), _turnoutData.id,
|
||||
!_turnoutData.closed);
|
||||
}
|
||||
|
||||
|
@ -72,8 +72,9 @@ Stream * WifiInterface::wifiStream;
|
||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) \
|
||||
|| defined(ARDUINO_NUCLEO_F446ZE) || defined(ARDUINO_NUCLEO_F412ZG) \
|
||||
|| defined(ARDUINO_NUCLEO_F439ZI) || defined(ARDUINO_NUCLEO_F4X9ZI)
|
||||
#define NUM_SERIAL 2
|
||||
#define NUM_SERIAL 3
|
||||
#define SERIAL1 Serial6
|
||||
#define SERIAL3 Serial2
|
||||
#else
|
||||
#warning This variant of Nucleo not yet explicitly supported
|
||||
#endif
|
||||
|
@ -333,5 +333,19 @@ The configuration file for DCC-EX Command Station
|
||||
// to the sabertooth controller _as_well_. Default: Undefined.
|
||||
//
|
||||
//#define SABERTOOTH 1
|
||||
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// SENSORCAM
|
||||
// ESP32-CAM based video sensors require #define to use appropriate base vpin number.
|
||||
//#define SENSORCAM_VPIN 700
|
||||
// To bypass vPin number, define CAM for ex-rail use e.g. AT(CAM 012) for S12 etc.
|
||||
//#define CAM SENSORCAM_VPIN+
|
||||
//
|
||||
//#define SENSORCAM2_VPIN 600 //define other CAM's if installed.
|
||||
//#define CAM2 SENSORCAM2_VPIN+ //for EX-RAIL commands e.g. IFLT(CAM2 020,1)
|
||||
//
|
||||
// For smoother power-up, define a STARTUP_DELAY to allow CAM to initialise ref images
|
||||
//#define STARTUP_DELAY 5000 // up to 20sec. CS delay
|
||||
//
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -202,7 +202,7 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Nucleo-F411RE]
|
||||
platform = ststm32
|
||||
platform = ststm32 @ 17.6.0
|
||||
board = nucleo_f411re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
@ -211,7 +211,7 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
[env:Nucleo-F446RE]
|
||||
platform = ststm32
|
||||
platform = ststm32 @ 17.6.0
|
||||
board = nucleo_f446re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
@ -223,7 +223,7 @@ monitor_echo = yes
|
||||
; tested as yet
|
||||
;
|
||||
[env:Nucleo-F401RE]
|
||||
platform = ststm32
|
||||
platform = ststm32 @ 17.6.0
|
||||
board = nucleo_f401re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
@ -236,7 +236,7 @@ monitor_echo = yes
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F413ZH]
|
||||
; platform = ststm32
|
||||
; platform = ststm32 @ 17.6.0
|
||||
; board = nucleo_f413zh
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
@ -248,7 +248,7 @@ monitor_echo = yes
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
[env:Nucleo-F446ZE]
|
||||
platform = ststm32
|
||||
platform = ststm32 @ 17.6.0
|
||||
board = nucleo_f446ze
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
@ -260,8 +260,8 @@ monitor_echo = yes
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F412ZG]
|
||||
; platform = ststm32
|
||||
; board = blah_f412zg
|
||||
; platform = ststm32 @ 17.6.0
|
||||
; board = nucleo_f412zg
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
@ -272,12 +272,12 @@ monitor_echo = yes
|
||||
; Experimental - Ethernet work still in progress
|
||||
;
|
||||
[env:Nucleo-F429ZI]
|
||||
platform = ststm32
|
||||
platform = ststm32 @ 17.6.0
|
||||
board = nucleo_f429zi
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
stm32duino/STM32Ethernet @ ^1.3.0
|
||||
stm32duino/STM32duino LwIP @ ^2.1.2
|
||||
stm32duino/STM32Ethernet @ ^1.4.0
|
||||
stm32duino/STM32duino LwIP @ ^2.1.3
|
||||
MDNS_Generic
|
||||
lib_ignore = WiFi101
|
||||
WiFi101_Generic
|
||||
@ -289,18 +289,29 @@ monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
upload_protocol = stlink
|
||||
|
||||
; [env:Nucleo-F429ZI]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f429zi
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; arduino-libraries/Ethernet @ ^2.0.1
|
||||
; stm32duino/STM32Ethernet @ ^1.3.0
|
||||
; stm32duino/STM32duino LwIP @ ^2.1.2
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
; upload_protocol = stlink
|
||||
; Experimental - Ethernet work still in progress
|
||||
;
|
||||
[env:Nucleo-F439ZI]
|
||||
platform = ststm32 @ 17.6.0
|
||||
; board = nucleo_f439zi
|
||||
; Temporarily treat it as an F429ZI (they are code compatible) until
|
||||
; the PR to PlatformIO to update the F439ZI JSON file is available
|
||||
; PMA - 28-Sep-2024
|
||||
board = nucleo_f429zi
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
stm32duino/STM32Ethernet @ ^1.4.0
|
||||
stm32duino/STM32duino LwIP @ ^2.1.3
|
||||
MDNS_Generic
|
||||
lib_ignore = WiFi101
|
||||
WiFi101_Generic
|
||||
WiFiEspAT
|
||||
WiFiMulti_Generic
|
||||
WiFiNINA_Generic
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
upload_protocol = stlink
|
||||
|
||||
[env:Teensy3_2]
|
||||
platform = teensy
|
||||
|
15
version.h
15
version.h
@ -3,8 +3,19 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.2.80"
|
||||
// 5.2.80 - EthernetInterface upgrade
|
||||
#define VERSION "5.2.91"
|
||||
// 5.2.91 - Bugfix: Neopixel I2C overlap check
|
||||
// 5.2.90 - Bugfix: EXRAIL EXTT_TURNTABLE() now has description as optional in line with ocumentation (also fixed DCC_TURNTABLE)
|
||||
// 5.2.89 - EXRAIL SET(vpin[,npins]) RESET(vpin,[,npins]) pin range manipulation
|
||||
// 5.2.88 - Fix bug where EX-Turntable objects return incorrect angle for home with <JP x>
|
||||
// 5.2.87 - CamParser and IO_EXSensorCam driver
|
||||
// 5.2.86 - IO_TCA8418 driver for keypad matrix input now fully functioning, including being able to use an interrupt pin
|
||||
// 5.2.85 - IO_TM1638 driver, SEG7 Exrail macro and _s7 segment pattern generator.
|
||||
// 5.2.84 - Fix TrackManager setDCCSignal and setPROGSignal for STM32 shadowing of PORTG/PORTH - this time it really is correct!
|
||||
// 5.2.83 - Various STM32 related fixes for serial ports, I2C pullups now turned off, and shadowing of PORTG/PORTH for TrackManager now correct
|
||||
// 5.2.82 - TrackManager and EXRAIL: Introduce more consistent names for <= ...> and SET_TRACK
|
||||
// 5.2.81 - STM32 Ethernet boards support, also now have specific EX8874 motor driver definition
|
||||
// 5.2.80 - EthernetInterface upgrade, including STM32 Ethernet support
|
||||
// 5.2.79 - serial manager loop that handles quoted strings
|
||||
// - WiFiESP32 reconfig
|
||||
// 5.2.78 - NeoPixel support.
|
||||
|
Loading…
Reference in New Issue
Block a user