Используют ли определения регистров в стиле Struct оперативную память

Итак, я искал ответ в Интернете и нигде не получил.

Что я хочу знать, так это то, потребляют ли определения регистров в стиле структуры для микроконтроллеров (ARM mcu, AVR mcu) оперативную память. Я знаю, что если объект структуры создан, он будет потреблять ОЗУ (либо в стеке, либо где-то еще).

Но как насчет определений регистров, подобных приведенным ниже, которые используются ARM в их CMSIS и аналогичны тому, что новая серия ATTiny использует для своих определений регистров, насколько мне известно.

Они специально потребляют оперативную память. Я совершенно уверен, что они будут несколько потреблять флэш-память/программное пространство, но ОЗУ?

#define PORT              ((Port     *)0x41008000UL) /**< \brief (PORT) APB Base Address */
typedef struct {
       PortGroup                 Group[4];    /**< \brief Offset: 0x00 PortGroup groups [GROUPS] */
} Port;
typedef struct {
  __IO PORT_DIR_Type             DIR;         /**< \brief Offset: 0x00 (R/W 32) Data Direction */
  __IO PORT_DIRCLR_Type          DIRCLR;      /**< \brief Offset: 0x04 (R/W 32) Data Direction Clear */
  __IO PORT_DIRSET_Type          DIRSET;      /**< \brief Offset: 0x08 (R/W 32) Data Direction Set */
  __IO PORT_DIRTGL_Type          DIRTGL;      /**< \brief Offset: 0x0C (R/W 32) Data Direction Toggle */
  __IO PORT_OUT_Type             OUT;         /**< \brief Offset: 0x10 (R/W 32) Data Output Value */
  __IO PORT_OUTCLR_Type          OUTCLR;      /**< \brief Offset: 0x14 (R/W 32) Data Output Value Clear */
  __IO PORT_OUTSET_Type          OUTSET;      /**< \brief Offset: 0x18 (R/W 32) Data Output Value Set */
  __IO PORT_OUTTGL_Type          OUTTGL;      /**< \brief Offset: 0x1C (R/W 32) Data Output Value Toggle */
  __I  PORT_IN_Type              IN;          /**< \brief Offset: 0x20 (R/  32) Data Input Value */
  __IO PORT_CTRL_Type            CTRL;        /**< \brief Offset: 0x24 (R/W 32) Control */
  __O  PORT_WRCONFIG_Type        WRCONFIG;    /**< \brief Offset: 0x28 ( /W 32) Write Configuration */
  __IO PORT_EVCTRL_Type          EVCTRL;      /**< \brief Offset: 0x2C (R/W 32) Event Input Control */
  __IO PORT_PMUX_Type            PMUX[16];    /**< \brief Offset: 0x30 (R/W  8) Peripheral Multiplexing */
  __IO PORT_PINCFG_Type          PINCFG[32];  /**< \brief Offset: 0x40 (R/W  8) Pin Configuration */
       RoReg8                    Reserved1[0x20];
} PortGroup;

ПРИМЕЧАНИЕ. Весь код, представленный в приведенных выше блоках кода, является прямым цитированием пользователя на EEVBlog. Это касается определений регистров, представленных в CMSIS. Ссылка находится здесь.

РЕДАКТИРОВАТЬ: я понимаю, что у микроконтроллеров есть регистры, и доступ к этим регистрам не потребляет ОЗУ. Но меня смущает способ обращения к этим регистрам. Например :

// if a register address is 0x50

#define address 0x50    // This consumes no RAM as it is resolved during compilation

uint8_t addr = 0x50;    // This consumes RAM because it is now a variable


// So what about this??

typedef struct {
    uint8_t addr = 0x50;
} address_group;

Таким образом, доступ к самим регистрам не будет занимать место в ОЗУ, но метод, который мы используем для простого обращения к этим адресам, в данном случае с использованием структур, - это то, что меня смущает.

Все регистры могут быть #define'd один за другим, что не будет потреблять ОЗУ, но выбор сделать это в формате структуры ...?


person Tine    schedule 04.02.2021    source источник
comment
Нет, они не потребляют оперативной памяти   -  person marco-a    schedule 04.02.2021
comment
Они также не потребляют вспышку.   -  person Codo    schedule 04.02.2021
comment
Не требуется ОЗУ.   -  person Devolus    schedule 04.02.2021
comment
Ничего себе, 3 человека говорят, что они не потребляют оперативную память. Так все ли они разрешаются во время компиляции? Или на этапе препроцессора?   -  person Tine    schedule 04.02.2021
comment
Обычно вы определяете указатель на этот тип данных и назначаете адрес блока регистров, отображаемого в память, указателю. Разыменовав указатель, вы можете получить прямой доступ к аппаратному регистру. Это то, что делает первая строка с макросом PORT, в котором даже отсутствует сама переменная-указатель.   -  person Gerhardh    schedule 04.02.2021
comment
@Gerhardh Я думаю, что на самом деле я немного понимаю. Таким образом, трюк состоит в том, чтобы делать все как в обычном коде C (имеется в виду то, что обычно требует ОЗУ), а затем строка, которая все меняет, - это #define, которая заставляет препроцессор сначала разрешить это. Я прав, предполагая это?   -  person Tine    schedule 04.02.2021
comment
Пожалуйста, отредактируйте свой вопрос, объяснив на письменном английском языке, как вы определяете потребление оперативной памяти, и предоставьте минимальный воспроизводимый пример в вашем вопросе. Как насчет стека вызовов? Какой точный кросс-компилятор вы используете? Какая-то версия GCC ? Который из? Какие параметры компилятора?   -  person Basile Starynkevitch    schedule 04.02.2021
comment
Ваше оборудование предоставляет ряд регистров. Они находятся внутри ЦП или какого-либо периферийного устройства и доступны только через адрес памяти. Нет смысла создавать копию этих регистров в ОЗУ. Вы просто напрямую читаете или пишете из адреса памяти, где находится регистр. Это `#define' предоставляет только имя для этой области памяти.   -  person Gerhardh    schedule 04.02.2021


Ответы (3)


Для управления периферийными устройствами, такими как GPIO и UART, периферийные устройства предоставляют регистры, которые отображаются в адресное пространство памяти MCU.

Типичный диапазон адресов для таких регистров на STM32 MCUS — от 0x40000000 и выше. Это отдельно от флэш-памяти и оперативной памяти.

Например: регистр скорости передачи для USART1 может находиться по адресу 0x41006008. Таким образом, прочитав слово (32 бита) с этого адреса, можно прочитать скорость передачи данных. А написав по адресу, его можно изменить.

В C это может выглядеть так:

*(volatile uint32_t*)0x41006008 = 115200;

Однако гораздо читабельнее и эффективнее выглядит так:

USART1.BRR = 115200;

Все определения и typedef, показанные в вашем вопросе, объявляют типы данных (например, PortGroup) и псевдопеременные (например, PORT). Я называю их псевдо, потому что они не являются обычными переменными, размещенными в ОЗУ, а представляют собой отображаемые в память структуры, встроенные в оборудование. (И объявление типа данных в любом случае никогда не потребляет память.)

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

person Codo    schedule 04.02.2021

Строго говоря, отображаемые в память регистры в некотором смысле являются оперативной памятью, хотя они могут обновляться как аппаратно, так и программно. Они расположены на карте памяти в месте, где компоновщику не разрешено возиться. Компилятор вообще не знает, что область регистров существует.

С помощью методов, описанных здесь: Как получить доступ к аппаратному регистру из прошивки?, или используя заранее созданную карту регистров от поставщика, мы сообщаем компилятору, что нужно получить доступ к области памяти, где он понятия не имеет, что хранится.

Вы можете сообщить компилятору, например, что в этом месте есть структура PortGroup, начиная с адреса 0x41008000UL и далее. Затем компилятор слепо доверяет программисту и получает доступ к этой области через предоставленный указатель, используя объявленные типы структуры. Если эти типы совпадают с аппаратными регистрами, то все будет работать так же, как если бы вы разместили там переменные. Но на самом деле вам не нужно заставлять компоновщик выделять память в этой области, потому что все уже предусмотрено в аппаратном обеспечении.

Доступ к регистрам или доступ к любой другой форме переменной в этом отношении не обязательно требует оперативной памяти. Учитывая любую случайную переменную int x;, сама x где-то размещается, но акт доступа к ней с помощью x=0; и т. д., скорее всего, не требует оперативной памяти.


И, наконец, некоторые заблуждения: #define address 0x50 не потребляет ОЗУ не потому, что он разрешается во время компиляции, а потому, что вместо этого он потребляет ПЗУ. Все числа, используемые программой, будут размещены где-то в исполняемом файле.

Если у вас есть uint8_t addr = 0x50; ... if(addr == something), оптимизирующий компилятор не обязательно должен выделять место в ОЗУ для addr. Это может оптимизировать всю переменную. См. этот пример для x86: https://godbolt.org/z/KroKhq. Выделять переменную не имело смысла, поэтому компилятор сохранил магическое число 0x50 (80 dec) в машинном коде как часть инструкции cmp. Вы получите идентичный машинный код, если используете #define в том же примере.

person Lundin    schedule 04.02.2021
comment
#define address 0x50 НЕ потребляет ПЗУ. Объявление define само по себе не генерирует никакого кода и ничего не выделяет. Он имеет эффект только в том случае, если он используется как x.y = address;. Затем он будет генерировать код (включая константы). - person Codo; 04.02.2021
comment
@Codo Ну, я как бы предполагал, что это будет использоваться в программе, и полагаю, что у читателя есть здравый смысл. Код не потребляет никакого ПЗУ, если вы также не загружаете программу в MCU... - person Lundin; 04.02.2021
comment
Обычно программа включает заголовочные файлы с тысячами define и будет использовать только некоторые из них. Важно понимать, генерирует ли код define и когда. - person Codo; 04.02.2021

Нет, эти структуры не занимают оперативной памяти. Оперативная память потребляется, когда вы фактически определяете переменную с таким кодом

unsigned int myInteger;  // takes RAM
struct foo myStruct;     // takes RAM

Строки, подобные приведенным выше, говорят вашему компилятору/компоновщику создать символы с именами myInteger и myStruct и выделить место для этих символов в области ОЗУ вашего микроконтроллера (возможно, в вашем стеке).

С другой стороны, когда вы определяете тип структуры или создаете typedef, это отличается от определения переменной и не требует оперативной памяти:

struct foo {  // Does not take RAM, just defines a type.
  ...
};

Когда вы определяете указатель на структуру с помощью приведенного ниже кода, вы просто определяете выражение, результатом которого является указатель на структуру, уже существующую в аппаратном обеспечении. Вы не делаете ничего, что должно потреблять оперативную память:

// Does not take up RAM, just defines an expression.
#define PORTA ((struct GPIO *)0x1230)

Когда вы используете PORTA в своем коде, строки кода, которые его используют, будут занимать некоторое место в коде. Эти строки могут занимать некоторое пространство стека при выполнении, но они не должны постоянно занимать оперативную память.

person David Grayson    schedule 04.02.2021