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

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

4 ответа

Решение

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

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

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

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

Использование stopwatch_delay(ticks) ниже, чтобы выполнить ваши задержки. Он использует регистр STT32 DWT_CYCCNT, который специально разработан для подсчета фактических тактов, расположенных по адресу 0xE0001004.

Чтобы проверить точность задержки (см. main), ты можешь позвонить STOPWATCH_START, бежать stopwatch_delay(ticks)затем позвоните STOPWATCH_STOP и проверить с 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 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 nClkTicksPerMicrosec;

    nDiffTicks = nStop - nStart;
    nDiffTicks *= 1000;                               // Scale diff by 1000.
    nClkTicksPerMicrosec = SystemCoreClock / 1000000; // Convert (clkTicks/sec) to (clkTicks/microsec), SystemCoreClock = 168000000

    return nDiffTicks / nClkTicksPerMicrosec;         // nanosec = (ticks * 1000) / (clkTicks/microsec)
} 

void main(void)
{
    int timeDiff = 0;
    stopwatch_reset();

    STOPWATCH_START;
    run_my_function();
    STOPWATCH_STOP;

    timeDiff = CalcNanosecondsFromStopwatch(m_nStart, m_nStop);
    printf("My function took %d nanoseconds\n", timeDiff);
}

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

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

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

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

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