Skip to content

Instantly share code, notes, and snippets.

@mliberty1
Created September 3, 2021 14:56
Show Gist options
  • Save mliberty1/a3c1332cff193f9e7f2cd47925d2cb30 to your computer and use it in GitHub Desktop.
Save mliberty1/a3c1332cff193f9e7f2cd47925d2cb30 to your computer and use it in GitHub Desktop.
ATSAMS70 64-bit time counter
/*
* 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