18 marca 2017

Pozbywanie się bibliotek

Pierwszy programik powstał szybko i pokazał, że biblioteki mają plusy ale też i minusy. Czas spróbować pozbyć się bibliotek i napisać wersję działającą bezpośrednio na rejestrach.

Programy można pisać na bardzo wiele sposobów, ja wykorzystam tzw. refactoring, czyli zacznę od działającego kodu i będę go zmieniał ciągle sprawdzając czy nadal działa.
Jako program początkowy wykorzystam napisany wcześniej przykład z migającą diodą. W sumie nie ma różnicy, od której wersji zaczniemy - StdPeriph, czy Cube. Wybrałem tą pierwszą, ponieważ lepiej znam bibliotekę.
Na początek można usunąć kod z folderu Utilities, który został dodany aby obsługiwać płytkę Nucleo. Zastąpiłem więc wywołania funkcji STM_EVAL_x ich kodem. Nadal wykorzystujemy StdPeriph, ale projekt można trochę ograniczyć. Program wygląda następująco:

#include "stm32f10x.h"
int main(void)
{
volatile uint32_t dly;

GPIO_InitTypeDef  GPIO_InitStructure;

/* Enable the GPIO Clock */
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);

/* Configure the GPIO pin */
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);

while (1) {
GPIOA->BSRR = GPIO_Pin_5;
for (dly = 0; dly < 1000000; dly++)
;
GPIOA->BRR = GPIO_Pin_5;
for (dly = 0; dly < 1000000; dly++)
;
}

}

Dioda jest podłączona do pinu A5, poprzednio było to zdefiniowane jako stała co jest dobrym rozwiązaniem, ale chwilowo niech będą to "na sztywno" wpisane odwołania. Czasem trzeba kod trochę popsuć zanim się go poprawi.
Od razu widać, że zapalanie i gaszenie diody działa na rejestrach. Ponieważ miało nie być StdPeriph, trzeba jeszcze zastąpić funkcje RCC_APB2PeriphClockCmd oraz GPIO_Init ich treścią. Pierwsza funkcja jest bardzo prosta, więc jej rozwinięcie nie stanowi problemu. Niestety GPIO_Init już taka prosta nie jest. Użyłem więc pewnej sztuczki - za pomocą debuggera sprawdziłem co robi program i wpisałem wartość która jest ustawiania w odpowiednim rejestrze. Na razie jest w tym trochę magii, ale później wrócimy do tego i wyjaśnimy jak na prawdę program działa.
Obecnie wygląda on następująco:

#include "stm32f10x.h"
int main(void)
{
volatile uint32_t dly;

/* Enable the GPIO Clock */
RCC->APB2ENR |= 0x00000004;

/* Configure the GPIO pin */
GPIOA->CRL = 0x44344444;

while (1) {
GPIOA->BSRR = 1 << 5;
for (dly = 0; dly < 1000000; dly++)
;
GPIOA->BRR = 1 << 5;
for (dly = 0; dly < 1000000; dly++)
;
}

}

Niestety w tym momencie nie możemy po prostu usunąć folderu z biblioteką StdPeriph. Okazuje się, że coś jeszcze jej używa. Aby wyjaśnić co to takiego, musimy zajrzeć do pliku startup_stm32f10x_md.S. Plik ten zawiera kod w asemblerze uruchamiany przed wywołaniem funkcji main.

Plik ten przeanalizujemy później, teraz interesuje nas fragment:

/* Call the clock system intitialization function.*/
    bl  SystemInit
/* Call static constructors */
    bl __libc_init_array
/* Call the application's entry point.*/
    bl main

Instrukcja bl to wywołanie funkcji. Jak widać najpierw wywoływana jest funkcja SystemInit, następnie __libc_init_array, a na koniec napisana przez nas funkcja main.
SystemInit ustawia źródło taktowania zegara naszego mikrokontrolera. Jej treść znajdziemy w pliku system_stm32f10x.c, który został dodany podczas tworzenia projektu. Jeśli do niej zajrzymy, znajdziemy wywołania biblioteki.
Okazuje się, że możemy z wywołania SystemInit zrezygnować. Wtedy nasz procesor będzie działał na domyślnym zegarze o częstotliwości 8MHz. Czas sprawdzić jak to działa.
Usuwamy linijkę z wywołaniem SystemInit i uruchamiamy program.
Teraz dioda miga, chociaż znacznie wolniej. Możemy zmierzyć częstotliwość migania, wynosi ona 0.33 Hz - czyli tyle samo co w przypadku HAL Cube. Wiemy więc jak pozbyć się StdPeriph, a jednocześnie wyjaśniliśmy dlaczego pierwsze programy działały z różną prędkością gdy wykorzystywaliśmy różne biblioteki.
Teraz wreszcie możemy pozbyć się bibliotek. Usuwamy plik system_stm32f10x.c i cały folder StdPeriph_Driver. Przy próbie kompilacji zobaczymy błąd, z pliku stm32f10x.h usuwamy więc niepotrzebny już fragment:

#ifdef USE_STDPERIPH_DRIVER
  #include "stm32f10x_conf.h"

#endif

Zmiast usuwać ten kod, możemy też pozbyć się deklaracji stałej USE_STDPERIPH_DRIVER o ile oczywiście lubimy grzebać w ustawieniach Eclipse. Później utworzymy projekt od podstaw - więc w tej chwili to bez znaczenia którą opcję wybierzemy.
Nasz projekt jest teraz znacznie mniejszy, dzięki temu łatwiej zorientować się co jest nam potrzebne, a co tylko przeszkadzało.


Krótko podsumowując zawartość naszego projektu:
  • src/main.c - to nasz program napisany bez użycia bibliotek
  • src/syscalls.c - wygenerowane przez środowisko funkcje pomocnicze dla bibliteki C. Pozbędziemy się ich później
  • startup/startup_stm32f10x_md.S - plik startowy w asemblerze oraz definicja wektora przerwań
  • CMSIS - definicje rejestrów oraz funkcje pomocnicze. Do nich też wrócimy
  • LinkerScript.ld - skrypt linkera, niezbędny do kompilacji programu
Wiemy już mniej więcej co jest niezbędne do napisania prostego programu. Następnym razem utworzymy więc projekt od podstaw, pomijając zupełnie bibliotekę StdPeriph.


Brak komentarzy:

Prześlij komentarz