[recovery mode] ARM Cortex M* — «сколько вешать в граммах»

Исходные предпосылки

Критичный ко времени код на участке ]L1, L7[ регулярно выполняется за время T1.

Аппаратные прерывания T2, T3 и Т4 замедляют критичный код T1 асинхронно. Точки срабатывания аппаратных прерываний условно обозначены метками L1 .. L6.

Как следствие, критичный код выполняется за время T = T1, при отсутствии аппаратных прерываний, и за время T = T1 + T2 +T3 + T4, при вызове всех обработчиков на участке ]L1, L7[. Возможны варианты на множестве {T2, T3, T4}.

Постановка задачи

  1. Прикладная библиотека для динамической оценки и корректировки малых интервалов в реальном времени с погрешностью менее 10 us ( 0,00001 s ), дополняющая функциональность стандартной библиотеки STM32*_HAL_Driver.

  2. Число контролируемых интервалов времени — без ограничений.

  3. Измерение малых интервалов с заданной точностью в любых режимах генерации и оптимизации ( -O* ) бинарного кода.

  4. Сравнение и корректировка малых интервалов по сдвигу контрольных меток на оси времени.

Решение задачи

Архитектура 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 )».

Допустимые интервалы

Максимальные значения малых интервалов зависят от тактовой частоты микроконтроллера.

Размеры интервалов ограничены разрядностью данных - int32_t
Размеры интервалов ограничены разрядностью данных — int32_t

Погрешность измерения

Погрешность измерения соразмерна среднему времени работы функций библиотеки.

Иллюстрация ниже демонстрирует время работы всех функций библиотеки, измеренная в периодах тактовой частоты (сс) и микросекундах (us).

Экспресс-оценка точности там же — время выполнения арифметических операторов на целых числах в периодах тактовой частоты (сс) и микросекундах (us).

zero - время работы пустого участка кода на языке "C".
zero — время работы пустого участка кода на языке «C».

Среднее время работы функций библиотеки на частоте 32MHz — 1 us ( 0,000001 s ).

Гарантированный интервал измерения — 10 us ( 0,00001 s ).

Время работы функций зависит от тактовой частоты микроконтроллера.
Чем выше тактовая частота, тем хуже утилизация, что демонстрируют таблица и график.

Время работы функций в периодах тактовой частоты, столбцы - частота микроконтроллера
Время работы функций в периодах тактовой частоты, столбцы — частота микроконтроллера

В таблице и на графике время работы функций в периодах тактовой частоты, тактовая частота микроконтроллера (MHz), оптимизация -Os.

Тактовая частота микроконтроллера по оси X (MHz), оптимизация -Os
Тактовая частота микроконтроллера по оси X (MHz), оптимизация -Os

Ухудшение утилизации с ростом тактовой частоты в пределах ожидания: +/-1 us.

Оптимизация оказывает слабое влияние на время работы функций библиотеки, ожидаемая погрешность +/-1 us.

Время работы функций в периодах тактовой частоты, столбцы - флаг оптимизации
Время работы функций в периодах тактовой частоты, столбцы — флаг оптимизации
Время работы функций в периодах тактовой частоты, меньше - лучше
Время работы функций в периодах тактовой частоты, меньше — лучше

Оценка погрешности выполнена в среде CubeIDE 1.4 (gcc version 5.4.0), на микроконтроллере ARM Cortex M4 — STM32F303VCT6, 48MHz.

Проверка погрешности измерения на микроконтроллере ARM Cortex M0 показала похожие результаты.

Повторяется ухудшение утилизации с ростом тактовой частоты.

Время работы функций в периодах тактовой частоты, столбцы - частота микроконтроллера
Время работы функций в периодах тактовой частоты, столбцы — частота микроконтроллера

В таблице и на графике время работы функций в периодах тактовой частоты, тактовая частота микроконтроллера (MHz), оптимизация -Os.

Частота микроконтроллера по оси X (MHz), оптимизация -Os
Частота микроконтроллера по оси X (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

Читайте так же: