1
0
mirror of https://github.com/DCC-EX/CommandStation-EX.git synced 2024-12-24 13:21:23 +01:00

Update IO_I2CDFPlayer.h

Completed implementation of exposing most relevant DFPlayer commands
Fixed a problem with RX corrupted data from DFPlayer.
Added retry (currently max retry 3) after timeout and recovery
Note: Need to think of a more elegant way to recover from a reset command
This commit is contained in:
kempe63 2023-12-29 22:11:40 +00:00
parent e74c619fac
commit 0feb2c74e7

View File

@ -87,7 +87,7 @@
//#define DIAG_I2CDFplayer //#define DIAG_I2CDFplayer
//#define DIAG_I2CDFplayer_data //#define DIAG_I2CDFplayer_data
//#define DIAG_I2CDFplayer_reg //#define DIAG_I2CDFplayer_reg
#define DIAG_I2CDFplayer_playing //#define DIAG_I2CDFplayer_playing
class I2CDFPlayer : public IODevice { class I2CDFPlayer : public IODevice {
private: private:
@ -98,6 +98,8 @@ private:
unsigned long _timeoutTime; unsigned long _timeoutTime;
uint8_t _recvCMD; // Last received command code byte uint8_t _recvCMD; // Last received command code byte
bool _awaitingResponse = false; bool _awaitingResponse = false;
uint8_t RETRYCOUNT = 0x03;
uint8_t _retryCounter = RETRYCOUNT; // Max retries before timing out
uint8_t _requestedVolumeLevel = MAXVOLUME; uint8_t _requestedVolumeLevel = MAXVOLUME;
uint8_t _currentVolume = MAXVOLUME; uint8_t _currentVolume = MAXVOLUME;
int _requestedSong = -1; // -1=none, 0=stop, >0=file number int _requestedSong = -1; // -1=none, 0=stop, >0=file number
@ -107,7 +109,7 @@ private:
I2CAddress _I2CAddress; I2CAddress _I2CAddress;
I2CRB _rb; I2CRB _rb;
uint8_t _UART_CH; uint8_t _UART_CH;
uint8_t _audioMixer = 0x00; // Default no audio mixer installed uint8_t _audioMixer = 0x01; // Default to output amplifier 1
// Communication parameters for the DFPlayer are fixed at 8 bit, No parity, 1 stopbit // 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 WORD_LEN = 0x03; // Value LCR bit 0,1
uint8_t STOP_BIT = 0x00; // Value LCR bit 2 uint8_t STOP_BIT = 0x00; // Value LCR bit 2
@ -136,11 +138,12 @@ private:
uint8_t _outbuffer [11]; // DFPlayer command is 10 bytes + 1 byte register address & UART 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 uint8_t _inbuffer[10]; // expected DFPlayer return 10 bytes
//unsigned long SC16IS752_XTAL_FREQ = 1843200; // May need to change oscillator frequency to 14.7456Mhz (14745600) to allow for higher baud rates //unsigned long SC16IS752_XTAL_FREQ = 1843200; // May need to change oscillator frequency to 14.7456Mhz (14745600) to allow for higher baud rates
unsigned long SC16IS752_XTAL_FREQ = 14745600; // Support for higher baud rates unsigned long SC16IS752_XTAL_FREQ = 14745600; // Support for higher baud rates
unsigned long test = 0;
public: public:
// Constructor // Constructor
I2CDFPlayer(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t UART_CH, uint8_t AM){ I2CDFPlayer(VPIN firstVpin, int nPins, I2CAddress i2cAddress, uint8_t UART_CH, uint8_t AM){
@ -176,7 +179,7 @@ public:
// Now init DFPlayer // Now init DFPlayer
// Send a query to the device to see if it responds // Send a query to the device to see if it responds
_deviceState = DEVSTATE_INITIALISING; _deviceState = DEVSTATE_INITIALISING;
sendPacket(0x42); sendPacket(0x42,0,0);
_timeoutTime = micros() + 5000000UL; // 5 second timeout _timeoutTime = micros() + 5000000UL; // 5 second timeout
_awaitingResponse = true; _awaitingResponse = true;
} }
@ -188,13 +191,26 @@ public:
if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything if (status == I2C_STATUS_PENDING) return; // Busy, so don't do anything
if (status == I2C_STATUS_OK) { if (status == I2C_STATUS_OK) {
processIncoming(currentMicros); processIncoming(currentMicros);
// Check if a command sent to device has timed out. Allow 0.5 second for response // Check if a command sent to device has timed out. Allow 0.5 second for response
if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) { // added retry counter, sometimes we do not sent keep alive due to other commands sent to DFPlayer
DIAG(F("I2CDFPlayer:%s, DFPlayer not responding on UART channel: 0x%x"), _I2CAddress.toString(), _UART_CH); if (_awaitingResponse && (int32_t)(currentMicros - _timeoutTime) > 0) { // timeout triggered
_deviceState = DEVSTATE_FAILED; if(_retryCounter == 0){ // retry counter out of luck, must take the device to failed state
_awaitingResponse = false; DIAG(F("I2CDFPlayer:%s, DFPlayer not responding on UART channel: 0x%x"), _I2CAddress.toString(), _UART_CH);
_playing = false; _deviceState = DEVSTATE_FAILED;
} _awaitingResponse = false;
_playing = false;
_retryCounter = RETRYCOUNT;
} else { // timeout and retry protection and recovery of corrupt data frames from DFPlayer
DIAG(F("I2CDFPlayer: %s, DFPlayer timout, retry counter: %d on UART channel: 0x%x"), _I2CAddress.toString(), _retryCounter, _UART_CH);
_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
_resetCmd = true; // queue a DFPlayer reset
_currentVolume = MAXVOLUME; // Resetting the DFPlayer makes the volume go to default i.e. MAXVOLUME
//sendPacket(0x0C,0,0); // Reset DFPlayer
resetRX_fifo(); // reset the RX fifo as it maybe poisoned
}
}
} }
status = _rb.status; status = _rb.status;
@ -232,13 +248,6 @@ public:
return; // No data or not enough data in rx fifo, check again next time around return; // No data or not enough data in rx fifo, check again next time around
} }
/*
#ifdef DIAG_I2CDFplayer
if (FIFO_RX_LEVEL > 10) {
DIAG(F("I2CDFPlayer: %s FIFO_RX_LEVEL: %d"),_I2CAddress.toString(), FIFO_RX_LEVEL);
}
#endif
*/
bool ok = false; bool ok = false;
//DIAG(F("I2CDFPlayer: RX_BUFFER: %d"), RX_BUFFER); //DIAG(F("I2CDFPlayer: RX_BUFFER: %d"), RX_BUFFER);
@ -260,7 +269,7 @@ public:
break; break;
case 6: case 6:
switch (_recvCMD) { switch (_recvCMD) {
DIAG(F("I2CDFPlayer: %s, _recvCMD: 0x%x _awaitingResponse: 0x0%x"),_I2CAddress.toString(), _recvCMD, _awaitingResponse); //DIAG(F("I2CDFPlayer: %s, _recvCMD: 0x%x _awaitingResponse: 0x0%x"),_I2CAddress.toString(), _recvCMD, _awaitingResponse);
case 0x42: case 0x42:
// Response to status query // Response to status query
_playing = (c != 0); _playing = (c != 0);
@ -299,6 +308,7 @@ public:
case 9: case 9:
if (c==0xef) { if (c==0xef) {
// Message finished // Message finished
_retryCounter = RETRYCOUNT; // reset the retry counter as we have received a valid packet
} }
break; break;
default: default:
@ -315,7 +325,6 @@ public:
RX_BUFFER = 0; //Set to 0, we'll read a new RX FIFO level again RX_BUFFER = 0; //Set to 0, we'll read a new RX FIFO level again
} }
//sendPacket(0x1A,0x00,0x01); //Enable DAC
// Send any commands that need to be sent // Send any commands that need to be sent
void processOutgoing(unsigned long currentMicros) { void processOutgoing(unsigned long currentMicros) {
@ -323,8 +332,15 @@ public:
// execute one. Testing has indicated that a delay of 100ms or more is required // execute one. Testing has indicated that a delay of 100ms or more is required
// between successive commands to get reliable operation. // 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 100ms has elapsed since the last thing sent, then check if there's some output to do.
if (((int32_t)currentMicros - _commandSendTime) > 100000) { if (((int32_t)currentMicros - _commandSendTime) > 100000) {
if (_currentVolume > _requestedVolumeLevel) { if ( _resetCmd == true){
sendPacket(0x0C,0,0);
_resetCmd = false;
return; // after reset do not execute more commands, wait for the next time giving the DFPlayer time to reset
// A more saver/elegant way is to wait for the 'SD card online' packet (7E FF 06 3F 00 00 02 xx xx EF)
// this indicate that the DFPlayer is ready.This may take between 500ms and 1500ms depending on the
// number of tracks on the SD card
} else if (_currentVolume > _requestedVolumeLevel) {
// Change volume before changing song if volume is reducing. // Change volume before changing song if volume is reducing.
_currentVolume = _requestedVolumeLevel; _currentVolume = _requestedVolumeLevel;
sendPacket(0x06, 0x00, _currentVolume); sendPacket(0x06, 0x00, _currentVolume);
@ -332,9 +348,9 @@ public:
// Change song // Change song
if (_requestedSong != -1) { if (_requestedSong != -1) {
#ifdef DIAG_I2CDFplayer_playing #ifdef DIAG_I2CDFplayer_playing
DIAG(F("I2CDFPlayer: _requestedVolumeLevel: %u, _requestedSong: %u, _playCmd: 0x%x"), _requestedVolumeLevel, _requestedSong, _playCmd); DIAG(F("I2CDFPlayer: _requestedVolumeLevel: %u, _requestedSong: %u, _currentFolder: %u _playCmd: 0x%x"), _requestedVolumeLevel, _requestedSong, _currentFolder, _playCmd);
#endif #endif
sendPacket(0x0F, 0x01, _requestedSong); // audio file in folder 01 sendPacket(0x0F, _currentFolder, _requestedSong); // audio file in folder
_requestedSong = -1; _requestedSong = -1;
_playCmd = false; _playCmd = false;
} }
@ -348,6 +364,9 @@ public:
_repeat = false; // reset repeat _repeat = false; // reset repeat
_stopplayCmd = false; _stopplayCmd = false;
} else if (_folderCmd == true) { } else if (_folderCmd == true) {
#ifdef DIAG_I2CDFplayer_playing
DIAG(F("I2CDFPlayer: Folder: _folderCmd: 0x%x, _requestedFolder: %d"), _stopplayCmd, _requestedFolder);
#endif
if (_currentFolder != _requestedFolder){ if (_currentFolder != _requestedFolder){
_currentFolder = _requestedFolder; _currentFolder = _requestedFolder;
} }
@ -377,9 +396,6 @@ public:
sendPacket(0x07,0x00,_currentEQvalue); sendPacket(0x07,0x00,_currentEQvalue);
} }
_eqCmd = false; _eqCmd = false;
} else if ( _resetCmd == true){
sendPacket(0x0C,0,0);
_resetCmd = false;
} else if (_currentVolume < _requestedVolumeLevel) { } else if (_currentVolume < _requestedVolumeLevel) {
// Change volume after changing song if volume is increasing. // Change volume after changing song if volume is increasing.
_currentVolume = _requestedVolumeLevel; _currentVolume = _requestedVolumeLevel;
@ -387,8 +403,14 @@ public:
} else if ((int32_t)currentMicros - _commandSendTime > 1000000) { } else if ((int32_t)currentMicros - _commandSendTime > 1000000) {
// Poll device every second that other commands aren't being sent, // Poll device every second that other commands aren't being sent,
// to check if it's still connected and responding. // to check if it's still connected and responding.
sendPacket(0x42); #ifdef DIAG_I2CDFplayer_playing
DIAG(F("I2CDFPlayer: Send keepalive") );
#endif
sendPacket(0x42,0,0);
if (!_awaitingResponse) { 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 _timeoutTime = currentMicros + 5000000UL; // Timeout if no response within 5 seconds
_awaitingResponse = true; _awaitingResponse = true;
} }
@ -457,15 +479,13 @@ public:
// DFPlayerCmd = cmd; // DFPlayerCmd = cmd;
// break; // break;
case PLAY: case PLAY:
_playCmd = true; _playCmd = true;
//DFPlayerCmd = cmd;
_requestedSong = value; _requestedSong = value;
_requestedVolumeLevel = volume; _requestedVolumeLevel = volume;
_playing = true; _playing = true;
break; break;
case VOL: case VOL:
_volCmd = true; _volCmd = true;
//DFPlayerCmd = cmd;
_requestedVolumeLevel = volume; _requestedVolumeLevel = volume;
break; break;
case FOLDER: case FOLDER:
@ -479,7 +499,7 @@ public:
case REPEATPLAY: // Need to check if _repeat == true, if so do nothing case REPEATPLAY: // Need to check if _repeat == true, if so do nothing
if (_repeat == false) { if (_repeat == false) {
#ifdef DIAG_I2CDFplayer_playing #ifdef DIAG_I2CDFplayer_playing
DIAG(F("I2CDFPlayer: WriteAnalog Repeat: _repeat: 0x0%x, value: %d _repeatCmd: 0x%x"), _repeat, value, _repeatCmd); DIAG(F("I2CDFPlayer: WriteAnalog Repeat: _repeat: 0x0%x, value: %d _repeatCmd: 0x%x"), _repeat, value, _repeatCmd);
#endif #endif
_repeatCmd = true; _repeatCmd = true;
_requestedSong = value; _requestedSong = value;
@ -488,31 +508,27 @@ public:
} }
break; break;
case STOPPLAY: case STOPPLAY:
_stopplayCmd = true; _stopplayCmd = true;
//DFPlayerCmd = cmd;
break; break;
case EQ: case EQ:
//DIAG(F("I2CDFPlayer: WriteAnalog EQ: cmd: 0x%x, EQ value: 0x%x"), cmd, volume); #ifdef DIAG_I2CDFplayer_playing
_eqCmd = true; DIAG(F("I2CDFPlayer: WriteAnalog EQ: cmd: 0x%x, EQ value: 0x%x"), cmd, volume);
//DFPlayerCmd = cmd; #endif
_eqCmd = true;
if (volume <= NORMAL) { // to keep backward compatibility the volume parameter is used for values of the EQ cmd if (volume <= NORMAL) { // to keep backward compatibility the volume parameter is used for values of the EQ cmd
_requestedEQValue = NORMAL; _requestedEQValue = NORMAL;
//DFPlayerValue = NONE;
} else if (volume <= 0x05) { // Validate EQ parameters } else if (volume <= 0x05) { // Validate EQ parameters
_requestedEQValue = volume; _requestedEQValue = volume;
//DFPlayerValue = volume;
} }
break; break;
case RESET: case RESET:
_resetCmd = true; _resetCmd = true;
//DFPlayerCmd = cmd;
break; break;
case DACON: // Works, but without the DACOFF command limited value, except when not relying on DFPlayer default to turn the DAC on case DACON: // Works, but without the DACOFF command limited value, except when not relying on DFPlayer default to turn the DAC on
//DIAG(F("I2CDFPlayer: WrtieAnalog DACON: cmd: 0x%x"), cmd); #ifdef DIAG_I2CDFplayer_playing
DIAG(F("I2CDFPlayer: WrtieAnalog DACON: cmd: 0x%x"), cmd);
#endif
_daconCmd = true; _daconCmd = true;
//DFPlayerCmd = 0x1A;
//DFPlayerValue = 0x00;
break; break;
default: default:
break; break;
@ -666,12 +682,27 @@ private:
UART_ReadRegister(REG_RXLV); UART_ReadRegister(REG_RXLV);
FIFO_RX_LEVEL = _inbuffer[0]; FIFO_RX_LEVEL = _inbuffer[0];
#ifdef DIAG_I2CDFplayer #ifdef DIAG_I2CDFplayer
if (FIFO_RX_LEVEL > 0){ //if (FIFO_RX_LEVEL > 0){
// DIAG(F("SC16IS752: At I2C: %s, UART channel: 0x%x, FIFO_RX_LEVEL: 0d%d"), _I2CAddress.toString(), _UART_CH, _inbuffer[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 #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);
}
// Read the Tranmit FIFO Level register (TXLVL), return a single unsigned integer // 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 // of nr characters free in the TX FIFO, bit 6:0, 7 not used, set to zero
// value from 0 (0x00) to 64 (0x40) // value from 0 (0x00) to 64 (0x40)