RC-51-5.pdf

(239 KB) Pobierz
Microsoft Word - RC-51-5.doc
http://www.easy-soft.tsnet.pl
Język C a komunikacja mikrokontrolera z otoczeniem.
Rzadko zdarza się aby mikrokontroler, którego zamierzamy użyć, oferował nam
wszystkie potrzebne układy peryferyjne. Jeżeli jednak już tak jest, to albo ma również
szereg innych, zupełnie niepotrzebnych, albo też przeraża jego cena. Dobrym
rozwiązaniem tak przedstawionego problemu jest użycie taniego mikrokontrolera i
dołączenie do niego w możliwie jak najtańszy sposób możliwie jak najtańszych układów
peryferyjnych. Tu jednak pojawia się pewien problem – dołączenie układów
zewnętrznych...
„Problem” ten został rozwiązany różnie przez różnych autorów języków
programowania. Absolutnie najprostszy w użyciu pod tym względem jest Bascom, który
oferuje nam biblioteki gotowych rozwiązań. Inaczej jest w przypadku C. Tu musimy o
wszystko zadbać samemu. No może o prawie wszystko, ponieważ obsługę sprzętowego
portu UART (RS232) większość kompilatorów oferuje jako standard. Jest to związane ze
specyfikacją ANSI dla standardu języka C, która mówi, że instrukcje printf, getchar,
putchar wysyłają znak (pobierają z) do standardowego urządzenia wyjściowego
(wejściowego). W przypadku komputera PC jest to monitor (i klawiatura). Trudno jednak
wyobrazić sobie prosty sterownik zbudowany z użyciem mikrokontrolera podłączony do
monitora. Oczywiście jest to możliwe, ale raczej nieopłacalne. W związku z tym
standardowym urządzeniem wejścia / wyjścia dla mikrokontrolera jest port UART. Od
niego też zaczniemy opis implementacji interfejsów.
UART, czyli RS232 – funkcje stdio.h
Niestety – w związku ze specyfiką podawanych w tym opisie informacji, będą one
dotyczyć pakietu Raisonance. Instrukcje printf, getchar, putchar będą zapewne działać
identycznie w programach napisanych przy pomocy kompilatorów pochodzących od
różnych producentów, ale nastawy dotyczące szybkości przesyłanych danych mogą być
wykonywane zupełnie inaczej i jeśli ktoś używa na przykład Keil, to musi sięgnąć do
dokumentacji tego pakietu.
W asynchroniczny port UART spełniający wymogi standardu transmisji RS232
wyposażony jest prawie każdy mikrokontroler. Oczywiście podłączenie UART do linii
transmisyjnej wymaga układu dopasowującego zbudowanego z zastosowaniem
elementów dyskretnych lub układów scalonych typu MAX232. Standard transmisji
wymaga bowiem aby poziomy logiczne napięć były równe:
5 .. 12V dla logicznej jedynki
-12 .. –5V dla logicznego zera.
Wymaga to najczęściej zasilania układów dopasowujących z symetrycznego źródła
napięcia, czyli najczęściej budowy przetwornicy. Przytoczone tu zagadnienia techniczne
dotyczą jednak samego układu mikrokontrolera oraz jego otoczenia – zazwyczaj nie
wpływają na kształt programu napisanego w języku C.
Podobnie jak w przypadku rozwiązań innych problemów, również i tutaj mamy co
najmniej dwie drogi poprawnego wykorzystania układu UART. Możemy na przykład
skorzystać z systemu przerwań oferowanego przez mikrokontroler. Wówczas UART
pracuje niejako w tle i dopiero skompletowanie słowa danych spowoduje, że zgłoszone
zostanie przerwanie – w jego obsłudze możemy opróżnić bufor, odebrać dane itp. Inaczej
jest (i według mnie jest to prostsze rozwiązanie), jeśli możemy oczekiwać na odbiór bajtu
w pętli tworzonej przez kod instrukcji getchar(). Wówczas proste przypisanie znak=
getchar() rozwiązuje problem odbioru bajtu. Jedna i druga metoda jest prawidłowa,
trzeba wybrać tę, która będzie bardziej adekwatna do naszych potrzeb.
Zacznijmy opis wykorzystania UART od moim zdaniem prostszej metody, tej która
nie wykorzystuje przerwań. Funkcje obsługujące wysyłanie i odbiór znaków zdefiniowane
są w bibliotece stdio.h. Aby ich użyć, musimy tę bibliotekę dołączyć dyrektywą #include.
UART wykorzystuje Timer 1 do ustalenia szybkości transmisji. Timer pracuje w trybie 2,
czyli jako ośmiobitowy z automatycznym odświeżaniem zawartości przy przepełnieniu.
Szybkość pracy UART można więc ustalić wpływając na wartość bajtu ładowanego do
J.Bogusz, Programy obsługi UART, I 2 C, SPI oraz 1-W, Strona 1 z 15
305448525.005.png
http://www.easy-soft.tsnet.pl
rejestru TH1. Poniżej przytaczam wzór zaczerpnięty z instrukcji programowania
mikrokontrolera 80C51 pozwalający wyliczyć wartość TH1 odpowiednią do danej
szybkości transmisji:
TH1 = 256 – (k x częstotliwość kwarcu / (384 x szybkość transmisji))
„k” to mnożnik prędkości transmisji – dla bitu SMOD równego 0, wynosi on 1, natomiast
dla SMOD ustawionego na 1, wynosi on 2. Spróbujmy wyliczyć zawartość TH1 dla kwarcu
11,0592 MHz, bitu SMOD = 0 oraz prędkości transmisji 9600 bodów:
TH1 = 256 – (1 x 11059200 / (384 x 9600)) = 253 (0xFD)
Jak teraz ustalić wartość rejestru TH1 dla procedur transmisji danych tak, aby funkcje
zawarte w stdio.h mogły poprawnie ją odczytywać i interpretować?
I znowu można to zrobić na kilka sposobów. Można napisać samodzielnie
procedurę inicjalizacji. Można również w parametrach kompilatora ustawić potrzebną
wartość. Można też zmienić ją przy pomocy dyrektywy defj umożliwiającej modyfikację
stałych systemowych. Jeśli zdecydowaliśmy się na zmianę ustawienia stałej systemowej
bez pisania własnej procedury inicjalizacji, zdecydowanie nie polecam jej wykonywania
korzystając z okienka opcji. Może się zdarzyć, że wartość ustawiona dla jednego
programu nie będzie właściwą dla innego, natomiast system zapamięta ją jako domyślną.
Jeśli zapomnimy o okienku opcji, nowy program po skompilowaniu nie będzie działał
prawidłowo. Będziemy szukać błędu, który jest tym trudniejszy do lokalizacji, że nie
znajduje się w kodzie źródłowym programu. Tak więc pozornie wszystko będzie w
porządku, ale wartość TH1 ustawiona w opcjach kompilatora skutecznie uniemożliwi nam
prawidłową pracę programu.
Zdjęcie 1. Korzystając z okienka Options możemy ustawić wartość bajtu wpisywanego do TH1. Nie jest to
jednak metoda przeze mnie polecana, chociaż jest najszybsza.
Ten sam efekt, co przez zmianę opcji kompilatora, można osiągnąć używając dyrektywy
defj . Postać jej użycia jest następująca:
J.Bogusz, Programy obsługi UART, I 2 C, SPI oraz 1-W, Strona 2 z 15
305448525.006.png
http://www.easy-soft.tsnet.pl
#pragma DEFJ(TIM1_INIT=wartość)
Czyli dla przykładu:
#pragma DEFJ(TIM1_INIT=0xFD)
Zdecydowanie polecam właśnie to polecenie, jeśli nie chcesz pisać procedur do
inicjalizacji UART. Uzbrojeni już w tę zupełnie podstawową wiedzę, możemy przejść do
przykładów wykorzystania UART.
Listing 1 podaje przykład programu wysyłającego znaki do urządzenia
podłączonego do UART. Na samym początku dołączane są zbiory biblioteczne niezbędne
do prawidłowego jego funkcjonowania oraz inicjowana jest wartość TH1 przy pomocy
defj. Znaki (bajty) wysyłane są przez funkcję putchar(). Domyślnie bit SMOD ma wartość
„0”. Wydaje mi się, że rzut oka wystarczy, aby przesyłanie danych przez UART nie
stanowiło już żadnego problemu.
/*****************************************
wysyłanie kodów ASCII przez UART
mikrokontroler AT89S8252, kwarc 11,0592 MHz
Raisonance RC-51
******************************************/
#include <reg52.h> //definicje rejestrów
#include <stdio.h> //dołączenie funkcji wejścia – wyjścia
#pragma DEFJ(TIM1_INIT=0xFD) //ustalenieszybkości transmisji
//funkcja realizuje opóźnienie około k*1ms dla rezonatora f=11.0592 MHz
void delay (unsigned int k)
{
unsigned int i,j;
for ( j = 0; j < k; j++)
for (i = 0; i <= 84;) i++;
}
//program główny, znaki o kodach od 0x20 do 0xFF wysyłane są kolejno przez UART
//co około 300 milisekund
void main(void)
{
char i;
for (i = 0x20; i <= 0xFF; i++) //pętla wykonywana, gdy i<=255
{
putchar(i) ; //przesłanie bajtu
delay (300); //opóźnienie 0,3 sekundy
}
}
Listing 1. Tak można wysyłać znaki używając funkcji putchar(). Wyróżniono funkcje związane z UART.
Podobnie jest z odbiorem. Jednak zanim pokażę przykład odbioru danych, kilka
słów wyjaśnienia. Typowo, do odbioru danych ze standardowego urządzenia wejścia –
wyjścia (w naszym przypadku jest to UART) służy funkcja getchar(). Jednak tkwi w niej
pewna „pułapka”. Zgodnie ze specyfikacją ANSI funkcja ta odsyła odebrany bajt. Można
powiedzieć, że występuje efekt echa. I mimo iż jest to zgodne ze specyfikacją standardu,
to najczęściej zupełnie niepotrzebne. Są oczywiście sytuacje, w których jest to bardzo
wygodna metoda kontroli tego, co zostało wysłane, jednak w mojej praktyce zdarzało się
to raczej rzadko. Częściej potrzebowałem po prostu odebrać bajt echo odsyłane do
urządzenia nadającego, bardzo w tym przeszkadzało. Z moich doświadczeń wynika, że
wśród firm piszących kompilatory przyjęło się pewien standard: obok funkcji getchar()
definiowana jest funkcja getkey(), która odbiera znak i nie wysyła echa. Podobnie jest w
J.Bogusz, Programy obsługi UART, I 2 C, SPI oraz 1-W, Strona 3 z 15
305448525.007.png 305448525.008.png
http://www.easy-soft.tsnet.pl
przypadku kompilatora RC-51. Również i tu wśród funkcji biblioteki stdio.h znajdziemy
_getkey(). Uzbrojony w tę wiedzę możesz sam zdecydować, czego chcesz używać. Listing
2 to fragment kodu napisanego dla programatora sterowanego przez port szeregowy.
#pragma DEFJ(TIM1_INIT=0xFE) //timer 1 ustala prędkość transmisji
//tutaj 19200 bodów (SMOD będzie równy „1”)
#pragma SMALL //wybór modelu pamięci programu
#include <stdio.h> //funkcje wejścia - wyjścia
#include <reg51.h> //definicje rejestrów
//program główny
void main ()
{
char temp, temp1, cmd1, cmd2, cmd3;
set_reset(); //wystawienie sygnału reset dla programowanego uK
//faza reset jest zależna od stanu linii resettype
//1=AT90, 0=AT89
PCON |= 0x80 ; //ustawienie bitu SMOD na „1”
EA |= 0x81; //włączenie przerwań i zezwolenie na int0
clr_reset(); //zwolnienie reset z uwagami jak dla set_reset
while (1)
{
while ((temp = _getkey() ) == 0x1B);
switch (temp)
{
case 'T': //'T' typ urządzenia
device = _getkey() ;
put_ret();
break;
case 'S': //'S' rodzaj podłączonego programatora
putchar('A') ; //wysłanie napisu „AVR ISP”
putchar('V') ;
putchar('R') ;
putchar(' ') ;
putchar('I') ;
putchar('S') ;
putchar('P') ;
break;
case 'V': //'V' wersja programu
putchar('1') ; //wysłanie napisu „10”
putchar('0') ;
break;
case 'v': //'v' wersja urządzenia
putchar('1') ; //wysłanie napisu „10”
putchar('0') ;
break;
..............
Listing 2. Fragment programu do obsługi programatora szeregowego. Wyróżniono funkcje związane
z ustawieniem i transmisją UART.
Wyrażenie
while ((temp = _getkey() ) == 0x1B)
J.Bogusz, Programy obsługi UART, I 2 C, SPI oraz 1-W, Strona 4 z 15
305448525.001.png 305448525.002.png
http://www.easy-soft.tsnet.pl
to pętla, w której wykonywane są dwie operacje. Jedna z nich, to przypisanie zmiennej
temp wartości bajtu odebranego przez UART. Druga, to porównanie tego bajtu z kodem
ESC (0x1B) i zakończenia działania pętli, jeśli odebrany znak będzie różny od ESC.
Zwróćmy uwagę na różnice pomiędzy operacją przypisania (zmienna = wartość) a
porównania (zmienna == wartość).
Opisane przykłady transmisji są bardzo proste. Polegają tylko na dołączeniu
biblioteki stdio.h, ustawieniu właściwej prędkości transmisji i wywołaniu odpowiedniej dla
potrzeb funkcji. Troszkę inaczej (i trudniej) jest w przypadku wykorzystania przerwania.
Listing 3 pokazuje przykład programu do obsługi UART wykorzystującego jego
przerwanie.
/*****************************************
Obsługa transmisji szeregowej przez UART
z wykorzystaniem przerwań.
******************************************/
#include <reg51.h>
#define ROZM_BUFORA_TX 32
#define ROZM_BUFORA_RX 32
#define OSCYLATOR 11059200
unsigned char buf_wysylki[ROZM_BUFORA_TX];
unsigned char buf_odbioru[ROZM_BUFORA_RX];
unsigned char do_wysylki, wyslano;
unsigned char wysylka_wylaczona;
unsigned char do_odbioru, odebrano;
//funkcja obsługująca przerwanie UART; using 2 oznacza, że używany jest
//bank rejestrów R0..R7 numer 2
void UART_irq (void) interrupt 4 using 2
{
if (RI != 0)
//fragment wykonywany, gdy do_odbioru znak
{
RI = 0; //zerowanie flagi "do_odbioru"
if ((do_odbioru+1) != odebrano) buf_odbioru[do_odbioru++] = SBUF;
//pobranie znaku do bufora odbioru, gdy jego
} //rozmiar jest wystarczający
if (TI != 0) //fragment wykonywany, gdy znak do wysłania
{
TI = 0; //zerowanie flagi "do wysyłki"
if (do_wysylki != wyslano)
SBUF = buf_wysylki[wyslano++]; //jeśli indeksy ilości znaków
//i ilości znaków do wysłania
else wysylka_wylaczona = 1; //są różne, pobierz i wyślij
//znak
}
}
//obliczenie rozmiaru wolnego miejsca w buforze odbioru
unsigned char rozm_bufora_odbioru (void)
{
return (do_odbioru - odebrano);
}
//obliczenie ilości znaków pozostających do wysyłki
unsigned char rozm_bufora_wysylki (void)
{
return (do_wysylki - wyslano);
}
//ustawienie prędkości transmisji, inicjacja Timer'a 1
void UART_baudrate (unsigned char baudrate)
{
J.Bogusz, Programy obsługi UART, I 2 C, SPI oraz 1-W, Strona 5 z 15
305448525.003.png 305448525.004.png
Zgłoś jeśli naruszono regulamin