Pull to refresh

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

Reading time11 min
Views6.4K

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

Критичный ко времени код на участке ]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 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
Tags:
Hubs:
Total votes 12: ↑9 and ↓3+10
Comments10

Articles