logo elektroda
logo elektroda
X
logo elektroda
REKLAMA
REKLAMA
Adblock/uBlockOrigin/AdGuard mogą powodować znikanie niektórych postów z powodu nowej reguły.

[AVR-GCC] Napisy i oszczędzania pamięci RAM

leonow32 14 Sie 2010 13:00 3862 13
REKLAMA
  • #1 8397373
    leonow32
    Poziom 30  
    Piszę sobie programik, który wykonuje różne czynności i wysyła komunikaty do komputera przez UART, które mają być pokazane na terminalu. Problem polega na tym, że dość szybko wyczerpała mi się pamięć RAM. Po sprawdzeniu co w tym RAMie siedzi odkryłem, że wszystkie napisy zapisywane są właśnie w RAMie a nie pamięci flash. Jak zrobić, żeby program czytał napisy z flasha a nie z RAMu?

    Przykładowa funkcja, która sprawdza stan łącza I2C:
    void i2c_status(void) {
    	uart_write_txt("\t\tTWSR=0x");
    	switch(TWSR) {
    		case 0x08:
    			uart_write_txt("08 START out");
    			break;
    		case 0x10:
    			uart_write_txt("10 RE-START out");
    			break;
    		case 0x18:
    			uart_write_txt("18 SLA+W out, ACK in");
    			break;
    		case 0x20:
    			uart_write_txt("20 SLA+W out, NACK in");
    			break;
    		case 0x28:
    			uart_write_txt("28 Data out, ACK in");
    			break;
    		case 0x30:
    			uart_write_txt("30 Data out, NACK in");
    			break;
    		case 0x38:
    			uart_write_txt("38 Arbitration lost");
    			break;
    		case 0x40:
    			uart_write_txt("40 SLA+R out, ACK in");
    			break;
    		case 0x48:
    			uart_write_txt("48 SLA+R out, NACK in");
    			break;
    		case 0x50:
    			uart_write_txt("50 Data in, ACK out");
    			break;
    		case 0x58:
    			uart_write_txt("58 Data in, NACK out");
    			break;
    		default:
    			uart_write_hex(TWSR);
    			uart_write_txt(" Unknown code");
    			break;
    	}
    	uart_write_txt("\n\r");
    }

    Jak widać, napisów jest całkiem sporo, więc RAM szybko się kończy.

    Zamieszczam jeszcze funkcje do wysyłania napisów przez UART, może tu jest jakiś błąd?
    void uart_write(uint8_t data) {
    	while(!(UCSRA & (1<<UDRE)));
    	UDR = data;
    }
    
    void uart_write_txt(char * data) {
    	while(*data) uart_write(*data++);
    }
  • REKLAMA
  • #2 8397405
    mario06
    Poziom 15  
    Przykład zapisu tablicy w pamięci flash i trzymania jej tylko tam: unsigned char temp[3] PROGMEM = {0,1,2};
    aby odczytać dane z takiej zmiennej wykorzystać należy polecenie postaci: data = pgm_read_byte(&temp[j])
  • REKLAMA
  • #4 8397553
    Andrzej__S
    Poziom 28  
    To prawda co napisał mario06. Chciałem tylko dodać, że ciągi trochę wygodniej można zapisywać tak:
    
       const prog_char nazwa_ciagu[] PROGMEM = "Ciag znakow";
    

    Terminator ciągu (0x00) jest dodawany automatycznie.

    W celu wyświetlenia na LCD lub wysłania przez USART można skopiować ciąg znaków z FLASH do RAM za pomocą:
    
       strcpy_P(wsk_do_bufora_w_RAM, wsk_do_ciagu_we_FLASH);
    

    Funkcja strcpy_P kopiuje do momentu napotkania znaku 0x00 (łacznie z tym znakiem), więc nie trzeba znać rozmiaru ciągu. Trzeba tylko zapewnić odpowiednio duży bufor w RAM, żeby ciąg się zmieścił.

    No i trzeba oczywiście pamiętać o:
    
    #include <avr/pgmspace.h>
    
  • #5 8397571
    mario06
    Poziom 15  
    Ze zmiennymi trzymanymi we flashu nie ma tak łatwo, do ich odczytu musisz korzystać z odpowiednich funkcji. Tak jak w pokazanym kodzie dajesz funkcji uart_write_txt ciąg znaków teraz musisz podejść do tego inaczej. Musisz zadeklarować zmienne prezentujące ciągi znaków w sposób:
    char temp[3] PROGMEM = {'a','b','c'};
    , PROGMEM spowoduje iż zmienna będzie zapisana tylko w pamięci flash. Tu pojawia się problem z odczytem, bo bezpośrednio się do zmiennej we flash nie dostaniesz i do tego służy działanie:
    data = pgm_read_byte(&temp[j])
    odczyta ono dane długości bajta zpod zmiennej temp o indeksie j i zapisze do zmiennej data. Jak dobrze pamiętam powinny też byćfunkcje do odczytu dłuższych danych, ale tobie do obsługi ciągów znaków one nie są potrzebne.
  • #7 8397608
    Andrzej__S
    Poziom 28  
    avr_libc_user_manual napisał:

    In order to put the strings in Program Space, you have to have explicit declarations for each string, and put each string in Program Space:
    char string_1[] PROGMEM = "String 1";
    char string_2[] PROGMEM = "String 2";
    char string_3[] PROGMEM = "String 3";


    Czyli deklaracja:
    
    const prog_char nazwa_ciagu[] PROGMEM = "Ciag znakow";
    

    jest poprawna.

    A funkcja strcpy_P to nie strcpy. Funkcja strcpy_P właśnie służy do kopiowania ciągów z FLASH do RAM.

    Ja mam w ten sposób zbudowane całkiem pokaźne menu.

    EDIT:
    Dodam jeszcze, że można dodawać znaki specjalne, np:
    
    const prog_char nazwa_ciagu[] PROGMEM = "Ci" 0x01 "g znak" 0x02 "w";
    

    gdzie 0x01 odpowiada literze ą (czyli np. mamy ją pod tym adresem w CGRAM wyświetalcza HD44780), a 0x02 odpowiada literze ó.
  • #8 8398894
    leonow32
    Poziom 30  
    hotdog napisał:
    http://avr.elektroda.eu/index.php?q=node/14

    Pozdr.

    Dziękuję w szczególności za tę odpowiedź :) bardzo mi się to przydało. Problem rozwiązałem w taki sposób:

    Przykładowa funkcja po modyfikacji
    void i2c_status(void) {
    	uart_write_progmem(PSTR("\t\tTWSR=0x"));
    	switch(TWSR) {
    		case 0x08:
    			uart_write_progmem(PSTR("08 START out"));
    			break;
    		case 0x10:
    			uart_write_progmem(PSTR("10 RE-START out"));
    			break;
    		case 0x18:
    			uart_write_progmem(PSTR("18 SLA+W out, ACK in"));
    			break;
    		case 0x20:
    			uart_write_progmem(PSTR("20 SLA+W out, NACK in"));
    			break;
    		case 0x28:
    			uart_write_progmem(PSTR("28 Data out, ACK in"));
    			break;
    		case 0x30:
    			uart_write_progmem(PSTR("30 Data out, NACK in"));
    			break;
    		case 0x38:
    			uart_write_progmem(PSTR("38 Arbitration lost"));
    			break;
    		case 0x40:
    			uart_write_progmem(PSTR("40 SLA+R out, ACK in"));
    			break;
    		case 0x48:
    			uart_write_progmem(PSTR("48 SLA+R out, NACK in"));
    			break;
    		case 0x50:
    			uart_write_progmem(PSTR("50 Data in, ACK out"));
    			break;
    		case 0x58:
    			uart_write_progmem(PSTR("58 Data in, NACK out"));
    			break;
    		default:
    			uart_write_hex(TWSR);
    			uart_write_progmem(PSTR(" Unknown code"));
    			break;
    	}
    	uart_write_progmem(PSTR("\n\r"));
    }


    a funkcja do odczytywania napisów z flasha i wysyłania na uart wygląda tak:
    void uart_write_progmem(const char * data) {
    	char bufor;
    	while ((bufor = pgm_read_byte(data++))) uart_write(bufor);
    
    }

    Po przeróbkach kod programu zwiększył się nieznacznie, a zwolniło się z RAMu aż 240 bajtów!!! Zaraz przerobię wszystkie funkcje i aż mnie ciekawi ile miejsca się zwolni :D Da się to jeszcze bardziej usprawnić?

    EDIT:
    Po przerobieniu całego programu uzyskałem taki rezultat :D
    Device: atmega8
    
    Program:    4904 bytes (59.9% Full)
    (.text + .data + .bootloader)
    
    Data:          9 bytes (0.9% Full) - a przedtem tu było pełno :D
    (.data + .bss + .noinit)
  • REKLAMA
  • #9 8400523
    hotdog
    Poziom 26  
    Jedyne co IMO możesz jeszcze zrobić to połączyć stringi ze sobą. Pogrupować słowa ze sobą ("ACK ", "SLA+R ","START ","in ", "out ") itd. Dodatkowo napisać funkcję konwartującą liczby na znaki ascii. i dalej połączyć stringi razem. Suma sumarum zaoszczędzisz trochę pamięci programu, kosztem cykli do wykonania funkcji łączenia strngów.

    Czy gra warta świeczki... Jak masz już mało flash to może i robisz urządzenie seryjne to może i tak (soft piszesz tylko raz, a później zaoszczędzisz parę zł kosztem avr'a z większa liczbą pamięci na każdym urządzeniu). Jak robisz coś dla siebie lub w kilku sztukach to chyba nie warto.

    Pozdrawiam
  • REKLAMA
  • #10 8474555
    DonQuijote88
    Poziom 14  
    Przyłączę się do tematu, gdyż mój problem dotyczy zastosowania ujętej tutaj wiedzy.

    Mianowicie deklaruję sobie zmienną:

    unsigned char str[16][16][16] PROGMEM;


    przy czym:
    str[nr. submenu][nr. pozycji][ciąg znaków]

    Deklaracja występuje bezpośrednio po dyrektywach #include jest to zatem zmienna globalna.

    Dalej w funkcji defineStrs() definiuję sobie tą zmienną w następujący sposób:



    
    void defineStrs()
    {
         str[0][0] PROGMEM = "jakiś napis     ";
    }
    
    


    Ale niestety przy kompilacji występuje błąd w linii str[0][0] PROGMEM... o treści:
    Cytat:
    error: expected ';' before '__attribute__'


    Jeżeli natomiast usunę z w/w linii PROGMEM, to otrzymam taki błąd:
    Cytat:
    error: assignment of read-only location 'str[0][0]'


    A teraz zagadka: co tu nie gra?
    Czy może PROGMEM powinien występować tylko w definicjach - w deklaracji nie?

    Proszę o poradę.

    Pozdrawiam,
    DQJ
  • #11 8474748
    mario06
    Poziom 15  
    Problem dotyczy tego iż do działania na pamięci flash są przeznaczone odpowiednie funkcje i bezpośrednio nie zapiszesz do zmiennej określonej wartości. Działania na tej pamięci są dobrze opisane w już podanym wyżej linku http://avr.elektroda.eu/index.php?q=node/14 .
  • #12 8474894
    hotdog
    Poziom 26  
    Problem jest już w języku C. Wartość w zmiennej można zapisać tylko przy inicjalizacji zmiennej. Czyli można zrobić:

    char napis = "siemano"

    a nie można już zrobić

    napis = "nie siemano"


    Twój problem możesz rozwiązać w taki sposób że np deklarując zmienną przypisujesz odrazu wartość:
    unsigned char str[16][16][16] ={{"bleble","bleble2","bleble3 .. "bleble16"},{"bleble","bleble2","bleble3 .. "bleble16"}....}

    Nie wiem czy do końca to chciałeś zrobić bo zadeklarowałeś 16x16 stringów o rozmiarze 16.

    Pozdro
  • #13 8476614
    DonQuijote88
    Poziom 14  
    No właśnie nie bardzo odpowiada mi taki sposób zapisu - robi się straszny bałagan wtedy... No ale lepszy rydz niż nic. Zastosuję powyższe, chyba że znajdę coś lepszego.
REKLAMA