Исходные предпосылки
Критичный ко времени код на участке ]L1, L7[ регулярно выполняется за время T1.
Аппаратные прерывания T2, T3 и Т4 замедляют критичный код T1 асинхронно. Точки срабатывания аппаратных прерываний условно обозначены метками L1 .. L6.
Как следствие, критичный код выполняется за время T = T1, при отсутствии аппаратных прерываний, и за время T = T1 + T2 +T3 + T4, при вызове всех обработчиков на участке ]L1, L7[. Возможны варианты на множестве {T2, T3, T4}.
Постановка задачи
Прикладная библиотека для динамической оценки и корректировки малых интервалов в реальном времени с погрешностью менее 10 us ( 0,00001 s ), дополняющая функциональность стандартной библиотеки STM32*_HAL_Driver.
Число контролируемых интервалов времени — без ограничений.
Измерение малых интервалов с заданной точностью в любых режимах генерации и оптимизации ( -O* ) бинарного кода.
Сравнение и корректировка малых интервалов по сдвигу контрольных меток на оси времени.
Решение задачи
Архитектура ARM Cortex M* содержит два счётчика, определяющих системное время:
uwTick — системный счётчик времени, увеличивается на единицу каждую миллисекунду (ms);
(*SysTick).VAL — тактовый счётчик, уменьшается на единицу синхронно с тактовой частотой.
При достижении счётчиком (*SysTick).VAL нулевого значения:
генерируется прерывание, увеличивающее на единицу uwTick;
восстанавливается значения счётчика до величины ( (*SysTick).LOAD - 1 ).
Момент системного времени микроконтроллера фиксируется в структуре данных.
typedef __IO struct
{
__IOM int32_t cc;
__IOM uint32_t ms;
} STimeStamp;
Далее необходимый и достаточный набор функций для работы со структурой данных.
«this_moment_calibrate()» — эта функция вызывается один раз до вызова любой другой.
«this_moment_sync( uint32_t stc )» — эта функция ожидает пока значение (*SysTick).VAL окажется больше uint32_t stc, после чего возвращает управление, тем самым гарантируя, что обработчик «HAL_IncTick(void)» будет вызван после «эталонного» измерения.
Параметр uint32_t stc принимает любое значение в диапазоне
] 0 .. (*SysTick).LOAD [.
Пример.
int32_t L1 = RAND_NUMBER_32;
int32_t L2 = RAND_NUMBER_32;
int32_t L3 = 0;
STimeStamp stm, tm;
this_moment_sync( (*SysTick).LOAD & SysTick_LOAD_RELOAD_Msk ) / 3 * 2 );
this_moment( &stm );
L3 = L1 + L2;
this_moment( &tm );
int32_t gap = this_moment_gap( &stm, &tm );
«this_moment( STimeStamp *tm )» — эта функция фиксирует текущий момент времени в структуре tm.
«this_moment_cmp( STimeStamp *stm, STimeStamp *tm )» - эта функция сравнивает два момента времени и возвращает:
( 1 ) если stm > tm, момент времени stm был раньше tm;
( 0 ) если stm == tm, оба момента времени совпадают;
(-1 ) если stm < tm, момент времени stm случился позже tm.
«this_moment_dif( STimeStamp *stm, STimeStamp *tm )» - эта функция возвращает разницу между двумя моментами времени, измеренную в периодах тактовой частоты.
«this_moment_gap( STimeStamp *stm, STimeStamp *tm )» - эта функция возвращает разницу между двумя моментами времени, измеренную в периодах тактовой частоты, с поправкой на время вызова функции «this_moment( STimeStamp *tm )».
«this_moment_shft( STimeStamp *tm, int32_t dtm )» - эта функция вносит поправку в момент времени tm на величину dtm, измеренную в периодах тактовой частоты.
Группа функций, повторяющие те же действия, но в размерности 1 us ( 0,000001 s ):
«this_moment_dif_us( STimeStamp *stm, STimeStamp *tm )»;
«this_moment_gap_us( STimeStamp *stm, STimeStamp *tm )»;
«this_moment_shft_us( STimeStamp *tm, int32_t dtm )».
Допустимые интервалы
Максимальные значения малых интервалов зависят от тактовой частоты микроконтроллера.
Погрешность измерения
Погрешность измерения соразмерна среднему времени работы функций библиотеки.
Иллюстрация ниже демонстрирует время работы всех функций библиотеки, измеренная в периодах тактовой частоты (сс) и микросекундах (us).
Экспресс-оценка точности там же — время выполнения арифметических операторов на целых числах в периодах тактовой частоты (сс) и микросекундах (us).
Среднее время работы функций библиотеки на частоте 32MHz — 1 us ( 0,000001 s ).
Гарантированный интервал измерения — 10 us ( 0,00001 s ).
Время работы функций зависит от тактовой частоты микроконтроллера.
Чем выше тактовая частота, тем хуже утилизация, что демонстрируют таблица и график.
В таблице и на графике время работы функций в периодах тактовой частоты, тактовая частота микроконтроллера (MHz), оптимизация -Os.
Ухудшение утилизации с ростом тактовой частоты в пределах ожидания: +/-1 us.
Оптимизация оказывает слабое влияние на время работы функций библиотеки, ожидаемая погрешность +/-1 us.
Оценка погрешности выполнена в среде CubeIDE 1.4 (gcc version 5.4.0), на микроконтроллере ARM Cortex M4 — STM32F303VCT6, 48MHz.
Проверка погрешности измерения на микроконтроллере ARM Cortex M0 показала похожие результаты.
Повторяется ухудшение утилизации с ростом тактовой частоты.
В таблице и на графике время работы функций в периодах тактовой частоты, тактовая частота микроконтроллера (MHz), оптимизация -Os.
Оптимизация оказывает слабое влияние на время работы функций библиотеки, ожидаемая погрешность +/-1 us.
Оценка погрешности выполнена в среде CubeIDE 1.4 (gcc version 5.4.0), на микроконтроллере ARM Cortex M0 — STM32F030R8T6, 48MHz.
Заключение
Говоря о гарантированной точности измерения допускаем, что ошибка измерения должна быть на порядок меньше единицы измерения.
Другими словами. Для гарантированной работы с интервалами порядка 10 us, погрешность измерения должна быть в пределах +/- 1 us.
Демонстрируя прогнозируемую погрешность +/-1 us, библиотека уменьшает тактовый период прикладной задачи на 3-и порядка с 0,01 s (10 000 us) до 0,00001 s (10 us). Соответственно раскрывается потенциал к росту утилизации микроконтроллера.
Слабые места прикладной библиотеки:
прямое обращение к системным ресурсам: uwTick, SysTick;
зависимость(**) от параметров сборки и оптимизации бинарного кода.
Зависимость(**) в пределах заявленной погрешности измерения +/- 1 us.
Библиотека тестировалась на 3-х контроллерах: STM32F401RET6, STM32F303VCT6 и STM32F030R8T6.
Тестирование продемонстрировало удовлетворительный результат с заданной степенью точности.
Прикладная библиотека
Файл заголовка
/******************************************************************************
* File: this_moment.h Created on Jan 6, 2022
*
* Copyright (c) 2022, "Nikolay E. Garbuz" <nik_garbuz@list.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by "Nikolay E. Garbuz" <nik_garbuz@list.ru>
* Modified by
*
* TAB Size .EQ 4
********************************************************************************/
#ifndef TM_THIS_MOMENT_H_
#define TM_THIS_MOMENT_H_
#include "main.h"
//*********************************************
// platform defines
//*********************************************
#if !( defined( STM32F4 ) || defined( STM32F3 ) || defined( STM32F0 ) )
# pragma GCC error "Untested ARM Cortex M planform"
#endif
#ifdef __cplusplus
extern "C"
{
#endif
//*********************************************
// global defines
//*********************************************
#define TM_MS_TICK uwTick // sys ms counter
#define TM_MS_RANGE 1000ul // ms per sec
#define TM_US_RANGE ( TM_MS_RANGE * TM_MS_RANGE ) // us per sec
#define TM_CPU_CLK_MHz ( SystemCoreClock / TM_US_RANGE ) // CPU CLK in MHz
#define TM_SHIFT_MAX INT32_MAX
#define TM_SHIFT_MAX_US ( TM_SHIFT_MAX / TM_CPU_CLK_MHz )
#define Hz_to_ms(Hz) (TM_MS_RANGE / Hz)
#define Hz_to_us(Hz) (TM_US_RANGE / Hz)
#define ms_to_Hz(ms) (TM_MS_RANGE * ms)
#define us_to_Hz(us) (TM_US_RANGE * us)
//*********************************************
// STimeStamp struct
//*********************************************
typedef __IO struct
{
__IOM int32_t cc;
__IOM uint32_t ms;
} STimeStamp;
/*********************************************
* helper functions
*********************************************/
void this_moment_sync( uint32_t stc );
/*********************************************
* service functions
* do it first before any others
*********************************************/
void this_moment_calibrate();
/*********************************************
* remember the current moment
* tm - this moment
*********************************************/
void this_moment( STimeStamp *tm );
/*********************************************
* compare two moments by timeline
* stm - start moment
* tm - this moment
*
* ret: ( 1 ) if stm > tm // stm earlier tm
* ret: ( 0 ) if stm == tm // stm equal tm
* ret: (-1 ) if stm < tm // stm later tm
*
*********************************************/
int32_t this_moment_cmp( STimeStamp *stm, STimeStamp *tm );
/*********************************************
* measuring delay between two moments in cpu clocks
* stm - start moment
* tm - this moment
*********************************************/
int32_t this_moment_dif( STimeStamp *stm, STimeStamp *tm );
int32_t this_moment_gap( STimeStamp *stm, STimeStamp *tm );
/*********************************************
* shifting the moment at timeline by cpu clocks
* tm - this moment
* dtm - shift of this moment by cpu clocks
*********************************************/
void this_moment_shft( STimeStamp *tm, int32_t dtm );
/*********************************************
* measuring delay between two moments in us
* stm - start moment
* tm - this moment
*********************************************/
int32_t this_moment_dif_us( STimeStamp *stm, STimeStamp *tm );
int32_t this_moment_gap_us( STimeStamp *stm, STimeStamp *tm );
/*********************************************
* shift the moment at timeline by us
* 1 us = 1/1000 ms
*
* tm - this moment
* dtm - shift of this moment by us
*********************************************/
void this_moment_shft_us( __IO STimeStamp *tm, int32_t tdm );
#ifdef __cplusplus
}
#endif
#endif /* TM_THIS_MOMENT_H_ */
Исходный код
/******************************************************************************
* File: this_moment.c Created on Jan 6, 2022
*
* Copyright (c) 2022, "Nikolay E. Garbuz" <nik_garbuz@list.ru>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by "Nikolay E. Garbuz" <nik_garbuz@list.ru>
* Modified by
*
* TAB Size .EQ 4
********************************************************************************/
#include <this_moment.h>
#pragma GCC push_options
#pragma GCC optimize ("Os")
/*********************************************
* constants witch will calculated by runtime
*********************************************/
__IO int32_t tm_corrector = 0;
__IO int32_t tm_lad_cc = 1;
__IO int32_t tm_lad_us = 1;
/*********************************************
* helper macros
*********************************************/
//--------------------------------------------
#define SYSTICK_SYNC( t ) \
while( ( (*SysTick).VAL & SysTick_VAL_CURRENT_Msk ) < t )
//--------------------------------------------
#define M_DIF( dff_cc, stm, tm ) \
\
dff_cc = tm->ms; \
dff_cc -= stm->ms; \
dff_cc *= tm_lad_cc; \
dff_cc += stm->cc; \
dff_cc -= tm->cc
//--------------------------------------------
#define M_GAP( dff_cc ) \
\
if ( dff_cc > 0 ) \
dff_cc -= tm_corrector; \
else \
dff_cc += tm_corrector
//--------------------------------------------
#define M_SHFT( dms, dcc, tm, dtm ) \
\
dms = dtm / tm_lad_cc; \
dcc = dtm - ( dms * tm_lad_cc ); \
dcc = tm->cc - dcc; \
\
if ( dcc < 0 ) \
dcc += tm_lad_cc, dms++; \
\
if ( dcc >= tm_lad_cc ) \
dcc -= tm_lad_cc, dms--; \
\
tm->ms += dms; \
tm->cc = dcc
//--------------------------------------------
#define M_TO_US( sgn, dff_cc, dff_us ) \
\
sgn = ( tm_lad_us >> 1 ); \
if ( dff_cc < 0 ) \
sgn *= -1; \
\
dff_us = ( dff_cc + sgn ) / tm_lad_us \
/*********************************************
* helper functions
*********************************************/
void this_moment_sync( uint32_t stc )
{
SYSTICK_SYNC( stc );
}
/*********************************************
* service functions
* do it first and one time before any others
*********************************************/
void __attribute__((optimize("Og"))) this_moment_calibrate()
{
__IO STimeStamp stm;
__IO STimeStamp tm;
// Initialize the global variables
tm_lad_cc = ( ( *SysTick ).LOAD & SysTick_LOAD_RELOAD_Msk ) + 1;
tm_lad_us = (int32_t) ( SystemCoreClock / TM_US_RANGE );
tm_corrector = 0;
this_moment_sync( tm_lad_cc >> 1 );
this_moment( &stm );
this_moment( &tm );
tm_corrector = this_moment_gap( &stm, &tm );
}
/*********************************************
* remember the current moment
*
* ret: tm - this moment
*
*********************************************/
void __attribute__((optimize("Os"))) this_moment( STimeStamp *tm )
{
register int32_t rc;
register uint32_t rm;
while ( ( rc = ( *SysTick ).VAL ) < 7 )
;
rm = TM_MS_TICK;
tm->cc = rc;
tm->ms = rm;
}
/*********************************************
* compare two moments by timeline
*
* stm - start moment
* tm - this moment
*
* ret: ( 1 ) if stm > tm // stm earlier tm
* ret: ( 0 ) if stm == tm // stm equal tm
* ret: (-1 ) if stm < tm // stm later tm
*
*********************************************/
int32_t this_moment_cmp( STimeStamp *stm, STimeStamp *tm )
{
register int32_t result = 0;
if ( stm->ms == tm->ms )
{
if ( stm->cc != tm->cc )
{
if ( stm->cc > tm->cc )
result = 1;
else
result = -1;
}
}
else
{
if ( stm->ms < tm->ms )
result = 1;
else
result = -1;
}
return result;
}
/*********************************************
* measuring diff between two moments in cpu clocks
*
* stm - start moment
* tm - this moment
*
* ret: (int32_t) diff between stm and tm in cpu clocks
*
*********************************************/
int32_t this_moment_dif( STimeStamp *stm, STimeStamp *tm )
{
register int32_t dff_cc;
M_DIF( dff_cc, stm, tm );
return dff_cc;
}
/*********************************************
* measuring gap between two moments in cpu clocks
*
* stm - start moment
* tm - this moment
*
* ret: (int32_t) gap between stm and tm in cpu clocks
*
*********************************************/
int32_t this_moment_gap( STimeStamp *stm, STimeStamp *tm )
{
register int32_t dff_cc;
M_DIF( dff_cc, stm, tm );
M_GAP( dff_cc );
return dff_cc;
}
/*********************************************
* shift the moment at timeline by cpu clocks
*
* tm - this moment
* dtm - shift of this moment by cpu clocks
*
* ret: tm shifted by cpu clocks
*
*********************************************/
void this_moment_shft( STimeStamp *tm, int32_t dtm )
{
int32_t dms, dcc;
M_SHFT( dms, dcc, tm, dtm );
}
/*********************************************
* measuring diff between two moments in us
* 1 us = 1/1000 ms
*
* stm - start moment
* tm - this moment
*
* ret: (int32_t) diff between stm and tm in us
*
*********************************************/
int32_t this_moment_dif_us( STimeStamp *stm, STimeStamp *tm )
{
int32_t sng, dff_cc, dff_us;
M_DIF( dff_cc, stm, tm );
M_TO_US( sng, dff_cc, dff_us );
return dff_us;
}
/*********************************************
* measuring gap between two moments in us
* 1 us = 1/1000 ms
*
* stm - start moment
* tm - this moment
*
* ret: (int32_t) gap between stm and tm in us
*
*********************************************/
int32_t this_moment_gap_us( STimeStamp *stm, STimeStamp *tm )
{
int32_t sgn, dff_cc, dff_us;
M_DIF( dff_cc, stm, tm );
M_GAP( dff_cc );
M_TO_US( sgn, dff_cc, dff_us );
return dff_us;
}
/*********************************************
* shift the moment at timeline by us
* 1 us = 1/1000 ms
*
* tm - this moment
* dus - shift of this moment by us
*
* ret: tm shifted by us
*
*********************************************/
void this_moment_shft_us( STimeStamp *tm, int32_t dtm )
{
int32_t dms, dcc;
dtm *= tm_lad_us;
M_SHFT( dms, dcc, tm, dtm );
}
#pragma GCC pop_options
Комментарии (10)
buratino
10.01.2022 23:47+4В заголовка заявлено некое "ARM Cortex M*" (со звездочкой)
что это такое автор не объясняет. Гугль по звездочку ничего вразумительного не говорит..
На скриншотах видим STM и Cortex M4...
Однако STM не равно Cortex, а M4 весьма отличается от M0
В разделе "постановка задачи" никакой постановки задачи нет.. ну и т.д. и т.п.
numeric Автор
11.01.2022 00:43+1Однако STM не равно Cortex, а M4 весьма отличается от M0
Вы абсолютно правы.
В угоду Вашей правоты замечу, что задача планировалась под чип STM32L031, но в процессе подготовки текста под рукой оказались другие оценочные платы, на что указывает директива условной компиляции в файле заголовка.
Учить читателя, что-либо доказывать, объяснять - такой цели здесь нет. Есть практический опыт, изложенный в доступной форме, пригодной для проверки, обсуждения, заимствования и улучшения. Лицензия в заголовке исходного кода это позволяет.
В разделе "постановка задачи" никакой постановки задачи нет..
Обременять популярную статью согласно "ГОСТ Р 19*", "ГОСТ Р 34*" или там "ГОСТ Р ИСО 9000, 9001, 9004" и т.п. считаю нецелесообразным.
(imho)
IgorPie
Подход - серьезный. Сам полюбливаю измерять быстродействие "по бордюру" (Спектрумисты - поймут).
Но, создатели кортексов наградили их чудесным средством DWT_CYCCNT, которое избавляет от многих головоломок, особенно в h7 серии, с её могучим кэшем, но который надо иногда правильно готовить. Здесь как бы ему прямая дорога и почёт.
lamerok
Проблема в том, что этот модуль используется для отладки и его включение не очень хорошо в части безопасности и доступа к прошивке и её данным. Поэтому обычно в продаёте он отключён.
predator86
Не могли бы Вы пояснить как именно счётчик тактов влияет на безопасность, или хотя бы дать ссылочку на объяснение этой проблемы. Вопрос на самом деле очень важный!
lamerok
Сам счетчик возможно и не влияет, влияет модуль отладки, а так как для задействования счетчика нужно подключить весь модуль, то соотвественно все фичи этого модуля включаются. Там не зря стоит небольшая защита от его включения, чтобы включить DWT, насколько я помно, нужно разблокировать отладочный регистры, точно не помню уже LAR вроде записать какой-то магическое число. Где-то у меня есть развернутый ответ, если найду опишу, где настоятельно не рекомендовали в продукте использовать этот модуль, если устройство используется в какой-нибудь защищенной системе.
predator86
Проверил и оказалось что для счетчика тактов LAR не нужен, его используют для активации ITM
Проверено на STM32F405RG, STM32H745ZI и STM32U575ZI.
Если LAR не активировать то проблемы безопасности нет?
lamerok
Я постараюсь найти ответ и уточню попозже, пока не могу найти переписку, было давненько.
sami777
Постоянно пользую DWT для сбора метрик, постоянно во время отладки. Никогда не замечал проблем.
numeric Автор
Согласен, DWT - интересная вещица.
В статье опыт решения прикладной задачи динамического контроля и корректировки множества, в том числе вложенных, малых интервалов времени, в т.ч. в режиме отладки, с минимальным набором аппаратных ресурсов на бюджетном микроконтроллере M0.
Полученный метод позволяет оперировать вложенными интервалами времени от нескольких тактов до нескольких часов с известной точностью, что и требовалось в прикладной задаче.