Какой самый короткий код для записи непосредственно на адрес памяти в C / C ++? - PullRequest
18 голосов
/ 10 марта 2010

Я пишу код системного уровня для встраиваемой системы без защиты памяти (на ARM Cortex-M1, компилируемой с gcc 4.3), и мне нужно читать / записывать непосредственно в отображенный в память регистр. Пока мой код выглядит так:

#define UART0     0x4000C000
#define UART0CTL  (UART0 + 0x30)

volatile unsigned int *p;
p = UART0CTL;
*p &= ~1;

Есть ли более короткий путь (я имею в виду в коде), который не использует указатель? Я искал способ написать реальный код назначения, такой короткий, как этот (было бы хорошо, если бы мне пришлось использовать больше #defines):

*(UART0CTL) &= ~1;

Все, что я до сих пор пробовал, заканчивалось тем, что gcc жаловался, что он не может назначить что-то lvalue ...

Ответы [ 6 ]

21 голосов
/ 10 марта 2010
#define UART0CTL ((volatile unsigned int *) (UART0 + 0x30))

: - P

Отредактировано, чтобы добавить: О, в ответ на все комментарии о том, как вопрос помечен как C ++, так и C, вот решение C ++. : -Р

inline unsigned volatile& uart0ctl() {
    return *reinterpret_cast<unsigned volatile*>(UART0 + 0x30);
}

Это может быть вставлено прямо в заголовочный файл, точно так же, как макрос в стиле C, но вы должны использовать синтаксис вызова функции для его вызова.

16 голосов
/ 10 марта 2010

Я хотел бы быть придиркой: мы говорим на C или C ++?

Если C, я охотно откладываю ответ Криса (и я хотел бы удалить тег C ++).

Если C ++, я советую не использовать эти мерзкие C-броски и #define в целом.

Идиоматический способ C ++ - использовать глобальную переменную:

volatile unsigned int& UART0 = *((volatile unsigned int*)0x4000C000);
volatile unsigned int& UART0CTL = *(&UART0 + 0x0C);

Я объявляю типизированную глобальную переменную, которая будет подчиняться правилам области (в отличие от макросов).

Его можно легко использовать (не нужно использовать *()), и поэтому он еще короче!

UART0CTL &= ~1; // no need to dereference, it's already a reference

Если вы хотите, чтобы это был указатель, то это будет:

volatile unsigned int* const UART0 = 0x4000C000; // Note the const to prevent rebinding

Но какой смысл использовать указатель const, который не может быть нулевым? Это семантически, почему ссылки были созданы для.

1 голос
/ 10 марта 2010

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

Обычно компилятор помещает объекты, которые он генерирует, в секции как данные и бсс. Иногда, однако, вам нужны дополнительные разделы, или вам нужны определенные конкретные переменные, чтобы появиться в специальных разделы, например, для сопоставления со специальным оборудованием. Секция Атрибут указывает, что переменная (или функция) живет в конкретный раздел. Например, эта небольшая программа использует несколько конкретные названия разделов:

      struct duart a __attribute__ ((section ("DUART_A"))) = { 0 };
      struct duart b __attribute__ ((section ("DUART_B"))) = { 0 };
      char stack[10000] __attribute__ ((section ("STACK"))) = { 0 };
      int init_data __attribute__ ((section ("INITDATA")));

      main()
      {
        /* Initialize stack pointer */
        init_sp (stack + sizeof (stack));

        /* Initialize initialized data */
        memcpy (&init_data, &data, &edata - &data);

        /* Turn on the serial ports */
        init_duart (&a);
        init_duart (&b);
      }

Используйте атрибут section с глобальными переменными, а не локальными переменными, как показано в примере.

Вы можете использовать атрибут section с инициализированным или неинициализированным глобальные переменные, но компоновщик требует, чтобы каждый объект был определен один раз, за исключением того, что неинициализированные переменные ориентировочно идут в общий (или bss) раздел и может быть многократно «определен». С использованием Атрибут section изменит, в какой раздел переходит переменная и может заставить компоновщик выдать ошибку, если неинициализированная переменная имеет несколько определений. Вы можете принудительно инициализировать переменную с флагом -fno-common или атрибутом nocommon.

1 голос
/ 10 марта 2010

Мне нравится указывать фактические управляющие биты в структуре, а затем назначать их управляющему адресу. Что-то вроде:

typedef struct uart_ctl_t {
    unsigned other_bits : 31;
    unsigned disable : 1;
};
uart_ctl_t *uart_ctl = 0x4000C030;
uart_ctl->disable = 1;

(Прошу прощения, если синтаксис не совсем правильный, на самом деле я довольно давно не кодировал в C ...)

1 голос
/ 10 марта 2010

Вы можете пойти на шаг дальше, чем ответ Криса, если вы хотите, чтобы аппаратные регистры выглядели как простые старые переменные:

#define UART0     0x4000C000
#define UART0CTL (*((volatile unsigned int *) (UART0 + 0x30)))

UART0CTL &= ~1;

Это вопрос вкуса, который может быть предпочтительнее. Я работал в ситуациях, когда команда хотела, чтобы регистры выглядели как переменные, и я работал над кодом, где добавленное разыменование считалось «слишком большим», поэтому макрос для регистра оставлялся как указатель, быть явно разыменованным (как в ответе Криса).

1 голос
/ 10 марта 2010
#define UART0  ((volatile unsigned int*)0x4000C000)
#define UART0CTL (UART0 + 0x0C)
...