Исходные предпосылки
Критичный ко времени код на участке ]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 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 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 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 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