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_tL1 = 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_TICKuwTick// sys ms counter  #define TM_MS_RANGE1000ul// 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_MAXINT32_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 


ссылка на оригинал статьи https://habr.com/ru/post/599727/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *