Как мне напрямую получить доступ к отображенному в памяти регистру AVR с помощью C?

c

Предположим, я хочу написать следующее без использования DDRD или PORTD:

#include <avr/io.h>
#include <util/delay.h>

int main(void) {
    DDRD = 0xFF;
    PORTD = 0xFF; 
}

Я нашел эту ссылку, которая включает список сопоставленных регистров с адресами памяти: https://github.com/DarkSector/AVR/blob/master/asm/include/ m328Pdef.inc

Поэтому я заменил одно из ключевых слов указателем:

#include <avr/io.h>
#include <util/delay.h>

int main(void)
{
    DDRD = 0xFF;
    unsigned char *portd = 0x0b;
    *portd = 0xFF;
}

Но это не работает (у меня светодиод не загорается при втором коде). Почему?

, 👍2

Обсуждение

Его можно оптимизировать, поскольку он не изменчив. Или он использует неправильную инструкцию для хранения данных, поэтому ему нужен другой адрес., @KIIV

1. Если вы используете unsigned char *portd = &PORTD;, будет ли ваш указатель работать для манипулирования портами? Это приведет к пункту №2. 2. Откуда [здесь](https://www.avrfreaks.net/forum/reach-io-port-directly-pointer): unsigned char * myportA = (unsigned char *) 0x003B; Конечно, с учетом вашего микроконтроллера. Если это работает для вас, мне не хватает знаний, зачем вам его приводить, но если передача имени порта по ссылке работает (# 1), а прямое назначение с использованием шестнадцатеричного числа - нет, это как-то связано с этим. 3. Может ли объявление переменной-указателя изменчивой помочь в случае, если что-то еще ее изменит?, @dBm

что в этом вопросе про Arduino?, @Juraj


1 ответ


11

Стоит изучить, как PORTD определяется в реальном заголовочном файле AVR. Соответствующий файл — iom328p.h, и он определяется как:

#define PORTD   _SFR_IO8(0x0B)

_SFR_IO8 определяется как

#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)

и

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

Это показывает, что 0x0b — это не адрес регистра PORTD, а его смещение от адреса __SFR_OFFSET, который равен 0x20. Действительно, если вы посмотрите на таблицу данных ATMEGA328, сводка регистров покажет, что PORTD находится по смещению 0x2b. Итак, это адрес, к которому вы хотите получить доступ через указатель.

Обратите внимание, что здесь также отображается тип указателя. Если мы полностью расширим исходное определение PORTD, мы получим:

#define PORTD   _SFR_IO8(0x0B)
#define PORTD _MMIO_BYTE((0x0B) + __SFR_OFFSET)
#define PORTD _MMIO_BYTE((0x0B) + 0x20)
#define PORTD (*(volatile uint8_t *)(0x2B))

Этот синтаксис принимает целое число 0x2B, приводит его к типу указатель на изменчивый uint8_t и разыменовывает этот указатель, чтобы вы могли назначать его или читать из того места, на которое ссылается указатель. Квалификатор Летучий важен, поскольку он не позволяет компилятору оптимизировать доступ к памяти. Без него компилятор может вместо этого сохранить значение в регистре или не сохранить его вообще, что будет означать, что ваш порт ввода-вывода останется неизменным или будет сообщать о неправильном значении.

Чтобы вернуться к заголовку вашего вопроса, вы спросили, как «напрямую получить доступ к регистру, отображенному в памяти в C», и ответ таков: вы делаете это именно так, как определяет для вас файл заголовка:

(*(volatile uint8_t *)(0x2B) = 0xFF

он же

PORTD = 0xff

Нет более прямого пути, чем этот. Компилятор видит буквальный адрес и записывает в него. То, что вы написали, сильно отличается (с поправкой на правильный адрес и тип:

volatile uint8_t * portd = 0x2b;
 *portd = 0xFF;

При этом выделяется место в памяти, обозначенное portd, которое содержит значение 0x2B. Когда вы пишете *portd = foo, MCU должен получить доступ к месту, где хранится portd, принять найденное там значение в качестве адреса, чтобы найти место, которое вы действительно хотите написать в. Этот метод требует, чтобы вы выделили место в памяти для хранения portd и требует дополнительного доступа к памяти для чтения или записи в регистр порта. Таким образом, этот метод является косвенным.

В действительности, если оптимизация включена, компилятор может оптимизировать указатель и заменить его тем же литеральным доступом, что и в случае PORTD, однако это зависит от структуры программы и, в частности, квалификации и области действия указателя. . Максимально ограничить область действия portd и/или объявить его как Летучий uint8_t * const portd = 0x2b (что указывает на то, что значение указателя является постоянным, даже если значение указанный является изменчивым) увеличит вероятность оптимизации указателя.


Причина, по которой заголовки AVR определяют регистры порта ввода-вывода таким образом, заключается в том, что в дополнение к стандартным инструкциям загрузки/сохранения, которые могут получить доступ ко всему адресному пространству памяти данных и для выполнения которых требуется три тактовых цикла, регистры ввода-вывода являются специальными и Доступ к нему также возможен с помощью инструкций IN/OUT, которые выполняются за два цикла. Инструкции загрузки/сохранения используют адрес памяти для регистров ввода-вывода (начиная со смещения 0x20), тогда как инструкции IN/OUT используют адрес ввода-вывода, который начинается с 0x00, но соответствует 0x20 в адресном пространстве памяти. Из даташита:

Поэтому, когда вы получаете доступ к местоположению ввода-вывода, оптимизатор должен признать, что он может выдать более эффективную инструкцию, чем стандартная загрузка/сохранение, и заменит загрузку/сохранение на 0x2b на вход/выход на 0x0b.

р>
,

Re «Этот метод требует выделения места в памяти для хранения portd и требует дополнительного доступа к памяти»: Не совсем. Он компилируется в тот же машинный код, что и PORTD = 0xff., @Edgar Bonet

Она может быть *оптимизирована* по тем же инструкциям, но абстрактная машина C получает указание делать то, что я описал, и нет никакой гарантии, что такая оптимизация произойдет. Это будет зависеть от области применения portd и уровня оптимизации, но в любом случае это не *более* прямое решение, чем использование существующего макроса PORTD., @ajb

Затем вы можете пояснить в своем ответе, что под «MCU» вы подразумеваете абстрактную машину C, а не реальный MCU., @Edgar Bonet

Поведение цели должно быть эквивалентно поведению абстрактной машины; в этом вся суть. Доступ к буквальному адресу эквивалентен доступу к разыменованному указателю только в ограниченном подмножестве случаев, поэтому компилятор будет выдавать инструкции по разыменованию указателя *если только он не сможет определить, что литеральный доступ эквивалентен*. Попробуйте разные области действия и квалификации для portd и посмотрите, какие инструкции выдаются. В любом случае стоит упомянуть об оптимизации, поэтому я добавил это в ответ., @ajb

По поводу «Попробуйте разные области и квалификации для portd»: для конкретного случая вопроса он _будет_ оптимизирован. Нет никакого «может». Если вам удастся заставить компилятор сохранить указатель, мне бы очень хотелось это увидеть, но это явно будет код, _совсем отличающийся_ от того, о котором идет речь., @Edgar Bonet

Видите ли, я видел здесь много новичков, склонных к ошибочным попыткам микрооптимизации. Некоторые сбиты с толку, поскольку сильно недооценивают возможности компилятора по оптимизации. Никогда не наоборот. Некоторые избегают использования переменных для промежуточных результатов, ошибочно полагая, что они сокращают потребление оперативной памяти. На самом деле они только ухудшают читабельность программы. Ваши объяснения предполагаемой стоимости переменной portd могут только усилить это заблуждение. Использование промежуточных переменных — один из тех случаев, когда следует _действительно_ доверять компилятору микрооптимизацию., @Edgar Bonet

Вы правы, почти всегда лучше позволить компилятору позаботиться о подобных оптимизациях. Однако *важно* понимать, что в двух разных случаях вы говорите машине сделать две разные вещи, даже если синтаксис выглядит очень похожим. Также важно понимать, как область действия и квалификация влияют на поведение компилятора. Кстати, когда portd имеет глобальную область видимости и не является константой, я все равно получаю инструкции указателя на любом уровне оптимизации с AVR-GCC 4.9.2. Дальнейшее обсуждение этой темы, вероятно, следует провести в чате., @ajb