在STM32上用C语言生成纳秒级延迟

11

我正在使用STM32F2控制器,并通过8位并行接口与ST7036 LCD显示器进行接口。

数据手册指出,在地址保持和设置时间之间应该有20纳秒的延迟。

如何在C中生成一个20纳秒的延迟?


你尝试过使用 nanosleep() 函数吗?注意:需要包含 <time.h> 头文件才能使用它。 - user740316
你不需要进行ns延迟。这些是数据表中的最小延迟,但你可以设置更多。另外,为什么不想使用SPI或I2C呢?那样会更简单,而且你可以在一个数据包中发送数据。这样你就可以为其他任务释放控制器了。 - Bulkin
3个回答

15

在下面使用stopwatch_delay(4)来实现约24纳秒的延迟。它使用了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 CLK_SPEED         168000000 // EXAMPLE for CortexM4, EDIT as needed

#define STOPWATCH_START { m_nStart = CPU_CYCLES;}
#define STOPWATCH_STOP  { m_nStop = CPU_CYCLES;}


static inline void stopwatch_reset(void)
{
    /* Enable DWT */
    DEMCR |= DEMCR_TRCENA; 
    *DWT_CYCCNT = 0;             
    /* Enable CPU cycle counter */
    DWT_CTRL |= CYCCNTENA;
}

static inline void stopwatch_delay(uint32_t ticks)
{
    uint32_t end_ticks = ticks + CPU_CYCLES;
    while(1)
    {
            if (CPU_CYCLES >= end_ticks)
                    break;
    }
}

// WARNING: ONLY VALID FOR <25ms measurements due to scaling by 1000!
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);
}

1
你确定这会起作用吗?一个指令周期大约是5纳秒...显然,代码使用的指令不止5个。因此,最短时间将是25纳秒...然而,在硬件中使用的延迟可能远小于25纳秒。 - richieqianle
是的。代码需要根据需要进行修改。一个人肯定可以只使用所需的最小部分,或者理想情况下,这个代码的用户会在main()内部的循环中运行__no_operation()一千次(例如,在run_my_function()处),以获取1000次运行的纳秒秒表,然后只需将该数字除以1000即可查看在所讨论的系统上单个流水线__no_operation()调用需要多长时间...然后按需使用。 - bunkerdive
1
只是一条评论,1000个NOP/1000可能不等于1个NOP。无论如何,解释得很好! - richieqianle
是的,它只是近似于流水线化的NOP。所以,是的,使用的NOP越少,测量结果就会在某种程度上偏离现实(但是影响很小)。 - bunkerdive
2
一些注意事项:1)如果CPU_CYCLES是一个自由运行的计时器,当它接近0xFFFFFFFF时,这将无法工作,因为end_ticks会溢出,然后stopwatch_getticks() >= end_ticks将立即退出。2)如果你让秒表运行超过了26ms(如果我计算正确),那么1000 * nDiffTicks将会溢出。3)不清楚为什么你的STOPWATCH_START/STOPWATCH_STOP宏没有使用DWT_CYCCNT,因为它已经被定义了。4)对于一个通用的解决方案,stopwatch_reset是一个坏主意,因为它会阻止你从多个中断中使用秒表。 - vgru

9
我找到的第一份Stm32f2规格说明书假定时钟频率为120 MHz。这大约是每个时钟周期8纳秒。你需要在连续的写或读写操作之间使用大约三个单周期指令。在C语言中,a++; 可能可以实现(如果a位于堆栈中)。

是的 - 没错 - 所有答案可这一个提供的解决方案所需时间要多100倍...20ns 只是几个周期,汇编语言中的几个 NOP 将会更加充分... - Freddie Chopin
不管使用什么延迟方法,使用专门设计用于此目的的循环计数寄存器来验证延迟是否正确不是很好吗?否则,我猜可以使用示波器和一些数字引脚来验证。 - bunkerdive
上面的 stopwatch_delay() 函数完美地为我完成了这个任务,可以验证或用于不同的延迟长度。 - bunkerdive

1

你应该研究一下你的芯片中可用的FSMC外设。虽然配置可能会比较复杂,特别是如果你没有使用它设计用于的内存部件,但你可能会发现你的并行接口设备在某种程度上很适合与其中一种内存接口模式相匹配。

这类外部内存控制器必须具备一系列可配置的时序选项,以支持各种不同的内存芯片,因此你将能够确保数据手册所要求的时序。

能够这样做的一个好处是,你的LCD将会像任何普通的内存映射外设一样,将底层的接口细节抽象化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接