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

Język C - umieszczanie tablic pod wyznaczonym adresem

Aro_ 30 Wrz 2007 11:11 2804 12
REKLAMA
  • #1 4334204
    Aro_
    Poziom 15  
    Posty: 185
    Pomógł: 7
    Ocena: 1
    Witam, Nigdzie nie mogę znaleźc info czy jest taka możliwośc i jak to zrobic. Muszę jakoś umieścic tablicę zaczynającą się pod adresem 0x100, ewentualnie 0x200 lub 0x300. Generalnie chodzi o to, żeby potem jak najszybciej ją odczytac. Ma ona 256bajtów więc takie rozmieszczenie pozwoli operowac tylko na jednym rejsetrze Z (R30). Mowa o mikrokontrolerze Atmega8. Czy da się ustawiac z poziomu języka C adresy zmienneych i tablic w pamięci SRAM?
  • REKLAMA
  • #2 4334236
    BoskiDialer
    Poziom 34  
    Posty: 1530
    Pomógł: 353
    Ocena: 42
    Sam kiedyś próbowałem się dowiedzieć, czy się da.. umieszczenie 256 bajtów w pamięci programu pod stałym adresem nie udało się - w asemblerze wymuszanie adresów było względne do pliku obiektowego, po linkowaniu wszystko się przemieszczało... Rozwiązaniem może być ("może", bo nie próbowałem) dodanie sekcji i wskazanie miejsca w którym ma ona być umieszczona w wyniku. Jeśli zaś chodzi o pamięć ram - sytuacja będzie podobna, tyle że powinno się dać przesunąć stos, żeby zaczynał się 256 bajtów wcześniej lub ile tam, żeby tablica była pod równym adresem. Nie wiem czy z sekcjami też się tak da...
  • #3 4335016
    Dr_DEAD
    Poziom 28  
    Posty: 829
    Pomógł: 126
    Ocena: 3
    Generalnie pytanie jest trochę źle zfromułowane bo to nie C ani zaden inny język programowania odpowiedzialny jest za rozmieszczenie segmentów kodu w pamięci uP, tym zajmuje sie linker. Trzeba poszukać w opcjach linkera i utworzyć sobie nowy segment w żądanym obszarze pamięci.
  • REKLAMA
  • #4 4335465
    Aro_
    Poziom 15  
    Posty: 185
    Pomógł: 7
    Ocena: 1
    No więc jest to do zrobienia. Tylko jak się taką sekcje tworzy w WinAVR? Domyslam się, że to trzeba w makefile poustawiac, tylko czy w AVRach da się przesunąc stos? W asemblerze jak narazie nic konkretnego nie pisałem i całego programu w nim nie napiszę. Myślałem że w C można deklarowac zmienną z atrybutem adresu pamięci, tak jak to się robi z wyborem sekcji. A czy nie można zrobic sekcji która zanajduje się w środku pamięci? Np. od adresu 0x100 do 0x1ff. Ewentualnie podzielic całośc na trzy sekcje. No chyba że prościej można przeniesc stos.
  • #5 4337732
    Jdsoul
    Poziom 23  
    Posty: 501
    Pomógł: 47
    Ocena: 10
    Nie bardzo rozumiem co konkretnie chcesz uzyskać i skąd ta dbałość o tablicę w SRAM ?!? Czyżby chodziło o proces szybkiego przeliczania?

    C jest chyba po to żeby pisać kod sprzętowo niezależny ??

    Natomiast do nawigacji w pamięci warto posługiwać się wskaźnikami tzn. danymi zawierającymi adres wierzchołka tablicy etc. oraz przesunięciem względnym.

    Co ciekawe chroni cię to przed koniecznością zmiany miejsca położenia "tablicy" w momencie, gdy dane robocze puchną.

    Generalnie kompilator umieszcza dane w pamięci w kolejności zgodnej z ich deklaracją, więc jeśli tablica ma stały rozmiar to jeśli zostanie zadeklarowana na początku będzie pierwszym blokiem pamięci :)

    Co do opcji --code-loc, --data-loc etc. to sztucznie rozmieszczają kod i dane programu i jest to niezdrowe dla systemu jako całości :(

    Dodano po 2 [minuty]:

    A początek stosu jest zazwyczaj przez kompilator umieszczany na końcu zadeklarowanych danych automatycznie ?!?

    Więc czemu chcesz z tej opcji zrezygnować ?? poprostu zadeklaruj więcej zmiennych niż tego potrzebujesz :) i stos się sam przesunie :)
  • #6 4338388
    BoskiDialer
    Poziom 34  
    Posty: 1530
    Pomógł: 353
    Ocena: 42
    Stos sam się nie przesunie - w winavr początek stosu ustalany jest na ostatnią komórkę pamięci SRAM (wierzchołek stosu idzie ku niższym adresom) - w najgorszym przypadku przepełnienie stosu nadpisze dane ze zmiennych.

    Co do umieszczania tablicy pod konkretnym adresem - Jdsoul nie bądź taki do przodu bo cię z tyłu zabraknie ([C jest chyba po to żeby pisać kod sprzętowo niezależny]).. są naprawdę różne tricki, w taki sposób można w asemblerze (optymalizowane moduły w asmie - w czystym C nie ma wogóle sensu ingerować w proces rozmieszczania zmiennych) zaoszczędzić cenne cykle spowodowane dostępem do takiej tablicy - zamiast 2(ldi ZL/ZH)+2(subi ZL/sbci ZH) [+ld/st] można zainicjalizować ZH, a do ZL wpisywać przesunięcie względem początku tabeli - dodatkowo ZH(lub XH/YH w zależności od wskaźnika) można zainicjalizować raz w partii kodu, który wielokrotnie odwołuje się do tablicy.. Zysk cyli jest ogromny w niektórych zastosowaniach...
  • #7 4338592
    Aro_
    Poziom 15  
    Posty: 185
    Pomógł: 7
    Ocena: 1
    Uporałem się z tym w troche inny sposób. Po prostu zadeklarowałem jeszcze jedną tablicę o odpowiedniej objętości i teraz ta druga elegancko zaczyna sie od 0x300H.
    Tak, potrzebuje jak najszybszego przetwarzania, a Z+ czy podobne się nie nadają, bo sa 16bitowe. Jak narazie udało mi się uzyskac przetarzanie równe częstotliwosci kwarcu, ale na rejestrach. No prawie się udało, bo trzeba doliczych petlę - 2cykle. A jeśli chodzi o pamięc SRAM to jest to jedna dana na 6 cykli zegarowych wliczajac rjmp. Przy okazji jeszcze zobaczyłem że w ATmedze162 SRAM zaczyna sie od 0x100, więc tu nie będzie problemu.

    Pozdr. i dzieki za pomoc.

    P.S.
    A moze macie jakiś pomysł jak napiac program żeby jak najszybciej wysyłał na port kolejne dane z takiej tablicy (256bajtów) ? Mnie się udało dojsc do 6 cykli. Oraz jak napisac procedurę opóźniajacą żeby była jak najszybsza. Tzn podaje podczas pracy parametr od którego zależy czas opóźnienia. U mnie są to (4cykle)*(wartośc paramertu) - (1) . Dla paramertu 0 = 1023cykle. Czy da się to jeszcze jakoś odchudzic?
    mój kod to:
    petla:
    subi Rx, 1
    cpi Rx, 0
    brne petla
    

    Nie znam za bardzo asemblera i dlatego pytam.
  • #8 4338775
    BoskiDialer
    Poziom 34  
    Posty: 1530
    Pomógł: 353
    Ocena: 42
    Jeśli to ma być pobieranie kolejnych komórek z tablicy, to bez problemu inkrementacja wskaźnika przy odczycie będzie ok - odczyt i inkrementacja są wykonywane równolegle, więc nadal masz 2 cykle dostępu..
    	ldi XH,(adres_tablicy)>>8
    	ldi XL,(adres_tablicy)
    	ldi rcnt, 128	; liczba przebiegów przez 2
    .petla:
    	ld r0, X+
    	out PORT, r0	; wystawienie stanu
    	nop
    	dec rcnt		; zmniejszenie licznika liczby przebiegów.. wyżej jest nop, czyli można by zrobić odliczanie nawet na zmiennej 2 bajtowej
    	ld r0, X+
    	out PORT, r0	; wystawienie stanu
    	brne .petla
    

    W ten sposób aktualizację masz co 5 cyklii. Poniżej tego nie da się zejść: gdyby to były 4 cykle, to 1 jest dla wystawiania, 3 zostają na resztę. w 3 cyklach nie da się zmieścić 2 operacji odczytu, więc każdy blok 3 cyklowy musiał by zawierać jeden odczyt (nie da się przesunąć odczytu do sąsiedniego/nadmiarowego) - wtedy zostaje jeden cykl, a nie ma skoku trwającego 1 cykl.

    Co do procedury opóźniającej, która ma być jak najszybsza... hm.. co najmniej zastanawiające stwierdzenie.. chcesz odczekać np 100ms, ale procedura ma być szybka i ma się wykonać w 10ms?? Dość ciężko zrozumieć...
    Jakkolwiek jeśli chodziło Ci o pętlę do małych opóźnień, to można zrobić tak:
    	ldi Rx, liczba_cykli/3
    .petla_op:
    	subi Rx, 1
    	brne .petla_op

    subi pomimo zmniejszania wartości rejestru ustawia też flagi, które można wykorzystać przy skokach.. w tym przypadku pętla wykonuje się w 3*Rx-1, a wczytywanie trwa 1 cykl, czyli lącznie całość trwa dokładnie 3*Rx cykli - bardzo często korzystam z tej formułki.. czasem można zmienić skok typu "brne" na skok typu "brsh" - np jeśli na wartość zero z początku ma reagować przerwaniem pętli zamiast 256 iteracjami..

    A co mi się jeszcze wspomniało: subi można zastąpić przez dekrementację - instrukcja "dec" - można wtedy jako licznik korzystać ze wszystkich 32 rejestrów (subi ogranicza możliwość do rejestrów r16-r31). Jedyna uwaga, to "dec" nie zmienia wartości flagi C, a więc nie można go stosować wraz ze skokiem "brsh"
  • REKLAMA
  • #9 4338854
    piotrkopec
    Poziom 17  
    Posty: 189
    Pomógł: 24
    Ocena: 2
    z dokumentacji 'gcc'
    http://gcc.gnu.org/onlinedocs/gcc/Variable-Attributes.html
    short array[3] __attribute__ ((aligned));

    Cytat:
    Whenever you leave out the alignment factor in an aligned attribute specification, the compiler automatically sets the alignment for the declared variable or field to the largest alignment which is ever used for any data type on the target machine you are compiling for. Doing this can often make copy operations more efficient, because the compiler can use whatever instructions copy the biggest chunks of memory when performing copies to or from the variables or fields that you have aligned this way.

    czy
    short array[3] __attribute__ ((aligned(0x100)));

    +++++++++++++++++++++++++++++++++++++++++++++++++
    Niestety dla 'avr' maksymalny 'align' to '1'. Panowie od 'toolchain'a uznali, że to wystarczające dla wszystkich danych
    z 'avr.h':
    Cytat:
    /* No data type wants to be aligned rounder than this. */
    #define BIGGEST_ALIGNMENT 8

    w bitach.
    Wprowadzonych w błąd przepraszam.
    Przy okazji: największy 'align' dla 'gcc' z paczki dla 'fedora7_x86-64' to '0x8000' bajtów
  • #10 4341422
    Aro_
    Poziom 15  
    Posty: 185
    Pomógł: 7
    Ocena: 1
    Mnie wogóle 'aligned' nie działa. Ale ten problemik już rozwiązałem.
    petla:
    subi Rx, 1
    brne petla

    O własnie o to mi chodziło:) 3 cykle to już nieżle. A jeśli chodzi o wysyłanie do portów danych z tablicy to zapomniałem dodac że to musi byc petla nieskończona. Ale ten przykład dał mi troche do myslenia, zrobiłem petle nieskończona i wszystko pieknie chodzi.
    	asm volatile("movw r30, r10");
    //petla	
    	asm volatile(    \
    	"start:"           "\n\t"  //start
    	"ld r0, Z+"      "\n\t"
    	"out 0x15, r0"  "\n\t"
    	"nop"              "\n\t"
    	"dec r16"         "\n\t"
    	"ld r0, Z+"       "\n\t"
    	"out 0x15, r0"   "\n\t"  //wyśli na portc
    	"brne start"      "\n\t"
    	"movw r30, r10" "\n\t"  //skopiuj adres tablicy
    	"ld r0, Z+"        "\n\t"
    	"out 0x15, r0"   "\n\t"
    	"ldi r16, 125"   "\n\t"   //zainicjuj licznik petli
    	"ld r0, Z+"      "\n\t"
    	"nop"              "\n\t"
    	"out 0x15, r0"   "\n\t"
    	"rjmp start"     "\n\t"
    	);

    Jakby tego było mało, to są tam dwie instrukcje NOP dla dowolnego wykorzystania:)
    Wszystkim wielkie dzięki!
  • #11 4343686
    Jdsoul
    Poziom 23  
    Posty: 501
    Pomógł: 47
    Ocena: 10
    To jakiś filtr cyfrowy :) skąd taka dbałość o szybkość wystawiania tablicy i to jak rozumiem zmiennych :) skoro SRAM :)
  • #12 4345696
    BoskiDialer
    Poziom 34  
    Posty: 1530
    Pomógł: 353
    Ocena: 42
    Jdsoul napisał:
    To jakiś filtr cyfrowy :) skąd taka dbałość o szybkość wystawiania tablicy i to jak rozumiem zmiennych :) skoro SRAM :)

    Mi to nie wygląda na filtr, tylko jakiś prosty logiczny generator funkcyjny.. Co do wykorzystania pamięci SRAM - dostęp do tej pamięci to 2 cykle, dostęp do pamięci Flash to 3 cykle - oszczędność wydaje się być mała, ale jeśli zmiana wyjść ma następować co 5 lub co 6 cykli, to widać ogromną różnicę..
  • REKLAMA
  • #13 4347777
    Jdsoul
    Poziom 23  
    Posty: 501
    Pomógł: 47
    Ocena: 10
    Czy w tej sytuacji jest jeszcze sens stosować ten mikrokontroler ???

    Czy nie lepiej poprostu dać pamięć zewnętrzną SRAM równoległą podłączoną wprost do urządzenia portem danych 8 bitów, i sterować mikroprocesorem jej magistralą danych :) :) :) będzie 2 razy szybciej :) :) :) :) :) niż brać dane z wewnętrznej SRAM i wystawiać na port :) :). Zawsze zmianę tablicy można zrealizować zapisując dane z kolejnego portu kontrolera :) - szybciej to już chyba się nie da ;)

    Widziałem kiedyś taką zabawkę na EP gość zapisywał pamięć EPROM znakami ASCII, a potem podłączał licznik cykliczny na szynę adresową i przemiatał go zegarem o regulowanej szybkości.
    Port wyjściowy pamięci był na krótko podłączony do wejścia danych wyświetlacza LCD i efektem działania zabawki było wyświetlanie napisu o 1024 znakach w kółko bez konieczności użycia procesora. ;)

    Czy to przypadkiem nie chodzi o podobną funkcjonalność :) :) :) no może zamiast wyświelacza, przetwornik albo coś :)

Podsumowanie tematu

✨ Dyskusja dotyczy możliwości umieszczenia tablicy o rozmiarze 256 bajtów pod konkretnym adresem w pamięci SRAM mikrokontrolera Atmega8 (np. 0x100, 0x200, 0x300) w celu optymalizacji szybkości dostępu, szczególnie wykorzystując rejestr Z (R30). Wskazano, że rozmieszczeniem segmentów pamięci zajmuje się linker, a nie sam język C, dlatego konieczne jest tworzenie i przypisywanie sekcji pamięci w pliku linkera lub makefile. Przesunięcie stosu jest możliwe, ale w WinAVR stos jest domyślnie ustawiony na końcu pamięci SRAM i sam się nie przesuwa. W asemblerze można stosować triki optymalizacyjne, takie jak inicjalizacja rejestru Z do początku tablicy i operowanie na przesunięciach, co pozwala na oszczędność cykli zegarowych podczas odczytu danych. W C nie ma natywnej możliwości deklarowania zmiennych pod stałym adresem, ale można zadeklarować dodatkową tablicę, która zaczyna się od pożądanego adresu, jeśli linker jest odpowiednio skonfigurowany. Przykłady kodu asemblerowego pokazują, jak efektywnie odczytywać i wysyłać dane z tablicy na port, osiągając cykle rzędu 5-6 na operację. Dyskutowano także o ograniczeniach atrybutu aligned w GCC dla AVR oraz o alternatywnych rozwiązaniach sprzętowych, takich jak zewnętrzna pamięć SRAM podłączona bezpośrednio do portu danych, co może znacznie przyspieszyć operacje. Całość wskazuje na konieczność ingerencji w proces linkowania i stosowanie asemblerowych optymalizacji dla maksymalnej wydajności w mikrokontrolerach AVR.
Wygenerowane przez model językowy.
REKLAMA