_sfr_mem_addr adres >0x100
_SFR_MEM_ADDR(x)
zwraca fizyczny adres rejestru peryferyjnego (SFR) w przestrzeni danych mikrokontrolera AVR. > 0x100
, oznacza to, że wskazany adres leży już w obszarze SRAM, a nie w zakresie mapy SFR/rozszerzonego I/O (< 0x100). LDS/STS
lub poprzez zwykłe wskaźniki w C; nie wolno używać makr _SFR_IO8/_SFR_MEM8
ani instrukcji IN/OUT
.Kluczowe punkty
• SFR-y standardowych AVR mieszczą się w adresach 0x00–0xFF
(0x00–0x3F I/O, 0x60–0xFF rozszerzone I/O).
• 0x100
jest początkiem SRAM (RAMSTART
).
• Warunek „_sfr_mem_addr > 0x100
” jest testem „czy to jeszcze rejestr, czy już RAM”.
0x0000–0x001F : 32 rejestry ogólnego przeznaczenia R0–R31
0x0020–0x003F : 64 rejestry I/O (dostęp IN/OUT, 1 cykl)
0x0040–0x005F : nieużywane (alias I/O w niektórych modelach)
0x0060–0x00FF : rozszerzone I/O (dostęp LDS/STS, 2 cykle)
0x0100–… : SRAM (dane użytkownika, stos, zmienne globalne)
Instrukcje:
• IN/OUT
obsługują wyłącznie adresy 0x00–0x3F
.
• LDS/STS
obsługują dowolny adres w przestrzeni danych (16 bitów), najczęściej używane dla 0x60–0xFF
i SRAM >=0x100
.
#define _SFR_IO_ADDR(sfr) ((sfr) - __SFR_OFFSET) // adres I/O (<0x40)
#define _SFR_MEM_ADDR(sfr) (sfr) // pełny adres danych
#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + __SFR_OFFSET)
#define _SFR_MEM8(mem_addr) _MMIO_BYTE(mem_addr)
‐ SFR zdefiniowany przez _SFR_MEM8(0xC6)
(np. UDR0
) ma adres 0xC6
.
‐ Jeśli to makro zwróci wartość >=0x100
, definiuje się już zwykłą zmienną w SRAM, a nie rejestr peryferyjny. Kompilator nie wygeneruje instrukcji IN/OUT
.
; poprawne
lds r16, _SFR_MEM_ADDR(UDR0) ; 0xC6 → OK
; błąd logiczny
lds r16, _SFR_MEM_ADDR(MY_VAR) ; 0x120 → to SRAM, nie SFR
uint8_t tmp = _SFR_MEM8(0x0120); // tmp = SRAM[0x120]
Kompilator nie zgłosi błędu, ale dostęp jest wolniejszy i NIE dotyczy peryferiów.
‐ Nowe rodziny megaAVR-0/tinyAVR-0/1/2 (ATmega4809, ATtiny3217…) mają peryferia mapowane od 0x0800
wzwyż; te układy używają innego pakietu nagłówków (packs) i nie opierają się na _SFR_*
. Test „>0x100” nie ma tam sensu.
‐ Niektóre XMEGA mają rejestry do 0x3FFF
; tam również stosuje się inne makra (_PROTECTED_WRITE()
, struktury pamięci).
‐ Ostatnie wersje avr-libc (≥2.2) nie zmieniły definicji _SFR_*
; dla nowych rdzeni (AVR-DA, AVR-DB) Microchip dostarcza pliki io*.h
, w których rejestry opisuje się strukturami, nie makrami.
‐ Popularne środowiska (Atmel Studio 7 / MPLAB X 6.x) same wybierają odpowiedni model pamięci; przy migracji starego kodu trzeba zweryfikować zakresy adresowe.
Przykładowe uniwersalne makro do zapisu:
#define WRITE_REG8(reg, val) \
do { \
if (_SFR_MEM_ADDR(reg) < 0x40) { \
/* 1-bajtowy IN/OUT */ \
__asm__ volatile("out %[addr], %[v]" \
:: [addr] "I" (_SFR_IO_ADDR(reg)), \
[v] "r" (val)); \
} else { \
/* LDS/STS */ \
reg = (val); \
} \
} while (0)
Chroni przed niezamierzonym użyciem OUT
dla adresów >0x3F.
‐ Brak specyficznych implikacji etycznych; jedynie należy zachować zgodność z licencją avr-libc (BSD-like) i dokumentacją producenta.
‐ Błędne użycie zakresów może prowadzić do nieprzewidywalnej pracy systemu (np. nadpisanie stosu), co w aplikacjach bezpieczeństwa krytyczne.
#if _SFR_MEM_ADDR(MY_REG) >= 0x100
#error "MY_REG nie jest rejestrem SFR!"
#endif
.if
i komunikat .error
. _SFR_*
; użyć plików <avr/io*.h>
przeznaczonych dla danego układu.‐ Na niektórych forach można znaleźć sugestie, że SFR-y mogą wystąpić powyżej 0x100
; dotyczy to XMEGA czy megax8U; tam jednak nagłówki definiują inne stałe, a makro _SFR_MEM_ADDR
zachowuje kompatybilność wsteczną (wartości < 0x100).
‐ Kody pisane pod 8051/ARM z makrami sfr
nie są przenośne na AVR – nazwa wygląda podobnie, ale działanie jest odmienne.
‐ Zapoznać się z dokumentem “AVR Instruction Set Manual” – różnice czasowe IN/OUT vs STS/LDS.
‐ Przeanalizować mapę pamięci konkretnego układu w dokumentacji (rozdział „Data Memory”).
‐ Sprawdzić nowe koncepcje „Memory Mapped Peripheral (MMP)” w rodzinach AVR-DA/DB.
Adres > 0x100
po zastosowaniu _SFR_MEM_ADDR
oznacza, że odwołujesz się do obszaru SRAM, a nie do klasycznego rejestru peryferyjnego AVR. Dostęp należy realizować instrukcjami pamięciowymi (LDS/STS
lub wskaźnik w C). Test takiego zakresu pozwala odróżnić prawdziwy SFR (<= 0xFF) od zwykłej komórki RAM i zapobiegać niezamierzonym błędom w sterowaniu peryferiami.
czy dla avr64ea32 w microchip studio wstawka asemblera jest poprawna? u08 i_1; asm volatile ( "lds R24, i_1" "n\t" "sts %0, R24" "n\t" :: "M" (&USART0_TXDATAL) : "r24" );
i_1
, niepoprawne ograniczenie "M"
dla adresu USART0_TXDATAL
, zła notacja nowych linii, brak pełnej informacji o modyfikacji pamięci i rejestrów. Architektura AVR-EA (rdzeń AVR-xt)
• Wszystkie rejestry peryferyjne leżą w zunifikowanej przestrzeni danych ≥ 0x0800, dlatego dostęp realizuje się wyłącznie instrukcjami LDS/STS
lub przez rejestry X/Y/Z.
• Adresy te są 16-bitowe; klasyczne ograniczenia I/O ("M"
, "I"
, "O"
) odwołujące się do zakresu 0–0x3F/0xFF są nieaktualne.
Błędy w kodzie użytkownika
a) "lds R24, i_1"
– zmienna C wstawiona dosłownie. Optymalizator może przenieść ją do rejestru, po czym symbol zniknie ⇒ link-error lub, gorzej, użycie błędnego adresu.
b) "sts %0, R24"
przy jednoczesnym :: "M" (&USART0_TXDATAL)
–
• "M"
wymusza operand natychmiastowy mieszczący się w 8 bitach (stare I/O). Adres USART0_TXDATAL
(= 0x0802) tego nie spełnia ⇒ kompilator odrzuci kod, albo wygeneruje nieprawidłową instrukcję.
• %0
może być użyty, ale tylko z poprawnym ograniczeniem ("n"
lub "i"
dla wartości natychmiastowej 16-bitowej).
c) Sekwencja nowej linii: "n\t"
– powinno być "\n\t"
.
d) Lista clobberów: podany r24
(mała litera) jest OK, ale brakuje "memory"
jeżeli meldujemy kompilatorowi, że modyfikujemy RAM/peryferia.
Poprawna i bezpieczna wstawka (gdy rzeczywiście potrzebna)
#include <avr/io.h>
uint8_t i_1;
asm volatile (
"lds r24, %2 \n\t" // załaduj i_1 -> r24
"sts %0, r24 \n\t" // zapisz do TXDATAL
:
: "n" (_SFR_MEM_ADDR(USART0.TXDATAL)), // natychmiastowy 16-bitowy adres docelowy
"m" (i_1) // źródło w pamięci
: "r24", "memory" // rejestr nadpisany + informacja o RAM
);
• _SFR_MEM_ADDR()
zwraca liczbę, co spełnia ograniczenie "n"
.
• "m"(i_1)
wymusza, aby kompilator faktycznie ulokował zmienną w RAM i wstawił jej adres.
• Lista clobberów informuje o użyciu r24
i potencjalnej zmianie pamięci.
Lepsze (zalecane) rozwiązanie w czystym C
USART0.TXDATAL = i_1; // avr-gcc 12.x wygeneruje identyczny kod LDS/STS
Kompilator sam wybierze rejestry, zachowa reguły ABI i – przy optymalizacji -Os/-O2 – zwykle wygeneruje dwie instrukcje:
lds r24, i_1
sts 0x0802, r24
_SFR_MEM_ADDR()
i nowe definicje rejestrów peryferyjnych są dostępne w <avr/io.h>
. "r"
– dowolny rejestr R0–R31"n"
– natychmiastowa wartość 16-bitowa (kompilator oceni, czy mieści się w zakresach dla LDS/STS)"m"
– dowolna pamięć (adres wygenerowany przez linkera). r24
, można dodać do operandów &"=r"(tmp)
i wstavce pisać %[tmp]
. Kompilator przydzieli wolny rejestr i w razie potrzeby przeniesie dane. -Save-Temps
i kontroluj plik .lss/.s
– zweryfikujesz, jak naprawdę wygląda końcowy assembler. %[name]
) i włącz -Werror=clobbered
, aby kompilator ostrzegł o zapomnianych rejestrach. _SFR_MEM_ADDR()
lub makr ze <avr/io.h>
, a nie z symboli linkera, bo łatwo pomylić przestrzeń I/O z SRAM. i_1
ma zostać w rejestrze (np. wynik wcześniejszego obliczenia), należy zastosować ograniczenie "r"(i_1)
i pominąć lds
. Kompilator może wtedy zanegować potrzebę dodatkowego ładowania. CCP = 0xD8;
. Pokazana wstawka nie spełnia reguł inline assemblera GCC dla nowych AVR-EA: używa niewłaściwego ograniczenia, ryzykuje utratę adresu zmiennej, posiada błędy składniowe. Popraw ją według przedstawionego wzorca lub – prościej i bezpieczniej – zapisz operację w czystym C, pozwalając kompilatorowi wygenerować identycznie wydajny kod.
czy dla avr64ea32 w microchip studio wstawka asemblera w języku C jest poprawna? main() { u08 i_1; asm volatile ( "lds R24, i_1" "n\t" "sts %0, R24" "n\t" :: "M" (&USART0_TXDATAL) : "r24" ); }
"M"
dla adresu USART0_TXDATAL
,i_1
do assemblera,Adresowanie zmiennej i_1
• Instrukcja lds r24, i_1
odwołuje się do symbolu, którego asembler nie zna.
• Zmienna musi być przekazana jako operand wejściowy z ograniczeniem m
(pamięć) lub r
(rejestr).
Ograniczenie operandów dla adresu rejestru peryferyjnego
• "M"
akceptuje tylko 8-bitowe adresy (0–255) przeznaczone dla instrukcji IN/OUT
.
• W AVR-EA wszystkie rejestry peryferyjne (w tym USART0_TXDATAL
= 0x0802) leżą poza tym zakresem; dostęp jest możliwy wyłącznie przez instrukcje LDS/STS
.
• Należy użyć ograniczenia "n"
lub "i"
wraz z _SFR_MEM_ADDR()
.
Składnia łańcuchów znaków
• "n\t"
to literały ‘n’ i ‘t’. Poprawna sekwencja linii to "\n\t"
.
Lista clobberów
• Modyfikowany jest rejestr r24
i pamięć-mapowany rejestr peryferyjny → trzeba dodać "memory"
.
Instrukcje IN/OUT
a LDS/STS
• Na AVR64EA32 (AVRxt
) IN/OUT
obsługują wyłącznie zakres 0x00–0x3F.
• Dla adresu ≥ 0x0100 zawsze stosujemy LDS/STS
; zatem pierwotny wybór instrukcji jest słuszny, lecz implementacja wadliwa.
#include <avr/io.h>
typedef uint8_t u08;
int main(void)
{
u08 i_1 = 0x55; // przykładowa wartość
asm volatile (
"lds r24, %1\n\t" // %1 = zmienna w SRAM
"sts %0, r24\n\t" // %0 = adres TXDATAL
:
: "n" (_SFR_MEM_ADDR(USART0.TXDATAL)), // operand 0
"m" (i_1) // operand 1
: "r24", "memory"
);
return 0;
}
• Nowe rodziny AVR-EA/DA/DB przeniosły wszystkie peryferia do przestrzeni > 0x0800; starsze skróty (IN/OUT
, ograniczenia I
, M
) tracą znaczenie.
• avr-gcc ≥ 12 w trybie -O2/-Os
generuje kod identyczny lub szybszy niż ręczne wstawki dla prostych operacji; preferuje się czysty C:
USART0.TXDATAL = i_1;
• Inline asm pozostaje potrzebny głównie przy sekcjach time-critical, sekwencjach atomowych lub gdy wymagane są niestandardowe instrukcje (np. SLEEP
, WDR
z ograniczonymi cyklami).
Ograniczenie | Do czego służy (AVR-gcc) | Uwagi dla AVR-EA |
---|---|---|
"M" |
0–255 (IO addr) | Niedozwolone dla ≥0x40 |
"I" |
0–63 (IN/OUT) | jw. |
"n" / "i" |
dowolna stała 16-bit | Prawidłowe dla _SFR_MEM_ADDR() |
"m" |
operand w pamięci | Gwarantuje, że zmienna fizycznie istnieje w SRAM |
"r" |
dowolny GPR | Kompilator wybiera rejestr |
• Nadmierne użycie wstawek utrudnia przegląd kodu i audyt bezpieczeństwa; może prowadzić do nieintencjonalnych naruszeń MISRA czy CERT-C.
• Utrzymywanie asm zmniejsza przenośność i komplikuje wsparcie techniczne.
"memory"
do clobberów, gdy wstawka dotyka dowolnej lokacji RAM lub SFR. _SFR_MEM_ADDR()
– gwarantuje zgodność z przyszłymi wersjami plików nagłówkowych. __attribute__((optimize("O0")))
).• Kod korzystający z LDS/STS
na adres 0x0802 nie jest atomiczny wobec przerwań -> w sekcji ISR mogą wystąpić kolizje; rozważ blokadę cli/sei
lub bufor FIFO.
• Przy trybie LTO kompilator może inlinować funkcje i zmieniać rozmieszczenie zmiennych; operand m
pozostaje bezpieczny, ale operand n
musi pozostać stałą.
• „AVR-libc Inline Assembler Cookbook” (rozdz. Constraints).
• Dokumentacja AVR64EA32 – rozdz. „Memory Map and Module Summary”.
• Analiza generowanego kodu z opcją -fdump-rtl-asm
.
• Porównanie efektywności volatile
vs barier pamięciowych (asm volatile("" ::: "memory")
).
Wstawka w oryginalnej formie jest błędna z powodów syntaktycznych i semantycznych; po korekcie należy:
i_1
przez operand wejściowy "m"
, "n"
dla 16-bitowego adresu USART0_TXDATAL
, "\n\t"
i uzupełnić clobbery o "memory"
. Dla większości zastosowań zalecane jest jednak pozostanie przy czystym C – kompilator wygeneruje identyczny, a często bezpieczniejszy kod.