środa, 16 marca 2011

PROGMEM i problemy z kompatybilnością

Autor: tmf
Redakcja: Dondu

W związku z wprowadzeniem do toolchaina nowej wersji avr-gcc (4.6 i wyższe) użytkownicy często napotykają drobny problem wynikający ze zmiany nastawienia twórców kompilatora do sposobu deklarowania zmiennych w pamięci FLASH. Zmiany te są daleko bardziej idące, niż tu pokażę, ale to temat na zupełnie oddzielny artykuł.

Na razie zajmijmy się kompatybilnością starego kodu z nowszymi wersjami avr-gcc.


W avr-gcc do wersji 4.4 zmienne i dane mogliśmy umieszczać w pamięci FLASH korzystając z makrodefinicji PROGMEM (plik nagłówkowy <avr/pgmspace.h>):
uint8_t LED PROGMEM = {1, 2, 3};
Lecz równie dobrze mogliśmy stosować nieco inną notację:
const uint8_t LED PROGMEM = {1, 2, 3};
Co nam zmienia modyfikator const? Jak wiemy, powoduje on, że tak zdefiniowana zmienna jest stałą, której w sposób legalny w programie nie możemy zmieniać (możemy ją zmienić przy pomocy wskaźników, lecz takie działanie jest zgodnie ze standardem C niezdefiniowane i np. na architekturze ARM zwykle prowadzi do błędu ochrony pamięci).

Dzięki umieszczeniu const kompilator lepiej zna nasze intencje – powyżej zdefiniowaliśmy 3-elementową tablicę, której zawartość w programie nie może być zmieniona, a sama tablica znajdzie się w pamięci FLASH.

Stąd też umieszczenie słowa const przed definicją nie ma znaczenia wyłącznie kosmetycznego – pozwala kompilatorowi wykrywać błędy polegające na próbie modyfikacji tablicy (czy ogólnie danych w pamięci FLASH, która jak wiemy jest pamięcią niezapisywalną w „normalny” sposób). Twórcy avr-gcc wyszli właśnie z tego założenia – skoro dane w pamięci FLASH są niemodyfikowalne, to nie tylko powinniśmy, ale od wersji avr-gcc 4.6 musimy je poprzedzić modyfikatorem const.

Co jeśli tego nie zrobimy? Oczywiście kompilator zgłosi błąd:
Error      1             variable 'LED' must be const in order to be put into read-only section by means of '__attribute__((progmem))'

Ok, czyli pamiętamy, że jeśli używamy PROGMEM to zmienna do której się ono odnosi musi być zdefiniowana/zadeklarowana z atrybutem const.

Nie jest to wielka dokuczliwość, po prostu zamiast:
char xx[] PROGMEM="AAA";
piszemy:
const char xx[] PROGMEM="AAA";

Proste, prawda?

Proste, ale w sumie uciążliwe, stąd też dla czytelników mojej książki “Język C dla mikrokontrolerów AVR. Od podstaw do zaawansowanych aplikacji” przygotowałem nowe wersje przykładów, w których owo const zostało dodane i dzięki temu poprawnie się one kompilują z nowymi kompilatorami (przy okazji poprawiłem też drobne inne błędy i dodałem pliki projektów dla Atmel Studio 6.1).

Zachęcam do pobrania nowych przykładów (załączników do książki) ze strony: Przykłady na FTP

Czasami czytelnicy pytają dlaczego po prostu nie wymusić, aby stałe były automatycznie umieszczane w pamięci FLASH? Standardowa odpowiedź twórców gcc jest prosta – ponieważ const nie służy do określenia lokalizacji zmiennej. I mają w tym sporo racji. Do tego w gcc 4.7 wprowadzono nowe mechanizmy, ale to zupełnie inna historia ...

Osoby przechodzące na nową wersję gcc powinny pamiętać jeszcze o jednej zmianie. Otóż dla wygody często programiści definiowali sobie nowe typy:
typedef void PROGMEM prog_char;
Powyższe definiuje typ prog_char, w efekcie dane przyporządkowane takiej zmiennej powinny zostać umieszczone w pamięci FLASH. Unikamy dzięki temu pisania za każdym razem char PROGMEM ...

Tu niestety pojawia się problem. Zgodnie ze standardem C, atrybuty [a PROGMEM jest po prostu makrodefinicją rozwijaną do postaci __attribute__((__progmem__)) ] nie mogą być przypisywane do typów.

Taką funkcjonalność niejako przez przypadek posiada kompilator avr-gcc, lecz nie jest to funkcja udokumentowana i wsparcie dla niej może zostać w każdej chwili usunięte, co oczywiście spowoduje katastrofę jeśli z tego mechanizmu korzystamy.

Stąd obecnie nie zaleca się używania typów zdefiniowanycvh w <avr/progmem.h> rozpoczynających się prefiksem prog_.

Jeśli z jakiegoś powodu nie chcemy się dostosować do zmian, możemy włączyć (na własne ryzyko) kompatybilność ze starą wersją gcc definując symbol __PROG_TYPES_COMPAT__:
#define __PROG_TYPES_COMPAT__

#include <avr/pgmspace.h>

const prog_char aa[]="aaa";
Jak widzimy słowa const i tak nie unikniemy. Niemniej ciągle będziemy dostawać ostrzeżenie:
Warning 1 'prog_char' is deprecated: prog_char type is deprecated. [-Wdeprecated-declarations] 
przypominające nam, że to co robimy nie świadczy o naszym rozsądku :-)

Przy okazji mamy podane jak się tego typu ostrzeżeń pozbyć (co jest jeszcze bardziej niemądrym postępowaniem).
Pamiętajmy, żeby atrybuty używać wyłącznie w połączeniu z definicją/deklaracją zmiennej, nigdy z definicją typu.
A może tego całego PROGMEM da się w ogóle jakoś pozbyć? A i owszem, jak to zrobić czytelnicy mojej nowej książki AVR. Praktyczne projekty" dowiedzą się już teraz, a pozostali – jak tylko znajdę chwilkę na nowy artykuł.

2 komentarze:

  1. Można też dodać flagę -Wno-deprecated-declarations -D__PROG_TYPES_COMPAT__ przy kompilacji, wtedy żadne ostrzeżenia się nie pojawią.

    OdpowiedzUsuń
  2. Super, już się cieszyłem, że znalazłem odpowiedź na uruchomienie AVR231 ale co tam, są inne błędy :( W prawdzie sam sobie jakoś z utworzeniem projektu w atmel studio 7(wcześniej 6.2) poradziłem o tyle podczas aktualizacji mam ciągle błąd że blednę crc (a przecież wyłączyłem CRC w konfiguracji) - pomyślałem, że coś skopałem więc jaka była moja radość, gdy pobrałem zaktualizowane przykłady. Od razu przerobiłem projekt z przykładu R27 dla atmegi32 (zmiana procesora, dodałem do opcji linkera –nostartfiles oraz -Wl,--section-start=.text=0x3800) no ale teraz mam problem w miejscu użycia SPMCSR w pliku aesflash.S - zauważyłem, że to inny plik niż w starym projekcie z książki). Mogę prosić o pomoc? W sensie, proszę o pomoc:)

    OdpowiedzUsuń