Mikrokontrolery - Jak zacząć?

... czyli zbiór praktycznej wiedzy dot. mikrokontrolerów.

wtorek, 29 marca 2011

F_CPU – gdzie definiować?


Autor: tmf
Redakcja: Dondu


Problem braku definicji zegara taktującego mikrokontroler występuje bardzo często wśród początkujących, niezależnie jaki kompilator wykorzystują. Zapominanie o tak istotnej sprawie powoduje nieprawidłowe działanie mikrokontrolera po zaprogramowaniu go skompilowanym kodem.

Temat ten jest rozszerzeniem tematu poruszonego tutaj: Błędy kompilacji programu

Na przykładzie kompilatora języka C (GNU Compiler Collection) pokażę, jakie problemy spotka nieuważny programista mikrokontrolerów.

Rzućmy okiem na prosty program:
#include <util/delay.h>

int main()
{
   _delay_us(100);   //opóźnienie 100 mikrosekund
}

Próba jego kompilacji spowoduje wygenerowanie następującego ostrzeżenia:

c:/winavr-20100110/lib/gcc/../../avr/include/util/delay.h:85:3: warning: #warning "F_CPU not defined for <util/delay.h>"


Część początkujących programistów pewnie zignoruje to ostrzeżenie, co szybko spowoduje lawinę pytań:

Dlaczego moje opóźnienia nie są takie jakie być powinny?
Z prostej przyczyny, braku definicji częstotliwości zegara taktującego Twój mikrokontroler, co w tej sytuacji jest automatycznie rozwiązane następująco:

Przy braku definicji F_CPU, dołączenie nagłówka delay.h oprócz wygenerowania ostrzeżenia skutkuje zdefiniowaniem F_CPU o wartości 1MHz.

Jest to spowodowane definucją F_CPU w pliku delay.h:
/* prevent compiler error by supplying a default */
#ifndef F_CPU
# warning "F_CPU not defined for <util/delay.h>"
# define F_CPU 1000000UL
#endif

która zabezpiecza kompilator przez przyjęcie wartości domyślnej równej 1MHz. Innymi słowy jeżeli przed linkowaniem pliku delay.h F_CPU nie jest zdefiniowane, zostanie ono ustawione na 1MHz.


Czytający tego bloga już wiedzą, żeby każdy warning traktować jak błąd – dobrze napisany program po kompilacji nie powinien generować żadnych ostrzeżeń i co zrozumiałe – błędów :-)

Problemem w powyższym kodzie jest brak definicji F_CPU – symbolu zawierającego częstotliwość taktowania procesora. Bez tej informacji nie da się wyliczyć opóźnień, dlatego kompilator GCC przyjmuje wartość domyślą równą 1MHz.

W wielu przykładowych programach dostępnych w Internecie można spotkać się z sytuacją w której F_CPU jest definiowane bezpośrednio w kodzie programu – jest to ryzykowne rozwiązanie – dlaczego? Pokażę to na przykładzie.

Oczywiście w tak banalnym kodzie jak powyższy dodanie definicji F_CPU przed włączeniem nagłówka delay.h jest w zupełności poprawne i wystarczające:

#define F_CPU 8000000UL  //definiujemy F_CPU na 8MHz
#include <util/delay.h>

int main()
{
   _delay_us(100);   //opóźnienie 100 mikrosekund
}


Stwórzmy więc mniej trywialny przykład, ale za to częściej występujący w życiu. Potrzebujemy dwa pliki źródłowe zawierające kod programu. Nasz pierwszy plik o nazwie test111.c zawiera:

#define F_CPU 8000000UL  //definiujemy F_CPU na 8MHz
#include <util/delay.h>

void blink();

int main()
{
   blink();
   _delay_us(100);   //opóźnienie 100 mikrosekund
}

Jak widać w funkcji main wywoływana jest funkcja blink(), której prototyp dla uproszczenia został zadeklarowany w tym samym pliku (pamiętaj, że to tylko dla uproszczenia – normalnie należałoby stworzyć plik nagłówkowy zawierający prototyp funkcji). Potrzebny jest także drugi plik źródłowy, zawierający definicję funkcji blink()test112.c:

#include <avr/io.h>
#include <util/delay.h>

void blink()
{
   PORTA |= (1<<PA0);    //ustaw bit 0 portu A
   _delay_ms(100);       //opóźnienie 100 mikrosekund
   PORTA &= ~(1<<PA0);   //wyzeruj bit 0 portu A
}


Dla uproszczenia załóżmy, że oba pliki stanowią część projektu AVR Studio 4 – w ten sposób nie musimy się martwić o generowanie tych całych makefile i resztę. Po zapisaniu i kompilacji obu plików mamy upragnione:

Build succeeded with 0 Warnings...


Ale czy aby na pewno?
Zmieńmy cokolwiek w pliku test112.c (wystarczy dodać spację) i zapiszmy ponownie ten plik. Ponowna kompilacja i ... rozczarowanie:

Build succeeded with 1 Warnings...

Co do diaska jest grane? Rzut okiem na okno build i widzimy coś dziwnego:

c:/winavr-20100110/lib/gcc/../../avr/include/util/delay.h:85:3: warning: #warning "F_CPU not defined for "


Dziwna sprawa, w końcu w test111.c mamy F_CPU zdefiniowane, więc o co chodzi? Niby definicja F_CPU jest globalna, ale nie do końca, co jest związane ze sposobem kompilacji programu.

Każdy plik źródłowy kompilowany jest oddzielnie – stanowi odrębną jednostkę kompilacji. W sytuacji, w której zmieniamy zawartość tylko jednego pliku wchodzącego w skład złożonego projektu - tylko ten jeden plik jest rekompilowany, do linkowania całego programu zostaną wykorzystane pliki obiektowe otrzymane w wyniku wcześniejszych kompilacji (w naszym przykładzie plik test111.o).

Problem w tym, że F_CPU zostało zdefiniowane w pliku test111.c, ale nie w pliku test112.c – w efekcie jeśli najpierw nie jest kompilowany plik test111.c, to symbol F_CPU nie jest znany i pojawia się ostrzeżenie.


Czy warto się nim przejmować?
Zdecydowanie tak. W naszym przypadku powstanie aplikacja, która będzie miała chimeryczną definicję symbolu F_CPU. Funkcje opóźniające z pliku test111.c zostaną wyliczone dla wartości symbolu F_CPU wynoszącej 8MHz, podczas kompilacji test112.c symbol ten nie jest znany, więc zostanie przyjęta jego wartość domyślna wynosząca 1MHz – no i mamy cyrk.

Są też inne – mniej istotne powody, aby F_CPU nie definiować w kodzie programu.


Jak więc powyższy problem należy poprawnie rozwiązać? 
Ano należy przekazać definicję symbolu przy każdym wywołaniu gcc, stosując odpowiedni argument wywołania (opcja –D kompilatora):

-DF_CPU=8000000UL

Dzięki temu każdy plik źródłowy będzie skompilowany z właściwą wartością symbolu F_CPU. Opcję taką możemy umieścić w makefile (jeśli stosujemy własny), lub prościej, wybierając w AVR Studio 4 opcje Project/Configuration Options – w zakładce General wpisujemy w polu Frequency wybraną częstotliwość taktowania procesora:

Rys. 1 - AVR Studio 4

Możesz także definicję zegara zamieścić w Custom Options:

Rys. 2 - AVR Studio 4

Począwszy od AVR Studio 5 definiowanie F_CPU odbywa się podobnie jak na rys 2 w AVR Studio 4:

Rys. 3 - AVR Studio 5 i późniejsze Atmel Studio

Więcej informacji na temat Atmel Studio znajdziesz tutaj: Atmel Studio - zintegrowane IDE


Optymalizacja
Do prawidłowego działania funkcji _delay_ms() oraz _delay_us() musi być włączona optymalizacja!

W kodzie zostaw w celach archiwalnych
Z powodów archiwalnych warto jednak w kodzie zamieścić informację, dla jakiego zegara kod został przygotowany. Na ten temat znajdziesz informacje tutaj: Błędy kompilacji programu

Więcej o opóźnieniach znajdziesz w książce „Język C dla mikrokontrolerów AVR. Od podstaw do zaawansowanych aplikacji” – lub być może w przyszłości na tym blogu  :-)

Oceń artykuł.
Wasze opinie są dla nas ważne, gdyż pozwalają dopracować poszczególne artykuły.
Pozdrawiamy, Autorzy
Ten artykuł oceniam na:

18 komentarzy:

  1. Witaj,

    Mam pytanie, czy ta sytuacja z dwoma plikami test11x.c które są wykorzystywane w jednym projekcie, nie powinny być połączone za pomocą plików test112.h ?

    Chodzi mi o to, że podajesz przykład, pokazując problem, iż definicja zegara tylko w pliku głównym nie przenosi się do drugiego pliku, który jest jakby osobno kompilowany. Ale czy gdy prawidłowo przygotuję plik nagłówkowy test112.h i zainkluduję go w test111.c, to także zegar z test111.c nie przeniesie się do test112.c?

    Mam nadzieję, że opisałem pytanie zrozumiale ;)


    I drugie pytanie: Co to jest plik test111.o ?

    OdpowiedzUsuń
  2. Jeśli w pliku nagłówkowym zostanie umieszczona definicja F_CPU, a następnie ten plik zainkludujemy w każdym pliku źródłowym wykorzystującym F_CPU to oczywiście rozwiąże to problem - ale rozwiązanie jest o tyle ryzykowne, że gdzieś go zainkludować możemy zapomnieć. Co więcej, taka pomyłka nie musi wyjść przy każdej kompilacji, a więc jest to rozwiązanie prawie tak samo kiepskie jak definiowanie F_CPU w plikach źródłowych.
    Generalnie nie ma co wymyślać koła - najlepszym rozwiązaniem i jest umieszczenie tej definicji w makefile, co rozwiązuje problem raz na zawsze.
    Plik test111.o to plik obiektowy powstały w wyniku kompilacji test111.c - pliki takie są wykorzystywane przez linker. Tu po szczegóły odsyłam do mojej Książki, bo za dużo by o tym pisać trzeba było.

    OdpowiedzUsuń
  3. Dziękuję za odpowiedź, chciałem się tylko upewnić. Oczywiście stosuję metodę ustawiania zegara w opcjach projektu, jak na załączonym zrzucie ekranu.
    Pozdrawiam!

    OdpowiedzUsuń
  4. A jak jest w przypadku makefile+burn o mat? częstotliwość zegara taktującego ustawiamy w pliku makefile czy w burn o macie? wiem tylko tyle, że nieważne jaką wartość wpiszę przy tworzeniu mfile'a burn-o-mat również bez problemu zmienia częstotliwosć atmegi. Jest to bezpieczny manewr?

    OdpowiedzUsuń
  5. To są dwie różne sprawy. Wymieniony przez ciebie program zmienia ustawienia fusebitów określających źródła taktowania procesora, czyli zmienia rzeczywistą częstotliwość pracy procesora. Z kolei F_CPU informuje kompilator z jaką częstotliwością pracuje mikrokontroler, tak, aby kompilator mógł np. wyliczyć właściwe opóźnienia. Ale F_CPU nie wpływa na realną częstotliwość pracy procesora. Stąd F_CPU *musi* odpowiadać częstotliwości rzeczywistej, determinowanej wartością fusebitów i np. zastosowanym kwarcem/generatorem/etc.

    OdpowiedzUsuń
  6. Witam a czy w Code::Blocks w zakładce należy wpisać F_CPU czy razem z parametrem -D

    Pozdrawiam

    OdpowiedzUsuń
  7. Gdzie w ATMEL STUDIO 6 jest opcja dodania -DF_CPU xxxxxxx

    Czy jest to w Project->Other Properties->Toolchain->AVR/GNU C Compiler i tam w "All Options:" to dodać?

    Domyślnie mam:
    "-funsigned-char -funsigned-bitfields -O1 -fpack-struct -fshort-enums -g2 -Wall -c -std=gnu99 -MD -MP -MF "$(@:%.o=%.d)" -MT"$(@:%.o=%.d)" -MT"$(@:%.o=%.o)" -mmcu=atmega8a"

    OdpowiedzUsuń
  8. Patrz ostatni screen powyżej.

    OdpowiedzUsuń
  9. Czegoś nie rozumiem, a dla innych chyba wydaje się to oczywiste.

    Czy każdy uC ma określone wartości F_CPU jakie mogę mu ustawić, czy mogę tam wpisywać coś zupełnie dowolnego? Mogę coś nabroić wpisując jakąś złą wartość?

    Równie dobrze mogę F_CPU w ogóle nie ustawiać i wtedy będę miał taktowanie 1MHz, i dla tej wartości będę wszystko obliczał. Ale rozumiem, że jeśli sam ją ustawie, na większą, to będzie lepiej i dokładniej?

    OdpowiedzUsuń
  10. F_CPU to tylko symbol. informujący o taktowaniu MCU. Powinien mieć taką wartość jak aktualne taktowanie MCU. Jeśli będzie miał inną to wszelkie opóźnienia i inne rzeczy w programie od niego zależne będą wyliczane nieprawidłowo.

    OdpowiedzUsuń
  11. Zrobiłem tak jak w tym artykule, zadefiniowałem w ustawieniach atmegi128A, że F_CPU=8000000 jednak gdy odpalę symulator to koło procesor jest napisane że częstotliwość to 100000 Hz, jak sprawdzić czy to co ustawiłem jest ustawione?

    OdpowiedzUsuń
  12. Symulator nie korzysta z definicji F_CPU, tak samo jak nie korzysta z niej procesor. Częstotliwość taktowania w symulatorze musisz po prostu wpisać w to pole, lecz jej wartość jest bez znaczenia - wpływa ona tylko na wyświetlany czas symulacji, który możesz sobie sam policzyć na podstawie liczby taktów CPU.

    OdpowiedzUsuń
  13. Troszke moze nie na temat :-) Mam atmelstudio 6.1. W zakladce tool nie moge wybrab programatora, lista jest pusta (select programmer). Posiadam avrisp mk2. Gdzie dodaje sie programator? jak nie mam go dodanego to nie moge utworzyc pliku hex :-(

    OdpowiedzUsuń
  14. a moze po prostu atmel studio 6 nie obsluguje tego programatora?

    OdpowiedzUsuń
  15. Witam, chciał bym się zapytać a co jeśli zdefiniowanie w zakładce "symbol" nie pomaga ? Wrzuciłem zgodnie z opisem F_CP=11059200UL, atmel studio 7.0 i dalej mam dwa warrningi. Ponowna kompilacja usuwa je lecz każda następna zmiana w kodzie powoduje ich ponowne wystąpienie. Dodatkowo definicja #define F_CP 11059200UL też nic nie zmienia

    OdpowiedzUsuń
    Odpowiedzi
    1. Nie F_CP, tylko F_CPU :)
      czyli: F_CPU=11059200UL

      Usuń
    2. Ajj widać programowanie po 12 godzinach pracy nie jest dla mnie :) dzięki wielkie :)

      Usuń
    3. Tak bywa :D
      Powodzenia!

      Usuń

Działy
Działy dodatkowe
Inne
O blogu




Dzisiaj
--> za darmo!!! <--
1. USBasp
2. microBOARD M8


Napisz artykuł
--> i wygraj nagrodę. <--


Co nowego na blogu?
Śledź naszego Facebook-a



Co nowego na blogu?
Śledź nas na Google+

/* 20140911 Wyłączona prawa kolumna */
  • 00

    dni

  • 00

    godzin

  • :
  • 00

    minut

  • :
  • 00

    sekund

Nie czekaj do ostatniego dnia!
Jakość opisu projektu także jest istotna (pkt 9.2 regulaminu).

Sponsorzy:

Zapamiętaj ten artykuł w moim prywatnym spisie treści.