/* * Functions for controlling and calibrating against the external oscillator * Copyright (C) 2015 Richard Meadows * * 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 #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" #include "watchdog.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; measurement_result_t _callback; /** * Configures external oscillator, waits for it to stabilise, and * connects it to GLCK1. */ void xosc_init(void) { #if USE_XOSC 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)); #endif /* Configure GCLK1 to XOSC */ system_gclk_gen_set_config(GCLK_GENERATOR_1, #if USE_XOSC GCLK_SOURCE_XOSC, /* Source */ #else GCLK_SOURCE_OSC8M, /* Source */ #endif false, /* High When Disabled */ XOSC_GCLK1_DIVIDE, /* Division Factor */ false, /* Run in standby */ false); /* Output Pin Enable */ /* Enable GCLK1 */ system_gclk_gen_enable(GCLK_GENERATOR_1); } 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); } /** * Configure timer 4 to generate events at 1Hz of OSC8M */ /* 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); */ /* } */ /** * Configure the timepulse extint to generate events */ void timepulse_extint_event_source(void) { /* Nothing to do: event should be already in place */ } void timepulse_extint_event_source_disable(void) { /* Nothing to do here */ } /** * Triggers a measurements the number of cycles on XOSC * * A callback from the timer interrupt is available. Obviously don't dwell here too long. */ void measure_xosc(enum xosc_measurement_t measurement_t, measurement_result_t callback) { measure_state = MEASURE_WAIT_FOR_FIRST_EVENT; _measurement_t = measurement_t; _callback = callback; /* Timer 2 runs on GLCK1: XOSC */ 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 irq_register_handler(TC2_IRQn, TC2_INT_PRIO); /* Lowish Priority */ /* Timer 2 is event user on channel 0 */ events_attach_user(0, 2); /* Configure an event source */ switch (measurement_t) { case XOSC_MEASURE_OSC8M: // osc8m_event_source(); // osc8m issues events 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: // osc8m_event_source_disable(); break; case XOSC_MEASURE_TIMEPULSE: timepulse_extint_event_source_disable(); break; } } /** * Triggered on timer 2 capture */ void TC2_Handler(void) { uint32_t capture_value; uint32_t source_freq; awake_do_watchdog(); 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); /* Calcuate the frequency of XOSC relative to this source */ switch (_measurement_t) { case XOSC_MEASURE_OSC8M: source_freq = capture_value * XOSC_GCLK1_DIVIDE; break; case XOSC_MEASURE_TIMEPULSE: source_freq = capture_value * XOSC_GCLK1_DIVIDE * GPS_TIMEPULSE_FREQ * 2; break; } /* Callback if we have one */ if (_callback) { _callback(source_freq); } /* Disable measurement system */ measure_xosc_disable(_measurement_t); } } }