Compared to SPI, UART is even simpler - it's only 2 pins, RX & TX. UARTs are a real pain to emulate or bitbang due to their very precise timing and their asynchronous RX lines are very difficult to do if you dont have hardware interrupt pins available.
For that reason, being able to create new Serial's is awesome
How Serial is Created Now
Luckily, Atmel & Arduino did a really great job with structuring SERCOMs so you can create and assign new ones. You can find the actual code that is used to create the "Serial" SERCOM in variants.cpp
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(); }
If you have a Feather, you wont see the SERCOM5_Hander or Serial(&sercom5...) lines
The first hardware Serial object (on D0 and D1) is created with:
Uart Serial1( &sercom0, PIN_SERIAL1_RX, PIN_SERIAL1_TX, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;
The actual definitions of those macros is available in variants.h
// Serial1 #define PIN_SERIAL1_RX (0ul) #define PIN_SERIAL1_TX (1ul) #define PAD_SERIAL1_TX (UART_TX_PAD_2) #define PAD_SERIAL1_RX (SERCOM_RX_PAD_3)
You can see here that Serial1 is of type Uart and if you expand the macros:
Uart Serial1( &sercom0, 0, 1, PAD_SERIAL1_RX, PAD_SERIAL1_TX ) ;
We can match this up with the pins we know are used for Serial1
PA11 D0 SERCOM0.3 SERCOM2.3 PA10 D1 SERCOM0.2 SERCOM2.2
You can see that the first argument (&sercom0) does in fact match our observation that Serial1 is on SERCOM0. The second and third arguments match with the RX & TX pins.
The last two args are what actually define the pin muxing. We can see these definitions in SERCOM.h
typedef enum { SERCOM_RX_PAD_0 = 0, SERCOM_RX_PAD_1, SERCOM_RX_PAD_2, SERCOM_RX_PAD_3 } SercomRXPad; typedef enum { UART_TX_PAD_0 = 0x0ul, // Only for UART UART_TX_PAD_2 = 0x1ul, // Only for UART UART_TX_RTS_CTS_PAD_0_2_3 = 0x2ul, // Only for UART with TX on PAD0, RTS on PAD2 and CTS on PAD3 } SercomUartTXPad;
The two together let us define the muxing. For example we can define which pin is for RX
- SERCOM_RX_PAD_0 means RX on SERCOMn.0
- SERCOM_RX_PAD_1 means RX on SERCOMn.1
- SERCOM_RX_PAD_2 means RX on SERCOMn.2
- SERCOM_RX_PAD_3 means RX on SERCOMn.3
and then for TX:
- UART_TX_PAD_0 means TX on SERCOMn.0
- UART_TX_PAD_2 means TX on SERCOMn.2
As you can tell, you can't quite have every possibility, for example you cannot have TX on pad 1 or pad 3. But you do have a lot of options!
SAMD51 Serial SERCOM Pads
The SAMD51 is a little more restrictive, you can only have TX on PAD 0 - PAD 2 is no longer permitted as it was on the SAMD21
OK so let's make a new Serial SERCOM already
I understand, you want to make Serials! Lets try creating a new SERCOM and testing it out
Let's make a Serial device on SERCOM #1, lets look at what pin muxing options we've got:
Pin Arduino 'Pin' SERCOM SERCOM alt ----------------------------------------- PA18 D10 SERCOM1.2 SERCOM3.2 PA16 D11 SERCOM1.0 SERCOM3.0 PA19 D12 SERCOM1.3 SERCOM3.3 PA17 D13 SERCOM1.1 SERCOM3.1
We can put TX on D11 (SERCOM1.0) or D10 (SERCOM1.2), RX can be on any pin.
How about we have D10 be TX and D11 be RX?
That would mean something like:
Uart Serial2 (&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2);
Lets test it out!
Uart Serial2 (&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2); void SERCOM1_Handler() { Serial2.IrqHandler(); } void setup() { Serial.begin(115200); Serial2.begin(115200); } uint8_t i=0; void loop() { Serial.println(i); Serial2.write(i++); delay(10); }
aaaand...nothing! Why? Good question! Check out those pin definitions in the variants.cpp file:
const PinDescription g_APinDescription[]= ... { PORTA, 18, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM3_CH0, TC3_CH0, EXTERNAL_INT_2 }, // TC3/WO[0] { PORTA, 16, PIO_TIMER, (PIN_ATTR_DIGITAL|PIN_ATTR_PWM|PIN_ATTR_TIMER), No_ADC_Channel, PWM2_CH0, TCC2_CH0, EXTERNAL_INT_0 }, // TCC2/WO[0]
The first argument is the port (e.g. PORTA or PORTB), the second is the port's pin number, e.g. PORTA, 18 -> PA18. The third argument 'sets' the type of pinmux we're going to use.
In this case, these are set to be used as PIO_TIMER!
You can do that by calling pinPeripheral(pinnumber, function) which is an internal function but hey we're being hardk0re
#include <Arduino.h> // required before wiring_private.h #include "wiring_private.h" // pinPeripheral() function Uart Serial2 (&sercom1, 11, 10, SERCOM_RX_PAD_0, UART_TX_PAD_2); void SERCOM1_Handler() { Serial2.IrqHandler(); } void setup() { Serial.begin(115200); Serial2.begin(115200); // Assign pins 10 & 11 SERCOM functionality pinPeripheral(10, PIO_SERCOM); pinPeripheral(11, PIO_SERCOM); } 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); }
You can use your oscilloscope to see the data traces
We can also try SERCOM2:
Pin Arduino 'Pin' SERCOM SERCOM alt ----------------------------------------- PA14 D2 SERCOM2.2 SERCOM4.2 PA09 D3 SERCOM0.1 SERCOM2.1 PA08 D4 SERCOM0.0 SERCOM2.0 PA15 D5 SERCOM2.3 SERCOM4.3
Lets put TX on D4 (SERCOM2.0) and RX on D3 (SERCOM2.1)
#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); }
Note that since pin 3 and 4 use a SERCOM alt mux, we have to pass in PIO_SERCOM_ALT rather than PIO_SERCOM when we call pinPeripheral()
SAMD51 Interrupts
The SAMD51 has 4 total interrupt handlers, you'll need to define all of them. Here's an example for the SAMD51
#define PIN_SERIAL2_RX 30 // PA05 #define PAD_SERIAL2_RX (SERCOM_RX_PAD_1) #define PIN_SERIAL2_TX 18 // PA04 #define PAD_SERIAL2_TX (UART_TX_PAD_0) Uart Serial2( &sercom0, PIN_SERIAL2_RX, PIN_SERIAL2_TX, PAD_SERIAL2_RX, PAD_SERIAL2_TX ); Stream *SERIALOUT = &Serial2; void SERCOM0_0_Handler() { Serial2.IrqHandler(); } void SERCOM0_1_Handler() { Serial2.IrqHandler(); } void SERCOM0_2_Handler() { Serial2.IrqHandler(); } void SERCOM0_3_Handler() { Serial2.IrqHandler(); }
Text editor powered by tinymce.