SoftwareSerial Doesn't Allow Multiple Instances - Workaround?

EDIT: This title is incorrect, but I don't want to change it as it will mess with the search engine indexing. There is no problem with SoftwareSerial instances, it's multiple instances of DFRobotDFPlayerMini I'm having issues with.

I (think I) know what the problem is I just have no clue how to fix it in this environment. :smiley: As the title says, I don't think you can do multiple instances of SoftwareSerial. The example under listen() in this document seems to indicate you can, so maybe I'm doing something wrong.

I have three MP3-TF-16P players. Individually, they all work fine, I will need them to work concurrently, the sounds may overlap (I have a mixer circuit to apply.) The problem is instances of SoftwareSerial "clobber" previous instances, causing none of them to work.

  • Arduino Uno R3
  • Arduino powered by USB, players by breadboard power source (5V)

In the Fritzing below, only one player is connected, and this does work. This is "method two" of connecting the MiniPlayer, 1K resistor only on the RX - previously I had set up a voltage divider and wanted to simplify to debug. There's a bit of confusion about voltage on these - the VCC can easily handle 5V, it's the RX and other pins that require lower voltage.

Note the comment at line 32 in the code below - when I uncomment any of the other instances of SoftwareSerial on initial upload the sound plays once on player #1 but does not loop. I can fully connect all of the players and leave it as posted, the first one is fine. As soon as I uncomment the line that causes subsequent players to begin(), nothing works.

Is there a workaround for this?

Already tried making library copies, it's a mess.

I am also looking at programming the AD1/2 keys but I won't use buttons . . . I will need to figure out how to control it from the arduino (if it will even work LOL.) I haven't gotten there yet.


#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>

const char potPin = A5;

// Use pins 2 and 3 to communicate with DFPlayer Mini
static const uint8_t PIN_MP3_TX = 2;  // Connects to module's RX
static const uint8_t PIN_MP3_RX = 3;  // Connects to module's TX
static const uint8_t PIN_MP4_TX = 4;  // Connects to module's RX
static const uint8_t PIN_MP4_RX = 5;  // Connects to module's TX
static const uint8_t PIN_MP5_TX = 6;  // Connects to module's RX
static const uint8_t PIN_MP5_RX = 7;  // Connects to module's TX

SoftwareSerial softwareSerial(PIN_MP3_RX, PIN_MP3_TX);
SoftwareSerial softwareSerial1(PIN_MP4_TX, PIN_MP4_TX);
SoftwareSerial softwareSerial2(PIN_MP5_TX, PIN_MP5_TX);

// Create the Player object
DFRobotDFPlayerMini player;
DFRobotDFPlayerMini player1;
DFRobotDFPlayerMini player2;


void setup()
{

  // Init USB serial port for debugging
  Serial.begin(9600);
  // Init serial port for DFPlayer Mini
  softwareSerial.begin(9600);
  // Uncomment either of these lines and player 1 will play once but not loop.
  //softwareSerial1.begin(9600);
  //softwareSerial2.begin(9600);

  // Start communication with DFPlayer Mini
  if (player.begin(softwareSerial)) {
    Serial.println("OK");



  } else {
    Serial.println("Connecting to DFPlayer Mini failed!");
  }
}

void loop()
{
  setVolume();
  playFile();
  delay(4000);
}

void setVolume()
{
  int potVal = analogRead(potPin);
  int vol = map(potVal, 0, 1023, 0, 30);
  player.volume(vol);
  //player1.volume(vol);
  //player2.volume(vol);
}

void playFile()
{ Serial.println('play file');
  player.playMp3Folder(1);
  //delay(2000);
  //player1.playMp3Folder(1);
  //delay(2000);
  //player2.playMp3Folder(1);
}

Use a board with more hardware UARTs such as a Mega

Perhaps I should rephrase the question. Is there a workaround to the software limitation of SoftwareSerial?

I've tested single instances on the other pins 4,5, 6,7, they appear to function fine as serial ports individually. It's only when I attempt to create new instances of the class that it borks.

I have been coding for over 27 years and could probably rewrite it but I'm hoping not to have to do that LOL

1 Like

SoftwareSerial does allow multiple instances, but only one can be listening at a time. Always check the docs.

Given that, and the severe limitations on Baud rate, use hardware serial instead.

Almost all modern MCUs have more than one hardware serial port, and you are always better off with those.

1 Like

I will look deeper, thank you . . . this makes perfect sense:

  • Serial listens on 2/3
  • Serial1 now tells Arduino to listen on 4/5
  • Serial2 now tells Arduino to listen on 6/7

So in my sketch, it's going to only listen on the last instance, correct?

Haven't looked at hardware serial yet. Aside, I really don't need the TX from the module, I just need the RX (TX from Arduino.) It just needs to play, delay, play next, delay, play next and doesn't care if the previous is still playing (hence, the mixer.)

I'm considering using the AD keys functions if I can figure out how to program them, all I need is play, there will be a single file on each card.

Edit: before giving up on SoftwareSerial I'm going to take a stab at this, where it tells the Arduino which ports to listen() to.

1 Like

May be you can assume the modules are working fine and rewrite the DFRobotDFPlayerMini library so that it does not listen back

Just send the right binary commands for what you need and trust all goes well?

(I agree with the other posters, a MEGA would be more suitable)

SoftwareSerial is a workaround !

2 Likes

Yes that is correct. Only the last instance(object) that is told to listen() is the active one. You can not talk to the others in that moment either as far as i know, i think they share the tx buffer as well as the rx buffer.
You should of course use different names for the objects.

I think in this case since you actually only send short commands and do not expect much data in return, it may actually work. But if you keep having issues, switch to a board with multiple UARTs, which is a sure solution.

1 Like

the Rx buffer is shared (static) indeed

The Tx buffer is managed differently, SoftwareSerial inherits from Stream (which inherits from Print) and just implement write() to send one byte out. There is no Tx buffering at the library's level

1 Like

Take a look at this board, it may help. It is an I2C to RS232 board containing a duel USART. SC16IS752 I2C-SPI to Dual Channel UART Converter Module

1 Like

Thank you, I spent last night going through data sheets, understanding these better . . . the truth is, I just need to send it a play command, then switch to the other two serial ports and hit play. I don't really need to read anything from the TX port of the player.

Does anyone know if it would be a bad idea to send a high 3.3V signal to ADKEY1? I know it sends "play" if grounded with no resistor, that is really all I need for this project. If it could work I wouldn't even need to use serial ports at all. I haven't tried it because so far I haven't managed to burn out anything on these boards. :smiley:

After more testing and fiddling about last night my initial assessment was totally incorrect. It's not SoftwareSerial that's giving me grief, it's the DFRobotDFPlayerMini class that is acting up. I think it's not allowing a second instance, or something, when I attempt to instantiate a new player everything just hangs up, does nothing, can't even get serial output.

If I can't send to ADKEY1, my thought was to only instantiate a single DFRobotDFPlayerMini instance and "tell" the program to write to the different ports as needed by switching to the different SoftwareSerial instances. Most of the samples use listen(), I won't need listen - I just need to write().

They are, I run them in standalone mode and they're fine. I can program a single one to play, it's attempting multiple ones that's acting up.

That is exactly what I was considering, this guy is onto something and looking at his code against the data sheet I can see how I could do that. He is using a different board and using the native RX/TX of the Arduino, but I could make that work. I only need to compile a single command and send it to the RX of the player, that's all I need.

1 Like

You may have already seen these or similar, but here's a few images I made while playing with the 'resistor only' method a year or so ago.

Not sure if it's been corrected since, but at that time the specs had the VoI Up/Vol Down reversed.

1 Like

I believe you don't need a DFRobotDFPlayerMini library at all. The only thing it doing is present you a human-friendly interface for binary DFPlayer commands. You could send them without library.

1 Like

Is it possible to change SoftwareSerial's RXpin and TXpin "on the fly"?

not easily but you can easily call end() and begin() again with the new pins. it will reset the Rx buffer

Right, but that is with physical buttons. I need to send a command from the code, which is why I asked if anyone knew if I could send a 3.3V or LOW signal to a pin connected to ADKEY1. That would solve everything but I'm "pretty sure" the answer is no, or it would do nothing. I don't want to blow up a board so I still haven't tried. :smiley:

Right, I tried that using the methods mentioned here but failed. I checked his values against the data sheet, they appear correct, I'm doing something wrong. Haven't given up on it yet. :smiley:

You know, I've been up and down tons of docs here, here, and here (among others) and one of the frustrating things is don't see a functional reference anywhere, which is abundant in most class object downloads. Are you sayng begin() and end() method on SoftwareSerial, not the player? I see an end() method in SoftwareSerial, not the player.

This could be a good clue - the docs on SoftwareSerial say to switch to other SS objects call the listen() method, but I don't need to listen, I only need to execute a send command. I'd like to eliminate the listening entirely and free up 3 Arduino pins in the final project.

Thanks to everyone, more stuff to try!

I'd been looking into this and it was pumping mud.
I will try with the begin() and end(). I was only changing rxPin, txPin.

I believe you need a transistor buffer ─ NPN; Collector to ADKey, Emitter to GND, and 1K between Arduino output and Base.

1 Like

@J-M-L +
I don't see anything on 3 or 7 --

#include <SoftwareSerial.h>

byte rxPin;
byte txPin;

SoftwareSerial onthefly (rxPin, txPin);

void setup() 
{
  pinMode(13, OUTPUT);
  digitalWrite(13, LOW);
  //onthefly.begin(19200);
}

void loop() 
{
  digitalWrite(13, HIGH);
  rxPin = 2;
  txPin = 3;
  onthefly.begin(19200);
  onthefly.println("OOO");
  onthefly.end();
  delay(1000);

  digitalWrite(13, LOW);
  rxPin = 6;
  txPin = 7;
  onthefly.begin(19200);
  onthefly.println("OOO");
  onthefly.end();
  delay(1000);
}
1 Like

I was talking indeed about the SoftwareSerial library

have you tried not connecting the Rx pin on the arduino, just the Tx pin (so you don't get pinChange interrupts) and use your instances to send binary commands based on the protocol described in the documentation

may be try something like that:

#include <SoftwareSerial.h>

static const uint8_t serialPlayer1_TX = 2;  // Connects to module's RX
static const uint8_t serialPlayer1_RX = 3;  // don't connect, don't use for something else
static const uint8_t serialPlayer2_TX = 4;  // Connects to module's RX
static const uint8_t serialPlayer2_RX = 5;  // don't connect, don't use for something else
static const uint8_t serialPlayer3_TX = 6;  // Connects to module's RX
static const uint8_t serialPlayer3_RX = 7;  // don't connect, don't use for something else

SoftwareSerial serialPlayer1(serialPlayer1_RX, serialPlayer1_TX);
SoftwareSerial serialPlayer2(serialPlayer2_TX, serialPlayer2_TX);
SoftwareSerial serialPlayer3(serialPlayer3_TX, serialPlayer3_TX);


void sendCommmand(Stream& dfplayer, uint8_t cmd, uint16_t param) {
  uint8_t paramMSB = param >> 8;
  uint8_t paramLSB = param;
  uint16_t checksum = 1u + (~(0xFF + 0x06 + cmd  + paramMSB + paramLSB));

  dfplayer.write((uint8_t) 0x7E);             // start byte
  dfplayer.write((uint8_t) 0xFF);             // version
  dfplayer.write((uint8_t) 0x06);             // length
  dfplayer.write(cmd);                        // Command
  dfplayer.write((uint8_t) 0x00);             // No feedback
  dfplayer.write((uint8_t) paramMSB);         // param MSB
  dfplayer.write((uint8_t) paramLSB);         // param LSB
  dfplayer.write((uint8_t) (checksum >> 8));  // checksum MSB
  dfplayer.write((uint8_t) checksum);         // checksum LSB
  dfplayer.flush();
}

void play(Stream& dfplayer, uint16_t trackIndex) {
  sendCommmand(dfplayer, 0x03, trackIndex); // 0x03 is the play command
}

void stop(Stream& dfplayer) {
  sendCommmand(dfplayer, 0x16, 0); // 0x16 is the stop command
}

void setup() {
  serialPlayer1.begin(9600);
  serialPlayer2.begin(9600);
  serialPlayer3.begin(9600);

  // try to play song 1
  play(serialPlayer1, 1);
  play(serialPlayer2, 1);
  play(serialPlayer3, 1);

  // wait a bit to hear the song
  delay(3000);

  // stop the song
  stop(serialPlayer1);
  stop(serialPlayer2);
  stop(serialPlayer3);
}

void loop() {}