Only One Serial Port?

Also managed to get a third UART going on the Zero as well.

Add the two following lines to the end of the "g_APinDescription" array in the file ..Computer\AppData\Roaming\Arduino15\packages\arduino\hardware\samd\1.6.1\variants\arduino_zero\variant.cpp:

// 44..45 - SERCOM2
  { PORTA, 14, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // SERCOM2/PAD[2]
  { PORTA, 15, PIO_SERCOM, PIN_ATTR_NONE, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_NONE }, // SERCOM2/PAD[3]

The example code below creates a UART class "Serial3" and connects it to SERCOM2. The Tx pin is on digital pin 2 (D2) and Rx on digital pin 5 (D5). It simply echos back characters sent from the COM terminal. Note that on Arduino.org's Zero Pro/M0 Pro/M0 the Tx pin is on digital pin 4 (D4).

// Serial3 pin and pad definitions (in Arduino files Variant.h & modified Variant.cpp)
#define PIN_SERIAL3_RX       (45ul)               // Pin description number for PIO_SERCOM on D5
#define PIN_SERIAL3_TX       (44ul)               // Pin description number for PIO_SERCOM on D2
#define PAD_SERIAL3_TX       (UART_TX_PAD_2)      // SERCOM pad 2
#define PAD_SERIAL3_RX       (SERCOM_RX_PAD_3)    // SERCOM pad 3

// Instantiate the Serial3 class
Uart Serial3(&sercom2, PIN_SERIAL3_RX, PIN_SERIAL3_TX, PAD_SERIAL3_RX, PAD_SERIAL3_TX);

void setup() 
{
  Serial3.begin(115200);          // Begin Serial3
}

void loop() 
{  
  if (Serial3.available())        // Check if incoming data is available
  {
    byte byteRead = Serial3.read();    // Read the most recent byte  
    Serial3.write(byteRead);      // Echo the byte back out on the serial port
  }
}

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

Hi MartinL,

With this issue would be possible to use SERCOM1 as Half-Duplex? Perhaps using just PORTA16 and PAD0? Is there any more changes that should be made for Serial with half-Duplex?

Best regards.

The UARTs are full-duplex.

When you say half-duplex, do you mean using only one pin either as a transmitter or receiver?

Yes, using only one pin for tx/rx.

The UARTs can be used as full-duplex, half-duplex or simplex(one direction only). My question is, How Can I configure SERCOM1 to use it as Half-Duplex?

The UARTs can be used as full-duplex, half-duplex or simplex(one direction only).

Thanks for correcting me. That's jolted my memory, half-duplex can be used for the old RS-485 twisted pair transceivers.

In half-duplex both the transmitter and receiver are enabled, but I believe you need to set the collision detection (COLDEN) bit in the sercom USART's CTRLB register. This can be set using the following code, in setup{}, just before Serial2.begin(). The CTRLB register needs to be synchronized by waiting for the CTRLB bit in the USART's SYNCBUSY register to clear.

REG_SERCOM1_USART_CTRLB |= SERCOM_USART_CTRLB_COLDEN;
while(SERCOM1->USART.SYNCBUSY.bit.CTRLB);

The details for clearing down and re-enabling the transmitter after a collision are given on page 449 of the SAMD21 datasheet. The datasheet states:

When a collision is detected, the USART automatically follows this sequence:
The current transfer is aborted.
The transmit buffer is flushed.
The transmitter is disabled (CTRLB.TXEN=0).
This commences immediately and is complete after synchronization time. The CTRLB Synchronization
Busy bit (SYNCBUSY.CTRLB) will be set until this is complete.
This results in the TxD pin being tri-stated.
The Collision Detected bit (STATUS.COLL) is set along with the Error interrupt flag (INTFLAG.ERROR).
Since the transmit buffer no longer contains data, the Transmit Complete interrupt flag (INTFLAG.TXC) is set.
After a collision, software must manually enable the transmitter before continuing. Software must ensure CTRLB
Synchronization Busy bit (SYNCBUSY.CTRLB) is not asserted before re-enabling the transmitter.

You can check the STATUS.COLL bit with:

if (REG_SERCOM1_USART_STATUS & SERCOM_USART_STATUS_COLL)

INTFLAG.ERROR with:

if (REG_SERCOM1_USART_INTFLAG & SERCOM_USART_INTFLAG_ERROR)

INTFLAG.TXC with:

if (REG_SERCOM1_USART_INTFLAG & SERCOM_USART_INTFLAG_TXC)

The transmitter can be re-enabled with:

while(SERCOM1->USART.SYNCBUSY.bit.CTRLB);          // Wait for SYNCBUSY.CTRLB to clear
REG_SERCOM1_USART_CTRLB |= SERCOM_USART_CTRLB_TXEN;
while(SERCOM1->USART.SYNCBUSY.bit.CTRLB);          // Wait for synchronization

By the way, all the register maniplulation definitions are stored in the directories:

..Computer\AppData\Roaming\Arduino15\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\component

which contains the SAMD21 peripheral component definitions, e.g. sercom.h, tcc.h, tc.h etc...

and

..Computer\AppData\Roaming\Arduino15\packages\arduino\tools\CMSIS\4.0.0-atmel\Device\ATMEL\samd21\include\instance

which contains the SAMD21 peripheral instance definitions, e.g. sercom0.h, sercom1.h etc...

Thank you MartinL,

I will try make this code and if i get sucess i will be sharing!

Thank you again!

best regards!

MartinL:
Finally managed to get a second UART going on the Zero.

Thanks! I'll try this our right away. I just happen to be working on a project that could really use a 2nd HW serial port on the Zero.

Wayne

MartinL:
Finally managed to get a second UART going on the Zero.

The example code below creates a UART class "Serial2" and connects it to SERCOM1. The Tx pin is on digital pin 10 (D10) and Rx on digital pin 12 (D12). It simply echos back characters sent from the COM terminal.

void SERCOM1_Handler() // Interrupt handler for SERCOM1
{
Serial2.IrqHandler();
}

I tried out your code and it seems to work. However, I do not understand the function of the SERCOM1_Handler() code. I tried commenting it out, but Serial2 seems not to work if I do this.

Wayne

Hi Wayne

The SERCOM1_Handler() is the interrupt handler for sercom1.

When the Uart object is created, it's attached to the specified sercom. The sercom is then configured to become a UART, (rather than an I2C or SPI port). During the configuration process the sercom is set to enable receiver interrupts. This effectively calls the SERCOM1_Handler() every time a character is received. This in turn calls on the UART handler (Serial2.Irq_Handler()) that places the incoming character in a buffer. You then access the buffer using Serial2.read().

The code I wrote to get the other two UARTs working simply mimics the code at the bottom of the Arduino file "variant.cpp", located at ...Computer\AppData\Roaming\Arduino15\packages\arduino\hardware\samd\1.6.1\variants\arduino_zero\

Here's the code in variant.cpp:

// Multi-serial objects instantiation
SERCOM sercom0( SERCOM0 ) ;
SERCOM sercom1( SERCOM1 ) ;
SERCOM sercom2( SERCOM2 ) ;
SERCOM sercom3( SERCOM3 ) ;
SERCOM sercom4( SERCOM4 ) ;
SERCOM sercom5( SERCOM5 ) ;

Uart Serial1( &sercom0, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;
Uart Serial( &sercom5, PIN_SERIAL_RX, PIN_SERIAL_TX, PAD_SERIAL_RX, PAD_SERIAL_TX ) ;
void SERCOM0_Handler()
{
  Serial1.IrqHandler();
}

void SERCOM5_Handler()
{
  Serial.IrqHandler();
}

Here you can see that the Arduino code creates all the sercom objects, 0..5 and two UART objects Serial and Serial1, plus their associated interrupt handlers. As sercom1 and 2 are free, you can call on them in the same way to configure them as UARTs, but this time you're doing it from your Arduino sketch.

To get the third UART going requires the two extra lines in the "g_APinDescription" pin descriptions array (also in the variant.cpp file). This connects sercom2 to pins D2 (D4 on Zero Pro/M0 Pro) and D5, as the option isn't available in the existing Arduino code. The pin descriptions are Arduino's way of configuring the SAMD21's port multiplexer.

I've just decided to add the #defines to my "variant.h" and the code creating the Uart objects and interrupt handlers to my "variant.cpp". That way I can just call on Serial2.begin() or Serial3.begin() without the additional code in my Arduino sketch.

In addition, I added the two extra lines to the sercom definitions in "variant.h":

extern Uart Serial2;
extern Uart Serial3;

So now I can call on Serial2 and Serial3, just like Serial and Serial1:

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

void loop() {
  if (Serial2.available())        // Check if incoming data is available on D12
  {
    byte byteRead = Serial2.read();    // Read the most recent byte on D12
    Serial2.write(byteRead);      // Echo the byte back out on the serial port on D10
  }
}

MartinL:
I've just decided to add the #defines to my "variant.h" and the code creating the Uart objects and interrupt handlers to my "variant.cpp". That way I can just call on Serial2.begin() or Serial3.begin() without the additional code in my Arduino sketch.

It would be nice to see these changes get added to the standard distribution.

Wayne

wholder:
It would be nice to see these changes get added to the standard distribution.

Wayne

Yes. Why don't you do a pull request on Arduino's github?

Maybe someone will hear you...or not.

Osqui:
Why don't you do a pull request on Arduino's github?

+1

MartinL:
Hi Wayne

The SERCOM1_Handler() is the interrupt handler for sercom1.

I'm trying to find a way to handle incoming serial data in an interrupt handler rather than having to poll for it. Is there a recommended way to do this? Can I put this kind of code in the SERCOM1_Handler() interrupt handler?

Wayne

It would be nice to see these changes get added to the standard distribution.

Yes. Why don't you do a pull request on Arduino's github?

Maybe someone will hear you...or not.

+1

Ok, I'll pull a request on Arduino's GitHub, see if they're interested in adding both UARTs to the standard distribution.

Not sure what plans Arduino have for sercom1 and 2? Sercom1 can also be used as an additional SPI port on digital pins 10, 11, 12 and 13, just like on the UNO.

I'm trying to find a way to handle incoming serial data in an interrupt handler rather than having to poll for it. Is there a recommended way to do this? Can I put this kind of code in the SERCOM1_Handler() interrupt handler?

Hi Wayne

Thanks, that's an interesting point, something I hadn't considered.

On the AVR Arduinos you usually poll the UART, as normally you don't have access to the its interrupt handler, but as SERCOM1_Handler is the interrupt handler for the UART's receiver, there's nothing to stop you from adding your own code.

Currently the sercom handler calls the UART handler (Serial2.Handler()). Inside the UART handler it simply checks if the sercom has received data and if so, stores it in the UART's receiver buffer:

void Uart::IrqHandler()
{
  if (sercom->availableDataUART()) {
    rxBuffer.store_char(sercom->readDataUART());
  }...

The "sercom->availableDataUART()" funtion checks the receiver interrupt flag:

bool SERCOM::availableDataUART()
{
  //RXC : Receive Complete
  return sercom->USART.INTFLAG.bit.RXC;
}

The "sercom->readDataUart()" function returns the received character:

uint8_t SERCOM::readDataUART()
{
  return sercom->USART.DATA.bit.DATA;
}

So you could use your own code to check and read incoming characters using the two sercom functions, though as you'd be accessing them from outside the UART object you'd need to call on the sercom object directly, for example: sercom1.availableDataUART() and sercom1.readDataUART(). However, this method does sort of bypass Arduino's way of handling serial data.

Hello,

I have a M0 board (not M0 pro). I wanted to had serial ports but it does not work with Serial2 and 3, but works with serial5 (wich is the one coming with arduino). Could qomeone hep me ?
what i have done is :
Into variant.cpp :

  { PORTA, 11, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_11 },
  { PORTA, 10, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_10 },
  { PORTA, 14, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_14 },
  { PORTA, 15, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_15 },
  { PORTA, 18, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_2 }, 
  { PORTA, 16, PIO_SERCOM, PIN_ATTR_DIGITAL, No_ADC_Channel, NOT_ON_PWM, NOT_ON_TIMER, EXTERNAL_INT_0 },

Uart Serial(&sercom0, 36, 35 );
Uart Serial2(&sercom1, 12, 10);
Uart Serial3(&sercom2, 5, 4);
Uart Serial5(&sercom5, 0, 1 );

Into variant.h :

extern SERCOM sercom0;
extern SERCOM sercom1;
extern SERCOM sercom2;
extern SERCOM sercom3;
extern SERCOM sercom4;
extern SERCOM sercom5;

extern Uart Serial;
extern Uart Serial2;
extern Uart Serial3;
extern Uart Serial5;

Into arduino code :

void setup() {
  // put your setup code here, to run once:
Serial5.begin(9600);
Serial2.begin(9600);
Serial3.begin(9600);
SerialUSB.begin(9600);
pinMode(13, OUTPUT);
}

void loop() {
  // put your main code here, to run repeatedly:
Serial2.write("Serial3");
digitalWrite(13, LOW);
if (Serial2.available())        // Check if incoming data is available
  {
    
 //   byte byteRead = Serial3.read();    // Read the most recent byte 
    SerialUSB.write("Serial2");      // Echo the byte back out on the serial port
    digitalWrite(13, HIGH);       // turn the LED on (HIGH is the voltage level)
    delay(1000);                  // wait for a second
    digitalWrite(13, LOW);    // turn the LED off by making the voltage LOW
    delay(1000);
 }
//SerialUSB.write("SerialUSB");      // Echo the byte back out on the serial port
}

Hi Rone,

The best thing is probably to return your "variant.h" and "variant.cpp" back to their original state and add the code to your sketch instead.

I've made a summary of how to get Serial2 and Serial3 working by adding the code to your sketch in the thread called "Software Serial Library", here's a link: Arduino Zero - SoftwareSerial library - Arduino Zero - Arduino Forum. Serial 3 does require two lines of extra code in "variant.cpp" though.