Use of 32768Hz crystal

The information on the Seeduino Xiang states “ Usually a Dev. Board as small as this size will use the chip’s inner crystal oscillator for time fixing, in order to make the clock more accurate, Seeeduino XIAO layouts an extra 32.768KHz to make the clock more stable.”

Does this mean that it has a Crystal on the board or does one need to be added? If it is on the board, how accurate is it and how would you access it to drive a timer? If it needs to be added, is any additional circuitry required and to which pins should it be connected?

Many thanks for any help.

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

  • Program to drive 25BYJ-48 stepper motor using ULN 2003 driver for Giant 4 Arbor Clock.
  • Shaft rotation 32 steps per rev, 64 when half stepping - gear ratio 64:1
  • Therefore 64 x 64 = 4096 steps per rev.
  • Uses 32768 Hz crystal
  • Timer count = (frequency/steps)*required rotation period
  • (32768/4096) * 60 = 480 for 60 second revolution (1rpm)
  • (32768/4096) * 30 = 240 for 30 second revolution (2rpm)
  • Giant 4 Arbor Clock requires 2rpm motor speed
  • Step sequence
  • IN1 1 1 0 0 0 0 0 1
  • IN2 0 1 1 1 0 0 0 0
  • IN3 0 0 0 1 1 1 0 0
  • IN4 0 0 0 0 0 1 1 1
  •  1  3  2  6  4 12  8  9   -  Half Step
    

*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
}

‘’’

2 Likes