Arduino Zero - SoftwareSerial library

Hi Miq1,

If you're using an Arduino.cc's Zero then D2/D5 and D3/D4 make TX/RX sercom pairs, whereas on an Arduino.org's M0/M0 Pro it's D2/D3 and D4/D5. This is because on Arduino.cc's Zero, pins D2 and D4 are swapped.

The Adafruit article: Creating a new Serial | Using ATSAMD21 SERCOM for more SPI, I2C and Serial ports | Adafruit Learning System, shows how it's possible to access the sercom ports using the pinPeripheral() function. This function gives the pins sercom functionality without having to modify the variant files.

Assuming that you're using the Arduino.cc's Zero, then D2 and D5 use sercom2, or if you're prepared to forgo the SPI port on sercom4. Pins D3 and D4 use sercom0 or sercom2, however sercom0 is already used by D0 and D1.

The pins you mention read differently in the page reference you linked. I will try to understand what is written there and experiment a bit :wink:

Hi Miq1,

The pins you mention read differently in the page reference you linked.

If you look at the bottom of the linked page, you'll find an example that exactly matches your requirement, namely using sercom2 on pins D3 and D4. Here's the code from adafruit's page:

    #include <Arduino.h>   // required before wiring_private.h
    #include "wiring_private.h" // pinPeripheral() function
     
    Uart Serial2 (&sercom2, 3, 4, SERCOM_RX_PAD_1, UART_TX_PAD_0);
    void SERCOM2_Handler()
    {
      Serial2.IrqHandler();
    }
     
    void setup() {
      Serial.begin(115200);
     
      Serial2.begin(115200);
      
      // Assign pins 3 & 4 SERCOM functionality
      pinPeripheral(3, PIO_SERCOM_ALT);
      pinPeripheral(4, PIO_SERCOM_ALT);
    }
     
    uint8_t i=0;
    void loop() {
      Serial.print(i);
      Serial2.write(i++);
      if (Serial2.available()) {
        Serial.print(" -> 0x"); Serial.print(Serial2.read(), HEX);
      }
      Serial.println();
      
      delay(10);
     
    }

The same code can be used as a template to configure sercom4 on D2 and D5.

Hi MartinL,

thanks, it worked with sercom2 on D3/D4. For sercom4 on D2/D5, would it be:

Uart Serial4 (&sercom4, 2, 5, SERCOM_RX_PAD_1, UART_TX_PAD_0);
    void SERCOM4_Handler()
    {
      Serial4.IrqHandler();
    }

setup()
{
...
 pinPeripheral(2, PIO_SERCOM_ALT);
 pinPeripheral(5, PIO_SERCOM_ALT);
...

then?

Hi Miq1,

You just need to reverse the pins D2=TX, D5=RX and change the PAD definitions. I've also changed Serial4 to Serial3, although this doesn't really matter:

Uart Serial3 (&sercom4, 5, 2, SERCOM_RX_PAD_3, UART_TX_PAD_2);
    void SERCOM4_Handler()
    {
      Serial3.IrqHandler();
    }

setup(){}

Hi MartinL,

looks like I am blind - with the code below, the D3/D4 UART ceased working, the Nextion display connected to it shows no reaction anymore. But why?

#include <Arduino.h>   // required before wiring_private.h
#include "wiring_private.h" // pinPeripheral() function

...

// Instantiate the Nextion-Serial and Serial2 class
Uart CellOne(&sercom4,  5,  2, SERCOM_RX_PAD_3, UART_TX_PAD_2);  
Uart Nextion(&sercom2,  3,  4, SERCOM_RX_PAD_1, UART_TX_PAD_0);    

void SERCOM2_Handler()    // Interrupt handler for SERCOM2
{
  Nextion.IrqHandler();
}

void SERCOM4_Handler()    // Interrupt handler for SERCOM4
{
  CellOne.IrqHandler();
}
     
void setup() {

...

// Assign pins 3 & 4 for SERCOM2 functionality
  pinPeripheral(3, PIO_SERCOM_ALT);
  pinPeripheral(4, PIO_SERCOM_ALT);  
// Assign pins 2 & 5 for SERCOM4 functionality
  pinPeripheral(2, PIO_SERCOM_ALT);
  pinPeripheral(5, PIO_SERCOM_ALT);  

 ...

  Nextion.begin(9600);

  initNextion("Log"); // no effect :-(

  flashNextion("Nextion init done"); // neither effect

Oh, by the way: Serial1 (D0/D1) is in use as well and works.

Hi Miq1,

It's because you're calling the pinPeripheral() function before the Serialx.begin(). Call the begin() function for the serial ports first, then it should work OK.

You are right! Thanks a lot!

I never would have thought that assigning pins must be done after the initialization :astonished:

I am also looking for SoftwareSerial on Zero.
The reason is support for 9N1... There are forks of SoftwareSerial that implement it.

Can it be achieved with SERCOMs?

Hi mzayats,

Can it be achieved with SERCOMs?

Yes it can. It's detailed in Adafruit's tutorial: Creating a new Serial | Using ATSAMD21 SERCOM for more SPI, I2C and Serial ports | Adafruit Learning System.

MartinL:
Hi mzayats,

Yes it can. It's detailed in Adafruit's tutorial: Creating a new Serial | Using ATSAMD21 SERCOM for more SPI, I2C and Serial ports | Adafruit Learning System.

I'm not sure it is possible as is. if you take a look at HardwareSerial.h, you'll see that the 9N1 config doesn't exist.

Hi AloyseTech,

I'm not sure it is possible as is. if you take a look at HardwareSerial.h, you'll see that the 9N1 config doesn't exist.

You're right, I didn't notice that 9 bits worth of data isn't an option in hardware serial.

Although, it might be possible to disable the UART, set the number of bits to 9 and then restart. I haven't tested it though:

Serial1.begin(115200);                                          // Set-up Serial1 at 115200bps
SERCOM0->USART.CTRLA.reg &= ~SERCOM_USART_CTRLA_ENABLE;         // Disable the UART
while(SERCOM0->USART.SYNCBUSY.bit.ENABLE);                      // Wait for synchronization
SERCOM0->USART.CTRLB.reg |= SERCOM_USART_CTRLB_CHSIZE(1);       // Set CHSIZE to 1 = 9 bits per character 
SERCOM0->USART.CTRLA.reg |= SERCOM_USART_CTRLA_ENABLE;          // Re-enable the UART
while(SERCOM0->USART.SYNCBUSY.bit.ENABLE);                      // Wait for synchronization

yes, my understanding is that for 9N1, I would have to patch the libraries - those patches exist, but would force me to go back to 1.5.x or I will have to port the patches over. SoftwareSerial is available, just has the dependency on AVR.. Any plans to support ARM targets?

Hi mzayats,

I believe that a Software Serial library exists for Arduino.org's M0 PRO, (essentially the same as an Arduino Zero). It was included in Arduino.org's IDE from version 1.7.9 onwards: https://github.com/arduino-org/Arduino/releases.

Not sure if it supports 9 bit data, but might be worth a look?

Indeed - https://github.com/arduino-org/Arduino/tree/d110edbbfdf48d1b6ffc116ed1e58e742be4cf6e/hardware/arduino/samd/libraries/SoftwareSerial

Thanks!

Hey guys, So using this arduino.org library I can use software serial on a M0?

I am trying to compile a sketch on a adafruit feather M0, I dont understand enough to be able to port a software serial to an actual hardware serial.

Can can I go about using this arduino.org software serial library?

Thank!

I needed a serial port for output and the only available pin (PA3) didn't have access to a SERCOM, so I gave the M0 library a shot. I can confirm that the SoftwareSerial library works on the Zero and its derivatives.

NB: I ran into an error compiling the example sketch:

In member function 'void SoftwareSerial::setTX(uint8_t)': ../libraries/SoftwareSerial/SoftwareSerial.cpp:169:25: error: cannot convert 'volatile uint32_t* {aka volatile long unsigned int*}' to 'volatile PORT_OUT_Type*' in assignment
   _transmitPortRegister = portOutputRegister(port);
                         ^
In member function 'void SoftwareSerial::setRX(uint8_t)':
../libraries/SoftwareSerial/SoftwareSerial.cpp:182:25: error: cannot convert 'volatile uint32_t* {aka volatile long unsigned int*}' to 'volatile PORT_IN_Type*' in assignment
    _receivePortRegister = portInputRegister(port);

The fix for this is to change the following lines in SoftwareSerial.cpp:

From:

169:  _transmitPortRegister = portOutputRegister(port);
182:  _receivePortRegister = portInputRegister(port);

To:

169:  _transmitPortRegister = (volatile PORT_OUT_Type*)portOutputRegister(port);
182:  _receivePortRegister = (volatile PORT_IN_Type*)portInputRegister(port);

This allows the arduino.cc IDE to process the library files and then the examples all work fine. I have confirmed it working at 31250 baud for MIDI, I can only imagine that it would also work at other baud rates.

pharaohamps:
I needed a serial port for output and the only available pin (PA3) didn't have access to a SERCOM, so I gave the M0 library a shot. I can confirm that the SoftwareSerial library works on the Zero and its derivatives.

NB: I ran into an error compiling the example sketch:

In member function 'void SoftwareSerial::setTX(uint8_t)': ../libraries/SoftwareSerial/SoftwareSerial.cpp:169:25: error: cannot convert 'volatile uint32_t* {aka volatile long unsigned int*}' to 'volatile PORT_OUT_Type*' in assignment

_transmitPortRegister = portOutputRegister(port);
                        ^
In member function 'void SoftwareSerial::setRX(uint8_t)':
../libraries/SoftwareSerial/SoftwareSerial.cpp:182:25: error: cannot convert 'volatile uint32_t* {aka volatile long unsigned int*}' to 'volatile PORT_IN_Type*' in assignment
    _receivePortRegister = portInputRegister(port);




The fix for this is to change the following lines in SoftwareSerial.cpp:

From:



169:  _transmitPortRegister = portOutputRegister(port);
182:  _receivePortRegister = portInputRegister(port);




To:



169:  _transmitPortRegister = (volatile PORT_OUT_Type*)portOutputRegister(port);
182:  _receivePortRegister = (volatile PORT_IN_Type*)portInputRegister(port);




This allows the arduino.cc IDE to process the library files and then the examples all work fine. I have confirmed it working at 31250 baud for MIDI, I can only imagine that it would also work at other baud rates.

Thank you so much for posting the compilation error fix! Works on Arduino Zero for me.

Thank you for this great write-up!

I just want to chime in with some additional lines of code needed for the Arduino M0 Pro and the Atmel Studio 7.

In order to get sercom0 & sercom2 to work, I had to add these lines:

pinPeripheral(0, PIO_SERCOM); //Assign RX function to pin 0
pinPeripheral(1, PIO_SERCOM); //Assign TX function to pin 1

pinPeripheral(5, PIO_SERCOM); //Assign RX function to pin 5
pinPeripheral(4, PIO_SERCOM); //Assign TX function to pin 4

Hope someone find that helpful :slight_smile:

Cheers