I have answered my own question and thought I would share for completeness!
Yes, the XIAO board does have a 32,768Hz crystal installed. I have put together the sketch below to use the XIAO to drive a ULN2003 driver linked to a 25BJY-48 stepper motor with an accurate time signal for use in my wooden clocks. Not my most elegant bit of coding but works for me. Time will tell how accurate and stable the crystal is.
‘’’
/*
Sketch to drive 25BYJ-48 stepper motor using ULN2003 driver for accuarate analog clock.
Developed on Speeeduino XIAO board
In setup, sequence of events is :-
-Enable the external crystal.
-Set one of the clocks to use the crystal.
-Link a timer to clock.
-Set timer characteristics and turn on.
From exploring output pins,
Pin A10 maps to bit 6 in REG_PORT_OUT0
Pin A9 maps to bit 5 in REG_PORT_OUT0
Pin A8 maps to bit 7 in REG_PORT_OUT0
Pin A7 maps to bit 9 in REF_PORT_OUT1
Pin A6 maps to bit 8 in REG_PORT_OUT1
*P1.0 -> 1 -> Blue -> A7 -> OUT1 BIT9
*P1.1 -> 2 -> Pink -> A8 -> OUT0 BIT7
*P1.2 -> 3 -> Yellow -> A9 -> OUT0 BIT5
*P1.3 -> 4 -> Orange -> A10 -> OUT0 BIT6
*
*/
unsigned int TimerCount = 720; //32768 = 1 sec, 16382 = 0.5 sec, etc
const unsigned int sequence [] = {1,3,2, 6,4, 12,8, 9}; //clockwise half stepping i.e. 64 steps per rev
int step = 0;
const int direction = -1; // -1 = clockwise, 1 = anticlockwise
int arraySize = (sizeof(sequence)/sizeof(int)) - 1;
void setup() {
// initialize digital pins as output.
pinMode(PIN_A10, OUTPUT);
pinMode(PIN_A9,OUTPUT);
pinMode(PIN_A8, OUTPUT);
pinMode(PIN_A7, OUTPUT);
CrystalStart(); // Enable the external crystal oscillator.
CrystalToClock (); // Set up GCLK1 to use XOSC32K as the clock source
ClockToTimer (); // Link Clock (GCLK1) to timer (Timer 3)
TimerSetup (); // Set timer characteristics and Enable
}
void loop() {
// Everything is interrupt driven so no loop
}
// Timer TC_3 interupt handler
void TC3_Handler(void) {
TC3->COUNT16.INTFLAG.reg |= 0b00010000; // Clear MC0 flag to rearm
switch(sequence [step])
{
case 1:
REG_PORT_OUT1 |= BIT9;
REG_PORT_OUT0 =(REG_PORT_OUT0 & ~BIT5 & ~BIT6 &~BIT7);
break;
case 2:
REG_PORT_OUT1 &= ~BIT9;
REG_PORT_OUT0 = (REG_PORT_OUT0 & ~BIT5 & ~BIT6) | BIT7;
break;
case 3:
REG_PORT_OUT1 |= BIT9;
REG_PORT_OUT0 =(REG_PORT_OUT0 & ~BIT5 & ~BIT6) | BIT7;
break;
case 4:
REG_PORT_OUT1 &= ~BIT9;
REG_PORT_OUT0 =(REG_PORT_OUT0 & ~BIT6 &~BIT7) | BIT5;
break;
case 6:
REG_PORT_OUT1 &= ~BIT9;
REG_PORT_OUT0 =(REG_PORT_OUT0 & ~BIT6) | BIT5 | BIT7;
break;
case 8:
REG_PORT_OUT1 &= ~BIT9;
REG_PORT_OUT0 =(REG_PORT_OUT0 & ~BIT5 &~BIT7) | BIT6;
break;
case 9:
REG_PORT_OUT1 |= BIT9;
REG_PORT_OUT0 =(REG_PORT_OUT0 & ~BIT5 &~BIT7) | BIT6;
break;
case 12:
REG_PORT_OUT1 &= ~BIT9;
REG_PORT_OUT0 =(REG_PORT_OUT0 &~BIT7) | BIT5 | BIT6;
break;
default:
break;
}
step = step + direction;
if (step>arraySize) step = 0;
if (step<0) step = arraySize;
}
void CrystalStart (void) {
SYSCTRL->XOSC32K.reg =
SYSCTRL_XOSC32K_STARTUP(0x7) |
SYSCTRL_XOSC32K_EN32K |
SYSCTRL_XOSC32K_XTALEN;
SYSCTRL->XOSC32K.bit.ENABLE = 1;
while(!SYSCTRL->PCLKSR.bit.XOSC32KRDY); //wait for crystal to be ready.
}
void CrystalToClock (void) {
// Configure GCLK1 divider to 1.
GCLK->GENDIV.reg =
GCLK_GENDIV_ID(1) |
GCLK_GENDIV_DIV(1);
//Set GCLK1 to use external 32.768kHz oscillator
GCLK->GENCTRL.reg =
GCLK_GENCTRL_ID(1) |
GCLK_GENCTRL_SRC_XOSC32K |
GCLK_GENCTRL_IDC | //Improve duty cycle
GCLK_GENCTRL_GENEN;
//Wait for the write to complete
while(GCLK->STATUS.bit.SYNCBUSY);
}
void ClockToTimer (void) {
GCLK->CLKCTRL.reg = GCLK_CLKCTRL_ID_TCC2_TC3_Val; // select TC3 peripheral channel
GCLK->CLKCTRL.reg |= GCLK_CLKCTRL_GEN_GCLK1; // select source GCLK1
GCLK->CLKCTRL.bit.CLKEN = 1; // enable TC3 generic clock
PM->APBCSEL.bit.APBCDIV = 0; //no prescaler
PM->APBCMASK.bit.TC3_ = 1; //enable TC3 interface
}
void TimerSetup (void) {
TC3->COUNT16.INTENSET.bit.OVF = 1; // Enable TC3 Overflow Interupt signal
TC3->COUNT16.CTRLA.bit.MODE = 0x0; // Count mode to 16 bit
TC3->COUNT16.CTRLA.bit.PRESCALER = 0; // No prescaler
TC3->COUNT16.CTRLA.bit.WAVEGEN = 0x1; // Compare mode is “Match Frequency”
TC3->COUNT16.CC[0].reg = (TimerCount - 1) ; // set compare value to TimerCount in compare channel 0
TC3->COUNT16.INTENSET.bit.MC0 = 0x1; // enable match interupts on compare channel 0
TC3->COUNT16.CTRLA.bit.ENABLE = 1; //Enable TC3
while(TC3->COUNT16.STATUS.bit.SYNCBUSY == 1); // Wait until TC3 is enabled
void __enable_irq(void); // Enable Global Interupts
NVIC_EnableIRQ(TC3_IRQn); // Enable TC3 NVIC Interrupt line
}
‘’’