MCS-51
Opis rozszerzeń języka C w kompilatorze SDCC

Omówienie niektórych funkcji bibliotecznych

Poniżej przedstawiono opis tylko niektórych funkcji bibliotecznych występujących w kompilatorze SDCC, które w jakiś sposób różnią się od standardu ANSI C.

Funkcje getchar i putchar

Chociaż w pliku nagłówkowym stdio.h dostarczanym razem z kompilatorem SDCC znajdują się prototypy funkcji getchar i putchar to nigdzie nie zostały one zaimplementowane. Zatem przy ich wywołaniu pojawiają się błędy linkera postaci

?ASlink-Warning-Undefined Global '_putchar' referenced by module 'plik'

Podobne błędy wystepują, gdy wywoływana jest np. funkcja printf_tiny, która wykorzystuje funkcję putchar.

Brak implementacji tych funkcji jest świadomym posunięciem twórców kompilatora. W standardzie ANSI C funkcje putchar i getchar z założenie operują odpowiednio na standardowym wejściu i wyjściu. O ile z punktu widzenia komputera PC standardowym wejściem jest klawiatura, a wyjściem ekran monitora, to w systemach mikroprocesorowych nie jest to już takie jasne. Mianowicie standardowym wejściem jak i wyjściem może być port szeregowy. Ale również w systemach zaopatrzonych np. w wyświetlacz LCD standardowym wyjściem może być właśnie ten wyświetlacz. Zatem z punktu widzenia pisanego programu funkcja putchar, będąca podstawową funkcją przesyłania pojedynczych znaków, może operować na różnych wyjściach. Dlatego jej implementację pozostawiono użytkownikom.

Jeśli założymy, że funkcja putchar ma wysyłać znaki przez port szeregowy mikrokontrolera, to jej implementacja może wyglądać jak poniżej. Należy tylko pamiętać o zgodności jej implementacji z zamieszczonym w pliku nagłówkowym stdio.h prototypem, tzn. funkcja ta nie powinna zwracać żadnych wartości, a jej jedyny parametr wejściowy powinien być typu char.

void putchar(char znak)
{
  while(!TI)     // oczekiwanie na ustawienie bitu zakończenia..
    ;            // ..nadawania znaku informacji
  TI = 0;        // zerowanie bitu TI
  SBUF = znak;   // nadanie kolejnego znaku informacji
}

Jeśli natomiast za standardowe wyjście w oprogramowywanym systemie mikoprocesorowym uznać wyświetlacz LCD, to implementacja funkcji putchar może mieć postać jak poniżej. Impelentacja ta zakłada, że:

  • istnieje implementacja funkcji SpradzBitBF, która w pętli sprawdza bit zajętości sterownika wyświewtlacza, a jej wykonanie kończy się dopiero, gdy sterownik może przyjąć kolejną informację,
  • sterownik wyświetlacza widziany jest jako komórki zewnętrznej pamięci danych począwszy od adresu 0xF800,
  • sterowanie bitami RW oraz RS sterownika wyświetlacza odbywa się poprzez najmłodsze bity adresu.
// określenie adreu sterownika wyświetlacza
// patrz "Adresowanie układów we/wy"
volatile xdata at 0xF801 unsigned char LCD_ZNAK;

...

void putchar(char znak)
{
  // oczekiwanie na możliwość przesłania kolejnej informacji
  SprawdzBitBF();
  // przesłanie znaku do sterownika wyświetlacza LCD
  LCD_ZNAK = znak;
}

Poniżej zebrano nazwy funkcji, które wykorzystują w swojej implementacji funcje putchar oraz getchar. Funckja putchar wykorzystywana jest w implementacjach następujących funkcji:

  • printf,
  • printf_small,
  • printf_fast,
  • printf_fast_f,
  • printf_tiny,
  • vprintf,
  • puts,
  • gets.

Funckja getchar wykorzystywana jest w następujących funkcjach:

  • gets.

Funkcja printf

Funkcje formatujące, jak np. funkcja printf, ze względu na możliwość umieszczania w ciągu formatującym wielu różnych sekwencji formatujących i ich modyfikatorów zajmują duży obszar pamięci kodu. Prawdopodobnie implementacja tej funkcji w w bibliotekach kompilatora SDCC dla mikrokontrolerów MCS-51 wykorzystuje również dość duży obszar pamięci danych, gdyż w dokumentacji kompilatora znajduje się uwaga o konieczności użycia modelu large dla tworzonego programu, w którym jest wywoływana niniejsza funkcja.

Przypuszczalnie implementacja funkcji printf umożliwia stosowanie wszystkich znaków typu określonych w standardzie ANSI C oraz ich modyfikatorów, jednak nie jest to podane w dokumentacji kompilatora. Jedyna informacja dotyczy braku obsługi liczb typu float.

W praktyce część specyfikatorów typów używana jest częściej niż pozostałe, a niektóre ich modyfikatory prawie wcale, dlatego w kompilatorze SDCC istnieje kilka innych implementacji funkcji printf.

printf_small

Implementacja printf_small funkcji printf pozwala na realizację operacji sformatowanego wyjścia dla sekwencji znaków w łańcuchu formatującym przedstawionych w poniższej tablicy. W polu 'Postać wyjściowa' podano, w jakim zapisie formatowana jest liczba odpowiadająca danej sekwencji formatującej.

Sekwencja
formatująca
Typ argumentu
wejściowego
Postać wyjściowa
%d int dziesiętna
%ld long int dziesiętna
%hd char dziesiętna
%x int szesnastkowa
%lx long int szesnastkowa
%hx char szesnastkowa
%o int ósemkowa
%lo long int ósemkowa
%ho char ósemkowa
%c char znakowa
%s wskaźnikowy znakowa

Ze względu na założenie przenośności kodu na inne rodzaje mikrokontrolerów, niniejsza implementacja funkcji printf została zrealizowana w języku C a następnie skompilowana dla różnych mikrokontrolerów obsługiwanych przez kompilator SDCC. W związku z tym nie jest to raczej optymalna jej implementacja i może zajmować znaczny obszar pamięci kodu.

printf_fast

Specjalną implementacją funkcji printf dla mikrokontrolerów rodziny MCS-51 jest funkcja printf_fast. Implementacji tej funkcji dokonano ręcznie w asemblerze, dlatego można spodziewać się, że jest to ona w miarę optymalna.

Analizując kod źródłowy implementacji tej funkcji można dopatrzeć się nastepujących kodów formatujących przedstawionych w poniższej tabeli. Pola oznaczone nawiasami kwadratowymi [] są opcjonalne. Pole [szerokość] odnosi się do modyfikatora określającego minimalną liczbę znaków występującą w postaci wyjściowej po przeprowadzeniu formatowania.

Sekwencja
formatująca
Typ argumentu
wejściowego
Postać wyjściowa
%[[0]szerokość]d int dziesiętna
%[[0]szerokość]ld long int dziesiętna
%[[0]szerokość]hd char dziesiętna
%[[0]szerokość]u unsigned int dziesiętna
%[[0]szerokość]lu unsigned long int dziesiętna
%[[0]szerokość]hu unsigned char dziesiętna
%x int szesnastkowa
%lx long int szesnastkowa
%hx char szesnastkowa
%c char znakowa
%[szerokość]s wskaźnikowy znakowa

printf_fast_f

Implementacja funkcji printf w postaci funkcji printf_fast_f jest rozwinięciem funkcji printf_fast o sekwencję formatującą dla liczb typu float. W poniższej tabeli przedstawiono dostępne modyfikatory dla tej sekwencji.

Sekwencja
formatująca
Typ argumentu
wejściowego
Postać wyjściowa
%[szerokość][.precyzja]f float dziesiętna

Pole [szerokość] pozwala na podanie minimalnej liczby znaków przy konwersji do postaci ciągu znakowego, natomiast pole [.precyzja] pozwala określić ilość cyfr po przecinku branych pod uwagę przy konwersji. Domyślnie, przy braku tego pola, przyjmuje się precyzję do 6 cyfr po przecinku.

printf_tiny

Funkcja printf_tiny jest kolejną odmianą funkcji printf dostępną w kompilatorze SDCC. Podobnie jak dwie poprzednie zrealizowana jest w asemblerze, co zapewnia małą objętość kodu. Jest ona właściwie okrojoną wersją funkcji printf_fast, tj. nie umożliwia obsługi liczb typu long oraz modyfikatora szerokości. Dostępne w tej funkcji sekwencje formatujące przedstawiono w poniższej tabeli.

Sekwencja
formatująca
Typ argumentu
wejściowego
Postać wyjściowa
%d int dziesiętna
%u unsigned int dziesiętna
%x int szesnastkowa
%c char znakowa
%s wskaźnikowy znakowa