4

我目前正在将我的DCF77 库(您可以在 GitHub 上找到源代码)从 Arduino(基于 AVR)移植到 Arduino Due(ARM Cortex M3)。我是 ARM 平台的绝对初学者。

使用基于 AVR 的 Arduino,我可以使用avr-libc来获取原子块。基本上这会阻塞阻塞期间的所有中断,并在以后再次允许中断。对于 AVR,这很好。现在对于 ARM Cortex,事情开始变得复杂起来。

首先:对于图书馆的当前用途,这种方法也可以。所以我的第一个问题是:是否有类似于 ARM 的 avr-libc 的“ATOMIC”宏?显然其他人已经在这个方向上想到了一些东西。由于我使用的是 gcc,因此我可以增强这些宏,使其几乎与 avr-libv ATOMIC 宏一样工作。我已经找到了一些CMSIS 文档,但这似乎只提供了“enable_irq”宏而不是“restore_irq”宏。

问题 1:是否有任何图书馆(用于 gcc)已经这样做了?

因为 ARM 有不同的优先级中断,我也可以用不同的方式建立原子性。在我的情况下,“原子”块必须只确保它们不会被 systick 中断打断。所以实际上我不需要阻止一切来使我的块“足够原子”。进一步搜索,我在开发人员信息中心找到了一篇 ARM 同步原语文章。尤其是无锁编程的提示。根据这篇文章,这是一个先进的概念,并且有很多关于它的出版物。在网上搜索我只发现了这个概念的一般解释,例如here。我认为无锁实现会非常酷,但此时我对 ARM 没有足够的信心从头开始实现它。

问题 2:有人对我在 ARM Cortex M3 上无锁读取内存块有一些提示吗?

正如我已经说过的,我只需要保护较低优先级的线程免受 sysTicks 的影响。因此,另一种选择是暂时禁用 sysTicks。由于我正在实现对时序敏感的时钟算法,因此从长远来看,这一定不会降低整体 sysTick 频率。不过,引入一些小的抖动是可以的。在这个时候,我会觉得这个最有吸引力。

问题 3:有什么好的方法可以阻止 sysTick 中断而不丢失任何滴答声?

我还找到了semaphores 的 CMSIS 文档。然而,我有些不知所措。特别是我想知道我是否应该使用 CMSIS 以及如何在 Arduino Due 上执行此操作。

问题4:我最好的选择是什么?或者我应该在哪里继续阅读?

部分答案:在 Notlike 的提示下,我实现了

#if defined(ARDUINO_ARCH_AVR)
    #include <util/atomic.h>
    #define CRITICAL_SECTION ATOMIC_BLOCK(ATOMIC_RESTORESTATE)

#elif defined(ARDUINO_ARCH_SAM)
    // Workaround as suggested by Stackoverflow user "Notlikethat"
    // http://stackoverflow.com/questions/27998059/atomic-block-for-reading-vs-arm-systicks

    static inline int __int_disable_irq(void) {
        int primask;
        asm volatile("mrs %0, PRIMASK\n" : "=r"(primask));
        asm volatile("cpsid i\n");
        return primask & 1;
    }

    static inline void __int_restore_irq(int *primask) {
        if (!(*primask)) {
            asm volatile ("" ::: "memory");
            asm volatile("cpsie i\n");
        }
    }
    // This critical section macro borrows heavily from
    // avr-libc util/atomic.h
    // --> http://www.nongnu.org/avr-libc/user-manual/atomic_8h_source.html
    #define CRITICAL_SECTION for (int primask_save __attribute__((__cleanup__(__int_restore_irq))) = __int_disable_irq(), __ToDo = 1; __ToDo; __ToDo = 0)

#else
    #error Unsupported controller architecture
#endif

这个宏或多或少地做了我需要的。但是,我发现还有改进的余地,因为这会阻止所有中断,尽管仅阻止 systicks 就足够了。所以问题 3 仍然是开放的。

4

1 回答 1

6

您所引用的大部分内容都是关于在多个 CPU 之间同步内存访问,或者在同一个 CPU 上抢先调度线程,考虑到所述情况,这似乎完全不合适。从这个意义上说,“原子性”是指保证当一个观察者更新内存时,任何读取内存的观察者看到的不是初始状态,就是更新的状态,但绝不会介于两者之间。

关于中断的“原子性”遵循相同的原则——即确保如果发生中断,代码序列要么根本不运行,要么完全运行——但在概念上是不同的事情1。只有两件事可以保证是原子中断:一条指令2,或者在禁用中断的情况下执行的指令序列。

实现这一目标的“正确”方法确实是通过CPSID/CPSIE指令,这些指令包含在__disable_irq()/__enable_irq()内部函数中。请注意,系统中有两个“阶段”的中断处理:M3 内核本身只有一个 IRQ 信号——外部 NVIC 的工作是将系统 IRQ 的所有路由/多路复用/优先级分配到这一行中。当 CPU 想要进入临界区时,它需要做的就是用 屏蔽它自己的 IRQ 输入CPSID,做它需要做的事情,然后用 取消屏蔽CPSIE,此时任何来自 NVIC 的未决 IRQ 将立即被获取。

对于嵌套/重入临界区的情况,内在函数提供了一种方便的int __disable_irq(void)形式来返回先前的状态,因此您可以有条件地取消屏蔽。

对于不提供此类内在函数的其他编译器,滚动您自己的非常简单,例如:

static inline int disable_irq(void) {
    int primask;
    asm volatile("mrs %0, PRIMASK\n"
                 "cpsid i\n" : "=r"(primask));
    return primask & 1;
}

static inline void enable_irq(int primask) {
    if (primask)
        asm volatile("cpsie i\n");
}

[1] 一个令人困惑的重叠是后一种含义通常用于在单 CPU 多任务处理中实现前者 - 如果中断关闭,则在您完成之前无法安排其他线程,因此永远不会看到部分更新的内存。

[2] 除了加载/存储多条指令的可能例外 - 在低延迟中断配置中,这些指令可以被中断,并在返回时重新启动或继续。

于 2015-01-17T13:29:48.807 回答