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



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

如果你需要这种非常短的,但确定性的"至少"延迟,也许你可以考虑使用其他指令,而不是具有确定性的非零延迟的nop

所描述的Cortex-M4 NOP不一定耗时。

你可以把它替换成,比如and reg, reg,或者在上下文中大致相当于nop的东西。或者,在切换GPIO时,您还可以重复I/O指令本身来强制执行状态的最小长度(例如,如果您的GPIO写入指令至少需要5ns,则重复五次以获得至少25ns)。如果您在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 nanosecondsn", 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 nanosecondsn", timeDiff);
}

对于任何可靠的计时,我总是建议使用通用计时器。你的那部分可能有一个计时器,它能够足够高地计时,给你所需的时间。对于串行,是否有不能使用相应串行外设的原因?我所知道的大多数Cortex M3/m4都提供USARTS, I2C和SPI,其中多个还提供SDIO,这应该可以满足大多数需求。

如果这是不可能的,这个stackoverflow问题/答案细节使用循环计数器,如果可用,在Cortex M3/M4上。你可以抓取周期计数器,并添加一些到它并轮询它,但我不认为你会实现任何合理低于~8个周期的最小延迟使用这种方法。

首先你必须从ram而不是flash中运行,因为flash计时很慢,一个nop可能需要许多周期。gpio访问应该至少需要几个时钟,所以你可能不需要/想要nops,只要敲击gpio。循环末尾的分支也很明显。您应该编写一些指令来ram和分支它,看看您可以多快地摆动gpio。

底线是,如果你的预算很紧张,你的串行时钟在速度上接近你的处理器时钟,很可能你不会让这个处理器工作。提高处理器中的锁相环不会改变闪存速度,它会使情况变得更糟(相对于处理器时钟),sram应该缩放,所以如果你的处理器时钟和功率预算上还有剩余空间来支持,那么在sram中使用更快的处理器时钟速度重复实验。

相关内容

  • 没有找到相关文章

最新更新