_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.