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

Jak czytać wielowymiarowe duże tablice w PGM SPACE [C]

mas24 16 Lut 2015 12:29 2943 41
Najlepsze odpowiedzi LABEL_AI_GENERATED

Jak odczytywać wielowymiarowe tablice z pamięci FLASH większe niż 64 kB na AVR i podawać ich elementy do DAC?

Aby odczytywać takie tablice z FLASH większe niż 64 kB na AVR, użyj przestrzeni adresowej __memx i 24-bitowych wskaźników, a wartości pobieraj przez dereferencję wskaźnika do __memx [#14444074][#14466225] Przy przekazywaniu tablic do funkcji nie wpisuj na sztywno rozmiaru w typie, tylko użyj wskaźnika na tablicę, np. const uint16_t (*)[]; puste [] oznacza, że rozmiar nie jest potrzebny do samego rzutowania [#14462034] Trzeba też pamiętać, że ograniczenie dotyczy pojedynczej tablicy: na AVR nie uzyskasz obiektu-tablicy większego niż 32 kB/32767 bajtów, więc większy zbiór danych trzeba podzielić na mniejsze tablice albo zlinkować dane binarne jako obj/lib [#14458319][#14467985] W praktyce autorowi pomogło usunięcie z GetTabsVal zapisu [2048] i wtedy program zaczął czytać wszystkie sampły, choć pojawiło się ostrzeżenie o niezgodnym typie wskaźnika [#14602936]
Podsumowanie AI na podstawie dyskusji. Może zawierać błędy.
REKLAMA
  • #1 14444053
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Witam,

    Potrzebuję umieścić dużo danych w pamięci Flash, jednak prosty sposób ogranicza się tylko do 64 kB, ja potrzebuje więcej. Robię wiec tak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    I nie wiem teraz, jak przeczytać te tablice. TABS1 będzie numerem tablicy, a jak przeczytać poszczególne w nich liczby? Te liczby będą czytane w pętli a rezultat wysyłany do przetwornika DAC.
  • REKLAMA
  • #2 14444074
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    Należy użyć przestrzeni adresowej __memx i 24-bitowych wskaźników.
  • #3 14445111
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Bardzo przepraszam, ale nie jestem w tym aż tak biegły jeszcze, czy mógłby Kolega podać więcej szczegółów?
  • REKLAMA
  • #4 14458031
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Zauważyłem jeszcze jedno dziwne zjawisko. Próbuję zadeklarować 9 tablic po 2048 elementów 16-bitowych, wiec z rachunku wynika, że 9x2048 to 18232 elementy do zaadresowania, nawet jeśli dane są zapisywane podwójnie (2x 8 bit), to mamy 36864 elementy, więc jest jeszcze daleko do magicznej granicy 65536 elementów i według tego powinienem dać radę zadeklarować 32 lub co najmniej 16 takich tablic, tymczasem już przy tablicach 9x2048 kompilator wywala błąd i pisze, że tablica jest za duża.
    Nie rozumiem tego, dlaczego tylko tyle mogę zaadresować?
  • #5 14458179
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    Dlatego, że rozmiar tablicy w C nie może przekroczyć typu int, a więc dla AVR - 32767 bajtów.
  • #6 14458289
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Rozumiem. Czyli dane są liczone podwójnie, bo mam zmienne typu UNSIGNED INT. Więc dane są liczone jako SIGNED INT, skoro tylko 32 kB można zaadresować? A co z drugą połówką, czyli drugimi 32kB?
  • #7 14458319
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    Typ danych nie ma nic do rzeczy - po prostu wielkość tablicy w bajtach nie może przekroczyć 32767. Limit dotyczy pojedynczej tablicy, wszystkie tablice mogą mieć długość o wiele większą.
  • #8 14458380
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Tak więc chętnie zastosowałbym konstrukcję tablic omawianą kilka postów wyżej. Sama konstrukcja jest dla mnie zrozumiała, jednak nadal mam poważne trudności ze zbudowaniem algorytmu ich czytania. Czy byłaby możliwość podania, jak to się robi? Jak mniemam, to 1, najwyżej 2 linijki?
  • #9 14458589
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    google: __memx. A jeśli nie pasuje, to sprawdź jak C przechowuje dane i sobie na tej podstawie napisz funkcję, która czyta tablice dowolnych rozmiarów.
  • #10 14459384
    gaskoin
    Poziom 38  
    Posty: 4159
    Pomógł: 436
    Ocena: 102
    Po co Ci dane w postaci n, n-1,n-2,n-3... 1 ?? W takim przypadku zamiast marnować pamięć lepiej jest przechowywać obiekt, który wie od ilu do ilu masz liczby. Ot taka kompresja:

    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    I pakujesz to do tablicy. Dla ciągów liczb, nie ma sensu inaczej ich przechowywać niż przez takie struktury.
  • #11 14460047
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    mas24 napisał:

    Potrzebuję umieścić dużo danych w pamięci Flash, jednak prosty sposób ogranicza się tylko do 64 kB, ja potrzebuje więcej. Robię wiec tak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    I nie wiem teraz, jak przeczytać te tablice. TABS1 będzie numerem tablicy, a jak przeczytać poszczególne w nich liczby? Te liczby będą czytane w pętli a rezultat wysyłany do przetwornika DAC.



    Spróbuj, nie testowane...
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod


    Oczywiście funkcję GatTabsVal można napisać na różne sposoby np. żeby dane ze wszystkich tablic były widziane jako jedna przestrzeń liniowa. Już nie będę Cię namawiał na przesiadkę na jakiegoś Cortexa, ale tam takich dziwolongów byś nie miał.

    W sensowność tych tablic nie wnikam, zakładam że dane które podałeś są tylko jako przykład.
  • #12 14461827
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Dzięki za zainteresowanie :)

    Sprawdzę to niebawem. Dane w tablicach są tu podane przykładowo. W rzeczywistości te tablice zawierają ciągi liczb - próbek przebiegów okresowych dźwiękowych, które procesor ma odtwarzać. Każda pojedyncza tablica to jeden przebieg, np. sinusoidalny. Pamiętacie wobulator z RE 2'83? To jest jego mikroprocesorowa podobizna.

    Ja to wpiszę wprost w program, gdyż skok do procedury to cenne takty zegara.
    Mam jeszcze pytanie: dlaczego w wyrażeniu "const uint16_t (*)[]" nawias kwadratowy jest pusty?

    gaskoin: Nie ma znaczenia, jak są ułożone te liczby, to tylko przykład. Sugerujesz każdą tablicę zapakować w strukturę? Mi chodzi o to, by były one w pamięci Flash, której mam duużo (256kB) i żeby nie marnować pamięci RAM. Do 32kB to wszystko bardzo ładnie działa w tablicach PGM SPACE, ale chcąc rozszerzyć ilość przebiegów, potrzeba innego podejścia, stąd mój post.
  • #13 14462034
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    mas24 napisał:
    Mam jeszcze pytanie: dlaczego w wyrażeniu "const uint16_t (*)[]" nawias kwadratowy jest pusty?


    Przy rzutowaniu rozmiar tej tablicy nie jest istotny. Podanie tej wartości niczego nie zmieni. Kompilator musi mieć tylko informację, że do wskaźnika na dany typ wpisywane są adresy danych tychże właśnie typów. Puste nawiasy [] informują kompilator, że jest to wskaźnik na tablicę danych typu const uint16_t i tyle mu wystarcza.
  • #14 14464373
    gaskoin
    Poziom 38  
    Posty: 4159
    Pomógł: 436
    Ocena: 102
    Ok. Myślałem, że chcesz w tych tablicach trzymać dane, które da się opisać jakimś równaniem, etc.
  • #15 14464456
    BlueDraco
    Specjalista - Mikrokontrolery
    Posty: 6479
    Pomógł: 939
    Ocena: 421
    A ja dodam, że cały problem bierze się z tego, że zacząłeś projekt od narzucenia typu mikrokontrolera zamiast od założeń, z których powinien wynikać wybór mikrokontrolera. Co za problem wziąć tani uC z pamięcią Flash o pojemności np. 128 KiB, albo nieco droższy z pamięcią np. 1 MiB?
  • REKLAMA
  • #16 14464526
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    BlueDraco napisał:
    A ja dodam, że cały problem bierze się z tego, że zacząłeś projekt od narzucenia typu mikrokontrolera zamiast od założeń, z których powinien wynikać wybór mikrokontrolera. Co za problem wziąć tani uC z pamięcią Flash o pojemności np. 128 KiB, albo nieco droższy z pamięcią np. 1 MiB?


    Jak zwykle będę polemizował. Po pierwsze ograniczenia wynikają tu ze standardu języka C o czym już pisałem. Po drugie już w pierwszym poście podałem rozwiązanie jak to prosto zrobić na AVR (po raz trzeci - użyć przestrzeni adresowej __memx, tylko autorowi najwyraźniej nie chce się wrzucić tego w google, bo już pierwszy link wyjaśnia o co chodzi). Dyskusja dalej idzie w co raz bardziej absurdalnym kierunku, co po części wynika z tego, że nie wiemy dokładnie co autor chce zrobić, bo zamiast problemu, który ma, skupiamy się na kiepskich próbach poprawy wymyślonego przez autora rozwiązania. Jeśli chodzi o dostęp do jakiś danych to zamiast kombinować z tablicami, prościej skompilować plik binarny z tymi danymi do pliku obj (i ew. zrobić z tego lib), po czym dołączyć do programu. Różnica wtedy pomiędzy AVR i ARM sprowadzi się do tego, że dostęp do tak dołączonych danych będzie się odbywał np. tak:
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod

    lub
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
    .
  • #17 14465082
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Wrzuciłem w Googla frazę "__memx", to najpierw zwrócił zupełnie nie na temat o MEMS, i przez filtrowanie udało się dotrzeć do strony np. takiej:
    Link

    z której niewiele zrozumiałem, gdyż (tu się przyznam) nie rozumiem jeszcze do końca tego języka, uczę się go dopiero kilka miesięcy.
    Oczywiście nie ma problemu, żeby użyć zupełnie innego rozwiązania i ciągi liczb zapisać i skompilować w oddzielnym pliku (jeszcze tego nie robiłem), PGM Space nie jest jedynym rozwiązaniem, to wiem, ale jedynym, które w miarę rozumiem.

    Już pisałem, co chcę zrobić: Chcę odtwarzać próbki dźwiękowe za pomocą DAC mikrokontrolera. Będą to proste przebiegi okresowe. Jedna tablica, to jeden przebieg, a chcę tych przebiegów mieć więcej, niż tylko 7.

    Rozważam zaopatrzenie się w jakąś książkę o programowaniu w C.
  • #18 14465413
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    mas24 napisał:
    Rozważam zaopatrzenie się w jakąś książkę o programowaniu w C.

    Rozważ zmianę architektury, jak masz możliwość to nie brnij w AVR. Kwiatków takie jak te w tym poście spotkasz jeszcze więcej, to jest naprawdę udręka i strata czasu. Takie protezy jak wspomniane __memx mają więcej wad jak zalet, sam próbowałem to stosować i nic z tego nie wyszło. Łatwo tylko można powiedzieć żeby to stosować. __memx nie współpracuje z avr-libc więc i tak będziesz musiał potem dużo kombinować żeby je razem pogodzić.
  • #19 14465493
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    @michalko12 Oczywiście, że __memx współpracuje z AVR-libc - masz tam funkcje _far, które przyjmują tego typu wskaźniki. Jak z każdą rzeczą trzeba się jej po prostu nauczyć. Oczywiście, użycie kompilatora, w którym wskaźniki są po prostu 32-bitowe nieco to upraszcza, niemniej dzięki przestrzeniom adresowym jak pokazałem w poście #16 różnice są kosmetyczne. Bez obrazy, ale jeśli ktoś ma problem z przeczytaniem prostego manuala, w którym są przykłady użycia tej przestrzeni, to przesiadka na ARM będzie odpowiadała lotowi na księżyc.
  • #20 14465545
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    tmf napisał:
    Oczywiście, że __memx współpracuje z AVR-libc - masz tam funkcje _far, które przyjmują tego typu wskaźniki.


    http://www.nongnu.org/avr-libc/user-manual/group__avr__string.html
    http://www.nongnu.org/avr-libc/user-manual/group__avr__stdio.html
    http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html

    Która z tych podstawowych funkcji obsługuje przestrzeń __memx?
  • REKLAMA
  • #21 14465629
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    Funkcje te są w nagłówku <avr/pgmspace.h>:
    http://www.tuxgraphics.org/common/src2/articl...-user-manual/manual/group__avr__pgmspace.html

    Te, które mają sufiks _PF obsługują wskaźniki 24 i 32 bitowe do FLASH. Pozostałe funkcje - np. sprintf_P i inne z IO.h z _P obsługują __memx ale tylko w obrębie pierwszych 64 kB - nie ma potrzeby, aby obsługiwały >64 kB, gdyż linker zawsze umieszcza ich stałe we FLASH poniżej <64 kB, co przyśpiesza do nich dostęp. Raczej nikt nie stworzy łańcuchów formatujących do printf i scanf o długości >64 kB :) Funkcje malloc/calloc itd. nie obsługują __memx bo standardowe AVR adresują tylko do 64 kB RAM, tylko XMEGA adresuje do 16 MB i w tym przypadku obsługa takich wskaźników miałaby sens. Tego typu biblioteki alokacji pamięci są dla AVR dostępne, lecz zgodzisz się ze mną, że dla AVR rzadko zachodzi potrzeba dynamicznej alokacji pamięci w ilości >64 kB.
  • #22 14465698
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    Więc i tak trzeba używać dziwolongów typu pgm_read_xxxxx i do tego zgadywać czy można użyć zwykłą wersję czy już trzeba far. W takim wypadku to co napisałeś powyżej jest chyba nie do końca prawdą:
    tmf napisał:
    Różnica wtedy pomiędzy AVR i ARM sprowadzi się do tego, że dostęp do tak dołączonych danych będzie się odbywał np. tak:

    Kod C - [rozwiń]
    uint16_t __memx *dane; // AVR

    lub

    Kod C - [rozwiń]
    uint16_t *dane // ARM

  • #23 14465734
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    michalko12 napisał:
    Więc i tak trzeba używać dziwolongów typu pgm_read_xxxxx i do tego zgadywać czy można użyć zwykłą wersję czy już trzeba far. W takim wypadku to co napisałeś powyżej jest chyba nie do końca prawdą:
    tmf napisał:
    Różnica wtedy pomiędzy AVR i ARM sprowadzi się do tego, że dostęp do tak dołączonych danych będzie się odbywał np. tak:

    Kod C - [rozwiń]
    uint16_t __memx *dane; // AVR

    lub

    Kod C - [rozwiń]
    uint16_t *dane // ARM



    Dlaczego tak uważasz? Mam wrażenie, że nawet nie zaglądnąłeś do wskazanego przeze mnie nagłówka. To, że zawiera on też prototypy funkcji pgm_read_xxxx nie znaczy, że do czegokolwiek one obecnie są potrzebne. Ot są, dla zachowania wstecznej kompatybilności ze starymi programami. Zgadywać też nic nie trzeba - można używać zawsze wersji far - działa ona poprawnie także dla zwykłych wskaźników i przestrzeni __flash. Jeśli ktoś lubi pisać optymalnie, to zasada jest prosta - jeśli dane są w pierwszych 64 kB FLASH to można używać wersji bez far (_PF), co nieznaczie (jedna instrukcja) przyśpieszy dostęp. Natomiast dereferencja wskaźnika z przestrzeni __memx zawsze zwróci poprawnie odczytaną wartość, niezależnie gdzie się ona znajduje (FLASH/SRAM). Działa to bardzo dobrze, do tego stopnia, że programy, które piszę, które korzystają z named address spaces na AVR działają bez zmian na ARM - z tą tylko różnicą, że na ARM kasuję nazwy przestrzeni (oczywiście załatwia to preprocesor).
  • #24 14465865
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    tmf napisał:
    Mam wrażenie, że nawet nie zaglądnąłeś do wskazanego przeze mnie nagłówka.
    Bardzo dobrze znam ten nagłówek.
    tmf napisał:
    Działa to bardzo dobrze, do tego stopnia, że programy, które piszę, które korzystają z named address spaces na AVR działają bez zmian na ARM - z tą tylko różnicą, że na ARM kasuję nazwy przestrzeni (oczywiście załatwia to preprocesor).


    To w takim razie jak napisać na AVR funkcję, która jako argument ma wskaźnik i raz dostaje adres na dane w RAM, a innym razem we FLASH?
  • #25 14466089
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Ja już się w tym pogubiłem. Niestety nadal nie wiem, jak skorzystać z danych z FLASH większych od 32 czy 64 kB? W manualu piszą, ze ograniczenie jest do 64kB, Użytkownik tmf napisał, że do 32 kB i to by się zgadzało, choć znajomość tej różnicy wcale nie pomoże mi w napisaniu procedury czytania danych, o którą mi chodzi.
  • #26 14466225
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    michalko12 napisał:

    To w takim razie jak napisać na AVR funkcję, która jako argument ma wskaźnik i raz dostaje adres na dane w RAM, a innym razem we FLASH?


    Właśnie przy pomocy __memx - ta przestrzeń unifikuje FLASH i SRAM. Najstarszy bit wskaźnika określa czy jest to wskaźnik na SRAM, czy na FLASH. Oczywiście jego ustawienie i potem dereferencję do odpowiedniej przestrzeni załatwia kompilator. Są to niuanse, które dla programisty są bez znaczenia - po prostu robisz wskaźnik na przestrzeń __memx i zapominasz o architekturze AVR. Oczywiście funkcje biblioteczne póki co nie potrafią rozróżnić czy wskaźnik jest do RAM, czy do FLASH, więc musisz użyć osobnych przy dostępie do RAM, a osobnych przy dostępie do FLASH. Tu niestety AVR-libc nie nadąża za kompilatorem. Natomiast nie ma problemu z adresacją całej przestrzeni FLASH przy pomocy funkcji o sufiksie _PF.

    Dodano po 1 [minuty]:

    mas24 napisał:
    Ja już się w tym pogubiłem. Niestety nadal nie wiem, jak skorzystać z danych z FLASH większych od 32 czy 64 kB? W manualu piszą, ze ograniczenie jest do 64kB, Użytkownik tmf napisał, że do 32 kB i to by się zgadzało, choć znajomość tej różnicy wcale nie pomoże mi w napisaniu procedury czytania danych, o którą mi chodzi.


    Czytanie jest proste - po prostu robisz dereferencję wskaźnika należącego do przestrzeni __memx. Ograniczenie do 32 kB dotyczy tablic, lecz nie dotyczy wskaźników. Kwestia umieszczenia tylko danych w pamięci - IMHO jeśli to dane binarne to należy je po prostu zlinkować z programem.
  • #27 14466481
    mas24
    Poziom 16  
    Posty: 530
    Pomógł: 4
    Ocena: 15
    Uuu, a co to jest dereferancja i IMHO?
  • #29 14466607
    michalko12
    Specjalista - Mikrokontrolery
    Posty: 3394
    Pomógł: 462
    Ocena: 321
    tmf napisał:
    Właśnie przy pomocy __memx - ta przestrzeń unifikuje FLASH i SRAM. Najstarszy bit wskaźnika określa czy jest to wskaźnik na SRAM, czy na FLASH. Oczywiście jego ustawienie i potem dereferencję do odpowiedniej przestrzeni załatwia kompilator.

    Zasadę działania __memx znam. Ostatni raz próbę stosowania tej przestrzeni podejmowałem jakieś 2 lata temu, wtedy kompilator sypał błędami 4.7.x. Spróbowałem teraz na prostym kodzie i sytuacja wcale nie wygląda lepiej (kompilator 4.8.1). Jeśli tylko włączę jakąkolwiek optymalizację powyżej O0 to kompilator sypie wewnętrznymi błędami, a próba kompilacji na O0 przechodzi, ale wygenerowany kod to jakaś abstrakcja.
    Kod: C / C++
    Zaloguj się, aby zobaczyć kod
  • #30 14466673
    tmf
    VIP Zasłużony dla elektroda
    Posty: 14318
    Pomógł: 2090
    Ocena: 2206
    @michalko12 U mnie powyższy kod się kompiluje, oprócz oczywistego problemu:
    Kod: Bash
    Zaloguj się, aby zobaczyć kod


    Ale to kosmetyka, związana z typami. Kompilator avr-gcc 4.8.1 z pakietu Atmela 3.4.5.1522, optymalizacja -Os. Kiepskawo to działało czasem w gcc 4.7.x, w 4.8.x jest ok. Zresztą wszystkie przykłady do moich dwóch ostatnich książek wykorzystują przestrzenie adresowe i problemu z tym nie miałem.

Podsumowanie tematu

LABEL_AI_GENERATED
Użytkownik poszukiwał sposobu na przechowywanie dużych tablic danych w pamięci Flash mikrokontrolera, przekraczających limit 64 kB. W odpowiedzi na jego pytania, uczestnicy dyskusji wskazali na ograniczenia związane z typem danych i rozmiarem tablic w języku C, szczególnie w kontekście architektury AVR. Zasugerowano użycie przestrzeni adresowej __memx oraz 24-bitowych wskaźników do odczytu danych z pamięci Flash. Użytkownik zrozumiał, że rozmiar pojedynczej tablicy nie może przekraczać 32 kB, ale może złożyć większe obiekty z mniejszych tablic. W końcu udało mu się zaimplementować funkcję do odczytu tablic, co pozwoliło na wgranie większej liczby próbek dźwiękowych do jego projektu.
Podsumowanie AI na podstawie dyskusji. Może zawierać błędy.
REKLAMA