Как получить надежные короткие задержки Cortex M4

Я переношу некоторый код с M3 на M4, который использует 3 NOP, чтобы обеспечить очень короткую задержку между изменениями часов последовательного вывода. Набор инструкций M3 определяет время для NOP как 1 цикл. Я заметил, что NOP в M4 вообще не обязательно задерживают какое-либо время. Я знаю, что мне нужно будет отключить оптимизацию компилятора, но я ищу низкоуровневую команду, которая даст мне надежное, воспроизводимое время. На практике в этом конкретном случае последовательный порт используется очень редко и может быть очень медленным, но я все же хотел бы знать, как лучше всего получить задержки на уровне цикла.


person Ant    schedule 12.05.2014    source источник
comment
Вы не можете использовать UART или периферийный таймер?   -  person rjp    schedule 12.05.2014
comment
Нет, у меня нет доступных таймеров, которые можно было бы настроить вовремя или оставить для свободного запуска.   -  person Ant    schedule 13.05.2014
comment
у uart есть свой делитель часов.   -  person old_timer    schedule 14.05.2014
comment
Я не могу использовать UART или периферийный таймер для генерации задержки 24 нс.   -  person Ant    schedule 14.05.2014
comment
Согласно ARM Cortex-M3 Общее руководство пользователя для устройств инструкция NOP не обязательно будет занимать какое-то время и на Cortex M3.   -  person Blue    schedule 26.06.2018


Ответы (4)


Если вам нужны такие очень короткие, но детерминированные «как минимум» задержки, возможно, вы могли бы рассмотреть возможность использования других инструкций, кроме nop, которые имеют детерминированную ненулевую задержку.

NOP Cortex-M4 как описано не обязательно требует много времени.

Вы можете заменить его, скажем, на and reg, reg или на что-то приблизительно эквивалентное nop в контексте. В качестве альтернативы, при переключении GPIO вы также можете повторить сами инструкции ввода-вывода, чтобы обеспечить минимальную длину состояния (например, если ваша инструкция записи GPIO занимает не менее 5 нс, повторите ее пять раз, чтобы получить не менее 25 нс). Это могло бы хорошо работать даже в C, если бы вы вставляли nops в программу C (просто повторите запись в порт, если он volatile, как и должно быть, компилятор не удалит повторные обращения).

Конечно, это относится только к очень коротким задержкам, в противном случае для коротких задержек, как упоминалось другими, занятые циклы, ожидающие некоторого источника синхронизации, будут работать намного лучше (они берут по крайней мере такты, необходимые для выборки источника синхронизации, установки цели, и пройти один раз цикл ожидания).

person Jubatian    schedule 01.06.2017
comment
Большое спасибо, как я сказал ниже, я использую MOV R0, # 1. Он использовался на многих производственных объектах вскоре после того, как я написал вопрос в 2014 году, и до сих пор он работал отлично. - person Ant; 01.06.2017

Используйте регистр подсчета циклов (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);
}
person bunkerdive    schedule 21.04.2017
comment
Вы также можете проверить это поведение с помощью осциллографа и цифровых контактов. - person bunkerdive; 21.04.2017
comment
Это дает мне короткие задержки, но не очень короткие задержки. - person Ant; 03.05.2017
comment
@Ant, вы можете установить задержку в тиках по мере необходимости; На какое короткое время вы рассчитывали? - person bunkerdive; 29.10.2019
comment
Задержка, которую я хотел, составляла 3 цикла. - person Ant; 29.10.2019
comment
Те же комментарии, что и в этом ответе. На процессоре 168 МГц DWT_CYCCNT переполняется через 25 секунд, но когда вы выполняете 1000 * nDiffTicks, вы переполняете его через 25 мс, что не нужно. stopwatch_reset() тоже обычно не нужен, хотя если его убрать, то stopwatch_getticks() >= end_ticks работать не будет. Я бы предложил более простую (и правильную) реализацию, например функция delayUS_DWT, опубликованная в конце этой статьи. - person Groo; 02.12.2019
comment
Surly лучший способ измерить циклы. Можете ли вы предоставить документацию, где представлены регистры DWT, и объяснить, как это работает? Документ ARM и ST, который я смог найти, представляет только поля регистров и напрямую связывает их с ETM, ITM и т. д. - person Welgriv; 22.04.2020

Для любой надежной синхронизации я всегда предлагаю использовать таймер общего назначения. Ваша часть может иметь таймер, способный работать достаточно высоко, чтобы дать вам необходимое время. Что касается последовательного порта, есть ли причина, по которой вы не можете использовать соответствующее последовательное периферийное устройство? Большинство известных мне Cortex M3/M4 предлагают USARTS, I2C и SPI, а некоторые также предлагают SDIO, что должно удовлетворить большинство потребностей.

Если это невозможно, см. этот вопрос/ответ по стеку. используя счетчик циклов, если он доступен, на Cortex M3/M4. Вы можете взять счетчик циклов, добавить к нему несколько и опросить его, но я не думаю, что с помощью этого метода вы достигнете чего-либо разумно меньше ~ 8 циклов для минимальной задержки.

person rjp    schedule 12.05.2014
comment
Это не стандартный последовательный порт, для SPI и I2C я с удовольствием использую периферийные устройства. Это должно управляться GPIO с задержкой в ​​​​несколько циклов. Я также согласен с тем, что счетчик циклов не будет работать. - person Ant; 13.05.2014

Ну, во-первых, вам нужно запускать из оперативной памяти, а не из флэш-памяти, так как время флэш-памяти будет медленным, один nop может занять много циклов. доступ к gpio должен занимать как минимум несколько часов, поэтому вам, вероятно, не понадобятся / не нужны nops, просто нажмите на gpio. Ответвление в конце цикла также будет заметно. вы должны написать несколько инструкций для ram и ветвления к нему и посмотреть, как быстро вы можете шевелить gpio.

Суть в том, что если у вас такой ограниченный бюджет, что ваши последовательные часы так близки по скорости к часам вашего процессора, очень вероятно, что вы не заставите это работать с этим процессором. увеличение pll в процессоре не изменит скорость флэш-памяти, это может ухудшить ее (относительно тактовой частоты процессора), однако sram должен масштабироваться, поэтому, если у вас есть запас по тактовой частоте вашего процессора и бюджет мощности для его поддержки, повторите эксперимент. в sram с более высокой тактовой частотой процессора.

person old_timer    schedule 14.05.2014
comment
На практике 3 NOP дают мне именно то время, которое я хочу, но я не думаю, что этого достаточно, поскольку в документации указано, что они могут быть удалены конвейером. Я мог бы представить продукт, поставляемый с процессором следующей версии, который имеет лучшую оптимизацию, и вдруг ничего не работает, как раньше. Я ищу надежный метод вставки задержки в несколько наносекунд. В настоящее время я использую MOV R0, # 1 после отключения оптимизации компилятора, так как я не нашел комментариев об их удалении. - person Ant; 14.05.2014
comment
Я бы подумал об этом утверждении, что заставило бы их решить удалить их из конвейера, какие внутренние или внешние силы, если бы ваш код не менялся, система жестко контролировалась бы, ядро ​​не имело бы никаких новых входных данных или вариантов выборки, и т. д., что заставит трубу не делать то же самое, что она всегда делала. Теперь, с другой стороны, конечно, от одной версии чипа к другой это может измениться, но вы можете посмотреть на версию доступных ядер и версию, которую использует поставщик чипа (я подозреваю, что они не просто выталкивают кору). m4 и заменить его другим - person old_timer; 14.05.2014
comment
во время простого вращения фишки, но кто знает. - person old_timer; 14.05.2014
comment
Суть та же, если лучшее, что вы можете сделать, это три nops, чтобы получить свой тайминг, и это не PIC, вы слишком тугие, вам нужен какой-то другой чип, у вашего процессора по скорости сигнала не хватает запаса. - person old_timer; 14.05.2014
comment
Что заставило бы их решить удалить их из конвейера? Поскольку они реализуют то, что задокументировали, в документации сказано, что они могут быть удалены. Нужен какой-то другой чип - этот продукт находится в производстве, это не хобби для спальни. - person Ant; 15.05.2014
comment
хорошо, что это часть игры с документами, возможно, что все реализации этого ядра или семейства ядер делают это некоторое время. Возможно, некоторые версии одного из ядер это делают, а другие нет. Как только вы разберетесь с этими вопросами, тогда, если есть ядро, которое иногда это делает, тогда вопрос в том, что определяет время, когда оно работает, а когда нет, и я, конечно, не знаю ответов ни на один из этих вопросов. Я все еще соперничаю с этим процессором, даже если nops выполняются КАЖДЫЙ раз, вы слишком сильно ограничиваете скорость процессора по отношению к сигналу. - person old_timer; 15.05.2014