2015-03-13 11:39:43 +00:00
|
|
|
/*
|
|
|
|
* Functions for controlling and calibrating against the external oscillator
|
|
|
|
* Copyright (C) 2015 Richard Meadows <richardeoin>
|
|
|
|
*
|
|
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
|
|
* a copy of this software and associated documentation files (the
|
|
|
|
* "Software"), to deal in the Software without restriction, including
|
|
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
|
|
* the following conditions:
|
|
|
|
*
|
|
|
|
* The above copyright notice and this permission notice shall be
|
|
|
|
* included in all copies or substantial portions of the Software.
|
|
|
|
*
|
|
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "samd20.h"
|
|
|
|
#include "system/clock.h"
|
|
|
|
#include "system/gclk.h"
|
|
|
|
#include "system/interrupt.h"
|
|
|
|
#include "system/pinmux.h"
|
|
|
|
#include "system/events.h"
|
|
|
|
#include "system/extint.h"
|
|
|
|
#include "tc/tc_driver.h"
|
|
|
|
#include "hw_config.h"
|
|
|
|
#include "xosc.h"
|
|
|
|
|
|
|
|
|
|
|
|
enum measure_state_t {
|
|
|
|
MEASURE_WAIT_FOR_FIRST_EVENT,
|
|
|
|
MEASURE_MEASUREMENT,
|
|
|
|
} measure_state = MEASURE_WAIT_FOR_FIRST_EVENT;
|
|
|
|
enum xosc_measurement_t _measurement_t;
|
2015-03-15 17:34:46 +00:00
|
|
|
measurement_result_t _callback;
|
2015-03-13 11:39:43 +00:00
|
|
|
|
|
|
|
/**
|
2015-03-15 21:18:33 +00:00
|
|
|
* Configures external oscillator, waits for it to stabilise, and
|
|
|
|
* connects it to GLCK1.
|
2015-03-13 11:39:43 +00:00
|
|
|
*/
|
|
|
|
void xosc_init(void) {
|
2015-04-02 19:57:25 +00:00
|
|
|
#ifdef USE_XOSC
|
2015-03-13 11:39:43 +00:00
|
|
|
system_clock_source_xosc_set_config(SYSTEM_CLOCK_EXTERNAL_CLOCK,
|
|
|
|
SYSTEM_XOSC_STARTUP_1,
|
|
|
|
true,
|
|
|
|
XOSC_FREQUENCY,
|
|
|
|
false,
|
|
|
|
false);
|
|
|
|
system_clock_source_enable(SYSTEM_CLOCK_SOURCE_XOSC);
|
|
|
|
|
|
|
|
while (!system_clock_source_is_ready(SYSTEM_CLOCK_SOURCE_XOSC));
|
2015-04-02 19:57:25 +00:00
|
|
|
#endif
|
2015-03-15 21:18:33 +00:00
|
|
|
|
|
|
|
/* Configure GCLK1 to XOSC */
|
|
|
|
system_gclk_gen_set_config(GCLK_GENERATOR_1,
|
2015-04-02 19:57:25 +00:00
|
|
|
#ifdef USE_XOSC
|
2015-04-02 10:38:48 +00:00
|
|
|
GCLK_SOURCE_XOSC, /* Source */
|
|
|
|
#else
|
|
|
|
GCLK_SOURCE_OSC8M, /* Source */
|
2015-04-02 19:57:25 +00:00
|
|
|
#endif
|
2015-03-15 21:18:33 +00:00
|
|
|
false, /* High When Disabled */
|
2015-04-02 10:38:48 +00:00
|
|
|
XOSC_GCLK1_DIVIDE, /* Division Factor */
|
2015-03-15 21:18:33 +00:00
|
|
|
false, /* Run in standby */
|
|
|
|
false); /* Output Pin Enable */
|
|
|
|
|
|
|
|
|
|
|
|
/* Enable GCLK1 */
|
|
|
|
system_gclk_gen_enable(GCLK_GENERATOR_1);
|
2015-03-13 11:39:43 +00:00
|
|
|
}
|
|
|
|
|
2015-03-15 17:34:46 +00:00
|
|
|
struct osc8m_calibration_t osc8m_get_calibration(void) {
|
|
|
|
uint16_t calib_word = SYSCTRL->OSC8M.bit.CALIB;
|
|
|
|
struct osc8m_calibration_t calib;
|
|
|
|
|
|
|
|
calib.temperature = (calib_word >> 6) & 0x3F;
|
|
|
|
calib.process = (calib_word >> 0) & 0x3F;
|
|
|
|
|
|
|
|
return calib;
|
|
|
|
}
|
|
|
|
void osc8m_set_calibration(struct osc8m_calibration_t calib) {
|
|
|
|
uint16_t calib_word = ((calib.temperature & 0x3F) << 6) | (calib.process & 0x3F);
|
|
|
|
|
|
|
|
system_clock_source_write_calibration(SYSTEM_CLOCK_SOURCE_OSC8M, calib_word, 0x1);
|
|
|
|
}
|
|
|
|
|
2015-03-13 11:39:43 +00:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Configure timer 4 to generate events at 1Hz of OSC8M
|
|
|
|
*/
|
2015-07-03 22:40:47 +00:00
|
|
|
/* void osc8m_event_source(void) { */
|
|
|
|
|
|
|
|
/* /\* Timer 4 runs on GCLK0 (4MHz) *\/ */
|
|
|
|
/* bool t4_capture_channel_enables[] = {false, false}; */
|
|
|
|
/* uint32_t t4_compare_channel_values[] = {15625, 0x0000}; */
|
|
|
|
/* /\* Divide by 256*15625 = 1Hz events *\/ */
|
|
|
|
/* tc_init(TC4, */
|
|
|
|
/* GCLK_GENERATOR_0, */
|
|
|
|
/* TC_COUNTER_SIZE_16BIT, */
|
|
|
|
/* TC_CLOCK_PRESCALER_DIV256, */
|
|
|
|
/* TC_WAVE_GENERATION_NORMAL_FREQ, */
|
|
|
|
/* TC_RELOAD_ACTION_GCLK, */
|
|
|
|
/* TC_COUNT_DIRECTION_UP, */
|
|
|
|
/* TC_WAVEFORM_INVERT_OUTPUT_NONE, */
|
|
|
|
/* false, /\* Oneshot *\/ */
|
|
|
|
/* false, /\* Run in standby *\/ */
|
|
|
|
/* 0x0000, /\* Initial value *\/ */
|
|
|
|
/* 0xFFFF, /\* Top value *\/ */
|
|
|
|
/* t4_capture_channel_enables, /\* Capture Channel Enables *\/ */
|
|
|
|
/* t4_compare_channel_values); /\* Compare Channels Values *\/ */
|
|
|
|
|
|
|
|
/* /\* Timer 4 generates an event on compare channel 0 *\/ */
|
|
|
|
/* struct tc_events events; */
|
|
|
|
/* events.generate_event_on_compare_channel[0] = true; */
|
|
|
|
/* events.generate_event_on_compare_channel[1] = false; */
|
|
|
|
/* events.generate_event_on_overflow = false; */
|
|
|
|
/* events.invert_event_input = false; */
|
|
|
|
/* events.on_event_perform_action = true; */
|
|
|
|
/* events.event_action = TC_EVENT_ACTION_RETRIGGER; */
|
|
|
|
/* tc_enable_events(TC4, &events); */
|
|
|
|
|
|
|
|
/* events_attach_user(0, 4); // Timer 4 is event user on channel 1 */
|
|
|
|
|
|
|
|
/* /\* This event is picked up on event channel 0 *\/ */
|
|
|
|
/* events_allocate(0, */
|
|
|
|
/* EVENTS_EDGE_DETECT_NONE, */
|
|
|
|
/* EVENTS_PATH_ASYNCHRONOUS, */
|
|
|
|
/* 0x29, /\* TC4 MC0 *\/ */
|
|
|
|
/* 0); */
|
|
|
|
|
|
|
|
/* /\* This event is picked up on event channel 1 *\/ */
|
|
|
|
/* events_allocate(1, */
|
|
|
|
/* EVENTS_EDGE_DETECT_NONE, */
|
|
|
|
/* EVENTS_PATH_ASYNCHRONOUS, */
|
|
|
|
/* 0x29, /\* TC4 MC0 *\/ */
|
|
|
|
/* 0); */
|
|
|
|
|
|
|
|
/* tc_enable(TC4); /\* Retrigger event means counter doesn't start yet *\/ */
|
|
|
|
/* tc_start_counter(TC4); /\* We start it manually now *\/ */
|
|
|
|
/* } */
|
|
|
|
/* void osc8m_event_source_disable(void) { */
|
|
|
|
/* tc_disable(TC4); */
|
|
|
|
/* } */
|
2015-03-13 11:39:43 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Configure the timepulse extint to generate events
|
|
|
|
*/
|
|
|
|
void timepulse_extint_event_source(void) {
|
2015-03-15 21:18:33 +00:00
|
|
|
/* Nothing to do: event should be already in place */
|
2015-03-13 11:39:43 +00:00
|
|
|
}
|
|
|
|
void timepulse_extint_event_source_disable(void) {
|
2015-03-15 21:18:33 +00:00
|
|
|
/* Nothing to do here */
|
2015-03-13 11:39:43 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggers a measurements the number of cycles on XOSC
|
2015-03-15 17:34:46 +00:00
|
|
|
*
|
|
|
|
* A callback from the timer interrupt is available. Obviously don't dwell here too long.
|
2015-03-13 11:39:43 +00:00
|
|
|
*/
|
2015-03-15 17:34:46 +00:00
|
|
|
void measure_xosc(enum xosc_measurement_t measurement_t,
|
|
|
|
measurement_result_t callback) {
|
2015-03-13 11:39:43 +00:00
|
|
|
|
|
|
|
measure_state = MEASURE_WAIT_FOR_FIRST_EVENT;
|
|
|
|
_measurement_t = measurement_t;
|
2015-03-15 17:34:46 +00:00
|
|
|
_callback = callback;
|
2015-03-13 11:39:43 +00:00
|
|
|
|
2015-03-15 21:18:33 +00:00
|
|
|
/* Timer 2 runs on GLCK1: XOSC */
|
2015-03-13 11:39:43 +00:00
|
|
|
bool t2_capture_channel_enables[] = {true, true};
|
|
|
|
uint32_t t2_compare_channel_values[] = {0x0000, 0x0000};
|
|
|
|
|
|
|
|
tc_init(TC2,
|
|
|
|
GCLK_GENERATOR_1,
|
|
|
|
TC_COUNTER_SIZE_32BIT,
|
|
|
|
TC_CLOCK_PRESCALER_DIV1,
|
|
|
|
TC_WAVE_GENERATION_NORMAL_FREQ,
|
|
|
|
TC_RELOAD_ACTION_GCLK,
|
|
|
|
TC_COUNT_DIRECTION_UP,
|
|
|
|
TC_WAVEFORM_INVERT_OUTPUT_NONE,
|
|
|
|
false, /* Oneshot */
|
|
|
|
false, /* Run in standby */
|
|
|
|
0x0000, /* Initial value */
|
|
|
|
0xFFFFFFFF, /* Top value */
|
|
|
|
t2_capture_channel_enables, /* Capture Channel Enables */
|
|
|
|
t2_compare_channel_values); /* Compare Channels Values */
|
|
|
|
|
|
|
|
/* Timer 2 event input captures period in CC0, pulse width in CC1 */
|
|
|
|
struct tc_events events;
|
|
|
|
events.generate_event_on_compare_channel[0] = false;
|
|
|
|
events.generate_event_on_compare_channel[1] = false;
|
|
|
|
events.generate_event_on_overflow = false;
|
|
|
|
events.invert_event_input = false;
|
|
|
|
events.on_event_perform_action = true;
|
|
|
|
events.event_action = TC_EVENT_ACTION_PPW;
|
|
|
|
tc_enable_events(TC2, &events);
|
|
|
|
|
|
|
|
/* Enable Interrupt */
|
|
|
|
TC2->COUNT32.INTENSET.reg = (1 << 4); // MC0
|
2015-03-15 21:18:33 +00:00
|
|
|
irq_register_handler(TC2_IRQn, TC2_INT_PRIO); /* Lowish Priority */
|
2015-03-13 11:39:43 +00:00
|
|
|
|
|
|
|
/* Timer 2 is event user on channel 0 */
|
|
|
|
events_attach_user(0, 2);
|
|
|
|
|
|
|
|
/* Configure an event source */
|
|
|
|
switch (measurement_t) {
|
|
|
|
case XOSC_MEASURE_OSC8M:
|
2015-07-03 22:40:47 +00:00
|
|
|
// osc8m_event_source(); // osc8m issues events
|
2015-03-13 11:39:43 +00:00
|
|
|
break;
|
|
|
|
case XOSC_MEASURE_TIMEPULSE:
|
|
|
|
timepulse_extint_event_source(); // timepulse issues events
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tc_enable(TC2);
|
|
|
|
}
|
|
|
|
void measure_xosc_disable(enum xosc_measurement_t measurement_t) {
|
|
|
|
|
|
|
|
tc_disable(TC2);
|
|
|
|
|
|
|
|
switch (measurement_t) {
|
|
|
|
case XOSC_MEASURE_OSC8M:
|
2015-07-03 22:40:47 +00:00
|
|
|
// osc8m_event_source_disable();
|
2015-03-13 11:39:43 +00:00
|
|
|
break;
|
|
|
|
case XOSC_MEASURE_TIMEPULSE:
|
|
|
|
timepulse_extint_event_source_disable();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Triggered on timer 2 capture
|
|
|
|
*/
|
|
|
|
void TC2_Handler(void) {
|
|
|
|
uint32_t capture_value;
|
2015-03-15 21:35:42 +00:00
|
|
|
uint32_t source_freq;
|
2015-03-13 11:39:43 +00:00
|
|
|
|
|
|
|
if (tc_get_status(TC2) & TC_STATUS_CHANNEL_0_MATCH) {
|
|
|
|
tc_clear_status(TC2, TC_STATUS_CHANNEL_0_MATCH);
|
|
|
|
|
|
|
|
switch (measure_state) {
|
|
|
|
case MEASURE_WAIT_FOR_FIRST_EVENT:
|
|
|
|
measure_state = MEASURE_MEASUREMENT; /* Start measurement */
|
|
|
|
break;
|
|
|
|
case MEASURE_MEASUREMENT:
|
|
|
|
/* Measurement done. Read off data */
|
|
|
|
capture_value = tc_get_capture_value(TC2, 0);
|
|
|
|
|
2015-03-15 21:35:42 +00:00
|
|
|
/* Calcuate the frequency of XOSC relative to this source */
|
|
|
|
switch (_measurement_t) {
|
|
|
|
case XOSC_MEASURE_OSC8M:
|
2015-03-16 00:18:41 +00:00
|
|
|
source_freq = capture_value * XOSC_GCLK1_DIVIDE;
|
2015-03-15 21:35:42 +00:00
|
|
|
break;
|
|
|
|
case XOSC_MEASURE_TIMEPULSE:
|
2015-03-16 00:18:41 +00:00
|
|
|
source_freq = capture_value * XOSC_GCLK1_DIVIDE * GPS_TIMEPULSE_FREQ;
|
2015-03-15 21:35:42 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2015-03-15 17:34:46 +00:00
|
|
|
/* Callback if we have one */
|
|
|
|
if (_callback) {
|
2015-03-15 21:35:42 +00:00
|
|
|
_callback(source_freq);
|
2015-03-15 17:34:46 +00:00
|
|
|
}
|
2015-03-13 11:39:43 +00:00
|
|
|
|
|
|
|
/* Disable measurement system */
|
|
|
|
measure_xosc_disable(_measurement_t);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|