Какие Arduino поддерживают ATOMIC_BLOCK?

Существует ли окончательный список архитектур Arduino, поддерживающих макрос ATOMIC_BLOCK (), и список #defines для каждой архитектуры?

Я попытался поискать ядра Arduino на GitHub, чтобы узнать, где используется ATOMIC_BLOCK. Если он не используется в ядре, я предположил, что он не поддерживается. Я сгенерировал этот список, основываясь на этом предположении, но насколько он точен?

ArduinoCore         Supported       Archived    #define
-------------------------------------------------------
  samd
  avr                  Yes                      __AVR__
  arc32
  sam                  Yes
  primo                               Yes
  megaavr              Yes
  API
  nRF528x-mbedos
  mbed

Я хотел бы написать какой-нибудь код, примерно такой (отредактированный в ответ на комментарии):

#define ATOMIC_BLOCK_SUPPORTED (__AVR__ || __MEGAAVR__ || __SAM__)
.
.
.
#if ATOMIC_BLOCK_SUPPORTED
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
#endif

        // Не беспокоить.

#if ATOMIC_BLOCK_SUPPORTED
    }
#endif

Для архитектур , которые не поддерживают ATOMIC_BLOCK, я хотел бы написать свой собственный код для сохранения, отключения и восстановления глобального флага прерывания, что-то вроде этого.

    asm
    {
        // Сохранить флаг прерывания.
    }
    // Снимите флаг прерывания.
    // Не беспокоить.
    // Восстановить флаг прерывания.

Потенциальное решение?

#ifdef __AVR__
#include <util/atomic.h>
#elif defined(__ARCH2__)
#define ATOMIC_BLOCK(/* for architecture 2 */)
#elif defined(__ARCH3__)
#define ATOMIC_BLOCK(/* for architecture 3 */)
// etc
#endif
.
.
.
#ifdef ATOMIC_BLOCK
    ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
    {
#endif

        // Не беспокоить.

#ifdef ATOMIC_BLOCK
    }
#endif

Существует ли окончательный список кода, который сохраняет, отключает и восстанавливает глобальный флаг прерывания для каждой архитектуры Arduino, который можно было бы поместить в каждую ветвь #elif?

, 👍2

Обсуждение

Вы пробовали ' #ifdef ATOMIC_BLOCK`?, @user253751

Или даже: #ifndef ATOMIC_BLOCK /* определите свой собственный ATOMIC_BLOCK здесь */ #endif, @user253751

Вы нашли кого-нибудь, кто не поддерживает это?, @user253751

Вы действительно пробовали его на всех этих архитектурах?, @user253751

Обратите внимание, что в AVR "ATOMIC_BLOCK" предоставляется не ядром Arduino, а [avr-libc](https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html)., @Edgar Bonet

Пожалуйста, обратите внимание, что ваша идея объединить символы #define и использовать для этого #ifdef ' не сработает. Лучше попробуйте "#define X (defined(A) | defined(B) | defined(C)) и проверьте с помощью " #if X`., @the busybee

Спасибо @edgar-bonet и @the-busybee. В настоящее время я читаю [Идентификацию...](https://forum.arduino.cc/index.php?topic=128520.0) и [Спецификация платформы](https://arduino.github.io/arduino-cli/platform-specification/). По - видимому, это вечная проблема, которую никто до сих пор целостно не рассматривал. Думая, что мне, возможно, придется соскрести данные со всех форумов Arduino в поисках "#define"..., @tim

@тим, посмотри, даст ли мой ответ то, что тебе нужно. Это тема, которую я много изучал., @Gabriel Staples


1 ответ


Лучший ответ:

6

Этот ответ охватывает следующие вопросы: Какие Arduino поддерживают ATOMIC_BLOCK? И как я могу дублировать эту концепцию в C с помощью __attribute__((__cleanup__(func_to_call_when_x_exits_scope))) и в C++ с помощью конструкторов классов и деструкторов?.

Какие Arduino поддерживают макросы ATOMIC_BLOCK?

Какие Arduino поддерживают ATOMIC_BLOCK?

Только микроконтроллеры AVR (ATmel AVR architecture) поддерживают макросы ATOMIC_BLOCK, потому что эти макросы являются частью библиотеки avr-libc, которая, как вы уже догадались, поддерживает только микроконтроллеры AVR.

Arduino на основе AVR включают те, которые основаны на микроконтроллере ATmega328 (Arduino Uno, Nano, Mini и т. Д.), ATmega2560 (Arduino Mega 2560), ATmega32U4 (Arduino Leonardo, Pro Micro) и т. Д.

Как макросы ATOMIC_BLOCK реализованы в C с компилятором gcc, и где я могу увидеть их исходный код?

avr-libc доступен для скачивания здесь: https://www.nongnu.org/avr-libc/ -- > Ссылка "Исходный код и документация" в разделе "Загрузки". Пример: вот tar-шар для v 2.0.0: http://download.savannah.gnu.org/releases/avr-libc/avr-libc-2.0.0.tar.bz2...

Весь исходный код макросов ATOMIC_BLOCK содержится в avr-libc-2.0.0/include/util/atomic.h.

ATOMIC_BLOCK-это гениальный макрос, написанный на языке Си и опирающийся на расширение атрибута C gcc "cleanup", которое может вызывать функцию, определенную вами, когда переменная выходит за пределы области видимости. По сути, это идентично деструктору класса C++, который вызывается, когда экземпляр класса в C++ выходит за пределы области видимости. Расширение атрибута gcc "cleanup"-это способ получить C++ - подобное поведение в C.

Вот официальная документация gcc: https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html... В нем говорится:

очистка (cleanup_function)

Атрибут очистки выполняет функцию, когда переменная выходит за пределы области действия. Этот атрибут может быть применен только к переменным области действия автоматической функции; он не может быть применен к параметрам или переменным со статической длительностью хранения. Функция должна принимать один параметр, указатель на тип, совместимый с переменной. Возвращаемое значение функции (если таковое имеется) игнорируется.

Если функция-fexceptions включена, то функция cleanup_function запускается во время разматывания стека, которое происходит во время обработки исключения. Обратите внимание, что атрибут cleanup не позволяет перехватывать исключение, а только выполнять какое-либо действие. Не определено, что происходит, если cleanup_function не возвращается нормально.

Его формат выглядит следующим образом:

int x __attribute__((__cleanup__(func_to_call_when_x_exits_scope))) = 0;

Вы используете его следующим образом:
ЗАПУСТИТЕ РЕАЛЬНЫЙ ПРИМЕР ЭТОГО КОДА НА ЯЗЫКЕ Си ОНЛАЙН ЗДЕСЬ.

static __inline__ void cleanup_my_byte(uint8_t *my_byte_p)
{
    Serial.print("my_byte is going out of scope! Its value is ");
    Serial.println(*my_byte_p);
}

void setup()
{
    uint8_t my_byte __attribute__((__cleanup__(cleanup_my_byte))) = 7;
} // The following function call occurs automatically here as `my_byte` 
  // exits its scope!:
  //    `cleanup_my_byte(&my_byte);`

Изучая файл avr-libc-2.0.0/include/util/atomic.h, упомянутый выше, вы можете увидеть, как реализуется макрос ATOMIC_RESTORESTATE с использованием этого атрибута. Я поместил здесь только соответствующие фрагменты из этого файла:

#include <avr/io.h>
#include <avr/interrupt.h>

#define ATOMIC_BLOCK(type) for ( type, __ToDo = __iCliRetVal(); \
                           __ToDo ; __ToDo = 0 )

static __inline__ uint8_t __iCliRetVal(void)
{
    cli();
    return 1;
}

#define ATOMIC_RESTORESTATE uint8_t sreg_save \
    __attribute__((__cleanup__(__iRestore))) = SREG

static __inline__ void __iRestore(const  uint8_t *__s)
{
    SREG = *__s;
    __asm__ volatile ("" ::: "memory");
}

Обратите внимание, что cli() очищает (выключает) прерывания, а sei() устанавливает (включает) прерывания.

Итак, вы видите, что ATOMIC_RESTORE_STATE заменяется uint8_t sreg_save __атрибут__((__Толока__(__iRestore))) = регистре sreg, которое выполняет резервное копирование прерывание состояния с АВР регистре sreg 8-разрядный регистр на sreg_save, затем вызывает вызов этой функции, когда sreg_save выходов своим размахом: __iRestore(&sreg_save);. Этот вызов функции __iRestore() затем возвращает регистр SREG туда, где он был, восстанавливая состояние прерывания (включая его, если он был включен раньше, или ВЫКЛЮЧАЯ, если он был выключен раньше).

Официальная документация для ATOMIC_BLOCK находится здесь: https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html... Пример использования таков:

ATOMIC_BLOCK(ATOMIC_RESTORESTATE)
{
    my_var_copy = my_var;
}

В ATOMIC_RESTORESTATE часть спины вверх в регистре sreg реестр и настраивает позвонить по __iRestore(&sreg_save); в случае, когда sreg_save выходы этого блока сферу, как мы уже обсуждали, и ATOMIC_BLOCK часть преобразуется в это: для ( ATOMIC_RESTORESTATE, __Тодо = __iCliRetVal(); __дел ; __Тодо = 0 ). Это означает, что весь блок теперь представляет собой гениальный цикл for, который запускается только один раз! Перед входом в for блока цикла, __iCliRetVal() вызывается один раз, снятия (выключения) прерывания, и __Тодо имеет значение 1, или значение true, поэтому для цикла выполняется как минимум один раз, и затем в конце первого запуска он установлен в 0, или значение false, чтобы остановить на цикле после одного запуска! Как мы уже говорили, переменная sreg_save теперь выходит из области действия в закрывающей скобке, и вызов __iRestore(&sreg_save); производится через расширение gcc для восстановления состояния прерывания! Гениально!

Как вы могли бы реализовать функциональность ATOMIC_BLOCK в Arduino на языке C++ (в отличие от версии avrlibc gcc C)?

В C++ (Arduino-это C++, поэтому мы не должны ограничиваться только C плюс ССЗ расширения, как было ограничение в avrlibc ATOMIC_BLOCK макросы), вы можете получить подобный эффект, используя деструкторы в структуры или классы (или, просто используйте avrlibc шаблона, показанного выше, если вы любите, а в том, что gcc c код также отлично действует на gcc c++ кода!).

Вариант 1: пример создания стиля использования, аналогичного std::lock_guard C++

Это, например, реализовало бы форму ATOMIC_BLOCK(ATOMIC_FORCEON) {}, используя простой класс в C++:

Он опирается на встроенные функции Arduino interrupts() (такие же, как функция sei() avrlibc) и noInterrupts() (такие же, как функция cli() avrlibc). Я хотел бы, чтобы Arduino также имел встроенные функции saveInterrupts() и restoreInterrupts (), но, к сожалению, их нет. Кто-то должен будет подать запрос на функции и запросить их, или реализовать их самостоятельно и сделать Pull Request (PR) на GitHub, чтобы попытаться вернуть их обратно в основные ветви Arduino.

ЗАПУСТИТЕ РЕАЛЬНЫЙ ПРИМЕР ЭТОГО КОДА НА C++17 ОНЛАЙН ЗДЕСЬ.

#define ATOMIC_BLOCK_FORCEON AtomicBlockForceOn atomicBlockForceOn_

class AtomicBlockForceOn
{
public:
    // Конструктор: вызывается при создании объекта
    inline AtomicBlockForceOn()
    {
        noInterrupts(); // выключение прерываний
    }

    // Destructor: вызывается, когда объект уничтожен (например, goes
    // вне сферы действия)
    inline ~AtomicBlockForceOn()
    {
        interrupts(); // включение прерываний
    }
};

Вот так! Теперь используйте его так:

uint16_t my_var_copy;
{
    ATOMIC_BLOCK_FORCEON;
    // теперь делайте здесь все, что хотите, с отключенными прерываниями; ex:
    my_var_copy = my_var;
} // здесь вызывается деструктор объекта ' atomicBlockForceOn_`  
  // и прерывания автоматически включаются!

Довольно круто!

Это, я полагаю, вероятно, именно так, как реализован std::lock_guard в C++! Его использование очень похоже:

void safe_increment()
{
    const std::lock_guard<std::mutex> lock(g_i_mutex);
    ++g_i;
 
    std::cout << std::this_thread::get_id() << ": " << g_i << '\n';
 
    // g_i_mutex автоматически освобождается при блокировке
    // выходит за рамки
}

Вариант 2: пример создания стиля использования, похожего на макросы avrlibc ATOMIC_BLOCK

Чтобы получить стиль использования ATOMIC_BLOCK(ATOMIC_FORCEON) { } в C++, можно определенно использовать стиль gcc C, который использует avrlibc, так как это также допустимо в C++, но давайте продолжим со стилем класса C++, показанным чуть выше, всего с несколькими настройками. Мы добавим в класс еще пару вспомогательных макросов и еще пару функций, чтобы заставить его работать. Вот он, с примером использования, показанным в конце, который идентичен тому, как можно использовать ATOMIC_BLOCK avrlibc! Я думаю, что это тоже довольно круто.

ЗАПУСТИТЕ РЕАЛЬНЫЙ ПРИМЕР ЭТОГО КОДА НА C++17 ОНЛАЙН ЗДЕСЬ.

#define ATOMIC_BLOCK(type) for(type; type##_OBJECT_NAME.run(); \
    type##_OBJECT_NAME.stop())
#define ATOMIC_FORCEON_OBJECT_NAME atomicBlockForceOn_
#define ATOMIC_FORCEON AtomicBlockForceOn ATOMIC_FORCEON_OBJECT_NAME

class AtomicBlockForceOn
{
public:
    // Constructor: called when the object is created
    inline AtomicBlockForceOn()
    {
        noInterrupts(); // turn interrupts OFF
    }

    // Destructor: вызывается, когда объект уничтожен (например, goes
    // вне сферы действия)
    inline ~AtomicBlockForceOn()
    {
        interrupts(); // turn interrupts ON
    }
    
    // Мы можем бежать? Возвращает true для запуска цикла `for` или
    // `false` для его остановки.
    inline bool run()
    {
        return run_now;
    }
    
    // Скажите циклу "for", чтобы он остановился
    inline void stop()
    {
        run_now = false;
    }
    
private:
    bool run_now = true;
};

Используйте его как макрос ATOMIC_BLOCK() { } avrlibc!

Пример использования:

uint16_t my_var_copy;
ATOMIC_BLOCK(ATOMIC_FORCEON)
{
    // now do whatever you want here, with interrupts disabled; ex:
    my_var_copy = my_var;
} // the `ATOMIC_FORCEON_OBJECT_NAME` (`atomicBlockForceOn_`) object's
  // destructor is called here and interrupts are automatically 
  // turned back ON!

Дальнейшее чтение:

  1. Превосходная страница Ника Гэммона "Прерывания", на которой я впервые начал изучать прерывания в 2012~2014 годах или около того: https://gammon.com.au/interrupts.
  2. Другие ответы, которые я сделал о прерываниях, замках/охранниках и тому подобном:
    1. глобальная изменчивая переменная не обновляется в ISR
    2. Моя собственная проблема с расовым состоянием: https://stackoverflow.com/questions/36381932/c-decrementing-an-element-of-a-single-byte-volatile-array-is-not-atomic-why
    3. Мое собственное решение race condition, демонстрирующее 3 (или 4, в зависимости от того, как вы на это смотрите) способа применения "атомарных охранников доступа" на 8-битных Arduino на базе AVR: https://stackoverflow.com/questions/36381932/c-decrementing-an-element-of-a-single-byte-volatile-array-is-not-atomic-why/39693278#39693278
  3. Кстати, что это за странный тип##_OBJECT_NAME, который я сделал в макросе? Подробнее о конкатенации маркеров макросов с помощью оператора препроцессора ## читайте в официальном руководстве пользователя gcc C++ здесь: https://gcc.gnu.org/onlinedocs/cpp/Concatenation.html... Примечание: конечно, это также работает и на языке Си.
  4. gcc inline или __inline__, а также статические встроенные или статические __inline__ правила: https://gcc.gnu.org/onlinedocs/gcc/Inline.html
,

Спасибо. Это отличный ответ. Я ранее читал некоторые из ваших дальнейших прочтений (пункты 1 и 2i-iii), исследуя этот вопрос. Я собирался разобрать его, но вы спасли мне работу, создав отличную справку. Макрос с циклом " for "и вариант 1 и 2 с "class" - это то, что я рассматривал при реализации, поэтому спасибо за четкое объяснение их. Теперь нужно выяснить, что поместить в макрос __iRestore и class constructure/destructor, чтобы сохранить/восстановить глобальный флаг прерывания для каждой архитектуры., @tim

Правильный. Теперь вам нужно выяснить, как отключить/включить прерывания для каждой архитектуры, а затем поместить соответствующий код в конструктор/деструктор. [Вот как это сделать STM32](https://stm32f4-discovery.net/2015/06/how-to-properly-enabledisable-interrupts-in-arm-cortex-m/)например, который используется в линейке Arduino "professional", такой как [$103 Portenta H7](https://store.arduino.cc/usa/portenta-h7), основанный на микроконтроллере STM32H7: prim = __get_PRIMASK();, __disable_irq();, if (!prim) { __enable_irq(); }. Это использование низкоуровневых библиотек CMSIS., @Gabriel Staples

Вы также можете использовать полдюжины других методов, включая библиотеки ядра STM32 и библиотеки FreeRTOS (при использовании операционной системы FreeRTOS в реальном времени). У меня есть личные заметки обо всем этом, которые мне нужно когда-нибудь опубликовать на своем сайте. 32-битные микроконтроллеры-звери. Я понятия не имею, как это сделать на 32-битных микроконтроллерах Atmel/Microchip SAM или SAMD, таких как [Zero](https://store.arduino.cc/usa/arduino-zero) или [Должный](https://store.arduino.cc/usa/due), но есть способы для всех фишек, вы просто должны понять их., @Gabriel Staples