TL;DR:
- Почему
(unsigned long)(0x400253FC)
не эквивалентно(unsigned long)((*((volatile unsigned long *)0x400253FC)))
? - Как я могу заставить макрос, который работает с первым, работать со вторым?
Исходная информация
Окружающая обстановка
Я работаю с процессором ARM Cortex-M3, LM3S6965 от TI, с их StellarisWare (бесплатная загрузка, экспорт с контролем). Я использую gcc версии 4.6.1 (Sourcery CodeBench Lite 2011.09-69). Stellaris предоставляет определения для примерно 5000 регистров и адресов памяти в «inc/lm3s6965.h», и я действительно не хочу переделывать все это. Однако они кажутся несовместимыми с макросом, который я хочу написать.
Битовая полоса
В ARM Cortex-M3 часть памяти имеет псевдоним с одним 32-битным словом на бит пространства памяти периферийного устройства и ОЗУ. Установка памяти по адресу 0x42000000 в 0x00000001 установит первый бит памяти по адресу 0x40000000 в 1, но не повлияет на остальную часть слова. Чтобы изменить бит 2, измените слово по адресу 0x42000004 на 1. Это удобная и чрезвычайно полезная функция. Согласно Техническому справочному руководству ARM, алгоритм вычисления адреса следующий:
bit_word_offset = (byte_offset x 32) + (bit_number × 4)
bit_word_addr = bit_band_base + bit_word_offset
где:
bit_word_offset
— это позиция целевого бита в битовой области памяти.bit_word_addr
— это адрес слова в области памяти псевдонимов, которая соответствует целевому биту.bit_band_base
— это начальный адрес региона псевдонима.byte_offset
— это номер байта в области битового диапазона, который содержит целевой бит.bit_number
— битовая позиция от 0 до 7 целевого бита
Реализация битового бандинга
Файл "inc/hw_types.h"
включает следующий макрос, реализующий этот алгоритм. Чтобы было ясно, он реализует его для модели на основе слов, которая принимает слова, выровненные по 4 байтам, и смещения от 0 до 31 бит, но результирующий адрес эквивалентен:
#define HWREGBITB(x, b) \
HWREGB(((unsigned long)(x) & 0xF0000000) | 0x02000000 | \
(((unsigned long)(x) & 0x000FFFFF) << 5) | ((b) << 2))
Этот алгоритм берет базу, которая находится либо в SRAM по адресу 0x20000000, либо в пространстве периферийной памяти по адресу 0x40000000), и выполняет операцию ИЛИ с 0x02000000, добавляя базовое смещение битовой полосы. Затем он умножает смещение от основания на 32 (эквивалентно сдвигу влево на пять позиций) и добавляет номер бита.
Упомянутый HWREG просто выполняет необходимое приведение для записи в заданное место в памяти:
#define HWREG(x) \
(*((volatile unsigned long *)(x)))
Это хорошо работает с такими заданиями, как
HWREGBITW(0x400253FC, 0) = 1;
где 0x400253FC — магическое число для отображаемого в память периферийного устройства, и я хочу установить бит 0 этого периферийного устройства в 1. Вышеприведенный код вычисляет (конечно, во время компиляции) битовое смещение и устанавливает это слово в 1.
Что не работает
К сожалению, вышеупомянутые определения в "inc/lm3s6965.h" уже выполняют преобразование, выполненное HWREG. Я хочу избежать магических чисел и вместо этого использовать предоставленные определения, такие как
#define GPIO_PORTF_DATA_R (*((volatile unsigned long *)0x400253FC))
Попытка вставить это в HWREGBITW приводит к тому, что макрос больше не работает, так как мешает приведение:
HWREGBITW(GPIO_PORTF_DATA_R, 0) = 1;
Препроцессор генерирует следующую кашу (добавлены отступы):
(*((volatile unsigned long *)
((((unsigned long)((*((volatile unsigned long *)0x400253FC)))) & 0xF0000000)
| 0x02000000 |
((((unsigned long)((*((volatile unsigned long *)0x400253FC)))) & 0x000FFFFF) << 5)
| ((0) << 2))
)) = 1;
Обратите внимание на два экземпляра
(((unsigned long)((*((volatile unsigned long *)0x400253FC)))))
Я считаю, что эти дополнительные приведения являются причиной сбоя моего процесса. Следующий результат предварительной обработки HWREGBITW(0x400253FC, 0) = 1;
действительно работает, подтверждая мое утверждение:
(*((volatile unsigned long *)
((((unsigned long)(0x400253FC)) & 0xF0000000)
| 0x02000000 |
((((unsigned long)(0x400253FC)) & 0x000FFFFF) << 5)
| ((0) << 2))
)) = 1;
Оператор приведения (type)
имеет приоритет справа налево, поэтому должно применяться последнее приведение, а unsigned long
используется для побитовой арифметики (которая в этом случае должна работать правильно). Нигде нет ничего неявного, никаких преобразований с плавающей запятой в указатель, никаких изменений точности/диапазона... самое левое приведение должно просто аннулировать приведения вправо.
Мой вопрос (наконец-то...)
- Почему
(unsigned long)(0x400253FC)
не эквивалентно(unsigned long)((*((volatile unsigned long *)0x400253FC)))
? - Как заставить работать существующий макрос
HWREGBITW
? Или как написать макрос для выполнения той же задачи, но без сбоев при задании аргумента с уже существующим приведением?