czwartek, 10 maja 2018

Mikrokontrolery PIC - kilka przydatnych makr oraz jak po swojemu zbudować dostęp do dowolnego rejestru MCU.

Pomimo , że rejestry w mikrokontrolerach PIC są bardzo dobrze opisane przyjaznymi strukturami co znacznie ułatwia programowanie ich , to czasami jednak zachodzi potrzeba zrobienia czegoś po swojemu .....

Poniżej kilka prostych (prostackich) definicji i makr , które mogą przydać się  w praktyce.


/*Pozycja bitów w tym przypadku bity RB4(=4) i RB5(=5) na porcie PORTB*/
#define RB4 _PORTB_RB4_POSITION /*to samo co _RB4 w PIC24*/
#define RB5 _PORTB_RB5_POSITION /*to samo co _RB5 w PIC24*/

/*ustaw pojedyńczy pin w stan niski na porcie PORTB , nr_pinu = RB4 etc..*/
#define PinSetLow(nr_pinu) (PORTB &= ~(1<<(nr_pinu)))/*to samo co PORTBbits.RB4 = 0*/
/*ustaw pojedyńczy pin w stan wysoki na porcie PORTB , nr_pinu = RB4 etc..*/
#define PinSetHigh(nr_pinu) (PORTB |= (1<<(nr_pinu)))/*to samo co PORTBbits.RB4 = 1*/

/*ustaw kierunek (OUT/IN) pinu na porcie PORTB , nr_pinu = RB4 etc..*/
#define PinDirectionSetOut(nr_pinu) (TRISB &= ~(1<<(nr_pinu)))/*to samo co TRISBbits.RB4 = 0*/
#define PinDirectionSetIn(nr_pinu) (TRISB |= (1<<(nr_pinu)))/*to samo co TRISBbits.RB4 = 1*/

/*testuj stan pinu na porcie PORTB , nr_pinu = RB4 etc..*/
#define TestBit(nr_pinu) (PORTB & (1<<(nr_pinu)))

Przykład użycia w kodzie :
Sprawdzamy stan bitu RB4 jeśli 1 to wykonaj A jeśli 0 to wykonaj B
(TestBit(RB4))? (wykonaj A) : (wykonaj B) ;

Poniżej kawałek fajnego kodu który może się przydać w praktyce !!!!! :
Połączenie Unii, Struktury i pól bitowych dające dostęp m.in do pojedyńczych bitów dowolnej zmiennej.
Możemy zmieniać wartość całej zmiennej lub pojedyncze bity tej zmiennej :

typedef union {
uint8_t byte;
struct {
uint8_t bit0 :1;
uint8_t bit1 :1;
uint8_t bit2 :1;
uint8_t bit3 :1;
uint8_t bit4 :1;
uint8_t bit5 :1;
uint8_t bit6 :1;
uint8_t bit7 :1;
};
} byte_t;

byte_t foo;                  /*powołujemy nową unię*/
uint8_t test1, test2, test3 ;/*zmienne pomocnicze*/
test1 = foo.byte ;           /*test1 = 0b00000000, odczyt całej zmiennej*/
foo.bit0 = 1 ;               /*zmiana na bicie nr 0*/
foo.bit1 = 1 ;               /*zmiana na bicie nr 1*/
test2 = foo.byte ;           /*test2 = 0b00000011 ,odczyt całej zmiennej*/
foo.byte = 0b00000000 ;      /*zerujemy zmienną po całości*/
test3 = foo.byte ;           /*test3 = 0b00000000 ,odczyt całej zmiennej*/

Do czego takie połączenie może się przydać ?? ano np wyobraźmy sobie, że ubieramy w ten kod jakiś rejestr mikrokontrolera i nagle robi się przyjazny dostęp do rejestru i poszczególnych jego bitów. W przypadku PIC-ów nie ma zasadniczo takiej potrzeby bo tu wszystko jest przyjazne :).

Rozważmy jednak taki przypadek. Mamy w nowo poznanym MCU rejestr do którego chcemy po swojemu zrobić dostęp, bo nie satysfakcjonuje nas to co proponuje producent MCU. W praktyce spotkamy się nagminnie z takimi dylematami przy okazji macania Cortexów wszelakiej maści, w PIC-ach tego typu dylematy nie występują.

Weżmy zatem wyrwany z manuala Cortex M0+ pierwszy lepszy rejestr, oto jego postać :



Widzimy , że rejestr jest 32 bitowy. Jest podzielony na 8 kawałków po 4 bity każdy.Na 4 bitach zapisujemy wartości, które reprezentują jakieś funkcjonalności opisane poniżej struktury rejestru.Jakie to funkcjonalności w tym momencie nas to nie interesuje. Chcemy w wygodny dla nas sposób mieć możliwość ustawiania wartości w rejestrze. Poniżej prosta droga jak do tego dotrzeć :)

Podstawową rzeczą oprócz znajomości struktury samego rejestru jest informacja o adresie początku naszego rejestru. Na szczęście z opasłych manuali Cortexowych te informację wygrzebiemy. Przyjmijmy zatem że adres bazowy grupy rejestrów GPIO ma wartość :

#define GPIO_BASE   (0x40088000) /**< GPIO base address  */

nasz rozważany rejestr ma offset 0x008 względem adresu bazowego. Stąd wydumamy ,że adres początku naszego konkretnego rejestru to wartość (GPIO_BASE + 0x008)
Stwórzmy zatem unię bazującą na budowie naszego rejestru.

typedef union {
   uint32_t w;
   struct {
    uint32_t MODE8:4;
    uint32_t MODE9:4;
    uint32_t MODE10:4;
    uint32_t MODE11:4;
    uint32_t MODE12:4;
    uint32_t MODE13:4;
    uint32_t MODE14:4;
    uint32_t MODE15:4;
  };
 } __GPIO_PA_MODEHbits_t;

A co to jest za stwór np Mode8:4 ?? No to już wiemy to nic innego jak tzw pole bitowe i pod tym hasłem proszę sobie doczytać w jakimś tutorialu lub książce z językiem C.

No dobra mamy zadeklarowany typ __GPIO_PA_MODEHbits_t, reprezentujący wewnętrzną budowę naszego rejestru ale jak teraz przyporządkować mu adres początku naszego rejestru ?? i tu zaczyna się potęga języka C i wskaźników.

Robimy to tak czary mary hokus pokus :

#define GPIO_PA_MODEHbits   ((__GPIO_PA_MODEHbits_t *) (GPIO_BASE + 0x008)) 

Konstrukcja taka , że może głowa zaboleć :)
Powołujemy sobie poprzez definicję nową nazwę GPIO_PA_MODEHbits do , której przyporządkowujemy makro rzutujące adres na typ wskaźnikowy (łomatko) :)

Może dla rozjaśnienia zastosowanej konwencji :

int *wsk = (int*)&w gdzie &w to wyłuskanie adresu zmiennej a w naszym przypadku podstawiamy konkretny adres.

Ktoś mądry kiedyś stwierdził , że aby łatwiej rozkminić zapisy wskaźnikowe trzeba je czytać od tyłu. Spróbujmy zatem to od tyłu zinterpretować jakoś, ale nie jestem na 100 % pewny czy nie pitolę głupot :). Jeśli ktoś mądrzejszy przez przypadek tutaj by zajrzał to bardzo proszę o korektę moich dywagacji :) Interpretacja odtylcowa :

Adres (GPIO_BASE + 0x008) jest zawartością wskaźnika * o typie __GPIO_PA_MODEHbits_t

Wiem , że to może być nadal słabo zrozumiałe i na pewno tak jest, trzeba w tym przypadku bardziej doszlifować wiedzę o wskaźnikach .

Teraz jeszcze kosmetyka czyli definicje funkcjonalności rejestru dla przykładu dwie definicje z 16 dostępnych w rejestrze.

#define INPUT      0x00000001 /**< Mode INPUT for GPIO_Px_MODEH */
#define PUSHPULL   0x00000004 /**< Mode PUSHPULL for GPIO_Px_MODEH */

I teraz najfajniejsze czyli jak dostajemy się i ustawiamy funkcjonalności w rejestrze za pomocą naszej konstrukcji "budowlanej". W przykładzie poniżej odwołujemy się do pierwszych czterech bitów oznaczonych w rejestrze MCU jako MODE8 i ustawiamy te cztery bity tak aby uzyskać funkcjonalność oznaczoną jako PUSHPULL:

GPIO_PA_MODEHbits->MODE8 = PUSHPULL /*jak dla mnie rewelacja, wygoda i przejrzystość*/ |

znaczek -> to operator wskaźnika na element struktur.

Jeśli ktoś się uprze i zamiast znaczka -> chce używać . (kropki) to proszę bardzo : (*GPIO_PA_MODEHbits).MODE8 = PUSHPULL

Dla porównania przyjrzyjmy się jak tę samą czynność proponuje nam zrobić producent Cortexa M0+ czyli standardowy Cortexowy potworek :

GPIO->P[0].MODEH = GPIO->P[0].MODEH & ~_GPIO_P_MODEH_MODE8_MASK | 
GPIO_P_MODEH_MODE8_PUSHPULL ;

Panowie Producenci MCU z rdzeniem ARM-a weźcie się walnijcie porządnie w głowę.
Czytelność kodu na poziomie dialektu kantońskiego.

Pokazałem , że nawet taki amator jak ja potrafi zrobić to czytelniej niż producent dowolnego Cortexa ze sztabem mądrych programistów na czele.

A jak to wygląda w świecie PIC-ów Microchipa :
TRISAbits.RA8 = 0; /*rewelacja w standardzie od samego początku, nie ma potrzeby poprawiać producenta*/
 
i za to m.in kocham ekosystem PIC Microchipa.
 



Pozdrawiam
picmajster.blog@gmail.com




Wycinek z Wiki tak ku pamięci.

Struktury jako pola unii

Elementami unii mogą być struktury, co czasem jest przydatne:
 
union liczba {
    struct {
        unsigned int a :8;
        unsigned int b :8;
        unsigned int c :8;
        unsigned int d :8;
    } rozbicie_na_bajty;

    struct {
        unsigned int a :16;
        unsigned int b :16;
    } rozbicie_na_slowa;

    unsigned int liczba;
};





Brak komentarzy:

Prześlij komentarz