реализация sbi() и cli()

Я часто видел cli() и sbi() в коде Arduino. Обычно я не обращаю на них внимания, поскольку знаю, что они делают (очищают или устанавливают бит, указанный как второй аргумент в регистре микроконтроллера, указанном как первый). Я всегда думал, что эти функции — всего лишь понятный способ выполнения простых манипуляций с битами, которые могут быть весьма подвержены ошибкам. Что-то вроде этого: #define cli(reg,bit) (*reg &= ~(1 << bit) и #define sbi(reg,bit) (*reg |= (1 << bit)).

Затем я узнал, что фактическая реализация выглядит следующим образом:

#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))

Это как раз то, о чём я раньше думал... но с двумя дополнительными макросами. Я искал их в библиотеках Arduino:

#define _BV(bit) (1 << (bit))

и

#define _SFR_BYTE(sfr) _MMIO_BYTE(_SFR_ADDR(sfr))

который содержит

#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))

и

#if _SFR_ASM_COMPAT
#if (__SFR_OFFSET == 0x20)
#define _SFR_ADDR(sfr) _SFR_MEM_ADDR(sfr)
#elif !defined(__ASSEMBLER__)
#define _SFR_ADDR(sfr) (_SFR_IO_REG_P(sfr) ? (_SFR_IO_ADDR(sfr) + 0x20) : _SFR_MEM_ADDR(sfr))
#endif
#else  /* !_SFR_ASM_COMPAT */    
#define _SFR_ADDR(sfr) _SFR_MEM_ADDR(sfr)

Дальнейшее исследование привело меня к определениям, которые я не мог понять (например, потому что они объясняются в комментариях с использованием множества неизвестных мне аббревиатур). Итак, у меня есть следующие вопросы:

  • Почему sfr приводится к *(volatile uint8_t *)?
  • Что означают аббревиатуры, используемые в этом коде? (ASM, SFR, MMIO)
  • При каких обстоятельствах определяется __ASSEMBLER__ и где (в каком файле)?
  • Почему мы просто не можем написать #define cli(reg,bit) (*reg &= ~(1 << bit) и #define sbi(reg,bit) (*reg |= (1 << bit))?

, 👍3


1 ответ


4

В микроконтроллерах Atmel (теперь Microchip) AVR инструкции языка ассемблера sbi, cbi, sbis и sbic могут адресовать только 32 порта ввода-вывода по адресам от 0x20 до 0x3f. Atmega168 и 328 (использовавшиеся в Arduino до Leonardo) имеют НАМНОГО больше 32 регистров ввода-вывода, поэтому только часть из них может быть адресована sbi и cbi (набор инструкций AVR был закреплен ГОДЫ назад, когда 32 регистра ввода-вывода казались более чем достаточными).

В результате операции, устанавливающие или очищающие один бит в этих 32 регистрах ввода-вывода, могут быть выполнены одной инструкцией, которая выполняется за два такта, но те же операции с оставшимися регистрами ввода-вывода требуют полноценной последовательности загрузки-выполнения-сохранения, выполнение которой занимает не менее трех циклов.

Насколько я помню, в AVRlibC было несколько макросов, которые делали почти одно и то же, но имели разные варианты использования:

  • Можно было бы скомпилировать самые быстрые и эффективные инструкции, доступные для этого конкретного адреса ввода-вывода.

  • Один всегда компилируется в sbi/cbi и выдает ошибку компилятора, если вы пытаетесь получить доступ к регистру за пределами 0x20-0x3f

  • ВСЕГДА можно было бы скомпилировать в последовательность load-act-store, независимо от того, можно ли было бы использовать sbi/cbi вместо этого.

  • Один всегда компилируется в последовательность load-act-store, но выдает ошибку компилятора, если вы пытаетесь использовать его по адресу между 0x20 и 0x3f.

По сути, выбор позволял вам выбирать между оптимизированной (но переменной) производительностью, равномерно медленным (но детерминированным) временем выполнения или максимально быстрым (но детерминированным во время компиляции) вариантом [который требовал, чтобы программист знал, находится ли рассматриваемый регистр в пределах или выше 0x20-0x3f].

,