Я переношу некоторый код с M3 на M4, который использует 3 NOP, чтобы обеспечить очень короткую задержку между изменениями часов последовательного вывода. Набор инструкций M3 определяет время для NOP как 1 цикл. Я заметил, что NOP в M4 вообще не обязательно задерживают какое-либо время. Я знаю, что мне нужно будет отключить оптимизацию компилятора, но я ищу низкоуровневую команду, которая даст мне надежное, воспроизводимое время. На практике в этом конкретном случае последовательный порт используется очень редко и может быть очень медленным, но я все же хотел бы знать, как лучше всего получить задержки на уровне цикла.
Как получить надежные короткие задержки Cortex M4
Ответы (4)
Если вам нужны такие очень короткие, но детерминированные «как минимум» задержки, возможно, вы могли бы рассмотреть возможность использования других инструкций, кроме nop
, которые имеют детерминированную ненулевую задержку.
NOP Cortex-M4 как описано не обязательно требует много времени.
Вы можете заменить его, скажем, на and reg, reg
или на что-то приблизительно эквивалентное nop
в контексте. В качестве альтернативы, при переключении GPIO вы также можете повторить сами инструкции ввода-вывода, чтобы обеспечить минимальную длину состояния (например, если ваша инструкция записи GPIO занимает не менее 5 нс, повторите ее пять раз, чтобы получить не менее 25 нс). Это могло бы хорошо работать даже в C, если бы вы вставляли nops в программу C (просто повторите запись в порт, если он volatile
, как и должно быть, компилятор не удалит повторные обращения).
Конечно, это относится только к очень коротким задержкам, в противном случае для коротких задержек, как упоминалось другими, занятые циклы, ожидающие некоторого источника синхронизации, будут работать намного лучше (они берут по крайней мере такты, необходимые для выборки источника синхронизации, установки цели, и пройти один раз цикл ожидания).
Используйте регистр подсчета циклов (DWT_CYCCNT), чтобы получить высокоточное время!
Примечание. Я также проверил это с помощью цифровых выводов и осциллографа, и это очень точно.
См. stopwatch_delay(ticks
) и вспомогательный код ниже, который использует регистр STM32 DWT_CYCCNT, специально разработанный для подсчета фактических тактовых импульсов, расположенный по адресу 0xE0001004.
См. main
пример, который использует STOPWATCH_START
/STOPWATCH_STOP
для измерения того, сколько времени на самом деле заняло stopwatch_delay(ticks)
, используя CalcNanosecondsFromStopwatch(m_nStart, m_nStop)
.
Измените ввод ticks
, чтобы внести коррективы.
uint32_t m_nStart; //DEBUG Stopwatch start cycle counter value
uint32_t m_nStop; //DEBUG Stopwatch stop cycle counter value
#define DEMCR_TRCENA 0x01000000
/* Core Debug registers */
#define DEMCR (*((volatile uint32_t *)0xE000EDFC))
#define DWT_CTRL (*(volatile uint32_t *)0xe0001000)
#define CYCCNTENA (1<<0)
#define DWT_CYCCNT ((volatile uint32_t *)0xE0001004)
#define CPU_CYCLES *DWT_CYCCNT
#define CLK_SPEED 168000000 // EXAMPLE for CortexM4, EDIT as needed
#define STOPWATCH_START { m_nStart = *((volatile unsigned int *)0xE0001004);}
#define STOPWATCH_STOP { m_nStop = *((volatile unsigned int *)0xE0001004);}
static inline void stopwatch_reset(void)
{
/* Enable DWT */
DEMCR |= DEMCR_TRCENA;
*DWT_CYCCNT = 0;
/* Enable CPU cycle counter */
DWT_CTRL |= CYCCNTENA;
}
static inline uint32_t stopwatch_getticks()
{
return CPU_CYCLES;
}
static inline void stopwatch_delay(uint32_t ticks)
{
uint32_t end_ticks = ticks + stopwatch_getticks();
while(1)
{
if (stopwatch_getticks() >= end_ticks)
break;
}
}
uint32_t CalcNanosecondsFromStopwatch(uint32_t nStart, uint32_t nStop)
{
uint32_t nDiffTicks;
uint32_t nSystemCoreTicksPerMicrosec;
// Convert (clk speed per sec) to (clk speed per microsec)
nSystemCoreTicksPerMicrosec = CLK_SPEED / 1000000;
// Elapsed ticks
nDiffTicks = nStop - nStart;
// Elapsed nanosec = 1000 * (ticks-elapsed / clock-ticks in a microsec)
return 1000 * nDiffTicks / nSystemCoreTicksPerMicrosec;
}
void main(void)
{
int timeDiff = 0;
stopwatch_reset();
// =============================================
// Example: use a delay, and measure how long it took
STOPWATCH_START;
stopwatch_delay(168000); // 168k ticks is 1ms for 168MHz core
STOPWATCH_STOP;
timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
printf("My delay measured to be %d nanoseconds\n", timeDiff);
// =============================================
// Example: measure function duration in nanosec
STOPWATCH_START;
// run_my_function() => do something here
STOPWATCH_STOP;
timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
printf("My function took %d nanoseconds\n", timeDiff);
}
DWT_CYCCNT
переполняется через 25 секунд, но когда вы выполняете 1000 * nDiffTicks
, вы переполняете его через 25 мс, что не нужно. stopwatch_reset()
тоже обычно не нужен, хотя если его убрать, то stopwatch_getticks() >= end_ticks
работать не будет. Я бы предложил более простую (и правильную) реализацию, например функция delayUS_DWT
, опубликованная в конце этой статьи.
- person Groo; 02.12.2019
Для любой надежной синхронизации я всегда предлагаю использовать таймер общего назначения. Ваша часть может иметь таймер, способный работать достаточно высоко, чтобы дать вам необходимое время. Что касается последовательного порта, есть ли причина, по которой вы не можете использовать соответствующее последовательное периферийное устройство? Большинство известных мне Cortex M3/M4 предлагают USARTS, I2C и SPI, а некоторые также предлагают SDIO, что должно удовлетворить большинство потребностей.
Если это невозможно, см. этот вопрос/ответ по стеку. используя счетчик циклов, если он доступен, на Cortex M3/M4. Вы можете взять счетчик циклов, добавить к нему несколько и опросить его, но я не думаю, что с помощью этого метода вы достигнете чего-либо разумно меньше ~ 8 циклов для минимальной задержки.
Ну, во-первых, вам нужно запускать из оперативной памяти, а не из флэш-памяти, так как время флэш-памяти будет медленным, один nop может занять много циклов. доступ к gpio должен занимать как минимум несколько часов, поэтому вам, вероятно, не понадобятся / не нужны nops, просто нажмите на gpio. Ответвление в конце цикла также будет заметно. вы должны написать несколько инструкций для ram и ветвления к нему и посмотреть, как быстро вы можете шевелить gpio.
Суть в том, что если у вас такой ограниченный бюджет, что ваши последовательные часы так близки по скорости к часам вашего процессора, очень вероятно, что вы не заставите это работать с этим процессором. увеличение pll в процессоре не изменит скорость флэш-памяти, это может ухудшить ее (относительно тактовой частоты процессора), однако sram должен масштабироваться, поэтому, если у вас есть запас по тактовой частоте вашего процессора и бюджет мощности для его поддержки, повторите эксперимент. в sram с более высокой тактовой частотой процессора.