Я пишу код системного уровня для встроенной системы без защиты памяти (на Коре-M1 ARM, компилируя с gcc 4.3) и потребность к чтению-записи непосредственно к регистру с отображенной памятью. До сих пор мой код похож на это:
#define UART0 0x4000C000
#define UART0CTL (UART0 + 0x30)
volatile unsigned int *p;
p = UART0CTL;
*p &= ~1;
Есть ли какой-либо более короткий путь (короче в коде, я имею в виду), который не использует указатель? Я ищущий способ написать фактический код присвоения, столь же короткий как это (это было бы хорошо, если бы я должен был использовать больше #defines):
*(UART0CTL) &= ~1;
Что-либо, что я попробовал до сих пор, закончилось с gcc, жалующимся, что он не мог присвоить что-то lvalue...
Я бы хотел придираться: мы говорим о 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
, который не может быть нулевым? Это семантически то, почему ссылки были созданы для.
#define UART0 ((volatile unsigned int*)0x4000C000)
#define UART0CTL (UART0 + 0x0C)
#define UART0CTL ((volatile unsigned int *) (UART0 + 0x30))
: - P
Отредактировано для добавления: О, в ответ на все комментарии о том, как вопрос помечен как C ++, так и C, вот Решение C ++. :-P
inline unsigned volatile& uart0ctl() {
return *reinterpret_cast<unsigned volatile*>(UART0 + 0x30);
}
Его можно застрять прямо в файле заголовка, как и макрос в стиле C, но вы должны использовать синтаксис вызова функции для его вызова.
Другой вариант, который мне нравится для встраиваемых приложений, - это использование компоновщика для определения разделов для ваших аппаратных устройств и сопоставления вашей переменной с этими разделами. Это имеет то преимущество, что если вы ориентируетесь на несколько устройств, даже от одного и того же поставщика, такого как TI, вам, как правило, придется изменять файлы компоновщика для каждого устройства в отдельности. То есть разные устройства в одном семействе имеют разное количество внутренней памяти с прямым отображением, и от платы к плате у вас может быть разное количество оперативной памяти и оборудования в разных местах. Вот пример из документации GCC:
Обычно компилятор помещает объекты, которые он генерирует, в разделы , такие как data и bss. Иногда, однако, вам нужны дополнительные разделы, или вам нужно, чтобы определенные переменные отображались в специальных разделах , например, для сопоставления со специальным оборудованием. Атрибут section указывает, что переменная (или функция) находится в определенном разделе .Например, эта небольшая программа использует несколько определенных имен разделов:
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 () { / * Инициализировать указатель стека * / init_sp (stack + sizeof ( stack)); / * Инициализировать инициализированные данные * / memcpy (& init_data, & data, & edata - & data); / * Turn на последовательных портах * / init_duart (& a); init_duart (& b); }
Используйте атрибут раздела с глобальными переменными, а не локальными переменные, как показано в примере.
Вы можете использовать атрибут section с инициализированными или неинициализированными глобальными переменными , но компоновщик требует, чтобы каждый объект был определен один раз, за исключением того, что неинициализированные переменные предварительно помещаются в общий (или bss) раздел и может быть многократно «определен». Использование атрибута раздела изменит, в какой раздел входит переменная, и может вызвать ошибку компоновщика, если неинициализированная переменная имеет несколько определений. Вы можете принудительно инициализировать переменную с помощью флага -fno-common или атрибута nocommon.
Мне нравится указывать фактические управляющие биты в структуре, а затем назначать их для управляющего адреса. Что-то вроде:
typedef struct uart_ctl_t {
unsigned other_bits : 31;
unsigned disable : 1;
};
uart_ctl_t *uart_ctl = 0x4000C030;
uart_ctl->disable = 1;
(Прошу прощения, если синтаксис не совсем правильный, я действительно давно не писал на C ...)
Вы можете пойти дальше, чем ответ Криса, если хотите, чтобы аппаратные регистры выглядели как простые старые переменные:
#define UART0 0x4000C000
#define UART0CTL (*((volatile unsigned int *) (UART0 + 0x30)))
UART0CTL &= ~1;
Это вопрос вкуса, который может быть предпочтительнее. Я работал в ситуациях, когда команда хотела, чтобы регистры выглядели как переменные, и я работал над кодом, где добавленное разыменование считалось «скрывающим слишком много», поэтому макрос для регистра был оставлен в качестве указателя, который должен был быть явно разыменован (как в ответе Криса).