Created
September 3, 2021 14:56
-
-
Save mliberty1/a3c1332cff193f9e7f2cd47925d2cb30 to your computer and use it in GitHub Desktop.
ATSAMS70 64-bit time counter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright 2021 Jetperch LLC | |
* | |
* Licensed under the Apache License, Version 2.0 (the "License"); | |
* you may not use this file except in compliance with the License. | |
* You may obtain a copy of the License at | |
* | |
* http://www.apache.org/licenses/LICENSE-2.0 | |
* | |
* Unless required by applicable law or agreed to in writing, software | |
* distributed under the License is distributed on an "AS IS" BASIS, | |
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
* See the License for the specific language governing permissions and | |
* limitations under the License. | |
*/ | |
/* | |
* I spent far too long trying to get a monotonically increasing, | |
* fast, 64-bit performance counter on the Microchip/Atmel ATSAMS70. | |
* This gist captures the winning solution using the Cortex-M7 cycle | |
* counter along with the initial solution using the | |
* timer-counter peripheral TC0. | |
*/ | |
// -------------------------------------------------------------------------- | |
// Winning solution, use the Cortex-M7 Cycle Counter | |
static volatile uint32_t reltime_upper; | |
static volatile uint32_t reltime_lower_last; | |
/** | |
* @brief Get a high-performance, 64-bit cycle counter. | |
* | |
* @return The counter value. | |
* | |
* This function is guaranteed monotonic. However, it must | |
* be called more frequently than the rollover period, which | |
* is 2**32 / cpu_freq ~= 10 seconds. This counter directly | |
* measures CPU cycles. Sleeping the processor or adjusting | |
* the processor clock frequency will affect this counter. | |
* | |
* If you need to high performance counter for shorter durations, | |
* simply read DWT->CYCCNT for lower overhead. | |
*/ | |
uint64_t dbg_cycle_counter() { | |
uint32_t primask = __get_PRIMASK(); | |
__disable_irq(); | |
uint32_t upper = reltime_upper; | |
uint32_t now = DWT->CYCCNT; | |
if (now < reltime_lower_last) { | |
upper += 1; | |
reltime_upper = upper; | |
} | |
reltime_lower_last = now; | |
__DMB(); | |
if (primask) { | |
__enable_irq(); | |
} | |
return now | (((uint64_t) upper) << 32); | |
} | |
// -------------------------------------------------------------------------- | |
// Original solution using the TC0 | |
// Requires programmable clock 6 | |
void power_init() { | |
hri_pmc_write_PCK_reg(PMC, 6, PMC_PCK_CSS(CONF_CLK_GEN_PCK6_SRC) | PMC_PCK_PRES(CONF_PCK6_PRESC - 1)); | |
while (!hri_pmc_get_SR_PCKRDY6_bit(PMC)) { | |
/* Wait until PCK6 clock is ready */ | |
} | |
hri_pmc_write_SCER_reg(PMC, PMC_SCER_PCK6); | |
_pmc_enable_periph_clock(ID_TC0_CHANNEL0); | |
_pmc_enable_periph_clock(ID_TC0_CHANNEL1); | |
_pmc_enable_periph_clock(ID_TC0_CHANNEL2); | |
} | |
static volatile uint32_t timer_upper_16 = 0; | |
static void timer_initialize() { | |
// https://community.atmel.com/forum/same70-problem-chaining-three-16-bit-timers | |
NVIC_DisableIRQ(TC2_IRQn); | |
NVIC_SetPriority(TC2_IRQn, 1); // more urgent than FreeRTOS | |
timer_upper_16 = 0; | |
TC0->TC_WPMR = TC_WPMR_WPKEY_PASSWD; | |
// Need to "prime" channel 1 & 2 since first tick performs a synchronous reset | |
TC0->TC_BMR = TC_BMR_TC0XC0S_TCLK0 | TC_BMR_TC1XC1S_TIOA0 | TC_BMR_TC2XC2S_TIOA0; | |
TC0->TcChannel[0].TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 | TC_CMR_WAVEFORM_WAVSEL_UP | TC_CMR_WAVE | |
| TC_CMR_WAVEFORM_ACPA_SET | TC_CMR_WAVEFORM_ACPC_CLEAR | TC_CMR_WAVEFORM_ASWTRG_SET | TC_CMR_CLKI; | |
TC0->TcChannel[0].TC_RA = 0; | |
TC0->TcChannel[0].TC_RC = 1; | |
TC0->TcChannel[1].TC_CMR = TC_CMR_TCCLKS_XC1 | TC_CMR_WAVEFORM_WAVSEL_UP | TC_CMR_WAVE | |
| TC_CMR_WAVEFORM_ACPA_SET | TC_CMR_WAVEFORM_ACPC_CLEAR | TC_CMR_WAVEFORM_ASWTRG_CLEAR | TC_CMR_CLKI; | |
TC0->TcChannel[1].TC_RA = 0; | |
TC0->TcChannel[1].TC_RC = 1; | |
TC0->TcChannel[2].TC_CMR = TC_CMR_TCCLKS_XC2 | TC_CMR_WAVEFORM_WAVSEL_UP | TC_CMR_WAVE | |
| TC_CMR_WAVEFORM_ACPA_SET | TC_CMR_WAVEFORM_ACPC_CLEAR | TC_CMR_WAVEFORM_ASWTRG_CLEAR | TC_CMR_CLKI; | |
TC0->TcChannel[2].TC_RA = TC0->TcChannel[1].TC_RA; | |
TC0->TcChannel[2].TC_RC = TC0->TcChannel[1].TC_RC; | |
TC0->TcChannel[2].TC_IER = TC_IER_COVFS; | |
TC0->TcChannel[2].TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG; | |
TC0->TcChannel[1].TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG; | |
TC0->TcChannel[0].TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG; | |
__DMB(); | |
while (TC0->TcChannel[0].TC_CV < 4) { | |
; // wait | |
} | |
// Primed, now connect channel 1 to channel 2 and continue | |
TC0->TcChannel[0].TC_CCR = TC_CCR_CLKDIS; | |
TC0->TC_BMR = TC_BMR_TC0XC0S_TCLK0 | TC_BMR_TC1XC1S_TIOA0 | TC_BMR_TC2XC2S_TIOA1; | |
TC0->TcChannel[0].TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG; | |
NVIC_ClearPendingIRQ(TC2_IRQn); | |
NVIC_EnableIRQ(TC2_IRQn); | |
} | |
void TC2_Handler(void) { | |
TC0->TcChannel[2].TC_SR; | |
timer_upper_16 += 1; | |
} | |
uint64_t dbg_reltime_us() { | |
uint32_t upper; | |
uint32_t lower1; | |
uint32_t lower2; | |
uint32_t primask = __get_PRIMASK(); | |
__disable_irq(); | |
do { | |
lower1 = TC0->TcChannel[0].TC_CV; | |
upper = (TC0->TcChannel[1].TC_CV) | (TC0->TcChannel[2].TC_CV << 16); | |
__DMB(); | |
lower2 = TC0->TcChannel[0].TC_CV; | |
} while (lower1 > lower2); | |
if (primask) { | |
__enable_irq(); | |
} | |
return lower2 | (((uint64_t) upper) << 16) | (((uint64_t) timer_upper_16) << 48); | |
} | |
void test_timer() { | |
FBP_LOGI("Test timer"); | |
uint64_t t1 = dbg_reltime_us(); | |
uint64_t t2; | |
for (int i = 0; i < 1000000; ++i) { | |
t2 = dbg_reltime_us(); | |
if (t2 < t1) { | |
dbg_print_sync("Timer went backwards: 0x%08llx 0x%08llx\n", t1, t2); | |
//FBP_FATAL("timer failed"); | |
} | |
t1 = t2; | |
} | |
dbg_print_sync("Timer end: 0x%08llx\n", t2); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment