如何获得可靠的Cortex M4短延迟

时间:2014-05-12 14:52:53

标签: assembly delay cortex-m3 nop

我正在将一些代码从M3移植到M4,它使用3个NOP在串行输出时钟更改之间提供非常短的延迟。 M3指令集将NOP的时间定义为1个周期。我注意到M4中的NOP并不一定会在任何时候延迟。 我知道我将需要禁用编译器优化,但我正在寻找一个低级命令,它将给我可靠,可重复的时间。 在这种特殊情况下的实际应用中,序列偶尔使用并且可能非常慢但我仍然想知道获得周期级延迟的最佳方法。

4 个答案:

答案 0 :(得分:2)

如果你需要这么短,但确定性至少"至少"延迟,也许您可​​以考虑使用除nop以外的其他指令,这些指令具有确定的非零延迟。

The Cortex-M4 NOP如上所述并不一定非常耗时。

您可以将其替换为and reg, reg,或者在上下文中粗略等同于nop的内容。或者,当切换GPIO时,您也可以重复I / O指令本身以强制执行最小长度的状态(例如,如果您的GPIO写入指令至少需要5ns,重复五次以获得至少25ns)。如果你在C程序中插入nops,这甚至可以在C中很好地工作(只需重复写入端口,如果它应该是volatile,编译器就不会删除重复访问)。

当然这仅适用于非常短的延迟,否则对于其他人提到的短暂延迟,等待某些定时源的繁忙循环会更好地工作(它们至少需要采样定时源所需的时钟,设置目标,并经过一次等待循环)。

答案 1 :(得分:1)

对于任何可靠的时间,我总是建议使用通用计时器。你的部件可能有一个定时器,能够提供足够高的时钟,为你提供所需的时间。对于串行,有没有理由不能使用相应的串行外设?我所知道的大多数Cortex M3 / M4都提供USARTS,I2C和SPI,还有多个也提供SDIO,这应该可以满足大多数需求。

如果无法做到这一点,this stackoverflow question/answer详细说明了使用Cortex M3 / M4上的循环计数器(如果有)。您可以抓住循环计数器并添加一些循环计数器并进行轮询,但我不认为使用此方法可以在约8个周期内实现任何合理的最小延迟。

答案 2 :(得分:1)

使用下面的stopwatch_delay(ticks)来完成延迟。它使用STM32的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);
}

答案 3 :(得分:0)

首先你必须从ram而不是闪光灯运行,因为闪光时间将变慢,一个nop可能需要很多周期。 gpio访问应该至少花费几个时钟,所以你可能不需要/想要在gpio上敲击nops。循环结束时的分支也是显而易见的。你应该写一些指令来ram并分支到它,看看你有多快摆动gpio。

但最重要的是,如果您的预算如此紧张,以至于您的串行时钟速度接近您的处理器时钟,那么您很可能无法使用此处理器。在处理器中增加pll不会改变闪存速度,它可能会使(更糟糕的是相对于处理器时钟)sram应该扩展,所以如果你的处理器时钟留有余量并支持功率预算那么重复实验在sram中,处理器时钟速度更快。