// References / Resources / Inspiration
//     https://gist.github.com/nonsintetic/ad13e70f164801325f5f552f84306d6f

// Varous system includes
#include <Arduino.h>
#include "wiring_private.h"

// Various library includes
#include <CircularBuffer.h>

// Buffer for serial data -> i2c
#define UART_BUFFER_SIZE 16 * 1024
CircularBuffer<char,UART_BUFFER_SIZE> uartBuffer;

// Board LED Pin
#define PIN_BOARD_LED 13

// Blinking timer setup
#define TIMER_MILLIS 500
bool ledState = false;
void TC5_Handler(void);
void timerConfigure(int sampleRate);

// Additional serial ports for communication <> RPI
//     https://learn.adafruit.com/using-atsamd21-sercom-to-add-more-spi-i2c-serial-ports?view=all#creating-a-new-serial
//     RX -- A2 -- S 4:1
//     TX -- A1 -- S 4:0
//     RX -- 10 -- S 3:2
//     TX -- 11 -- S 3:0
Uart rpiUART0 (&sercom4, A1, A2, SERCOM_RX_PAD_1, UART_TX_PAD_0);
Uart rpiUART5 (&sercom3, 10, 11, SERCOM_RX_PAD_2, UART_TX_PAD_0);

void SERCOM4_Handler() {
  rpiUART0.IrqHandler();
}
void SERCOM3_Handler() {
  rpiUART5.IrqHandler();
}

// Setup stuff needed for build
void setup() {
  // Setup board LED
  pinMode(PIN_BOARD_LED, OUTPUT);

  // Timer setup
  timerConfigure(TIMER_MILLIS);
  timerStartCounter();

  // Assign SERCOM functionality
  pinPeripheral(A1, PIO_SERCOM);
  pinPeripheral(A2, PIO_SERCOM);
  pinPeripheral(10, PIO_SERCOM);
  pinPeripheral(11, PIO_SERCOM);

  // Setup serial ports
  Serial.begin(115200);
  rpiUART0.begin(115200);
  rpiUART5.begin(115200);
}

// Main function
void loop() {
  digitalWrite(PIN_BOARD_LED, HIGH);
  delay(500);
  digitalWrite(PIN_BOARD_LED, LOW);
  delay(500);
}

// Timer handler
void TC5_Handler (void) {
  if(ledState == true) {
    digitalWrite(PIN_BOARD_LED, HIGH);
  } else {
    digitalWrite(PIN_BOARD_LED, LOW);
  }
  ledState = !ledState;
  TC5->COUNT16.INTFLAG.bit.MC0 = 1; //Writing a 1 to INTFLAG.bit.MC0 clears the interrupt so that it will run again
}

//Configures the TC to generate output events at the sample frequency.
//Configures the TC in Frequency Generation mode, with an event output once
//each time the audio sample frequency period expires.
void timerConfigure(int sampleRate) {
  // select the generic clock generator used as source to the generic clock multiplexer
  GCLK->CLKCTRL.reg = (uint16_t) (GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 | GCLK_CLKCTRL_ID(GCM_TC4_TC5)) ;
  while (GCLK->STATUS.bit.SYNCBUSY);

  timerReset(); //reset TC5

  // Set Timer counter 5 Mode to 16 bits, it will become a 16bit counter ('mode1' in the datasheet)
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_MODE_COUNT16;
  // Set TC5 waveform generation mode to 'match frequency'
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_WAVEGEN_MFRQ;
  //set prescaler
  //the clock normally counts at the GCLK_TC frequency, but we can set it to divide that frequency to slow it down
  //you can use different prescaler divisons here like TC_CTRLA_PRESCALER_DIV1 to get a different range
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_PRESCALER_DIV1024 | TC_CTRLA_ENABLE; //it will divide GCLK_TC frequency by 1024
  //set the compare-capture register. 
  //The counter will count up to this value (it's a 16bit counter so we use uint16_t)
  //this is how we fine-tune the frequency, make it count to a lower or higher value
  //system clock should be 1MHz (8MHz/8) at Reset by default
  TC5->COUNT16.CC[0].reg = (uint16_t) (SystemCoreClock / sampleRate);
  while (timerIsSyncing());
 
  // Configure interrupt request
  NVIC_DisableIRQ(TC5_IRQn);
  NVIC_ClearPendingIRQ(TC5_IRQn);
  NVIC_SetPriority(TC5_IRQn, 0);
  NVIC_EnableIRQ(TC5_IRQn);

  // Enable the TC5 interrupt request
  TC5->COUNT16.INTENSET.bit.MC0 = 1;
  while (timerIsSyncing()); //wait until TC5 is done syncing 
} 

//Function that is used to check if TC5 is done syncing
//returns true when it is done syncing
bool timerIsSyncing() {
  return TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY;
}

//This function enables TC5 and waits for it to be ready
void timerStartCounter() {
  TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; //set the CTRLA register
  while (timerIsSyncing()); //wait until snyc'd
}

//Reset TC5 
void timerReset() {
  TC5->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
  while (timerIsSyncing());
  while (TC5->COUNT16.CTRLA.bit.SWRST);
}

//disable TC5
void timerDisable() {
  TC5->COUNT16.CTRLA.reg &= ~TC_CTRLA_ENABLE;
  while (timerIsSyncing());
}
