Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 84 additions & 5 deletions AltSoftSerial.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,6 @@


#include "AltSoftSerial.h"
#include "config/AltSoftSerial_Boards.h"
#include "config/AltSoftSerial_Timers.h"

/****************************************/
/** Initialization **/
Expand All @@ -60,13 +58,27 @@ static volatile uint8_t tx_buffer_tail;
#define TX_BUFFER_SIZE 68
static volatile uint8_t tx_buffer[TX_BUFFER_SIZE];

volatile PinStatus AltSoftSerial::uart_HIGH = HIGH;
volatile PinStatus AltSoftSerial::uart_LOW = LOW;
volatile pin_size_t AltSoftSerial::tx_pin = OUTPUT_COMPARE_A_PIN;
volatile pin_size_t AltSoftSerial::rx_pin = INPUT_CAPTURE_PIN;

#ifndef INPUT_PULLUP
#define INPUT_PULLUP INPUT
#endif

#define MAX_COUNTS_PER_BIT 6241 // 65536 / 10.5

#if defined(ALTSS_TX_DIGITALWRITE)
enum MATCH_MODE{
NORMAL,
SET,
CLEAR
};

static volatile MATCH_MODE match_mode = NORMAL;
#endif

void AltSoftSerial::init(uint32_t cycles_per_bit)
{
//Serial.printf("cycles_per_bit = %d\n", cycles_per_bit);
Expand Down Expand Up @@ -101,9 +113,9 @@ void AltSoftSerial::init(uint32_t cycles_per_bit)
}
ticks_per_bit = cycles_per_bit;
rx_stop_ticks = cycles_per_bit * 37 / 4;
pinMode(INPUT_CAPTURE_PIN, INPUT_PULLUP);
digitalWrite(OUTPUT_COMPARE_A_PIN, HIGH);
pinMode(OUTPUT_COMPARE_A_PIN, OUTPUT);
pinMode(rx_pin, INPUT_PULLUP);
digitalWrite(tx_pin, uart_HIGH);
pinMode(tx_pin, OUTPUT);
rx_state = 0;
rx_buffer_head = 0;
rx_buffer_tail = 0;
Expand Down Expand Up @@ -135,8 +147,13 @@ void AltSoftSerial::writeByte(uint8_t b)
head = tx_buffer_head + 1;
if (head >= TX_BUFFER_SIZE) head = 0;
while (tx_buffer_tail == head) ; // wait until space in buffer
#if defined(ALTSS_SAMD)
// SAMD architecture uses different Macros
__disable_irq();
#else
intr_state = SREG;
cli();
#endif
if (tx_state) {
tx_buffer[head] = b;
tx_buffer_head = head;
Expand All @@ -148,12 +165,39 @@ void AltSoftSerial::writeByte(uint8_t b)
CONFIG_MATCH_CLEAR();
SET_COMPARE_A(GET_TIMER_COUNT() + 16);
}
#if defined(ALTSS_SAMD)
// SAMD architecture uses different Macros than AVR
__enable_irq();
#else
SREG = intr_state;
#endif
}

#if defined(ALTSS_SAMD)
void COMPARE_A_ISR()
{
// SAMD architecutre: request current timer value & save until read via `GET_COMPARE_A()` macro
timer_request();

#else
ISR(COMPARE_A_INTERRUPT)
{
#endif

#if defined(ALTSS_TX_DIGITALWRITE)
// Use Arduino function for TX pin
switch(match_mode){
case NORMAL:
break;
case SET:
digitalWrite(AltSoftSerial::tx_pin, AltSoftSerial::uart_HIGH);
break;
case CLEAR:
digitalWrite(AltSoftSerial::tx_pin, AltSoftSerial::uart_LOW);
break;
};
#endif

uint8_t state, byte, bit, head, tail;
uint16_t target;

Expand Down Expand Up @@ -219,8 +263,13 @@ void AltSoftSerial::flushOutput(void)
/** Reception **/
/****************************************/

#if defined(ALTSS_RX_ATTACHINTERRUPT)
void INPUT_PIN_ISR()
{
#else
ISR(CAPTURE_INTERRUPT)
{
#endif
uint8_t state, bit, head;
uint16_t capture, target;
uint16_t offset, offset_overflow;
Expand Down Expand Up @@ -272,7 +321,11 @@ ISR(CAPTURE_INTERRUPT)
//if (GET_TIMER_COUNT() - capture > ticks_per_bit) AltSoftSerial::timing_error = true;
}

#if defined(ALTSS_SAMD)
void COMPARE_B_ISR()
#else
ISR(COMPARE_B_INTERRUPT)
#endif
{
uint8_t head, state, bit;

Expand Down Expand Up @@ -358,3 +411,29 @@ void ftm0_isr(void)
}
#endif


/****************************************/
/** SAMD Architecture ISR **/
/****************************************/

#if defined(ALTSS_SAMD)
// SAMD architecture uses only one ISR for all timer events
void ALTSS_SAMD_TIMER_HANDLER()
{
uint8_t int_flag = ALTSS_SAMD_TC->COUNT16.INTFLAG.reg; // flags are also set when interrupt is not enabled
uint8_t int_ena = ALTSS_SAMD_TC->COUNT16.INTENSET.reg; // get interrupt enable flags
uint8_t isr_trigger = int_flag & int_ena; // interrupt triggers
uint8_t clear = 0;

if (isr_trigger & TC_INTFLAG_MC0){
clear |= TC_INTFLAG_MC0; // Use bitwise OR to accumulate flags
COMPARE_A_ISR();
}
if (isr_trigger & TC_INTFLAG_MC1){
clear |= TC_INTFLAG_MC1; // Use bitwise OR to accumulate flags
COMPARE_B_ISR();
}

ALTSS_SAMD_TC->COUNT16.INTFLAG.reg = clear; // clear interrupt triggers
}
#endif
13 changes: 12 additions & 1 deletion AltSoftSerial.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@

#include <inttypes.h>

#include <Arduino.h>
#include "config/AltSoftSerial_Boards.h"
#include "config/AltSoftSerial_Timers.h"

#if ARDUINO >= 100
#include "Arduino.h"
#else
Expand Down Expand Up @@ -61,13 +65,20 @@ class AltSoftSerial : public Stream
static void flushInput();
static void flushOutput();
// for drop-in compatibility with NewSoftSerial, rxPin & txPin ignored
AltSoftSerial(uint8_t rxPin, uint8_t txPin, bool inverse = false) { (void)rxPin; (void)txPin; (void)inverse; }
AltSoftSerial(uint8_t rxPin, uint8_t txPin, bool inverse = false) // WARNING: only applicable for SAMD boards & `inverse` only affects TX polarity
{ tx_pin = txPin; rx_pin = rxPin; if(inverse) {uart_HIGH = LOW; uart_LOW = HIGH;} }
bool listen() { return false; }
bool isListening() { return true; }
bool overflow() { bool r = timing_error; timing_error = false; return r; }
static int library_version() { return 1; }
static void enable_timer0(bool enable) { (void)enable; }
static bool timing_error;

// used by ISR (must be public)
static volatile PinStatus uart_HIGH;
static volatile PinStatus uart_LOW;
static volatile pin_size_t tx_pin;
static volatile pin_size_t rx_pin;
private:
static void init(uint32_t cycles_per_bit);
static void writeByte(uint8_t byte);
Expand Down
9 changes: 9 additions & 0 deletions config/AltSoftSerial_Boards.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@
#define OUTPUT_COMPARE_B_PIN 12 // unusable PWM


#elif defined(ARDUINO_ARCH_SAMD)
#define ALTSS_SAMD
#define ALTSS_TX_DIGITALWRITE
#define ALTSS_RX_ATTACHINTERRUPT
#define INPUT_PULLUP PinMode::INPUT_PULLUP // an enum is used, but AltSoftSerial.cpp does need a define

#define ALTSS_USE_SAMD_TIMER3
#define INPUT_CAPTURE_PIN 3 // receive
#define OUTPUT_COMPARE_A_PIN 2 // transmit

// Unknown board
#else
Expand Down
114 changes: 114 additions & 0 deletions config/AltSoftSerial_Timers.h
Original file line number Diff line number Diff line change
Expand Up @@ -183,5 +183,119 @@
#endif
#define ISR(f) static void f (void)

#elif defined(ALTSS_USE_SAMD_TIMER3)
#define ALTSS_SAMD_TC TC3
#define ALTSS_SAMD_TIMER_HANDLER TC3_Handler
#define ALTSS_SAMD_TIMER_IRQn TC3_IRQn
#define ALTSS_SAMD_GCLK_ID 3 // use Generic Clock 3
#define ALTSS_SAMD_GCLK_CLKCTRL_ID GCLK_CLKCTRL_ID_TCC2_TC3_Val

#endif

/* ==========================
* AltSoftSerial - use TX digitalWrite
* - TX pin can be set to any pin
* - should work on all Arduino Boards
* (introduced for SAMD boards, work up to 19200 baud)
*/
#if defined(ALTSS_TX_DIGITALWRITE)
#define CONFIG_MATCH_NORMAL() {match_mode = NORMAL;}
#define CONFIG_MATCH_SET() {match_mode = SET;}
#define CONFIG_MATCH_CLEAR() {match_mode = CLEAR;}
#endif

/* ==========================
* AltSoftSerial - use RX attachInterrupt
* - RX pin can be set to any pin
* - should work on all Arduino Boards
* (introduced for SAMD boards, work up to 19200 baud)
*/
#if defined(ALTSS_RX_ATTACHINTERRUPT)
// Use Arduino functions for RX pin
void INPUT_PIN_ISR();

#define DETACH_PIN_ISR() (detachInterrupt(digitalPinToInterrupt(AltSoftSerial::rx_pin)))
#define ATTACH_PIN_ISR(MODE) {attachInterrupt(digitalPinToInterrupt(AltSoftSerial::rx_pin), INPUT_PIN_ISR, MODE);}
#define ENABLE_INT_INPUT_CAPTURE() (ATTACH_PIN_ISR(FALLING))
#define DISABLE_INT_INPUT_CAPTURE() (DETACH_PIN_ISR())
#define CONFIG_CAPTURE_FALLING_EDGE() (ATTACH_PIN_ISR(FALLING))
#define CONFIG_CAPTURE_RISING_EDGE() (ATTACH_PIN_ISR(RISING))


#endif

/* ==========================
* AltSoftSerial - SAMD Timer Setup
* - should work on all SAMD boards, tested only on `Arduino MKR Zero`
* - using MACROS which where originally designed for AVR boards
*/
#if defined (ALTSS_SAMD)
// Request the current value of the COUNT register
inline void timer_request(){
// the current value of COUNT will be read even later (except, when COUNT register is written to)
ALTSS_SAMD_TC->COUNT16.READREQ.reg = TC_READREQ_RREQ | TC_READREQ_ADDR(TC_COUNT16_COUNT_OFFSET);
};

// Read COUNT register (from point in time when request was mad)
inline uint16_t timer_read(){
// Wait for read-synchronization (if not already done)
while(ALTSS_SAMD_TC->COUNT16.STATUS.bit.SYNCBUSY);
return ALTSS_SAMD_TC->COUNT16.COUNT.reg;
};

// Request & Read COUNT register
inline uint16_t timer_request_read(){
timer_request();
return timer_read();
};

inline void init_timer(uint8_t TC_CTRLA_PRESCALER_Val){
// Setup GCLK (Generic Clock Controller) for ALTSS_SAMD_TC
GCLK->GENDIV.bit.ID = ALTSS_SAMD_GCLK_ID; // Select Generic Clock ID
GCLK->GENDIV.bit.DIV = 1; // Setup Divison - GCLK = 48MHz / DIV
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization

GCLK->GENCTRL.bit.ID = ALTSS_SAMD_GCLK_ID; // Select Generic Clock ID
GCLK->GENCTRL.bit.SRC = GCLK_GENCTRL_SRC_DFLL48M_Val; // Set the 48MHz clock source
GCLK->GENCTRL.bit.GENEN = 1; // Enable GCLK
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization

GCLK->CLKCTRL.bit.GEN = ALTSS_SAMD_GCLK_ID; // Select the GCLK
GCLK->CLKCTRL.bit.ID = ALTSS_SAMD_GCLK_CLKCTRL_ID; // Feed the GCLK to TCC2 and TC3
GCLK->CLKCTRL.bit.CLKEN = 1; // Enable
while (GCLK->STATUS.bit.SYNCBUSY); // Wait for synchronization

// Select 16-bit timer counter mode (TC3)
// Software reset of TC3; Initialization only possible when TC is disabled -> do a reset
ALTSS_SAMD_TC->COUNT16.CTRLA.bit.SWRST = 1;
while(ALTSS_SAMD_TC->COUNT16.CTRLA.bit.SWRST); // Wait for reset to complete

ALTSS_SAMD_TC->COUNT16.CTRLA.bit.PRESCALER = TC_CTRLA_PRESCALER_Val; // Set prescaler bits
while(ALTSS_SAMD_TC->COUNT16.STATUS.bit.SYNCBUSY);


// Enable TC3 interrupt (there is only one vector for all TC3 interrupts)
NVIC_SetPriority(ALTSS_SAMD_TIMER_IRQn, 0); // Set the Nested Vector Interrupt Controller (NVIC) priority for TC3 to 0 (highest)
NVIC_EnableIRQ(ALTSS_SAMD_TIMER_IRQn); // Connect TC3 to Nested Vector Interrupt Controller (NVIC)

// Enable TC3
ALTSS_SAMD_TC->COUNT16.CTRLA.bit.ENABLE = 1;
while(ALTSS_SAMD_TC->COUNT16.STATUS.bit.SYNCBUSY); // Wait for synchronization
};

#define CONFIG_TIMER_NOPRESCALE() (init_timer(TC_CTRLA_PRESCALER_DIV1_Val))
#define CONFIG_TIMER_PRESCALE_8() (init_timer(TC_CTRLA_PRESCALER_DIV8_Val))
#define CONFIG_TIMER_PRESCALE_256() (init_timer(TC_CTRLA_PRESCALER_DIV256_Val))

#define SET_COMPARE_A(val) (ALTSS_SAMD_TC->COUNT16.CC[0].reg = val)
#define SET_COMPARE_B(val) (ALTSS_SAMD_TC->COUNT16.CC[1].reg = val)

#define ENABLE_INT_COMPARE_A() {ALTSS_SAMD_TC->COUNT16.INTENSET.bit.MC0 = 1 ; ALTSS_SAMD_TC->COUNT16.INTFLAG.bit.MC0 = 1;}
#define ENABLE_INT_COMPARE_B() {ALTSS_SAMD_TC->COUNT16.INTENSET.bit.MC1 = 1 ; ALTSS_SAMD_TC->COUNT16.INTFLAG.bit.MC1 = 1;}
#define DISABLE_INT_COMPARE_A() (ALTSS_SAMD_TC->COUNT16.INTENCLR.bit.MC0 = 1)
#define DISABLE_INT_COMPARE_B() (ALTSS_SAMD_TC->COUNT16.INTENCLR.bit.MC1 = 1)

#define GET_TIMER_COUNT() (timer_request_read())
#define GET_INPUT_CAPTURE() (timer_request_read())
#define GET_COMPARE_A() (timer_read())
#endif