[ARM] Obs?uga peryferiów poprzez API (wskazniki do

Cześć. Mamy np uC STM32. Dostęp do portów np ustawienie jakiegoś wyprowadzenia możemy zrobić albo korzystając z darowanych przez producenta bibliotek albo poprzez odwołania do rejestrów, jak np GPIOB-

BRR=0x0100;

I właśnie chodzi mi o ten drugi sposób z użyciem wskaźnika. Czy dokonując zapisu GPIOB->BRR=.. operujemy na zmiennej wskaźnikowej? Inaczej mówiąc czy mamy zdefiniowaną zmienną *GPIOB? Nie mogę dojść do tego jak to działa. W plikach jest taka definicja struktury: typedef struct { __IO uint32_t CRL; __IO uint32_t CRH; __IO uint32_t IDR; __IO uint32_t ODR; __IO uint32_t BSRR; __IO uint32_t BRR; __IO uint32_t LCKR; } GPIO_TypeDef;

A potem coś takiego #define GPIOB ((GPIO_TypeDef *) GPIOB_BASE) //GPIO_BASE to liczba

Ale nie widze tutaj żadnej definicji zmiennej wskaźnikowej? Czyżby nie była ona potrzebna? To jak to działa?

Wiem że dla Was to proste, ale jak się trochę zakręciłem i nie wiem juz o co chodzi?

Reply to
slawek7
Loading thread data ...

W dniu 2012-05-26 07:56, slawek7 pisze:

tutaj: (GPIO_TypeDef *) W skrócie. GPIOB_BASE to adres bazowy (początkowy) rejestrów I/O portu B. Rzutujemy tu wskaźnik na do struktury po kolei poukładanych rejestrów I/O. Dzięki temu jedną strukturą GPIO_TypeDef możemy opisać tyle portów ile ma dany procesor znając tylko ich adresy bazowe. GPIOB jest więc wskaźnikiem do struktury.

Michał

Reply to
Michał Lankosz

Ale czy ten wskaźnik do struktury to fizycznie jest zmienna? Jeśli tak to gdzie jej definicja? Typedef nie definuje i nie rodzi zmiennej, tak samo jak #define, wiec gdzie się pojawia ta zmienna wskaźnikowa? Przecież wskaźnik to zmienna przechowująca adres innej zmiennej, w tym przypadku struktury, czy nie tak?

Reply to
slawek7

A kto powiedział, że wskaźnik musi być zmienną? W Twoim przykładze jest akurat stałą, która po rozwinięciu definicji GPIOB_BASE przez preprocesor zostanie efektywnie zastąpiona natychmiastową stałą liczbową (adres) zrzutowaną na wskaźnik odpowiedniego typu coby kompilator wiedział co pod tym adresem siedzi dzięki czemu można w kodzie mieć odnośniki do konkretnych pól struktury tam umieszczonej. Nie działa to nic inaczej jak definiowanie stałych liczbowych jako makr preprocesora a potem używanie ich np. do nadawania wartości zmiennym - tu też dopóki nie użyjesz, to nigdzie w kodzie ta wartość nie będzie przechowywana. W Twoim przykładzie jest tylko jedno istotne założenie tzn. że wskaźnik do struktury jest co do warotści równy jej fizycznemu adresowi czyli nie ma wirtualizacji pamięci (czytaj: brak OSa z prawdziwego zdarzenia).

Pozdr Portal

Reply to
Portal

Dnia Fri, 25 May 2012 22:56:32 -0700 (PDT), slawek7 napisał(a):

Zapis (costam *) liczba

mowi "skonwertuj 'liczba' na typ: wskaznik na 'costam'"

poniewaz wskaznik to adres liczbowy, czyli po prostu liczba, wiec poza nielicznymi wyjatkami zadna konwersja nie musi byc wykonywana.

Ale ... takie wyrazenie jest dalej traktowane jak wskaznik na obiekt typu "costam", wiec mozesz uzywac zwyczajnych dla wskaznikow konstrukcji, np GPIOB->pole //to jest element struktury

*GPIOB.pole //to samo co wyzej itd.

P.S. Uwaga na nawiasy, przy takich rozwinieciach przez preprocesor cuda moga sie zdarzyc, ktos moglby napisac

#define GPIOB (GPIO_TypeDef *) GPIOB_BASE

a potem ktos inny

#define GPIOB_BASE GPIOB_BASE_1 + 0x80 .... GPIOB->BRR = 5

I nieszczescie gotowe. Im wiecej nawiasow tym lepiej.

J.

Reply to
J.F.

W książce o ARMach AT91SAM7 znalazłem jeszcze coś takiego przy deklaracji struktury:

typedef struct s_TC { __IO uint32_t CONTROL_R; __IO uint32_t MODE_R; __IO uint32_t COUNTER_VALUE; __IO uint32_t INT_STATUS;

} S_TC, *PS_TC;

Potem #define TC0 ((PS_TC)0xFFFA0000)

liczbę rzutujemy na wskaźnik, ale dlaczego bez gwiazdki? Czy nie powinno być #define TC0 ((*PS_TC)0xFFFA0000) skoro typedef utworzył typ wskaźnikowy?

Reply to
slawek7

W dniu 2012-05-26 17:09, slawek7 pisze:

No włąśnie. Rzutujesz liczbę na wskażnik. TO samo robiłeś tu:

#define GPIOB ((GPIO_TypeDef *) GPIOB_BASE)

GPIOB to rzut liczby GPIOB na _wskaźnik_ do typu GPIO_TypeDef.

int a to liczba int * b to wskaźnik do liczby.

GPIO_TypeDef A; to strukturka GPIO_TypeDef * bla; to skaźnik do strukturki.

Wtedy TC0 byłby wskaźnikeim do wskaźnika do struktury typu S_TC.

pzdr bartekltg

Reply to
bartekltg

Tak jak piszesz (wskaźnik na wskaźnik) to by było gdyby zdefiniować: #define TC0 ((PS_TC *)0xFFFA0000) tymaczsem konstrukcja: #define TC0 ((*PS_TC)0xFFFA0000) zwyczajnie się nie skompiluje jeżeli spróbuje się użyć w źródle odniesienia do TC0.

Co do reszty zgoda - PS_TC już jest typem wskaźnikowym i gwiazdek mu nie potrzeba. Zamiennie można użyć: #define TC0 ((S_TC *)0xFFFA0000)

Pozdr Portal

Reply to
Portal

Aj, racja. Nie sprawdzałem dokłądnie kodu sławka, autokorekta zadziałała;)

pzdr bartekltg

Reply to
bartekltg

Zgadza się to co piszecie i zrozumiałem o co chodzi. Przecież to jest coś takiego (*(uint32_t*)0x40010C10)=0x0000000f; Powoduje to bezpośredni dostęp do rejestru i operację na porcie PB.

Natomiast nie rozumiem zapisów które pojawiają się dokumentacji. Adres jest 32 bitowy więc stąd zapewne pierwsze rzutowanie ale pojawia się też informacja że do rejestru można się dostać albo jako word, albo jako half-word, albo jako byte? O co tu chodzi? Jaki adres i jakie rzutowanie wtedy się robi i co jak poda się liczbę word 32 bitową zamiast wymaganą half-word 16 bitową?

Reply to
slawek7

Zgadza się to co piszecie i zrozumiałem o co chodzi. Przecież to jest coś takiego (*(uint32_t*)0x40010C10)=0x0000000f; Powoduje to bezpośredni dostęp do rejestru i operację na porcie PB.

Natomiast nie rozumiem zapisów które pojawiają się dokumentacji. Adres jest 32 bitowy więc stąd zapewne pierwsze rzutowanie ale pojawia się też informacja że do rejestru można się dostać albo jako word, albo jako half-word, albo jako byte? O co tu chodzi? Jaki adres i jakie rzutowanie wtedy się robi i co jak poda się liczbę word 32 bitową zamiast wymaganą half-word 16 bitową?

Reply to
slawek7

Chyba mylisz trochę postać adresu z typem danych siedzących pod tym adresem. Operacja: (*(uint32_t*)0x40010C10)=0x0000000f; oznacza tyle co zapisz wartość 0x0000000f pod adres 0x40010C10 traktując ją (wartość, nie adres) jako liczbę 32-bitową bez znaku.

Jeżeli zrobisz podobną operację, ale w postaci: (*(uint8_t*)0x40010C10)=0x0f; to pod ten sam adres zapiszesz tylko pojedynczy bajt, pozostawiając pozostałe trzy bajty 32-bitowego słowa zapisanego pod adresem 0x40010C10 bez zmian. Kwestia czy nadpisany zostanie najmniej czy najbardziej znaczący bajt słowa zależy od "endianności" systemu - najczęściej jest to little endian czyli 0x40010C10 wskazuje na najmniej znaczący bajt słowa,

0x40010C11 na kolejny i tak aż do 0x40010C13.

Przy zapisach 16-bitowych możesz analogicznie zapisać tylko pół rejestru adresując połówki jako uint16_t pod adresem 0x40010C10 oraz 0x40010C12. W większości RISCowych architektur dostęp do danych musi uwzględniać wyrównanie tzn. adresy dla 32-bitowych dostępów muszą być wielokrotnością czwórki, dla 16-bitowych wielokrotnością dwójki a bajty można czytać i pisać "zewsząd" (o ile dany fragment przestrzeni adresowej w ogóle uwzględnia możliwość takiego dostępu - w Twoim przykładzie jak widać rejestry peryferyjne uwzględniają taką możliwość).

Pozdr Portal

Reply to
Portal

Chyba rozumiem. Bo czy to znaczy że jeśli jakiś rejestr 32 bitowy ma możliwość zapisania go wartością 16 bitowa bo tak podaje dokumentacja to chcąc dokonać takiego zapisu liczbą 16 bitową używam rzutowania 16 bitowego w postaci (*(uint16_t*)0x40010C10)=0x1234; Natomiast jeśli rejestr musi byc zapisany tylko wartością 32 bitowa bo tak każe dokumentacja to u zywam (*(uint32_t*)0x40010C10)=0x12345678;

Ale mam wątpliwość, czy czasem to rzutowanie nie oznacza tylko arytmetyki wskaźników? Tzn za następny wskazywany obszar bęzie większy albo o 2 bajty albo o 4, jak w przypadku zwykłej arytmetyki wskaźników np uint16_t *ptr; // wskaźnik na liczbę 16 bitową teraz zwiększamy adres o jeden ptr++; czyli tak naprawdę wskaźnik skacze o dwa a nie o jeden adres?

Reply to
slawek7

Chyba rozumiem. Bo czy to znaczy że jeśli jakiś rejestr 32 bitowy ma możliwość zapisania go wartością 16 bitowa bo tak podaje dokumentacja to chcąc dokonać takiego zapisu liczbą 16 bitową używam rzutowania 16 bitowego w postaci (*(uint16_t*)0x40010C10)=0x1234; Natomiast jeśli rejestr musi byc zapisany tylko wartością 32 bitowa bo tak każe dokumentacja to u zywam (*(uint32_t*)0x40010C10)=0x12345678;

Ale mam wątpliwość, czy czasem to rzutowanie nie oznacza tylko arytmetyki wskaźników? Tzn za następny wskazywany obszar bęzie większy albo o 2 bajty albo o 4, jak w przypadku zwykłej arytmetyki wskaźników np uint16_t *ptr; // wskaźnik na liczbę 16 bitową teraz zwiększamy adres o jeden ptr++; czyli tak naprawdę wskaźnik skacze o dwa a nie o jeden adres?

Reply to
slawek7

Chyba rozumiem. Bo czy to znaczy że jeśli jakiś rejestr 32 bitowy ma możliwość zapisania go wartością 16 bitowa bo tak podaje dokumentacja to chcąc dokonać takiego zapisu liczbą 16 bitową używam rzutowania 16 bitowego w postaci (*(uint16_t*)0x40010C10)=0x1234; Natomiast jeśli rejestr musi byc zapisany tylko wartością 32 bitowa bo tak każe dokumentacja to u zywam (*(uint32_t*)0x40010C10)=0x12345678;

Ale mam wątpliwość, czy czasem to rzutowanie nie oznacza tylko arytmetyki wskaźników? Tzn za następny wskazywany obszar bęzie większy albo o 2 bajty albo o 4, jak w przypadku zwykłej arytmetyki wskaźników np uint16_t *ptr; // wskaźnik na liczbę 16 bitową teraz zwiększamy adres o jeden ptr++; czyli tak naprawdę wskaźnik skacze o dwa a nie o jeden adres?

Reply to
slawek7

Chyba rozumiem. Bo czy to znaczy że jeśli jakiś rejestr 32 bitowy ma możliwość zapisania go wartością 16 bitowa bo tak podaje dokumentacja to chcąc dokonać takiego zapisu liczbą 16 bitową używam rzutowania 16 bitowego w postaci (*(uint16_t*)0x40010C10)=0x1234; Natomiast jeśli rejestr musi byc zapisany tylko wartością 32 bitowa bo tak każe dokumentacja to u zywam (*(uint32_t*)0x40010C10)=0x12345678;

Ale mam wątpliwość, czy czasem to rzutowanie nie oznacza tylko arytmetyki wskaźników? Tzn za następny wskazywany obszar bęzie większy albo o 2 bajty albo o 4, jak w przypadku zwykłej arytmetyki wskaźników np uint16_t *ptr; // wskaźnik na liczbę 16 bitową teraz zwiększamy adres o jeden ptr++; czyli tak naprawdę wskaźnik skacze o dwa a nie o jeden adres?

Reply to
slawek7

Dnia Mon, 28 May 2012 11:13:59 -0700 (PDT), slawek7 napisał(a):

Czyli chyba nie rozumiesz

0x40010C10 to liczba. a moze i adres.

(uint32_t*)0x40010C10 liczba, zasadniczo ta sama, ale juz typu "wskaznik na cos"

*(uint32_t*)0x40010C10 obiekt wskazywany przez ten wskaznik. A poniewaz wskaznik mial wskazywac na uint32, to obiekt jest uint32. Albo inny, jesli tak ustalisz. (*(uint32_t*)0x40010C10)=0x0000000f; a tu masz wpisanie wartosci do obiektu.

Przy czym moze nastapic kolejna konwersja wyrazenia z prawej strony, na typ obiektu z lewej. Np

(*(uint8_t*)0x40010C10)=0x00001234; to zapisze jeden bajt, 34

(*(uint32_t*)0x40010C10)=0x9f; a to zapisze 0000009f, albo ffffff9f :-)

A to oczywiscie tez. Tylko trzeba umiejetnie wykorzystywac.

Tylko wiesz ze nie zapiszesz ((uint8_t*)0x40010C10)++)

ani ((uint32_t *) ptr)++;

Za to mozesz sprobowac

*((uint32_t *)ptr+1)=1 ;

A na koniec masz zadanie domowe (*((uint32_t **) &ptr))++;

J.

Reply to
J.F.

Skacze o jeden sizeof(typ_wskazywany) - do tego jest arytmetyka wskaźników.

Natomiast to czy użyjesz wskaźnika na uint32_t czy uint8_t wpłynie m.i. na to, że kompilator wtedy zadba o odpowiednie opakowanie niewyrównanego dostępu, czy wręcz (zależnie od zestawu instrukcji) z:

*((uint8_t *)0x12)=1; zamieni na odpowiednią sekwencję load-modify-store tak, żeby nie popsuć pozostałych 3 bajtów.
Reply to
Michoo

Niebezpieczne założenie - nie każdy kompilator to robi i nieostrożność programisty może skończyć się wywalaniem błedu wyrównania przez procesor. Najlepiej w przypadkach kiedy to programista dostarcza adres bezpośrednio w kodzie źródłowym, żeby jednak on sam zadbał również o odpowiednie wyrównanie.

Pozdr Portal

Reply to
Portal

Źle podejrzewasz. Przede wszystkim typ użytego wskaźnika determinuje typ dostępu w kodzie wynikowym tzn. fizycznie na szynie jest wystawiony rozkaz odczytu lub zapisu tylko pojedynczego bajtu lub 16-bitowego słowa zamiast pełnego 32-bitowego. Czyli jeżeli zapisujesz uint8_t np. pod adres 0x00000000, to bajty pod adresami 0x00000001, 0x00000002 i

0x00000003 pozostaną nienaruszone, podczas gdy zrobienie tego samego jako operacji na uint32_t pod adres 0x00000000 zapisze wszystkie cztery bajty słowa.

Pozdr Portal

Reply to
Portal

ElectronDepot website is not affiliated with any of the manufacturers or service providers discussed here. All logos and trade names are the property of their respective owners.