Before I begin I'd like to mention that I'm relatively new to working with microcontrollers at this low level, so please bear with me.
I am trying to use an Adafruit Trinket M0 to process and generate signals to communicate over the Nintendo GameCube communications protocol. Namely, the SAMD21 microcontroller needs to be able to output bit sequences with a frequency of 1 microsecond. I know this is possible on 16MHz AVR microcontrollers with the existence of this library; therefore I imagine it would be possible to do so as well on the 48MHz SAMD21.
From some research online I found this code sample, based on which I made the following sketch to toggle the output pin on the Trinket M0 every 1μs:
// Physical pin labelled 3 on the Trinket M0
const uint8_t PIN = 7;
volatile uint8_t n = 0;
volatile uint8_t next_bit = 0;
void TC5_Handler(void)
{
// Toggle pin
PORT->Group[PORTA].OUTTGL.reg = (1 << PIN);
// Reset timer interrupt flag
TC5->COUNT16.INTFLAG.bit.MC0 = 1;
}
void setup()
{
// Initialize serial
Serial.begin(115200);
// Configure pin as output
PORT->Group[PORTA].PINCFG[PIN].reg = PORT_PINCFG_INEN;
PORT->Group[PORTA].DIRSET.reg = (1ul << PIN);
// Enable generic clock for Timer/Counter 4 and 5
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK0 |
GCLK_CLKCTRL_ID(GCM_TC4_TC5);
while (GCLK->STATUS.bit.SYNCBUSY);
// Perform software reset
TC5->COUNT16.CTRLA.reg = TC_CTRLA_SWRST;
while (TC5->COUNT16.STATUS.reg & TC_STATUS_SYNCBUSY);
while (TC5->COUNT16.CTRLA.bit.SWRST);
// Configure TC5
TC5->COUNT16.CTRLA.reg =
TC_CTRLA_MODE_COUNT16 | // Counter of 16 bits
TC_CTRLA_WAVEGEN_MFRQ | // Match frequency
TC_CTRLA_PRESCALER_DIV1; // Prescaler of 1 (no division)
TC5->COUNT16.CC[0].reg = F_CPU / 1e6 - 1; // Trigger @ 1MHz=10^6Hz
while (TC5->COUNT16.STATUS.bit.SYNCBUSY);
// Configure interrupt
uint32_t oldISER = NVIC->ISER[0];
NVIC->ICER[0] |= ~0ul; // Disable all interrupts
NVIC->ICPR[0] = 1ul << TC5_IRQn; // Clear pending timer interrupt
NVIC->ISER[0] = 1ul << (TC5_IRQn & 0x1f); // Enable TC5 interrupt
TC5->COUNT16.INTENSET.bit.MC0 = 1; // Match/Compare 0 interrupt
while (TC5->COUNT16.STATUS.bit.SYNCBUSY);
// Start counter
TC5->COUNT16.CTRLA.reg |= TC_CTRLA_ENABLE; // Enable TC5
while (TC5->COUNT16.STATUS.bit.SYNCBUSY);
}
void loop()
{
}
This works great. My logic analyzer shows me perfect 1μs pulses.
However, trying to add a small conditional statement in there to output "0" bit sequences according to the GameCube protocol causes the signal to instead adopt a frequency of closer to 2μs.
void TC5_Handler(void)
{
// Toggle pin
(next_bit ?
PORT->Group[PORTA].OUTSET.reg :
PORT->Group[PORTA].OUTCLR.reg) = (1 << PIN);
next_bit = (n & 3) < 3 ? 0 : 1;
++n;
// Reset timer interrupt flag
TC5->COUNT16.INTFLAG.bit.MC0 = 1;
}
I didn't expect that little added code to suddenly make the function take that much longer than a microsecond. In that case, how can I make this task more efficient? Must I pursue a different route with generating these signals?