mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-30 19:03:44 +02:00
Compare commits
11 Commits
devel-esp3
...
I2CDFplaye
Author | SHA1 | Date | |
---|---|---|---|
|
74c5a974e2 | ||
|
0e9caa11c8 | ||
|
46673007cc | ||
|
c468979501 | ||
|
0feb2c74e7 | ||
|
e74c619fac | ||
|
32ecbbe147 | ||
|
b8e0185540 | ||
|
5e0cf8eb74 | ||
|
44ce1c0cfa | ||
|
22b066c400 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -13,5 +13,3 @@ myFilter.cpp
|
||||
my*.h
|
||||
!my*.example.h
|
||||
compile_commands.json
|
||||
newcode.txt.old
|
||||
UserAddin.txt
|
||||
|
@@ -162,7 +162,7 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTurntable(int16_t id, uint8_t position, bool moving) {
|
||||
broadcastReply(COMMAND_TYPE, F("<I %d %d %d>\n"), id, position, moving);
|
||||
broadcastReply(COMMAND_TYPE, F("<i %d %d %d>\n"), id, position, moving);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
||||
@@ -248,11 +248,6 @@ void CommandDistributor::broadcastLoco(byte slot) {
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastPower() {
|
||||
char pstr[] = "? x";
|
||||
for(byte t=0; t<8; t++)
|
||||
if (TrackManager::getPower(t, pstr))
|
||||
broadcastReply(COMMAND_TYPE, F("<p%s>\n"),pstr);
|
||||
|
||||
bool main=TrackManager::getMainPower()==POWERMODE::ON;
|
||||
bool prog=TrackManager::getProgPower()==POWERMODE::ON;
|
||||
bool join=TrackManager::isJoined();
|
||||
@@ -274,14 +269,6 @@ void CommandDistributor::broadcastRaw(clientType type, char * msg) {
|
||||
broadcastReply(type, F("%s"),msg);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format, trackLetter, dcAddr);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastRouteState(uint16_t routeId, byte state ) {
|
||||
broadcastReply(COMMAND_TYPE, F("<jB %d %d>\n"),routeId,state);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastRouteCaption(uint16_t routeId, const FSH* caption ) {
|
||||
broadcastReply(COMMAND_TYPE, F("<jB %d \"%S\">\n"),routeId,caption);
|
||||
void CommandDistributor::broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr) {
|
||||
broadcastReply(COMMAND_TYPE, format,trackLetter,dcAddr);
|
||||
}
|
||||
|
@@ -55,12 +55,9 @@ public :
|
||||
static int16_t retClockTime();
|
||||
static void broadcastPower();
|
||||
static void broadcastRaw(clientType type,char * msg);
|
||||
static void broadcastTrackState(const FSH* format,byte trackLetter, int16_t dcAddr);
|
||||
static void broadcastTrackState(const FSH* format,byte trackLetter,int16_t dcAddr);
|
||||
template<typename... Targs> static void broadcastReply(clientType type, Targs... msg);
|
||||
static void forget(byte clientId);
|
||||
static void broadcastRouteState(uint16_t routeId,byte state);
|
||||
static void broadcastRouteCaption(uint16_t routeId,const FSH * caption);
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
5407
Console log
Normal file
5407
Console log
Normal file
File diff suppressed because it is too large
Load Diff
244
DCCEXParser.cpp
244
DCCEXParser.cpp
@@ -49,7 +49,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
b, Write CV bit on main
|
||||
B, Write CV bit
|
||||
c, Request current command
|
||||
C, configure the CS
|
||||
C,
|
||||
d,
|
||||
D, Diagnostic commands
|
||||
e, Erase EEPROM
|
||||
@@ -60,14 +60,14 @@ Once a new OPCODE is decided upon, update this list.
|
||||
G,
|
||||
h,
|
||||
H, Turnout state broadcast
|
||||
i, Server details string
|
||||
I, Turntable object command, control, and broadcast
|
||||
i, Reserved for future use - Turntable object broadcast
|
||||
I, Reserved for future use - Turntable object command and control
|
||||
j, Throttle responses
|
||||
J, Throttle queries
|
||||
k, Reserved for future use - Potentially Railcom
|
||||
K, Reserved for future use - Potentially Railcom
|
||||
l, Loco speedbyte/function map broadcast
|
||||
L, Reserved for LCC interface (implemented in EXRAIL)
|
||||
L,
|
||||
m,
|
||||
M, Write DCC packet
|
||||
n,
|
||||
@@ -157,7 +157,6 @@ const int16_t HASH_KEYWORD_VPIN=-415;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
const int16_t HASH_KEYWORD_G='G';
|
||||
const int16_t HASH_KEYWORD_H='H';
|
||||
const int16_t HASH_KEYWORD_I='I';
|
||||
const int16_t HASH_KEYWORD_O='O';
|
||||
const int16_t HASH_KEYWORD_P='P';
|
||||
@@ -553,67 +552,70 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
case '1': // POWERON <1 [MAIN|PROG|JOIN]>
|
||||
{
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::ON);
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
bool join=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
main=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <1 MAIN>
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::ON);
|
||||
}
|
||||
#ifndef DISABLE_PROG
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
TrackManager::setJoin(true);
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN|TRACK_MODE_PROG, POWERMODE::ON);
|
||||
}
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::ON);
|
||||
}
|
||||
#endif
|
||||
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
|
||||
byte t = (p[0] - 'A');
|
||||
TrackManager::setTrackPower(POWERMODE::ON, t);
|
||||
//StringFormatter::send(stream, F("<p1 %c>\n"), t+'A');
|
||||
}
|
||||
else break; // will reply <X>
|
||||
else if (p[0] == HASH_KEYWORD_JOIN) { // <1 JOIN>
|
||||
main=true;
|
||||
prog=true;
|
||||
join=true;
|
||||
}
|
||||
CommandDistributor::broadcastPower();
|
||||
//TrackManager::streamTrackState(NULL,t);
|
||||
|
||||
return;
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <1 PROG>
|
||||
prog=true;
|
||||
}
|
||||
#endif
|
||||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
TrackManager::setJoin(join);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::ON);
|
||||
if (prog) TrackManager::setProgPower(POWERMODE::ON);
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
|
||||
case '0': // POWEROFF <0 [MAIN | PROG] >
|
||||
{
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_ALL, POWERMODE::OFF);
|
||||
bool main=false;
|
||||
bool prog=false;
|
||||
if (params > 1) break;
|
||||
if (params==0) { // All
|
||||
main=true;
|
||||
prog=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
main=true;
|
||||
}
|
||||
if (params==1) {
|
||||
if (p[0]==HASH_KEYWORD_MAIN) { // <0 MAIN>
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(TRACK_MODE_MAIN, POWERMODE::OFF);
|
||||
}
|
||||
#ifndef DISABLE_PROG
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setTrackPower(TRACK_MODE_PROG, POWERMODE::OFF);
|
||||
}
|
||||
#endif
|
||||
else if (p[0] >= HASH_KEYWORD_A && p[0] <= HASH_KEYWORD_H) { // <1 A-H>
|
||||
byte t = (p[0] - 'A');
|
||||
TrackManager::setJoin(false);
|
||||
TrackManager::setTrackPower(POWERMODE::OFF, t);
|
||||
//StringFormatter::send(stream, F("<p0 %c>\n"), t+'A');
|
||||
}
|
||||
else break; // will reply <X>
|
||||
else if (p[0]==HASH_KEYWORD_PROG) { // <0 PROG>
|
||||
prog=true;
|
||||
}
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
#endif
|
||||
else break; // will reply <X>
|
||||
}
|
||||
|
||||
TrackManager::setJoin(false);
|
||||
if (main) TrackManager::setMainPower(POWERMODE::OFF);
|
||||
if (prog) {
|
||||
TrackManager::progTrackBoosted=false; // Prog track boost mode will not outlive prog track off
|
||||
TrackManager::setProgPower(POWERMODE::OFF);
|
||||
}
|
||||
|
||||
CommandDistributor::broadcastPower();
|
||||
return;
|
||||
}
|
||||
|
||||
case '!': // ESTOP ALL <!>
|
||||
DCC::setThrottle(0,1,1); // this broadcasts speed 1(estop) and sets all reminders to speed 1.
|
||||
return;
|
||||
@@ -628,7 +630,7 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
Sensor::printAll(stream);
|
||||
return;
|
||||
|
||||
case 's': // STATUS <s>
|
||||
case 's': // <s>
|
||||
StringFormatter::send(stream, F("<iDCC-EX V-%S / %S / %S G-%S>\n"), F(VERSION), F(ARDUINO_TYPE), DCC::getMotorShieldName(), F(GITHUB_SHA));
|
||||
CommandDistributor::broadcastPower(); // <s> is the only "get power status" command we have
|
||||
Turnout::printAll(stream); //send all Turnout states
|
||||
@@ -649,17 +651,13 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case ' ': // < >
|
||||
StringFormatter::send(stream, F("\n"));
|
||||
return;
|
||||
case 'C': // CONFIG <C [params]>
|
||||
if (parseC(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
#ifndef DISABLE_DIAG
|
||||
case 'D': // DIAG <D [params]>
|
||||
case 'D': // < >
|
||||
if (parseD(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
#endif
|
||||
case '=': // TACK MANAGER CONTROL <= [params]>
|
||||
case '=': // <= Track manager control >
|
||||
if (TrackManager::parseJ(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
@@ -716,11 +714,27 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
TrackManager::reportCurrent(stream); // <g limit...limit>
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_A: // <JA> intercepted by EXRAIL// <JA> returns automations/routes
|
||||
if (params!=1) break; // <JA>
|
||||
StringFormatter::send(stream, F("<jA>\n"));
|
||||
return;
|
||||
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
if (params==1) {// <JA>
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
SENDFLASHLIST(stream,RMFT2::routeIdList)
|
||||
SENDFLASHLIST(stream,RMFT2::automationIdList)
|
||||
#endif
|
||||
}
|
||||
else { // <JA id>
|
||||
StringFormatter::send(stream,F(" %d %c \"%S\""),
|
||||
id,
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
RMFT2::getRouteType(id), // A/R
|
||||
RMFT2::getRouteDescription(id)
|
||||
#else
|
||||
'X',F("")
|
||||
#endif
|
||||
);
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
case HASH_KEYWORD_R: // <JR> returns rosters
|
||||
StringFormatter::send(stream, F("<jR"));
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
@@ -728,15 +742,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
SENDFLASHLIST(stream,RMFT2::rosterIdList)
|
||||
}
|
||||
else {
|
||||
auto rosterName= RMFT2::getRosterName(id);
|
||||
if (!rosterName) rosterName=F("");
|
||||
|
||||
auto functionNames= RMFT2::getRosterFunctions(id);
|
||||
if (!functionNames) functionNames=RMFT2::getRosterFunctions(0);
|
||||
if (!functionNames) functionNames=F("");
|
||||
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, rosterName, functionNames);
|
||||
}
|
||||
const FSH * functionNames= RMFT2::getRosterFunctions(id);
|
||||
StringFormatter::send(stream,F(" %d \"%S\" \"%S\""),
|
||||
id, RMFT2::getRosterName(id),
|
||||
functionNames == NULL ? RMFT2::getRosterFunctions(0) : functionNames);
|
||||
}
|
||||
#endif
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
@@ -829,9 +839,6 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
break;
|
||||
#endif
|
||||
|
||||
case 'L': // LCC interface implemented in EXRAIL parser
|
||||
break; // Will <X> if not intercepted by EXRAIL
|
||||
|
||||
default: //anything else will diagnose and drop out to <X>
|
||||
DIAG(F("Opcode=%c params=%d"), opcode, params);
|
||||
for (int i = 0; i < params; i++)
|
||||
@@ -1037,27 +1044,19 @@ bool DCCEXParser::parseS(Print *stream, int16_t params, int16_t p[])
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
if (params == 0)
|
||||
return false;
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
||||
switch (p[0])
|
||||
{
|
||||
#ifndef DISABLE_PROG
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
TrackManager::progTrackBoosted=true;
|
||||
return true;
|
||||
#endif
|
||||
case HASH_KEYWORD_RESET:
|
||||
DCCTimer::reset();
|
||||
break; // and <X> if we didnt restart
|
||||
case HASH_KEYWORD_SPEED28:
|
||||
DCC::setGlobalSpeedsteps(28);
|
||||
DIAG(F("28 Speedsteps"));
|
||||
case HASH_KEYWORD_CABS: // <D CABS>
|
||||
DCC::displayCabList(stream);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_SPEED128:
|
||||
DCC::setGlobalSpeedsteps(128);
|
||||
DIAG(F("128 Speedsteps"));
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
StringFormatter::send(stream, F("Free memory=%d\n"), DCCTimer::getMinimumFreeMemory());
|
||||
return true;
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
@@ -1077,35 +1076,12 @@ bool DCCEXParser::parseC(Print *stream, int16_t params, int16_t p[]) {
|
||||
LCD(0, F("Ack Retry=%d Sum=%d"), p[2], DCCACK::setAckRetry(p[2])); // <D ACK RETRY 2>
|
||||
}
|
||||
} else {
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
||||
|
||||
DIAG(F("Ack diag %S"), onOff ? F("on") : F("off"));
|
||||
StringFormatter::send(stream, F("Ack diag %S\n"), onOff ? F("on") : F("off"));
|
||||
Diag::ACK = onOff;
|
||||
}
|
||||
return true;
|
||||
#endif
|
||||
|
||||
default: // invalid/unknown
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
if (params == 0)
|
||||
return false;
|
||||
bool onOff = (params > 0) && (p[1] == 1 || p[1] == HASH_KEYWORD_ON); // dont care if other stuff or missing... just means off
|
||||
switch (p[0])
|
||||
{
|
||||
case HASH_KEYWORD_CABS: // <D CABS>
|
||||
DCC::displayCabList(stream);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RAM: // <D RAM>
|
||||
DIAG(F("Free memory=%d"), DCCTimer::getMinimumFreeMemory());
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_CMD: // <D CMD ON/OFF>
|
||||
Diag::CMD = onOff;
|
||||
return true;
|
||||
@@ -1127,14 +1103,34 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
Diag::LCN = onOff;
|
||||
return true;
|
||||
#endif
|
||||
#ifndef DISABLE_PROG
|
||||
case HASH_KEYWORD_PROGBOOST:
|
||||
TrackManager::progTrackBoosted=true;
|
||||
return true;
|
||||
#endif
|
||||
case HASH_KEYWORD_RESET:
|
||||
DCCTimer::reset();
|
||||
break; // and <X> if we didnt restart
|
||||
|
||||
|
||||
#ifndef DISABLE_EEPROM
|
||||
case HASH_KEYWORD_EEPROM: // <D EEPROM NumEntries>
|
||||
if (params >= 2)
|
||||
EEStore::dump(p[1]);
|
||||
return true;
|
||||
#endif
|
||||
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
|
||||
|
||||
case HASH_KEYWORD_SPEED28:
|
||||
DCC::setGlobalSpeedsteps(28);
|
||||
StringFormatter::send(stream, F("28 Speedsteps"));
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_SPEED128:
|
||||
DCC::setGlobalSpeedsteps(128);
|
||||
StringFormatter::send(stream, F("128 Speedsteps"));
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_SERVO: // <D SERVO vpin position [profile]>
|
||||
case HASH_KEYWORD_ANOUT: // <D ANOUT vpin position [profile]>
|
||||
IODevice::writeAnalogue(p[1], p[2], params>3 ? p[3] : 0);
|
||||
break;
|
||||
@@ -1157,7 +1153,7 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
break;
|
||||
|
||||
default: // invalid/unknown
|
||||
return parseC(stream, params, p);
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -1185,7 +1181,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
if (tto) {
|
||||
bool type = tto->isEXTT();
|
||||
uint8_t position = tto->getPosition();
|
||||
StringFormatter::send(stream, F("<I %d %d>\n"), type, position);
|
||||
StringFormatter::send(stream, F("<i %d %d>\n"), type, position);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1211,7 +1207,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
if (!DCCTurntable::create(p[0])) return false;
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
tto->addPosition(0, 0, p[2]);
|
||||
StringFormatter::send(stream, F("<I>\n"));
|
||||
StringFormatter::send(stream, F("<i>\n"));
|
||||
} else {
|
||||
if (!tto) return false;
|
||||
if (!tto->isEXTT()) return false;
|
||||
@@ -1228,7 +1224,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
if (!EXTTTurntable::create(p[0], (VPIN)p[2])) return false;
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
tto->addPosition(0, 0, p[3]);
|
||||
StringFormatter::send(stream, F("<I>\n"));
|
||||
StringFormatter::send(stream, F("<i>\n"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1242,7 +1238,7 @@ bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
// tto must exist, no more than 48 positions, angle 0 - 3600
|
||||
if (!tto || p[2] > 48 || p[4] < 0 || p[4] > 3600) return false;
|
||||
tto->addPosition(p[2], p[3], p[4]);
|
||||
StringFormatter::send(stream, F("<I>\n"));
|
||||
StringFormatter::send(stream, F("<i>\n"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
@@ -49,7 +49,6 @@ struct DCCEXParser
|
||||
static bool parseZ(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseS(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parsef(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseC(Print * stream, int16_t params, int16_t p[]);
|
||||
static bool parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
#ifndef IO_NO_HAL
|
||||
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
||||
|
@@ -83,34 +83,16 @@ int DCCTimer::freeMemory() {
|
||||
#include <soc/sens_struct.h>
|
||||
#undef ADC_INPUT_MAX_VALUE
|
||||
#define ADC_INPUT_MAX_VALUE 4095 // 12 bit ADC
|
||||
#if defined(ARDUINO_ESP32_DEV)
|
||||
#define pinToADC1Channel(X) (adc1_channel_t)(((X) > 35) ? (X)-36 : (X)-28)
|
||||
#elif defined(ARDUINO_ESP32S3_DEV)
|
||||
#define pinToADC1Channel(X) (adc1_channel_t)(((X) < 11) ? (X)-2 : (X)-11)
|
||||
#else
|
||||
#warning This ESP32 variant is not supported!
|
||||
#endif
|
||||
|
||||
int IRAM_ATTR local_adc1_get_raw(int channel) {
|
||||
uint16_t adc_value;
|
||||
#if defined(ARDUINO_ESP32_DEV)
|
||||
SENS.sar_meas_start1.sar1_en_pad = (1 << channel); // only one channel is selected
|
||||
while (SENS.sar_slave_addr1.meas_status != 0);
|
||||
SENS.sar_meas_start1.meas1_start_sar = 0;
|
||||
SENS.sar_meas_start1.meas1_start_sar = 1;
|
||||
while (SENS.sar_meas_start1.meas1_done_sar == 0);
|
||||
adc_value = SENS.sar_meas_start1.meas1_data_sar;
|
||||
#elif defined(ARDUINO_ESP32S3_DEV)
|
||||
// Between variants... Espressif decides to rename things... sigh
|
||||
SENS.sar_meas1_ctrl2.sar1_en_pad = (1 << channel); // only one channel is selected
|
||||
while (SENS.sar_slave_addr1.meas_status != 0);
|
||||
SENS.sar_meas1_ctrl2.meas1_start_sar = 0;
|
||||
SENS.sar_meas1_ctrl2.meas1_start_sar = 1;
|
||||
while (SENS.sar_meas1_ctrl2.meas1_done_sar == 0);
|
||||
adc_value = SENS.sar_meas1_ctrl2.meas1_data_sar;
|
||||
#else
|
||||
#warning This ESP32 variant is not supported!
|
||||
#endif
|
||||
return adc_value;
|
||||
}
|
||||
|
||||
|
360
EXRAIL2.cpp
360
EXRAIL2.cpp
@@ -55,6 +55,22 @@
|
||||
#include "Turntables.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
// Command parsing keywords
|
||||
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
||||
const int16_t HASH_KEYWORD_ON = 2657;
|
||||
const int16_t HASH_KEYWORD_START=23232;
|
||||
const int16_t HASH_KEYWORD_RESERVE=11392;
|
||||
const int16_t HASH_KEYWORD_FREE=-23052;
|
||||
const int16_t HASH_KEYWORD_LATCH=1618;
|
||||
const int16_t HASH_KEYWORD_UNLATCH=1353;
|
||||
const int16_t HASH_KEYWORD_PAUSE=-4142;
|
||||
const int16_t HASH_KEYWORD_RESUME=27609;
|
||||
const int16_t HASH_KEYWORD_KILL=5218;
|
||||
const int16_t HASH_KEYWORD_ALL=3457;
|
||||
const int16_t HASH_KEYWORD_ROUTES=-3702;
|
||||
const int16_t HASH_KEYWORD_RED=26099;
|
||||
const int16_t HASH_KEYWORD_AMBER=18713;
|
||||
const int16_t HASH_KEYWORD_GREEN=-31493;
|
||||
|
||||
// One instance of RMFT clas is used for each "thread" in the automation.
|
||||
// Each thread manages a loco on a journey through the layout, and/or may manage a scenery automation.
|
||||
@@ -69,8 +85,8 @@ RMFT2 * RMFT2::pausingTask=NULL; // Task causing a PAUSE.
|
||||
// when pausingTask is set, that is the ONLY task that gets any service,
|
||||
// and all others will have their locos stopped, then resumed after the pausing task resumes.
|
||||
byte RMFT2::flags[MAX_FLAGS];
|
||||
Print * RMFT2::LCCSerial=0;
|
||||
LookList * RMFT2::routeLookup=NULL;
|
||||
|
||||
LookList * RMFT2::sequenceLookup=NULL;
|
||||
LookList * RMFT2::onThrowLookup=NULL;
|
||||
LookList * RMFT2::onCloseLookup=NULL;
|
||||
LookList * RMFT2::onActivateLookup=NULL;
|
||||
@@ -84,8 +100,9 @@ LookList * RMFT2::onClockLookup=NULL;
|
||||
LookList * RMFT2::onRotateLookup=NULL;
|
||||
#endif
|
||||
LookList * RMFT2::onOverloadLookup=NULL;
|
||||
byte * RMFT2::routeStateArray=nullptr;
|
||||
const FSH * * RMFT2::routeCaptionArray=nullptr;
|
||||
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
|
||||
// getOperand instance version, uses progCounter from instance.
|
||||
uint16_t RMFT2::getOperand(byte n) {
|
||||
@@ -103,7 +120,6 @@ uint16_t RMFT2::getOperand(int progCounter,byte n) {
|
||||
LookList::LookList(int16_t size) {
|
||||
m_size=size;
|
||||
m_loaded=0;
|
||||
m_chain=nullptr;
|
||||
if (size) {
|
||||
m_lookupArray=new int16_t[size];
|
||||
m_resultArray=new int16_t[size];
|
||||
@@ -121,35 +137,8 @@ int16_t LookList::find(int16_t value) {
|
||||
for (int16_t i=0;i<m_size;i++) {
|
||||
if (m_lookupArray[i]==value) return m_resultArray[i];
|
||||
}
|
||||
return m_chain ? m_chain->find(value) :-1;
|
||||
}
|
||||
void LookList::chain(LookList * chain) {
|
||||
m_chain=chain;
|
||||
}
|
||||
void LookList::handleEvent(const FSH* reason,int16_t id) {
|
||||
// New feature... create multiple ONhandlers
|
||||
for (int i=0;i<m_size;i++)
|
||||
if (m_lookupArray[i]==id)
|
||||
RMFT2::startNonRecursiveTask(reason,id,m_resultArray[i]);
|
||||
}
|
||||
|
||||
|
||||
void LookList::stream(Print * _stream) {
|
||||
for (int16_t i=0;i<m_size;i++) {
|
||||
_stream->print(" ");
|
||||
_stream->print(m_lookupArray[i]);
|
||||
}
|
||||
}
|
||||
|
||||
int16_t LookList::findPosition(int16_t value) {
|
||||
for (int16_t i=0;i<m_size;i++) {
|
||||
if (m_lookupArray[i]==value) return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
int16_t LookList::size() {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
int progCounter;
|
||||
@@ -182,36 +171,28 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
for (int f=0;f<MAX_FLAGS;f++) flags[f]=0;
|
||||
|
||||
// create lookups
|
||||
routeLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION);
|
||||
routeLookup->chain(LookListLoader(OPCODE_SEQUENCE));
|
||||
if (compileFeatures && FEATURE_ROUTESTATE) {
|
||||
routeStateArray=(byte *)calloc(routeLookup->size(),sizeof(byte));
|
||||
routeCaptionArray=(const FSH * *)calloc(routeLookup->size(),sizeof(const FSH *));
|
||||
}
|
||||
sequenceLookup=LookListLoader(OPCODE_ROUTE, OPCODE_AUTOMATION,OPCODE_SEQUENCE);
|
||||
onThrowLookup=LookListLoader(OPCODE_ONTHROW);
|
||||
onCloseLookup=LookListLoader(OPCODE_ONCLOSE);
|
||||
onActivateLookup=LookListLoader(OPCODE_ONACTIVATE);
|
||||
onDeactivateLookup=LookListLoader(OPCODE_ONDEACTIVATE);
|
||||
onRedLookup=LookListLoader(OPCODE_ONRED);
|
||||
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
onChangeLookup=LookListLoader(OPCODE_ONCHANGE);
|
||||
onClockLookup=LookListLoader(OPCODE_ONTIME);
|
||||
#ifndef IO_NO_HAL
|
||||
onRotateLookup=LookListLoader(OPCODE_ONROTATE);
|
||||
#endif
|
||||
onOverloadLookup=LookListLoader(OPCODE_ONOVERLOAD);
|
||||
// onLCCLookup is not the same so not loaded here.
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
// add sequences onRoutines to the lookups
|
||||
if (compileFeatures & FEATURE_SIGNAL) {
|
||||
onRedLookup=LookListLoader(OPCODE_ONRED);
|
||||
onAmberLookup=LookListLoader(OPCODE_ONAMBER);
|
||||
onGreenLookup=LookListLoader(OPCODE_ONGREEN);
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
doSignal(sigid & SIGNAL_ID_MASK, SIGNAL_RED);
|
||||
}
|
||||
}
|
||||
|
||||
int progCounter;
|
||||
for (progCounter=0;; SKIPOP){
|
||||
@@ -330,15 +311,184 @@ void RMFT2::setTurntableHiddenState(Turntable * tto) {
|
||||
#endif
|
||||
|
||||
char RMFT2::getRouteType(int16_t id) {
|
||||
int16_t progCounter=routeLookup->find(id);
|
||||
if (progCounter>=0) {
|
||||
byte type=GET_OPCODE;
|
||||
if (type==OPCODE_ROUTE) return 'R';
|
||||
if (type==OPCODE_AUTOMATION) return 'A';
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(routeIdList,i);
|
||||
if (rid==INT16_MAX) break;
|
||||
if (rid==id) return 'R';
|
||||
}
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(automationIdList,i);
|
||||
if (rid==INT16_MAX) break;
|
||||
if (rid==id) return 'A';
|
||||
}
|
||||
return 'X';
|
||||
}
|
||||
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
|
||||
(void)stream; // avoid compiler warning if we don't access this parameter
|
||||
bool reject=false;
|
||||
switch(opcode) {
|
||||
|
||||
case 'D':
|
||||
if (p[0]==HASH_KEYWORD_EXRAIL) { // <D EXRAIL ON/OFF>
|
||||
diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1);
|
||||
opcode=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '/': // New EXRAIL command
|
||||
reject=!parseSlash(stream,paramCount,p);
|
||||
opcode=0;
|
||||
break;
|
||||
|
||||
default: // other commands pass through
|
||||
break;
|
||||
}
|
||||
if (reject) {
|
||||
opcode=0;
|
||||
StringFormatter::send(stream,F("<X>"));
|
||||
}
|
||||
}
|
||||
|
||||
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
|
||||
if (paramCount==0) { // STATUS
|
||||
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
|
||||
(int)(task->taskId),task->progCounter,task->loco,
|
||||
task->invert?'I':' ',
|
||||
task->speedo,
|
||||
task->forward?'F':'R'
|
||||
);
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
// Now stream the flags
|
||||
for (int id=0;id<MAX_FLAGS; id++) {
|
||||
byte flag=flags[id];
|
||||
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
}
|
||||
}
|
||||
// do the signals
|
||||
// flags[n] represents the state of the nth signal in the table
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||
sigid & SIGNAL_ID_MASK);
|
||||
}
|
||||
|
||||
StringFormatter::send(stream,F(" *>\n"));
|
||||
return true;
|
||||
}
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_PAUSE: // </ PAUSE>
|
||||
if (paramCount!=1) return false;
|
||||
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESUME: // </ RESUME>
|
||||
if (paramCount!=1) return false;
|
||||
pausingTask=NULL;
|
||||
{
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->loco) task->driveLoco(task->speedo);
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
case HASH_KEYWORD_START: // </ START [cab] route >
|
||||
if (paramCount<2 || paramCount>3) return false;
|
||||
{
|
||||
int route=(paramCount==2) ? p[1] : p[2];
|
||||
uint16_t cab=(paramCount==2)? 0 : p[1];
|
||||
int pc=sequenceLookup->find(route);
|
||||
if (pc<0) return false;
|
||||
RMFT2* task=new RMFT2(pc);
|
||||
task->loco=cab;
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
return true;
|
||||
}
|
||||
|
||||
// all other / commands take 1 parameter
|
||||
if (paramCount!=2 ) return false;
|
||||
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_KILL: // Kill taskid|ALL
|
||||
{
|
||||
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->taskId==p[1]) {
|
||||
task->kill(F("KILL"));
|
||||
return true;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case HASH_KEYWORD_RESERVE: // force reserve a section
|
||||
return setFlag(p[1],SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_FREE: // force free a section
|
||||
return setFlag(p[1],0,SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_LATCH:
|
||||
return setFlag(p[1], LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_UNLATCH:
|
||||
return setFlag(p[1], 0, LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_RED:
|
||||
doSignal(p[1],SIGNAL_RED);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_AMBER:
|
||||
doSignal(p[1],SIGNAL_AMBER);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_GREEN:
|
||||
doSignal(p[1],SIGNAL_GREEN);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// This emits Routes and Automations to Withrottle
|
||||
// Automations are given a state to set the button to "handoff" which implies
|
||||
// handing over the loco to the automation.
|
||||
// Routes are given "Set" buttons and do not cause the loco to be handed over.
|
||||
|
||||
|
||||
|
||||
RMFT2::RMFT2(int progCtr) {
|
||||
progCounter=progCtr;
|
||||
@@ -387,7 +537,7 @@ RMFT2::~RMFT2() {
|
||||
}
|
||||
|
||||
void RMFT2::createNewTask(int route, uint16_t cab) {
|
||||
int pc=routeLookup->find(route);
|
||||
int pc=sequenceLookup->find(route);
|
||||
if (pc<0) return;
|
||||
RMFT2* task=new RMFT2(pc);
|
||||
task->loco=cab;
|
||||
@@ -630,27 +780,13 @@ void RMFT2::loop2() {
|
||||
TrackManager::setJoin(false);
|
||||
CommandDistributor::broadcastPower();
|
||||
break;
|
||||
|
||||
case OPCODE_SET_POWER:
|
||||
// operand is TRACK_POWER , trackid
|
||||
//byte thistrack=getOperand(1);
|
||||
switch (operand) {
|
||||
case TRACK_POWER_0:
|
||||
TrackManager::setTrackPower(POWERMODE::OFF, getOperand(1));
|
||||
break;
|
||||
case TRACK_POWER_1:
|
||||
TrackManager::setTrackPower(POWERMODE::ON, getOperand(1));
|
||||
break;
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case OPCODE_SET_TRACK:
|
||||
// operand is trackmode<<8 | track id
|
||||
// If DC/DCX use my loco for DC address
|
||||
{
|
||||
TRACK_MODE mode = (TRACK_MODE)(operand>>8);
|
||||
int16_t cab=(mode & TRACK_MODE_DC) ? loco : 0;
|
||||
int16_t cab=(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) ? loco : 0;
|
||||
TrackManager::setTrackMode(operand & 0x0F, mode, cab);
|
||||
}
|
||||
break;
|
||||
@@ -788,7 +924,7 @@ void RMFT2::loop2() {
|
||||
}
|
||||
|
||||
case OPCODE_FOLLOW:
|
||||
progCounter=routeLookup->find(operand);
|
||||
progCounter=sequenceLookup->find(operand);
|
||||
if (progCounter<0) kill(F("FOLLOW unknown"), operand);
|
||||
return;
|
||||
|
||||
@@ -798,7 +934,7 @@ void RMFT2::loop2() {
|
||||
return;
|
||||
}
|
||||
callStack[stackDepth++]=progCounter+3;
|
||||
progCounter=routeLookup->find(operand);
|
||||
progCounter=sequenceLookup->find(operand);
|
||||
if (progCounter<0) kill(F("CALL unknown"),operand);
|
||||
return;
|
||||
|
||||
@@ -861,7 +997,7 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_START:
|
||||
{
|
||||
int newPc=routeLookup->find(operand);
|
||||
int newPc=sequenceLookup->find(operand);
|
||||
if (newPc<0) break;
|
||||
new RMFT2(newPc);
|
||||
}
|
||||
@@ -869,7 +1005,7 @@ void RMFT2::loop2() {
|
||||
|
||||
case OPCODE_SENDLOCO: // cab, route
|
||||
{
|
||||
int newPc=routeLookup->find(getOperand(1));
|
||||
int newPc=sequenceLookup->find(getOperand(1));
|
||||
if (newPc<0) break;
|
||||
RMFT2* newtask=new RMFT2(newPc); // create new task
|
||||
newtask->loco=operand;
|
||||
@@ -884,21 +1020,7 @@ void RMFT2::loop2() {
|
||||
invert=false;
|
||||
}
|
||||
break;
|
||||
|
||||
case OPCODE_LCC: // short form LCC
|
||||
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
|
||||
StringFormatter::send(LCCSerial,F("<L x%h>"),(uint16_t)operand);
|
||||
break;
|
||||
|
||||
case OPCODE_LCCX: // long form LCC
|
||||
if ((compileFeatures & FEATURE_LCC) && LCCSerial)
|
||||
StringFormatter::send(LCCSerial,F("<L x%h%h%h%h>\n"),
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_SERVO: // OPCODE_SERVO,V(vpin),OPCODE_PAD,V(position),OPCODE_PAD,V(profile),OPCODE_PAD,V(duration)
|
||||
IODevice::writeAnalogue(operand,getOperand(1),getOperand(2),getOperand(3));
|
||||
@@ -923,16 +1045,7 @@ void RMFT2::loop2() {
|
||||
case OPCODE_PRINT:
|
||||
printMessage(operand);
|
||||
break;
|
||||
case OPCODE_ROUTE_HIDDEN:
|
||||
manageRouteState(operand,2);
|
||||
break;
|
||||
case OPCODE_ROUTE_ACTIVE:
|
||||
manageRouteState(operand,0);
|
||||
break;
|
||||
case OPCODE_ROUTE_INACTIVE:
|
||||
manageRouteState(operand,1);
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_ROUTE:
|
||||
case OPCODE_AUTOMATION:
|
||||
case OPCODE_SEQUENCE:
|
||||
@@ -945,7 +1058,6 @@ void RMFT2::loop2() {
|
||||
case OPCODE_SERVOTURNOUT: // Turnout definition ignored at runtime
|
||||
case OPCODE_PINTURNOUT: // Turnout definition ignored at runtime
|
||||
case OPCODE_ONCLOSE: // Turnout event catchers ignored here
|
||||
case OPCODE_ONLCC: // LCC event catchers ignored here
|
||||
case OPCODE_ONTHROW:
|
||||
case OPCODE_ONACTIVATE: // Activate event catchers ignored here
|
||||
case OPCODE_ONDEACTIVATE:
|
||||
@@ -1015,14 +1127,13 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
}
|
||||
|
||||
/* static */ void RMFT2::doSignal(int16_t id,char rag) {
|
||||
if (!(compileFeatures & FEATURE_SIGNAL)) return; // dont compile code below
|
||||
if (diag) DIAG(F(" doSignal %d %x"),id,rag);
|
||||
|
||||
// Schedule any event handler for this signal change.
|
||||
// Thjis will work even without a signal definition.
|
||||
if (rag==SIGNAL_RED) onRedLookup->handleEvent(F("RED"),id);
|
||||
else if (rag==SIGNAL_GREEN) onGreenLookup->handleEvent(F("GREEN"),id);
|
||||
else onAmberLookup->handleEvent(F("AMBER"),id);
|
||||
if (rag==SIGNAL_RED) handleEvent(F("RED"),onRedLookup,id);
|
||||
else if (rag==SIGNAL_GREEN) handleEvent(F("GREEN"), onGreenLookup,id);
|
||||
else handleEvent(F("AMBER"), onAmberLookup,id);
|
||||
|
||||
int16_t sigslot=getSignalSlot(id);
|
||||
if (sigslot<0) return;
|
||||
@@ -1083,7 +1194,6 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
}
|
||||
|
||||
/* static */ bool RMFT2::isSignal(int16_t id,char rag) {
|
||||
if (!(compileFeatures & FEATURE_SIGNAL)) return false;
|
||||
int16_t sigslot=getSignalSlot(id);
|
||||
if (sigslot<0) return false;
|
||||
return (flags[sigslot] & SIGNAL_MASK) == rag;
|
||||
@@ -1091,26 +1201,26 @@ int16_t RMFT2::getSignalSlot(int16_t id) {
|
||||
|
||||
void RMFT2::turnoutEvent(int16_t turnoutId, bool closed) {
|
||||
// Hunt for an ONTHROW/ONCLOSE for this turnout
|
||||
if (closed) onCloseLookup->handleEvent(F("CLOSE"),turnoutId);
|
||||
else onThrowLookup->handleEvent(F("THROW"),turnoutId);
|
||||
if (closed) handleEvent(F("CLOSE"),onCloseLookup,turnoutId);
|
||||
else handleEvent(F("THROW"),onThrowLookup,turnoutId);
|
||||
}
|
||||
|
||||
|
||||
void RMFT2::activateEvent(int16_t addr, bool activate) {
|
||||
// Hunt for an ONACTIVATE/ONDEACTIVATE for this accessory
|
||||
if (activate) onActivateLookup->handleEvent(F("ACTIVATE"),addr);
|
||||
else onDeactivateLookup->handleEvent(F("DEACTIVATE"),addr);
|
||||
if (activate) handleEvent(F("ACTIVATE"),onActivateLookup,addr);
|
||||
else handleEvent(F("DEACTIVATE"),onDeactivateLookup,addr);
|
||||
}
|
||||
|
||||
void RMFT2::changeEvent(int16_t vpin, bool change) {
|
||||
// Hunt for an ONCHANGE for this sensor
|
||||
if (change) onChangeLookup->handleEvent(F("CHANGE"),vpin);
|
||||
if (change) handleEvent(F("CHANGE"),onChangeLookup,vpin);
|
||||
}
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
void RMFT2::rotateEvent(int16_t turntableId, bool change) {
|
||||
// Hunt or an ONROTATE for this turntable
|
||||
if (change) onRotateLookup->handleEvent(F("ROTATE"),turntableId);
|
||||
if (change) handleEvent(F("ROTATE"),onRotateLookup,turntableId);
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1119,8 +1229,8 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Looking for clock event at : %d"), clocktime);
|
||||
if (change) {
|
||||
onClockLookup->handleEvent(F("CLOCK"),clocktime);
|
||||
onClockLookup->handleEvent(F("CLOCK"),25*60+clocktime%60);
|
||||
handleEvent(F("CLOCK"),onClockLookup,clocktime);
|
||||
handleEvent(F("CLOCK"),onClockLookup,25*60+clocktime%60);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1129,11 +1239,15 @@ void RMFT2::powerEvent(int16_t track, bool overload) {
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Looking for Power event on track : %c"), track);
|
||||
if (overload) {
|
||||
onOverloadLookup->handleEvent(F("POWER"),track);
|
||||
handleEvent(F("POWER"),onOverloadLookup,track);
|
||||
}
|
||||
}
|
||||
|
||||
void RMFT2::startNonRecursiveTask(const FSH* reason, int16_t id,int pc) {
|
||||
|
||||
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
int pc= handlers->find(id);
|
||||
if (pc<0) return;
|
||||
|
||||
// Check we dont already have a task running this handler
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
@@ -1249,29 +1363,3 @@ void RMFT2::thrungeString(uint32_t strfar, thrunger mode, byte id) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RMFT2::manageRouteState(uint16_t id, byte state) {
|
||||
if (compileFeatures && FEATURE_ROUTESTATE) {
|
||||
// Route state must be maintained for when new throttles connect.
|
||||
// locate route id in the Routes lookup
|
||||
int16_t position=routeLookup->findPosition(id);
|
||||
if (position<0) return;
|
||||
// set state beside it
|
||||
if (routeStateArray[position]==state) return;
|
||||
routeStateArray[position]=state;
|
||||
CommandDistributor::broadcastRouteState(id,state);
|
||||
}
|
||||
}
|
||||
void RMFT2::manageRouteCaption(uint16_t id,const FSH* caption) {
|
||||
if (compileFeatures && FEATURE_ROUTESTATE) {
|
||||
// Route state must be maintained for when new throttles connect.
|
||||
// locate route id in the Routes lookup
|
||||
int16_t position=routeLookup->findPosition(id);
|
||||
if (position<0) return;
|
||||
// set state beside it
|
||||
if (routeCaptionArray[position]==caption) return;
|
||||
routeCaptionArray[position]=caption;
|
||||
CommandDistributor::broadcastRouteCaption(id,caption);
|
||||
}
|
||||
}
|
||||
|
||||
|
49
EXRAIL2.h
49
EXRAIL2.h
@@ -26,6 +26,7 @@
|
||||
#include "IODevice.h"
|
||||
#include "Turnouts.h"
|
||||
#include "Turntables.h"
|
||||
#include "IO_I2CDFPLayer.h"
|
||||
|
||||
// The following are the operation codes (or instructions) for a kind of virtual machine.
|
||||
// Each instruction is normally 3 bytes long with an operation code followed by a parameter.
|
||||
@@ -59,16 +60,16 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ROSTER,OPCODE_KILLALL,
|
||||
OPCODE_ROUTE,OPCODE_AUTOMATION,OPCODE_SEQUENCE,
|
||||
OPCODE_ENDTASK,OPCODE_ENDEXRAIL,
|
||||
OPCODE_SET_TRACK,OPCODE_SET_POWER,
|
||||
OPCODE_SET_TRACK,
|
||||
OPCODE_ONRED,OPCODE_ONAMBER,OPCODE_ONGREEN,
|
||||
OPCODE_ONCHANGE,
|
||||
OPCODE_ONCLOCKTIME,
|
||||
OPCODE_ONTIME,
|
||||
#ifndef IO_NO_HAL
|
||||
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
|
||||
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_WAITFORTT,
|
||||
OPCODE_LCC,OPCODE_LCCX,OPCODE_ONLCC,
|
||||
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_IFTTPOSITION,OPCODE_WAITFORTT,
|
||||
#endif
|
||||
OPCODE_ONOVERLOAD,
|
||||
OPCODE_ROUTE_ACTIVE,OPCODE_ROUTE_INACTIVE,OPCODE_ROUTE_HIDDEN,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -81,8 +82,7 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_IFRANDOM,OPCODE_IFRESERVE,
|
||||
OPCODE_IFCLOSED,OPCODE_IFTHROWN,
|
||||
OPCODE_IFRE,
|
||||
OPCODE_IFLOCO,
|
||||
OPCODE_IFTTPOSITION
|
||||
OPCODE_IFLOCO
|
||||
};
|
||||
|
||||
// Ensure thrunge_lcd is put last as there may be more than one display,
|
||||
@@ -96,12 +96,7 @@ enum thrunger: byte {
|
||||
thrunge_lcd, // Must be last!!
|
||||
};
|
||||
|
||||
// Flag bits for compile time features.
|
||||
static const byte FEATURE_SIGNAL= 0x80;
|
||||
static const byte FEATURE_LCC = 0x40;
|
||||
static const byte FEATURE_ROSTER= 0x20;
|
||||
static const byte FEATURE_ROUTESTATE= 0x10;
|
||||
|
||||
|
||||
|
||||
// Flag bits for status of hardware and TPL
|
||||
static const byte SECTION_FLAG = 0x80;
|
||||
@@ -121,20 +116,13 @@ enum thrunger: byte {
|
||||
class LookList {
|
||||
public:
|
||||
LookList(int16_t size);
|
||||
void chain(LookList* chainTo);
|
||||
void add(int16_t lookup, int16_t result);
|
||||
int16_t find(int16_t value); // finds result value
|
||||
int16_t findPosition(int16_t value); // finds index
|
||||
int16_t size();
|
||||
void stream(Print * _stream);
|
||||
void handleEvent(const FSH* reason,int16_t id);
|
||||
|
||||
int16_t find(int16_t value);
|
||||
private:
|
||||
int16_t m_size;
|
||||
int16_t m_loaded;
|
||||
int16_t * m_lookupArray;
|
||||
int16_t * m_resultArray;
|
||||
LookList* m_chain;
|
||||
int16_t * m_resultArray;
|
||||
};
|
||||
|
||||
class RMFT2 {
|
||||
@@ -168,8 +156,7 @@ class LookList {
|
||||
static const FSH * getRosterFunctions(int16_t id);
|
||||
static const FSH * getTurntableDescription(int16_t id);
|
||||
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
||||
static void startNonRecursiveTask(const FSH* reason, int16_t id,int pc);
|
||||
|
||||
|
||||
private:
|
||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
static bool parseSlash(Print * stream, byte & paramCount, int16_t p[]) ;
|
||||
@@ -186,6 +173,7 @@ private:
|
||||
#endif
|
||||
static LookList* LookListLoader(OPCODE op1,
|
||||
OPCODE op2=OPCODE_ENDEXRAIL,OPCODE op3=OPCODE_ENDEXRAIL);
|
||||
static void handleEvent(const FSH* reason,LookList* handlers, int16_t id);
|
||||
static uint16_t getOperand(int progCounter,byte n);
|
||||
static RMFT2 * loopTask;
|
||||
static RMFT2 * pausingTask;
|
||||
@@ -205,8 +193,7 @@ private:
|
||||
static const HIGHFLASH byte RouteCode[];
|
||||
static const HIGHFLASH int16_t SignalDefinitions[];
|
||||
static byte flags[MAX_FLAGS];
|
||||
static Print * LCCSerial;
|
||||
static LookList * routeLookup;
|
||||
static LookList * sequenceLookup;
|
||||
static LookList * onThrowLookup;
|
||||
static LookList * onCloseLookup;
|
||||
static LookList * onActivateLookup;
|
||||
@@ -220,14 +207,6 @@ private:
|
||||
static LookList * onRotateLookup;
|
||||
#endif
|
||||
static LookList * onOverloadLookup;
|
||||
|
||||
static const int countLCCLookup;
|
||||
static int onLCCLookup[];
|
||||
static const byte compileFeatures;
|
||||
static void manageRouteState(uint16_t id, byte state);
|
||||
static void manageRouteCaption(uint16_t id, const FSH* caption);
|
||||
static byte * routeStateArray;
|
||||
static const FSH ** routeCaptionArray;
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
@@ -249,8 +228,4 @@ private:
|
||||
byte stackDepth;
|
||||
int callStack[MAX_STACK_DEPTH];
|
||||
};
|
||||
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
|
||||
#endif
|
||||
|
@@ -86,8 +86,6 @@
|
||||
#undef LATCH
|
||||
#undef LCD
|
||||
#undef SCREEN
|
||||
#undef LCC
|
||||
#undef LCCX
|
||||
#undef LCN
|
||||
#undef MOVETT
|
||||
#undef ONACTIVATE
|
||||
@@ -96,7 +94,6 @@
|
||||
#undef ONDEACTIVATE
|
||||
#undef ONDEACTIVATEL
|
||||
#undef ONCLOSE
|
||||
#undef ONLCC
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONCLOCKMINS
|
||||
@@ -126,10 +123,6 @@
|
||||
#undef ROTATE
|
||||
#undef ROTATE_DCC
|
||||
#undef ROUTE
|
||||
#undef ROUTE_ACTIVE
|
||||
#undef ROUTE_INACTIVE
|
||||
#undef ROUTE_HIDDEN
|
||||
#undef ROUTE_CAPTION
|
||||
#undef SENDLOCO
|
||||
#undef SEQUENCE
|
||||
#undef SERIAL
|
||||
@@ -145,7 +138,6 @@
|
||||
#undef SERVO_SIGNAL
|
||||
#undef SET
|
||||
#undef SET_TRACK
|
||||
#undef SET_POWER
|
||||
#undef SETLOCO
|
||||
#undef SIGNAL
|
||||
#undef SIGNALH
|
||||
@@ -200,7 +192,7 @@
|
||||
#define ENDTASK
|
||||
#define ESTOP
|
||||
#define EXRAIL
|
||||
#define EXTT_TURNTABLE(id,vpin,home,description)
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description)
|
||||
#define FADE(pin,value,ms)
|
||||
#define FOFF(func)
|
||||
#define FOLLOW(route)
|
||||
@@ -228,9 +220,7 @@
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
#define KILLALL
|
||||
#define LATCH(sensor_id)
|
||||
#define LCC(eventid)
|
||||
#define LCCX(senderid,eventid)
|
||||
#define LATCH(sensor_id)
|
||||
#define LCD(row,msg)
|
||||
#define SCREEN(display,row,msg)
|
||||
#define LCN(msg)
|
||||
@@ -245,7 +235,6 @@
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
#define ONLCC(sender,event)
|
||||
#define ONGREEN(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONROTATE(turntable_id)
|
||||
@@ -271,10 +260,6 @@
|
||||
#define ROTATE_DCC(turntable_id,position)
|
||||
#define ROSTER(cab,name,funcmap...)
|
||||
#define ROUTE(id,description)
|
||||
#define ROUTE_ACTIVE(id)
|
||||
#define ROUTE_INACTIVE(id)
|
||||
#define ROUTE_HIDDEN(id)
|
||||
#define ROUTE_CAPTION(id,caption)
|
||||
#define SENDLOCO(cab,route)
|
||||
#define SEQUENCE(id)
|
||||
#define SERIAL(msg)
|
||||
@@ -290,7 +275,6 @@
|
||||
#define SERVO_TURNOUT(id,pin,activeAngle,inactiveAngle,profile,description...)
|
||||
#define SET(pin)
|
||||
#define SET_TRACK(track,mode)
|
||||
#define SET_POWER(track,onoff)
|
||||
#define SETLOCO(loco)
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
|
@@ -1,292 +0,0 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2023 Chris Harlow
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
// THIS file is an extension of the RMFT2 class
|
||||
// normally found in EXRAIL2.cpp
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "defines.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "DCC.h"
|
||||
// Command parsing keywords
|
||||
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
||||
const int16_t HASH_KEYWORD_ON = 2657;
|
||||
const int16_t HASH_KEYWORD_START=23232;
|
||||
const int16_t HASH_KEYWORD_RESERVE=11392;
|
||||
const int16_t HASH_KEYWORD_FREE=-23052;
|
||||
const int16_t HASH_KEYWORD_LATCH=1618;
|
||||
const int16_t HASH_KEYWORD_UNLATCH=1353;
|
||||
const int16_t HASH_KEYWORD_PAUSE=-4142;
|
||||
const int16_t HASH_KEYWORD_RESUME=27609;
|
||||
const int16_t HASH_KEYWORD_KILL=5218;
|
||||
const int16_t HASH_KEYWORD_ALL=3457;
|
||||
const int16_t HASH_KEYWORD_ROUTES=-3702;
|
||||
const int16_t HASH_KEYWORD_RED=26099;
|
||||
const int16_t HASH_KEYWORD_AMBER=18713;
|
||||
const int16_t HASH_KEYWORD_GREEN=-31493;
|
||||
const int16_t HASH_KEYWORD_A='A';
|
||||
|
||||
// This filter intercepts <> commands to do the following:
|
||||
// - Implement RMFT specific commands/diagnostics
|
||||
// - Reject/modify JMRI commands that would interfere with RMFT processing
|
||||
|
||||
void RMFT2::ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]) {
|
||||
(void)stream; // avoid compiler warning if we don't access this parameter
|
||||
bool reject=false;
|
||||
switch(opcode) {
|
||||
|
||||
case 'D':
|
||||
if (p[0]==HASH_KEYWORD_EXRAIL) { // <D EXRAIL ON/OFF>
|
||||
diag = paramCount==2 && (p[1]==HASH_KEYWORD_ON || p[1]==1);
|
||||
opcode=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case '/': // New EXRAIL command
|
||||
reject=!parseSlash(stream,paramCount,p);
|
||||
opcode=0;
|
||||
break;
|
||||
|
||||
case 'L':
|
||||
// This entire code block is compiled out if LLC macros not used
|
||||
if (!(compileFeatures & FEATURE_LCC)) return;
|
||||
|
||||
if (paramCount==0) { //<L> LCC adapter introducing self
|
||||
LCCSerial=stream; // now we know where to send events we raise
|
||||
|
||||
// loop through all possible sent events
|
||||
for (int progCounter=0;; SKIPOP) {
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
if (opcode==OPCODE_LCC) StringFormatter::send(stream,F("<LS x%h>\n"),getOperand(progCounter,0));
|
||||
if (opcode==OPCODE_LCCX) { // long form LCC
|
||||
StringFormatter::send(stream,F("<LS x%h%h%h%h>\n"),
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
}}
|
||||
|
||||
// we stream the hex events we wish to listen to
|
||||
// and at the same time build the event index looku.
|
||||
|
||||
|
||||
int eventIndex=0;
|
||||
for (int progCounter=0;; SKIPOP) {
|
||||
byte opcode=GET_OPCODE;
|
||||
if (opcode==OPCODE_ENDEXRAIL) break;
|
||||
if (opcode==OPCODE_ONLCC) {
|
||||
onLCCLookup[eventIndex]=progCounter; // TODO skip...
|
||||
StringFormatter::send(stream,F("<LL %d x%h%h%h:%h>\n"),
|
||||
eventIndex,
|
||||
getOperand(progCounter,1),
|
||||
getOperand(progCounter,2),
|
||||
getOperand(progCounter,3),
|
||||
getOperand(progCounter,0)
|
||||
);
|
||||
eventIndex++;
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F("<LR>\n")); // Ready to rumble
|
||||
opcode=0;
|
||||
break;
|
||||
}
|
||||
if (paramCount==1) { // <L eventid> LCC event arrived from adapter
|
||||
int16_t eventid=p[0];
|
||||
reject=eventid<0 || eventid>=countLCCLookup;
|
||||
if (!reject) startNonRecursiveTask(F("LCC"),eventid,onLCCLookup[eventid]);
|
||||
opcode=0;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'J': // throttle info commands
|
||||
// This entire code block is compiled out if FEATURE_ROUTESTATE macros not used
|
||||
if (paramCount<1) return;
|
||||
switch(p[0]) {
|
||||
case HASH_KEYWORD_A: // <JA> returns automations/routes
|
||||
if (paramCount==1) {// <JA>
|
||||
StringFormatter::send(stream, F("<jA"));
|
||||
routeLookup->stream(stream);
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
opcode=0;
|
||||
return;
|
||||
}
|
||||
if (paramCount==2) { // <JA id>
|
||||
uint16_t id=p[1];
|
||||
StringFormatter::send(stream,F("<jA %d %c \"%S\">\n"),
|
||||
id, getRouteType(id), getRouteDescription(id));
|
||||
|
||||
if (compileFeatures & FEATURE_ROUTESTATE) {
|
||||
// Send any non-default button states or captions
|
||||
int16_t statePos=routeLookup->findPosition(id);
|
||||
if (statePos>=0) {
|
||||
if (routeStateArray[statePos])
|
||||
StringFormatter::send(stream,F("<jB %d %d>\n"), id, routeStateArray[statePos]);
|
||||
if (routeCaptionArray[statePos])
|
||||
StringFormatter::send(stream,F("<jB %d \"%S\">\n"), id,routeCaptionArray[statePos]);
|
||||
}
|
||||
}
|
||||
opcode=0;
|
||||
return;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
default: // other commands pass through
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
bool RMFT2::parseSlash(Print * stream, byte & paramCount, int16_t p[]) {
|
||||
|
||||
if (paramCount==0) { // STATUS
|
||||
StringFormatter::send(stream, F("<* EXRAIL STATUS"));
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
StringFormatter::send(stream,F("\nID=%d,PC=%d,LOCO=%d%c,SPEED=%d%c"),
|
||||
(int)(task->taskId),task->progCounter,task->loco,
|
||||
task->invert?'I':' ',
|
||||
task->speedo,
|
||||
task->forward?'F':'R'
|
||||
);
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
// Now stream the flags
|
||||
for (int id=0;id<MAX_FLAGS; id++) {
|
||||
byte flag=flags[id];
|
||||
if (flag & ~TASK_FLAG & ~SIGNAL_MASK) { // not interested in TASK_FLAG only. Already shown above
|
||||
StringFormatter::send(stream,F("\nflags[%d] "),id);
|
||||
if (flag & SECTION_FLAG) StringFormatter::send(stream,F(" RESERVED"));
|
||||
if (flag & LATCH_FLAG) StringFormatter::send(stream,F(" LATCHED"));
|
||||
}
|
||||
}
|
||||
|
||||
if (compileFeatures & FEATURE_SIGNAL) {
|
||||
// do the signals
|
||||
// flags[n] represents the state of the nth signal in the table
|
||||
for (int sigslot=0;;sigslot++) {
|
||||
VPIN sigid=GETHIGHFLASHW(RMFT2::SignalDefinitions,sigslot*8);
|
||||
if (sigid==0) break; // end of signal list
|
||||
byte flag=flags[sigslot] & SIGNAL_MASK; // obtain signal flags for this id
|
||||
StringFormatter::send(stream,F("\n%S[%d]"),
|
||||
(flag == SIGNAL_RED)? F("RED") : (flag==SIGNAL_GREEN) ? F("GREEN") : F("AMBER"),
|
||||
sigid & SIGNAL_ID_MASK);
|
||||
}
|
||||
}
|
||||
StringFormatter::send(stream,F(" *>\n"));
|
||||
return true;
|
||||
}
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_PAUSE: // </ PAUSE>
|
||||
if (paramCount!=1) return false;
|
||||
DCC::setThrottle(0,1,true); // pause all locos on the track
|
||||
pausingTask=(RMFT2 *)1; // Impossible task address
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_RESUME: // </ RESUME>
|
||||
if (paramCount!=1) return false;
|
||||
pausingTask=NULL;
|
||||
{
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->loco) task->driveLoco(task->speedo);
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
|
||||
case HASH_KEYWORD_START: // </ START [cab] route >
|
||||
if (paramCount<2 || paramCount>3) return false;
|
||||
{
|
||||
int route=(paramCount==2) ? p[1] : p[2];
|
||||
uint16_t cab=(paramCount==2)? 0 : p[1];
|
||||
int pc=routeLookup->find(route);
|
||||
if (pc<0) return false;
|
||||
RMFT2* task=new RMFT2(pc);
|
||||
task->loco=cab;
|
||||
}
|
||||
return true;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// check KILL ALL here, otherwise the next validation confuses ALL with a flag
|
||||
if (p[0]==HASH_KEYWORD_KILL && p[1]==HASH_KEYWORD_ALL) {
|
||||
while (loopTask) loopTask->kill(F("KILL ALL")); // destructor changes loopTask
|
||||
return true;
|
||||
}
|
||||
|
||||
// all other / commands take 1 parameter
|
||||
if (paramCount!=2 ) return false;
|
||||
|
||||
switch (p[0]) {
|
||||
case HASH_KEYWORD_KILL: // Kill taskid|ALL
|
||||
{
|
||||
if ( p[1]<0 || p[1]>=MAX_FLAGS) return false;
|
||||
RMFT2 * task=loopTask;
|
||||
while(task) {
|
||||
if (task->taskId==p[1]) {
|
||||
task->kill(F("KILL"));
|
||||
return true;
|
||||
}
|
||||
task=task->next;
|
||||
if (task==loopTask) break;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
case HASH_KEYWORD_RESERVE: // force reserve a section
|
||||
return setFlag(p[1],SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_FREE: // force free a section
|
||||
return setFlag(p[1],0,SECTION_FLAG);
|
||||
|
||||
case HASH_KEYWORD_LATCH:
|
||||
return setFlag(p[1], LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_UNLATCH:
|
||||
return setFlag(p[1], 0, LATCH_FLAG);
|
||||
|
||||
case HASH_KEYWORD_RED:
|
||||
doSignal(p[1],SIGNAL_RED);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_AMBER:
|
||||
doSignal(p[1],SIGNAL_AMBER);
|
||||
return true;
|
||||
|
||||
case HASH_KEYWORD_GREEN:
|
||||
doSignal(p[1],SIGNAL_GREEN);
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@@ -63,11 +63,6 @@
|
||||
// (10#mins)%100)
|
||||
#define STRIP_ZERO(value) 10##value%100
|
||||
|
||||
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
|
||||
//const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
|
||||
//const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||
|
||||
|
||||
// Pass 1 Implements aliases
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ALIAS
|
||||
@@ -79,42 +74,12 @@
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef HAL
|
||||
#define HAL(haltype,params...) haltype::create(params);
|
||||
#undef EXTT_TURNTABLE
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) EXTurntable::create(vpin,1,i2c_address);
|
||||
void exrailHalSetup() {
|
||||
#include "myAutomation.h"
|
||||
}
|
||||
|
||||
// Pass 1c detect compile time featurtes
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef SIGNAL
|
||||
#define SIGNAL(redpin,amberpin,greenpin) | FEATURE_SIGNAL
|
||||
#undef SIGNALH
|
||||
#define SIGNALH(redpin,amberpin,greenpin) | FEATURE_SIGNAL
|
||||
#undef SERVO_SIGNAL
|
||||
#define SERVO_SIGNAL(vpin,redval,amberval,greenval) | FEATURE_SIGNAL
|
||||
#undef DCC_SIGNAL
|
||||
#define DCC_SIGNAL(id,addr,subaddr) | FEATURE_SIGNAL
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#define VIRTUAL_SIGNAL(id) | FEATURE_SIGNAL
|
||||
|
||||
#undef LCC
|
||||
#define LCC(eventid) | FEATURE_LCC
|
||||
#undef LCCX
|
||||
#define LCCX(senderid,eventid) | FEATURE_LCC
|
||||
#undef ONLCC
|
||||
#define ONLCC(senderid,eventid) | FEATURE_LCC
|
||||
#undef ROUTE_ACTIVE
|
||||
#define ROUTE_ACTIVE(id) | FEATURE_ROUTESTATE
|
||||
#undef ROUTE_INACTIVE
|
||||
#define ROUTE_INACTIVE(id) | FEATURE_ROUTESTATE
|
||||
#undef ROUTE_HIDDEN
|
||||
#define ROUTE_HIDDEN(id) | FEATURE_ROUTESTATE
|
||||
#undef ROUTE_CAPTION
|
||||
#define ROUTE_CAPTION(id,caption) | FEATURE_ROUTESTATE
|
||||
|
||||
const byte RMFT2::compileFeatures = 0
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
|
||||
// Pass 2 create throttle route list
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROUTE
|
||||
@@ -161,12 +126,6 @@ const int StringMacroTracker1=__COUNTER__;
|
||||
#define PRINT(msg) THRUNGE(msg,thrunge_print)
|
||||
#undef LCN
|
||||
#define LCN(msg) THRUNGE(msg,thrunge_lcn)
|
||||
#undef ROUTE_CAPTION
|
||||
#define ROUTE_CAPTION(id,caption) \
|
||||
case (__COUNTER__ - StringMacroTracker1) : {\
|
||||
manageRouteCaption(id,F(caption));\
|
||||
return;\
|
||||
}
|
||||
#undef SERIAL
|
||||
#define SERIAL(msg) THRUNGE(msg,thrunge_serial)
|
||||
#undef SERIAL1
|
||||
@@ -218,8 +177,6 @@ void RMFT2::printMessage(uint16_t id) {
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef TURNOUT
|
||||
#define TURNOUT(id,addr,subaddr,description...) O_DESC(id,description)
|
||||
#undef TURNOUTL
|
||||
#define TURNOUTL(id,addr,description...) O_DESC(id,description)
|
||||
#undef PIN_TURNOUT
|
||||
#define PIN_TURNOUT(id,pin,description...) O_DESC(id,description)
|
||||
#undef SERVO_TURNOUT
|
||||
@@ -240,7 +197,7 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||
#undef DCC_TURNTABLE
|
||||
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
|
||||
#undef EXTT_TURNTABLE
|
||||
#define EXTT_TURNTABLE(id,vpin,home,description...) O_DESC(id,description)
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) O_DESC(id,description)
|
||||
|
||||
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
|
||||
switch (turntableId) {
|
||||
@@ -316,16 +273,6 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#include "myAutomation.h"
|
||||
0,0,0,0 };
|
||||
|
||||
// Pass 9 ONLCC counter and lookup array
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ONLCC
|
||||
#define ONLCC(sender,event) +1
|
||||
|
||||
const int RMFT2::countLCCLookup=0
|
||||
#include "myAutomation.h"
|
||||
;
|
||||
int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
|
||||
// Last Pass : create main routes table
|
||||
// Only undef the macros, not dummy them.
|
||||
#define RMFT2_UNDEF_ONLY
|
||||
@@ -370,7 +317,7 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define ESTOP OPCODE_SPEED,V(1),
|
||||
#define EXRAIL
|
||||
#ifndef IO_NO_HAL
|
||||
#define EXTT_TURNTABLE(id,vpin,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(home),
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) OPCODE_EXTTTURNTABLE,V(id),OPCODE_PAD,V(vpin),OPCODE_PAD,V(i2c_address),OPCODE_PAD,V(home),
|
||||
#endif
|
||||
#define FADE(pin,value,ms) OPCODE_SERVO,V(pin),OPCODE_PAD,V(value),OPCODE_PAD,V(PCA9685::ProfileType::UseDuration|PCA9685::NoPowerOff),OPCODE_PAD,V(ms/100L),
|
||||
#define FOFF(func) OPCODE_FOFF,V(func),
|
||||
@@ -402,11 +349,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
#define KILLALL OPCODE_KILLALL,0,0,
|
||||
#define LATCH(sensor_id) OPCODE_LATCH,V(sensor_id),
|
||||
#define LCC(eventid) OPCODE_LCC,V(eventid),
|
||||
#define LCCX(sender,event) OPCODE_LCCX,V(event),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
|
||||
#define LCD(id,msg) PRINT(msg)
|
||||
#define SCREEN(display,id,msg) PRINT(msg)
|
||||
#define LCN(msg) PRINT(msg)
|
||||
@@ -415,10 +357,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define ONACTIVATEL(linear) OPCODE_ONACTIVATE,V(linear+3),
|
||||
#define ONAMBER(signal_id) OPCODE_ONAMBER,V(signal_id),
|
||||
#define ONCLOSE(turnout_id) OPCODE_ONCLOSE,V(turnout_id),
|
||||
#define ONLCC(sender,event) OPCODE_ONLCC,V(event),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>32)&0xFFFF),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>16)&0xFFFF),\
|
||||
OPCODE_PAD,V((((uint64_t)sender)>>0)&0xFFFF),
|
||||
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
||||
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
||||
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
|
||||
@@ -454,10 +392,6 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#define ROTATE_DCC(id,position) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(0),
|
||||
#endif
|
||||
#define ROUTE(id, description) OPCODE_ROUTE, V(id),
|
||||
#define ROUTE_ACTIVE(id) OPCODE_ROUTE_ACTIVE,V(id),
|
||||
#define ROUTE_INACTIVE(id) OPCODE_ROUTE_INACTIVE,V(id),
|
||||
#define ROUTE_HIDDEN(id) OPCODE_ROUTE_HIDDEN,V(id),
|
||||
#define ROUTE_CAPTION(id,caption) PRINT(caption)
|
||||
#define SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
||||
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
||||
#define SERIAL(msg) PRINT(msg)
|
||||
@@ -473,12 +407,11 @@ int RMFT2::onLCCLookup[RMFT2::countLCCLookup];
|
||||
#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_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),
|
||||
#define SIGNAL(redpin,amberpin,greenpin)
|
||||
#define SIGNALH(redpin,amberpin,greenpin)
|
||||
#define SPEED(speed) OPCODE_SPEED,V(speed),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
#define THROW(id) OPCODE_THROW,V(id),
|
||||
#ifndef IO_NO_HAL
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "devel-202311132306Z"
|
||||
#define GITHUB_SHA "devel-202309241855Z"
|
||||
|
@@ -54,6 +54,8 @@ static const FSH * guessI2CDeviceType(uint8_t address) {
|
||||
return F("Time-of-flight sensor");
|
||||
else if (address >= 0x3c && address <= 0x3d)
|
||||
return F("OLED Display");
|
||||
else if (address >= 0x48 && address <= 0x57) // Henkk: Added SC16IS752 UART detection
|
||||
return F("SC16IS752 UART");
|
||||
else if (address >= 0x48 && address <= 0x4f)
|
||||
return F("Analogue Inputs or PWM");
|
||||
else if (address >= 0x40 && address <= 0x4f)
|
||||
@@ -64,6 +66,7 @@ static const FSH * guessI2CDeviceType(uint8_t address) {
|
||||
return F("Real-time clock");
|
||||
else if (address >= 0x70 && address <= 0x77)
|
||||
return F("I2C Mux");
|
||||
else if (address >= 0x90 && address <= 0xAE);
|
||||
else
|
||||
return F("?");
|
||||
}
|
||||
@@ -363,4 +366,4 @@ void I2CAddress::toHex(const uint8_t value, char *buffer) {
|
||||
|
||||
/* static */ bool I2CAddress::_addressWarningDone = false;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -22,7 +22,8 @@
|
||||
#define iodevice_h
|
||||
|
||||
// Define symbol DIAG_IO to enable diagnostic output
|
||||
//#define DIAG_IO Y
|
||||
//#define DIAG_IO
|
||||
|
||||
|
||||
// Define symbol DIAG_LOOPTIMES to enable CS loop execution time to be reported
|
||||
//#define DIAG_LOOPTIMES
|
||||
|
825
IO_I2CDFPlayer-test.h
Normal file
825
IO_I2CDFPlayer-test.h
Normal file
@@ -0,0 +1,825 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* DFPlayer is an MP3 player module with an SD card holder. It also has an integrated
|
||||
* amplifier, so it only needs a power supply and a speaker.
|
||||
* This driver is a modified version of the IO_DFPlayer.h file
|
||||
* *********************************************************************************************
|
||||
*
|
||||
* 2023, Added NXP SC16IS752 I2C Dual UART to enable the DFPlayer connection over the I2C bus
|
||||
* The SC16IS752 has 64 bytes TX & RX FIFO buffer
|
||||
* First version without interrupts from I2C UART and only RX/TX are used, interrupts may not be
|
||||
* needed as the RX Fifo holds the reply
|
||||
*
|
||||
* myHall.cpp configuration syntax:
|
||||
*
|
||||
* I2CDFPlayer::create(1st vPin, vPins, I2C address, UART ch, AM);
|
||||
*
|
||||
* Parameters:
|
||||
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||
* vPins : Total number of virtual pins allocated (only 1 vPin is supported)
|
||||
* I2C Address : I2C address of the serial controller, in 0x format,
|
||||
* UART ch : Indicating UART 0 or UART 1, values 0 or 1
|
||||
* AM : audio mixer, values: 1 or 2 to select an audio amplifier, no effect if AM is not installed
|
||||
*
|
||||
* The vPin is also an pin that can be read, it indicated if the DFPlayer has finished playing a track
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_I2CDFPlayer_h
|
||||
#define IO_I2CDFPlayer_h
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
||||
//#define DIAG_I2CDFplayer
|
||||
//#define DIAG_I2CDFplayer_data
|
||||
//#define DIAG_I2CDFplayer_reg
|
||||
#define DIAG_I2CDFplayer_playing
|
||||
|
||||
class I2CDFPlayer : public IODevice {
|
||||
I2CRB _rb;
|
||||
uint8_t _outbuffer[11]; // common buffer -- test
|
||||
uint8_t _inbuffer[10]; // common buffer -- test
|
||||
private:
|
||||
const uint8_t MAXVOLUME=30;
|
||||
uint8_t RETRYCOUNT = 0x03;
|
||||
bool _playing = false;
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Time (us) that last transmit took place.
|
||||
unsigned long _timeoutTime;
|
||||
uint8_t _recvCMD; // Last received command code byte
|
||||
bool _awaitingResponse = false;
|
||||
uint8_t _retryCounter = RETRYCOUNT; // Max retries before timing out
|
||||
uint8_t _requestedVolumeLevel = MAXVOLUME;
|
||||
uint8_t _currentVolume = MAXVOLUME;
|
||||
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
|
||||
bool _repeat = false; // audio file is repeat playing
|
||||
uint8_t _previousCmd = true;
|
||||
// SC16IS752 defines
|
||||
I2CAddress _I2CAddress;
|
||||
//I2CRB _rb;
|
||||
uint8_t _UART_CH;
|
||||
// Communication parameters for the DFPlayer are fixed at 8 bit, No parity, 1 stopbit
|
||||
uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
||||
uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
||||
uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
||||
uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
||||
uint32_t BAUD_RATE = 9600;
|
||||
uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
||||
uint8_t TEMP_REG_VAL = 0x00;
|
||||
uint8_t FIFO_RX_LEVEL = 0x00;
|
||||
uint8_t RX_BUFFER = 0x00; // nr of bytes copied into _inbuffer
|
||||
uint8_t FIFO_TX_LEVEL = 0x00;
|
||||
bool _playCmd = false;
|
||||
bool _volCmd = false;
|
||||
bool _folderCmd = false;
|
||||
uint8_t _requestedFolder = 0x01; // default to folder 01
|
||||
uint8_t _currentFolder = 0x01; // default to folder 01
|
||||
bool _repeatCmd = false;
|
||||
bool _stopplayCmd = false;
|
||||
bool _resetCmd = false;
|
||||
bool _eqCmd = false;
|
||||
uint8_t _requestedEQValue = NORMAL;
|
||||
uint8_t _currentEQvalue = NORMAL; // start equalizer value
|
||||
bool _daconCmd = false;
|
||||
uint8_t _audioMixer = 0x01; // Default to output amplifier 1
|
||||
bool _setamCmd = false; // Set the Audio mixer channel
|
||||
//uint8_t _outbuffer[11]; // common buffer -- test
|
||||
//uint8_t _inbuffer[10]; // common buffer -- test
|
||||
uint8_t _outbuffer_0[11]; // DFPlayer command is 10 bytes + 1 byte register address & UART channel -- for UART 0
|
||||
uint8_t _outbuffer_1[11]; // DFPlayer command is 10 bytes + 1 byte register address & UART channel -- for UART 1
|
||||
uint8_t _inbuffer_0[10]; // expected DFPlayer return 10 bytes -- for UART 0
|
||||
uint8_t _inbuffer_1[10]; // expected DFPlayer return 10 bytes -- for UART 1
|
||||
|
||||
//unsigned long SC16IS752_XTAL_FREQ = 1843200; // To support cheap eBay/AliExpress SC16IS752 boards
|
||||
unsigned long SC16IS752_XTAL_FREQ = 14745600; // Support for higher baud rates, standard for modular EX-IO system
|
||||
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
I2CDFPlayer(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t UART_CH, uint8_t AM){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
_UART_CH = UART_CH;
|
||||
_audioMixer = AM;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t UART_CH, uint8_t AM) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new I2CDFPlayer(firstVpin, nPins, i2cAddress, UART_CH, AM);
|
||||
}
|
||||
|
||||
void _begin() override {
|
||||
// check if SC16IS752 exist first, initialize and then resume DFPlayer init via SC16IS752
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
if (I2CManager.exists(_I2CAddress)){
|
||||
DIAG(F("SC16IS752 I2C:%s UART detected. UART CH: %d"), _I2CAddress.toString(), _UART_CH);
|
||||
Init_SC16IS752(); // Initialize UART
|
||||
if (_deviceState == DEVSTATE_FAILED){
|
||||
DIAG(F("SC16IS752 I2C:%s UART initialization failed, UART CH: %d"), _I2CAddress.toString(), _UART_CH);
|
||||
}
|
||||
} else {
|
||||
DIAG(F("SC16IS752 I2C:%s UART not detected, UART CH: %d"), _I2CAddress.toString(), _UART_CH);
|
||||
}
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
// Now init DFPlayer
|
||||
// Send a query to the device to see if it responds
|
||||
_deviceState = DEVSTATE_INITIALISING;
|
||||
sendPacket(0x42,0,0);
|
||||
_timeoutTime = micros() + 5000000UL; // 5 second timeout
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Read responses from device
|
||||
uint8_t status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything
|
||||
if (status == I2C_STATUS_OK) {
|
||||
processIncoming(currentMicros);
|
||||
// Check if a command sent to device has timed out. Allow 0.5 second for response
|
||||
// added retry counter, sometimes we do not sent keep alive due to other commands sent to DFPlayer
|
||||
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) { // timeout triggered
|
||||
if(_retryCounter == 0){ // retry counter out of luck, must take the device to failed state
|
||||
DIAG(F("I2CDFPlayer:%s, DFPlayer not responding on UART channel: %d"), _I2CAddress.toString(), _UART_CH);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_awaitingResponse = false;
|
||||
_playing = false;
|
||||
_retryCounter = RETRYCOUNT;
|
||||
} else { // timeout and retry protection and recovery of corrupt data frames from DFPlayer
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: %s, DFPlayer timout, retry counter: %d on UART channel: %d"), _I2CAddress.toString(), _retryCounter, _UART_CH);
|
||||
#endif
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds// reset timeout
|
||||
_awaitingResponse = false; // trigger sending a keep alive 0x42 in processOutgoing()
|
||||
_retryCounter --; // decrement retry counter
|
||||
resetRX_fifo(); // reset the RX fifo as it has corrupt data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, try next time
|
||||
if (status == I2C_STATUS_OK) {
|
||||
// Send any commands that need to go.
|
||||
processOutgoing(currentMicros);
|
||||
}
|
||||
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||
}
|
||||
|
||||
|
||||
// Check for incoming data, and update busy flag and other state accordingly
|
||||
|
||||
void processIncoming(unsigned long currentMicros) {
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
RX_fifo_lvl();
|
||||
if (FIFO_RX_LEVEL >= 10) {
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("I2CDFPlayer: %s Retrieving data from RX Fifo on UART_CH: 0x%x FIFO_RX_LEVEL: %d"),_I2CAddress.toString(), _UART_CH, FIFO_RX_LEVEL);
|
||||
#endif
|
||||
if (_UART_CH == 0){
|
||||
_outbuffer_0[0] = REG_RHR << 3 | _UART_CH << 1;
|
||||
// Only copy 10 bytes from RX FIFO, there maybe additional partial return data after a track is finished playing in the RX FIFO
|
||||
I2CManager.read(_I2CAddress, _inbuffer_0, 10, _outbuffer_0, 1); // inbuffer_0[] has the data now
|
||||
RX_BUFFER = 10; // We have copied 10 bytes from RX FIFO to _inbuffer_0
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: I2C: %s, Receive data, RX FIFO Data, UART CH: %d"), _I2CAddress.toString(), _UART_CH);
|
||||
for (int i = 0; i < sizeof _inbuffer_0; i++){
|
||||
DIAG(F("SC16IS752: Data _inbuffer_0[0x%x]: 0x%x"), i, _inbuffer_0[i]);
|
||||
}
|
||||
#endif
|
||||
} else if (_UART_CH == 1){
|
||||
_outbuffer_1[0] = REG_RHR << 3 | _UART_CH << 1;
|
||||
// Only copy 10 bytes from RX FIFO, there maybe additional partial return data after a track is finished playing in the RX FIFO
|
||||
I2CManager.read(_I2CAddress, _inbuffer_1, 10, _outbuffer_1, 1); // inbuffer_1[] has the data now
|
||||
RX_BUFFER = 10; // We have copied 10 bytes from RX FIFO to _inbuffer_1
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: I2C: %s, Receive data, RX FIFO Data, UART CH: %d"), _I2CAddress.toString(), _UART_CH);
|
||||
for (int i = 0; i < sizeof _inbuffer_1; i++){
|
||||
DIAG(F("SC16IS752: Data _inbuffer_1[0x%x]: 0x%x"), i, _inbuffer_1[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
FIFO_RX_LEVEL = 0; //set to 0, we'll read a fresh FIFO_RX_LEVEL next time
|
||||
return; // No data or not enough data in rx fifo, check again next time around
|
||||
}
|
||||
|
||||
// Tranfer _inbuffer_0 or _inbuffer_1 to _inbuffer (this should be a local variable for this instance only)
|
||||
if (_UART_CH==0){
|
||||
for( int i = 0;i < sizeof _inbuffer_0; i++){
|
||||
_inbuffer[i] = _inbuffer_0[i];
|
||||
}
|
||||
} else if (_UART_CH==1){
|
||||
for( int i = 0;i < sizeof _inbuffer_1; i++){
|
||||
_inbuffer[i] = _inbuffer_1[i];
|
||||
}
|
||||
}
|
||||
|
||||
bool ok = false;
|
||||
//DIAG(F("I2CDFPlayer: RX_BUFFER: %d"), RX_BUFFER);
|
||||
while (RX_BUFFER != 0) {
|
||||
int c = _inbuffer[_inputIndex]; // Start at 0, increment to FIFO_RX_LEVEL
|
||||
switch (_inputIndex) {
|
||||
case 0:
|
||||
if (c == 0x7E) ok = true;
|
||||
break;
|
||||
case 1:
|
||||
if (c == 0xFF) ok = true;
|
||||
break;
|
||||
case 2:
|
||||
if (c== 0x06) ok = true;
|
||||
break;
|
||||
case 3:
|
||||
_recvCMD = c; // CMD byte
|
||||
ok = true;
|
||||
break;
|
||||
case 6:
|
||||
switch (_recvCMD) {
|
||||
//DIAG(F("I2CDFPlayer: %s, _recvCMD: 0x%x _awaitingResponse: 0x0%x"),_I2CAddress.toString(), _recvCMD, _awaitingResponse);
|
||||
case 0x42:
|
||||
// Response to status query
|
||||
_playing = (c != 0);
|
||||
// Mark the device online and cancel timeout
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: %s, keepalive response: 0x%x, UART_CH: 0x0%x, _deviceState: 0x0%x"),_I2CAddress.toString(), _recvCMD, _UART_CH, _deviceState);
|
||||
#endif
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: %s, keepalive response: 0x%x, UART CH: %d"), _I2CAddress.toString(), _recvCMD, _UART_CH);
|
||||
#endif
|
||||
_awaitingResponse = false;
|
||||
break;
|
||||
case 0x3d:
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: Finished, UART CH: %d"), _UART_CH);
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
// Error codes; 1: Module Busy
|
||||
DIAG(F("I2CDFPlayer: Error %d returned from device, UART CH: %d"), c, _UART_CH);
|
||||
_playing = false;
|
||||
break;
|
||||
}
|
||||
ok = true;
|
||||
break;
|
||||
case 4: case 5: case 7: case 8:
|
||||
ok = true; // Skip over these bytes in message.
|
||||
break;
|
||||
case 9:
|
||||
if (c==0xef) {
|
||||
// Message finished
|
||||
_retryCounter = RETRYCOUNT; // reset the retry counter as we have received a valid packet
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ok){
|
||||
_inputIndex++; // character as expected, so increment index
|
||||
RX_BUFFER --; // Decrease FIFO_RX_LEVEL with each character read from _inbuffer[_inputIndex]
|
||||
} else {
|
||||
_inputIndex = 0; // otherwise reset.
|
||||
RX_BUFFER = 0;
|
||||
}
|
||||
}
|
||||
RX_BUFFER = 0; //Set to 0, we'll read a new RX FIFO level again
|
||||
}
|
||||
|
||||
|
||||
// Send any commands that need to be sent
|
||||
void processOutgoing(unsigned long currentMicros) {
|
||||
// When two commands are sent in quick succession, the device will often fail to
|
||||
// execute one. Testing has indicated that a delay of 100ms or more is required
|
||||
// between successive commands to get reliable operation.
|
||||
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
|
||||
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||
if ( _resetCmd == true){
|
||||
sendPacket(0x0C,0,0);
|
||||
_resetCmd = false;
|
||||
} else if(_volCmd == true) { // do the volme before palying a track
|
||||
if(_requestedVolumeLevel >= 0 && _requestedVolumeLevel <= 30){
|
||||
_currentVolume = _requestedVolumeLevel; // If _requestedVolumeLevel is out of range, sent _currentV1olume
|
||||
}
|
||||
sendPacket(0x06, 0x00, _currentVolume);
|
||||
_volCmd = false;
|
||||
} else if (_playCmd == true) {
|
||||
// Change song
|
||||
if (_requestedSong != -1) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: _requestedVolumeLevel: %u, _requestedSong: %u, _currentFolder: %u _playCmd: 0x%x"), _requestedVolumeLevel, _requestedSong, _currentFolder, _playCmd);
|
||||
#endif
|
||||
sendPacket(0x0F, _currentFolder, _requestedSong); // audio file in folder
|
||||
_requestedSong = -1;
|
||||
_playCmd = false;
|
||||
}
|
||||
} //else if (_requestedSong == 0) {
|
||||
else if (_stopplayCmd == true) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Stop playing: _stopplayCmd: 0x%x"), _stopplayCmd);
|
||||
#endif
|
||||
sendPacket(0x16, 0x00, 0x00); // Stop playing
|
||||
_requestedSong = -1;
|
||||
_repeat = false; // reset repeat
|
||||
_stopplayCmd = false;
|
||||
} else if (_folderCmd == true) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Folder: _folderCmd: 0x%x, _requestedFolder: %d"), _stopplayCmd, _requestedFolder);
|
||||
#endif
|
||||
if (_currentFolder != _requestedFolder){
|
||||
_currentFolder = _requestedFolder;
|
||||
}
|
||||
_folderCmd = false;
|
||||
} else if (_repeatCmd == true) {
|
||||
if(_repeat == false) { // No repeat play currently
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Repeat: _repeatCmd: 0x%x, _requestedSong: %d, _repeat: 0x0%x"), _repeatCmd, _requestedSong, _repeat);
|
||||
#endif
|
||||
sendPacket(0x08, 0x00, _requestedSong); // repeat playing audio file in root folder
|
||||
_requestedSong = -1;
|
||||
_repeat = true;
|
||||
}
|
||||
_repeatCmd= false;
|
||||
} else if (_daconCmd == true) { // Always turn DAC on
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: DACON: _daconCmd: 0x%x"), _daconCmd);
|
||||
#endif
|
||||
sendPacket(0x1A,0,0x00);
|
||||
_daconCmd = false;
|
||||
} else if (_eqCmd == true){ // Set Equalizer, values 0x00 - 0x05
|
||||
if (_currentEQvalue != _requestedEQValue){
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: EQ: _eqCmd: 0x%x, _currentEQvalue: 0x0%x, _requestedEQValue: 0x0%x"), _eqCmd, _currentEQvalue, _requestedEQValue);
|
||||
#endif
|
||||
_currentEQvalue = _requestedEQValue;
|
||||
sendPacket(0x07,0x00,_currentEQvalue);
|
||||
}
|
||||
_eqCmd = false;
|
||||
} else if (_setamCmd == true){ // Set Audio mixer channel
|
||||
setGPIO(); // Set the audio mixer channel
|
||||
_setamCmd = false;
|
||||
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
|
||||
// Poll device every second that other commands aren't being sent,
|
||||
// to check if it's still connected and responding.
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Send keepalive, UART CH: %d") , _UART_CH);
|
||||
#endif
|
||||
sendPacket(0x42,0,0);
|
||||
if (!_awaitingResponse) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Send keepalive, _awaitingResponse: 0x0%x, , UART CH: %d"), _awaitingResponse, _UART_CH);
|
||||
#endif
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write to a vPin will do nothing
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: Writing to any vPin not supported"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0.
|
||||
// Volume may be specified as second parameter to writeAnalogue.
|
||||
// If value is zero, the player stops playing.
|
||||
// WriteAnalogue on second pin sets the output volume.
|
||||
//
|
||||
// WriteAnalogue to be done on first vpin
|
||||
//
|
||||
//void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t cmd=0) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: VPIN:%u FileNo:%d Volume:%d Command:0x%x"), vpin, value, volume, cmd);
|
||||
#endif
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
if (pin == 0) { // Enhanced DFPlayer commands, do nothing if not vPin 0
|
||||
// Read command and value
|
||||
switch (cmd){
|
||||
//case NONE:
|
||||
// DFPlayerCmd = cmd;
|
||||
// break;
|
||||
case PLAY:
|
||||
_playCmd = true;
|
||||
_volCmd = true;
|
||||
_requestedSong = value;
|
||||
_requestedVolumeLevel = volume;
|
||||
_playing = true;
|
||||
break;
|
||||
case VOL:
|
||||
_volCmd = true;
|
||||
_requestedVolumeLevel = volume;
|
||||
break;
|
||||
case FOLDER:
|
||||
_folderCmd = true;
|
||||
if (volume <= 0 || volume > 99){ // Range checking, valid values 1-99, else default to 1
|
||||
_requestedFolder = 0x01; // if outside range, default to folder 01
|
||||
} else {
|
||||
_requestedFolder = volume;
|
||||
}
|
||||
break;
|
||||
case REPEATPLAY: // Need to check if _repeat == true, if so do nothing
|
||||
if (_repeat == false) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WriteAnalog Repeat: _repeat: 0x0%x, value: %d _repeatCmd: 0x%x"), _repeat, value, _repeatCmd);
|
||||
#endif
|
||||
_repeatCmd = true;
|
||||
_requestedSong = value;
|
||||
_requestedVolumeLevel = volume;
|
||||
_playing = true;
|
||||
}
|
||||
break;
|
||||
case STOPPLAY:
|
||||
_stopplayCmd = true;
|
||||
break;
|
||||
case EQ:
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WriteAnalog EQ: cmd: 0x%x, EQ value: 0x%x"), cmd, volume);
|
||||
#endif
|
||||
_eqCmd = true;
|
||||
if (volume <= 0 || volume > 5) { // If out of range, default to NORMAL
|
||||
_requestedEQValue = NORMAL;
|
||||
} else { // Valid EQ parameter range
|
||||
_requestedEQValue = volume;
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
_resetCmd = true;
|
||||
break;
|
||||
case DACON: // Works, but without the DACOFF command limited value, except when not relying on DFPlayer default to turn the DAC on
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WrtieAnalog DACON: cmd: 0x%x"), cmd);
|
||||
#endif
|
||||
_daconCmd = true;
|
||||
break;
|
||||
case SETAM: // Set the audio mixer channel to 1 or 2
|
||||
_setamCmd = true;
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WrtieAnalog SETAM: value: %d, cmd: 0x%x"), value, cmd);
|
||||
#endif
|
||||
if (volume <= 0 || volume > 2) { // If out of range, default to 1
|
||||
_audioMixer = 1;
|
||||
} else { // Valid SETAM parameter in range
|
||||
_audioMixer = volume; // _audioMixer valid values 1 or 2
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A read on any pin indicates if the player is still playing.
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
if (pin == 0) { // Do nothing if not vPin 0
|
||||
return _playing;
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("I2CDFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
private:
|
||||
// DFPlayer command frame
|
||||
// 7E FF 06 0F 00 01 01 xx xx EF
|
||||
// 0 -> 7E is start code
|
||||
// 1 -> FF is version
|
||||
// 2 -> 06 is length
|
||||
// 3 -> 0F is command
|
||||
// 4 -> 00 is no receive
|
||||
// 5~6 -> 01 01 is argument
|
||||
// 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 )
|
||||
// 9 -> EF is end code
|
||||
|
||||
void sendPacket(uint8_t command, uint8_t arg1 = 0, uint8_t arg2 = 0) {
|
||||
FIFO_TX_LEVEL = 0; // Reset FIFO_TX_LEVEL
|
||||
uint8_t out[] = {
|
||||
0x7E,
|
||||
0xFF,
|
||||
06,
|
||||
command,
|
||||
00,
|
||||
//static_cast<uint8_t>(arg >> 8),
|
||||
//static_cast<uint8_t>(arg & 0x00ff),
|
||||
arg1,
|
||||
arg2,
|
||||
00,
|
||||
00,
|
||||
0xEF };
|
||||
|
||||
setChecksum(out);
|
||||
|
||||
// Prepend the DFPlayer command with REG address and UART Channel in _outbuffer_0 or _outbuffer_1
|
||||
if (_UART_CH==0){
|
||||
_outbuffer_0[0] = REG_THR << 3 | _UART_CH << 1; //TX FIFO and UART Channel
|
||||
for ( int i = 1; i < sizeof(out)+1 ; i++){
|
||||
_outbuffer_0[i] = out[i-1];
|
||||
}
|
||||
} else if (_UART_CH == 1){
|
||||
_outbuffer_1[0] = REG_THR << 3 | _UART_CH << 1; //TX FIFO and UART Channel
|
||||
for ( int i = 1; i < sizeof(out)+1 ; i++){
|
||||
_outbuffer_1[i] = out[i-1];
|
||||
}
|
||||
}
|
||||
|
||||
if (_UART_CH==0){
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: I2C: %s Sent packet function, UART CH: %d"), _I2CAddress.toString(), _UART_CH);
|
||||
for (int i = 0; i < sizeof _outbuffer_0; i++){
|
||||
DIAG(F("SC16IS752: Data _outbuffer_0[0x%x]: 0x%x"), i, _outbuffer_0[i]);
|
||||
}
|
||||
#endif
|
||||
} else if (_UART_CH==1){
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: I2C: %s Sent packet function, UART CH: %d"), _I2CAddress.toString(), _UART_CH);
|
||||
for (int i = 0; i < sizeof _outbuffer_1; i++){
|
||||
DIAG(F("SC16IS752: Data _outbuffer_1[0x%x]: 0x%x"), i, _outbuffer_1[i]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
TX_fifo_lvl();
|
||||
if(FIFO_TX_LEVEL > 0){ //FIFO is empty
|
||||
if (_UART_CH==0){
|
||||
//I2CManager.write(_I2CAddress, _outbuffer_0, sizeof(_outbuffer_0), &_rb); // ************************* use this once buffer issue is solved *********************
|
||||
I2CManager.write(_I2CAddress, _outbuffer_0, sizeof(_outbuffer_0));
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: I2C: %s data transmit complete on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
} else if (_UART_CH==1){
|
||||
//I2CManager.write(_I2CAddress, _outbuffer_1, sizeof(_outbuffer_1), &_rb); // ************************* use this once buffer issue is solved *********************
|
||||
I2CManager.write(_I2CAddress, _outbuffer_1, sizeof(_outbuffer_1));
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: I2C: %s data transmit complete on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("I2CDFPlayer at: %s, TX FIFO not empty on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
_deviceState = DEVSTATE_FAILED; // This should not happen
|
||||
}
|
||||
_commandSendTime = micros();
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
{
|
||||
uint16_t sum = 0;
|
||||
for (int i = 1; i < 7; i++)
|
||||
{
|
||||
sum += packet[i];
|
||||
}
|
||||
return -sum;
|
||||
}
|
||||
|
||||
void setChecksum(uint8_t* out)
|
||||
{
|
||||
uint16_t sum = calcChecksum(out);
|
||||
out[7] = (sum >> 8);
|
||||
out[8] = (sum & 0xff);
|
||||
}
|
||||
|
||||
// SC16IS752 functions
|
||||
// Initialise SC16IS752 only for this channel
|
||||
// First a software reset
|
||||
// Enable FIFO and clear TX & RX FIFO
|
||||
// Need to set the following registers
|
||||
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
||||
// IODIR set all bit to 1 indicating al are output
|
||||
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
||||
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
||||
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
||||
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
||||
// DLH most significant part of divisor
|
||||
// DLL least significant part of divisor
|
||||
//
|
||||
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
||||
//
|
||||
void Init_SC16IS752(){ // Return value is in _deviceState
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: Initialize I2C: %s , UART Ch: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
uint16_t _divisor = (SC16IS752_XTAL_FREQ / PRESCALER) / (BAUD_RATE * 16);
|
||||
TEMP_REG_VAL = 0x08; // UART Software reset
|
||||
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x00; // Set pins to GPIO mode
|
||||
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0xFF; //Set all pins as output
|
||||
UART_WriteRegister(REG_IODIR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x07; // Reset FIFO, clear RX & TX FIFO
|
||||
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x00; // Set MCR to all 0, includes Clock divisor
|
||||
UART_WriteRegister(REG_MCR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x80 | WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE;
|
||||
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch enabled
|
||||
UART_WriteRegister(REG_DLL, (uint8_t)_divisor); // Write DLL
|
||||
UART_WriteRegister(REG_DLH, (uint8_t)(_divisor >> 8)); // Write DLH
|
||||
UART_ReadRegister(REG_LCR);
|
||||
TEMP_REG_VAL = _inbuffer[0] & 0x7F; // Disable Divisor latch enabled bit
|
||||
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch disabled
|
||||
setGPIO(); // Set the audio mixer channel
|
||||
|
||||
uint8_t status = _rb.status;
|
||||
if (status != I2C_STATUS_OK) {
|
||||
DIAG(F("SC16IS752: I2C: %s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
} else {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("SC16IS752: I2C: %s, _deviceState: %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
#endif
|
||||
_deviceState = DEVSTATE_NORMAL; // If I2C state is OK, then proceed to initialize DFPlayer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read the Receive FIFO Level register (RXLVL), return a single unsigned integer
|
||||
// of nr of characters in the RX FIFO, bit 6:0, 7 not used, set to zero
|
||||
// value from 0 (0x00) to 64 (0x40) Only display if RX FIFO has data
|
||||
// The RX fifo level is used to check if there are enough bytes to process a frame
|
||||
void RX_fifo_lvl(){
|
||||
UART_ReadRegister(REG_RXLV);
|
||||
FIFO_RX_LEVEL = _inbuffer[0];
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
if (FIFO_RX_LEVEL > 0){
|
||||
//if (FIFO_RX_LEVEL > 0 && FIFO_RX_LEVEL < 10){
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_RX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, _inbuffer[0]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// When a frame is transmitted from the DFPlayer to the serial port, and at the same time the CS is sending a 42 query
|
||||
// the following two frames from the DFPlayer are corrupt. This result in the receive buffer being out of sync and the
|
||||
// CS will complain and generate a timeout.
|
||||
// The RX fifo has corrupt data and need to be flushed, this function does that
|
||||
//
|
||||
void resetRX_fifo(){
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX fifo reset"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
TEMP_REG_VAL = 0x03; // Reset RX fifo
|
||||
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||
}
|
||||
|
||||
// Set or reset GPIO pin 0 and 1 depending on the UART ch
|
||||
// This function may be modified in a future release to enable all 8 pins to be set or reset with EX-Rail
|
||||
// for various auxilary functions
|
||||
void setGPIO(){
|
||||
UART_ReadRegister(REG_IOSTATE); // Get the current GPIO pins state from the IOSTATE register
|
||||
TEMP_REG_VAL = _inbuffer[0];
|
||||
if (_audioMixer == 1){ // set to audio mixer 1
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
} else { // set to audio mixer 2
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 0 to Low
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 1 to Low
|
||||
}
|
||||
}
|
||||
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
_setamCmd = false;
|
||||
}
|
||||
|
||||
|
||||
// Read the Tranmit FIFO Level register (TXLVL), return a single unsigned integer
|
||||
// of nr characters free in the TX FIFO, bit 6:0, 7 not used, set to zero
|
||||
// value from 0 (0x00) to 64 (0x40)
|
||||
//
|
||||
void TX_fifo_lvl(){
|
||||
UART_ReadRegister(REG_TXLV);
|
||||
FIFO_TX_LEVEL = _inbuffer[0];
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
// DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_TX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, FIFO_TX_LEVEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//void UART_WriteRegister(I2CAddress _I2CAddress, uint8_t _UART_CH, uint8_t UART_REG, uint8_t Val, I2CRB &_rb){
|
||||
void UART_WriteRegister(uint8_t UART_REG, uint8_t Val){
|
||||
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1;
|
||||
_outbuffer[1] = Val;
|
||||
#ifdef DIAG_I2CDFplayer_reg
|
||||
DIAG(F("SC16IS752: Write register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _outbuffer[1]);
|
||||
#endif
|
||||
I2CManager.write(_I2CAddress, _outbuffer, 2);
|
||||
}
|
||||
|
||||
|
||||
void UART_ReadRegister(uint8_t UART_REG){
|
||||
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1; // _outbuffer[0] has now UART_REG and UART_CH
|
||||
I2CManager.read(_I2CAddress, _inbuffer, 1, _outbuffer, 1);
|
||||
// _inbuffer has the REG data
|
||||
#ifdef DIAG_I2CDFplayer_reg
|
||||
DIAG(F("SC16IS752: Read register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _inbuffer[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
// SC16IS752 General register set (from the datasheet)
|
||||
enum : uint8_t{
|
||||
REG_RHR = 0x00, // FIFO Read
|
||||
REG_THR = 0x00, // FIFO Write
|
||||
REG_IER = 0x01, // Interrupt Enable Register R/W
|
||||
REG_FCR = 0x02, // FIFO Control Register Write
|
||||
REG_IIR = 0x02, // Interrupt Identification Register Read
|
||||
REG_LCR = 0x03, // Line Control Register R/W
|
||||
REG_MCR = 0x04, // Modem Control Register R/W
|
||||
REG_LSR = 0x05, // Line Status Register Read
|
||||
REG_MSR = 0x06, // Modem Status Register Read
|
||||
REG_SPR = 0x07, // Scratchpad Register R/W
|
||||
REG_TCR = 0x06, // Transmission Control Register R/W
|
||||
REG_TLR = 0x07, // Trigger Level Register R/W
|
||||
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
||||
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
||||
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
||||
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
||||
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
||||
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
||||
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Special register set
|
||||
enum : uint8_t{
|
||||
REG_DLL = 0x00, // Division registers R/W
|
||||
REG_DLH = 0x01, // Division registers R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Enhanced regiter set
|
||||
enum : uint8_t{
|
||||
REG_EFR = 0X02, // Enhanced Features Register R/W
|
||||
REG_XON1 = 0x04, // R/W
|
||||
REG_XON2 = 0x05, // R/W
|
||||
REG_XOFF1 = 0x06, // R/W
|
||||
REG_XOFF2 = 0x07, // R/W
|
||||
};
|
||||
|
||||
// DFPlayer commands and values
|
||||
enum : uint8_t{
|
||||
PLAY = 0x0F,
|
||||
VOL = 0x06,
|
||||
FOLDER = 0x2B, // Not a DFPlayer command, used to set folder nr where audio file is
|
||||
REPEATPLAY = 0x08,
|
||||
STOPPLAY = 0x16,
|
||||
EQ = 0x07, // Set equaliser, require parameter NORMAL, POP, ROCK, JAZZ, CLASSIC or BASS
|
||||
RESET = 0x0C,
|
||||
DACON = 0x1A,
|
||||
SETAM = 0x2A, // Set audio mixer 1 or 2 for this DFPLayer
|
||||
NORMAL = 0x00, // Equalizer parameters
|
||||
POP = 0x01,
|
||||
ROCK = 0x02,
|
||||
JAZZ = 0x03,
|
||||
CLASSIC = 0x04,
|
||||
BASS = 0x05,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_I2CDFPlayer_h
|
1222
IO_I2CDFPlayer-test2.h
Normal file
1222
IO_I2CDFPlayer-test2.h
Normal file
File diff suppressed because it is too large
Load Diff
1279
IO_I2CDFPlayer-test3.h
Normal file
1279
IO_I2CDFPlayer-test3.h
Normal file
File diff suppressed because it is too large
Load Diff
1228
IO_I2CDFPlayer-test4.h
Normal file
1228
IO_I2CDFPlayer-test4.h
Normal file
File diff suppressed because it is too large
Load Diff
803
IO_I2CDFPlayer-test5.h
Normal file
803
IO_I2CDFPlayer-test5.h
Normal file
@@ -0,0 +1,803 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* DFPlayer is an MP3 player module with an SD card holder. It also has an integrated
|
||||
* amplifier, so it only needs a power supply and a speaker.
|
||||
* This driver is a modified version of the IO_DFPlayer.h file
|
||||
* *********************************************************************************************
|
||||
*
|
||||
* Dec 2023, Added NXP SC16IS752 I2C Dual UART to enable the DFPlayer connection over the I2C bus
|
||||
* The SC16IS752 has 64 bytes TX & RX FIFO buffer
|
||||
* First version without interrupts from I2C UART and only RX/TX are used, interrupts may not be
|
||||
* needed as the RX Fifo holds the reply
|
||||
*
|
||||
* Jan 2024, Issue with using both UARTs simultaniously, the secod uart seems to work but the first transmit
|
||||
* corrupt data. This need more analysis and experimenatation.
|
||||
* Will push this driver to the dev branch with the uart fixed to 0
|
||||
* Both SC16IS750 (single uart) and SC16IS752 (dual uart, but only uart 0 is enable)
|
||||
*
|
||||
* myHall.cpp configuration syntax:
|
||||
*
|
||||
* I2CDFPlayer::create(1st vPin, vPins, I2C address, xtal);
|
||||
*
|
||||
* Parameters:
|
||||
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||
* vPins : Total number of virtual pins allocated (2 vPins are supported, one for each UART)
|
||||
* 1st vPin for UART 0, 2nd for UART 1
|
||||
* I2C Address : I2C address of the serial controller, in 0x format
|
||||
* xtal : 0 for 1,8432Mhz, 1 for 14,7456Mhz
|
||||
*
|
||||
* The vPin is also a pin that can be read, it indicate if the DFPlayer has finished playing a track
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_I2CDFPlayer_h
|
||||
#define IO_I2CDFPlayer_h
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
||||
//#define DIAG_I2CDFplayer
|
||||
//#define DIAG_I2CDFplayer_data
|
||||
//#define DIAG_I2CDFplayer_reg
|
||||
//#define DIAG_I2CDFplayer_playing
|
||||
|
||||
class I2CDFPlayer : public IODevice {
|
||||
private:
|
||||
const uint8_t MAXVOLUME=30;
|
||||
uint8_t RETRYCOUNT = 0x03;
|
||||
bool _playing = false;
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Time (us) that last transmit took place.
|
||||
unsigned long _timeoutTime;
|
||||
uint8_t _recvCMD; // Last received command code byte
|
||||
bool _awaitingResponse = false;
|
||||
uint8_t _retryCounter = RETRYCOUNT; // Max retries before timing out
|
||||
uint8_t _requestedVolumeLevel = MAXVOLUME;
|
||||
uint8_t _currentVolume = MAXVOLUME;
|
||||
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
|
||||
bool _repeat = false; // audio file is repeat playing
|
||||
uint8_t _previousCmd = true;
|
||||
// SC16IS752 defines
|
||||
I2CAddress _I2CAddress;
|
||||
I2CRB _rb;
|
||||
uint8_t _UART_CH=0x00; // Fix uart ch to 0 for now
|
||||
// Communication parameters for the DFPlayer are fixed at 8 bit, No parity, 1 stopbit
|
||||
uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
||||
uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
||||
uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
||||
uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
||||
uint32_t BAUD_RATE = 9600;
|
||||
uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
||||
uint8_t TEMP_REG_VAL = 0x00;
|
||||
uint8_t FIFO_RX_LEVEL = 0x00;
|
||||
uint8_t RX_BUFFER = 0x00; // nr of bytes copied into _inbuffer
|
||||
uint8_t FIFO_TX_LEVEL = 0x00;
|
||||
bool _playCmd = false;
|
||||
bool _volCmd = false;
|
||||
bool _folderCmd = false;
|
||||
uint8_t _requestedFolder = 0x01; // default to folder 01
|
||||
uint8_t _currentFolder = 0x01; // default to folder 01
|
||||
bool _repeatCmd = false;
|
||||
bool _stopplayCmd = false;
|
||||
bool _resetCmd = false;
|
||||
bool _eqCmd = false;
|
||||
uint8_t _requestedEQValue = NORMAL;
|
||||
uint8_t _currentEQvalue = NORMAL; // start equalizer value
|
||||
bool _daconCmd = false;
|
||||
uint8_t _audioMixer = 0x01; // Default to output amplifier 1
|
||||
bool _setamCmd = false; // Set the Audio mixer channel
|
||||
uint8_t _outbuffer [11]; // DFPlayer command is 10 bytes + 1 byte register address & UART channel
|
||||
uint8_t _inbuffer[10]; // expected DFPlayer return 10 bytes
|
||||
|
||||
unsigned long _sc16is752_xtal_freq;
|
||||
unsigned long SC16IS752_XTAL_FREQ_LOW = 1843200; // To support cheap eBay/AliExpress SC16IS752 boards
|
||||
unsigned long SC16IS752_XTAL_FREQ_HIGH = 14745600; // Support for higher baud rates, standard for modular EX-IO system
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
I2CDFPlayer(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t xtal){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
if (xtal == 0){
|
||||
_sc16is752_xtal_freq = SC16IS752_XTAL_FREQ_LOW;
|
||||
} else { // should be 1
|
||||
_sc16is752_xtal_freq = SC16IS752_XTAL_FREQ_HIGH;
|
||||
}
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t xtal) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new I2CDFPlayer(firstVpin, nPins, i2cAddress, xtal);
|
||||
}
|
||||
|
||||
void _begin() override {
|
||||
// check if SC16IS752 exist first, initialize and then resume DFPlayer init via SC16IS752
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
if (I2CManager.exists(_I2CAddress)){
|
||||
DIAG(F("SC16IS752 I2C:%s UART detected"), _I2CAddress.toString());
|
||||
Init_SC16IS752(); // Initialize UART
|
||||
if (_deviceState == DEVSTATE_FAILED){
|
||||
DIAG(F("SC16IS752 I2C:%s UART initialization failed"), _I2CAddress.toString());
|
||||
}
|
||||
} else {
|
||||
DIAG(F("SC16IS752 I2C:%s UART not detected"), _I2CAddress.toString());
|
||||
}
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
// Now init DFPlayer
|
||||
// Send a query to the device to see if it responds
|
||||
_deviceState = DEVSTATE_INITIALISING;
|
||||
sendPacket(0x42,0,0);
|
||||
_timeoutTime = micros() + 5000000UL; // 5 second timeout
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Read responses from device
|
||||
uint8_t status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything
|
||||
if (status == I2C_STATUS_OK) {
|
||||
processIncoming(currentMicros);
|
||||
// Check if a command sent to device has timed out. Allow 0.5 second for response
|
||||
// added retry counter, sometimes we do not sent keep alive due to other commands sent to DFPlayer
|
||||
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) { // timeout triggered
|
||||
if(_retryCounter == 0){ // retry counter out of luck, must take the device to failed state
|
||||
DIAG(F("I2CDFPlayer:%s, DFPlayer not responding on UART channel: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_awaitingResponse = false;
|
||||
_playing = false;
|
||||
_retryCounter = RETRYCOUNT;
|
||||
} else { // timeout and retry protection and recovery of corrupt data frames from DFPlayer
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: %s, DFPlayer timout, retry counter: %d on UART channel: 0x%x"), _I2CAddress.toString(), _retryCounter, _UART_CH);
|
||||
#endif
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds// reset timeout
|
||||
_awaitingResponse = false; // trigger sending a keep alive 0x42 in processOutgoing()
|
||||
_retryCounter --; // decrement retry counter
|
||||
resetRX_fifo(); // reset the RX fifo as it has corrupt data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, try next time
|
||||
if (status == I2C_STATUS_OK) {
|
||||
// Send any commands that need to go.
|
||||
processOutgoing(currentMicros);
|
||||
}
|
||||
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||
}
|
||||
|
||||
|
||||
// Check for incoming data, and update busy flag and other state accordingly
|
||||
|
||||
void processIncoming(unsigned long currentMicros) {
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
RX_fifo_lvl();
|
||||
if (FIFO_RX_LEVEL >= 10) {
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("I2CDFPlayer: %s Retrieving data from RX Fifo on UART_CH: 0x%x FIFO_RX_LEVEL: %d"),_I2CAddress.toString(), _UART_CH, FIFO_RX_LEVEL);
|
||||
#endif
|
||||
_outbuffer[0] = REG_RHR << 3 | _UART_CH << 1;
|
||||
// Only copy 10 bytes from RX FIFO, there maybe additional partial return data after a track is finished playing in the RX FIFO
|
||||
I2CManager.read(_I2CAddress, _inbuffer, 10, _outbuffer, 1); // inbuffer[] has the data now
|
||||
//delayUntil(currentMicros + 10000); // Allow time to get the data
|
||||
RX_BUFFER = 10; // We have copied 10 bytes from RX FIFO to _inbuffer
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX FIFO Data"), _I2CAddress.toString(), _UART_CH);
|
||||
for (int i = 0; i < sizeof _inbuffer; i++){
|
||||
DIAG(F("SC16IS752: Data _inbuffer[0x%x]: 0x%x"), i, _inbuffer[i]);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
FIFO_RX_LEVEL = 0; //set to 0, we'll read a fresh FIFO_RX_LEVEL next time
|
||||
return; // No data or not enough data in rx fifo, check again next time around
|
||||
}
|
||||
|
||||
|
||||
bool ok = false;
|
||||
//DIAG(F("I2CDFPlayer: RX_BUFFER: %d"), RX_BUFFER);
|
||||
while (RX_BUFFER != 0) {
|
||||
int c = _inbuffer[_inputIndex]; // Start at 0, increment to FIFO_RX_LEVEL
|
||||
switch (_inputIndex) {
|
||||
case 0:
|
||||
if (c == 0x7E) ok = true;
|
||||
break;
|
||||
case 1:
|
||||
if (c == 0xFF) ok = true;
|
||||
break;
|
||||
case 2:
|
||||
if (c== 0x06) ok = true;
|
||||
break;
|
||||
case 3:
|
||||
_recvCMD = c; // CMD byte
|
||||
ok = true;
|
||||
break;
|
||||
case 6:
|
||||
switch (_recvCMD) {
|
||||
//DIAG(F("I2CDFPlayer: %s, _recvCMD: 0x%x _awaitingResponse: 0x0%x"),_I2CAddress.toString(), _recvCMD, _awaitingResponse);
|
||||
case 0x42:
|
||||
// Response to status query
|
||||
_playing = (c != 0);
|
||||
// Mark the device online and cancel timeout
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("I2CDFPlayer: %s, UART_CH: 0x0%x, _deviceState: 0x0%x"),_I2CAddress.toString(), _UART_CH, _deviceState);
|
||||
#endif
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
_awaitingResponse = false;
|
||||
break;
|
||||
case 0x3d:
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: Finished"));
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
// Error codes; 1: Module Busy
|
||||
DIAG(F("I2CDFPlayer: Error %d returned from device"), c);
|
||||
_playing = false;
|
||||
break;
|
||||
}
|
||||
ok = true;
|
||||
break;
|
||||
case 4: case 5: case 7: case 8:
|
||||
ok = true; // Skip over these bytes in message.
|
||||
break;
|
||||
case 9:
|
||||
if (c==0xef) {
|
||||
// Message finished
|
||||
_retryCounter = RETRYCOUNT; // reset the retry counter as we have received a valid packet
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ok){
|
||||
_inputIndex++; // character as expected, so increment index
|
||||
RX_BUFFER --; // Decrease FIFO_RX_LEVEL with each character read from _inbuffer[_inputIndex]
|
||||
} else {
|
||||
_inputIndex = 0; // otherwise reset.
|
||||
RX_BUFFER = 0;
|
||||
}
|
||||
}
|
||||
RX_BUFFER = 0; //Set to 0, we'll read a new RX FIFO level again
|
||||
}
|
||||
|
||||
|
||||
// Send any commands that need to be sent
|
||||
void processOutgoing(unsigned long currentMicros) {
|
||||
// When two commands are sent in quick succession, the device will often fail to
|
||||
// execute one. Testing has indicated that a delay of 100ms or more is required
|
||||
// between successive commands to get reliable operation.
|
||||
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
|
||||
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||
if ( _resetCmd == true){
|
||||
sendPacket(0x0C,0,0);
|
||||
_resetCmd = false;
|
||||
} else if(_volCmd == true) { // do the volme before palying a track
|
||||
if(_requestedVolumeLevel >= 0 && _requestedVolumeLevel <= 30){
|
||||
_currentVolume = _requestedVolumeLevel; // If _requestedVolumeLevel is out of range, sent _currentV1olume
|
||||
}
|
||||
sendPacket(0x06, 0x00, _currentVolume);
|
||||
_volCmd = false;
|
||||
} else if (_playCmd == true) {
|
||||
// Change song
|
||||
if (_requestedSong != -1) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: _requestedVolumeLevel: %u, _requestedSong: %u, _currentFolder: %u _playCmd: 0x%x"), _requestedVolumeLevel, _requestedSong, _currentFolder, _playCmd);
|
||||
#endif
|
||||
sendPacket(0x0F, _currentFolder, _requestedSong); // audio file in folder
|
||||
_requestedSong = -1;
|
||||
_playCmd = false;
|
||||
}
|
||||
} //else if (_requestedSong == 0) {
|
||||
else if (_stopplayCmd == true) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Stop playing: _stopplayCmd: 0x%x"), _stopplayCmd);
|
||||
#endif
|
||||
sendPacket(0x16, 0x00, 0x00); // Stop playing
|
||||
_requestedSong = -1;
|
||||
_repeat = false; // reset repeat
|
||||
_stopplayCmd = false;
|
||||
} else if (_folderCmd == true) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Folder: _folderCmd: 0x%x, _requestedFolder: %d"), _stopplayCmd, _requestedFolder);
|
||||
#endif
|
||||
if (_currentFolder != _requestedFolder){
|
||||
_currentFolder = _requestedFolder;
|
||||
}
|
||||
_folderCmd = false;
|
||||
} else if (_repeatCmd == true) {
|
||||
if(_repeat == false) { // No repeat play currently
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Repeat: _repeatCmd: 0x%x, _requestedSong: %d, _repeat: 0x0%x"), _repeatCmd, _requestedSong, _repeat);
|
||||
#endif
|
||||
sendPacket(0x08, 0x00, _requestedSong); // repeat playing audio file in root folder
|
||||
_requestedSong = -1;
|
||||
_repeat = true;
|
||||
}
|
||||
_repeatCmd= false;
|
||||
} else if (_daconCmd == true) { // Always turn DAC on
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: DACON: _daconCmd: 0x%x"), _daconCmd);
|
||||
#endif
|
||||
sendPacket(0x1A,0,0x00);
|
||||
_daconCmd = false;
|
||||
} else if (_eqCmd == true){ // Set Equalizer, values 0x00 - 0x05
|
||||
if (_currentEQvalue != _requestedEQValue){
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: EQ: _eqCmd: 0x%x, _currentEQvalue: 0x0%x, _requestedEQValue: 0x0%x"), _eqCmd, _currentEQvalue, _requestedEQValue);
|
||||
#endif
|
||||
_currentEQvalue = _requestedEQValue;
|
||||
sendPacket(0x07,0x00,_currentEQvalue);
|
||||
}
|
||||
_eqCmd = false;
|
||||
} else if (_setamCmd == true){ // Set Audio mixer channel
|
||||
setGPIO(); // Set the audio mixer channel
|
||||
/*
|
||||
if (_audioMixer == 1){ // set to audio mixer 1
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
//_setamCmd = false;
|
||||
//UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
} else { // set to audio mixer 2
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL &= (0x00 << _UART_CH); //Set GPIO pin 0 to Low
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL &= (0x00 << _UART_CH); //Set GPIO pin 1 to Low
|
||||
}
|
||||
//_setamCmd = false;
|
||||
//UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
}*/
|
||||
_setamCmd = false;
|
||||
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
|
||||
// Poll device every second that other commands aren't being sent,
|
||||
// to check if it's still connected and responding.
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Send keepalive") );
|
||||
#endif
|
||||
sendPacket(0x42,0,0);
|
||||
if (!_awaitingResponse) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Send keepalive, _awaitingResponse: 0x0%x"), _awaitingResponse );
|
||||
#endif
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write to a vPin will do nothing
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: Writing to any vPin not supported"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0.
|
||||
// Volume may be specified as second parameter to writeAnalogue.
|
||||
// If value is zero, the player stops playing.
|
||||
// WriteAnalogue on second pin sets the output volume.
|
||||
//
|
||||
// WriteAnalogue to be done on first vpin
|
||||
//
|
||||
//void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t cmd=0) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: VPIN:%u FileNo:%d Volume:%d Command:0x%x"), vpin, value, volume, cmd);
|
||||
#endif
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
if (pin == 0) { // Enhanced DFPlayer commands, do nothing if not vPin 0
|
||||
// Read command and value
|
||||
switch (cmd){
|
||||
//case NONE:
|
||||
// DFPlayerCmd = cmd;
|
||||
// break;
|
||||
case PLAY:
|
||||
_playCmd = true;
|
||||
_volCmd = true;
|
||||
_requestedSong = value;
|
||||
_requestedVolumeLevel = volume;
|
||||
_playing = true;
|
||||
break;
|
||||
case VOL:
|
||||
_volCmd = true;
|
||||
_requestedVolumeLevel = volume;
|
||||
break;
|
||||
case FOLDER:
|
||||
_folderCmd = true;
|
||||
if (volume <= 0 || volume > 99){ // Range checking, valid values 1-99, else default to 1
|
||||
_requestedFolder = 0x01; // if outside range, default to folder 01
|
||||
} else {
|
||||
_requestedFolder = volume;
|
||||
}
|
||||
break;
|
||||
case REPEATPLAY: // Need to check if _repeat == true, if so do nothing
|
||||
if (_repeat == false) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WriteAnalog Repeat: _repeat: 0x0%x, value: %d _repeatCmd: 0x%x"), _repeat, value, _repeatCmd);
|
||||
#endif
|
||||
_repeatCmd = true;
|
||||
_requestedSong = value;
|
||||
_requestedVolumeLevel = volume;
|
||||
_playing = true;
|
||||
}
|
||||
break;
|
||||
case STOPPLAY:
|
||||
_stopplayCmd = true;
|
||||
break;
|
||||
case EQ:
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WriteAnalog EQ: cmd: 0x%x, EQ value: 0x%x"), cmd, volume);
|
||||
#endif
|
||||
_eqCmd = true;
|
||||
if (volume <= 0 || volume > 5) { // If out of range, default to NORMAL
|
||||
_requestedEQValue = NORMAL;
|
||||
} else { // Valid EQ parameter range
|
||||
_requestedEQValue = volume;
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
_resetCmd = true;
|
||||
break;
|
||||
case DACON: // Works, but without the DACOFF command limited value, except when not relying on DFPlayer default to turn the DAC on
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WrtieAnalog DACON: cmd: 0x%x"), cmd);
|
||||
#endif
|
||||
_daconCmd = true;
|
||||
break;
|
||||
case SETAM: // Set the audio mixer channel to 1 or 2
|
||||
_setamCmd = true;
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WrtieAnalog SETAM: cmd: 0x%x"), cmd);
|
||||
#endif
|
||||
if (volume <= 0 || volume > 2) { // If out of range, default to 1
|
||||
_audioMixer = 1;
|
||||
} else { // Valid SETAM parameter in range
|
||||
_audioMixer = volume; // _audioMixer valid values 1 or 2
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A read on any pin indicates if the player is still playing.
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
if (pin == 0) { // Do nothing if not vPin 0
|
||||
return _playing;
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("I2CDFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
private:
|
||||
// DFPlayer command frame
|
||||
// 7E FF 06 0F 00 01 01 xx xx EF
|
||||
// 0 -> 7E is start code
|
||||
// 1 -> FF is version
|
||||
// 2 -> 06 is length
|
||||
// 3 -> 0F is command
|
||||
// 4 -> 00 is no receive
|
||||
// 5~6 -> 01 01 is argument
|
||||
// 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 )
|
||||
// 9 -> EF is end code
|
||||
|
||||
void sendPacket(uint8_t command, uint8_t arg1 = 0, uint8_t arg2 = 0) {
|
||||
FIFO_TX_LEVEL = 0; // Reset FIFO_TX_LEVEL
|
||||
uint8_t out[] = {
|
||||
0x7E,
|
||||
0xFF,
|
||||
06,
|
||||
command,
|
||||
00,
|
||||
//static_cast<uint8_t>(arg >> 8),
|
||||
//static_cast<uint8_t>(arg & 0x00ff),
|
||||
arg1,
|
||||
arg2,
|
||||
00,
|
||||
00,
|
||||
0xEF };
|
||||
|
||||
setChecksum(out);
|
||||
|
||||
// Prepend the DFPlayer command with REG address and UART Channel in _outbuffer
|
||||
_outbuffer[0] = REG_THR << 3 | _UART_CH << 1; //TX FIFO and UART Channel
|
||||
for ( int i = 1; i < sizeof(out)+1 ; i++){
|
||||
_outbuffer[i] = out[i-1];
|
||||
}
|
||||
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: I2C: %s Sent packet function"), _I2CAddress.toString());
|
||||
for (int i = 0; i < sizeof _outbuffer; i++){
|
||||
DIAG(F("SC16IS752: Data _outbuffer[0x%x]: 0x%x"), i, _outbuffer[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
TX_fifo_lvl();
|
||||
if(FIFO_TX_LEVEL > 0){ //FIFO is empty
|
||||
I2CManager.write(_I2CAddress, _outbuffer, sizeof(_outbuffer), &_rb);
|
||||
//I2CManager.write(_I2CAddress, _outbuffer, sizeof(_outbuffer));
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: I2C: %s data transmit complete on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("I2CDFPlayer at: %s, TX FIFO not empty on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
_deviceState = DEVSTATE_FAILED; // This should not happen
|
||||
}
|
||||
_commandSendTime = micros();
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
{
|
||||
uint16_t sum = 0;
|
||||
for (int i = 1; i < 7; i++)
|
||||
{
|
||||
sum += packet[i];
|
||||
}
|
||||
return -sum;
|
||||
}
|
||||
|
||||
void setChecksum(uint8_t* out)
|
||||
{
|
||||
uint16_t sum = calcChecksum(out);
|
||||
out[7] = (sum >> 8);
|
||||
out[8] = (sum & 0xff);
|
||||
}
|
||||
|
||||
// SC16IS752 functions
|
||||
// Initialise SC16IS752 only for this channel
|
||||
// First a software reset
|
||||
// Enable FIFO and clear TX & RX FIFO
|
||||
// Need to set the following registers
|
||||
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
||||
// IODIR set all bit to 1 indicating al are output
|
||||
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
||||
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
||||
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
||||
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
||||
// DLH most significant part of divisor
|
||||
// DLL least significant part of divisor
|
||||
//
|
||||
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
||||
//
|
||||
void Init_SC16IS752(){ // Return value is in _deviceState
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: Initialize I2C: %s , UART Ch: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
//uint16_t _divisor = (SC16IS752_XTAL_FREQ / PRESCALER) / (BAUD_RATE * 16);
|
||||
uint16_t _divisor = (_sc16is752_xtal_freq/PRESCALER)/(BAUD_RATE * 16); // Calculate _divisor for baudrate
|
||||
TEMP_REG_VAL = 0x08; // UART Software reset
|
||||
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x00; // Set pins to GPIO mode
|
||||
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0xFF; //Set all pins as output
|
||||
UART_WriteRegister(REG_IODIR, TEMP_REG_VAL);
|
||||
UART_ReadRegister(REG_IOSTATE); // Read current state as not to overwrite the other GPIO pins
|
||||
TEMP_REG_VAL = _inbuffer[0];
|
||||
setGPIO(); // Set the audio mixer channel
|
||||
/*
|
||||
if (_UART_CH == 0){ // Set Audio mixer channel
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
*/
|
||||
TEMP_REG_VAL = 0x07; // Reset FIFO, clear RX & TX FIFO
|
||||
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x00; // Set MCR to all 0, includes Clock divisor
|
||||
UART_WriteRegister(REG_MCR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x80 | WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE;
|
||||
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch enabled
|
||||
UART_WriteRegister(REG_DLL, (uint8_t)_divisor); // Write DLL
|
||||
UART_WriteRegister(REG_DLH, (uint8_t)(_divisor >> 8)); // Write DLH
|
||||
UART_ReadRegister(REG_LCR);
|
||||
TEMP_REG_VAL = _inbuffer[0] & 0x7F; // Disable Divisor latch enabled bit
|
||||
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch disabled
|
||||
|
||||
uint8_t status = _rb.status;
|
||||
if (status != I2C_STATUS_OK) {
|
||||
DIAG(F("SC16IS752: I2C: %s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
} else {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("SC16IS752: I2C: %s, _deviceState: %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
#endif
|
||||
_deviceState = DEVSTATE_NORMAL; // If I2C state is OK, then proceed to initialize DFPlayer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read the Receive FIFO Level register (RXLVL), return a single unsigned integer
|
||||
// of nr of characters in the RX FIFO, bit 6:0, 7 not used, set to zero
|
||||
// value from 0 (0x00) to 64 (0x40) Only display if RX FIFO has data
|
||||
// The RX fifo level is used to check if there are enough bytes to process a frame
|
||||
void RX_fifo_lvl(){
|
||||
UART_ReadRegister(REG_RXLV);
|
||||
FIFO_RX_LEVEL = _inbuffer[0];
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
if (FIFO_RX_LEVEL > 0){
|
||||
//if (FIFO_RX_LEVEL > 0 && FIFO_RX_LEVEL < 10){
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_RX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, _inbuffer[0]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// When a frame is transmitted from the DFPlayer to the serial port, and at the same time the CS is sending a 42 query
|
||||
// the following two frames from the DFPlayer are corrupt. This result in the receive buffer being out of sync and the
|
||||
// CS will complain and generate a timeout.
|
||||
// The RX fifo has corrupt data and need to be flushed, this function does that
|
||||
//
|
||||
void resetRX_fifo(){
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX fifo reset"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
TEMP_REG_VAL = 0x03; // Reset RX fifo
|
||||
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||
}
|
||||
|
||||
// Set or reset GPIO pin 0 and 1 depending on the UART ch
|
||||
// This function may be modified in a future release to enable all 8 pins to be set or reset with EX-Rail
|
||||
// for various auxilary functions
|
||||
void setGPIO(){
|
||||
UART_ReadRegister(REG_IOSTATE); // Get the current GPIO pins state from the IOSTATE register
|
||||
TEMP_REG_VAL = _inbuffer[0];
|
||||
if (_audioMixer == 1){ // set to audio mixer 1
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
} else { // set to audio mixer 2
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 0 to Low
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 1 to Low
|
||||
}
|
||||
}
|
||||
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
_setamCmd = false;
|
||||
}
|
||||
|
||||
|
||||
// Read the Tranmit FIFO Level register (TXLVL), return a single unsigned integer
|
||||
// of nr characters free in the TX FIFO, bit 6:0, 7 not used, set to zero
|
||||
// value from 0 (0x00) to 64 (0x40)
|
||||
//
|
||||
void TX_fifo_lvl(){
|
||||
UART_ReadRegister(REG_TXLV);
|
||||
FIFO_TX_LEVEL = _inbuffer[0];
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
// DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_TX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, FIFO_TX_LEVEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//void UART_WriteRegister(I2CAddress _I2CAddress, uint8_t _UART_CH, uint8_t UART_REG, uint8_t Val, I2CRB &_rb){
|
||||
void UART_WriteRegister(uint8_t UART_REG, uint8_t Val){
|
||||
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1;
|
||||
_outbuffer[1] = Val;
|
||||
#ifdef DIAG_I2CDFplayer_reg
|
||||
DIAG(F("SC16IS752: Write register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _outbuffer[1]);
|
||||
#endif
|
||||
I2CManager.write(_I2CAddress, _outbuffer, 2);
|
||||
}
|
||||
|
||||
|
||||
void UART_ReadRegister(uint8_t UART_REG){
|
||||
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1; // _outbuffer[0] has now UART_REG and UART_CH
|
||||
I2CManager.read(_I2CAddress, _inbuffer, 1, _outbuffer, 1);
|
||||
// _inbuffer has the REG data
|
||||
#ifdef DIAG_I2CDFplayer_reg
|
||||
DIAG(F("SC16IS752: Read register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _inbuffer[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
// SC16IS752 General register set (from the datasheet)
|
||||
enum : uint8_t{
|
||||
REG_RHR = 0x00, // FIFO Read
|
||||
REG_THR = 0x00, // FIFO Write
|
||||
REG_IER = 0x01, // Interrupt Enable Register R/W
|
||||
REG_FCR = 0x02, // FIFO Control Register Write
|
||||
REG_IIR = 0x02, // Interrupt Identification Register Read
|
||||
REG_LCR = 0x03, // Line Control Register R/W
|
||||
REG_MCR = 0x04, // Modem Control Register R/W
|
||||
REG_LSR = 0x05, // Line Status Register Read
|
||||
REG_MSR = 0x06, // Modem Status Register Read
|
||||
REG_SPR = 0x07, // Scratchpad Register R/W
|
||||
REG_TCR = 0x06, // Transmission Control Register R/W
|
||||
REG_TLR = 0x07, // Trigger Level Register R/W
|
||||
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
||||
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
||||
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
||||
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
||||
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
||||
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
||||
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Special register set
|
||||
enum : uint8_t{
|
||||
REG_DLL = 0x00, // Division registers R/W
|
||||
REG_DLH = 0x01, // Division registers R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Enhanced regiter set
|
||||
enum : uint8_t{
|
||||
REG_EFR = 0X02, // Enhanced Features Register R/W
|
||||
REG_XON1 = 0x04, // R/W
|
||||
REG_XON2 = 0x05, // R/W
|
||||
REG_XOFF1 = 0x06, // R/W
|
||||
REG_XOFF2 = 0x07, // R/W
|
||||
};
|
||||
|
||||
// DFPlayer commands and values
|
||||
enum : uint8_t{
|
||||
PLAY = 0x0F,
|
||||
VOL = 0x06,
|
||||
FOLDER = 0x2B, // Not a DFPlayer command, used to set folder nr where audio file is
|
||||
REPEATPLAY = 0x08,
|
||||
STOPPLAY = 0x16,
|
||||
EQ = 0x07, // Set equaliser, require parameter NORMAL, POP, ROCK, JAZZ, CLASSIC or BASS
|
||||
RESET = 0x0C,
|
||||
DACON = 0x1A,
|
||||
SETAM = 0x2A, // Set audio mixer 1 or 2 for this DFPLayer
|
||||
NORMAL = 0x00, // Equalizer parameters
|
||||
POP = 0x01,
|
||||
ROCK = 0x02,
|
||||
JAZZ = 0x03,
|
||||
CLASSIC = 0x04,
|
||||
BASS = 0x05,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_I2CDFPlayer_h
|
794
IO_I2CDFPlayer.h
Normal file
794
IO_I2CDFPlayer.h
Normal file
@@ -0,0 +1,794 @@
|
||||
/*
|
||||
* © 2023, Neil McKechnie. All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX API
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/*
|
||||
* DFPlayer is an MP3 player module with an SD card holder. It also has an integrated
|
||||
* amplifier, so it only needs a power supply and a speaker.
|
||||
* This driver is a modified version of the IO_DFPlayer.h file
|
||||
* *********************************************************************************************
|
||||
*
|
||||
* 2023, Added NXP SC16IS752 I2C Dual UART to enable the DFPlayer connection over the I2C bus
|
||||
* The SC16IS752 has 64 bytes TX & RX FIFO buffer
|
||||
* First version without interrupts from I2C UART and only RX/TX are used, interrupts may not be
|
||||
* needed as the RX Fifo holds the reply
|
||||
*
|
||||
* myHall.cpp configuration syntax:
|
||||
*
|
||||
* I2CDFPlayer::create(1st vPin, vPins, I2C address, UART ch, AM);
|
||||
*
|
||||
* Parameters:
|
||||
* 1st vPin : First virtual pin that EX-Rail can control to play a sound, use PLAYSOUND command (alias of ANOUT)
|
||||
* vPins : Total number of virtual pins allocated (only 1 vPin is supported)
|
||||
* I2C Address : I2C address of the serial controller, in 0x format,
|
||||
* UART ch : Indicating UART 0 or UART 1, values 0 or 1
|
||||
* AM : audio mixer, values: 1 or 2 to select an audio amplifier, no effect if AM is not installed
|
||||
*
|
||||
* The vPin is also an pin that can be read, it indicated if the DFPlayer has finished playing a track
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef IO_I2CDFPlayer_h
|
||||
#define IO_I2CDFPlayer_h
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
|
||||
// Debug and diagnostic defines, enable too many will result in slowing the driver
|
||||
//#define DIAG_I2CDFplayer
|
||||
//#define DIAG_I2CDFplayer_data
|
||||
//#define DIAG_I2CDFplayer_reg
|
||||
//#define DIAG_I2CDFplayer_playing
|
||||
|
||||
class I2CDFPlayer : public IODevice {
|
||||
private:
|
||||
const uint8_t MAXVOLUME=30;
|
||||
uint8_t RETRYCOUNT = 0x03;
|
||||
bool _playing = false;
|
||||
uint8_t _inputIndex = 0;
|
||||
unsigned long _commandSendTime; // Time (us) that last transmit took place.
|
||||
unsigned long _timeoutTime;
|
||||
uint8_t _recvCMD; // Last received command code byte
|
||||
bool _awaitingResponse = false;
|
||||
uint8_t _retryCounter = RETRYCOUNT; // Max retries before timing out
|
||||
uint8_t _requestedVolumeLevel = MAXVOLUME;
|
||||
uint8_t _currentVolume = MAXVOLUME;
|
||||
int _requestedSong = -1; // -1=none, 0=stop, >0=file number
|
||||
bool _repeat = false; // audio file is repeat playing
|
||||
uint8_t _previousCmd = true;
|
||||
// SC16IS752 defines
|
||||
I2CAddress _I2CAddress;
|
||||
I2CRB _rb;
|
||||
uint8_t _UART_CH;
|
||||
// Communication parameters for the DFPlayer are fixed at 8 bit, No parity, 1 stopbit
|
||||
uint8_t WORD_LEN = 0x03; // Value LCR bit 0,1
|
||||
uint8_t STOP_BIT = 0x00; // Value LCR bit 2
|
||||
uint8_t PARITY_ENA = 0x00; // Value LCR bit 3
|
||||
uint8_t PARITY_TYPE = 0x00; // Value LCR bit 4
|
||||
uint32_t BAUD_RATE = 9600;
|
||||
uint8_t PRESCALER = 0x01; // Value MCR bit 7
|
||||
uint8_t TEMP_REG_VAL = 0x00;
|
||||
uint8_t FIFO_RX_LEVEL = 0x00;
|
||||
uint8_t RX_BUFFER = 0x00; // nr of bytes copied into _inbuffer
|
||||
uint8_t FIFO_TX_LEVEL = 0x00;
|
||||
bool _playCmd = false;
|
||||
bool _volCmd = false;
|
||||
bool _folderCmd = false;
|
||||
uint8_t _requestedFolder = 0x01; // default to folder 01
|
||||
uint8_t _currentFolder = 0x01; // default to folder 01
|
||||
bool _repeatCmd = false;
|
||||
bool _stopplayCmd = false;
|
||||
bool _resetCmd = false;
|
||||
bool _eqCmd = false;
|
||||
uint8_t _requestedEQValue = NORMAL;
|
||||
uint8_t _currentEQvalue = NORMAL; // start equalizer value
|
||||
bool _daconCmd = false;
|
||||
uint8_t _audioMixer = 0x01; // Default to output amplifier 1
|
||||
bool _setamCmd = false; // Set the Audio mixer channel
|
||||
uint8_t _outbuffer [11]; // DFPlayer command is 10 bytes + 1 byte register address & UART channel
|
||||
uint8_t _inbuffer[10]; // expected DFPlayer return 10 bytes
|
||||
|
||||
//unsigned long SC16IS752_XTAL_FREQ = 1843200; // To support cheap eBay/AliExpress SC16IS752 boards
|
||||
unsigned long SC16IS752_XTAL_FREQ = 14745600; // Support for higher baud rates, standard for modular EX-IO system
|
||||
|
||||
|
||||
public:
|
||||
// Constructor
|
||||
I2CDFPlayer(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t UART_CH, uint8_t AM){
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = i2cAddress;
|
||||
_UART_CH = UART_CH;
|
||||
_audioMixer = AM;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
public:
|
||||
static void create(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t UART_CH, uint8_t AM) {
|
||||
if (checkNoOverlap(firstVpin, nPins, i2cAddress)) new I2CDFPlayer(firstVpin, nPins, i2cAddress, UART_CH, AM);
|
||||
}
|
||||
|
||||
void _begin() override {
|
||||
// check if SC16IS752 exist first, initialize and then resume DFPlayer init via SC16IS752
|
||||
I2CManager.begin();
|
||||
I2CManager.setClock(1000000);
|
||||
if (I2CManager.exists(_I2CAddress)){
|
||||
DIAG(F("SC16IS752 I2C:%s UART detected"), _I2CAddress.toString());
|
||||
Init_SC16IS752(); // Initialize UART
|
||||
if (_deviceState == DEVSTATE_FAILED){
|
||||
DIAG(F("SC16IS752 I2C:%s UART initialization failed"), _I2CAddress.toString());
|
||||
}
|
||||
} else {
|
||||
DIAG(F("SC16IS752 I2C:%s UART not detected"), _I2CAddress.toString());
|
||||
}
|
||||
#if defined(DIAG_IO)
|
||||
_display();
|
||||
#endif
|
||||
// Now init DFPlayer
|
||||
// Send a query to the device to see if it responds
|
||||
_deviceState = DEVSTATE_INITIALISING;
|
||||
sendPacket(0x42,0,0);
|
||||
_timeoutTime = micros() + 5000000UL; // 5 second timeout
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
|
||||
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Read responses from device
|
||||
uint8_t status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything
|
||||
if (status == I2C_STATUS_OK) {
|
||||
processIncoming(currentMicros);
|
||||
// Check if a command sent to device has timed out. Allow 0.5 second for response
|
||||
// added retry counter, sometimes we do not sent keep alive due to other commands sent to DFPlayer
|
||||
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) { // timeout triggered
|
||||
if(_retryCounter == 0){ // retry counter out of luck, must take the device to failed state
|
||||
DIAG(F("I2CDFPlayer:%s, DFPlayer not responding on UART channel: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
_awaitingResponse = false;
|
||||
_playing = false;
|
||||
_retryCounter = RETRYCOUNT;
|
||||
} else { // timeout and retry protection and recovery of corrupt data frames from DFPlayer
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: %s, DFPlayer timout, retry counter: %d on UART channel: 0x%x"), _I2CAddress.toString(), _retryCounter, _UART_CH);
|
||||
#endif
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds// reset timeout
|
||||
_awaitingResponse = false; // trigger sending a keep alive 0x42 in processOutgoing()
|
||||
_retryCounter --; // decrement retry counter
|
||||
resetRX_fifo(); // reset the RX fifo as it has corrupt data
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
status = _rb.status;
|
||||
if (status == I2C_STATUS_PENDING) return; // Busy, try next time
|
||||
if (status == I2C_STATUS_OK) {
|
||||
// Send any commands that need to go.
|
||||
processOutgoing(currentMicros);
|
||||
}
|
||||
delayUntil(currentMicros + 10000); // Only enter every 10ms
|
||||
}
|
||||
|
||||
|
||||
// Check for incoming data, and update busy flag and other state accordingly
|
||||
|
||||
void processIncoming(unsigned long currentMicros) {
|
||||
// Expected message is in the form "7E FF 06 3D xx xx xx xx xx EF"
|
||||
RX_fifo_lvl();
|
||||
if (FIFO_RX_LEVEL >= 10) {
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("I2CDFPlayer: %s Retrieving data from RX Fifo on UART_CH: 0x%x FIFO_RX_LEVEL: %d"),_I2CAddress.toString(), _UART_CH, FIFO_RX_LEVEL);
|
||||
#endif
|
||||
_outbuffer[0] = REG_RHR << 3 | _UART_CH << 1;
|
||||
// Only copy 10 bytes from RX FIFO, there maybe additional partial return data after a track is finished playing in the RX FIFO
|
||||
I2CManager.read(_I2CAddress, _inbuffer, 10, _outbuffer, 1); // inbuffer[] has the data now
|
||||
//delayUntil(currentMicros + 10000); // Allow time to get the data
|
||||
RX_BUFFER = 10; // We have copied 10 bytes from RX FIFO to _inbuffer
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX FIFO Data"), _I2CAddress.toString(), _UART_CH);
|
||||
for (int i = 0; i < sizeof _inbuffer; i++){
|
||||
DIAG(F("SC16IS752: Data _inbuffer[0x%x]: 0x%x"), i, _inbuffer[i]);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
FIFO_RX_LEVEL = 0; //set to 0, we'll read a fresh FIFO_RX_LEVEL next time
|
||||
return; // No data or not enough data in rx fifo, check again next time around
|
||||
}
|
||||
|
||||
|
||||
bool ok = false;
|
||||
//DIAG(F("I2CDFPlayer: RX_BUFFER: %d"), RX_BUFFER);
|
||||
while (RX_BUFFER != 0) {
|
||||
int c = _inbuffer[_inputIndex]; // Start at 0, increment to FIFO_RX_LEVEL
|
||||
switch (_inputIndex) {
|
||||
case 0:
|
||||
if (c == 0x7E) ok = true;
|
||||
break;
|
||||
case 1:
|
||||
if (c == 0xFF) ok = true;
|
||||
break;
|
||||
case 2:
|
||||
if (c== 0x06) ok = true;
|
||||
break;
|
||||
case 3:
|
||||
_recvCMD = c; // CMD byte
|
||||
ok = true;
|
||||
break;
|
||||
case 6:
|
||||
switch (_recvCMD) {
|
||||
//DIAG(F("I2CDFPlayer: %s, _recvCMD: 0x%x _awaitingResponse: 0x0%x"),_I2CAddress.toString(), _recvCMD, _awaitingResponse);
|
||||
case 0x42:
|
||||
// Response to status query
|
||||
_playing = (c != 0);
|
||||
// Mark the device online and cancel timeout
|
||||
if (_deviceState==DEVSTATE_INITIALISING) {
|
||||
_deviceState = DEVSTATE_NORMAL;
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("I2CDFPlayer: %s, UART_CH: 0x0%x, _deviceState: 0x0%x"),_I2CAddress.toString(), _UART_CH, _deviceState);
|
||||
#endif
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
}
|
||||
_awaitingResponse = false;
|
||||
break;
|
||||
case 0x3d:
|
||||
// End of play
|
||||
if (_playing) {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: Finished"));
|
||||
#endif
|
||||
_playing = false;
|
||||
}
|
||||
break;
|
||||
case 0x40:
|
||||
// Error codes; 1: Module Busy
|
||||
DIAG(F("I2CDFPlayer: Error %d returned from device"), c);
|
||||
_playing = false;
|
||||
break;
|
||||
}
|
||||
ok = true;
|
||||
break;
|
||||
case 4: case 5: case 7: case 8:
|
||||
ok = true; // Skip over these bytes in message.
|
||||
break;
|
||||
case 9:
|
||||
if (c==0xef) {
|
||||
// Message finished
|
||||
_retryCounter = RETRYCOUNT; // reset the retry counter as we have received a valid packet
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (ok){
|
||||
_inputIndex++; // character as expected, so increment index
|
||||
RX_BUFFER --; // Decrease FIFO_RX_LEVEL with each character read from _inbuffer[_inputIndex]
|
||||
} else {
|
||||
_inputIndex = 0; // otherwise reset.
|
||||
RX_BUFFER = 0;
|
||||
}
|
||||
}
|
||||
RX_BUFFER = 0; //Set to 0, we'll read a new RX FIFO level again
|
||||
}
|
||||
|
||||
|
||||
// Send any commands that need to be sent
|
||||
void processOutgoing(unsigned long currentMicros) {
|
||||
// When two commands are sent in quick succession, the device will often fail to
|
||||
// execute one. Testing has indicated that a delay of 100ms or more is required
|
||||
// between successive commands to get reliable operation.
|
||||
// If 100ms has elapsed since the last thing sent, then check if there's some output to do.
|
||||
if (((int32_t)currentMicros - _commandSendTime) > 100000) {
|
||||
if ( _resetCmd == true){
|
||||
sendPacket(0x0C,0,0);
|
||||
_resetCmd = false;
|
||||
} else if(_volCmd == true) { // do the volme before palying a track
|
||||
if(_requestedVolumeLevel >= 0 && _requestedVolumeLevel <= 30){
|
||||
_currentVolume = _requestedVolumeLevel; // If _requestedVolumeLevel is out of range, sent _currentV1olume
|
||||
}
|
||||
sendPacket(0x06, 0x00, _currentVolume);
|
||||
_volCmd = false;
|
||||
} else if (_playCmd == true) {
|
||||
// Change song
|
||||
if (_requestedSong != -1) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: _requestedVolumeLevel: %u, _requestedSong: %u, _currentFolder: %u _playCmd: 0x%x"), _requestedVolumeLevel, _requestedSong, _currentFolder, _playCmd);
|
||||
#endif
|
||||
sendPacket(0x0F, _currentFolder, _requestedSong); // audio file in folder
|
||||
_requestedSong = -1;
|
||||
_playCmd = false;
|
||||
}
|
||||
} //else if (_requestedSong == 0) {
|
||||
else if (_stopplayCmd == true) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Stop playing: _stopplayCmd: 0x%x"), _stopplayCmd);
|
||||
#endif
|
||||
sendPacket(0x16, 0x00, 0x00); // Stop playing
|
||||
_requestedSong = -1;
|
||||
_repeat = false; // reset repeat
|
||||
_stopplayCmd = false;
|
||||
} else if (_folderCmd == true) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Folder: _folderCmd: 0x%x, _requestedFolder: %d"), _stopplayCmd, _requestedFolder);
|
||||
#endif
|
||||
if (_currentFolder != _requestedFolder){
|
||||
_currentFolder = _requestedFolder;
|
||||
}
|
||||
_folderCmd = false;
|
||||
} else if (_repeatCmd == true) {
|
||||
if(_repeat == false) { // No repeat play currently
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Repeat: _repeatCmd: 0x%x, _requestedSong: %d, _repeat: 0x0%x"), _repeatCmd, _requestedSong, _repeat);
|
||||
#endif
|
||||
sendPacket(0x08, 0x00, _requestedSong); // repeat playing audio file in root folder
|
||||
_requestedSong = -1;
|
||||
_repeat = true;
|
||||
}
|
||||
_repeatCmd= false;
|
||||
} else if (_daconCmd == true) { // Always turn DAC on
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: DACON: _daconCmd: 0x%x"), _daconCmd);
|
||||
#endif
|
||||
sendPacket(0x1A,0,0x00);
|
||||
_daconCmd = false;
|
||||
} else if (_eqCmd == true){ // Set Equalizer, values 0x00 - 0x05
|
||||
if (_currentEQvalue != _requestedEQValue){
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: EQ: _eqCmd: 0x%x, _currentEQvalue: 0x0%x, _requestedEQValue: 0x0%x"), _eqCmd, _currentEQvalue, _requestedEQValue);
|
||||
#endif
|
||||
_currentEQvalue = _requestedEQValue;
|
||||
sendPacket(0x07,0x00,_currentEQvalue);
|
||||
}
|
||||
_eqCmd = false;
|
||||
} else if (_setamCmd == true){ // Set Audio mixer channel
|
||||
setGPIO(); // Set the audio mixer channel
|
||||
/*
|
||||
if (_audioMixer == 1){ // set to audio mixer 1
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
//_setamCmd = false;
|
||||
//UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
} else { // set to audio mixer 2
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL &= (0x00 << _UART_CH); //Set GPIO pin 0 to Low
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL &= (0x00 << _UART_CH); //Set GPIO pin 1 to Low
|
||||
}
|
||||
//_setamCmd = false;
|
||||
//UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
}*/
|
||||
_setamCmd = false;
|
||||
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
|
||||
// Poll device every second that other commands aren't being sent,
|
||||
// to check if it's still connected and responding.
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Send keepalive") );
|
||||
#endif
|
||||
sendPacket(0x42,0,0);
|
||||
if (!_awaitingResponse) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: Send keepalive, _awaitingResponse: 0x0%x"), _awaitingResponse );
|
||||
#endif
|
||||
_timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
|
||||
_awaitingResponse = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Write to a vPin will do nothing
|
||||
void _write(VPIN vpin, int value) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: Writing to any vPin not supported"));
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
// WriteAnalogue on first pin uses the nominated value as a file number to start playing, if file number > 0.
|
||||
// Volume may be specified as second parameter to writeAnalogue.
|
||||
// If value is zero, the player stops playing.
|
||||
// WriteAnalogue on second pin sets the output volume.
|
||||
//
|
||||
// WriteAnalogue to be done on first vpin
|
||||
//
|
||||
//void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t=0) override {
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t volume=0, uint16_t cmd=0) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("I2CDFPlayer: VPIN:%u FileNo:%d Volume:%d Command:0x%x"), vpin, value, volume, cmd);
|
||||
#endif
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
if (pin == 0) { // Enhanced DFPlayer commands, do nothing if not vPin 0
|
||||
// Read command and value
|
||||
switch (cmd){
|
||||
//case NONE:
|
||||
// DFPlayerCmd = cmd;
|
||||
// break;
|
||||
case PLAY:
|
||||
_playCmd = true;
|
||||
_volCmd = true;
|
||||
_requestedSong = value;
|
||||
_requestedVolumeLevel = volume;
|
||||
_playing = true;
|
||||
break;
|
||||
case VOL:
|
||||
_volCmd = true;
|
||||
_requestedVolumeLevel = volume;
|
||||
break;
|
||||
case FOLDER:
|
||||
_folderCmd = true;
|
||||
if (volume <= 0 || volume > 99){ // Range checking, valid values 1-99, else default to 1
|
||||
_requestedFolder = 0x01; // if outside range, default to folder 01
|
||||
} else {
|
||||
_requestedFolder = volume;
|
||||
}
|
||||
break;
|
||||
case REPEATPLAY: // Need to check if _repeat == true, if so do nothing
|
||||
if (_repeat == false) {
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WriteAnalog Repeat: _repeat: 0x0%x, value: %d _repeatCmd: 0x%x"), _repeat, value, _repeatCmd);
|
||||
#endif
|
||||
_repeatCmd = true;
|
||||
_requestedSong = value;
|
||||
_requestedVolumeLevel = volume;
|
||||
_playing = true;
|
||||
}
|
||||
break;
|
||||
case STOPPLAY:
|
||||
_stopplayCmd = true;
|
||||
break;
|
||||
case EQ:
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WriteAnalog EQ: cmd: 0x%x, EQ value: 0x%x"), cmd, volume);
|
||||
#endif
|
||||
_eqCmd = true;
|
||||
if (volume <= 0 || volume > 5) { // If out of range, default to NORMAL
|
||||
_requestedEQValue = NORMAL;
|
||||
} else { // Valid EQ parameter range
|
||||
_requestedEQValue = volume;
|
||||
}
|
||||
break;
|
||||
case RESET:
|
||||
_resetCmd = true;
|
||||
break;
|
||||
case DACON: // Works, but without the DACOFF command limited value, except when not relying on DFPlayer default to turn the DAC on
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WrtieAnalog DACON: cmd: 0x%x"), cmd);
|
||||
#endif
|
||||
_daconCmd = true;
|
||||
break;
|
||||
case SETAM: // Set the audio mixer channel to 1 or 2
|
||||
_setamCmd = true;
|
||||
#ifdef DIAG_I2CDFplayer_playing
|
||||
DIAG(F("I2CDFPlayer: WrtieAnalog SETAM: cmd: 0x%x"), cmd);
|
||||
#endif
|
||||
if (volume <= 0 || volume > 2) { // If out of range, default to 1
|
||||
_audioMixer = 1;
|
||||
} else { // Valid SETAM parameter in range
|
||||
_audioMixer = volume; // _audioMixer valid values 1 or 2
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// A read on any pin indicates if the player is still playing.
|
||||
int _read(VPIN vpin) override {
|
||||
if (_deviceState == DEVSTATE_FAILED) return false;
|
||||
uint8_t pin = vpin - _firstVpin;
|
||||
if (pin == 0) { // Do nothing if not vPin 0
|
||||
return _playing;
|
||||
}
|
||||
}
|
||||
|
||||
void _display() override {
|
||||
DIAG(F("I2CDFPlayer Configured on Vpins:%u-%u %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
(_deviceState==DEVSTATE_FAILED) ? F("OFFLINE") : F(""));
|
||||
}
|
||||
|
||||
private:
|
||||
// DFPlayer command frame
|
||||
// 7E FF 06 0F 00 01 01 xx xx EF
|
||||
// 0 -> 7E is start code
|
||||
// 1 -> FF is version
|
||||
// 2 -> 06 is length
|
||||
// 3 -> 0F is command
|
||||
// 4 -> 00 is no receive
|
||||
// 5~6 -> 01 01 is argument
|
||||
// 7~8 -> checksum = 0 - ( FF+06+0F+00+01+01 )
|
||||
// 9 -> EF is end code
|
||||
|
||||
void sendPacket(uint8_t command, uint8_t arg1 = 0, uint8_t arg2 = 0) {
|
||||
FIFO_TX_LEVEL = 0; // Reset FIFO_TX_LEVEL
|
||||
uint8_t out[] = {
|
||||
0x7E,
|
||||
0xFF,
|
||||
06,
|
||||
command,
|
||||
00,
|
||||
//static_cast<uint8_t>(arg >> 8),
|
||||
//static_cast<uint8_t>(arg & 0x00ff),
|
||||
arg1,
|
||||
arg2,
|
||||
00,
|
||||
00,
|
||||
0xEF };
|
||||
|
||||
setChecksum(out);
|
||||
|
||||
// Prepend the DFPlayer command with REG address and UART Channel in _outbuffer
|
||||
_outbuffer[0] = REG_THR << 3 | _UART_CH << 1; //TX FIFO and UART Channel
|
||||
for ( int i = 1; i < sizeof(out)+1 ; i++){
|
||||
_outbuffer[i] = out[i-1];
|
||||
}
|
||||
|
||||
#ifdef DIAG_I2CDFplayer_data
|
||||
DIAG(F("SC16IS752: I2C: %s Sent packet function"), _I2CAddress.toString());
|
||||
for (int i = 0; i < sizeof _outbuffer; i++){
|
||||
DIAG(F("SC16IS752: Data _outbuffer[0x%x]: 0x%x"), i, _outbuffer[i]);
|
||||
}
|
||||
#endif
|
||||
|
||||
TX_fifo_lvl();
|
||||
if(FIFO_TX_LEVEL > 0){ //FIFO is empty
|
||||
I2CManager.write(_I2CAddress, _outbuffer, sizeof(_outbuffer), &_rb);
|
||||
//I2CManager.write(_I2CAddress, _outbuffer, sizeof(_outbuffer));
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: I2C: %s data transmit complete on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
} else {
|
||||
DIAG(F("I2CDFPlayer at: %s, TX FIFO not empty on UART: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
_deviceState = DEVSTATE_FAILED; // This should not happen
|
||||
}
|
||||
_commandSendTime = micros();
|
||||
}
|
||||
|
||||
uint16_t calcChecksum(uint8_t* packet)
|
||||
{
|
||||
uint16_t sum = 0;
|
||||
for (int i = 1; i < 7; i++)
|
||||
{
|
||||
sum += packet[i];
|
||||
}
|
||||
return -sum;
|
||||
}
|
||||
|
||||
void setChecksum(uint8_t* out)
|
||||
{
|
||||
uint16_t sum = calcChecksum(out);
|
||||
out[7] = (sum >> 8);
|
||||
out[8] = (sum & 0xff);
|
||||
}
|
||||
|
||||
// SC16IS752 functions
|
||||
// Initialise SC16IS752 only for this channel
|
||||
// First a software reset
|
||||
// Enable FIFO and clear TX & RX FIFO
|
||||
// Need to set the following registers
|
||||
// IOCONTROL set bit 1 and 2 to 0 indicating that they are GPIO
|
||||
// IODIR set all bit to 1 indicating al are output
|
||||
// IOSTATE set only bit 0 to 1 for UART 0, or only bit 1 for UART 1 //
|
||||
// LCR bit 7=0 divisor latch (clock division registers DLH & DLL, they store 16 bit divisor),
|
||||
// WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE
|
||||
// MCR bit 7=0 clock divisor devide-by-1 clock input
|
||||
// DLH most significant part of divisor
|
||||
// DLL least significant part of divisor
|
||||
//
|
||||
// BAUD_RATE, WORD_LEN, STOP_BIT, PARITY_ENA and PARITY_TYPE have been defined and initialized
|
||||
//
|
||||
void Init_SC16IS752(){ // Return value is in _deviceState
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: Initialize I2C: %s , UART Ch: 0x%x"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
uint16_t _divisor = (SC16IS752_XTAL_FREQ / PRESCALER) / (BAUD_RATE * 16);
|
||||
TEMP_REG_VAL = 0x08; // UART Software reset
|
||||
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x00; // Set pins to GPIO mode
|
||||
UART_WriteRegister(REG_IOCONTROL, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0xFF; //Set all pins as output
|
||||
UART_WriteRegister(REG_IODIR, TEMP_REG_VAL);
|
||||
UART_ReadRegister(REG_IOSTATE); // Read current state as not to overwrite the other GPIO pins
|
||||
TEMP_REG_VAL = _inbuffer[0];
|
||||
setGPIO(); // Set the audio mixer channel
|
||||
/*
|
||||
if (_UART_CH == 0){ // Set Audio mixer channel
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
*/
|
||||
TEMP_REG_VAL = 0x07; // Reset FIFO, clear RX & TX FIFO
|
||||
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x00; // Set MCR to all 0, includes Clock divisor
|
||||
UART_WriteRegister(REG_MCR, TEMP_REG_VAL);
|
||||
TEMP_REG_VAL = 0x80 | WORD_LEN | STOP_BIT | PARITY_ENA | PARITY_TYPE;
|
||||
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch enabled
|
||||
UART_WriteRegister(REG_DLL, (uint8_t)_divisor); // Write DLL
|
||||
UART_WriteRegister(REG_DLH, (uint8_t)(_divisor >> 8)); // Write DLH
|
||||
UART_ReadRegister(REG_LCR);
|
||||
TEMP_REG_VAL = _inbuffer[0] & 0x7F; // Disable Divisor latch enabled bit
|
||||
UART_WriteRegister(REG_LCR, TEMP_REG_VAL); // Divisor latch disabled
|
||||
|
||||
uint8_t status = _rb.status;
|
||||
if (status != I2C_STATUS_OK) {
|
||||
DIAG(F("SC16IS752: I2C: %s failed %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
_deviceState = DEVSTATE_FAILED;
|
||||
} else {
|
||||
#ifdef DIAG_IO
|
||||
DIAG(F("SC16IS752: I2C: %s, _deviceState: %S"), _I2CAddress.toString(), I2CManager.getErrorMessage(status));
|
||||
#endif
|
||||
_deviceState = DEVSTATE_NORMAL; // If I2C state is OK, then proceed to initialize DFPlayer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Read the Receive FIFO Level register (RXLVL), return a single unsigned integer
|
||||
// of nr of characters in the RX FIFO, bit 6:0, 7 not used, set to zero
|
||||
// value from 0 (0x00) to 64 (0x40) Only display if RX FIFO has data
|
||||
// The RX fifo level is used to check if there are enough bytes to process a frame
|
||||
void RX_fifo_lvl(){
|
||||
UART_ReadRegister(REG_RXLV);
|
||||
FIFO_RX_LEVEL = _inbuffer[0];
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
if (FIFO_RX_LEVEL > 0){
|
||||
//if (FIFO_RX_LEVEL > 0 && FIFO_RX_LEVEL < 10){
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_RX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, _inbuffer[0]);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
// When a frame is transmitted from the DFPlayer to the serial port, and at the same time the CS is sending a 42 query
|
||||
// the following two frames from the DFPlayer are corrupt. This result in the receive buffer being out of sync and the
|
||||
// CS will complain and generate a timeout.
|
||||
// The RX fifo has corrupt data and need to be flushed, this function does that
|
||||
//
|
||||
void resetRX_fifo(){
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, RX fifo reset"), _I2CAddress.toString(), _UART_CH);
|
||||
#endif
|
||||
TEMP_REG_VAL = 0x03; // Reset RX fifo
|
||||
UART_WriteRegister(REG_FCR, TEMP_REG_VAL);
|
||||
}
|
||||
|
||||
// Set or reset GPIO pin 0 and 1 depending on the UART ch
|
||||
// This function may be modified in a future release to enable all 8 pins to be set or reset with EX-Rail
|
||||
// for various auxilary functions
|
||||
void setGPIO(){
|
||||
UART_ReadRegister(REG_IOSTATE); // Get the current GPIO pins state from the IOSTATE register
|
||||
TEMP_REG_VAL = _inbuffer[0];
|
||||
if (_audioMixer == 1){ // set to audio mixer 1
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 0 to high
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL |= (0x01 << _UART_CH); //Set GPIO pin 1 to high
|
||||
}
|
||||
} else { // set to audio mixer 2
|
||||
if (_UART_CH == 0){
|
||||
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 0 to Low
|
||||
} else { // must be UART 1
|
||||
TEMP_REG_VAL &= ~(0x01 << _UART_CH); //Set GPIO pin 1 to Low
|
||||
}
|
||||
}
|
||||
UART_WriteRegister(REG_IOSTATE, TEMP_REG_VAL);
|
||||
_setamCmd = false;
|
||||
}
|
||||
|
||||
|
||||
// Read the Tranmit FIFO Level register (TXLVL), return a single unsigned integer
|
||||
// of nr characters free in the TX FIFO, bit 6:0, 7 not used, set to zero
|
||||
// value from 0 (0x00) to 64 (0x40)
|
||||
//
|
||||
void TX_fifo_lvl(){
|
||||
UART_ReadRegister(REG_TXLV);
|
||||
FIFO_TX_LEVEL = _inbuffer[0];
|
||||
#ifdef DIAG_I2CDFplayer
|
||||
// DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_TX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, FIFO_TX_LEVEL);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
//void UART_WriteRegister(I2CAddress _I2CAddress, uint8_t _UART_CH, uint8_t UART_REG, uint8_t Val, I2CRB &_rb){
|
||||
void UART_WriteRegister(uint8_t UART_REG, uint8_t Val){
|
||||
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1;
|
||||
_outbuffer[1] = Val;
|
||||
#ifdef DIAG_I2CDFplayer_reg
|
||||
DIAG(F("SC16IS752: Write register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _outbuffer[1]);
|
||||
#endif
|
||||
I2CManager.write(_I2CAddress, _outbuffer, 2);
|
||||
}
|
||||
|
||||
|
||||
void UART_ReadRegister(uint8_t UART_REG){
|
||||
_outbuffer[0] = UART_REG << 3 | _UART_CH << 1; // _outbuffer[0] has now UART_REG and UART_CH
|
||||
I2CManager.read(_I2CAddress, _inbuffer, 1, _outbuffer, 1);
|
||||
// _inbuffer has the REG data
|
||||
#ifdef DIAG_I2CDFplayer_reg
|
||||
DIAG(F("SC16IS752: Read register at I2C: %s, UART channel: 0x%x, Register: 0x%x, Data: 0b%b"), _I2CAddress.toString(), _UART_CH, UART_REG, _inbuffer[0]);
|
||||
#endif
|
||||
}
|
||||
|
||||
// SC16IS752 General register set (from the datasheet)
|
||||
enum : uint8_t{
|
||||
REG_RHR = 0x00, // FIFO Read
|
||||
REG_THR = 0x00, // FIFO Write
|
||||
REG_IER = 0x01, // Interrupt Enable Register R/W
|
||||
REG_FCR = 0x02, // FIFO Control Register Write
|
||||
REG_IIR = 0x02, // Interrupt Identification Register Read
|
||||
REG_LCR = 0x03, // Line Control Register R/W
|
||||
REG_MCR = 0x04, // Modem Control Register R/W
|
||||
REG_LSR = 0x05, // Line Status Register Read
|
||||
REG_MSR = 0x06, // Modem Status Register Read
|
||||
REG_SPR = 0x07, // Scratchpad Register R/W
|
||||
REG_TCR = 0x06, // Transmission Control Register R/W
|
||||
REG_TLR = 0x07, // Trigger Level Register R/W
|
||||
REG_TXLV = 0x08, // Transmitter FIFO Level register Read
|
||||
REG_RXLV = 0x09, // Receiver FIFO Level register Read
|
||||
REG_IODIR = 0x0A, // Programmable I/O pins Direction register R/W
|
||||
REG_IOSTATE = 0x0B, // Programmable I/O pins State register R/W
|
||||
REG_IOINTENA = 0x0C, // I/O Interrupt Enable register R/W
|
||||
REG_IOCONTROL = 0x0E, // I/O Control register R/W
|
||||
REG_EFCR = 0x0F, // Extra Features Control Register R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Special register set
|
||||
enum : uint8_t{
|
||||
REG_DLL = 0x00, // Division registers R/W
|
||||
REG_DLH = 0x01, // Division registers R/W
|
||||
};
|
||||
|
||||
// SC16IS752 Enhanced regiter set
|
||||
enum : uint8_t{
|
||||
REG_EFR = 0X02, // Enhanced Features Register R/W
|
||||
REG_XON1 = 0x04, // R/W
|
||||
REG_XON2 = 0x05, // R/W
|
||||
REG_XOFF1 = 0x06, // R/W
|
||||
REG_XOFF2 = 0x07, // R/W
|
||||
};
|
||||
|
||||
// DFPlayer commands and values
|
||||
enum : uint8_t{
|
||||
PLAY = 0x0F,
|
||||
VOL = 0x06,
|
||||
FOLDER = 0x2B, // Not a DFPlayer command, used to set folder nr where audio file is
|
||||
REPEATPLAY = 0x08,
|
||||
STOPPLAY = 0x16,
|
||||
EQ = 0x07, // Set equaliser, require parameter NORMAL, POP, ROCK, JAZZ, CLASSIC or BASS
|
||||
RESET = 0x0C,
|
||||
DACON = 0x1A,
|
||||
SETAM = 0x2A, // Set audio mixer 1 or 2 for this DFPLayer
|
||||
NORMAL = 0x00, // Equalizer parameters
|
||||
POP = 0x01,
|
||||
ROCK = 0x02,
|
||||
JAZZ = 0x03,
|
||||
CLASSIC = 0x04,
|
||||
BASS = 0x05,
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
#endif // IO_I2CDFPlayer_h
|
69
IO_Template.h
Normal file
69
IO_Template.h
Normal file
@@ -0,0 +1,69 @@
|
||||
|
||||
/*
|
||||
* Creation - a create() function and constructor are required;
|
||||
* Initialisation - a _begin() function is written (optional);
|
||||
* Background operations - a _loop() function is written (optional);
|
||||
* Operations - you can optionally supply any of _write() (digital) function, _writeAnalogue() function, _read() (digital) function and _readAnalogue() function.
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#ifndef IO_MYDEVICE_H
|
||||
#define IO_MYDEVICE_H
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "DIAG.h" // for DIAG calls
|
||||
|
||||
class MyDevice: public IODevice {
|
||||
public:
|
||||
// Constructor
|
||||
MyDevice(VPIN firstVpin, int nPins) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = min(nPins,16);
|
||||
// Other object initialisation here
|
||||
// ...
|
||||
addDevice(this);
|
||||
}
|
||||
static void create(VPIN firstVpin, int nPins, uint8_t i2cAddress) {
|
||||
new MyDevice(firstVpin, nPins);
|
||||
}
|
||||
private:
|
||||
void _begin() override {
|
||||
// Initialise device
|
||||
// ...
|
||||
}
|
||||
void _loop(unsigned long currentMicros) override {
|
||||
// Regular operations, e.g. acquire data
|
||||
// ...
|
||||
delayUntil(currentMicros + 10*1000UL); // 10ms till next entry
|
||||
}
|
||||
int _readAnalogue(VPIN vpin) override {
|
||||
// Return acquired data value, e.g.
|
||||
int pin = vpin - _firstVpin;
|
||||
return _value[pin];
|
||||
}
|
||||
int _read(VPIN vpin) override {
|
||||
// Return acquired data value, e.g.
|
||||
int pin = vpin - _firstVpin;
|
||||
return _value[pin];
|
||||
}
|
||||
void write(VPIN vpin, int value) override {
|
||||
// Do something with value , e.g. write to device.
|
||||
// ...
|
||||
}
|
||||
void writeAnalogue(VPIN vpin, int value) override {
|
||||
// Do something with value, e.g. write to device.
|
||||
// ...
|
||||
}
|
||||
void _display() override {
|
||||
DIAG(F("MyDevice Configured on Vpins:%d-%d %S"), _firstVpin, _firstVpin+_nPins-1,
|
||||
_deviceState == DEVSTATE_FAILED ? F("OFFLINE") : F(""));
|
||||
}
|
||||
uint16_t _value[16];
|
||||
};
|
||||
#endif // IO_MYDEVICE_H
|
@@ -605,10 +605,6 @@ 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))){
|
||||
DIAG(F("TRACK %c INVERT"), trackno + 'A');
|
||||
invertOutput();
|
||||
}
|
||||
break;
|
||||
}
|
||||
// all well
|
||||
@@ -680,10 +676,8 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
power_sample_overload_wait *= 2;
|
||||
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
DIAG(F("Calling EXRAIL"));
|
||||
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
|
||||
#endif
|
||||
// power on test
|
||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||
setPower(POWERMODE::ALERT);
|
||||
|
@@ -3,7 +3,7 @@
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020 Chris Harlow
|
||||
* © 2022,2023 Harald Barth
|
||||
* © 2022 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -28,15 +28,8 @@
|
||||
#include "DCCTimer.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)
|
||||
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, TRACK_MODE_BOOST = 32,
|
||||
TRACK_MODE_ALL = 62, // only to operate all tracks
|
||||
TRACK_MODE_INV = 64, TRACK_MODE_DCX = 72 /*DC + INV*/, TRACK_MODE_AUTOINV = 128};
|
||||
TRACK_MODE_DC = 8, TRACK_MODE_DCX = 16, TRACK_MODE_EXT = 32};
|
||||
|
||||
#define setHIGH(fastpin) *fastpin.inout |= fastpin.maskHIGH
|
||||
#define setLOW(fastpin) *fastpin.inout &= fastpin.maskLOW
|
||||
@@ -155,9 +148,7 @@ class MotorDriver {
|
||||
// otherwise the call from interrupt context can undo whatever we do
|
||||
// from outside interrupt
|
||||
void setBrake( bool on, bool interruptContext=false);
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
if (invertPhase)
|
||||
high = !high;
|
||||
__attribute__((always_inline)) inline void setSignal( bool high) {
|
||||
if (trackPWM) {
|
||||
DCCTimer::setPWM(signalPin,high);
|
||||
}
|
||||
@@ -177,12 +168,6 @@ class MotorDriver {
|
||||
pinMode(signalPin, OUTPUT);
|
||||
else
|
||||
pinMode(signalPin, INPUT);
|
||||
if (signalPin2 != UNUSED_PIN) {
|
||||
if (on)
|
||||
pinMode(signalPin2, OUTPUT);
|
||||
else
|
||||
pinMode(signalPin2, INPUT);
|
||||
}
|
||||
};
|
||||
inline pinpair getSignalPin() { return pinpair(signalPin,signalPin2); };
|
||||
void setDCSignal(byte speedByte);
|
||||
@@ -247,32 +232,6 @@ class MotorDriver {
|
||||
#endif
|
||||
inline void setMode(TRACK_MODE m) {
|
||||
trackMode = m;
|
||||
invertOutput(trackMode & TRACK_MODE_INV);
|
||||
};
|
||||
inline void invertOutput() { // toggles output inversion
|
||||
invertPhase = !invertPhase;
|
||||
invertOutput(invertPhase);
|
||||
};
|
||||
inline void invertOutput(bool b) { // sets output inverted or not
|
||||
if (b)
|
||||
invertPhase = 1;
|
||||
else
|
||||
invertPhase = 0;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
pinpair p = getSignalPin();
|
||||
uint32_t *outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.pin);
|
||||
if (invertPhase) // set or clear the invert bit in the gpio out register
|
||||
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||
else
|
||||
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
outreg = (uint32_t *)(GPIO_FUNC0_OUT_SEL_CFG_REG + 4*p.invpin);
|
||||
if (invertPhase) // clear or set the invert bit in the gpio out register
|
||||
*outreg &= ~((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||
else
|
||||
*outreg |= ((uint32_t)0x1 << GPIO_FUNC0_OUT_INV_SEL_S);
|
||||
}
|
||||
#endif
|
||||
};
|
||||
inline TRACK_MODE getMode() {
|
||||
return trackMode;
|
||||
@@ -304,7 +263,7 @@ class MotorDriver {
|
||||
bool invertBrake; // brake pin passed as negative means pin is inverted
|
||||
bool invertPower; // power pin passed as negative means pin is inverted
|
||||
bool invertFault; // fault pin passed as negative means pin is inverted
|
||||
bool invertPhase = 0; // phase of out pin is inverted
|
||||
|
||||
// Raw to milliamp conversion factors avoiding float data types.
|
||||
// Milliamps=rawADCreading * sensefactorInternal / senseScale
|
||||
//
|
||||
|
@@ -117,7 +117,6 @@ void StringFormatter::send2(Print * stream,const FSH* format, va_list args) {
|
||||
case 'o': stream->print(va_arg(args, int), OCT); break;
|
||||
case 'x': stream->print((unsigned int)va_arg(args, unsigned int), HEX); break;
|
||||
case 'X': stream->print((unsigned long)va_arg(args, unsigned long), HEX); break;
|
||||
case 'h': printHex(stream,(unsigned int)va_arg(args, unsigned int)); break;
|
||||
case 'M':
|
||||
{ // this prints a unsigned long microseconds time in readable format
|
||||
unsigned long time = va_arg(args, long);
|
||||
@@ -219,15 +218,4 @@ void StringFormatter::printPadded(Print* stream, long value, byte width, bool fo
|
||||
if (!formatLeft) stream->print(value, DEC);
|
||||
}
|
||||
|
||||
// printHex prints the full 2 byte hex with leading zeros, unlike print(value,HEX)
|
||||
const char FLASH hexchars[]="0123456789ABCDEF";
|
||||
void StringFormatter::printHex(Print * stream,uint16_t value) {
|
||||
char result[5];
|
||||
for (int i=3;i>=0;i--) {
|
||||
result[i]=GETFLASH(hexchars+(value & 0x0F));
|
||||
value>>=4;
|
||||
}
|
||||
result[4]='\0';
|
||||
stream->print(result);
|
||||
}
|
||||
|
||||
|
||||
|
@@ -49,7 +49,6 @@ class StringFormatter
|
||||
static void lcd2(uint8_t display, byte row, const FSH* input...);
|
||||
static void printEscapes(char * input);
|
||||
static void printEscape( char c);
|
||||
static void printHex(Print * stream,uint16_t value);
|
||||
|
||||
private:
|
||||
static void send2(Print * serial, const FSH* input,va_list args);
|
||||
|
291
TrackManager.cpp
291
TrackManager.cpp
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022,2023 Harald Barth
|
||||
* © 2022 Harald Barth
|
||||
* © 2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -26,8 +26,7 @@
|
||||
#include "MotorDriver.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "DCCEXParser.h"
|
||||
#include"CommandDistributor.h"
|
||||
// Virtualised Motor shield multi-track hardware Interface
|
||||
#define FOR_EACH_TRACK(t) for (byte t=0;t<=lastTrack;t++)
|
||||
|
||||
@@ -45,11 +44,6 @@ const int16_t HASH_KEYWORD_DC = 2183;
|
||||
const int16_t HASH_KEYWORD_DCX = 6463; // DC reversed polarity
|
||||
const int16_t HASH_KEYWORD_EXT = 8201; // External DCC signal
|
||||
const int16_t HASH_KEYWORD_A = 65; // parser makes single chars the ascii.
|
||||
const int16_t HASH_KEYWORD_AUTO = -5457;
|
||||
#ifdef BOOSTER_INPUT
|
||||
const int16_t HASH_KEYWORD_BOOST = 11269;
|
||||
#endif
|
||||
const int16_t HASH_KEYWORD_INV = 11857;
|
||||
|
||||
MotorDriver * TrackManager::track[MAX_TRACKS];
|
||||
int16_t TrackManager::trackDCAddr[MAX_TRACKS];
|
||||
@@ -92,7 +86,7 @@ void TrackManager::sampleCurrent() {
|
||||
if (!waiting) {
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_BOOST|TRACK_MODE_EXT )) {
|
||||
if (track[tr]->getMode() & ( TRACK_MODE_MAIN|TRACK_MODE_PROG|TRACK_MODE_DC|TRACK_MODE_DCX|TRACK_MODE_EXT )) {
|
||||
track[tr]->startCurrentFromHW();
|
||||
// for scope debug track[1]->setBrake(1);
|
||||
waiting = true;
|
||||
@@ -202,8 +196,8 @@ void TrackManager::setPROGSignal( bool on) {
|
||||
void TrackManager::setDCSignal(int16_t cab, byte speedbyte) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (trackDCAddr[t]!=cab && cab != 0) continue;
|
||||
if (track[t]->getMode() & TRACK_MODE_DC)
|
||||
track[t]->setDCSignal(speedbyte);
|
||||
if (track[t]->getMode()==TRACK_MODE_DC) track[t]->setDCSignal(speedbyte);
|
||||
else if (track[t]->getMode()==TRACK_MODE_DCX) track[t]->setDCSignal(speedbyte ^ 128);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -212,7 +206,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
|
||||
//DIAG(F("Track=%c Mode=%d"),trackToSet+'A', mode);
|
||||
// DC tracks require a motorDriver that can set brake!
|
||||
if (mode & TRACK_MODE_DC) {
|
||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
||||
#if defined(ARDUINO_AVR_UNO)
|
||||
DIAG(F("Uno has no PWM timers available for DC"));
|
||||
return false;
|
||||
@@ -228,37 +222,21 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
pinpair p = track[trackToSet]->getSignalPin();
|
||||
//DIAG(F("Track=%c remove pin %d"),trackToSet+'A', p.pin);
|
||||
gpio_reset_pin((gpio_num_t)p.pin);
|
||||
pinMode(p.pin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
//DIAG(F("Track=%c remove ^pin %d"),trackToSet+'A', p.invpin);
|
||||
gpio_reset_pin((gpio_num_t)p.invpin);
|
||||
pinMode(p.invpin, OUTPUT); // gpio_reset_pin may reset to input
|
||||
}
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (mode & TRACK_MODE_BOOST) {
|
||||
//DIAG(F("Track=%c mode boost pin %d"),trackToSet+'A', p.pin);
|
||||
pinMode(BOOSTER_INPUT, INPUT);
|
||||
gpio_matrix_in(26, SIG_IN_FUNC228_IDX, false); //pads 224 to 228 available as loopback
|
||||
gpio_matrix_out(p.pin, SIG_IN_FUNC228_IDX, false, false);
|
||||
if (p.invpin != UNUSED_PIN) {
|
||||
gpio_matrix_out(p.invpin, SIG_IN_FUNC228_IDX, true /*inverted*/, false);
|
||||
}
|
||||
} else // elseif clause continues
|
||||
#endif
|
||||
if (mode & (TRACK_MODE_MAIN | TRACK_MODE_PROG | TRACK_MODE_DC)) {
|
||||
// gpio_reset_pin may reset to input
|
||||
pinMode(p.pin, OUTPUT);
|
||||
if (p.invpin != UNUSED_PIN)
|
||||
pinMode(p.invpin, OUTPUT);
|
||||
}
|
||||
|
||||
#endif
|
||||
#ifndef DISABLE_PROG
|
||||
if (mode & TRACK_MODE_PROG) {
|
||||
if (mode==TRACK_MODE_PROG) {
|
||||
#else
|
||||
if (false) {
|
||||
#endif
|
||||
// only allow 1 track to be prog
|
||||
FOR_EACH_TRACK(t)
|
||||
if ( (track[t]->getMode() & TRACK_MODE_PROG) && t != trackToSet) {
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG && t != trackToSet) {
|
||||
track[t]->setPower(POWERMODE::OFF);
|
||||
track[t]->setMode(TRACK_MODE_NONE);
|
||||
track[t]->makeProgTrack(false); // revoke prog track special handling
|
||||
@@ -276,20 +254,16 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
// state, otherwise trains run away or just dont move.
|
||||
|
||||
// This can be done BEFORE the PWM-Timer evaluation (methinks)
|
||||
if (!(mode & TRACK_MODE_DC)) {
|
||||
if (!(mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX)) {
|
||||
// DCC tracks need to have set the PWM to zero or they will not work.
|
||||
track[trackToSet]->detachDCSignal();
|
||||
track[trackToSet]->setBrake(false);
|
||||
}
|
||||
|
||||
// BOOST:
|
||||
// Leave it as is
|
||||
// otherwise:
|
||||
// EXT is a special case where the signal pin is
|
||||
// turned off. So unless that is set, the signal
|
||||
// pin should be turned on
|
||||
if (!(mode & TRACK_MODE_BOOST))
|
||||
track[trackToSet]->enableSignal(!(mode & TRACK_MODE_EXT));
|
||||
// EXT is a special case where the signal pin is
|
||||
// turned off. So unless that is set, the signal
|
||||
// pin should be turned on
|
||||
track[trackToSet]->enableSignal(mode != TRACK_MODE_EXT);
|
||||
|
||||
#ifndef ARDUINO_ARCH_ESP32
|
||||
// re-evaluate HighAccuracy mode
|
||||
@@ -299,7 +273,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
// DC tracks must not have the DCC PWM switched on
|
||||
// so we globally turn it off if one of the PWM
|
||||
// capable tracks is now DC or DCX.
|
||||
if (track[t]->getMode() & TRACK_MODE_DC) {
|
||||
if (track[t]->getMode()==TRACK_MODE_DC || track[t]->getMode()==TRACK_MODE_DCX) {
|
||||
if (track[t]->isPWMCapable()) {
|
||||
canDo=false; // this track is capable but can not run PWM
|
||||
break; // in this mode, so abort and prevent globally below
|
||||
@@ -307,7 +281,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
track[t]->trackPWM=false; // this track sure can not run with PWM
|
||||
//DIAG(F("Track %c trackPWM 0 (not capable)"), t+'A');
|
||||
}
|
||||
} else if (track[t]->getMode() & (TRACK_MODE_MAIN |TRACK_MODE_PROG)) {
|
||||
} else if (track[t]->getMode()==TRACK_MODE_MAIN || track[t]->getMode()==TRACK_MODE_PROG) {
|
||||
track[t]->trackPWM = track[t]->isPWMCapable(); // trackPWM is still a guess here
|
||||
//DIAG(F("Track %c trackPWM %d"), t+'A', track[t]->trackPWM);
|
||||
canDo &= track[t]->trackPWM;
|
||||
@@ -325,12 +299,10 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
#else
|
||||
// For ESP32 we just reinitialize the DCC Waveform
|
||||
DCCWaveform::begin();
|
||||
// setMode() again AFTER Waveform::begin() of ESP32 fixes INVERTED signal
|
||||
track[trackToSet]->setMode(mode);
|
||||
#endif
|
||||
|
||||
// This block must be AFTER the PWM-Timer modifications
|
||||
if (mode & TRACK_MODE_DC) {
|
||||
if (mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX) {
|
||||
// DC tracks need to be given speed of the throttle for that cab address
|
||||
// otherwise will not match other tracks on same cab.
|
||||
// This also needs to allow for inverted DCX
|
||||
@@ -339,7 +311,7 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
|
||||
// Normal running tracks are set to the global power state
|
||||
track[trackToSet]->setPower(
|
||||
(mode & (TRACK_MODE_MAIN | TRACK_MODE_DC | TRACK_MODE_EXT | TRACK_MODE_BOOST)) ?
|
||||
(mode==TRACK_MODE_MAIN || mode==TRACK_MODE_DC || mode==TRACK_MODE_DCX || mode==TRACK_MODE_EXT) ?
|
||||
mainPowerGuess : POWERMODE::OFF);
|
||||
//DIAG(F("TrackMode=%d"),mode);
|
||||
return true;
|
||||
@@ -347,6 +319,8 @@ bool TrackManager::setTrackMode(byte trackToSet, TRACK_MODE mode, int16_t dcAddr
|
||||
|
||||
void TrackManager::applyDCSpeed(byte t) {
|
||||
uint8_t speedByte=DCC::getThrottleSpeedByte(trackDCAddr[t]);
|
||||
if (track[t]->getMode()==TRACK_MODE_DCX)
|
||||
speedByte = speedByte ^ 128; // reverse direction bit
|
||||
track[t]->setDCSignal(speedByte);
|
||||
}
|
||||
|
||||
@@ -357,7 +331,6 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
FOR_EACH_TRACK(t)
|
||||
streamTrackState(stream,t);
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
p[0]-=HASH_KEYWORD_A; // convert A... to 0....
|
||||
@@ -378,21 +351,12 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_EXT) // <= id EXT>
|
||||
return setTrackMode(p[0],TRACK_MODE_EXT);
|
||||
#ifdef BOOSTER_INPUT
|
||||
if (params==2 && p[1]==HASH_KEYWORD_BOOST) // <= id BOOST>
|
||||
return setTrackMode(p[0],TRACK_MODE_BOOST);
|
||||
#endif
|
||||
if (params==2 && p[1]==HASH_KEYWORD_AUTO) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_AUTOINV);
|
||||
|
||||
if (params==2 && p[1]==HASH_KEYWORD_INV) // <= id AUTO>
|
||||
return setTrackMode(p[0], track[p[0]]->getMode() | TRACK_MODE_INV);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DC && p[2]>0) // <= id DC cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC,p[2]);
|
||||
|
||||
if (params==3 && p[1]==HASH_KEYWORD_DCX && p[2]>0) // <= id DCX cab>
|
||||
return setTrackMode(p[0],TRACK_MODE_DC|TRACK_MODE_INV,p[2]);
|
||||
return setTrackMode(p[0],TRACK_MODE_DCX,p[2]);
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -400,44 +364,33 @@ bool TrackManager::parseJ(Print *stream, int16_t params, int16_t p[])
|
||||
void TrackManager::streamTrackState(Print* stream, byte t) {
|
||||
// null stream means send to commandDistributor for broadcast
|
||||
if (track[t]==NULL) return;
|
||||
auto format=F("<= %d XXX>\n");
|
||||
TRACK_MODE tm = track[t]->getMode();
|
||||
if (tm & TRACK_MODE_MAIN) {
|
||||
if(tm & TRACK_MODE_AUTOINV)
|
||||
format=F("<= %c MAIN A>\n");
|
||||
else if (tm & TRACK_MODE_INV)
|
||||
format=F("<= %c MAIN I>\n");
|
||||
else
|
||||
auto format=F("");
|
||||
switch(track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
format=F("<= %c MAIN>\n");
|
||||
}
|
||||
break;
|
||||
#ifndef DISABLE_PROG
|
||||
else if (tm & TRACK_MODE_PROG)
|
||||
format=F("<= %c PROG>\n");
|
||||
case TRACK_MODE_PROG:
|
||||
format=F("<= %c PROG>\n");
|
||||
break;
|
||||
#endif
|
||||
else if (tm & TRACK_MODE_NONE)
|
||||
format=F("<= %c NONE>\n");
|
||||
else if(tm & TRACK_MODE_EXT)
|
||||
format=F("<= %c EXT>\n");
|
||||
else if(tm & TRACK_MODE_BOOST) {
|
||||
if(tm & TRACK_MODE_AUTOINV)
|
||||
format=F("<= %c B A>\n");
|
||||
else if (tm & TRACK_MODE_INV)
|
||||
format=F("<= %c B I>\n");
|
||||
else
|
||||
format=F("<= %c B>\n");
|
||||
}
|
||||
else if (tm & TRACK_MODE_DC) {
|
||||
if (tm & TRACK_MODE_INV)
|
||||
format=F("<= %c DCX %d>\n");
|
||||
else
|
||||
case TRACK_MODE_NONE:
|
||||
format=F("<= %c NONE>\n");
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
format=F("<= %c EXT>\n");
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
format=F("<= %c DC %d>\n");
|
||||
break;
|
||||
case TRACK_MODE_DCX:
|
||||
format=F("<= %c DCX %d>\n");
|
||||
break;
|
||||
default:
|
||||
break; // unknown, dont care
|
||||
}
|
||||
|
||||
if (stream)
|
||||
StringFormatter::send(stream,format,'A'+t, trackDCAddr[t]);
|
||||
else
|
||||
CommandDistributor::broadcastTrackState(format,'A'+t, trackDCAddr[t]);
|
||||
|
||||
if (stream) StringFormatter::send(stream,format,'A'+t,trackDCAddr[t]);
|
||||
else CommandDistributor::broadcastTrackState(format,'A'+t,trackDCAddr[t]);
|
||||
}
|
||||
|
||||
byte TrackManager::nextCycleTrack=MAX_TRACKS;
|
||||
@@ -452,13 +405,13 @@ void TrackManager::loop() {
|
||||
if (nextCycleTrack>lastTrack) nextCycleTrack=0;
|
||||
if (track[nextCycleTrack]==NULL) return;
|
||||
MotorDriver * motorDriver=track[nextCycleTrack];
|
||||
bool useProgLimit=dontLimitProg ? false : (bool)(track[nextCycleTrack]->getMode() & TRACK_MODE_PROG);
|
||||
bool useProgLimit=dontLimitProg? false: track[nextCycleTrack]->getMode()==TRACK_MODE_PROG;
|
||||
motorDriver->checkPowerOverload(useProgLimit, nextCycleTrack);
|
||||
}
|
||||
|
||||
MotorDriver * TrackManager::getProgDriver() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG) return track[t];
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG) return track[t];
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -466,93 +419,56 @@ MotorDriver * TrackManager::getProgDriver() {
|
||||
std::vector<MotorDriver *>TrackManager::getMainDrivers() {
|
||||
std::vector<MotorDriver *> v;
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode() & TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
if (track[t]->getMode()==TRACK_MODE_MAIN) v.push_back(track[t]);
|
||||
return v;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Set track power for all tracks with this mode
|
||||
void TrackManager::setTrackPower(TRACK_MODE trackmode, POWERMODE powermode) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver *driver=track[t];
|
||||
if (trackmode & driver->getMode()) {
|
||||
if (powermode == POWERMODE::ON) {
|
||||
if (trackmode & TRACK_MODE_DC) {
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
} else {
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
}
|
||||
}
|
||||
driver->setPower(powermode);
|
||||
void TrackManager::setPower2(bool setProg,POWERMODE mode) {
|
||||
if (!setProg) mainPowerGuess=mode;
|
||||
FOR_EACH_TRACK(t) {
|
||||
MotorDriver * driver=track[t];
|
||||
if (!driver) continue;
|
||||
switch (track[t]->getMode()) {
|
||||
case TRACK_MODE_MAIN:
|
||||
if (setProg) break;
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
// XXX see if we can make this conditional
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_DC:
|
||||
case TRACK_MODE_DCX:
|
||||
if (setProg) break;
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_PROG:
|
||||
if (!setProg) break;
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_EXT:
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false);
|
||||
driver->setPower(mode);
|
||||
break;
|
||||
case TRACK_MODE_NONE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Set track power for this track, inependent of mode
|
||||
void TrackManager::setTrackPower(POWERMODE powermode, byte t) {
|
||||
MotorDriver *driver=track[t];
|
||||
TRACK_MODE trackmode = driver->getMode();
|
||||
if (trackmode & TRACK_MODE_DC) {
|
||||
if (powermode == POWERMODE::ON) {
|
||||
driver->setBrake(true); // DC starts with brake on
|
||||
applyDCSpeed(t); // speed match DCC throttles
|
||||
}
|
||||
} else {
|
||||
if (powermode == POWERMODE::ON) {
|
||||
// toggle brake before turning power on - resets overcurrent error
|
||||
// on the Pololu board if brake is wired to ^D2.
|
||||
driver->setBrake(true);
|
||||
driver->setBrake(false); // DCC runs with brake off
|
||||
}
|
||||
}
|
||||
driver->setPower(powermode);
|
||||
}
|
||||
|
||||
void TrackManager::reportPowerChange(Print* stream, byte thistrack) {
|
||||
// This function is for backward JMRI compatibility only
|
||||
// It reports the first track only, as main, regardless of track settings.
|
||||
// <c MeterName value C/V unit min max res warn>
|
||||
int maxCurrent=track[0]->raw2mA(track[0]->getRawCurrentTripValue());
|
||||
StringFormatter::send(stream, F("<c CurrentMAIN %d C Milli 0 %d 1 %d>\n"),
|
||||
track[0]->raw2mA(track[0]->getCurrentRaw(false)), maxCurrent, maxCurrent);
|
||||
}
|
||||
|
||||
// returns state of the one and only prog track
|
||||
|
||||
POWERMODE TrackManager::getProgPower() {
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG)
|
||||
return track[t]->getPower(); // optimize: there is max one prog track
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
|
||||
// returns on if all are on. returns off otherwise
|
||||
POWERMODE TrackManager::getMainPower() {
|
||||
POWERMODE result = POWERMODE::OFF;
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (track[t]->getMode() & TRACK_MODE_MAIN) {
|
||||
POWERMODE p = track[t]->getPower();
|
||||
if (p == POWERMODE::OFF)
|
||||
return POWERMODE::OFF; // done and out
|
||||
if (p == POWERMODE::ON)
|
||||
result = POWERMODE::ON;
|
||||
}
|
||||
FOR_EACH_TRACK(t)
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG)
|
||||
return track[t]->getPower();
|
||||
return POWERMODE::OFF;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool TrackManager::getPower(byte t, char s[]) {
|
||||
if (track[t]) {
|
||||
s[0] = track[t]->getPower() == POWERMODE::ON ? '1' : '0';
|
||||
s[2] = t + 'A';
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void TrackManager::reportObsoleteCurrent(Print* stream) {
|
||||
// This function is for backward JMRI compatibility only
|
||||
@@ -594,7 +510,7 @@ void TrackManager::setJoin(bool joined) {
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
if (joined) {
|
||||
FOR_EACH_TRACK(t) {
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG) {
|
||||
if (track[t]->getMode()==TRACK_MODE_PROG) {
|
||||
tempProgTrack = t;
|
||||
setTrackMode(t, TRACK_MODE_MAIN);
|
||||
break;
|
||||
@@ -622,32 +538,3 @@ bool TrackManager::isPowerOn(byte t) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TrackManager::isProg(byte t) {
|
||||
if (track[t]->getMode() & TRACK_MODE_PROG)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
byte TrackManager::returnMode(byte t) {
|
||||
return (track[t]->getMode());
|
||||
}
|
||||
|
||||
int16_t TrackManager::returnDCAddr(byte t) {
|
||||
return (trackDCAddr[t]);
|
||||
}
|
||||
|
||||
const char* TrackManager::getModeName(byte Mode) {
|
||||
|
||||
//DIAG(F("PowerMode %d"), Mode);
|
||||
|
||||
switch (Mode)
|
||||
{
|
||||
case 1: return "NONE";
|
||||
case 2: return "MAIN";
|
||||
case 4: return "PROG";
|
||||
case 8: return "DC";
|
||||
case 16: return "DCX";
|
||||
case 32: return "EXT";
|
||||
default: return "----";
|
||||
}
|
||||
}
|
||||
|
@@ -39,10 +39,6 @@ const byte TRACK_NUMBER_5=5, TRACK_NUMBER_F=5;
|
||||
const byte TRACK_NUMBER_6=6, TRACK_NUMBER_G=6;
|
||||
const byte TRACK_NUMBER_7=7, TRACK_NUMBER_H=7;
|
||||
|
||||
// These constants help EXRAIL macros convert Track Power e.g. SET_POWER(A ON|OFF).
|
||||
const byte TRACK_POWER_0=0, TRACK_POWER_OFF=0;
|
||||
const byte TRACK_POWER_1=1, TRACK_POWER_ON=1;
|
||||
|
||||
class TrackManager {
|
||||
public:
|
||||
static void Setup(const FSH * shieldName,
|
||||
@@ -62,36 +58,28 @@ class TrackManager {
|
||||
static void setDCSignal(int16_t cab, byte speedbyte);
|
||||
static MotorDriver * getProgDriver();
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static std::vector<MotorDriver *>getMainDrivers();
|
||||
static std::vector<MotorDriver *>getMainDrivers();
|
||||
#endif
|
||||
|
||||
static void setPower2(bool progTrack,POWERMODE mode);
|
||||
static void setPower(POWERMODE mode) {setMainPower(mode); setProgPower(mode);}
|
||||
static void setTrackPower(POWERMODE mode, byte t);
|
||||
static void setTrackPower(TRACK_MODE trackmode, POWERMODE powermode);
|
||||
static void setMainPower(POWERMODE mode) {setTrackPower(TRACK_MODE_MAIN, mode);}
|
||||
static void setProgPower(POWERMODE mode) {setTrackPower(TRACK_MODE_PROG, mode);}
|
||||
static void setMainPower(POWERMODE mode) {setPower2(false,mode);}
|
||||
static void setProgPower(POWERMODE mode) {setPower2(true,mode);}
|
||||
|
||||
static const int16_t MAX_TRACKS=8;
|
||||
static bool setTrackMode(byte track, TRACK_MODE mode, int16_t DCaddr=0);
|
||||
static bool parseJ(Print * stream, int16_t params, int16_t p[]);
|
||||
static void loop();
|
||||
static POWERMODE getMainPower();
|
||||
static POWERMODE getMainPower() {return mainPowerGuess;}
|
||||
static POWERMODE getProgPower();
|
||||
static bool getPower(byte t, char s[]);
|
||||
static void setJoin(bool join);
|
||||
static bool isJoined() { return progTrackSyncMain;}
|
||||
static void setJoinRelayPin(byte joinRelayPin);
|
||||
static void sampleCurrent();
|
||||
static void reportGauges(Print* stream);
|
||||
static void reportCurrent(Print* stream);
|
||||
static void reportPowerChange(Print* stream, byte thistrack);
|
||||
static void reportObsoleteCurrent(Print* stream);
|
||||
static void streamTrackState(Print* stream, byte t);
|
||||
static bool isPowerOn(byte t);
|
||||
static bool isProg(byte t);
|
||||
static byte returnMode(byte t);
|
||||
static int16_t returnDCAddr(byte t);
|
||||
static const char* getModeName(byte Mode);
|
||||
|
||||
static int16_t joinRelay;
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
@@ -111,7 +99,7 @@ class TrackManager {
|
||||
static POWERMODE mainPowerGuess;
|
||||
static void applyDCSpeed(byte t);
|
||||
|
||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC
|
||||
static int16_t trackDCAddr[MAX_TRACKS]; // dc address if TRACK_MODE_DC or TRACK_MODE_DCX
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
static byte tempProgTrack; // holds the prog track number during join
|
||||
#endif
|
||||
|
@@ -185,7 +185,7 @@ public:
|
||||
for (Turntable *tto = _firstTurntable; tto != 0; tto = tto->_nextTurntable)
|
||||
if (!tto->isHidden()) {
|
||||
gotOne = true;
|
||||
StringFormatter::send(stream, F("<I %d %d>\n"), tto->getId(), tto->getPosition());
|
||||
StringFormatter::send(stream, F("<i %d %d>\n"), tto->getId(), tto->getPosition());
|
||||
}
|
||||
return gotOne;
|
||||
}
|
||||
|
@@ -38,7 +38,6 @@
|
||||
#include "soc/timer_group_struct.h"
|
||||
#include "soc/timer_group_reg.h"
|
||||
void feedTheDog0(){
|
||||
#if defined(ARDUINO_ESP32_DEV)
|
||||
// feed dog 0
|
||||
TIMERG0.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||
TIMERG0.wdt_feed=1; // feed dog
|
||||
@@ -47,13 +46,6 @@ void feedTheDog0(){
|
||||
//TIMERG1.wdt_wprotect=TIMG_WDT_WKEY_VALUE; // write enable
|
||||
//TIMERG1.wdt_feed=1; // feed dog
|
||||
//TIMERG1.wdt_wprotect=0; // write protect
|
||||
#elif defined(ARDUINO_ESP32S3_DEV)
|
||||
TIMERG0.wdtwprotect.wdt_wkey = TIMG_WDT_WKEY_V; // write enable
|
||||
TIMERG0.wdtfeed.wdt_feed = 1; // feed dog
|
||||
TIMERG0.wdtwprotect.wdt_wkey = 0; // write protect
|
||||
#else
|
||||
#warning This ESP32 variant not supported
|
||||
#endif
|
||||
}
|
||||
|
||||
/*
|
||||
|
@@ -37,5 +37,5 @@ public:
|
||||
static void loop();
|
||||
private:
|
||||
};
|
||||
#endif // include guard
|
||||
#endif // ARDUINO_ARCH_ESP32
|
||||
#endif //WifiESP8266_h
|
||||
#endif //ESP8266
|
||||
|
@@ -201,19 +201,17 @@ wifiSerialState WifiInterface::setup2(const FSH* SSid, const FSH* password,
|
||||
// Display the AT version information
|
||||
StringFormatter::send(wifiStream, F("AT+GMR\r\n"));
|
||||
if (checkForOK(2000, F("AT version:"), true, false)) {
|
||||
char version[] = "0.0.0.0-xxx";
|
||||
for (int i=0; i<11;i++) {
|
||||
char version[] = "0.0.0.0";
|
||||
for (int i=0; i<8;i++) {
|
||||
while(!wifiStream->available());
|
||||
version[i]=wifiStream->read();
|
||||
StringFormatter::printEscape(version[i]);
|
||||
}
|
||||
if ((version[0] == '0') ||
|
||||
(version[0] == '2' && version[2] == '0') ||
|
||||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0'
|
||||
&& version[7] == '-' && version[8] == 'd' && version[9] == 'e' && version[10] == 'v')) {
|
||||
DIAG(F("You need to up/downgrade the ESP firmware"));
|
||||
SSid = F("UPDATE_ESP_FIRMWARE");
|
||||
forceAP = true;
|
||||
if ((version[0] == '0') ||
|
||||
(version[0] == '2' && version[2] == '0') ||
|
||||
(version[0] == '2' && version[2] == '2' && version[4] == '0' && version[6] == '0')) {
|
||||
SSid = F("DCCEX_SAYS_BROKEN_FIRMWARE");
|
||||
forceAP = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
checkForOK(2000, true, false);
|
||||
|
@@ -266,12 +266,6 @@ The configuration file for DCC-EX Command Station
|
||||
//
|
||||
//#define SERIAL_BT_COMMANDS
|
||||
|
||||
// BOOSTER PIN INPUT ON ESP32
|
||||
// On ESP32 you have the possibility to define a pin as booster input
|
||||
// Arduio pin D2 is GPIO 26 on ESPDuino32
|
||||
//
|
||||
//#define BOOSTER_INPUT 26
|
||||
|
||||
// SABERTOOTH
|
||||
//
|
||||
// This is a very special option and only useful if you happen to have a
|
||||
|
@@ -125,15 +125,9 @@
|
||||
#define ARDUINO_TYPE "ESP8266"
|
||||
#warning "ESP8266 platform untested, you are on your own"
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
#define ARDUINO_TYPE "ESP32"
|
||||
#ifndef DISABLE_EEPROM
|
||||
#define DISABLE_EEPROM
|
||||
#if defined(ARDUINO_ESP32_DEV)
|
||||
#define ARDUINO_TYPE "ESP32-WROOM"
|
||||
#elif defined (ARDUINO_ESP32S3_DEV)
|
||||
#define ARDUINO_TYPE "ESP32-S3"
|
||||
// #undef USB_SERIAL
|
||||
// #define USB_SERIAL SerialUSB
|
||||
#endif
|
||||
#endif
|
||||
#elif defined(ARDUINO_ARCH_SAMD)
|
||||
#define ARDUINO_TYPE "SAMD21"
|
||||
|
@@ -234,6 +234,23 @@ void halSetup() {
|
||||
// DFPlayer::create(10000, 10, Serial1);
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// Play mp3 files from a Micro-SD card, using a DFPlayer MP3 Module on a SC16IS752 I2C Dual UART
|
||||
//=======================================================================
|
||||
// DFPlayer via NXP SC16IS752 I2C Dual UART. Each device has 2 UARTs on a single I2C address
|
||||
// Total nr of devices on an I2C bus is 16, with 2 UARTs on each address making a total of 32 UARTs per I2C bus
|
||||
// I2C address range 0x48 - 0x57
|
||||
// I2CDFPlayer::create(1st vPin,vPins, I2C address, UART ch);
|
||||
|
||||
// I2CDFPlayer::create(10000, 10, 0x48, 0);
|
||||
// I2CDFPlayer::create(10010, 10, 0x48, 1);
|
||||
|
||||
// Multiplexer example
|
||||
// I2CDFPlayer::create(10020, 10, {I2CMux_0, SubBus_0, 0x50}, 0);
|
||||
|
||||
|
||||
|
||||
|
||||
//=======================================================================
|
||||
// 16-pad capacitative touch key pad based on TP229 IC.
|
||||
//=======================================================================
|
||||
|
@@ -30,6 +30,7 @@ include_dir = .
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
; monitor_filters = time
|
||||
|
||||
[env:samd21-dev-usb]
|
||||
platform = atmelsam
|
||||
@@ -192,6 +193,9 @@ build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
; Experimental - no reason this should not work, but not
|
||||
; tested as yet
|
||||
;
|
||||
[env:Nucleo-F401RE]
|
||||
platform = ststm32
|
||||
board = nucleo_f401re
|
||||
@@ -201,6 +205,59 @@ build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F13ZH has variant files
|
||||
; but NOT the nucleo_f413zh.json file which needs to be
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F413ZH]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f413zh
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F446ZE needs variant files
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F446ZE]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f446ze
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
|
||||
; Commented out by default as the F412ZG needs variant files
|
||||
; installed before you can let PlatformIO see this
|
||||
;
|
||||
; [env:Nucleo-F412ZG]
|
||||
; platform = ststm32
|
||||
; board = blah_f412zg
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
; upload_protocol = stlink
|
||||
|
||||
; Experimental - Ethernet work still in progress
|
||||
;
|
||||
; [env:Nucleo-F429ZI]
|
||||
; platform = ststm32
|
||||
; board = nucleo_f429zi
|
||||
; framework = arduino
|
||||
; lib_deps = ${env.lib_deps}
|
||||
; arduino-libraries/Ethernet @ ^2.0.1
|
||||
; stm32duino/STM32Ethernet @ ^1.3.0
|
||||
; stm32duino/STM32duino LwIP @ ^2.1.2
|
||||
; build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
; monitor_speed = 115200
|
||||
; monitor_echo = yes
|
||||
; upload_protocol = stlink
|
||||
|
||||
[env:Teensy3_2]
|
||||
platform = teensy
|
||||
board = teensy31
|
||||
@@ -239,18 +296,4 @@ board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
|
||||
[env:ESP32_S3]
|
||||
platform = espressif32
|
||||
board = esp32-s3-devkitc-1
|
||||
framework = arduino
|
||||
board_upload.flash_size = 8MB
|
||||
board_build.partitions = default_8MB.csv
|
||||
build_flags =
|
||||
-std=c++17
|
||||
-DARDUINO_USB_CDC_ON_BOOT=1
|
||||
board_build.arduino.memory_type = opi_opi
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
lib_ignore =
|
||||
|
19
version.h
19
version.h
@@ -3,24 +3,7 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.2.2"
|
||||
// 5.2.2 - ESP32-S3 initial code support, WDT, ADCee(), but note RMT not tested!
|
||||
// 5.2.1 - Trackmanager rework for simpler structure
|
||||
// 5.2.0 - ESP32: Autoreverse and booster mode support
|
||||
// 5.1.21 - EXRAIL invoke multiple ON handlers for same event
|
||||
// 5.1.20 - EXRAIL Tidy and ROUTE_STATE, ROUTE_CAPTION
|
||||
// 5.1.19 - Only flag 2.2.0.0-dev as broken, not 2.2.0.0
|
||||
// 5.1.18 - TURNOUTL bugfix
|
||||
// 5.1.17 - Divide out C for config and D for diag commands
|
||||
// 5.1.16 - Remove I2C address from EXTT_TURNTABLE macro to work with MUX, requires separate HAL macro to create
|
||||
// 5.1.15 - LCC/Adapter support and Exrail feature-compile-out.
|
||||
// 5.1.14 - Fixed IFTTPOSITION
|
||||
// 5.1.13 - Changed turntable broadcast from i to I due to server string conflict
|
||||
// 5.1.12 - Added Power commands <0 A> & <1 A> etc. and update to <=>
|
||||
// Added EXRAIL SET_POWER(track, ON/OFF)
|
||||
// Fixed a problem whereby <1 MAIN> also powered on PROG track
|
||||
// Added functions to TrackManager.cpp to allow UserAddin code for power display on OLED/LCD
|
||||
// Added - returnMode(byte t), returnDCAddr(byte t) & getModeName(byte Mode)
|
||||
#define VERSION "5.1.11"
|
||||
// 5.1.11 - STM32F4xx revised I2C clock setup, no correctly sets clock and has fully variable frequency selection
|
||||
// 5.1.10 - STM32F4xx DCCEXanalogWrite to handle PWM generation for TrackManager DC/DCX
|
||||
// - STM32F4xx DCC 58uS timer now using non-PWM output timers where possible
|
||||
|
Reference in New Issue
Block a user