mirror of
https://github.com/DCC-EX/CommandStation-EX.git
synced 2025-07-29 02:13:45 +02:00
Compare commits
98 Commits
v5.0.6-Pro
...
I2CDFplaye
Author | SHA1 | Date | |
---|---|---|---|
|
74c5a974e2 | ||
|
0e9caa11c8 | ||
|
46673007cc | ||
|
c468979501 | ||
|
0feb2c74e7 | ||
|
e74c619fac | ||
|
32ecbbe147 | ||
|
b8e0185540 | ||
|
5e0cf8eb74 | ||
|
44ce1c0cfa | ||
|
22b066c400 | ||
|
370dae0ab8 | ||
|
2ff1619ad1 | ||
|
7afd4443d6 | ||
|
ed0cfee091 | ||
|
624656ebc9 | ||
|
5a7f278b1e | ||
|
11b9fd4ef5 | ||
|
d07718be8c | ||
|
d57b5ba537 | ||
|
dab02ec659 | ||
|
6ad5326f1d | ||
|
39e1363ce0 | ||
|
c9d4f5e94d | ||
|
dfe3e9d42c | ||
|
5d810a620b | ||
|
550ad58c4d | ||
|
7a305e179c | ||
|
8437b0e7aa | ||
|
ebbeea5fbb | ||
|
a8321fff42 | ||
|
a16790f585 | ||
|
da6a3c442f | ||
|
4fcd81a118 | ||
|
eb450dbd89 | ||
|
a0562fdf5c | ||
|
7ee2c29a52 | ||
|
dba5d35aa2 | ||
|
be10be5a1a | ||
|
dca023ffd7 | ||
|
4eef9581fe | ||
|
bd02d1c15b | ||
|
004d7b6631 | ||
|
21ce87eb3e | ||
|
ab393047c1 | ||
|
1f5f7754c1 | ||
|
6adff43f4b | ||
|
1ac104704e | ||
|
2f8e915b1e | ||
|
152f9850bb | ||
|
3094c52bf8 | ||
|
86f4567556 | ||
|
dd890e65bf | ||
|
1e48c59cd8 | ||
|
004d10ee58 | ||
|
e734661d1b | ||
|
bcb250bacf | ||
|
798241927f | ||
|
df2f09f4d2 | ||
|
f40d57d8bd | ||
|
9fa213e198 | ||
|
44d8154223 | ||
|
01919b33df | ||
|
a0c1ad182c | ||
|
dbf053858b | ||
|
232ac993ec | ||
|
26ddd27ecf | ||
|
6cad794411 | ||
|
b0d8510127 | ||
|
3bfdd16288 | ||
|
df4a501e8a | ||
|
2202cb0c5e | ||
|
1425da20b5 | ||
|
b823a647ac | ||
|
2c64f10da8 | ||
|
25426d076d | ||
|
3453da0671 | ||
|
fb226311e5 | ||
|
6392c74ead | ||
|
25f8852af6 | ||
|
9842ea8a42 | ||
|
fa0aa27d46 | ||
|
57d4655d54 | ||
|
ff9c558b61 | ||
|
b277d204f0 | ||
|
c4febd1d0f | ||
|
98f8022268 | ||
|
1491da4813 | ||
|
4b2c0702a4 | ||
|
e27cceeb74 | ||
|
247763ac00 | ||
|
e327e0ae8d | ||
|
9f38dae8ba | ||
|
e51f8e9c0a | ||
|
4f43a413b5 | ||
|
4f56837d28 | ||
|
cc2846d932 | ||
|
83325ebf78 |
@@ -161,6 +161,10 @@ void CommandDistributor::broadcastTurnout(int16_t id, bool isClosed ) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastTurntable(int16_t id, uint8_t position, bool moving) {
|
||||
broadcastReply(COMMAND_TYPE, F("<i %d %d %d>\n"), id, position, moving);
|
||||
}
|
||||
|
||||
void CommandDistributor::broadcastClockTime(int16_t time, int8_t rate) {
|
||||
// The JMRI clock command is of the form : PFT65871<;>4
|
||||
// The CS broadcast is of the form "<jC mmmm nn" where mmmm is time minutes and dd speed
|
||||
|
@@ -49,6 +49,7 @@ public :
|
||||
static void broadcastLoco(byte slot);
|
||||
static void broadcastSensor(int16_t id, bool value);
|
||||
static void broadcastTurnout(int16_t id, bool isClosed);
|
||||
static void broadcastTurntable(int16_t id, uint8_t position, bool moving);
|
||||
static void broadcastClockTime(int16_t time, int8_t rate);
|
||||
static void setClockTime(int16_t time, int8_t rate, byte opt);
|
||||
static int16_t retClockTime();
|
||||
|
5407
Console log
Normal file
5407
Console log
Normal file
File diff suppressed because it is too large
Load Diff
189
DCCEXParser.cpp
189
DCCEXParser.cpp
@@ -114,6 +114,7 @@ Once a new OPCODE is decided upon, update this list.
|
||||
#include "TrackManager.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "Turntables.h"
|
||||
|
||||
// This macro can't be created easily as a portable function because the
|
||||
// flashlist requires a far pointer for high flash access.
|
||||
@@ -157,6 +158,8 @@ const int16_t HASH_KEYWORD_A='A';
|
||||
const int16_t HASH_KEYWORD_C='C';
|
||||
const int16_t HASH_KEYWORD_G='G';
|
||||
const int16_t HASH_KEYWORD_I='I';
|
||||
const int16_t HASH_KEYWORD_O='O';
|
||||
const int16_t HASH_KEYWORD_P='P';
|
||||
const int16_t HASH_KEYWORD_R='R';
|
||||
const int16_t HASH_KEYWORD_T='T';
|
||||
const int16_t HASH_KEYWORD_X='X';
|
||||
@@ -168,6 +171,8 @@ const int16_t HASH_KEYWORD_ANOUT = -26399;
|
||||
const int16_t HASH_KEYWORD_WIFI = -5583;
|
||||
const int16_t HASH_KEYWORD_ETHERNET = -30767;
|
||||
const int16_t HASH_KEYWORD_WIT = 31594;
|
||||
const int16_t HASH_KEYWORD_EXTT = 8573;
|
||||
const int16_t HASH_KEYWORD_ADD = 3201;
|
||||
|
||||
int16_t DCCEXParser::stashP[MAX_COMMAND_PARAMS];
|
||||
bool DCCEXParser::stashBusy;
|
||||
@@ -451,12 +456,16 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
case 'w': // WRITE CV on MAIN <w CAB CV VALUE>
|
||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||
return;
|
||||
if (params != 3)
|
||||
break;
|
||||
DCC::writeCVByteMain(p[0], p[1], p[2]);
|
||||
return;
|
||||
|
||||
case 'b': // WRITE CV BIT ON MAIN <b CAB CV BIT VALUE>
|
||||
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
|
||||
return;
|
||||
if (params != 4)
|
||||
break;
|
||||
DCC::writeCVBitMain(p[0], p[1], p[2], p[3]);
|
||||
return;
|
||||
#endif
|
||||
|
||||
case 'M': // WRITE TRANSPARENT DCC PACKET MAIN <M REG X1 ... X9>
|
||||
@@ -479,14 +488,16 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
|
||||
#ifndef DISABLE_PROG
|
||||
case 'W': // WRITE CV ON PROG <W CV VALUE CALLBACKNUM CALLBACKSUB>
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
break;
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
break;
|
||||
if (params == 1) // <W id> Write new loco id (clearing consist and managing short/long)
|
||||
DCC::setLocoId(p[0],callback_Wloco);
|
||||
else if (params == 4) // WRITE CV ON PROG <W CV VALUE [CALLBACKNUM] [CALLBACKSUB]>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W4);
|
||||
else // WRITE CV ON PROG <W CV VALUE>
|
||||
else if (params == 2) // WRITE CV ON PROG <W CV VALUE>
|
||||
DCC::writeCVByte(p[0], p[1], callback_W);
|
||||
else
|
||||
break;
|
||||
return;
|
||||
|
||||
case 'V': // VERIFY CV ON PROG <V CV VALUE> <V CV BIT 0|1>
|
||||
@@ -506,9 +517,11 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
break;
|
||||
|
||||
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB>
|
||||
case 'B': // WRITE CV BIT ON PROG <B CV BIT VALUE CALLBACKNUM CALLBACKSUB> or <B CV BIT VALUE>
|
||||
if (params != 3 && params != 5)
|
||||
break;
|
||||
if (!stashCallback(stream, p, ringStream))
|
||||
break;
|
||||
break;
|
||||
DCC::writeCVBit(p[0], p[1], p[2], callback_B);
|
||||
return;
|
||||
|
||||
@@ -638,12 +651,12 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
case ' ': // < >
|
||||
StringFormatter::send(stream, F("\n"));
|
||||
return;
|
||||
|
||||
#ifndef DISABLE_DIAG
|
||||
case 'D': // < >
|
||||
if (parseD(stream, params, p))
|
||||
return;
|
||||
return;
|
||||
|
||||
break;
|
||||
#endif
|
||||
case '=': // <= Track manager control >
|
||||
if (TrackManager::parseJ(stream, params, p))
|
||||
return;
|
||||
@@ -762,11 +775,70 @@ void DCCEXParser::parseOne(Print *stream, byte *com, RingStream * ringStream)
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
return;
|
||||
// No turntables without HAL support
|
||||
#ifndef IO_NO_HAL
|
||||
case HASH_KEYWORD_O: // <JO returns turntable list
|
||||
StringFormatter::send(stream, F("<jO"));
|
||||
if (params==1) { // <JO>
|
||||
for (Turntable * tto=Turntable::first(); tto; tto=tto->next()) {
|
||||
if (tto->isHidden()) continue;
|
||||
StringFormatter::send(stream, F(" %d"),tto->getId());
|
||||
}
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
} else { // <JO id>
|
||||
Turntable *tto=Turntable::get(id);
|
||||
if (!tto || tto->isHidden()) {
|
||||
StringFormatter::send(stream, F(" %d X>\n"), id);
|
||||
} else {
|
||||
uint8_t pos = tto->getPosition();
|
||||
uint8_t type = tto->isEXTT();
|
||||
uint8_t posCount = tto->getPositionCount();
|
||||
const FSH *todesc = NULL;
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
todesc = RMFT2::getTurntableDescription(id);
|
||||
#endif
|
||||
if (todesc == NULL) todesc = F("");
|
||||
StringFormatter::send(stream, F(" %d %d %d %d \"%S\">\n"), id, type, pos, posCount, todesc);
|
||||
}
|
||||
}
|
||||
return;
|
||||
case HASH_KEYWORD_P: // <JP id> returns turntable position list for the turntable id
|
||||
if (params==2) { // <JP id>
|
||||
Turntable *tto=Turntable::get(id);
|
||||
if (!tto || tto->isHidden()) {
|
||||
StringFormatter::send(stream, F(" %d X>\n"), id);
|
||||
} else {
|
||||
uint8_t posCount = tto->getPositionCount();
|
||||
const FSH *tpdesc = NULL;
|
||||
for (uint8_t p = 0; p < posCount; p++) {
|
||||
StringFormatter::send(stream, F("<jP"));
|
||||
int16_t angle = tto->getPositionAngle(p);
|
||||
#ifdef EXRAIL_ACTIVE
|
||||
tpdesc = RMFT2::getTurntablePositionDescription(id, p);
|
||||
#endif
|
||||
if (tpdesc == NULL) tpdesc = F("");
|
||||
StringFormatter::send(stream, F(" %d %d %d \"%S\""), id, p, angle, tpdesc);
|
||||
StringFormatter::send(stream, F(">\n"));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
StringFormatter::send(stream, F("<jP X>\n"));
|
||||
}
|
||||
return;
|
||||
#endif
|
||||
default: break;
|
||||
} // switch(p[1])
|
||||
break; // case J
|
||||
}
|
||||
|
||||
// No turntables without HAL support
|
||||
#ifndef IO_NO_HAL
|
||||
case 'I': // TURNTABLE <I ...>
|
||||
if (parseI(stream, params, p))
|
||||
return;
|
||||
break;
|
||||
#endif
|
||||
|
||||
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++)
|
||||
@@ -1086,6 +1158,99 @@ bool DCCEXParser::parseD(Print *stream, int16_t params, int16_t p[])
|
||||
return false;
|
||||
}
|
||||
|
||||
// ==========================
|
||||
// Turntable - no support if no HAL
|
||||
// <I> - list all
|
||||
// <I id> - broadcast type and current position
|
||||
// <I id DCC> - create DCC - This is TBA
|
||||
// <I id steps> - operate (DCC)
|
||||
// <I id steps activity> - operate (EXTT)
|
||||
// <I id ADD position value> - add position
|
||||
// <I id EXTT i2caddress vpin home> - create EXTT
|
||||
#ifndef IO_NO_HAL
|
||||
bool DCCEXParser::parseI(Print *stream, int16_t params, int16_t p[])
|
||||
{
|
||||
switch (params)
|
||||
{
|
||||
case 0: // <I> list turntable objects
|
||||
return Turntable::printAll(stream);
|
||||
|
||||
case 1: // <I id> broadcast type and current position
|
||||
{
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
if (tto) {
|
||||
bool type = tto->isEXTT();
|
||||
uint8_t position = tto->getPosition();
|
||||
StringFormatter::send(stream, F("<i %d %d>\n"), type, position);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case 2: // <I id position> - rotate a DCC turntable
|
||||
{
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
if (tto && !tto->isEXTT()) {
|
||||
if (!tto->setPosition(p[0], p[1])) return false;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case 3: // <I id position activity> | <I id DCC home> - rotate to position for EX-Turntable or create DCC turntable
|
||||
{
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
if (p[1] == HASH_KEYWORD_DCC) {
|
||||
if (tto || p[2] < 0 || p[2] > 3600) return false;
|
||||
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"));
|
||||
} else {
|
||||
if (!tto) return false;
|
||||
if (!tto->isEXTT()) return false;
|
||||
if (!tto->setPosition(p[0], p[1], p[2])) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case 4: // <I id EXTT vpin home> create an EXTT turntable
|
||||
{
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
if (p[1] == HASH_KEYWORD_EXTT) {
|
||||
if (tto || p[3] < 0 || p[3] > 3600) return false;
|
||||
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"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
case 5: // <I id ADD position value angle> add a position
|
||||
{
|
||||
Turntable *tto = Turntable::get(p[0]);
|
||||
if (p[1] == HASH_KEYWORD_ADD) {
|
||||
// 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"));
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
default: // Anything else is invalid
|
||||
return false;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
// CALLBACKS must be static
|
||||
bool DCCEXParser::stashCallback(Print *stream, int16_t p[MAX_COMMAND_PARAMS], RingStream * ringStream)
|
||||
{
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include <Arduino.h>
|
||||
#include "FSH.h"
|
||||
#include "RingStream.h"
|
||||
#include "defines.h"
|
||||
|
||||
typedef void (*FILTER_CALLBACK)(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
typedef void (*AT_COMMAND_CALLBACK)(HardwareSerial * stream,const byte * command);
|
||||
@@ -45,13 +46,16 @@ struct DCCEXParser
|
||||
static int16_t splitValues( int16_t result[MAX_COMMAND_PARAMS], const byte * command, bool usehex);
|
||||
|
||||
static bool parseT(Print * stream, int16_t params, int16_t p[]);
|
||||
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 parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
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 parseD(Print * stream, int16_t params, int16_t p[]);
|
||||
#ifndef IO_NO_HAL
|
||||
static bool parseI(Print * stream, int16_t params, int16_t p[]);
|
||||
#endif
|
||||
|
||||
static Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
static Print * getAsyncReplyStream();
|
||||
static void commitAsyncReplyStream();
|
||||
|
||||
static bool stashBusy;
|
||||
static byte stashTarget;
|
||||
|
@@ -125,8 +125,13 @@ private:
|
||||
// On platforms that scan, it is called from waveform ISR
|
||||
// only on a regular basis.
|
||||
static void scan();
|
||||
#if defined (ARDUINO_ARCH_STM32)
|
||||
// bit array of used pins (max 32)
|
||||
static uint32_t usedpins;
|
||||
#else
|
||||
// bit array of used pins (max 16)
|
||||
static uint16_t usedpins;
|
||||
#endif
|
||||
static uint8_t highestPin;
|
||||
// cached analog values (malloc:ed to actual number of ADC channels)
|
||||
static int *analogvals;
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2023 Neil McKechnie
|
||||
* © 2022-23 Paul M. Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021, 2023 Harald Barth
|
||||
* © 2021 Fred Decker
|
||||
@@ -52,7 +52,7 @@ HardwareSerial Serial6(PA12, PA11); // Rx=PA12, Tx=PA11 -- CN10 pins 12 and 14
|
||||
HardwareSerial Serial3(PC11, PC10); // Rx=PC11, Tx=PC10 -- USART3 - F446RE
|
||||
HardwareSerial Serial5(PD2, PC12); // Rx=PC7, Tx=PC6 -- UART5 - F446RE
|
||||
// On the F446RE, Serial4 and Serial6 also use pins we can't readily map while using the Arduino pins
|
||||
#elif defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)|| defined(ARDUINO_NUCLEO_F412ZG)
|
||||
#elif defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// Nucleo-144 boards don't have Serial1 defined by default
|
||||
HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||
// Serial3 is defined to use USART3 by default, but is in fact used as the diag console
|
||||
@@ -154,13 +154,28 @@ HardwareSerial Serial6(PG9, PG14); // Rx=PG9, Tx=PG14 -- USART6
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
INTERRUPT_CALLBACK interruptHandler=0;
|
||||
// Let's use STM32's timer #11 until disabused of this notion
|
||||
// Timer #11 is used for "servo" library, but as DCC-EX is not using
|
||||
// this libary, we should be free and clear.
|
||||
HardwareTimer timer(TIM11);
|
||||
|
||||
// On STM32F4xx models that have them, Timers 6 and 7 have no PWM output capability,
|
||||
// so are good choices for general timer duties - they are used for tone and servo
|
||||
// in stm32duino so we shall usurp those as DCC-EX doesn't use tone or servo libs.
|
||||
// NB: the F401, F410 and F411 do **not** have Timer 6 or 7, so we use Timer 11
|
||||
#ifndef DCC_EX_TIMER
|
||||
#if defined(TIM6)
|
||||
#define DCC_EX_TIMER TIM6
|
||||
#elif defined(TIM7)
|
||||
#define DCC_EX_TIMER TIM7
|
||||
#elif defined(TIM11)
|
||||
#define DCC_EX_TIMER TIM11
|
||||
#else
|
||||
#warning This STM32F4XX variant does not have Timers 6,7 or 11!!
|
||||
#endif
|
||||
#endif // ifndef DCC_EX_TIMER
|
||||
|
||||
HardwareTimer dcctimer(DCC_EX_TIMER);
|
||||
void DCCTimer_Handler() __attribute__((interrupt));
|
||||
|
||||
// Timer IRQ handler
|
||||
void Timer11_Handler() {
|
||||
void DCCTimer_Handler() {
|
||||
interruptHandler();
|
||||
}
|
||||
|
||||
@@ -168,22 +183,24 @@ void DCCTimer::begin(INTERRUPT_CALLBACK callback) {
|
||||
interruptHandler=callback;
|
||||
noInterrupts();
|
||||
|
||||
// adc_set_sample_rate(ADC_SAMPLETIME_480CYCLES);
|
||||
timer.pause();
|
||||
timer.setPrescaleFactor(1);
|
||||
dcctimer.pause();
|
||||
dcctimer.setPrescaleFactor(1);
|
||||
// timer.setOverflow(CLOCK_CYCLES * 2);
|
||||
timer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
timer.attachInterrupt(Timer11_Handler);
|
||||
timer.refresh();
|
||||
timer.resume();
|
||||
dcctimer.setOverflow(DCC_SIGNAL_TIME, MICROSEC_FORMAT);
|
||||
// dcctimer.attachInterrupt(Timer11_Handler);
|
||||
dcctimer.attachInterrupt(DCCTimer_Handler);
|
||||
dcctimer.setInterruptPriority(0, 0); // Set highest preemptive priority!
|
||||
dcctimer.refresh();
|
||||
dcctimer.resume();
|
||||
|
||||
interrupts();
|
||||
}
|
||||
|
||||
bool DCCTimer::isPWMPin(byte pin) {
|
||||
//TODO: SAMD whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
//TODO: STM32 whilst this call to digitalPinHasPWM will reveal which pins can do PWM,
|
||||
// there's no support yet for High Accuracy, so for now return false
|
||||
// return digitalPinHasPWM(pin);
|
||||
(void) pin;
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -235,22 +252,91 @@ void DCCTimer::reset() {
|
||||
while(true) {};
|
||||
}
|
||||
|
||||
// TODO: may need to use uint32_t on STMF4xx variants with > 16 analog inputs!
|
||||
#if defined(ARDUINO_NUCLEO_F446RE) || defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
#warning STM32 board selected not fully supported - only use ADC1 inputs 0-15 for current sensing!
|
||||
#endif
|
||||
// For now, define the max of 16 ports - some variants have more, but this not **yet** supported
|
||||
#define NUM_ADC_INPUTS 16
|
||||
// #define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
// TODO: rationalise the size of these... could really use sparse arrays etc.
|
||||
static HardwareTimer * pin_timer[100] = {0};
|
||||
static uint32_t channel_frequency[100] = {0};
|
||||
static uint32_t pin_channel[100] = {0};
|
||||
|
||||
uint16_t ADCee::usedpins = 0;
|
||||
uint8_t ADCee::highestPin = 0;
|
||||
int * ADCee::analogvals = NULL;
|
||||
uint32_t * analogchans = NULL;
|
||||
bool adc1configured = false;
|
||||
// Using the HardwareTimer library API included in stm32duino core to handle PWM duties
|
||||
// TODO: in order to use the HA code above which Neil kindly wrote, we may have to do something more
|
||||
// sophisticated about detecting any clash between the timer we'd like to use for PWM and the ones
|
||||
// currently used for HA so they don't interfere with one another. For now we'll just make PWM
|
||||
// work well... then work backwards to integrate with HA mode if we can.
|
||||
void DCCTimer::DCCEXanalogWriteFrequency(uint8_t pin, uint32_t frequency)
|
||||
{
|
||||
if (pin_timer[pin] == NULL) {
|
||||
// Automatically retrieve TIM instance and channel associated to pin
|
||||
// This is used to be compatible with all STM32 series automatically.
|
||||
TIM_TypeDef *Instance = (TIM_TypeDef *)pinmap_peripheral(digitalPinToPinName(pin), PinMap_PWM);
|
||||
if (Instance == NULL) {
|
||||
// We shouldn't get here (famous last words) as it ought to have been caught by brakeCanPWM()!
|
||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d has no PWM function!"), pin);
|
||||
return;
|
||||
}
|
||||
pin_channel[pin] = STM_PIN_CHANNEL(pinmap_function(digitalPinToPinName(pin), PinMap_PWM));
|
||||
|
||||
int16_t ADCee::ADCmax() {
|
||||
return 4095;
|
||||
// Instantiate HardwareTimer object. Thanks to 'new' instantiation,
|
||||
// HardwareTimer is not destructed when setup function is finished.
|
||||
pin_timer[pin] = new HardwareTimer(Instance);
|
||||
// Configure and start PWM
|
||||
// MyTim->setPWM(channel, pin, 5, 10, NULL, NULL); // No callback required, we can simplify the function call
|
||||
if (pin_timer[pin] != NULL)
|
||||
{
|
||||
pin_timer[pin]->setPWM(pin_channel[pin], pin, frequency, 0); // set frequency in Hertz, 0% dutycycle
|
||||
DIAG(F("DCCEXanalogWriteFrequency::Pin %d on Timer %d, frequency %d"), pin, pin_channel[pin], frequency);
|
||||
}
|
||||
else
|
||||
DIAG(F("DCCEXanalogWriteFrequency::failed to allocate HardwareTimer instance!"));
|
||||
}
|
||||
else
|
||||
{
|
||||
// Frequency change request
|
||||
if (frequency != channel_frequency[pin])
|
||||
{
|
||||
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||
pin_timer[pin]->setOverflow(frequency, HERTZ_FORMAT); // Just change the frequency if it's already running!
|
||||
DIAG(F("DCCEXanalogWriteFrequency::setting frequency to %d"), frequency);
|
||||
}
|
||||
}
|
||||
channel_frequency[pin] = frequency;
|
||||
return;
|
||||
}
|
||||
|
||||
void DCCTimer::DCCEXanalogWrite(uint8_t pin, int value) {
|
||||
// Calculate percentage duty cycle from value given
|
||||
uint32_t duty_cycle = (value * 100 / 256) + 1;
|
||||
if (pin_timer[pin] != NULL) {
|
||||
// if (duty_cycle == 100)
|
||||
// {
|
||||
// pin_timer[pin]->pauseChannel(pin_channel[pin]);
|
||||
// DIAG(F("DCCEXanalogWrite::Pausing timer channel on pin %d"), pin);
|
||||
// }
|
||||
// else
|
||||
// {
|
||||
pinmap_pinout(digitalPinToPinName(pin), PinMap_TIM); // ensure the pin has been configured!
|
||||
// pin_timer[pin]->resumeChannel(pin_channel[pin]);
|
||||
pin_timer[pin]->setCaptureCompare(pin_channel[pin], duty_cycle, PERCENT_COMPARE_FORMAT); // DCC_EX_PWM_FREQ Hertz, duty_cycle% dutycycle
|
||||
DIAG(F("DCCEXanalogWrite::Pin %d, value %d, duty cycle %d"), pin, value, duty_cycle);
|
||||
// }
|
||||
}
|
||||
else
|
||||
DIAG(F("DCCEXanalogWrite::Pin %d is not configured for PWM!"), pin);
|
||||
}
|
||||
|
||||
|
||||
// Now we can handle more ADCs, maybe this works!
|
||||
#define NUM_ADC_INPUTS NUM_ANALOG_INPUTS
|
||||
|
||||
uint32_t ADCee::usedpins = 0; // Max of 32 ADC input channels!
|
||||
uint8_t ADCee::highestPin = 0; // Highest pin to scan
|
||||
int * ADCee::analogvals = NULL; // Array of analog values last captured
|
||||
uint32_t * analogchans = NULL; // Array of channel numbers to be scanned
|
||||
// bool adc1configured = false;
|
||||
ADC_TypeDef * * adcchans = NULL; // Array to capture which ADC is each input channel on
|
||||
|
||||
int16_t ADCee::ADCmax()
|
||||
{
|
||||
return 4095;
|
||||
}
|
||||
|
||||
int ADCee::init(uint8_t pin) {
|
||||
@@ -261,11 +347,33 @@ int ADCee::init(uint8_t pin) {
|
||||
return -1024; // some silly value as error
|
||||
|
||||
uint32_t stmgpio = STM_PORT(stmpin); // converts to the GPIO port (16-bits per port group on STM32)
|
||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC channel (only valid for ADC1!)
|
||||
GPIO_TypeDef * gpioBase;
|
||||
uint32_t adcchan = STM_PIN_CHANNEL(pinmap_function(stmpin, PinMap_ADC)); // find ADC input channel
|
||||
ADC_TypeDef *adc = (ADC_TypeDef *)pinmap_find_peripheral(stmpin, PinMap_ADC); // find which ADC this pin is on ADC1/2/3 etc.
|
||||
int adcnum = 1;
|
||||
if (adc == ADC1)
|
||||
DIAG(F("ADCee::init(): found pin %d on ADC1"), pin);
|
||||
// Checking for ADC2 and ADC3 being defined helps cater for more variants later
|
||||
#if defined(ADC2)
|
||||
else if (adc == ADC2)
|
||||
{
|
||||
DIAG(F("ADCee::init(): found pin %d on ADC2"), pin);
|
||||
adcnum = 2;
|
||||
}
|
||||
#endif
|
||||
#if defined(ADC3)
|
||||
else if (adc == ADC3)
|
||||
{
|
||||
DIAG(F("ADCee::init(): found pin %d on ADC3"), pin);
|
||||
adcnum = 3;
|
||||
}
|
||||
#endif
|
||||
else DIAG(F("ADCee::init(): found pin %d on unknown ADC!"), pin);
|
||||
|
||||
// Port config - find which port we're on and power it up
|
||||
switch(stmgpio) {
|
||||
// Port config - find which port we're on and power it up
|
||||
GPIO_TypeDef *gpioBase;
|
||||
|
||||
switch (stmgpio)
|
||||
{
|
||||
case 0x00:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOAEN; //Power up PORTA
|
||||
gpioBase = GPIOA;
|
||||
@@ -278,6 +386,20 @@ int ADCee::init(uint8_t pin) {
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOCEN; //Power up PORTC
|
||||
gpioBase = GPIOC;
|
||||
break;
|
||||
case 0x03:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIODEN; //Power up PORTD
|
||||
gpioBase = GPIOD;
|
||||
break;
|
||||
case 0x04:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOEEN; //Power up PORTE
|
||||
gpioBase = GPIOE;
|
||||
break;
|
||||
#if defined(GPIOF)
|
||||
case 0x05:
|
||||
RCC->AHB1ENR |= RCC_AHB1ENR_GPIOFEN; //Power up PORTF
|
||||
gpioBase = GPIOF;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return -1023; // some silly value as error
|
||||
}
|
||||
@@ -293,31 +415,33 @@ int ADCee::init(uint8_t pin) {
|
||||
if (adcchan > 18)
|
||||
return -1022; // silly value as error
|
||||
if (adcchan < 10)
|
||||
ADC1->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
adc->SMPR2 |= (0b111 << (adcchan * 3)); // Channel sampling rate 480 cycles
|
||||
else
|
||||
ADC1->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
adc->SMPR1 |= (0b111 << ((adcchan - 10) * 3)); // Channel sampling rate 480 cycles
|
||||
|
||||
// Read the inital ADC value for this analog input
|
||||
ADC1->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(ADC1->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = ADC1->DR; // Read value from register
|
||||
adc->SQR3 = adcchan; // 1st conversion in regular sequence
|
||||
adc->CR2 |= ADC_CR2_SWSTART; //(1 << 30); // Start 1st conversion SWSTART
|
||||
while(!(adc->SR & (1 << 1))); // Wait until conversion is complete
|
||||
value = adc->DR; // Read value from register
|
||||
|
||||
uint8_t id = pin - PNUM_ANALOG_BASE;
|
||||
if (id > 15) { // today we have not enough bits in the mask to support more
|
||||
return -1021;
|
||||
}
|
||||
// if (id > 15) { // today we have not enough bits in the mask to support more
|
||||
// return -1021;
|
||||
// }
|
||||
|
||||
if (analogvals == NULL) { // allocate analogvals and analogchans if this is the first invocation of init.
|
||||
if (analogvals == NULL) { // allocate analogvals, analogchans and adcchans if this is the first invocation of init
|
||||
analogvals = (int *)calloc(NUM_ADC_INPUTS+1, sizeof(int));
|
||||
analogchans = (uint32_t *)calloc(NUM_ADC_INPUTS+1, sizeof(uint32_t));
|
||||
adcchans = (ADC_TypeDef **)calloc(NUM_ADC_INPUTS+1, sizeof(ADC_TypeDef));
|
||||
}
|
||||
analogvals[id] = value; // Store sampled value
|
||||
analogchans[id] = adcchan; // Keep track of which ADC channel is used for reading this pin
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
adcchans[id] = adc; // Keep track of which ADC this channel is on
|
||||
usedpins |= (1 << id); // This pin is now ready
|
||||
if (id > highestPin) highestPin = id; // Store our highest pin in use
|
||||
|
||||
DIAG(F("ADCee::init(): value=%d, channel=%d, id=%d"), value, adcchan, id);
|
||||
DIAG(F("ADCee::init(): value=%d, ADC%d: channel=%d, id=%d"), value, adcnum, adcchan, id);
|
||||
|
||||
return value;
|
||||
}
|
||||
@@ -344,13 +468,16 @@ void ADCee::scan() {
|
||||
static uint8_t id = 0; // id and mask are the same thing but it is faster to
|
||||
static uint16_t mask = 1; // increment and shift instead to calculate mask from id
|
||||
static bool waiting = false;
|
||||
static ADC_TypeDef *adc;
|
||||
|
||||
if (waiting) {
|
||||
adc = adcchans[id];
|
||||
if (waiting)
|
||||
{
|
||||
// look if we have a result
|
||||
if (!(ADC1->SR & (1 << 1)))
|
||||
if (!(adc->SR & (1 << 1)))
|
||||
return; // no result, continue to wait
|
||||
// found value
|
||||
analogvals[id] = ADC1->DR;
|
||||
analogvals[id] = adc->DR;
|
||||
// advance at least one track
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(0);
|
||||
@@ -369,9 +496,10 @@ void ADCee::scan() {
|
||||
// look for a valid track to sample or until we are around
|
||||
while (true) {
|
||||
if (mask & usedpins) {
|
||||
// start new ADC aquire on id
|
||||
ADC1->SQR3 = analogchans[id]; //1st conversion in regular sequence
|
||||
ADC1->CR2 |= (1 << 30); //Start 1st conversion SWSTART
|
||||
// start new ADC aquire on id
|
||||
adc = adcchans[id];
|
||||
adc->SQR3 = analogchans[id]; // 1st conversion in regular sequence
|
||||
adc->CR2 |= (1 << 30); // Start 1st conversion SWSTART
|
||||
#ifdef DEBUG_ADC
|
||||
if (id == 1) TrackManager::track[1]->setBrake(1);
|
||||
#endif
|
||||
@@ -392,19 +520,83 @@ void ADCee::scan() {
|
||||
void ADCee::begin() {
|
||||
noInterrupts();
|
||||
//ADC1 config sequence
|
||||
// TODO: currently defaults to ADC1, may need more to handle other members of STM32F4xx family
|
||||
RCC->APB2ENR |= (1 << 8); //Enable ADC1 clock (Bit8)
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC1EN; // Enable ADC1 clock
|
||||
// Set ADC prescaler - DIV8 ~ 40ms, DIV6 ~ 30ms, DIV4 ~ 20ms, DIV2 ~ 11ms
|
||||
ADC->CCR = (0 << 16); // Set prescaler 0=DIV2, 1=DIV4, 2=DIV6, 3=DIV8
|
||||
ADC1->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC1->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC1->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
// Disable the DMA controller for ADC1
|
||||
ADC1->CR2 &= ~ADC_CR2_DMA;
|
||||
ADC1->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC1->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC1->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC1->CR2 |= (1 << 0); // Switch on ADC1
|
||||
// Wait for ADC1 to become ready (calibration complete)
|
||||
while (!(ADC1->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
#if defined(ADC2)
|
||||
// Enable the ADC2 clock
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC2EN;
|
||||
|
||||
// Initialize ADC2
|
||||
ADC2->CR1 = 0; // Disable all channels
|
||||
ADC2->CR2 = 0; // Clear CR2 register
|
||||
|
||||
ADC2->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC2->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC2->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC2->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
|
||||
ADC2->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC2->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC2->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC2->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC2->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
|
||||
// Enable the ADC
|
||||
ADC2->CR2 |= ADC_CR2_ADON;
|
||||
|
||||
// Wait for ADC2 to become ready (calibration complete)
|
||||
while (!(ADC2->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
|
||||
// Perform ADC3 calibration (optional)
|
||||
// ADC3->CR2 |= ADC_CR2_CAL;
|
||||
// while (ADC3->CR2 & ADC_CR2_CAL) {
|
||||
// }
|
||||
#endif
|
||||
#if defined(ADC3)
|
||||
// Enable the ADC3 clock
|
||||
RCC->APB2ENR |= RCC_APB2ENR_ADC3EN;
|
||||
|
||||
// Initialize ADC3
|
||||
ADC3->CR1 = 0; // Disable all channels
|
||||
ADC3->CR2 = 0; // Clear CR2 register
|
||||
|
||||
ADC3->CR1 &= ~(1 << 8); //SCAN mode disabled (Bit8)
|
||||
ADC3->CR1 &= ~(3 << 24); //12bit resolution (Bit24,25 0b00)
|
||||
ADC3->SQR1 = (1 << 20); //Set number of conversions projected (L[3:0] 0b0001) -> 1 conversion
|
||||
ADC3->CR2 &= ~ADC_CR2_DMA; // Disable the DMA controller for ADC3
|
||||
ADC3->CR2 &= ~(1 << 1); //Single conversion
|
||||
ADC3->CR2 &= ~(1 << 11); //Right alignment of data bits bit12....bit0
|
||||
ADC3->SQR1 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC3->SQR2 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
ADC3->SQR3 &= ~(0x3FFFFFFF); //Clear whole 1st 30bits in register
|
||||
|
||||
// Enable the ADC
|
||||
ADC3->CR2 |= ADC_CR2_ADON;
|
||||
|
||||
// Wait for ADC3 to become ready (calibration complete)
|
||||
while (!(ADC3->CR2 & ADC_CR2_ADON)) {
|
||||
}
|
||||
|
||||
// Perform ADC3 calibration (optional)
|
||||
// ADC3->CR2 |= ADC_CR2_CAL;
|
||||
// while (ADC3->CR2 & ADC_CR2_CAL) {
|
||||
// }
|
||||
#endif
|
||||
interrupts();
|
||||
}
|
||||
#endif
|
||||
|
116
EXRAIL2.cpp
116
EXRAIL2.cpp
@@ -2,7 +2,7 @@
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2021-2023 Harald Barth
|
||||
* © 2020-2023 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -52,6 +52,8 @@
|
||||
#include "Turnouts.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "TrackManager.h"
|
||||
#include "Turntables.h"
|
||||
#include "IODevice.h"
|
||||
|
||||
// Command parsing keywords
|
||||
const int16_t HASH_KEYWORD_EXRAIL=15435;
|
||||
@@ -94,6 +96,10 @@ LookList * RMFT2::onAmberLookup=NULL;
|
||||
LookList * RMFT2::onGreenLookup=NULL;
|
||||
LookList * RMFT2::onChangeLookup=NULL;
|
||||
LookList * RMFT2::onClockLookup=NULL;
|
||||
#ifndef IO_NO_HAL
|
||||
LookList * RMFT2::onRotateLookup=NULL;
|
||||
#endif
|
||||
LookList * RMFT2::onOverloadLookup=NULL;
|
||||
|
||||
#define GET_OPCODE GETHIGHFLASH(RMFT2::RouteCode,progCounter)
|
||||
#define SKIPOP progCounter+=3
|
||||
@@ -175,7 +181,10 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
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);
|
||||
|
||||
// Second pass startup, define any turnouts or servos, set signals red
|
||||
// add sequences onRoutines to the lookups
|
||||
@@ -195,6 +204,7 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
case OPCODE_AT:
|
||||
case OPCODE_ATTIMEOUT2:
|
||||
case OPCODE_AFTER:
|
||||
case OPCODE_AFTEROVERLOAD:
|
||||
case OPCODE_IF:
|
||||
case OPCODE_IFNOT: {
|
||||
int16_t pin = (int16_t)operand;
|
||||
@@ -238,7 +248,38 @@ LookList* RMFT2::LookListLoader(OPCODE op1, OPCODE op2, OPCODE op3) {
|
||||
setTurnoutHiddenState(VpinTurnout::create(id,pin));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_DCCTURNTABLE: {
|
||||
VPIN id=operand;
|
||||
int home=getOperand(progCounter,1);
|
||||
setTurntableHiddenState(DCCTurntable::create(id));
|
||||
Turntable *tto=Turntable::get(id);
|
||||
tto->addPosition(0,0,home);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_EXTTTURNTABLE: {
|
||||
VPIN id=operand;
|
||||
VPIN pin=getOperand(progCounter,1);
|
||||
int home=getOperand(progCounter,3);
|
||||
setTurntableHiddenState(EXTTTurntable::create(id,pin));
|
||||
Turntable *tto=Turntable::get(id);
|
||||
tto->addPosition(0,0,home);
|
||||
break;
|
||||
}
|
||||
|
||||
case OPCODE_TTADDPOSITION: {
|
||||
VPIN id=operand;
|
||||
int position=getOperand(progCounter,1);
|
||||
int value=getOperand(progCounter,2);
|
||||
int angle=getOperand(progCounter,3);
|
||||
Turntable *tto=Turntable::get(id);
|
||||
tto->addPosition(position,value,angle);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
|
||||
case OPCODE_AUTOSTART:
|
||||
// automatically create a task from here at startup.
|
||||
// Removed if (progCounter>0) check 4.2.31 because
|
||||
@@ -263,6 +304,12 @@ void RMFT2::setTurnoutHiddenState(Turnout * t) {
|
||||
t->setHidden(GETFLASH(getTurnoutDescription(t->getId()))==0x01);
|
||||
}
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
void RMFT2::setTurntableHiddenState(Turntable * tto) {
|
||||
tto->setHidden(GETFLASH(getTurntableDescription(tto->getId()))==0x01);
|
||||
}
|
||||
#endif
|
||||
|
||||
char RMFT2::getRouteType(int16_t id) {
|
||||
for (int16_t i=0;;i+=2) {
|
||||
int16_t rid= GETHIGHFLASHW(routeIdList,i);
|
||||
@@ -599,6 +646,14 @@ void RMFT2::loop2() {
|
||||
Turnout::setClosed(operand, true);
|
||||
break;
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_ROTATE:
|
||||
uint8_t activity;
|
||||
activity=getOperand(2);
|
||||
Turntable::setPosition(operand,getOperand(1),activity);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case OPCODE_REV:
|
||||
forward = false;
|
||||
driveLoco(operand);
|
||||
@@ -684,7 +739,17 @@ void RMFT2::loop2() {
|
||||
}
|
||||
if (millis()-waitAfter < 500 ) return;
|
||||
break;
|
||||
|
||||
|
||||
case OPCODE_AFTEROVERLOAD: // waits for the power to be turned back on - either by power routine or button
|
||||
if (!TrackManager::isPowerOn(operand)) {
|
||||
// reset timer to half a second and keep waiting
|
||||
waitAfter=millis();
|
||||
delayMe(50);
|
||||
return;
|
||||
}
|
||||
if (millis()-waitAfter < 500 ) return;
|
||||
break;
|
||||
|
||||
case OPCODE_LATCH:
|
||||
setFlag(operand,LATCH_FLAG);
|
||||
break;
|
||||
@@ -788,7 +853,13 @@ void RMFT2::loop2() {
|
||||
case OPCODE_IFCLOSED:
|
||||
skipIf=Turnout::isThrown(operand);
|
||||
break;
|
||||
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_IFTTPOSITION: // do block if turntable at this position
|
||||
skipIf=Turntable::getPosition(operand)!=(int)getOperand(1);
|
||||
break;
|
||||
#endif
|
||||
|
||||
case OPCODE_ENDIF:
|
||||
break;
|
||||
|
||||
@@ -961,7 +1032,16 @@ void RMFT2::loop2() {
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_WAITFORTT: // OPCODE_WAITFOR,V(turntable_id)
|
||||
if (Turntable::ttMoving(operand)) {
|
||||
delayMe(100);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
|
||||
case OPCODE_PRINT:
|
||||
printMessage(operand);
|
||||
break;
|
||||
@@ -986,6 +1066,13 @@ void RMFT2::loop2() {
|
||||
case OPCODE_ONGREEN:
|
||||
case OPCODE_ONCHANGE:
|
||||
case OPCODE_ONTIME:
|
||||
#ifndef IO_NO_HAL
|
||||
case OPCODE_DCCTURNTABLE: // Turntable definition ignored at runtime
|
||||
case OPCODE_EXTTTURNTABLE: // Turntable definition ignored at runtime
|
||||
case OPCODE_TTADDPOSITION: // Turntable position definition ignored at runtime
|
||||
case OPCODE_ONROTATE:
|
||||
#endif
|
||||
case OPCODE_ONOVERLOAD:
|
||||
|
||||
break;
|
||||
|
||||
@@ -1130,6 +1217,13 @@ void RMFT2::changeEvent(int16_t vpin, bool change) {
|
||||
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) handleEvent(F("ROTATE"),onRotateLookup,turntableId);
|
||||
}
|
||||
#endif
|
||||
|
||||
void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||
// Hunt for an ONTIME for this time
|
||||
if (Diag::CMD)
|
||||
@@ -1140,6 +1234,16 @@ void RMFT2::clockEvent(int16_t clocktime, bool change) {
|
||||
}
|
||||
}
|
||||
|
||||
void RMFT2::powerEvent(int16_t track, bool overload) {
|
||||
// Hunt for an ONOVERLOAD for this item
|
||||
if (Diag::CMD)
|
||||
DIAG(F("Looking for Power event on track : %c"), track);
|
||||
if (overload) {
|
||||
handleEvent(F("POWER"),onOverloadLookup,track);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void RMFT2::handleEvent(const FSH* reason,LookList* handlers, int16_t id) {
|
||||
int pc= handlers->find(id);
|
||||
if (pc<0) return;
|
||||
|
23
EXRAIL2.h
23
EXRAIL2.h
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -25,6 +25,8 @@
|
||||
#include "FSH.h"
|
||||
#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.
|
||||
@@ -35,7 +37,8 @@
|
||||
enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_FWD,OPCODE_REV,OPCODE_SPEED,OPCODE_INVERT_DIRECTION,
|
||||
OPCODE_RESERVE,OPCODE_FREE,
|
||||
OPCODE_AT,OPCODE_AFTER,OPCODE_AUTOSTART,
|
||||
OPCODE_AT,OPCODE_AFTER,
|
||||
OPCODE_AFTEROVERLOAD,OPCODE_AUTOSTART,
|
||||
OPCODE_ATGTE,OPCODE_ATLT,
|
||||
OPCODE_ATTIMEOUT1,OPCODE_ATTIMEOUT2,
|
||||
OPCODE_LATCH,OPCODE_UNLATCH,OPCODE_SET,OPCODE_RESET,
|
||||
@@ -62,6 +65,11 @@ enum OPCODE : byte {OPCODE_THROW,OPCODE_CLOSE,
|
||||
OPCODE_ONCHANGE,
|
||||
OPCODE_ONCLOCKTIME,
|
||||
OPCODE_ONTIME,
|
||||
#ifndef IO_NO_HAL
|
||||
OPCODE_TTADDPOSITION,OPCODE_DCCTURNTABLE,OPCODE_EXTTTURNTABLE,
|
||||
OPCODE_ONROTATE,OPCODE_ROTATE,OPCODE_IFTTPOSITION,OPCODE_WAITFORTT,
|
||||
#endif
|
||||
OPCODE_ONOVERLOAD,
|
||||
|
||||
// OPcodes below this point are skip-nesting IF operations
|
||||
// placed here so that they may be skipped as a group
|
||||
@@ -130,6 +138,8 @@ class LookList {
|
||||
static void activateEvent(int16_t addr, bool active);
|
||||
static void changeEvent(int16_t id, bool change);
|
||||
static void clockEvent(int16_t clocktime, bool change);
|
||||
static void rotateEvent(int16_t id, bool change);
|
||||
static void powerEvent(int16_t track, bool overload);
|
||||
static const int16_t SERVO_SIGNAL_FLAG=0x4000;
|
||||
static const int16_t ACTIVE_HIGH_SIGNAL_FLAG=0x2000;
|
||||
static const int16_t DCC_SIGNAL_FLAG=0x1000;
|
||||
@@ -144,6 +154,8 @@ class LookList {
|
||||
static const FSH * getTurnoutDescription(int16_t id);
|
||||
static const FSH * getRosterName(int16_t id);
|
||||
static const FSH * getRosterFunctions(int16_t id);
|
||||
static const FSH * getTurntableDescription(int16_t id);
|
||||
static const FSH * getTurntablePositionDescription(int16_t turntableId, uint8_t positionId);
|
||||
|
||||
private:
|
||||
static void ComandFilter(Print * stream, byte & opcode, byte & paramCount, int16_t p[]);
|
||||
@@ -156,6 +168,9 @@ private:
|
||||
static bool isSignal(int16_t id,char rag);
|
||||
static int16_t getSignalSlot(int16_t id);
|
||||
static void setTurnoutHiddenState(Turnout * t);
|
||||
#ifndef IO_NO_HAL
|
||||
static void setTurntableHiddenState(Turntable * tto);
|
||||
#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);
|
||||
@@ -188,6 +203,10 @@ private:
|
||||
static LookList * onGreenLookup;
|
||||
static LookList * onChangeLookup;
|
||||
static LookList * onClockLookup;
|
||||
#ifndef IO_NO_HAL
|
||||
static LookList * onRotateLookup;
|
||||
#endif
|
||||
static LookList * onOverloadLookup;
|
||||
|
||||
// Local variables - exist for each instance/task
|
||||
RMFT2 *next; // loop chain
|
||||
|
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
* © 2020-2022 Chris Harlow. All rights reserved.
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -27,6 +27,7 @@
|
||||
#undef ACTIVATE
|
||||
#undef ACTIVATEL
|
||||
#undef AFTER
|
||||
#undef AFTEROVERLOAD
|
||||
#undef ALIAS
|
||||
#undef AMBER
|
||||
#undef ANOUT
|
||||
@@ -40,6 +41,7 @@
|
||||
#undef CALL
|
||||
#undef CLOSE
|
||||
#undef DCC_SIGNAL
|
||||
#undef DCC_TURNTABLE
|
||||
#undef DEACTIVATE
|
||||
#undef DEACTIVATEL
|
||||
#undef DELAY
|
||||
@@ -51,8 +53,9 @@
|
||||
#undef ENDEXRAIL
|
||||
#undef ENDIF
|
||||
#undef ENDTASK
|
||||
#undef ESTOP
|
||||
#undef EXRAIL
|
||||
#undef ESTOP
|
||||
#undef EXRAIL
|
||||
#undef EXTT_TURNTABLE
|
||||
#undef FADE
|
||||
#undef FOFF
|
||||
#undef FOLLOW
|
||||
@@ -75,6 +78,7 @@
|
||||
#undef IFRESERVE
|
||||
#undef IFTHROWN
|
||||
#undef IFTIMEOUT
|
||||
#undef IFTTPOSITION
|
||||
#undef IFRE
|
||||
#undef INVERT_DIRECTION
|
||||
#undef JOIN
|
||||
@@ -93,8 +97,10 @@
|
||||
#undef ONTIME
|
||||
#undef ONCLOCKTIME
|
||||
#undef ONCLOCKMINS
|
||||
#undef ONOVERLOAD
|
||||
#undef ONGREEN
|
||||
#undef ONRED
|
||||
#undef ONROTATE
|
||||
#undef ONTHROW
|
||||
#undef ONCHANGE
|
||||
#undef PARSE
|
||||
@@ -113,7 +119,9 @@
|
||||
#undef RESUME
|
||||
#undef RETURN
|
||||
#undef REV
|
||||
#undef ROSTER
|
||||
#undef ROSTER
|
||||
#undef ROTATE
|
||||
#undef ROTATE_DCC
|
||||
#undef ROUTE
|
||||
#undef SENDLOCO
|
||||
#undef SEQUENCE
|
||||
@@ -136,7 +144,8 @@
|
||||
#undef SPEED
|
||||
#undef START
|
||||
#undef STOP
|
||||
#undef THROW
|
||||
#undef THROW
|
||||
#undef TT_ADDPOSITION
|
||||
#undef TURNOUT
|
||||
#undef TURNOUTL
|
||||
#undef UNJOIN
|
||||
@@ -144,6 +153,9 @@
|
||||
#undef VIRTUAL_SIGNAL
|
||||
#undef VIRTUAL_TURNOUT
|
||||
#undef WAITFOR
|
||||
#ifndef IO_NO_HAL
|
||||
#undef WAITFORTT
|
||||
#endif
|
||||
#undef WITHROTTLE
|
||||
#undef XFOFF
|
||||
#undef XFON
|
||||
@@ -152,6 +164,7 @@
|
||||
#define ACTIVATE(addr,subaddr)
|
||||
#define ACTIVATEL(addr)
|
||||
#define AFTER(sensor_id)
|
||||
#define AFTEROVERLOAD(track_id)
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id)
|
||||
#define ANOUT(vpin,value,param1,param2)
|
||||
@@ -165,6 +178,7 @@
|
||||
#define CALL(route)
|
||||
#define CLOSE(id)
|
||||
#define DCC_SIGNAL(id,add,subaddr)
|
||||
#define DCC_TURNTABLE(id,home,description)
|
||||
#define DEACTIVATE(addr,subaddr)
|
||||
#define DEACTIVATEL(addr)
|
||||
#define DELAY(mindelay)
|
||||
@@ -177,7 +191,8 @@
|
||||
#define ENDIF
|
||||
#define ENDTASK
|
||||
#define ESTOP
|
||||
#define EXRAIL
|
||||
#define EXRAIL
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description)
|
||||
#define FADE(pin,value,ms)
|
||||
#define FOFF(func)
|
||||
#define FOLLOW(route)
|
||||
@@ -200,6 +215,7 @@
|
||||
#define IFTHROWN(turnout_id)
|
||||
#define IFRESERVE(block)
|
||||
#define IFTIMEOUT
|
||||
#define IFTTPOSITION(turntable_id,position)
|
||||
#define IFRE(sensor_id,value)
|
||||
#define INVERT_DIRECTION
|
||||
#define JOIN
|
||||
@@ -215,11 +231,13 @@
|
||||
#define ONTIME(value)
|
||||
#define ONCLOCKTIME(hours,mins)
|
||||
#define ONCLOCKMINS(mins)
|
||||
#define ONOVERLOAD(track_id)
|
||||
#define ONDEACTIVATE(addr,subaddr)
|
||||
#define ONDEACTIVATEL(linear)
|
||||
#define ONCLOSE(turnout_id)
|
||||
#define ONGREEN(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONRED(signal_id)
|
||||
#define ONROTATE(turntable_id)
|
||||
#define ONTHROW(turnout_id)
|
||||
#define ONCHANGE(sensor_id)
|
||||
#define PAUSE
|
||||
@@ -238,8 +256,10 @@
|
||||
#define RESUME
|
||||
#define RETURN
|
||||
#define REV(speed)
|
||||
#define ROUTE(id,description)
|
||||
#define ROTATE(turntable_id,position,activity)
|
||||
#define ROTATE_DCC(turntable_id,position)
|
||||
#define ROSTER(cab,name,funcmap...)
|
||||
#define ROUTE(id,description)
|
||||
#define SENDLOCO(cab,route)
|
||||
#define SEQUENCE(id)
|
||||
#define SERIAL(msg)
|
||||
@@ -261,7 +281,8 @@
|
||||
#define SPEED(speed)
|
||||
#define START(route)
|
||||
#define STOP
|
||||
#define THROW(id)
|
||||
#define THROW(id)
|
||||
#define TT_ADDPOSITION(turntable_id,position,value,angle,description...)
|
||||
#define TURNOUT(id,addr,subaddr,description...)
|
||||
#define TURNOUTL(id,addr,description...)
|
||||
#define UNJOIN
|
||||
@@ -269,6 +290,9 @@
|
||||
#define VIRTUAL_SIGNAL(id)
|
||||
#define VIRTUAL_TURNOUT(id,description...)
|
||||
#define WAITFOR(pin)
|
||||
#ifndef IO_NO_HAL
|
||||
#define WAITFORTT(turntable_id)
|
||||
#endif
|
||||
#define WITHROTTLE(msg)
|
||||
#define XFOFF(cab,func)
|
||||
#define XFON(cab,func)
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
* © 2021 Neil McKechnie
|
||||
* © 2020-2022 Chris Harlow
|
||||
* © 2022 Colin Murdoch
|
||||
* © 2022-2023 Colin Murdoch
|
||||
* © 2023 Harald Barth
|
||||
* All rights reserved.
|
||||
*
|
||||
@@ -54,6 +54,8 @@
|
||||
|
||||
// helper macro for turnout descriptions, creates NULL for missing description
|
||||
#define O_DESC(id, desc) case id: return ("" desc)[0]?F("" desc):NULL;
|
||||
// helper macro for turntable descriptions, creates NULL for missing description
|
||||
#define T_DESC(tid,pid,desc) if(turntableId==tid && positionId==pid) return ("" desc)[0]?F("" desc):NULL;
|
||||
// helper macro for turnout description as HIDDEN
|
||||
#define HIDDEN "\x01"
|
||||
|
||||
@@ -67,10 +69,13 @@
|
||||
#define ALIAS(name,value...) const int name= 1##value##0 ==10 ? -__COUNTER__ : value##0/10;
|
||||
#include "myAutomation.h"
|
||||
|
||||
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
||||
// Pass 1h Implements HAL macro by creating exrailHalSetup function
|
||||
// Also allows creating EXTurntable object
|
||||
#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"
|
||||
}
|
||||
@@ -187,6 +192,31 @@ const FSH * RMFT2::getTurnoutDescription(int16_t turnoutid) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Pass to get turntable descriptions (optional)
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef DCC_TURNTABLE
|
||||
#define DCC_TURNTABLE(id,home,description...) O_DESC(id,description)
|
||||
#undef EXTT_TURNTABLE
|
||||
#define EXTT_TURNTABLE(id,vpin,i2c_address,home,description...) O_DESC(id,description)
|
||||
|
||||
const FSH * RMFT2::getTurntableDescription(int16_t turntableId) {
|
||||
switch (turntableId) {
|
||||
#include "myAutomation.h"
|
||||
default:break;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Pass to get turntable position descriptions (optional)
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef TT_ADDPOSITION
|
||||
#define TT_ADDPOSITION(turntable_id,position,value,home,description...) T_DESC(turntable_id,position,description)
|
||||
|
||||
const FSH * RMFT2::getTurntablePositionDescription(int16_t turntableId, uint8_t positionId) {
|
||||
#include "myAutomation.h"
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Pass 6: Roster IDs (count)
|
||||
#include "EXRAIL2MacroReset.h"
|
||||
#undef ROSTER
|
||||
@@ -256,6 +286,7 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1 | 1),
|
||||
#define ACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1 | 1),
|
||||
#define AFTER(sensor_id) OPCODE_AT,V(sensor_id),OPCODE_AFTER,V(sensor_id),
|
||||
#define AFTEROVERLOAD(track_id) OPCODE_AFTEROVERLOAD,V(TRACK_NUMBER_##track_id),
|
||||
#define ALIAS(name,value...)
|
||||
#define AMBER(signal_id) OPCODE_AMBER,V(signal_id),
|
||||
#define ANOUT(vpin,value,param1,param2) OPCODE_SERVO,V(vpin),OPCODE_PAD,V(value),OPCODE_PAD,V(param1),OPCODE_PAD,V(param2),
|
||||
@@ -268,6 +299,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define BROADCAST(msg) PRINT(msg)
|
||||
#define CALL(route) OPCODE_CALL,V(route),
|
||||
#define CLOSE(id) OPCODE_CLOSE,V(id),
|
||||
#ifndef IO_NO_HAL
|
||||
#define DCC_TURNTABLE(id,home,description...) OPCODE_DCCTURNTABLE,V(id),OPCODE_PAD,V(home),
|
||||
#endif
|
||||
#define DEACTIVATE(addr,subaddr) OPCODE_DCCACTIVATE,V(addr<<3 | subaddr<<1),
|
||||
#define DEACTIVATEL(addr) OPCODE_DCCACTIVATE,V((addr+3)<<1),
|
||||
#define DELAY(ms) ms<30000?OPCODE_DELAYMS:OPCODE_DELAY,V(ms/(ms<30000?1L:100L)),
|
||||
@@ -281,7 +315,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ENDIF OPCODE_ENDIF,0,0,
|
||||
#define ENDTASK OPCODE_ENDTASK,0,0,
|
||||
#define ESTOP OPCODE_SPEED,V(1),
|
||||
#define EXRAIL
|
||||
#define EXRAIL
|
||||
#ifndef IO_NO_HAL
|
||||
#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),
|
||||
#define FOLLOW(route) OPCODE_FOLLOW,V(route),
|
||||
@@ -304,6 +341,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define IFRESERVE(block) OPCODE_IFRESERVE,V(block),
|
||||
#define IFTHROWN(turnout_id) OPCODE_IFTHROWN,V(turnout_id),
|
||||
#define IFTIMEOUT OPCODE_IFTIMEOUT,0,0,
|
||||
#ifndef IO_NO_HAL
|
||||
#define IFTTPOSITION(id,position) OPCODE_IFTTPOSITION,V(id),OPCODE_PAD,V(position),
|
||||
#endif
|
||||
#define IFRE(sensor_id,value) OPCODE_IFRE,V(sensor_id),OPCODE_PAD,V(value),
|
||||
#define INVERT_DIRECTION OPCODE_INVERT_DIRECTION,0,0,
|
||||
#define JOIN OPCODE_JOIN,0,0,
|
||||
@@ -320,10 +360,14 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define ONTIME(value) OPCODE_ONTIME,V(value),
|
||||
#define ONCLOCKTIME(hours,mins) OPCODE_ONTIME,V((STRIP_ZERO(hours)*60)+STRIP_ZERO(mins)),
|
||||
#define ONCLOCKMINS(mins) ONCLOCKTIME(25,mins)
|
||||
#define ONOVERLOAD(track_id) OPCODE_ONOVERLOAD,V(TRACK_NUMBER_##track_id),
|
||||
#define ONDEACTIVATE(addr,subaddr) OPCODE_ONDEACTIVATE,V(addr<<2|subaddr),
|
||||
#define ONDEACTIVATEL(linear) OPCODE_ONDEACTIVATE,V(linear+3),
|
||||
#define ONGREEN(signal_id) OPCODE_ONGREEN,V(signal_id),
|
||||
#define ONRED(signal_id) OPCODE_ONRED,V(signal_id),
|
||||
#ifndef IO_NO_HAL
|
||||
#define ONROTATE(id) OPCODE_ONROTATE,V(id),
|
||||
#endif
|
||||
#define ONTHROW(turnout_id) OPCODE_ONTHROW,V(turnout_id),
|
||||
#define ONCHANGE(sensor_id) OPCODE_ONCHANGE,V(sensor_id),
|
||||
#define PAUSE OPCODE_PAUSE,0,0,
|
||||
@@ -343,6 +387,10 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define RETURN OPCODE_RETURN,0,0,
|
||||
#define REV(speed) OPCODE_REV,V(speed),
|
||||
#define ROSTER(cabid,name,funcmap...)
|
||||
#ifndef IO_NO_HAL
|
||||
#define ROTATE(id,position,activity) OPCODE_ROTATE,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(EXTurntable::activity),
|
||||
#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 SENDLOCO(cab,route) OPCODE_SENDLOCO,V(cab),OPCODE_PAD,V(route),
|
||||
#define SEQUENCE(id) OPCODE_SEQUENCE, V(id),
|
||||
@@ -366,6 +414,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define START(route) OPCODE_START,V(route),
|
||||
#define STOP OPCODE_SPEED,V(0),
|
||||
#define THROW(id) OPCODE_THROW,V(id),
|
||||
#ifndef IO_NO_HAL
|
||||
#define TT_ADDPOSITION(id,position,value,angle,description...) OPCODE_TTADDPOSITION,V(id),OPCODE_PAD,V(position),OPCODE_PAD,V(value),OPCODE_PAD,V(angle),
|
||||
#endif
|
||||
#define TURNOUT(id,addr,subaddr,description...) OPCODE_TURNOUT,V(id),OPCODE_PAD,V(addr),OPCODE_PAD,V(subaddr),
|
||||
#define TURNOUTL(id,addr,description...) TURNOUT(id,(addr-1)/4+1,(addr-1)%4, description)
|
||||
#define UNJOIN OPCODE_UNJOIN,0,0,
|
||||
@@ -374,6 +425,9 @@ const HIGHFLASH int16_t RMFT2::SignalDefinitions[] = {
|
||||
#define VIRTUAL_TURNOUT(id,description...) OPCODE_PINTURNOUT,V(id),OPCODE_PAD,V(0),
|
||||
#define WITHROTTLE(msg) PRINT(msg)
|
||||
#define WAITFOR(pin) OPCODE_WAITFOR,V(pin),
|
||||
#ifndef IO_NO_HAL
|
||||
#define WAITFORTT(turntable_id) OPCODE_WAITFORTT,V(turntable_id),
|
||||
#endif
|
||||
#define XFOFF(cab,func) OPCODE_XFOFF,V(cab),OPCODE_PAD,V(func),
|
||||
#define XFON(cab,func) OPCODE_XFON,V(cab),OPCODE_PAD,V(func),
|
||||
|
||||
|
@@ -1 +1 @@
|
||||
#define GITHUB_SHA "3bddf4d"
|
||||
#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("?");
|
||||
}
|
||||
@@ -92,7 +95,7 @@ void I2CManagerClass::begin(void) {
|
||||
// Probe and list devices. Use standard mode
|
||||
// (clock speed 100kHz) for best device compatibility.
|
||||
_setClock(100000);
|
||||
unsigned long originalTimeout = _timeout;
|
||||
uint32_t originalTimeout = _timeout;
|
||||
setTimeout(1000); // use 1ms timeout for probes
|
||||
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
@@ -363,4 +366,4 @@ void I2CAddress::toHex(const uint8_t value, char *buffer) {
|
||||
|
||||
/* static */ bool I2CAddress::_addressWarningDone = false;
|
||||
|
||||
#endif
|
||||
#endif
|
||||
|
@@ -485,7 +485,7 @@ private:
|
||||
// When retries are enabled, the timeout applies to each
|
||||
// try, and failure from timeout does not get retried.
|
||||
// A value of 0 means disable timeout monitoring.
|
||||
unsigned long _timeout = 100000UL;
|
||||
uint32_t _timeout = 100000UL;
|
||||
|
||||
// Finish off request block by waiting for completion and posting status.
|
||||
uint8_t finishRB(I2CRB *rb, uint8_t status);
|
||||
@@ -532,14 +532,15 @@ private:
|
||||
uint8_t bytesToSend = 0;
|
||||
uint8_t bytesToReceive = 0;
|
||||
uint8_t operation = 0;
|
||||
unsigned long startTime = 0;
|
||||
uint32_t startTime = 0;
|
||||
uint8_t muxPhase = 0;
|
||||
uint8_t muxAddress = 0;
|
||||
uint8_t muxData[1];
|
||||
uint8_t deviceAddress;
|
||||
const uint8_t *sendBuffer;
|
||||
uint8_t *receiveBuffer;
|
||||
|
||||
uint8_t transactionState = 0;
|
||||
|
||||
volatile uint32_t pendingClockSpeed = 0;
|
||||
|
||||
void startTransaction();
|
||||
|
@@ -172,6 +172,10 @@ void I2CManagerClass::startTransaction() {
|
||||
* Function to queue a request block and initiate operations.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
|
||||
if (((req->operation & OPERATION_MASK) == OPERATION_READ) && req->readLen == 0)
|
||||
return; // Ignore null read
|
||||
|
||||
req->status = I2C_STATUS_PENDING;
|
||||
req->nextRequest = NULL;
|
||||
ATOMIC_BLOCK() {
|
||||
@@ -184,6 +188,7 @@ void I2CManagerClass::queueRequest(I2CRB *req) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a write to an I2C device (non-blocking operation)
|
||||
***************************************************************************/
|
||||
@@ -240,8 +245,8 @@ void I2CManagerClass::checkForTimeout() {
|
||||
I2CRB *t = queueHead;
|
||||
if (state==I2C_STATE_ACTIVE && t!=0 && t==currentRequest && _timeout > 0) {
|
||||
// Check for timeout
|
||||
unsigned long elapsed = micros() - startTime;
|
||||
if (elapsed > _timeout) {
|
||||
int32_t elapsed = micros() - startTime;
|
||||
if (elapsed > (int32_t)_timeout) {
|
||||
#ifdef DIAG_IO
|
||||
//DIAG(F("I2CManager Timeout on %s"), t->i2cAddress.toString());
|
||||
#endif
|
||||
@@ -300,12 +305,12 @@ void I2CManagerClass::handleInterrupt() {
|
||||
|
||||
// Check if current request has completed. If there's a current request
|
||||
// and state isn't active then state contains the completion status of the request.
|
||||
if (state == I2C_STATE_COMPLETED && currentRequest != NULL) {
|
||||
if (state == I2C_STATE_COMPLETED && currentRequest != NULL && currentRequest == queueHead) {
|
||||
// Operation has completed.
|
||||
if (completionStatus == I2C_STATUS_OK || ++retryCounter > MAX_I2C_RETRIES
|
||||
|| currentRequest->operation & OPERATION_NORETRY)
|
||||
{
|
||||
// Status is OK, or has failed and retry count exceeded, or retries disabled.
|
||||
// Status is OK, or has failed and retry count exceeded, or failed and retries disabled.
|
||||
#if defined(I2C_EXTENDED_ADDRESS)
|
||||
if (muxPhase == MuxPhase_PROLOG ) {
|
||||
overallStatus = completionStatus;
|
||||
|
@@ -26,27 +26,44 @@
|
||||
#include "I2CManager.h"
|
||||
#include "I2CManager_NonBlocking.h" // to satisfy intellisense
|
||||
|
||||
//#include <avr/io.h>
|
||||
//#include <avr/interrupt.h>
|
||||
#include <wiring_private.h>
|
||||
#include "stm32f4xx_hal_rcc.h"
|
||||
|
||||
/***************************************************************************
|
||||
* Interrupt handler.
|
||||
* IRQ handler for SERCOM3 which is the default I2C definition for Arduino Zero
|
||||
* compatible variants such as the Sparkfun SAMD21 Dev Breakout etc.
|
||||
* Later we may wish to allow use of an alternate I2C bus, or more than one I2C
|
||||
* bus on the SAMD architecture
|
||||
***************************************************************************/
|
||||
/*****************************************************************************
|
||||
* STM32F4xx I2C native driver support
|
||||
*
|
||||
* Nucleo-64 and Nucleo-144 boards all use I2C1 as the default I2C peripheral
|
||||
* Later we may wish to support other STM32 boards, allow use of an alternate
|
||||
* I2C bus, or more than one I2C bus on the STM32 architecture
|
||||
*****************************************************************************/
|
||||
#if defined(I2C_USE_INTERRUPTS) && defined(ARDUINO_ARCH_STM32)
|
||||
void I2C1_IRQHandler() {
|
||||
#if defined(ARDUINO_NUCLEO_F401RE) || defined(ARDUINO_NUCLEO_F411RE) || defined(ARDUINO_NUCLEO_F446RE) \
|
||||
|| defined(ARDUINO_NUCLEO_F412ZG) || defined(ARDUINO_NUCLEO_F413ZH) \
|
||||
|| defined(ARDUINO_NUCLEO_F429ZI) || defined(ARDUINO_NUCLEO_F446ZE)
|
||||
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely all Nucleo-64
|
||||
// and Nucleo-144 variants
|
||||
I2C_TypeDef *s = I2C1;
|
||||
|
||||
// In init we will ask the STM32 HAL layer for the configured APB1 clock frequency in Hz
|
||||
uint32_t APB1clk1; // Peripheral Input Clock speed in Hz.
|
||||
uint32_t i2c_MHz; // Peripheral Input Clock speed in MHz.
|
||||
|
||||
// IRQ handler for I2C1, replacing the weak definition in the STM32 HAL
|
||||
extern "C" void I2C1_EV_IRQHandler(void) {
|
||||
I2CManager.handleInterrupt();
|
||||
}
|
||||
extern "C" void I2C1_ER_IRQHandler(void) {
|
||||
I2CManager.handleInterrupt();
|
||||
}
|
||||
#else
|
||||
#warning STM32 board selected is not yet supported - so I2C1 peripheral is not defined
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Assume I2C1 for now - default I2C bus on Nucleo-F411RE and likely Nucleo-64 variants
|
||||
I2C_TypeDef *s = I2C1;
|
||||
#define I2C_IRQn I2C1_EV_IRQn
|
||||
#define I2C_BUSFREQ 16
|
||||
// Peripheral Input Clock speed in MHz.
|
||||
// For STM32F446RE, the speed is 45MHz. Ideally, this should be determined
|
||||
// at run-time from the APB1 clock, as it can vary from STM32 family to family.
|
||||
// #define I2C_PERIPH_CLK 45
|
||||
|
||||
// I2C SR1 Status Register #1 bit definitions for convenience
|
||||
// #define I2C_SR1_SMBALERT (1<<15) // SMBus alert
|
||||
@@ -80,52 +97,66 @@ I2C_TypeDef *s = I2C1;
|
||||
// #define I2C_CR1_SMBUS (1<<1) // SMBus mode, 1=SMBus, 0=I2C
|
||||
// #define I2C_CR1_PE (1<<0) // I2C Peripheral enable
|
||||
|
||||
// States of the STM32 I2C driver state machine
|
||||
enum {TS_IDLE,TS_START,TS_W_ADDR,TS_W_DATA,TS_W_STOP,TS_R_ADDR,TS_R_DATA,TS_R_STOP};
|
||||
|
||||
|
||||
/***************************************************************************
|
||||
* Set I2C clock speed register. This should only be called outside of
|
||||
* a transmission. The I2CManagerClass::_setClock() function ensures
|
||||
* that it is only called at the beginning of an I2C transaction.
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
|
||||
// Calculate a rise time appropriate to the requested bus speed
|
||||
// Use 10x the rise time spec to enable integer divide of 62.5ns clock period
|
||||
// Use 10x the rise time spec to enable integer divide of 50ns clock period
|
||||
uint16_t t_rise;
|
||||
uint32_t ccr_freq;
|
||||
if (i2cClockSpeed < 200000L) {
|
||||
// i2cClockSpeed = 100000L;
|
||||
t_rise = 0x11; // (1000ns /62.5ns) + 1;
|
||||
}
|
||||
else if (i2cClockSpeed < 800000L)
|
||||
|
||||
while (s->CR1 & I2C_CR1_STOP); // Prevents lockup by guarding further
|
||||
// writes to CR1 while STOP is being executed!
|
||||
|
||||
// Disable the I2C device, as TRISE can only be programmed whilst disabled
|
||||
s->CR1 &= ~(I2C_CR1_PE); // Disable I2C
|
||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
asm("nop"); // wait a bit... suggestion from online!
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
|
||||
if (i2cClockSpeed > 100000UL)
|
||||
{
|
||||
i2cClockSpeed = 400000L;
|
||||
t_rise = 0x06; // (300ns / 62.5ns) + 1;
|
||||
// } else if (i2cClockSpeed < 1200000L) {
|
||||
// i2cClockSpeed = 1000000L;
|
||||
// t_rise = 120;
|
||||
// if (i2cClockSpeed > 400000L)
|
||||
// i2cClockSpeed = 400000L;
|
||||
|
||||
t_rise = 300; // nanoseconds
|
||||
}
|
||||
else
|
||||
{
|
||||
i2cClockSpeed = 100000L;
|
||||
t_rise = 0x11; // (1000ns /62.5ns) + 1;
|
||||
// i2cClockSpeed = 100000L;
|
||||
t_rise = 1000; // nanoseconds
|
||||
}
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 &= ~(I2C_CR1_PE); // Enable I2C
|
||||
// Software reset the I2C peripheral
|
||||
// s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
// Release reset
|
||||
// s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
|
||||
// Calculate baudrate - using a rise time appropriate for the speed
|
||||
ccr_freq = I2C_BUSFREQ * 1000000 / i2cClockSpeed / 2;
|
||||
// Configure the rise time register - max allowed tRISE is 1000ns,
|
||||
// so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||
s->TRISE = (t_rise * i2c_MHz / 1000) + 1;
|
||||
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
// Bit 14: Duty, fast mode duty cycle
|
||||
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
|
||||
s->CCR = (uint16_t)ccr_freq;
|
||||
// Bit 14: Duty, fast mode duty cycle (use 2:1)
|
||||
// Bit 11-0: FREQR
|
||||
// if (i2cClockSpeed > 400000UL) {
|
||||
// // In fast mode plus, I2C period is 3 * CCR * TPCLK1.
|
||||
// // s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
// s->CCR = APB1clk1 / 3 / i2cClockSpeed; // Set I2C clockspeed to start!
|
||||
// s->CCR |= 0xC000; // We need Fast Mode AND DUTY bits set
|
||||
// } else {
|
||||
// In standard and fast mode, I2C period is 2 * CCR * TPCLK1
|
||||
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
s->CCR |= (APB1clk1 / 2 / i2cClockSpeed); // Set I2C clockspeed to start!
|
||||
// s->CCR |= (i2c_MHz * 500 / (i2cClockSpeed / 1000)); // Set I2C clockspeed to start!
|
||||
// if (i2cClockSpeed > 100000UL)
|
||||
// s->CCR |= 0xC000; // We need Fast Mode bits set as well
|
||||
// }
|
||||
|
||||
// Configure the rise time register
|
||||
s->TRISE = t_rise; // 1000 ns / 62.5 ns = 16 + 1
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
|
||||
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
|
||||
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
@@ -136,32 +167,54 @@ void I2CManagerClass::I2C_setClock(uint32_t i2cClockSpeed) {
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_init()
|
||||
{
|
||||
//Setting up the clocks
|
||||
RCC->APB1ENR |= (1<<21); // Enable I2C CLOCK
|
||||
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
|
||||
// Query the clockspeed from the STM32 HAL layer
|
||||
APB1clk1 = HAL_RCC_GetPCLK1Freq();
|
||||
i2c_MHz = APB1clk1 / 1000000UL;
|
||||
// DIAG(F("I2C_init() peripheral clock speed is: %d"), i2c_MHz);
|
||||
// Enable clocks
|
||||
RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;//(1 << 21); // Enable I2C CLOCK
|
||||
// Reset the I2C1 peripheral to initial state
|
||||
RCC->APB1RSTR |= RCC_APB1RSTR_I2C1RST;
|
||||
RCC->APB1RSTR &= ~RCC_APB1RSTR_I2C1RST;
|
||||
// Standard I2C pins are SCL on PB8 and SDA on PB9
|
||||
RCC->AHB1ENR |= (1<<1); // Enable GPIOB CLOCK for PB8/PB9
|
||||
// Bits (17:16)= 1:0 --> Alternate Function for Pin PB8;
|
||||
// Bits (19:18)= 1:0 --> Alternate Function for Pin PB9
|
||||
GPIOB->MODER &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all MODER bits for PB8 and PB9
|
||||
GPIOB->MODER |= (2<<(8*2)) | (2<<(9*2)); // PB8 and PB9 set to ALT function
|
||||
GPIOB->OTYPER |= (1<<8) | (1<<9); // PB8 and PB9 set to open drain output capability
|
||||
GPIOB->OSPEEDR |= (3<<(8*2)) | (3<<(9*2)); // PB8 and PB9 set to High Speed mode
|
||||
GPIOB->PUPDR &= ~((3<<(8*2)) | (3<<(9*2))); // Clear all PUPDR bits for PB8 and PB9
|
||||
GPIOB->PUPDR |= (1<<(8*2)) | (1<<(9*2)); // PB8 and PB9 set to pull-up capability
|
||||
// Alt Function High register routing pins PB8 and PB9 for I2C1:
|
||||
// Bits (3:2:1:0) = 0:1:0:0 --> AF4 for pin PB8
|
||||
// Bits (7:6:5:4) = 0:1:0:0 --> AF4 for pin PB9
|
||||
GPIOB->AFR[1] &= ~((15<<0) | (15<<4)); // Clear all AFR bits for PB8 on low nibble, PB9 on next nibble up
|
||||
GPIOB->AFR[1] |= (4<<0) | (4<<4); // PB8 on low nibble, PB9 on next nibble up
|
||||
|
||||
// Software reset the I2C peripheral
|
||||
I2C1->CR1 &= ~I2C_CR1_PE; // Disable I2C1 peripheral
|
||||
s->CR1 |= I2C_CR1_SWRST; // reset the I2C
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
asm("nop"); // wait a bit... suggestion from online!
|
||||
s->CR1 &= ~(I2C_CR1_SWRST); // Normal operation
|
||||
|
||||
// Program the peripheral input clock in CR2 Register in order to generate correct timings
|
||||
s->CR2 |= I2C_BUSFREQ; // PCLK1 FREQUENCY in MHz
|
||||
// Clear all bits in I2C CR2 register except reserved bits
|
||||
s->CR2 &= 0xE000;
|
||||
|
||||
// Set I2C peripheral clock frequency
|
||||
// s->CR2 |= I2C_PERIPH_CLK;
|
||||
s->CR2 |= i2c_MHz;
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d"), s->CR2);
|
||||
|
||||
// set own address to 00 - not used in master mode
|
||||
I2C1->OAR1 = (1 << 14); // bit 14 should be kept at 1 according to the datasheet
|
||||
|
||||
#if defined(I2C_USE_INTERRUPTS)
|
||||
// Setting NVIC
|
||||
NVIC_SetPriority(I2C_IRQn, 1); // Match default priorities
|
||||
NVIC_EnableIRQ(I2C_IRQn);
|
||||
NVIC_SetPriority(I2C1_EV_IRQn, 1); // Match default priorities
|
||||
NVIC_EnableIRQ(I2C1_EV_IRQn);
|
||||
NVIC_SetPriority(I2C1_ER_IRQn, 1); // Match default priorities
|
||||
NVIC_EnableIRQ(I2C1_ER_IRQn);
|
||||
|
||||
// CR2 Interrupt Settings
|
||||
// Bit 15-13: reserved
|
||||
@@ -172,23 +225,28 @@ void I2CManagerClass::I2C_init()
|
||||
// Bit 8: ITERREN - Error interrupt enable
|
||||
// Bit 7-6: reserved
|
||||
// Bit 5-0: FREQ - Peripheral clock frequency (max 50MHz)
|
||||
// s->CR2 |= 0x0700; // Enable Buffer, Event and Error interrupts
|
||||
s->CR2 |= 0x0300; // Enable Event and Error interrupts
|
||||
s->CR2 |= (I2C_CR2_ITBUFEN | I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable Buffer, Event and Error interrupts
|
||||
#endif
|
||||
|
||||
// DIAG(F("I2C_init() setting initial I2C clock to 100KHz"));
|
||||
// Calculate baudrate and set default rate for now
|
||||
// Configure the Clock Control Register for 100KHz SCL frequency
|
||||
// Bit 15: I2C Master mode, 0=standard, 1=Fast Mode
|
||||
// Bit 14: Duty, fast mode duty cycle
|
||||
// Bit 11-0: FREQR = 16MHz => TPCLK1 = 62.5ns, so CCR divisor must be 0x50 (80 * 62.5ns = 5000ns)
|
||||
s->CCR = 0x0050;
|
||||
// Bit 11-0: so CCR divisor would be clk / 2 / 100000 (where clk is in Hz)
|
||||
// s->CCR = I2C_PERIPH_CLK * 5;
|
||||
s->CCR &= ~(0x3000); // Clear all bits except 12 and 13 which must remain per reset value
|
||||
s->CCR |= (APB1clk1 / 2 / 100000UL); // Set a default of 100KHz I2C clockspeed to start!
|
||||
|
||||
// Configure the rise time register - max allowed in 1000ns
|
||||
s->TRISE = 0x0011; // 1000 ns / 62.5 ns = 16 + 1
|
||||
// Configure the rise time register - max allowed is 1000ns, so value = 1000ns * I2C_PERIPH_CLK MHz / 1000 + 1.
|
||||
s->TRISE = (1000 * i2c_MHz / 1000) + 1;
|
||||
|
||||
// DIAG(F("I2C_init() peripheral clock is now: %d, full reg is %x"), (s->CR2 & 0xFF), s->CR2);
|
||||
// DIAG(F("I2C_init() peripheral CCR is now: %d"), s->CCR);
|
||||
// DIAG(F("I2C_init() peripheral TRISE is now: %d"), s->TRISE);
|
||||
|
||||
// Enable the I2C master mode
|
||||
s->CR1 |= I2C_CR1_PE; // Enable I2C
|
||||
// Setting bus idle mode and wait for sync
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -198,49 +256,30 @@ void I2CManagerClass::I2C_sendStart() {
|
||||
|
||||
// Set counters here in case this is a retry.
|
||||
rxCount = txCount = 0;
|
||||
uint8_t temp;
|
||||
|
||||
// On a single-master I2C bus, the start bit won't be sent until the bus
|
||||
// state goes to IDLE so we can request it without waiting. On a
|
||||
// multi-master bus, the bus may be BUSY under control of another master,
|
||||
// On a single-master I2C bus, the start bit won't be sent until the bus
|
||||
// state goes to IDLE so we can request it without waiting. On a
|
||||
// multi-master bus, the bus may be BUSY under control of another master,
|
||||
// in which case we can avoid some arbitration failures by waiting until
|
||||
// the bus state is IDLE. We don't do that here.
|
||||
//while (s->SR2 & I2C_SR2_BUSY) {}
|
||||
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend))
|
||||
{
|
||||
// Send start for read operation
|
||||
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
|
||||
s->CR1 |= I2C_CR1_START; // Generate START
|
||||
// Send address with read flag (1) or'd in
|
||||
s->DR = (deviceAddress << 1) | 1; // send the address
|
||||
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
|
||||
// Special case for 1 byte reads!
|
||||
if (bytesToReceive == 1)
|
||||
{
|
||||
s->CR1 &= ~I2C_CR1_ACK; // clear the ACK bit
|
||||
temp = I2C1->SR1 | I2C1->SR2; // read SR1 and SR2 to clear the ADDR bit.... EV6 condition
|
||||
s->CR1 |= I2C_CR1_STOP; // Stop I2C
|
||||
}
|
||||
else
|
||||
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
|
||||
}
|
||||
else {
|
||||
// Send start for write operation
|
||||
s->CR1 |= I2C_CR1_ACK; // Enable the ACK
|
||||
s->CR1 |= I2C_CR1_START; // Generate START
|
||||
// Send address with write flag (0) or'd in
|
||||
s->DR = (deviceAddress << 1) | 0; // send the address
|
||||
while (!(s->SR1 && I2C_SR1_ADDR)); // wait for ADDR bit to set
|
||||
temp = s->SR1 | s->SR2; // read SR1 and SR2 to clear the ADDR bit
|
||||
}
|
||||
// Check there's no STOP still in progress. If we OR the START bit into CR1
|
||||
// and the STOP bit is already set, we could output multiple STOP conditions.
|
||||
while (s->CR1 & I2C_CR1_STOP) {} // Wait for STOP bit to reset
|
||||
|
||||
s->CR2 |= (I2C_CR2_ITEVTEN | I2C_CR2_ITERREN); // Enable interrupts
|
||||
s->CR2 &= ~I2C_CR2_ITBUFEN; // Don't enable buffer interupts yet.
|
||||
s->CR1 &= ~I2C_CR1_POS; // Clear the POS bit
|
||||
s->CR1 |= (I2C_CR1_ACK | I2C_CR1_START); // Enable the ACK and generate START
|
||||
transactionState = TS_START;
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
* Initiate a stop bit for transmission (does not interrupt)
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_sendStop() {
|
||||
s->CR1 |= I2C_CR1_STOP; // Stop I2C
|
||||
s->CR1 |= I2C_CR1_STOP; // Stop I2C
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -252,9 +291,11 @@ void I2CManagerClass::I2C_close() {
|
||||
s->CR1 &= ~I2C_CR1_PE; // Disable I2C peripheral
|
||||
// Should never happen, but wait for up to 500us only.
|
||||
unsigned long startTime = micros();
|
||||
while ((s->CR1 && I2C_CR1_PE) != 0) {
|
||||
if (micros() - startTime >= 500UL) break;
|
||||
while ((s->CR1 & I2C_CR1_PE) != 0) {
|
||||
if ((int32_t)(micros() - startTime) >= 500) break;
|
||||
}
|
||||
NVIC_DisableIRQ(I2C1_EV_IRQn);
|
||||
NVIC_DisableIRQ(I2C1_ER_IRQn);
|
||||
}
|
||||
|
||||
/***************************************************************************
|
||||
@@ -263,50 +304,217 @@ void I2CManagerClass::I2C_close() {
|
||||
* (and therefore, indirectly, from I2CRB::wait() and I2CRB::isBusy()).
|
||||
***************************************************************************/
|
||||
void I2CManagerClass::I2C_handleInterrupt() {
|
||||
volatile uint16_t temp_sr1, temp_sr2;
|
||||
|
||||
if (s->SR1 && I2C_SR1_ARLO) {
|
||||
// Arbitration lost, restart
|
||||
I2C_sendStart(); // Reinitiate request
|
||||
} else if (s->SR1 && I2C_SR1_BERR) {
|
||||
// Bus error
|
||||
completionStatus = I2C_STATUS_BUS_ERROR;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} else if (s->SR1 && I2C_SR1_TXE) {
|
||||
// Master write completed
|
||||
if (s->SR1 && (1<<10)) {
|
||||
// Nacked, send stop.
|
||||
I2C_sendStop();
|
||||
temp_sr1 = s->SR1;
|
||||
|
||||
// Check for errors first
|
||||
if (temp_sr1 & (I2C_SR1_AF | I2C_SR1_ARLO | I2C_SR1_BERR)) {
|
||||
// Check which error flag is set
|
||||
if (temp_sr1 & I2C_SR1_AF)
|
||||
{
|
||||
s->SR1 &= ~(I2C_SR1_AF); // Clear AF
|
||||
I2C_sendStop(); // Clear the bus
|
||||
transactionState = TS_IDLE;
|
||||
completionStatus = I2C_STATUS_NEGATIVE_ACKNOWLEDGE;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} else if (bytesToSend) {
|
||||
// Acked, so send next byte
|
||||
s->DR = sendBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
} else if (bytesToReceive) {
|
||||
// Last sent byte acked and no more to send. Send repeated start, address and read bit.
|
||||
// s->I2CM.ADDR.bit.ADDR = (deviceAddress << 1) | 1;
|
||||
} else {
|
||||
// Check both TxE/BTF == 1 before generating stop
|
||||
while (!(s->SR1 && I2C_SR1_TXE)); // Check TxE
|
||||
while (!(s->SR1 && I2C_SR1_BTF)); // Check BTF
|
||||
// No more data to send/receive. Initiate a STOP condition and finish
|
||||
I2C_sendStop();
|
||||
}
|
||||
else if (temp_sr1 & I2C_SR1_ARLO)
|
||||
{
|
||||
// Arbitration lost, restart
|
||||
s->SR1 &= ~(I2C_SR1_ARLO); // Clear ARLO
|
||||
I2C_sendStart(); // Reinitiate request
|
||||
transactionState = TS_START;
|
||||
}
|
||||
else if (temp_sr1 & I2C_SR1_BERR)
|
||||
{
|
||||
// Bus error
|
||||
s->SR1 &= ~(I2C_SR1_BERR); // Clear BERR
|
||||
I2C_sendStop(); // Clear the bus
|
||||
transactionState = TS_IDLE;
|
||||
completionStatus = I2C_STATUS_BUS_ERROR;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
} else if (s->SR1 && I2C_SR1_RXNE) {
|
||||
// Master read completed without errors
|
||||
if (bytesToReceive == 1) {
|
||||
// s->I2CM.CTRLB.bit.ACKACT = 1; // NAK final byte
|
||||
I2C_sendStop(); // send stop
|
||||
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
||||
bytesToReceive = 0;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} else if (bytesToReceive) {
|
||||
// s->I2CM.CTRLB.bit.ACKACT = 0; // ACK all but final byte
|
||||
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
||||
bytesToReceive--;
|
||||
}
|
||||
else {
|
||||
// No error flags, so process event according to current state.
|
||||
switch (transactionState) {
|
||||
case TS_START:
|
||||
if (temp_sr1 & I2C_SR1_SB) {
|
||||
// Event EV5
|
||||
// Start bit has been sent successfully and we have the bus.
|
||||
// If anything to send, initiate write. Otherwise initiate read.
|
||||
if (operation == OPERATION_READ || ((operation == OPERATION_REQUEST) && !bytesToSend)) {
|
||||
// Send address with read flag (1) or'd in
|
||||
s->DR = (deviceAddress << 1) | 1; // send the address
|
||||
transactionState = TS_R_ADDR;
|
||||
} else {
|
||||
// Send address with write flag (0) or'd in
|
||||
s->DR = (deviceAddress << 1) | 0; // send the address
|
||||
transactionState = TS_W_ADDR;
|
||||
}
|
||||
}
|
||||
// SB bit is cleared by writing to DR (already done).
|
||||
break;
|
||||
|
||||
case TS_W_ADDR:
|
||||
if (temp_sr1 & I2C_SR1_ADDR) {
|
||||
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
|
||||
// Event EV6
|
||||
// Address sent successfully, device has ack'd in response.
|
||||
if (!bytesToSend) {
|
||||
I2C_sendStop();
|
||||
transactionState = TS_IDLE;
|
||||
completionStatus = I2C_STATUS_OK;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} else {
|
||||
// Put one byte into DR to load shift register.
|
||||
s->DR = sendBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
if (bytesToSend) {
|
||||
// Put another byte to load DR
|
||||
s->DR = sendBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
}
|
||||
if (!bytesToSend) {
|
||||
// No more bytes to send.
|
||||
// The TXE interrupt occurs when the DR is empty, and the BTF interrupt
|
||||
// occurs when the shift register is also empty (one character later).
|
||||
// To avoid repeated TXE interrupts during this time, we disable TXE interrupt.
|
||||
s->CR2 &= ~I2C_CR2_ITBUFEN; // Wait for BTF interrupt, disable TXE interrupt
|
||||
transactionState = TS_W_STOP;
|
||||
} else {
|
||||
// More data remaining to send after this interrupt, enable TXE interrupt.
|
||||
s->CR2 |= I2C_CR2_ITBUFEN;
|
||||
transactionState = TS_W_DATA;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TS_W_DATA:
|
||||
if (temp_sr1 & I2C_SR1_TXE) {
|
||||
// Event EV8_1/EV8
|
||||
// Transmitter empty, write a byte to it.
|
||||
if (bytesToSend) {
|
||||
s->DR = sendBuffer[txCount++];
|
||||
bytesToSend--;
|
||||
if (!bytesToSend) {
|
||||
s->CR2 &= ~I2C_CR2_ITBUFEN; // Disable TXE interrupt
|
||||
transactionState = TS_W_STOP;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TS_W_STOP:
|
||||
if (temp_sr1 & I2C_SR1_BTF) {
|
||||
// Event EV8_2
|
||||
// Done, last character sent. Anything to receive?
|
||||
if (bytesToReceive) {
|
||||
I2C_sendStart();
|
||||
// NOTE: Three redundant BTF interrupts take place between the
|
||||
// first BTF interrupt and the START interrupt. I've tried all sorts
|
||||
// of ways to eliminate them, and the only thing that worked for
|
||||
// me was to loop until the BTF bit becomes reset. Either way,
|
||||
// it's a waste of processor time. Anyone got a solution?
|
||||
//while (s->SR1 && I2C_SR1_BTF) {}
|
||||
transactionState = TS_START;
|
||||
} else {
|
||||
I2C_sendStop();
|
||||
transactionState = TS_IDLE;
|
||||
completionStatus = I2C_STATUS_OK;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
s->SR1 &= I2C_SR1_BTF; // Clear BTF interrupt
|
||||
}
|
||||
break;
|
||||
|
||||
case TS_R_ADDR:
|
||||
if (temp_sr1 & I2C_SR1_ADDR) {
|
||||
// Event EV6
|
||||
// Address sent for receive.
|
||||
// The next bit is different depending on whether there are
|
||||
// 1 byte, 2 bytes or >2 bytes to be received, in accordance with the
|
||||
// Programmers Reference RM0390.
|
||||
if (bytesToReceive == 1) {
|
||||
// Receive 1 byte
|
||||
s->CR1 &= ~I2C_CR1_ACK; // Disable ack
|
||||
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
|
||||
// Next step will occur after a RXNE interrupt, so enable it
|
||||
s->CR2 |= I2C_CR2_ITBUFEN;
|
||||
transactionState = TS_R_STOP;
|
||||
} else if (bytesToReceive == 2) {
|
||||
// Receive 2 bytes
|
||||
s->CR1 &= ~I2C_CR1_ACK; // Disable ACK for final byte
|
||||
s->CR1 |= I2C_CR1_POS; // set POS flag to delay effect of ACK flag
|
||||
// Next step will occur after a BTF interrupt, so disable RXNE interrupt
|
||||
s->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
|
||||
transactionState = TS_R_STOP;
|
||||
} else {
|
||||
// >2 bytes, just wait for bytes to come in and ack them for the time being
|
||||
// (ack flag has already been set).
|
||||
// Next step will occur after a BTF interrupt, so disable RXNE interrupt
|
||||
s->CR2 &= ~I2C_CR2_ITBUFEN;
|
||||
temp_sr2 = s->SR2; // read SR2 to complete clearing the ADDR bit
|
||||
transactionState = TS_R_DATA;
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case TS_R_DATA:
|
||||
// Event EV7/EV7_1
|
||||
if (temp_sr1 & I2C_SR1_BTF) {
|
||||
// Byte received in receiver - read next byte
|
||||
if (bytesToReceive == 3) {
|
||||
// Getting close to the last byte, so a specific sequence is recommended.
|
||||
s->CR1 &= ~I2C_CR1_ACK; // Reset ack for next byte received.
|
||||
transactionState = TS_R_STOP;
|
||||
}
|
||||
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
||||
bytesToReceive--;
|
||||
}
|
||||
break;
|
||||
|
||||
case TS_R_STOP:
|
||||
if (temp_sr1 & I2C_SR1_BTF) {
|
||||
// Event EV7 (last one)
|
||||
// When we've got here, the receiver has got the last two bytes
|
||||
// (or one byte, if only one byte is being received),
|
||||
// and NAK has already been sent, so we need to read from the receiver.
|
||||
if (bytesToReceive) {
|
||||
if (bytesToReceive > 1)
|
||||
I2C_sendStop();
|
||||
while(bytesToReceive) {
|
||||
receiveBuffer[rxCount++] = s->DR; // Store received byte(s)
|
||||
bytesToReceive--;
|
||||
}
|
||||
// Finish.
|
||||
transactionState = TS_IDLE;
|
||||
completionStatus = I2C_STATUS_OK;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
}
|
||||
} else if (temp_sr1 & I2C_SR1_RXNE) {
|
||||
if (bytesToReceive == 1) {
|
||||
// One byte on a single-byte transfer. Ack has already been set.
|
||||
I2C_sendStop();
|
||||
receiveBuffer[rxCount++] = s->DR; // Store received byte
|
||||
bytesToReceive--;
|
||||
// Finish.
|
||||
transactionState = TS_IDLE;
|
||||
completionStatus = I2C_STATUS_OK;
|
||||
state = I2C_STATE_COMPLETED;
|
||||
} else
|
||||
s->SR1 &= I2C_SR1_RXNE; // Acknowledge interrupt
|
||||
}
|
||||
break;
|
||||
}
|
||||
// If we've received an interrupt at any other time, we're not interested so clear it
|
||||
// to prevent it recurring ad infinitum.
|
||||
s->SR1 = 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif /* I2CMANAGER_STM32_H */
|
||||
|
@@ -176,6 +176,13 @@ bool IODevice::exists(VPIN vpin) {
|
||||
return findDevice(vpin) != NULL;
|
||||
}
|
||||
|
||||
// Return the status of the device att vpin.
|
||||
uint8_t IODevice::getStatus(VPIN vpin) {
|
||||
IODevice *dev = findDevice(vpin);
|
||||
if (!dev) return false;
|
||||
return dev->_deviceState;
|
||||
}
|
||||
|
||||
// check whether the pin supports notification. If so, then regular _read calls are not required.
|
||||
bool IODevice::hasCallback(VPIN vpin) {
|
||||
IODevice *dev = findDevice(vpin);
|
||||
|
17
IODevice.h
17
IODevice.h
@@ -22,17 +22,12 @@
|
||||
#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
|
||||
|
||||
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
|
||||
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
|
||||
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
|
||||
#define IO_NO_HAL
|
||||
#endif
|
||||
|
||||
// Define symbol IO_SWITCH_OFF_SERVO to set the PCA9685 output to 0 when an
|
||||
// animation has completed. This switches off the servo motor, preventing
|
||||
// the continuous buzz sometimes found on servos, and reducing the
|
||||
@@ -160,6 +155,9 @@ public:
|
||||
// exists checks whether there is a device owning the specified vpin
|
||||
static bool exists(VPIN vpin);
|
||||
|
||||
// getStatus returns the state of the device at the specified vpin
|
||||
static uint8_t getStatus(VPIN vpin);
|
||||
|
||||
// Enable shared interrupt on specified pin for GPIO extender modules. The extender module
|
||||
// should pull down this pin when requesting a scan. The pin may be shared by multiple modules.
|
||||
// Without the shared interrupt, input states are scanned periodically to detect changes on
|
||||
@@ -383,6 +381,7 @@ private:
|
||||
uint8_t *_pinInUse;
|
||||
};
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
/*
|
||||
* IODevice subclass for EX-Turntable.
|
||||
@@ -411,10 +410,14 @@ private:
|
||||
void _begin() override;
|
||||
void _loop(unsigned long currentMicros) override;
|
||||
int _read(VPIN vpin) override;
|
||||
void _broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity);
|
||||
void _writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) override;
|
||||
void _display() override;
|
||||
uint8_t _stepperStatus;
|
||||
uint8_t _previousStatus;
|
||||
uint8_t _currentActivity;
|
||||
};
|
||||
#endif
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
|
@@ -20,20 +20,21 @@
|
||||
/*
|
||||
* The IO_EXTurntable device driver is used to control a turntable via an Arduino with a stepper motor over I2C.
|
||||
*
|
||||
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/Turntable-EX) and contains the stepper motor logic.
|
||||
* The EX-Turntable code lives in a separate repo (https://github.com/DCC-EX/EX-Turntable) and contains the stepper motor logic.
|
||||
*
|
||||
* This device driver sends a step position to Turntable-EX to indicate the step position to move to using either of these commands:
|
||||
* This device driver sends a step position to EX-Turntable to indicate the step position to move to using either of these commands:
|
||||
* <D TT vpin steps activity> in the serial console
|
||||
* MOVETT(vpin, steps, activity) in EX-RAIL
|
||||
* Refer to the documentation for further information including the valid activities.
|
||||
*/
|
||||
|
||||
#ifndef IO_EXTurntable_h
|
||||
#define IO_EXTurntable_h
|
||||
|
||||
#include "IODevice.h"
|
||||
#include "I2CManager.h"
|
||||
#include "DIAG.h"
|
||||
#include "Turntables.h"
|
||||
#include "CommandDistributor.h"
|
||||
|
||||
#ifndef IO_NO_HAL
|
||||
|
||||
void EXTurntable::create(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||
new EXTurntable(firstVpin, nPins, I2CAddress);
|
||||
@@ -44,6 +45,8 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||
_firstVpin = firstVpin;
|
||||
_nPins = nPins;
|
||||
_I2CAddress = I2CAddress;
|
||||
_stepperStatus = 0;
|
||||
_previousStatus = 0;
|
||||
addDevice(this);
|
||||
}
|
||||
|
||||
@@ -51,6 +54,7 @@ EXTurntable::EXTurntable(VPIN firstVpin, int nPins, I2CAddress I2CAddress) {
|
||||
void EXTurntable::_begin() {
|
||||
I2CManager.begin();
|
||||
if (I2CManager.exists(_I2CAddress)) {
|
||||
DIAG(F("EX-Turntable device found, I2C:%s"), _I2CAddress.toString());
|
||||
#ifdef DIAG_IO
|
||||
_display();
|
||||
#endif
|
||||
@@ -67,15 +71,19 @@ void EXTurntable::_loop(unsigned long currentMicros) {
|
||||
uint8_t readBuffer[1];
|
||||
I2CManager.read(_I2CAddress, readBuffer, 1);
|
||||
_stepperStatus = readBuffer[0];
|
||||
// DIAG(F("Turntable-EX returned status: %d"), _stepperStatus);
|
||||
delayUntil(currentMicros + 500000); // Wait 500ms before checking again, turntables turn slowly
|
||||
if (_stepperStatus != _previousStatus && _stepperStatus == 0) { // Broadcast when a rotation finishes
|
||||
if ( _currentActivity < 4) {
|
||||
_broadcastStatus(_firstVpin, _stepperStatus, _currentActivity);
|
||||
}
|
||||
_previousStatus = _stepperStatus;
|
||||
}
|
||||
delayUntil(currentMicros + 100000); // Wait 100ms before checking again
|
||||
}
|
||||
|
||||
// Read returns status as obtained in our loop.
|
||||
// Return false if our status value is invalid.
|
||||
int EXTurntable::_read(VPIN vpin) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return 0;
|
||||
// DIAG(F("_read status: %d"), _stepperStatus);
|
||||
if (_stepperStatus > 1) {
|
||||
return false;
|
||||
} else {
|
||||
@@ -83,6 +91,17 @@ int EXTurntable::_read(VPIN vpin) {
|
||||
}
|
||||
}
|
||||
|
||||
// If a status change has occurred for a turntable object, broadcast it
|
||||
void EXTurntable::_broadcastStatus (VPIN vpin, uint8_t status, uint8_t activity) {
|
||||
Turntable *tto = Turntable::getByVpin(vpin);
|
||||
if (tto) {
|
||||
if (activity < 4) {
|
||||
tto->setMoving(status);
|
||||
CommandDistributor::broadcastTurntable(tto->getId(), tto->getPosition(), status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// writeAnalogue to send the steps and activity to Turntable-EX.
|
||||
// Sends 3 bytes containing the MSB and LSB of the step count, and activity.
|
||||
// value contains the steps, bit shifted to MSB + LSB.
|
||||
@@ -100,6 +119,7 @@ int EXTurntable::_read(VPIN vpin) {
|
||||
// Acc_Off = 9 // Turn accessory pin off
|
||||
void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_t duration) {
|
||||
if (_deviceState == DEVSTATE_FAILED) return;
|
||||
if (value < 0) return;
|
||||
uint8_t stepsMSB = value >> 8;
|
||||
uint8_t stepsLSB = value & 0xFF;
|
||||
#ifdef DIAG_IO
|
||||
@@ -108,7 +128,10 @@ void EXTurntable::_writeAnalogue(VPIN vpin, int value, uint8_t activity, uint16_
|
||||
DIAG(F("I2CManager write I2C Address:%d stepsMSB:%d stepsLSB:%d activity:%d"),
|
||||
_I2CAddress.toString(), stepsMSB, stepsLSB, activity);
|
||||
#endif
|
||||
_stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
if (activity < 4) _stepperStatus = 1; // Tell the device driver Turntable-EX is busy
|
||||
_previousStatus = _stepperStatus;
|
||||
_currentActivity = activity;
|
||||
_broadcastStatus(vpin, _stepperStatus, activity); // Broadcast when the rotation starts
|
||||
I2CManager.write(_I2CAddress, 3, stepsMSB, stepsLSB, activity);
|
||||
}
|
||||
|
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
|
11
IO_PCA9555.h
11
IO_PCA9555.h
@@ -30,20 +30,19 @@
|
||||
|
||||
class PCA9555 : public GPIOBase<uint16_t> {
|
||||
public:
|
||||
static void create(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1) {
|
||||
new PCA9555(vpin, min(nPins,16), I2CAddress, interruptPin);
|
||||
static void create(VPIN vpin, uint8_t nPins, I2CAddress i2cAddress, int interruptPin=-1) {
|
||||
if (checkNoOverlap(vpin, nPins, i2cAddress)) new PCA9555(vpin,nPins, i2cAddress, interruptPin);
|
||||
}
|
||||
|
||||
|
||||
private:
|
||||
// Constructor
|
||||
PCA9555(VPIN vpin, int nPins, uint8_t I2CAddress, int interruptPin=-1)
|
||||
PCA9555(VPIN vpin, uint8_t nPins, I2CAddress I2CAddress, int interruptPin=-1)
|
||||
: GPIOBase<uint16_t>((FSH *)F("PCA9555"), vpin, nPins, I2CAddress, interruptPin)
|
||||
{
|
||||
requestBlock.setRequestParams(_I2CAddress, inputBuffer, sizeof(inputBuffer),
|
||||
outputBuffer, sizeof(outputBuffer));
|
||||
outputBuffer[0] = REG_INPUT_P0;
|
||||
}
|
||||
|
||||
private:
|
||||
void _writeGpioPort() override {
|
||||
I2CManager.write(_I2CAddress, 3, REG_OUTPUT_P0, _portOutputState, _portOutputState>>8);
|
||||
}
|
||||
|
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
|
@@ -4,6 +4,7 @@
|
||||
* © 2021 Fred Decker
|
||||
* © 2020-2023 Harald Barth
|
||||
* © 2020-2021 Chris Harlow
|
||||
* © 2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -26,12 +27,18 @@
|
||||
#include "DCCWaveform.h"
|
||||
#include "DCCTimer.h"
|
||||
#include "DIAG.h"
|
||||
#include "EXRAIL2.h"
|
||||
|
||||
unsigned long MotorDriver::globalOverloadStart = 0;
|
||||
|
||||
volatile portreg_t shadowPORTA;
|
||||
volatile portreg_t shadowPORTB;
|
||||
volatile portreg_t shadowPORTC;
|
||||
#if defined(ARDUINO_ARCH_STM32)
|
||||
volatile portreg_t shadowPORTD;
|
||||
volatile portreg_t shadowPORTE;
|
||||
volatile portreg_t shadowPORTF;
|
||||
#endif
|
||||
|
||||
MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, int16_t brake_pin,
|
||||
byte current_pin, float sense_factor, unsigned int trip_milliamps, int16_t fault_pin) {
|
||||
@@ -66,6 +73,21 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTC;
|
||||
}
|
||||
if (HAVE_PORTD(fastSignalPin.inout == &PORTD)) {
|
||||
DIAG(F("Found PORTD pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTD;
|
||||
}
|
||||
if (HAVE_PORTE(fastSignalPin.inout == &PORTE)) {
|
||||
DIAG(F("Found PORTE pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTE;
|
||||
}
|
||||
if (HAVE_PORTF(fastSignalPin.inout == &PORTF)) {
|
||||
DIAG(F("Found PORTF pin %d"),signalPin);
|
||||
fastSignalPin.shadowinout = fastSignalPin.inout;
|
||||
fastSignalPin.inout = &shadowPORTF;
|
||||
}
|
||||
|
||||
signalPin2=signal_pin2;
|
||||
if (signalPin2!=UNUSED_PIN) {
|
||||
@@ -89,6 +111,21 @@ MotorDriver::MotorDriver(int16_t power_pin, byte signal_pin, byte signal_pin2, i
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTC;
|
||||
}
|
||||
if (HAVE_PORTD(fastSignalPin2.inout == &PORTD)) {
|
||||
DIAG(F("Found PORTD pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTD;
|
||||
}
|
||||
if (HAVE_PORTE(fastSignalPin2.inout == &PORTE)) {
|
||||
DIAG(F("Found PORTE pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTE;
|
||||
}
|
||||
if (HAVE_PORTF(fastSignalPin2.inout == &PORTF)) {
|
||||
DIAG(F("Found PORTF pin %d"),signalPin2);
|
||||
fastSignalPin2.shadowinout = fastSignalPin2.inout;
|
||||
fastSignalPin2.inout = &shadowPORTF;
|
||||
}
|
||||
}
|
||||
else dualSignal=false;
|
||||
|
||||
@@ -277,7 +314,7 @@ void MotorDriver::startCurrentFromHW() {
|
||||
#pragma GCC pop_options
|
||||
#endif //ANALOG_READ_INTERRUPT
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
#ifdef VARIABLE_TONES
|
||||
uint16_t taurustones[28] = { 165, 175, 196, 220,
|
||||
247, 262, 294, 330,
|
||||
@@ -328,7 +365,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
byte tSpeed=speedcode & 0x7F; // DCC Speed with 0,1 stop and speed steps 2 to 127
|
||||
byte tDir=speedcode & 0x80;
|
||||
byte brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
{
|
||||
int f = 131;
|
||||
#ifdef VARIABLE_TONES
|
||||
@@ -346,7 +383,7 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
else brake = 2 * (128-tSpeed);
|
||||
if (invertBrake)
|
||||
brake=255-brake;
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(ARDUINO_ARCH_STM32)
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,brake);
|
||||
#else
|
||||
analogWrite(brakePin,brake);
|
||||
@@ -370,6 +407,24 @@ void MotorDriver::setDCSignal(byte speedcode) {
|
||||
setSignal(tDir);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTD(fastSignalPin.shadowinout == &PORTD)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTD(shadowPORTD=PORTD);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTD(PORTD=shadowPORTD);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTE(fastSignalPin.shadowinout == &PORTE)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTE(shadowPORTE=PORTE);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTE(PORTE=shadowPORTE);
|
||||
interrupts();
|
||||
} else if (HAVE_PORTF(fastSignalPin.shadowinout == &PORTF)) {
|
||||
noInterrupts();
|
||||
HAVE_PORTF(shadowPORTF=PORTF);
|
||||
setSignal(tDir);
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
interrupts();
|
||||
} else {
|
||||
noInterrupts();
|
||||
setSignal(tDir);
|
||||
@@ -391,6 +446,13 @@ void MotorDriver::throttleInrush(bool on) {
|
||||
} else {
|
||||
ledcDetachPin(brakePin);
|
||||
}
|
||||
#elif defined(ARDUINO_ARCH_STM32)
|
||||
if(on) {
|
||||
DCCTimer::DCCEXanalogWriteFrequency(brakePin, 62500);
|
||||
DCCTimer::DCCEXanalogWrite(brakePin,duty);
|
||||
} else {
|
||||
pinMode(brakePin, OUTPUT);
|
||||
}
|
||||
#else
|
||||
if(on){
|
||||
switch(brakePin) {
|
||||
@@ -613,7 +675,9 @@ void MotorDriver::checkPowerOverload(bool useProgLimit, byte trackno) {
|
||||
// adjust next wait time
|
||||
power_sample_overload_wait *= 2;
|
||||
if (power_sample_overload_wait > POWER_SAMPLE_RETRY_MAX)
|
||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||
power_sample_overload_wait = POWER_SAMPLE_RETRY_MAX;
|
||||
DIAG(F("Calling EXRAIL"));
|
||||
RMFT2::powerEvent(trackno, true); // Tell EXRAIL we have an overload
|
||||
// power on test
|
||||
DIAG(F("TRACK %c POWER RESTORE (after %4M)"), trackno + 'A', mslpc);
|
||||
setPower(POWERMODE::ALERT);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* © 2022 Paul M Antoine
|
||||
* © 2022-2023 Paul M. Antoine
|
||||
* © 2021 Mike S
|
||||
* © 2021 Fred Decker
|
||||
* © 2020 Chris Harlow
|
||||
@@ -60,6 +60,16 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
|
||||
#define HAVE_PORTB(X) X
|
||||
#define PORTC GPIOC->ODR
|
||||
#define HAVE_PORTC(X) X
|
||||
#define PORTD GPIOD->ODR
|
||||
#define HAVE_PORTD(X) X
|
||||
#if defined(GPIOE)
|
||||
#define PORTE GPIOE->ODR
|
||||
#define HAVE_PORTE(X) X
|
||||
#endif
|
||||
#if defined(GPIOF)
|
||||
#define PORTF GPIOF->ODR
|
||||
#define HAVE_PORTF(X) X
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// if macros not defined as pass-through we define
|
||||
@@ -74,6 +84,15 @@ enum TRACK_MODE : byte {TRACK_MODE_NONE = 1, TRACK_MODE_MAIN = 2, TRACK_MODE_PRO
|
||||
#ifndef HAVE_PORTC
|
||||
#define HAVE_PORTC(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTD
|
||||
#define HAVE_PORTD(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTE
|
||||
#define HAVE_PORTE(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
#ifndef HAVE_PORTF
|
||||
#define HAVE_PORTF(X) byte TOKENPASTE2(Unique_, __LINE__) __attribute__((unused)) =0
|
||||
#endif
|
||||
|
||||
// Virtualised Motor shield 1-track hardware Interface
|
||||
|
||||
@@ -110,6 +129,9 @@ struct FASTPIN {
|
||||
extern volatile portreg_t shadowPORTA;
|
||||
extern volatile portreg_t shadowPORTB;
|
||||
extern volatile portreg_t shadowPORTC;
|
||||
extern volatile portreg_t shadowPORTD;
|
||||
extern volatile portreg_t shadowPORTE;
|
||||
extern volatile portreg_t shadowPORTF;
|
||||
|
||||
enum class POWERMODE : byte { OFF, ON, OVERLOAD, ALERT };
|
||||
|
||||
@@ -163,16 +185,16 @@ class MotorDriver {
|
||||
unsigned int raw2mA( int raw);
|
||||
unsigned int mA2raw( unsigned int mA);
|
||||
inline bool brakeCanPWM() {
|
||||
#if defined(ARDUINO_ARCH_ESP32) || defined(__arm__)
|
||||
// TODO: on ARM we can use digitalPinHasPWM, and may wish/need to
|
||||
return true;
|
||||
#else
|
||||
#ifdef digitalPinToTimer
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
return (brakePin != UNUSED_PIN); // This was just (true) but we probably do need to check for UNUSED_PIN!
|
||||
#elif defined(__arm__)
|
||||
// On ARM we can use digitalPinHasPWM
|
||||
return ((brakePin!=UNUSED_PIN) && (digitalPinHasPWM(brakePin)));
|
||||
#elif defined(digitalPinToTimer)
|
||||
return ((brakePin!=UNUSED_PIN) && (digitalPinToTimer(brakePin)));
|
||||
#else
|
||||
return (brakePin<14 && brakePin >1);
|
||||
#endif //digitalPinToTimer
|
||||
#endif //ESP32/ARM
|
||||
#endif
|
||||
}
|
||||
inline int getRawCurrentTripValue() {
|
||||
return rawCurrentTripValue;
|
||||
|
@@ -1,6 +1,7 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* © 2023 Colin Murdoch
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of DCC++EX
|
||||
@@ -153,10 +154,16 @@ void TrackManager::setDCCSignal( bool on) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
HAVE_PORTD(shadowPORTD=PORTD);
|
||||
HAVE_PORTE(shadowPORTE=PORTE);
|
||||
HAVE_PORTF(shadowPORTF=PORTF);
|
||||
APPLY_BY_MODE(TRACK_MODE_MAIN,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
HAVE_PORTD(PORTD=shadowPORTD);
|
||||
HAVE_PORTE(PORTE=shadowPORTE);
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
}
|
||||
|
||||
void TrackManager::setCutout( bool on) {
|
||||
@@ -171,10 +178,16 @@ void TrackManager::setPROGSignal( bool on) {
|
||||
HAVE_PORTA(shadowPORTA=PORTA);
|
||||
HAVE_PORTB(shadowPORTB=PORTB);
|
||||
HAVE_PORTC(shadowPORTC=PORTC);
|
||||
HAVE_PORTD(shadowPORTD=PORTD);
|
||||
HAVE_PORTE(shadowPORTE=PORTE);
|
||||
HAVE_PORTF(shadowPORTF=PORTF);
|
||||
APPLY_BY_MODE(TRACK_MODE_PROG,setSignal(on));
|
||||
HAVE_PORTA(PORTA=shadowPORTA);
|
||||
HAVE_PORTB(PORTB=shadowPORTB);
|
||||
HAVE_PORTC(PORTC=shadowPORTC);
|
||||
HAVE_PORTD(PORTD=shadowPORTD);
|
||||
HAVE_PORTE(PORTE=shadowPORTE);
|
||||
HAVE_PORTF(PORTF=shadowPORTF);
|
||||
}
|
||||
|
||||
// setDCSignal(), called from normal context
|
||||
@@ -518,3 +531,10 @@ void TrackManager::setJoin(bool joined) {
|
||||
progTrackSyncMain=joined;
|
||||
if (joinRelay!=UNUSED_PIN) digitalWrite(joinRelay,joined?HIGH:LOW);
|
||||
}
|
||||
|
||||
bool TrackManager::isPowerOn(byte t) {
|
||||
if (track[t]->getPower()!=POWERMODE::ON)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
/*
|
||||
* © 2022 Chris Harlow
|
||||
* © 2022 Harald Barth
|
||||
* © 2023 Colin Murdoch
|
||||
*
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
@@ -77,6 +79,7 @@ class TrackManager {
|
||||
static void reportCurrent(Print* stream);
|
||||
static void reportObsoleteCurrent(Print* stream);
|
||||
static void streamTrackState(Print* stream, byte t);
|
||||
static bool isPowerOn(byte t);
|
||||
|
||||
static int16_t joinRelay;
|
||||
static bool progTrackSyncMain; // true when prog track is a siding switched to main
|
||||
|
268
Turntables.cpp
Normal file
268
Turntables.cpp
Normal file
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
* © 2023 Peter Cole
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#include "defines.h"
|
||||
#include <Arduino.h>
|
||||
#include "Turntables.h"
|
||||
#include "StringFormatter.h"
|
||||
#include "CommandDistributor.h"
|
||||
#include "EXRAIL2.h"
|
||||
#include "DCC.h"
|
||||
|
||||
// No turntable support without HAL
|
||||
#ifndef IO_NO_HAL
|
||||
|
||||
/*
|
||||
* Protected static data
|
||||
*/
|
||||
Turntable *Turntable::_firstTurntable = 0;
|
||||
|
||||
|
||||
/*
|
||||
* Public static data
|
||||
*/
|
||||
int Turntable::turntablelistHash = 0;
|
||||
|
||||
|
||||
/*
|
||||
* Protected static functions
|
||||
*/
|
||||
// Add new turntable to end of list
|
||||
|
||||
void Turntable::add(Turntable *tto) {
|
||||
if (!_firstTurntable) {
|
||||
_firstTurntable = tto;
|
||||
} else {
|
||||
Turntable *ptr = _firstTurntable;
|
||||
for ( ; ptr->_nextTurntable!=0; ptr=ptr->_nextTurntable) {}
|
||||
ptr->_nextTurntable = tto;
|
||||
}
|
||||
turntablelistHash++;
|
||||
}
|
||||
|
||||
// Add a position
|
||||
void Turntable::addPosition(uint8_t idx, uint16_t value, uint16_t angle) {
|
||||
_turntablePositions.insert(idx, value, angle);
|
||||
}
|
||||
|
||||
// Get value for position
|
||||
uint16_t Turntable::getPositionValue(uint8_t position) {
|
||||
TurntablePosition* currentPosition = _turntablePositions.getHead();
|
||||
while (currentPosition) {
|
||||
if (currentPosition->index == position) {
|
||||
return currentPosition->data;
|
||||
}
|
||||
currentPosition = currentPosition->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get value for position
|
||||
uint16_t Turntable::getPositionAngle(uint8_t position) {
|
||||
TurntablePosition* currentPosition = _turntablePositions.getHead();
|
||||
while (currentPosition) {
|
||||
if (currentPosition->index == position) {
|
||||
return currentPosition->angle;
|
||||
}
|
||||
currentPosition = currentPosition->next;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get the count of positions associated with the turntable
|
||||
uint8_t Turntable::getPositionCount() {
|
||||
TurntablePosition* currentPosition = _turntablePositions.getHead();
|
||||
uint8_t count = 0;
|
||||
while (currentPosition) {
|
||||
count++;
|
||||
currentPosition = currentPosition->next;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/*
|
||||
* Public static functions
|
||||
*/
|
||||
// Find turntable from list
|
||||
Turntable *Turntable::get(uint16_t id) {
|
||||
for (Turntable *tto = _firstTurntable; tto != nullptr; tto = tto->_nextTurntable)
|
||||
if (tto->_turntableData.id == id) return tto;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
// Find turntable via Vpin
|
||||
Turntable *Turntable::getByVpin(VPIN vpin) {
|
||||
for (Turntable *tto = _firstTurntable; tto != nullptr; tto = tto->_nextTurntable) {
|
||||
if (tto->isEXTT()) {
|
||||
EXTTTurntable *exttTto = static_cast<EXTTTurntable*>(tto);
|
||||
if (exttTto->getVpin() == vpin) {
|
||||
return tto;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Get the current position for turntable with the specified ID
|
||||
uint8_t Turntable::getPosition(uint16_t id) {
|
||||
Turntable *tto = get(id);
|
||||
if (!tto) return false;
|
||||
return tto->getPosition();
|
||||
}
|
||||
|
||||
// Got the moving state of the specified turntable
|
||||
bool Turntable::ttMoving(uint16_t id) {
|
||||
Turntable *tto = get(id);
|
||||
if (!tto) return false;
|
||||
return tto->isMoving();
|
||||
}
|
||||
|
||||
// Initiate a turntable move
|
||||
bool Turntable::setPosition(uint16_t id, uint8_t position, uint8_t activity) {
|
||||
#if defined(DIAG_IO)
|
||||
DIAG(F("Rotate turntable %d to position %d, activity %d)"), id, position, activity);
|
||||
#endif
|
||||
Turntable *tto = Turntable::get(id);
|
||||
if (!tto) return false;
|
||||
if (tto->isMoving()) return false;
|
||||
bool ok = tto->setPositionInternal(position, activity);
|
||||
|
||||
if (ok) {
|
||||
// We only deal with broadcasts for DCC turntables here, EXTT in the device driver
|
||||
if (!tto->isEXTT()) {
|
||||
CommandDistributor::broadcastTurntable(id, position, false);
|
||||
}
|
||||
// Trigger EXRAIL rotateEvent for both types here if changed
|
||||
#if defined(EXRAIL_ACTIVE)
|
||||
bool rotated = false;
|
||||
if (position != tto->_previousPosition) rotated = true;
|
||||
RMFT2::rotateEvent(id, rotated);
|
||||
#endif
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
||||
/*************************************************************************************
|
||||
* EXTTTurntable - EX-Turntable device.
|
||||
*
|
||||
*************************************************************************************/
|
||||
// Private constructor
|
||||
EXTTTurntable::EXTTTurntable(uint16_t id, VPIN vpin) :
|
||||
Turntable(id, TURNTABLE_EXTT)
|
||||
{
|
||||
_exttTurntableData.vpin = vpin;
|
||||
}
|
||||
|
||||
using DevState = IODevice::DeviceStateEnum;
|
||||
|
||||
// Create function
|
||||
Turntable *EXTTTurntable::create(uint16_t id, VPIN vpin) {
|
||||
#ifndef IO_NO_HAL
|
||||
Turntable *tto = get(id);
|
||||
if (tto) {
|
||||
if (tto->isType(TURNTABLE_EXTT)) {
|
||||
EXTTTurntable *extt = (EXTTTurntable *)tto;
|
||||
extt->_exttTurntableData.vpin = vpin;
|
||||
return tto;
|
||||
}
|
||||
}
|
||||
if (!IODevice::exists(vpin)) return nullptr;
|
||||
if (IODevice::getStatus(vpin) == DevState::DEVSTATE_FAILED) return nullptr;
|
||||
if (Turntable::getByVpin(vpin)) return nullptr;
|
||||
tto = (Turntable *)new EXTTTurntable(id, vpin);
|
||||
DIAG(F("Turntable 0x%x size %d size %d"), tto, sizeof(Turntable), sizeof(struct TurntableData));
|
||||
return tto;
|
||||
#else
|
||||
(void)id;
|
||||
(void)vpin;
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void EXTTTurntable::print(Print *stream) {
|
||||
StringFormatter::send(stream, F("<i %d EXTURNTABLE %d>\n"), _turntableData.id, _exttTurntableData.vpin);
|
||||
}
|
||||
|
||||
// EX-Turntable specific code for moving to the specified position
|
||||
bool EXTTTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
|
||||
#ifndef IO_NO_HAL
|
||||
int16_t value;
|
||||
if (position == 0) {
|
||||
value = 0; // Position 0 is just to send activities
|
||||
} else {
|
||||
if (activity > 1) return false; // If sending a position update, only phase changes valid (0|1)
|
||||
value = getPositionValue(position); // Get position value from position list
|
||||
}
|
||||
if (position > 0 && !value) return false; // Return false if it's not a valid position
|
||||
// Set position via device driver
|
||||
_previousPosition = _turntableData.position;
|
||||
_turntableData.position = position;
|
||||
EXTurntable::writeAnalogue(_exttTurntableData.vpin, value, activity);
|
||||
#else
|
||||
(void)position;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
/*************************************************************************************
|
||||
* DCCTurntable - DCC Turntable device.
|
||||
*
|
||||
*************************************************************************************/
|
||||
// Private constructor
|
||||
DCCTurntable::DCCTurntable(uint16_t id) : Turntable(id, TURNTABLE_DCC) {}
|
||||
|
||||
// Create function
|
||||
Turntable *DCCTurntable::create(uint16_t id) {
|
||||
#ifndef IO_NO_HAL
|
||||
Turntable *tto = get(id);
|
||||
if (!tto) {
|
||||
tto = (Turntable *)new DCCTurntable(id);
|
||||
DIAG(F("Turntable 0x%x size %d size %d"), tto, sizeof(Turntable), sizeof(struct TurntableData));
|
||||
}
|
||||
return tto;
|
||||
#else
|
||||
(void)id;
|
||||
return NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void DCCTurntable::print(Print *stream) {
|
||||
StringFormatter::send(stream, F("<i %d DCCTURNTABLE>\n"), _turntableData.id);
|
||||
}
|
||||
|
||||
// EX-Turntable specific code for moving to the specified position
|
||||
bool DCCTurntable::setPositionInternal(uint8_t position, uint8_t activity) {
|
||||
#ifndef IO_NO_HAL
|
||||
int16_t value = getPositionValue(position);
|
||||
if (position == 0 || !value) return false; // Return false if it's not a valid position
|
||||
// Set position via device driver
|
||||
int16_t addr=value>>3;
|
||||
int16_t subaddr=(value>>1) & 0x03;
|
||||
bool active=value & 0x01;
|
||||
_previousPosition = _turntableData.position;
|
||||
_turntableData.position = position;
|
||||
DCC::setAccessory(addr, subaddr, active);
|
||||
#else
|
||||
(void)position;
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
#endif
|
243
Turntables.h
Normal file
243
Turntables.h
Normal file
@@ -0,0 +1,243 @@
|
||||
/*
|
||||
* © 2023 Peter Cole
|
||||
* All rights reserved.
|
||||
*
|
||||
* This file is part of CommandStation-EX
|
||||
*
|
||||
* This is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* It is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with CommandStation. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef TURNTABLES_H
|
||||
#define TURNTABLES_H
|
||||
|
||||
#include <Arduino.h>
|
||||
#include "IODevice.h"
|
||||
#include "StringFormatter.h"
|
||||
|
||||
// No turntable support without HAL
|
||||
#ifndef IO_NO_HAL
|
||||
|
||||
// Turntable type definitions
|
||||
// EXTT = EX-Turntable
|
||||
// DCC = DCC accessory turntables - to be added later
|
||||
enum {
|
||||
TURNTABLE_EXTT = 0,
|
||||
TURNTABLE_DCC = 1,
|
||||
};
|
||||
|
||||
/*************************************************************************************
|
||||
* Turntable positions.
|
||||
*
|
||||
*************************************************************************************/
|
||||
struct TurntablePosition {
|
||||
uint8_t index;
|
||||
uint16_t data;
|
||||
uint16_t angle;
|
||||
TurntablePosition* next;
|
||||
|
||||
TurntablePosition(uint8_t idx, uint16_t value, uint16_t angle) : index(idx), data(value), angle(angle), next(nullptr) {}
|
||||
};
|
||||
|
||||
class TurntablePositionList {
|
||||
public:
|
||||
TurntablePositionList() : head(nullptr) {}
|
||||
|
||||
void insert(uint8_t idx, uint16_t value, uint16_t angle) {
|
||||
TurntablePosition* newPosition = new TurntablePosition(idx, value, angle);
|
||||
if(!head) {
|
||||
head = newPosition;
|
||||
} else {
|
||||
newPosition->next = head;
|
||||
head = newPosition;
|
||||
}
|
||||
}
|
||||
|
||||
TurntablePosition* getHead() {
|
||||
return head;
|
||||
}
|
||||
|
||||
private:
|
||||
TurntablePosition* head;
|
||||
|
||||
};
|
||||
|
||||
|
||||
/*************************************************************************************
|
||||
* Turntable - Base class for turntables.
|
||||
*
|
||||
*************************************************************************************/
|
||||
|
||||
class Turntable {
|
||||
protected:
|
||||
/*
|
||||
* Object data
|
||||
*/
|
||||
|
||||
// Data common to all turntable types
|
||||
struct TurntableData {
|
||||
union {
|
||||
struct {
|
||||
bool hidden : 1;
|
||||
bool turntableType : 1;
|
||||
uint8_t position : 6; // Allows up to 63 positions including 0/home
|
||||
};
|
||||
uint8_t flags;
|
||||
};
|
||||
uint16_t id;
|
||||
} _turntableData;
|
||||
|
||||
// Pointer to next turntable object
|
||||
Turntable *_nextTurntable = 0;
|
||||
|
||||
// Linked list for positions
|
||||
TurntablePositionList _turntablePositions;
|
||||
|
||||
// Store the previous position to allow checking for changes
|
||||
uint8_t _previousPosition = 0;
|
||||
|
||||
// Store the current state of the turntable
|
||||
bool _isMoving = false;
|
||||
|
||||
/*
|
||||
* Constructor
|
||||
*/
|
||||
Turntable(uint16_t id, uint8_t turntableType) {
|
||||
_turntableData.id = id;
|
||||
_turntableData.turntableType = turntableType;
|
||||
_turntableData.hidden = false;
|
||||
_turntableData.position = 0;
|
||||
add(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Static data
|
||||
*/
|
||||
static Turntable *_firstTurntable;
|
||||
static int _turntablelistHash;
|
||||
|
||||
/*
|
||||
* Virtual functions
|
||||
*/
|
||||
virtual bool setPositionInternal(uint8_t position, uint8_t activity) = 0;
|
||||
|
||||
/*
|
||||
* Static functions
|
||||
*/
|
||||
static void add(Turntable *tto);
|
||||
|
||||
public:
|
||||
static Turntable *get(uint16_t id);
|
||||
static Turntable *getByVpin(VPIN vpin);
|
||||
|
||||
/*
|
||||
* Static data
|
||||
*/
|
||||
static int turntablelistHash;
|
||||
|
||||
/*
|
||||
* Public base class functions
|
||||
*/
|
||||
inline uint8_t getPosition() { return _turntableData.position; }
|
||||
inline bool isHidden() { return _turntableData.hidden; }
|
||||
inline void setHidden(bool h) {_turntableData.hidden=h; }
|
||||
inline bool isType(uint8_t type) { return _turntableData.turntableType == type; }
|
||||
inline bool isEXTT() const { return _turntableData.turntableType == TURNTABLE_EXTT; }
|
||||
inline uint16_t getId() { return _turntableData.id; }
|
||||
inline Turntable *next() { return _nextTurntable; }
|
||||
void printState(Print *stream);
|
||||
void addPosition(uint8_t idx, uint16_t value, uint16_t angle);
|
||||
uint16_t getPositionValue(uint8_t position);
|
||||
uint16_t getPositionAngle(uint8_t position);
|
||||
uint8_t getPositionCount();
|
||||
bool isMoving() { return _isMoving; }
|
||||
void setMoving(bool moving) { _isMoving=moving; }
|
||||
|
||||
/*
|
||||
* Virtual functions
|
||||
*/
|
||||
virtual void print(Print *stream) {
|
||||
(void)stream; // suppress compiler warnings
|
||||
}
|
||||
virtual ~Turntable() {} // Destructor
|
||||
|
||||
|
||||
/*
|
||||
* Public static functions
|
||||
*/
|
||||
inline static bool exists(uint16_t id) { return get(id) != 0; }
|
||||
static bool setPosition(uint16_t id, uint8_t position, uint8_t activity=0);
|
||||
static uint8_t getPosition(uint16_t id);
|
||||
static bool ttMoving(uint16_t id);
|
||||
inline static Turntable *first() { return _firstTurntable; }
|
||||
static bool printAll(Print *stream) {
|
||||
bool gotOne = false;
|
||||
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());
|
||||
}
|
||||
return gotOne;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/*************************************************************************************
|
||||
* EXTTTurntable - EX-Turntable device.
|
||||
*
|
||||
*************************************************************************************/
|
||||
class EXTTTurntable : public Turntable {
|
||||
private:
|
||||
// EXTTTurntableData contains device specific data
|
||||
struct EXTTTurntableData {
|
||||
VPIN vpin;
|
||||
} _exttTurntableData;
|
||||
|
||||
// Constructor
|
||||
EXTTTurntable(uint16_t id, VPIN vpin);
|
||||
|
||||
public:
|
||||
// Create function
|
||||
static Turntable *create(uint16_t id, VPIN vpin);
|
||||
void print(Print *stream) override;
|
||||
VPIN getVpin() const { return _exttTurntableData.vpin; }
|
||||
|
||||
protected:
|
||||
// EX-Turntable specific code for setting position
|
||||
bool setPositionInternal(uint8_t position, uint8_t activity) override;
|
||||
|
||||
};
|
||||
|
||||
/*************************************************************************************
|
||||
* DCCTurntable - DCC accessory Turntable device.
|
||||
*
|
||||
*************************************************************************************/
|
||||
class DCCTurntable : public Turntable {
|
||||
private:
|
||||
// Constructor
|
||||
DCCTurntable(uint16_t id);
|
||||
|
||||
public:
|
||||
// Create function
|
||||
static Turntable *create(uint16_t id);
|
||||
void print(Print *stream) override;
|
||||
|
||||
protected:
|
||||
// DCC specific code for setting position
|
||||
bool setPositionInternal(uint8_t position, uint8_t activity=0) override;
|
||||
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
#endif
|
19
defines.h
19
defines.h
@@ -144,9 +144,9 @@
|
||||
#define DISABLE_EEPROM
|
||||
#endif
|
||||
// STM32 support for native I2C is awaiting development
|
||||
#ifndef I2C_USE_WIRE
|
||||
#define I2C_USE_WIRE
|
||||
#endif
|
||||
// #ifndef I2C_USE_WIRE
|
||||
// #define I2C_USE_WIRE
|
||||
// #endif
|
||||
|
||||
/* TODO when ready
|
||||
#elif defined(ARDUINO_ARCH_RP2040)
|
||||
@@ -213,6 +213,19 @@
|
||||
//
|
||||
#define WIFI_SERIAL_LINK_SPEED 115200
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
//
|
||||
// Define symbol IO_NO_HAL to reduce FLASH footprint when HAL features not required
|
||||
// The HAL is disabled by default on Nano and Uno platforms, because of limited flash space.
|
||||
//
|
||||
#if defined(ARDUINO_AVR_NANO) || defined(ARDUINO_AVR_UNO)
|
||||
#if defined(DISABLE_DIAG) && defined(DISABLE_EEPROM) && defined(DISABLE_PROG)
|
||||
#warning you have sacrificed DIAG for HAL
|
||||
#else
|
||||
#define IO_NO_HAL
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if __has_include ( "myAutomation.h")
|
||||
#if defined(HAS_ENOUGH_MEMORY) || defined(DISABLE_EEPROM) || defined(DISABLE_PROG)
|
||||
#define EXRAIL_ACTIVE
|
||||
|
@@ -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,8 +30,7 @@ include_dir = .
|
||||
|
||||
[env]
|
||||
build_flags = -Wall -Wextra
|
||||
monitor_filters = time
|
||||
; lib_deps = adafruit/Adafruit ST7735 and ST7789 Library @ ^1.10.0
|
||||
; monitor_filters = time
|
||||
|
||||
[env:samd21-dev-usb]
|
||||
platform = atmelsam
|
||||
@@ -60,7 +59,7 @@ framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -std=c++17 ; -DI2C_USE_WIRE -DDIAG_LOOPTIMES -DDIAG_IO
|
||||
build_flags = -std=c++17
|
||||
|
||||
[env:mega2560-debug]
|
||||
platform = atmelavr
|
||||
@@ -72,7 +71,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
|
||||
build_flags = -DDIAG_IO=2 -DDIAG_LOOPTIMES
|
||||
|
||||
[env:mega2560-no-HAL]
|
||||
platform = atmelavr
|
||||
@@ -84,7 +83,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = -DIO_NO_HAL
|
||||
build_flags = -DIO_NO_HAL
|
||||
|
||||
[env:mega2560-I2C-wire]
|
||||
platform = atmelavr
|
||||
@@ -108,7 +107,7 @@ lib_deps =
|
||||
SPI
|
||||
monitor_speed = 115200
|
||||
monitor_echo = yes
|
||||
build_flags = ; -DDIAG_LOOPTIMES
|
||||
build_flags =
|
||||
|
||||
[env:mega328]
|
||||
platform = atmelavr
|
||||
@@ -190,10 +189,75 @@ platform = ststm32
|
||||
board = nucleo_f446re
|
||||
framework = arduino
|
||||
lib_deps = ${env.lib_deps}
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable ; -DDIAG_LOOPTIMES ; -DDIAG_IO
|
||||
build_flags = -std=c++17 -Os -g2 -Wunused-variable
|
||||
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
|
||||
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 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
|
||||
@@ -232,5 +296,4 @@ board = teensy41
|
||||
framework = arduino
|
||||
build_flags = -std=c++17 -Os -g2
|
||||
lib_deps = ${env.lib_deps}
|
||||
lib_ignore =
|
||||
|
||||
lib_ignore =
|
||||
|
23
version.h
23
version.h
@@ -3,10 +3,25 @@
|
||||
|
||||
#include "StringFormatter.h"
|
||||
|
||||
#define VERSION "5.0.4"
|
||||
// 5.0.4 - Bugfix: <JR> misses default roster.
|
||||
// 5.0.3 - Check bad AT firmware version
|
||||
// 5.0.2 - Bugfix: ESP32 30ms off time
|
||||
#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
|
||||
// - ESP32 brakeCanPWM check now detects UNUSED_PIN
|
||||
// - ARM architecture brakeCanPWM now uses digitalPinHasPWM()
|
||||
// - STM32F4xx shadowpin extensions to handle pins on ports D, E and F
|
||||
// 5.1.9 - Fixed IO_PCA9555'h to work with PCA9548 mux, tested OK
|
||||
// 5.1.8 - STM32Fxx ADCee extension to support ADCs #2 and #3
|
||||
// 5.1.7 - Fix turntable broadcasts for non-movement activities and <JP> result
|
||||
// 5.1.6 - STM32F4xx native I2C driver added
|
||||
// 5.1.5 - Added turntable object and EXRAIL commands
|
||||
// - <I ...>, <JO ...>, <JP ...> - turntable commands
|
||||
// - DCC_TURNTABLE, EXTT_TURNTABLE, IFTTPOSITION, ONROTATE, ROTATE, ROTATE_DCC, TT_ADDPOSITION, WAITFORTT EXRAIL
|
||||
// 5.1.4 - Added ONOVERLOAD & AFTEROVERLOAD to EXRAIL
|
||||
// 5.1.3 - Make parser more fool proof
|
||||
// 5.1.2 - Bugfix: ESP32 30ms off time
|
||||
// 5.1.1 - Check bad AT firmware version
|
||||
// - Update IO_PCA9555.h reflecting IO_MCP23017.h changes to support PCA9548 mux
|
||||
// 5.0.1 - Bugfix: execute 30ms off time before rejoin
|
||||
// 5.0.0 - Make 4.2.69 the 5.0.0 release
|
||||
// 4.2.69 - Bugfix: Make <!> work in DC mode
|
||||
|
Reference in New Issue
Block a user