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

x8goxv8g  于 2023-08-06  发布在  其他
关注(0)|答案(4)|浏览(93)

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

gfttwv5a

gfttwv5a1#

如果您需要这种非常短但确定性的“至少”延迟,也许您可以考虑使用nop以外的其他指令,这些指令具有确定性的非零延迟。
所描述的The Cortex-M4 NOP不一定是耗时的。
您可以将其替换为and reg, reg,或者在上下文中粗略地等效于nop的值。或者,在切换GPIO时,您也可以重复I/O指令本身以强制执行状态的最小长度(例如,如果您的GPIO写入指令至少需要5 ns,则重复五次以获得至少25 ns)。如果你在C程序中插入nops,这甚至可以在C中很好地工作(只要重复对端口的写入,如果它应该是volatile,编译器不会删除重复的访问)。
当然,这仅适用于非常短的延迟,否则对于短延迟,如其他人所提到的,等待一些定时源的忙碌循环将工作得更好(它们至少需要对定时源进行采样、设置目标并经历一次等待循环所需的时钟)。

vwhgwdsa

vwhgwdsa2#

使用 * 周期计数寄存器 *(DWT_CYCCNT)获得高精度定时!

  • 注:我还使用数字引脚和示波器对此进行了测试,结果非常准确。*

参见下面的stopwatch_delay(ticks)和支持代码,该代码使用STM32的DWT_CYCCNT寄存器,该寄存器专门用于计算实际时钟节拍,地址为0xE 0001004。
参见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);
}

字符串

a0zr77ik

a0zr77ik3#

对于任何可靠的计时,我总是建议使用通用计时器。您的部件可能有一个计时器,它能够提供足够高的时钟频率,以给予您的计时需求。对于串行,您是否有理由不能使用相应的串行外设?据我所知,大多数Cortex M3/M4都提供USARTS,I2C和SPI,其中多个还提供SDIO,这应该可以满足大多数需求。
如果不可能,this stackoverflow question/answer使用Cortex M3/M4上的周期计数器(如果可用)进行详细说明。您可以获取周期计数器,并向其添加一些,然后轮询它,但我不认为您可以使用此方法实现低于~8个周期的最小延迟。

5lhxktic

5lhxktic4#

好吧,首先你必须从RAM运行,而不是闪存,因为闪存的时间将是缓慢的,一个NOP可以采取许多周期。gpio访问至少也需要几个时钟,所以你可能不需要/不想让nops只在gpio上磅。循环结束时的分支也会很明显。你应该写一些指令到ram和分支到它,看看你能多快摆动gpio。
底线虽然是,如果你是在这样一个紧张的预算,你的串行时钟是如此接近你的处理器时钟的速度,它很可能你不会得到这个工作与这个处理器。提高处理器中的pll不会改变闪存速度,它会使它变得更糟(相对于处理器时钟)sram应该缩放,但如果你有余量留在你的处理器时钟和功率预算,以支持,然后重复实验在sram与更快的处理器时钟速度。

相关问题