ESP32- Help with Software Serial

Keep in mind a few points

  • There are only a handful of these diagrams

  • Each Diagram pertains to a different board

  • Some of the diagrams do not pertain to any boards and serve only as a rough guide

  • A lot of times the pinouts are not as stated in the diagram

Just consider those points, Unlike Arduino where the diagram is identical to the board
that you get

1 Like

Yeah i had the same, the pinout diagrams i use



and my confusion was compounded by my initial attempt

Serial1.begin(115200, SERIAL_8N1, 12, 14);

which also doesn't work ! and then in trying to receive over GPIO3 as RX for UART0, which didn't work, then i also figured UART2 was the only useable one, since i got that one to work.

let's just remember RE Pin 6
that Profanity is not allowed on the forum...... :stuck_out_tongue:
2023-09-03 18_52_47-ESP32- Help with Software Serial - Using Arduino _ Programming Questions - Ardui

Mine ESP32 Dev Module (Fig-1 post #40) is of 30-pin and NOT 38-pin as yours one. Moreover, at the back of my board, there is a laser engraving: ESP32S.

Yes i was just looking at the differences, actually the pins connected to the flash are not exposed, there are 2 GND pins whereas on the 38 pin there are 3, and GPIO 0 (boot) is not exposed (except for the switch of course)

This is the pin mapping dagram (Fig-1) that I try to make among 48-pin ESP32 MCU, 38-pin RF Sheld, and 30-pin ESP32 Dev Module.


Figure-1:

@Deva_Rishi
I have something to know (at the context of performing that UART1 experiment) which is posted below Fig-1 (part of Data Sheets):


Figure-1:

Pin-34 of ESP32 MCU has four alternate functions:

GPIO5
HS1_DATA6
VSPICS0
EMAC_RX_CLK

According to your proposed code Serial1.begin(115200, SERIAL_8N1, 5, 18);, the U1RXD pin/line will be connected with Pin-34/GPIO5 and U1TXD pin/line will be connected with Pin-35/GPIO18 of ESP32 MCU (Fig-1 above). Have I understood you correctly?

I have aslo another concern:
Pin-28/GPIO9 shows U1RXD as an alternate function; but, Pin-34/GPIO5 does not show U1TXD as an alternate function.

Yes, that is true, but i picked those pins more or less at random. I experienced issues with GPIO 14, 13 & 12, I was planning to use just 3 UARTs but not use SPI at all, so i did not have to re-assign anything as a result. I just picked the 2 pins next to the default pins for UART2, tested it and it worked.
GPIO 25, 26, 27, 33 & 32 are also an option, but 34, 35, 36 & 39 are only usable as inputs, should test those as RX pins and see how that goes.

As i've shown in post #36, you can also assign GPIO 3 & 1, which are the default pins for UART0 and are connected to the USB to TTL. You will in that case get the RX & TX to the USB. That of course will rarely serve any purpose other than to show exactly this, that it can be done.
There are more peripherals on an ESP32 than physical useable pins. I look at what i want to use and then see how i can pick the pins accordingly.

don't know what to say about that.

As for the assigning of custom pins to peripherals, i read somewhere that it is done with the setting of hall-locks, and i have also read somewhere that there is some impact on CPU processing, but again, i don't know the details.
If the default pins of a peripheral work for me, i leave them as they are, otherwise i will look for other pins that work for me.

1 Like

Congratulations! It also works for me!!

I have performed the experiment making direct connection of UART1(5, 18) of ESP32-1 and UART2(16, 17) of ESP32-2. UART1 sends and receives data correctly to/from UART2. However, there seems to be some issues (time delay insertion and full-duplex communication), which I will let you know after some more investigations.

TX1/RX1 Sketch:

void setup()
{
  Serial.begin(115200);
  Serial1.begin(115200, SERIAL_8N1, 5, 18);
}

void loop()
{
  byte n = Serial1.available();
  {
    if (n != 0)
    {
      char y = Serial1.read();
      Serial.print(y);
    }
  }
  Serial1.println("Hello!");
  vTaskDelay(1000);//delay(1000);
}

TX2/RX2 Sketch:

void setup()
{
  Serial.begin(115200);
  Serial2.begin(115200);
}

void loop()
{
  byte n = Serial2.available();
  {
    if (n != 0)
    {
      char y = Serial2.read();
      Serial.print(y);
    }
  }
  Serial2.println("Forum");
  //vTaskDelay(1000); delay(1000);
}
1 Like

Data sheet shows that GPIO25, by default, is a GPIO line. We want to use as RX line, which is possible. My concern is: should GPIO25 be unplugged first form IO Port before executing Serial1.begin();? Or will it be automatically unplugged when Serial1.begin(); is executed, which is observed in ATmega328P?

One more point:
Physical PIn-14/GPIO25 of ESP32 has not included RX/TX as an alternate function. Does it mean that Espressif is not recommending to use it as RX/TX line though possible.

1 Like

I am not surprised. As stated before i have a triple hwUART port working on a MIDI-merger, all 3 ports work, though also for UART0 i had to choose different pins, since the CH340g was disturbing the signal on the RX-pin.
I did however had to make a small modification to SerialMIDI.h

void begin()
	{
        // Initialise the Serial port
		#if defined (ARDUINO_ARCH_ESP32) || defined(ESP32)
		// easiest is to manually call begin() for an ESP32 and then include the RX & TX pins
		// Serial1 default pins can not be used, but i use rx=5 & tx=18
		// Serial defaultpins are connected to USB, RX can not be used, may as well just use rx=15 & tx=4
		// Serial2 default pins are fine to use, but may as well be declared rx=16 & tx=17
		#elif defined(AVR_CAKE)
            mSerial. template open<Settings::BaudRate>();
        #else
            mSerial.begin(Settings::BaudRate);
        #endif
	}

Basically preventing begin() being called on the selected Serial object if the board is an ESP32.
So that can be done manually before hand and not be re-done when the MIDI library call is made.

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <WiFiUdp.h>
#include <ArduinoOTA.h>
#include <MIDI.h>
#include <PatchDNSServer.h>

#define LED 2
#define DEBUG_SERIAL 0
#define DNS_PORT 53

#define FORMAT SERIAL_8N1
#define BAUD 31250

#define RXD2 16
#define TXD2 17
#define RXD0 15
#define TXD0 4
#define RXD1 5
#define TXD1 18

MIDI_CREATE_INSTANCE(HardwareSerial, Serial, midiA);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiB);
MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, midiC);


decltype(&midiA) ports[] = {&midiA, &midiB, &midiC };
decltype(&Serial) uarts[] = {&Serial, &Serial1, &Serial2};
int RXpins[] = {RXD0, RXD1, RXD2};
int TXpins[] = {TXD0, TXD1, TXD2};
uint8_t nrPorts = 3;

WebServer server(80);
DNSServer dnsServer;

const char* ssid = "LAB";
const char* password = "Page22Air";
const char *apname = "Midi-Merger";
const char *appass = "Evolution";

const char   // like this these lines are statically declared and const, so we can't change them at all
*pageheader = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">",
 *htmlhead = "<html><head><title>Midi-Merger</title><meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\" ></head>",
  *bodystyle = "<body style=\"color: wheat; background-color: teal; font-size: 12pt; font-family: sans-serif;\">",
   *accessIP = "http://192.168.4.1",
    *htmlclose = "</body></html>";

String webIP = "/";

void setup() {
  pinMode(LED, OUTPUT);
  digitalWrite(LED, LOW);


  WiFiSetup();
  OTAUpdateSetup();

  for (uint8_t i = 0; i < nrPorts; i++) {
    uarts[i]->begin(BAUD, FORMAT, RXpins[i], TXpins[i]); // call Serial begin
    ports[i]->begin(MIDI_CHANNEL_OMNI);  // call midi begin
  }
}

void loop() {
  ArduinoOTA.handle();
  server.handleClient();
  dnsServer.processNextRequest();
  MiDiHandle();
  Blink();
}

void Blink() {
  if ((millis() % 1000) / 500) {
    digitalWrite(LED, LOW);
  }
  else {
    digitalWrite(LED, HIGH);
  }
}

void MiDiHandle() {
  for (uint8_t in = 0; in < nrPorts; in++) {
    if (ports[in]->read()) {
      for (uint8_t out = 0; out < nrPorts; out++) {
        if (ports[in]->getType() == midi::Clock) {
          ports[out]->sendRealTime(midi::Clock);
        }
        else if (ports[in]->getType() == midi::Start) {
          ports[out]->sendRealTime(midi::Start);
        }
        else if (ports[in]->getType() == midi::Stop) {
          ports[out]->sendRealTime(midi::Stop);
        }
        else if (ports[in]->getType() == midi::Continue) {
          ports[out]->sendRealTime(midi::Continue);
        }
        else {
          ports[out]->send(ports[in]->getType(),
                           ports[in]->getData1(),
                           ports[in]->getData2(),
                           ports[in]->getChannel());
        }
      }
    }
  }
}




void handleRoot() {
  String s = "";
  s += pageheader;
  s += htmlhead;
  s += bodystyle;
  s += "<h1>Welcome to the Triple Midi Merger</h1>";
  s += "<pre>This unit has 3 input and 3 output ports, and you can<br>";
  s += "configure how the data from the input ports gets relayed to<br>";
  s += "the indivdual outputs.<br>";
  s += "By default, all input gets relayed to all outputs</pre>";
  s += htmlclose;
  yield();
  server.send(200, "text/html", s); //Send web page
}

void OTAUpdateSetup() {
  ArduinoOTA.setHostname("MiDiMerger");
  ArduinoOTA.setPassword("admin");

  ArduinoOTA
  .onStart([]() {
    String type;
    if (ArduinoOTA.getCommand() == U_FLASH)
      type = "sketch";
    else // U_SPIFFS
      type = "filesystem";

    // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
    if (DEBUG_SERIAL) Serial.println("Start updating " + type);
  })
  .onEnd([]() {
    if (DEBUG_SERIAL) Serial.println("\nEnd");
  })
  .onProgress([](unsigned int progress, unsigned int total) {
    if (DEBUG_SERIAL) Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
  })
  .onError([](ota_error_t error) {
    if (DEBUG_SERIAL) Serial.printf("Error[%u]: ", error);
    if (error == OTA_AUTH_ERROR) if (DEBUG_SERIAL) Serial.println("Auth Failed");
      else if (error == OTA_BEGIN_ERROR) if (DEBUG_SERIAL) Serial.println("Begin Failed");
        else if (error == OTA_CONNECT_ERROR) if (DEBUG_SERIAL) Serial.println("Connect Failed");
          else if (error == OTA_RECEIVE_ERROR) if (DEBUG_SERIAL) Serial.println("Receive Failed");
            else if (error == OTA_END_ERROR) if (DEBUG_SERIAL) Serial.println("End Failed");
  });
  ArduinoOTA.begin();
}

void WiFiSetup() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {

    delay(500);
  }

  if (WiFi.status() != WL_CONNECTED) {

    delay(5000);
    ESP.restart();
  }


  if (!MDNS.begin("Evolution")) {
    while (1) {
      delay(1000);
    }
  }


  dnsServer.start(DNS_PORT, "lab.com", IPAddress(192, 168, 4, 1));

  server.on("/", handleRoot);
  server.begin();

  // Add service to MDNS-SD
  MDNS.addService("http", "tcp", 80);
}
1 Like

I think the concern is not needed, looking at hardwareSerial.cpp

void HardwareSerial::begin(unsigned long baud, uint32_t config, int8_t rxPin, int8_t txPin, bool invert, unsigned long timeout_ms, uint8_t rxfifo_full_thrhd)
{
    if(0 > _uart_nr || _uart_nr >= SOC_UART_NUM) {
        log_e("Serial number is invalid, please use numers from 0 to %u", SOC_UART_NUM - 1);
        return;
    }

#if !CONFIG_DISABLE_HAL_LOCKS
    if(_lock == NULL){
        log_e("MUTEX Lock failed. Can't begin.");
        return;
    }
#endif

    HSERIAL_MUTEX_LOCK();
    // First Time or after end() --> set default Pins
    if (!uartIsDriverInstalled(_uart)) {
        switch (_uart_nr) {
            case UART_NUM_0:
                if (rxPin < 0 && txPin < 0) {
                    rxPin = SOC_RX0;
                    txPin = SOC_TX0;
                }
            break;
#if SOC_UART_NUM > 1                   // may save some flash bytes...
            case UART_NUM_1:
               if (rxPin < 0 && txPin < 0) {
                    rxPin = RX1;
                    txPin = TX1;
                }
            break;
#endif
#if SOC_UART_NUM > 2                   // may save some flash bytes...
            case UART_NUM_2:
               if (rxPin < 0 && txPin < 0) {
                    rxPin = RX2;
                    txPin = TX2;
                }
            break;
#endif
        }
    }

    // map logical pins to GPIO numbers
    rxPin = digitalPinToGPIONumber(rxPin);
    txPin = digitalPinToGPIONumber(txPin);

    if(_uart) {
        // in this case it is a begin() over a previous begin() - maybe to change baud rate
        // thus do not disable debug output
        end(false);
    }

    // IDF UART driver keeps Pin setting on restarting. Negative Pin number will keep it unmodified.
    _uart = uartBegin(_uart_nr, baud ? baud : 9600, config, rxPin, txPin, _rxBufferSize, _txBufferSize, invert, rxfifo_full_thrhd);
    if (!baud) {
        // using baud rate as zero, forces it to try to detect the current baud rate in place
        uartStartDetectBaudrate(_uart);
        time_t startMillis = millis();
        unsigned long detectedBaudRate = 0;
        while(millis() - startMillis < timeout_ms && !(detectedBaudRate = uartDetectBaudrate(_uart))) {
            yield();
        }

        end(false);

        if(detectedBaudRate) {
            delay(100); // Give some time...
            _uart = uartBegin(_uart_nr, detectedBaudRate, config, rxPin, txPin, _rxBufferSize, _txBufferSize, invert, rxfifo_full_thrhd);
        } else {
            log_e("Could not detect baudrate. Serial data at the port must be present within the timeout for detection to be possible");
            _uart = NULL;
        }
    }
    // create a task to deal with Serial Events when, for example, calling begin() twice to change the baudrate,
    // or when setting the callback before calling begin() 
    if (_uart != NULL && (_onReceiveCB != NULL || _onReceiveErrorCB != NULL) && _eventTask == NULL) {
        _createEventTask(this);
    }

    // Set UART RX timeout
    uartSetRxTimeout(_uart, _rxTimeout);

    // Set UART FIFO Full depending on the baud rate. 
    // Lower baud rates will force to emulate byte-by-byte reading
    // Higher baud rates will keep IDF default of 120 bytes for FIFO FULL Interrupt
    // It can also be changed by the application at any time 
    if (!_rxFIFOFull) {    // it has not being changed before calling begin()
      //  set a default FIFO Full value for the IDF driver
      uint8_t fifoFull = 1;
      if (baud > 57600 || (_onReceiveCB != NULL && _onReceiveTimeout)) {
        fifoFull = 120;
      }
      uartSetRxFIFOFull(_uart, fifoFull);
      _rxFIFOFull = fifoFull;
    }

    _rxPin = rxPin;
    _txPin = txPin;

    HSERIAL_MUTEX_UNLOCK();
}

It doesn't seem to be setup the same way as on an ESP8266, in fact i think it is still useable as a GPIO pin, though i guess you can not use the RX-pin as an OUTPUT. TBH though i am not sure.

That they are not recommending it as such does not mean they are advising against it.

1 Like

True!

It is not a counter argument; it is just a part of discussion.

Intel did not include/disclose 10 instructions/mnemonics in the Instruction Set of their 8085 Microprocessor. After receiving unofficial copy of those instructions, it was obsrved that they were very powerful instructions; but, the Company Manager instructed his Design Engineers not to use those instructions in the design of public product as they were not officially released.

Likewise, use of UART1(5, 18) is fine for private/personal purposes. Will it be good to use in the design of a public product as it is not in the specifications of manufacturer's data sheets?

Question to Espressif Forum and Answer from them:

Question: In the pinout diagram of my 30-pin ESP32 Dev Module, only UART2 Port is free. I need to use UART1 Port. Data sheet shows that the IO lines of this port are associated with physical pin-28/29 (GPIO9/10) of ESP32 MCU). Currently, PPin-28/29 of ESP32 MCU are engaged with flash memory. The following code allows me to connect the IO pins of UART1 Port with GPIO5/18. Does Espressif agree with this relocation and do they support it to use in commercial applications?

Answer:
Postby ESP_Sprite ยป 04 Sep 2023 18:17

Assuming this is a 'classic' ESP32 (not S2/3 or C2/3/6/...) as you didn't specify otherwise: those GPIOs are free and this should work.

We have confirmed it does. What the difference is between 'commercial' application and 'normal' application is not always clear to me. It works and it will work for extended period of time.

The only person who thought it wouldn't work was @GolamMostafa

I think he is simply reporting that he has now appealed to what he considers a higher authority and convinced himself.

Before consulting with Espressif Forum, @GolamMostafa experimentally proved that UART1(5, 18) worked well.

The issue is on its legal use of UART1 in the design of commercial/public product as it is not explicitly included n the technical specification of Espressif.

On this respect, both @Deva_Rishi and @Espressif are silent โ€“ both of them are saying that it works and I have already proved that it works.

As I did not get my answer/approval from Espressif; l will be reluctant to use it (the UART1) in the design of public product to avoid legal issue that may appear because of malfunctioning of the UART1(5, 18) port.

Commercial refers to an ESP32 basrd product in which UART1(5,18) has been used and a permission has been taken from Govt. to sell it among the public.

Normal may refer to personal/private use with no monetary gain.

With the amounts i sell, no permission is required, that actually probably being the reason that Espressif doesn't care what you do with it as long as you buy it, and they would prefer to sell it without any warranty. I think the rules in China may not be quite the same anyway. Thing is that Espressif built a chip with 3 UARTs and we buy boards from vendors that connect the Flash to some of the pins and UART1 is connected to those pins as well by default.
Now the ESP32 has a Hall-lock multiplexer if i understand correctly, anyway the working of this part of the device is document (i think) and there are functions for it's use included in the SDK.
In truth i am not so sure the vendor of the board i buy has permission from their government, and any form of warranty is doubtful. I mean you actually gotta find the guys, and if you would file any significant suit, the retail company will disappear.
That Espressif does not specifically tell you not too, actually means they are sure it is ok. There is no disclaimer. But hey if you want a board with just 1 useable UART, that is your choice.

I still think the sketch in post #36 is the easiest proof. Use Serial1 to TTL to USB :smiley:

1 Like

Thank you very much for the post and its content that is really in line with what I have thought. I had no doubt on your proposition that UART1 could be re-routed; rather, I was excited to hear the announcement and test it, and I had found it working.

Had I not took your jolly talking on "engagement and marriage", I could not come along with you up to now. From the very beginning, you were a passionate person to understand me and finally managed to bring me to what you wanted to convey about that sleeping UART1 and when I submitted that conceptual diagram you were happy to see that.

1 Like