[Mikropascal] i procki AVR

Tutaj umieszczamy tematy związane z językami programowania niepasującymi do innych działów.
Regulamin forum
Temat prosimy poprzedzić nazwą języka umieszczonego w nawiasach kwadratowych np. [Pascal].
Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

[Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 14:45

Moje eksperymenty i testy z pakietem oprogramowania do tworzenia programów dla mikrokontrolerów AVR w języku PASCAL. W sumie ten pakiet narzędziowy ma pewne fajne cechy, ale i w paru miejscach mnie zasmucił.
Zabawę w tworzenie programów dla mikrokontrolerów AVR w oparciu o język programowania Pascal należy oczywiście zacząć od zainstalowania właściwego oprogramowania. Można je bezpłatnie pobrać ze strony producenta: http://www.mikroe.com. Instalacja nie wymaga żadnych dziwnych czynności, toteż każdy może sobie zainstalować pakiet bez problemów.
mp0.png

Mikropascal AVR pierwszy projekt
Do uruchomienia programu do tworzenia aplikacji na mikrokontrolery AVR należy z menu Start wybrać: Mikroelektronika → mikroPascal PRO for AVR → mikroPascal PRO for AVR. Spowoduje to uruchomienie środowiska IDE do tworzenia oprogramowania dla mikrokontrolerów AVR. Po uruchomieniu można poprzez dostępny przycisk do tworzenia nowego projektu,
upascal-p0-01.png
wybór w programie opcji: Project → New Prokect lub zastosowaniu skrótu klawiszowego Shift+Ctrl+N przejść do utworzenia nowego projektu.
upascal-p0-02.png
Tworzenie projektu zaczyna się od następującej planszy:
upascal-p0-03.png
na której należy kliknąć na „Next >” w celu sprecyzowania szczegółów.
upascal-p0-04.png
W polu Project Name należy wpisać nazwę tworzonego projektu (przykładowo prog0), w polu Project folder wpisać lokalizację na wszystkie pliki projektowe (nazwę kartoteki wraz ze ścieżką dostępu). Można posiłkować się przyciskiem Browse w celu wskazania kartoteki.
upascal-p0-05.png
W dalszej kolejności należy określić docelowy mikrokontroler, na jaki tworzone jest oprogramowanie. Realizowane jest to poprzez kliknięcie „na trójkącik w celu wysunięcia całej szuflady”.
upascal-p0-06.png
Z dostępnych pozycji należy wybrać symbol docelowego mikrokontrolera. Kolejny szczegół dotyczy częstotliwości sygnału taktującego (w typowym zastosowaniu: częstotliwości rezonatora kwarcowego przyłączonego do odpowiednich wyprowadzeń mikrokontrolera).
upascal-p0-07.png
W większości przypadków stosowany jest rezonator o częstotliwości 7.3728MHz (ta częstotliwość pozwala uzyskać każdą znormalizowaną prędkość transmisji szeregowej, gdzie UART jest taktowany systemowym sygnałem zegarowym). Po określeniu wszystkich danych należy kliknąć na przycisk „Next >”. Powoduje to przejście do kolejnej planszy,
upascal-p0-08.png
gdzie istnieje możliwość dołączenia do projektu już istniejących składników. Przy tworzeniu pierwszego projektu, takich składników nie ma. Kliknięcie na przycisk „Next >” pozwala przejść do dalszego etapu tworzenia projektu. Ewentualne „zapomnienie” dodania elementu do projektu nie jest problemem i zawsze możliwa jest korekta zawartości projektu.
upascal-p0-09.png
Kolejna faza uszczegułowiania informacji dotyczy przyłączanych bibliotek. Do kolejnego etapu następuje przejście po kliknięciu na przycisk „Next >”.
upascal-p0-10.png
Zakończenie tworzenia projektu następuje po kliknięciu na przycisk „Finish”. Końcowym rezultatem jest utworzenie pliku opisującego projekt (w tym konkretnym przypadku jest to prog0.mppav) oraz startowej postaci pliku zawierającego instrukcje programu (w tym konkretnym przypadku jest to prog0.mpas).
upascal-p0-11.png
Teraz pozostało wypełnić właściwą treścią zaproponowany szablon. Biorąc pod uwagę środowisko pracy mikrokontrolera (pokazane na poniższej ilustracji), gdzie do mikrokontrolera przyłączonych jest osiem LED'ów, przykładowy program jest następujący (należy zauważyć, że włączenie diody LED do świecenia następuje przy wysterowaniu odpowiedniego wyjścia portu B do stanu logicznego zera):
upascal-p0-12.png

Kod: Zaznacz cały

program prog0 ;

var
  PortData                  : byte ;

begin
  DDRB := 0xFF ;
  PORTB := 0 ;
  PortData := 0 ;
  repeat
    PORTB := PortData XOR 0xFF ;
    inc ( PortData ) ;
    Delay_ms ( 1000 ) ;
  until false ;
end.

Kompilacja i generowanie wersji binarnej następuje po kliknięciu na odpowiedni przycisk, wybranie właściwego polecenia z menu: Build → Build lub poprzez użycie skrótu klawiszowego Ctrl + F9 (wszystkie metody są równorzędne).
upascal-p0-13.png
W tak prostym programie nie ma nawet gdzie się pomylić, toteż jego kompilacja przebiegła bez problemów. Informują o tym komunikaty w oknie Messages.
upascal-p0-14.png
W przypadku gdyby były błędy kompilacji, odpowiednie komunikaty również są wyświetlane w tym oknie, przykładowo:
upascal-p0-15.png
Do modyfikacji szczegółów dotyczących projektu należy kliknąć na zakładkę „Project Settings” z lewej strony ekranu.
upascal-p0-16.png
Kliknięcie na tą zakładkę powoduje wysunięcie okienek, gdzie możliwa jest modyfikacja typu mikrokontrolera oraz częstotliwości sygnału taktującego. Z drugiej strony ekranu znajdują się zakładki pozwalające na modyfikacje składników projektu. Kliknięcie na zakładkę „Project Manager” powoduje wysunięcie odpowiedniego okienka, gdzie dostępne są przyciski pozwalające dodać lub usunąć odpowiedni składnik.
upascal-p0-17.png
Przydatną funkcjonalnością jest możliwość przeglądania w asemblerze wyniku kompilacji.
upascal-p0-18.png
Przycisk „Deasemblacji modułu” pokazuje wygenerowany kod w języku asembler bieżącego modułu, z kolei „Deasemblacja całości” pozwala obejrzeć cały kod programu a języku asembler. W wydrukach tych są odnośniki do tekstu źródłowego programu co pozwala na analizę wygenerowanego kodu na poziomie pojedynczej instrukcji w języku pascal. Powyższe deasemblacje są dostępne jedynie w przypadku poprawnej kompilacji całości.
Przykładowo wracając do tekstu pierwszego programu w przypadku wariantu (we fragmencie):

begin(...)
repeat
PORTB := not PortData ;
(* PORTB := PortData xor 0xFF ;*)
inc ( PortData ) ;
(...)


wynik kompilacji jest następujący:

;prog0.mpas,10 :: repeat
L__main1:
;prog0.mpas,11 :: PORTB := not PortData ;
LDS R17, _PortData+0
MOV R16, R17
COM R16
OUT PORTB+0, R16

;prog0.mpas,13 :: inc ( PortData ) ;
MOV R16, R17
SUBI R16, 255
STS _PortData+0, R16


w sensie optymalności, to można napisać lepiej, ale ... może być. Natomiast w wariancie (we fragmencie):

begin
(...)
repeat
(* PORTB := not PortData ;*)
PORTB := PortData xor 0xFF ;
inc ( PortData ) ;
(...)


daje

;prog0.mpas,10 :: repeat
L__main1:
;prog0.mpas,12 :: PORTB := PortData xor 0xFF ;
LDS R17, _PortData+0
MOV R16, R17
LDI R27, 255
EOR R16, R27
OUT PORTB+0, R16

;prog0.mpas,13 :: inc ( PortData ) ;
MOV R16, R17
SUBI R16, 255
STS _PortData+0, R16


sposób realizacji instrukcji pascalowej jest odmienny (choć końcowy wynik jest identyczny).
Przy okazji łatwo zauważyć sposób rozwiązania instrukcji oczekiwania na upłynięcie czasu. W prezentowanym programie (we fragmencie):

repeat
(* PORTB := not PortData ;*)
PORTB := PortData xor 0xFF ;
inc ( PortData ) ;
Delay_ms ( 1000 ) ;
until false ;


wygenerowany kod jest następujący:

;prog0.mpas,14 :: Delay_ms ( 1000 ) ;
LDI R18, 38
LDI R17, 103
LDI R16, 246
L__main6:
DEC R16
BRNE L__main6
DEC R17
BRNE L__main6
DEC R18
BRNE L__main6

NOP
NOP
;prog0.mpas,15 :: until false ;


załadowana jest do rejestrów R18:R17:R16 (R18 jest najbardziej znaczącą częścią liczby) trzybajtowa jakaś liczba stała i dalej w pętli jest dekrementowana aż do osiągnięcia stanu zerowego. Zmieniając w specyfikacji projektowej częstotliwość generatora taktującego (zakładka Project Settings z lewej strony ekranu) przykładowo na 8 MHz, dokonując ponownej kompilacji oraz deasemblacji można zauważyć, że zmieniła się wartość stałej (nowa jest większa). Prowadzi to do wniosku, że kompilator na podstawie zadeklarowanej częstotliwości sygnału zegarowego oblicza liczbę koniecznych cykli pętli i rozwija instrukcję Delay_ms na ciąg asemblerowych instrukcji dekrementacji i skoków warunkowych. Biorąc pod uwagę możliwość obsługi przerwań, to rzeczywisty czas jaki upływa w wyniku realizacji instrukcji Delay_ms jest większy i właściwie jest trudny do oszacowania, gdyż nie można przewidzieć ile i jakich przerwań zostanie obsłużonych w ciągu mijającego interwału czasu. Z pewnością błąd będzie ułamkiem procenta, więc nie jest to powód do większych zmartwień.
Po wygenerowaniu kodu wynikowego dla procesora pozostaje jeszcze umieszczenie go w pamięci flash mikrokontrolera. Oczywiście w środowisku mikropascala jest taka możliwość,
upascal-p0-19.png
ale nie została ona przetestowana. Kliknięcie na pokazany przycisk uruchamia odpowiednie oprogramowanie, które próbuje zlokalizować jakieś urządzenie programujące przyłączone do USB. Ja używam do programowania zestawu STK500 firmy Atmel, który jest obsługiwany poprzez port szeregowy. Eksperymenty w mikropascalem nie są wystarczającym powodem inwestycji w wymagany programator. W sumie istotne jest uzyskanie kodu programu w formacie Intel-hex i ten efekt jest uzyskiwany. Mając plik z kodem można zastosować dowolne rozwiązanie, przykładowo uruchomić program AVRStudio i poprzez STK500 umieścić kod programu w pamięci flash mikrokontrolera. Po zaprogramowaniu mikrokontrolera efekt jego działania jest zgodny z oczekiwaniem.

Załącznik:
upascal_p0.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 15:17

Mikropascal AVR - pierwszy program z obsługą przerwań.

Chcąc samemu obsługiwać w programie określone przerwanie, to, rzecz jasna, należy napisać odpowiednią procedurę, która zostanie wywołana w momencie, gdy mikrokontroler rozpocznie jego obsługę. Samo utworzenie właściwej procedury nie jest czynnością skomplikowaną a głównym problemem sprowadza się do: jak powiedzieć kompilatorowi, że dana procedura jest taką specjalną, przeznaczoną do obsługi przerwania. Po pierwsze jej wyjątkowość polega na tym, że rozpoczęcie i zakończenie działania procedury musi spełniać pewne warunki, które w przypadku normalnych procedur nie występują. Rzeczą oczywistą jest to, że do obsługi przerwania, co jest równoznaczne z uruchomieniem określonej procedury, może dojść w dowolnym momencie i realizacja czynności wynikających z obsługi przerwania nie może modyfikować rejestrów używanych poza obsługą przerwania (stan rejestrów tuż przed uruchomieniem obsługi przerwań i na jej zakończeniu musi być identyczny). Oznacza to, że samo rozpoczęcie obsługi przerwania musi zachować na stosie wszystkie rejestry wykorzystane w obsłudze przerwania oraz dodatkowo rejestr SREG – rejestr przechowujący flagi z realizowanych operacji arytmetyczno-logicznych. W normalnych procedurach oraz funkcjach nie muszą być tak restrykcyjnie przestrzegane powyższe zasady (w przypadku funkcji to nawet nie da się tego uzyskać, gdyż wynik działania funkcji typowo jest przekazywany przez rejestry). Drugim istotnym elementem związanym z obsługą przerwań jest to, że jej adres musi być „dobrze znany” dla mikrokontrolera, gdyż uruchomienie obsługi przerwania następuje samoczynnie przez sprzęt (nie jest wynikiem działania jakiejkolwiek instrukcji). „Dobra znajomość” adresów obsługi przerwań oznacza, że w pamięci mikrokontrolera przeznaczonej na program są miejsca (o ściśle określonym adresie) o specjalnym przeznaczeniu. W tych miejscach są umieszczane instrukcje skoku do rzeczywistej procedury obsługi przerwania. Cały ten obszar jest nazywany tabelą wektorów przerwań. Każde przerwanie ma w tej tabeli indywidualne miejsce. Implikacją tego, że obsługa przerwania uruchamiana jest sprzętowo jest to, że procedura (nie może być to funkcja, gdyż ta generuje zawsze wynik swego działania, co jest w sprzeczności z nienaruszalnością rejestrów) musi być bezparametrowa.
W celu określenia, że dana procedura jest przewidziana do obsługi przerwania, należy tuż za jej nagłówkiem podać „zaklęcie” org wraz z określeniem pozycji w tabeli wektorów przerwań, gdzie ma być umieszczone wejście do obsługi przerwania. W prezentowanym mikroPascalu zapisuje się to następująco:

procedure <nazwa procedury> ; org <pozycja w tabeli wektorów przerwań> ;

gdzie
<pozycja w tabeli wektorów przerwań> jest liczbą całkowitą w przedziału obejmującego wszystkie przerwania przewidziane dla danego typu mikrokontrolera (ich liczba jest różna w zależności od modelu mikrokontrolera).
Po tym nagłówku następuje zapis algorytmu obsługi przerwania tak jak w zwykłej procedurze. Przykładowo do obsługi przerwania generowanego w wyniku przepełnienia licznika 0, zapis procedury do obsługi przerwania może wyglądać następująco:

procedure Timer0Overflow_ISR ; org 6 ;
begin
end (* Timer0Overflow_ISR *) ;


Znaczenie specyfikacji org nie powinno być tajemniczym dla osób, które tworzyły oprogramowanie dla mikrokontrolerów w języku asembler. Oznacza ono określenie miejsca w pamięci, gdzie coś ma być umieszczone. Podobnie jest w mikroPascalu, poprzez tą specyfikację można umieścić kod danej procedury w wymaganym miejscu. Jednak w przypadku, gdy wskazanie poprzez org dotyczy elementu tablicy wektorów przerwań, nie wpływa na położenie kodu w pamięci flash samej procedury obsługi przerwań a jedynie powoduje umieszczenie w tablicy wektorów przerwań instrukcji skoku do procedury obsługi przerwania. Dodatkowo sama procedura, dla której zastosowano org, zawiera specyficzne operacje charakterystyczne dla obsługi przerwania.
Jednak stosowanie w specyfikacji org liczb niesie ze sobą pewien dyskomfort. Wymaga to pamiętania w szczegółach jak jest zbudowana tablica wektorów przerwań dla danego mikrokontrolera. Znacznie bardziej przyjaźnie jest posłużyć się odpowiednim identyfikatorem stałej. Takie stałe są zdefiniowane w odpowiednim module opisującym indywidualne cechy i szczegóły każdego mikrokontrolera. Specyfikując w projekcie, że tworzony program dotyczy określonego modelu mikrokontrolera (w tym przypadku jest to ATTINY2313), do programu automatycznie jest dołączony odpowiedni moduł (takie uses <nazwa modułu>; wykonane przez kompilator niejako gratis, nie trzeba tego robić w programie). Wszystkie możliwe moduły określające mikrokontrlery, znajdują się w kartotece C:\Program Files\Mikroelektronika\mikroPascal PRO for AVR\Defs. Odpowiedni plik można otworzyć i posługiwać się elementami tam zawartymi (wymaga to jedynie wyklikania kartoteki i nazwy pliku). Istnieje inna prosta i szybka metoda otwierania wymaganego pliku definiującego. Należy kursorem myszki stanąć na odpowiedni identyfikator w programie źródłowym (to może być cokolwiek związane z danym mikrokontrolerem: nazwa portu, rejestru, bitu itp.) i kliknąć prawym klawiszem myszki. W przypadku, gdy program nie zawiera takiego identyfikatora (jak przykładowo nowoutworzony szablon programu) można napisać przykładowo SREG (nazwa rejestru flag operacji) i na nim kliknąć prawym klawiszem myszki (po czym wpisaną nazwę rejestru można usunąć). Pokazuje to poniższy rysunek.
upascal-p1-01.png
Po kliknięciu z zaproponowanego menu kontekstowego wybrać opcję „Find Declaration”, co spowoduje otwarcie właściwego pliku w miejscu gdzie znajduje się definicja poszukiwanego elementu (przykładowo rejestru SREG). W pliku tym można odszukać specyfikację stałych przeznaczonych do związania przerwania z pozycją w tabeli wektorów przerwań.
upascal-p1-02.png
Pozostało „wklepać” tekst programu. W programie tym na diodach LED przyłączonych do portu B będą wyświetlane kolejne liczby binarne. Wyświetlanie i inkrementowanie wyświetlanej liczby odbywa się w procedurze obsługi przerwania, która realizuje tą akcje co ileś przerwań (w celu zmniejszenia częstotliwości widocznych efektów działania obsługi przerwania). Wcześniej port B jest skonfigurowany jako port wyjściowy. Środowisko mikrokontrolera prezentującego prosty program z obsługą przerwań jest pokazane na poniższym rysunku.
upascal-p1-03.png

Program przedstawia się następująco:

Kod: Zaznacz cały

program prog1;

const
  TickLimit            = 8 ;
 
var
  Counter              : byte ;
  GlobalCounter        : byte ;
 
procedure Timer0Overflow_ISR ( ) ; org IVT_ADDR_TIMER0_OVF ;

begin
  if ( Counter >= TickLimit ) then
    begin
      inc ( GlobalCounter ) ;
      PORTB := not GlobalCounter ;
      Counter := 0 ;
    end (* if ... *)
  else
    Inc ( Counter ) ;
end (* Timer0Overflow_ISR *) ;


procedure HardwareInit ;
begin
  TOIE0_bit := 1 ;
  TCCR0A := 0 ;
  TCCR0B := 0x05 ;
end (* HardwareInit *) ;


procedure EnvirInit ;
begin
  DDRB := 0xFF ;
  PORTB := 0 ;
end (* EnvirInit *) ;


procedure SoftwareInit ;
begin
  Counter := 0 ;
  GlobalCounter := 0 ;
end (* SoftwareInit *) ;


begin
  HardwareInit ;
  EnvirInit ;
  SoftwareInit ;
  asm
    SEI ;
  end ;
  repeat
    nop ;
  until false ;
end.


Fragment:
procedure Timer0Overflow_ISR ( ) ; org IVT_ADDR_TIMER0_OVF ;

Identyfikator stałej IVT_ADDR_TIMER0_OVF pochodzi z pliku C:\Program Files\Mikroelektronika\mikroPascal PRO for AVR\Defs\ATTINYy2313.mpas (podobnie jak wszystkie pozostałe identyfikatory rejestrów).

Po załadowaniu programu do pamięci mikrokontrolera można obserwować działanie programu. Jest ono zgodne z oczekiwanym.
Wracając do tekstu programu postanowiłem sprawdzić, jak kompilator poradził sobie z obsługą przerwań. W tym celu została wykonana deasemblacja modułu. Jej wynik przedstawia się następująco i wprawił mnie w wielkie zdziwienie (we fragmencie dotyczącym procedury obsługi przerwania):

_Timer0Overflow_ISR:
PUSH R30
PUSH R31
PUSH R27
IN R27, SREG+0
PUSH R27

Mamy tu jakiś rozruch procedury obsługi przerwania. Na stosie zostaje zachowanych kilka rejestrów roboczych oraz rejestr flag SREG.

;prog1.mpas,12 :: begin
;prog1.mpas,13 :: if ( Counter >= TickLimit ) then
LDS R16, _Counter+0
CPI R16, 8

Natomiast tu występuje instrukcja zmiany zawartości rejestru R16, który nie został zachowany na stosie. Jak widać z dalszych instrukcji, rejestr R16 jest nagminnie używany.

BRSH L__Timer0Overflow_ISR14
JMP L__Timer0Overflow_ISR2
L__Timer0Overflow_ISR14:
;prog1.mpas,15 :: inc ( GlobalCounter ) ;
LDS R16, _GlobalCounter+0
SUBI R16, 255
STS _GlobalCounter+0, R16
;prog1.mpas,16 :: PORTB := not GlobalCounter ;
COM R16
OUT PORTB+0, R16
;prog1.mpas,17 :: Counter := 0 ;
LDI R27, 0
STS _Counter+0, R27
;prog1.mpas,18 :: end (* if ... *)
JMP L__Timer0Overflow_ISR3
;prog1.mpas,19 :: else
L__Timer0Overflow_ISR2:
;prog1.mpas,20 :: Inc ( Counter ) ;
LDS R16, _Counter+0
SUBI R16, 255
STS _Counter+0, R16
L__Timer0Overflow_ISR3:
;prog1.mpas,21 :: end (* Timer0Overflow_ISR *) ;
L_end_Timer0Overflow_ISR:
POP R27
OUT SREG+0, R27
POP R27
POP R31
POP R30
RETI

Ekwiwalentnie do zachowania rejestrów w fazie rozruchowej, następuje odtworzenia zachowanych rejestrów oraz zastosowanie instrukcji reti zamiast typowej ret.

; end of _Timer0Overflow_ISR


Takie zachowanie kompilatora stanowi ewidentne naruszenie pewnych zasad dotyczących obsługi przerwań. Przecież gdziekolwiek w programie zostanie wpisana do rejestru R16 jakaś wartość, to w wyniku obsługi przerwania, która może wystąpić asynchronicznie w dowolnym miejscu, czyli przykładowo tuż za instrukcją wpisu do rejestru R16, nastąpi magiczna, nieautoryzowana zmiana zawartości rejestru. W R16 było coś i nagle samo zmieniło się na inne! Trudno uwierzyć w wystąpienie tak podstawowego błędu związanego z generacją kodu. Tu przypomniały mi się eksperymenty z dawnych czasów związane z mikrokontrolerem AVR o symbolu AT90S1200. Jego cechą charakterystyczną był brak pamięci RAM, a co implikuje brak stosu. Jak w takim przypadku obsługiwać przerwania? Należy do obsługi przerwań wydzielić z istniejącej puli rejestrów (R0 .. R31) kilka, które będą używane jedynie w obsłudze przerwań i poza ich obsługą nie występuje ich użycie. Rozwiązanie jest skuteczne, nie dojdzie do nieautoryzowanej zmiany zawartości rejestrów. Po deasemblacji całego programu przejrzałem całość uzyskanego kodu i poza procedurą obsługi przerwania nigdzie nie wystąpiło użycie rejestru R16. Albo to wynik zamierzonego działania kompilatora albo przypadek.
Nie mogę uwierzyć, by narzędzie, które zaczyna zdobywać uznanie zawierało tak rażące błędy. Sprawę należy zbadać dokładniej. W tym celu wniosłem modyfikacje do programu zmuszające do użycia rejestru R16. Tekst programu po modyfikacji jest następujący (generalnie jest identyczny z wyjątkiem miejsc wyróżnionych):

Kod: Zaznacz cały

program prog1;

const
  TickLimit            = 8 ;
 
var
  Counter              : byte ;
  GlobalCounter        : byte ;
 

procedure Timer0Overflow_ISR ; org IVT_ADDR_TIMER0_OVF ;
begin
  if ( Counter >= TickLimit ) then
    begin
      inc ( GlobalCounter ) ;
      PORTB := not GlobalCounter ;
      Counter := 0 ;
    end (* if ... *)
  else
    Inc ( Counter ) ;
end (* Timer0Overflow_ISR *) ;


procedure HardwareInit ;
begin
  TOIE0_bit := 1 ;
  TCCR0A := 0 ;
  TCCR0B := 0x05 ;
end (* HardwareInit *) ;


procedure EnvirInit ;
begin
  DDRB := 0xFF ;
  PORTB := 0 ;
end (* EnvirInit *) ;


procedure SoftwareInit ;
begin
  Counter := 0 ;
  GlobalCounter := 0 ;
end (* SoftwareInit *) ;


procedure Fail ;
Begin
  asm
    cli ;
  end (* asm *) ;
  repeat
    PORTB := 0xFF ;
    Delay_ms ( 1000 ) ;
    PORTB := 0x00 ;
    Delay_ms ( 1000 ) ;
  until false ;
end (* Fail *) ;


begin
  HardwareInit ;
  EnvirInit ;
  SoftwareInit ;
  asm
    SEI ;
  end ;
  R16 := 0x5A ;
  repeat
    if R16 <> 0x5A then
      Fail ;
  until false ;
end.



procedure Fail ;Dodana zostaje procedura Fail, wywołanie której trwale zatrzyma wykonywanie programu z charakterystycznym mruganiem diodami LED.

R16 := 0x5A ;
Następuje zapis do rejestru R16 pewnej stałej. Tu R16 jest użyty jak każdy inny rejestr mikrokontrolera (przykładowo rejestr portu B). R16 jest zdefiniowany w ATTINYy2313.mpas razem z innymi rejestrami.

if R16 <> 0x5A then
Fail ;

W każdym obrocie pętli jest weryfikowana zawartość rejestru R16. W przypadku stwierdzenia innej zawartości niż oczekiwana, program wpadnie we wcześniej przygotowaną pułapkę. W obrębie instrukcji repeat ... until nie występuje modyfikacja R16, czyli należy oczekiwać, że będzie on przechowywać wpisaną przed pętlą repeat wartość stałą. Przy pierwszym wywołaniu obsługi przerwania od przepełnienia licznika T0 wykonanie programu powinno trafić do procedury Fail.

Kompilacja i weryfikacja programu po raz drugi wywołała zdziwienie. Wykonanie programu nie wpadło w przygotowaną pułapkę. Deasemblacja programu daje następujący wynik (w istotnych fragmentach):
prog1.mpas,87 :: R16 := 0x5A ;
0x00DC 0xE50A LDI R16, 90
;prog1.mpas,88 :: repeat
L__main19:
;prog1.mpas,89 :: if R16 = 0x5A then
0x00DE 0x350A CPI R16, 90
0x00E0 0xF411 BRNE L__main25
L__main35:
;prog1.mpas,90 :: OK
0x00E2 0xDFA8 RCALL _OK+0
0x00E4 0xC001 RJMP L__main26
;prog1.mpas,91 :: else
L__main25:
;prog1.mpas,92 :: Fail ;
0x00E6 0xDFA7 RCALL _Fail+0
L__main26:
;prog1.mpas,93 :: until false ;
0x00E8 0xCFFA RJMP L__main19
;prog1.mpas,94 :: end.
L_end_main:
L__main_end_loop:
0x00EA 0xCFFF RJMP L__main_end_loop
; end of _main


Kompilator został zmuszony do użycia rejestru R16 (występująca stała 90 dec = 5A hex). Natomiast w procedurze obsługi przerwania:

_Timer0Overflow_ISR:
0x008A 0x930F PUSH R16
0x008C 0x93EF PUSH R30
0x008E 0x93FF PUSH R31
0x0090 0x93BF PUSH R27
0x0092 0xB7BF IN R27, SREG+0
0x0094 0x93BF PUSH R27
;prog1.mpas,12 :: begin
(...)
L_end_Timer0Overflow_ISR:
0x00BE 0x91BF POP R27
0x00C0 0xBFBF OUT SREG+0, R27
0x00C2 0x91BF POP R27
0x00C4 0x91FF POP R31
0x00C6 0x91EF POP R30
0x00C8 0x910F POP R16
0x00CA 0x9518 RETI
; end of _Timer0Overflow_ISR


występują instrukcje chroniące zawartość rejestru R16. Czyżby kompilator „wiedział”, że obsługa przerwania może naruszyć zawartość rejestru R16, który jest wykorzystany poza procedurą obsługi przerwania?
Swoistą zagadką jest natomiast to, że deasemblacja samego modułu (nie całości wylinkowanego programu) daje odmienny rezultat (nie zawiera operacji chroniącej zawartość R16):

_Timer0Overflow_ISR:
PUSH R30
PUSH R31
PUSH R27
IN R27, SREG+0
PUSH R27
(...)
POP R27
OUT SREG+0, R27
POP R27
POP R31
POP R30
RETI
; end of _Timer0Overflow_ISR


Generalnie jest to dziwna metodologia obsługi przerwań, ale istotne jest to by była skuteczna. Jeden eksperyment nie jest dowodem na poprawność, więc pozostaje nutka niedowierzania, czy kompilator nigdy się nie myli. Oby nie, bo to może przysporzyć wielu niepotrzebnych zmartwień wynikających z niewłaściwego zachowania się mikrokontrolera, które będzie zdarzać się rzadko.
Przeglądając zawartość tabeli wektorów przerwań:

; LST file generated by mikroListExporter - v.2.0
; Date/Time: 2013-04-07 22:58:31
;----------------------------------------------

;Address Opcode ASM
0x0000 0xC045 RJMP _main
0x0002 0xCFFE RJMP 0
0x0004 0xCFFD RJMP 0
0x0006 0xCFFC RJMP 0
0x0008 0xCFFB RJMP 0
0x000A 0xCFFA RJMP 0
0x000C 0xC020 RJMP _Timer0Overflow_ISR
0x000E 0xCFF8 RJMP 0
0x0010 0xCFF7 RJMP 0
0x0012 0xCFF6 RJMP 0
0x0014 0xCFF5 RJMP 0
0x0016 0xCFF4 RJMP 0
0x0018 0xCFF3 RJMP 0
0x001A 0xCFF2 RJMP 0
0x001C 0xCFF1 RJMP 0
0x001E 0xCFF0 RJMP 0
0x0020 0xCFEF RJMP 0
0x0022 0xCFEE RJMP 0
0x0024 0xCFED RJMP 0
_main:
0x008C 0xEDBF LDI R27, 223
0x008E 0xBFBD OUT SPL+0, R27
0x0090 0xE0B0 LDI R27, 0
0x0092 0xBFBE OUT SPL+1, R27
;prog1.mpas,46 :: begin


można zauważyć, że wszystkie niewykorzystane elementy tablicy wektorów przerwań zawierają instrukcję skoku do miejsca o adresie 0. Płynie z tego wniosek, że gdyby doszło do obsługi przerwania, dla którego nie jest przyporządkowana procedura obsługi, nastąpi reset mikrokontrolera (tak jak tuż po włączeniu jemu zasilania). Jeżeli w trakcie tworzenia i weryfikacji działania programu nastąpi reset, to może być efektem wystąpienia powyższego przypadku (podobny efekt może dać zadziałanie watchdoga, jeżeli jest włączony). Taki efekt należy do pomyłki w trakcie tworzenia oprogramowania, gdyż specyfikacja zezwoleń na obsługę poszczególnych przerwań nie jest spójna z liczbą procedur przewidzianych do ich obsługi.

Załącznik:
upascal_p1.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
tasza
Geek
Geek
Posty: 1082
Rejestracja: czwartek 12 sty 2017, 10:24
Kontaktowanie:

Re: [Mikropascal] i procki AVR

Postautor: tasza » piątek 28 kwie 2017, 15:36

gawel pisze:Jeden eksperyment nie jest dowodem na poprawność, więc pozostaje nutka niedowierzania, czy kompilator nigdy się nie myli.

kapitalny materiał, ale czad
pierwsza graficzka w pierwszym poście, ładna skądinąd, ale - czy coś nie wyszło z dodawaniem obrazka?
na tym forum język Pascal to egzotyka raczej, w sumie to nie wiedzieć czemu, byłeś pierwszy
chyba przemyślenia swoje o Free Pascal i Lazarus także opiszę, nazbierało się troszkę, ale nie było pewności czy ciekawe w ogóle to jest
a strzępki kodu źródłowego to lepiej wyglądają w tagach: [ code ] tu kod [ / code ] (oczywiście bez spacji, tak napisałam, aby w komentarz weszło), to ta ikonka edytora - Obrazek
fajnie piszesz....
______________________________________________ ____ ___ __ _ _ _ _
Kończysz tworzyć dopiero, gdy umierasz. (Marina Abramović)

SuperGość
Uber Geek
Uber Geek
Posty: 2346
Rejestracja: piątek 04 wrz 2015, 09:03

Re: [Mikropascal] i procki AVR

Postautor: SuperGość » piątek 28 kwie 2017, 15:38

Ano rękę do pisania masz o czym wiemy dobrze :D - a mikropascala nie trzeba kupować w mikroe?

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 15:48

tasza pisze:pierwsza graficzka w pierwszym poście, ładna skądinąd, ale - czy coś nie wyszło z dodawaniem obrazka?

Graficzka pochodzi z dokumentacji od Mikropascala :D . Obrazek u mnie wygląda poprawnie.
Ostatnio zmieniony sobota 29 kwie 2017, 00:57 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 15:54

tasza pisze:...na tym forum język Pascal to egzotyka raczej, w sumie to nie wiedzieć czemu, byłeś pierwszy
chyba przemyślenia swoje o Free Pascal i Lazarus także opiszę,

Spodziewam sie.... Kiedyś zaprzyjaźniony człowiek poprosił mnie o opinię o pakiecie, zrobiłem badania, więc ... nie musi on być jedynym czytelnikiem. W sumie pakiet... jak pakiet, ale niektóre wynalazki podobają mi się. :D
Ostatnio zmieniony sobota 29 kwie 2017, 00:58 przez gaweł, łącznie zmieniany 1 raz.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
tasza
Geek
Geek
Posty: 1082
Rejestracja: czwartek 12 sty 2017, 10:24
Kontaktowanie:

Re: [Mikropascal] i procki AVR

Postautor: tasza » piątek 28 kwie 2017, 16:05

aa, no to rozumiem zatem teraz skąd ten fish-eye, ładne to
gawel pisze:W sumie pakiet... jak pakiet, ale niektóre wynalazki podobają mi się.

heh, no a ja, jako wychowana w kulcie języka C do takich wynalazków jak Pascal zawsze podchodziłam z nieufnością, oj wielką
aż nagle tak zwane życie pokazało, że w takim Lazarus czy Delphi dla przykładu, można na kolanie wręcz wydłubać i czasem przed nosem zleceniodawcy kawałek klikalnej, żyjącej aplikacji i mieć wtenczas fory przed innymi, w podsumowaniu - uważam że dobrze znać jeden ulubiony pakiet/język, a resztę jednak skutecznie ogarniać, choćby po łebkach, bo nigdy nie wiadomo co będzie jutro...

jeżeli taki Micropascal umożliwia zrobienie na cito choćby kadłubka sensownego oprogramowania i jego prezentację - to wystarczające, aby się nim na chwilę zająć
______________________________________________ ____ ___ __ _ _ _ _
Kończysz tworzyć dopiero, gdy umierasz. (Marina Abramović)

Awatar użytkownika
mokrowski
User
User
Posty: 190
Rejestracja: czwartek 08 paź 2015, 20:50
Lokalizacja: Tam gdzie Centymetro

Re: [Mikropascal] i procki AVR

Postautor: mokrowski » piątek 28 kwie 2017, 16:25

No i brawo @gawel :-)
A co do Lazarusa, ciekawe środowisko. Sam osobiście nie kodowałem ale w projekcie jedna z osób pisała klienta do tworzonej aplikacji. Całkiem zgrabnie wyszedł :-)
,,Myślenie nie jest łatwe, ale można się do niego przyzwyczaić" - Alan Alexander Milne: Kubuś Puchatek

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 16:35

Mikropascal AVR własna biblioteka procedur i funkcji do obsługi transmisji szeregowej w przerwaniach

W zestawie programów demonstracyjnych zawartych w pakiecie mikropascala znajduje się między innymi program obsługi portu szeregowego. Po uruchomieniu środowiska mikropacala należy otworzyć odpowiedni projekt znajdujący się w kartotece, gdzie zainstalowane jest oprogramowanie, typowo: C:\Program Files\Mikroelektronika\mikroPascal PRO for AVR\Examples.
upascal-p2-01.png
Jego kompilacja nie stanowi problemu. Nawet pobieżna analiza tekstu programu jest zachęcająca. Gdybym ja musiał przygotować program demonstracyjny pokazujący obsługę transmisji szeregowej, program posiadałby podobną funkcjonalność: program maszyny do pisania, czyli każdy wczytany znak jest zwrotnie wysłany do nadawcy.
We wstępnych komentarzach zawartych w programie jest napisane: This code demonstrates how to use UART library routines... w dodatku zastosowane w przykładzie procedury i funkcje są opisane w podręczniku, który można pobrać z internetu. Wygląda zachęcająco. Początkowo miałem trudności z ustaleniem sposobu przyłączenia odpowiedniej biblioteki do obsługi UART, bo w pliku projektowym nie znajduję żadnych dodatkowych składników mogących wskazywać na źródło tych procedur (a nie chciało mi się dokładnie studiować całość dokumentacji, więc zostaje zastosowana metoda prób i błędów).
By uzyskać dostęp do specyfikacji projektu należy kliknąć na zakładkę z prawej strony ekranu opisanej jako :Project Manager. Spowoduje to „wysunięcie się” odpowiedniego okienka.
upascal-p2-02.png

Dopiero klikając ma „Library Manager” na dole ekranu widzę zaznaczone użycie biblioteki UART.
upascal-p2-03.png
Klikając na odpowiedni „+”, można rozwinąć informacje dotyczące odpowiedniej biblioteki,
upascal-p2-04.png
oraz klikając na nazwę odpowiedniej funkcji/procedury uzyskać dostęp do szczegółowej informacji.
upascal-p2-05.png
To tak, jakby biblioteka została wbudowana w oprogramowanie. W moim przekonaniu to nie jest najlepsze rozwiązanie gdyż to „usztywnia” całą koncepcję (chyba, że został przewidziany jakiś mechanizm pozwalający na tworzenie własnych bibliotek, które można przyłączać na podobnej zasadzie), no ale jest jak jest.
Ponieważ nie ma możliwości przejrzenia treści bibliotecznych procedur i funkcji, postanowiłem przyjrzeć się wygenerowanemu kodowi. W tym celu zażądałem wydruku całego programu w języku asembler.
upascal-p2-06.png
To co zobaczyłem zaskoczyło mnie niezmiernie, aż musiałem oczy przetrzeć w obawie czy widzę poprawny obraz. Niestety przecieranie oczu niewiele pomogło i nadal widziałem ten sam obraz na ekranie. A co zobaczyłem? Otóż widzę całą (z podkreśleniem) tablicę obsługi przerwań jako pustą, nie ma uchwytu do obsługi jakiegokolwiek przerwania, czyli biblioteka nie posiłkuje się przerwaniami generowanymi przez sprzęt. Ja rozumiem, że w pewnych kręgach obsługa asynchronicznej transmisji szeregowej z wykorzystaniem przerwań jest zaawansowanym algorytmem i nie zawsze autorzy ich implementacji mają chęć udostępnić teksty programów źródłowych. Realizacja przykładowego programu (dla przypomnienia istotny fragment):

Kod: Zaznacz cały

  while (TRUE) do                           // Endless loop
    begin
      ifUART1_Data_Ready() <> 0then     // If data is received,
        begin
          uart_rd := UART1_Read();          // read the received data,
          UART1_Write(uart_rd);             // and send data via UART
        end;
    end;

zawiera ciasną pętlę, w której wykonywanymi operacjami jest przede wszystkim sprawdzanie, czy przypadkiem kontroler USART nie odebrał danych, po czym następuje odczyt odebranych danych. A co gdy potencjalny użytkownik będzie wymagał zastosowania kontrolera USART pracującego z prędkością 115200 bps gdzie przychodzi raz na godzinę kilka znaków a mikrokontroler generalnie jest bardzo zajęty czymś innym. Przy tej prędkości transmisji w ciągu jednej sekundy przychodzi około 10000 znaków (w zależności od trybu transmisji wartość rzeczywista waha się w pewnych granicach), co daje około 0,1 ms czasu na odebranie pojedynczego znaku. To oznacza, że należy bardzo często sprawdzać, czy nie nadszedł nowy znak. Tyle pary w gwizdek. Jeżeli jest gotowy znak do odczytu, to należy go odczytać. Może powstać problem: a co z nim zrobić gdy nadszedł i w danej chwili nie jest on potrzebny do przetwarzania? Praktycznie nie występuje zjawisko, że kontroler USART odebrał znak i tak się złożyło, że jest on właśnie potrzebny w algorytmie. Pozostawienie nieodczytanego znaku w rejestrach kontrolera USART może skutkować bezpowrotnym jego utraceniem (a co jeżeli to był magicznie wygenerowany zbiór liczb do totolotka, można sobie tego nie wybaczyć do końca życia). Odczytany znak można oczywiście gdzieś przybuforować, zorganizować obsługę buforów cyklicznych, ... tylko wtedy po jakiego grzmota mi taka biblioteka skoro wszystko trzeba zrobić samemu. Można zrozumieć potrzebę prostoty rozwiązań, ale z punktu widzenia całego programu zaproponowane rozwiązanie jest wręcz prymitywne (z podkreśleniem). Ciągle przed oczami stoi napis: ...how to use UART library routines... Biblioteka-porażka.
No i rozum mi wrócił, jak możesz liczyć na kogoś, to licz na siebie. Nadszedł czas na normalne rozwiązanie. By nie było konieczności wielokrotnego kopiowania kodu (w końcu obsługa kontrolera USART zdarza się dość często w zastosowaniach), warto zrobić to w miarę porządnie: utworzyć zestaw biblioteczny przyłączany do programu w razie zaistnienia takiej potrzeby. Mechanizm jest dość prosty: w programie korzystającym z obsługi USART należy zastosować odpowiedni zapis przyłączający wymaganą bibliotekę (niech nazywa się ona Serial). Do przyłączenia biblioteki służy uses. Stosowny fragment programu może wyglądać następująco:

Kod: Zaznacz cały

program Prog2 ;

uses Serial ;

(...)
begin
(...)
end.
W ogólnym przypadku należy pamiętać, że do projektu należy dodać bibliotekę Serial jako składnik. Samo uses spowoduje, że kompilator rozpozna elementy pochodzące z biblioteki, ale z problemem wygenerowania kodu wynikowego musi zmierzyć się również linker, który również musi być o tym poinformowany.
W obecnej chwili należy utworzyć nowy projekt, w którym wystąpi użycie procedur bibliotecznych. Tworząc nowy projekt kompilator mikropascala wygeneruje „startową” postać pliku programu źródłowego:

Kod: Zaznacz cały

program Prog2;

{ Declarations section }

begin
  { Main program }
end.

Dodatkowo należy utworzyć nowy element biblioteczny: Serial. W tym celu należy wybrać: File → New → New Unit.
upascal-p2-07.png
Spowoduje to utworzenie „startowej” postaci modułu bibliotecznego.
upascal-p2-08.png
Pierwsza operacja zachowania zawartości pliku na dysku spowoduje nadanie dla utworzonego modułu nazwy pliku na dysku (lub można wymusić operacją poprzez wybranie File → Save As). Plikowi należy nadać nazwę Serial. mpas.
upascal-p2-09.png
Dodatkowo trzeba dodać nowy element do projektu. W tym celu należy wybrać zakładkę dotyczącą specyfikacji projektu oraz kliknąć na ikonkę związaną z dodawaniem nowych elementów do projektu.
upascal-p2-10.png
W kolejnym okienku dialogowym należy wybrać nowoutworzony plik biblioteczny. Teraz pozostało wprowadzić treści plików. Utworzony moduł ma następującą postać:

Kod: Zaznacz cały

unit Serial ;

const
  TxBufferSize                 = 32 ;
  RxBufferSize                 = 16 ;
 
  Speed2400AtCrystal7k3728     = 191 ;
  Speed4800AtCrystal7k3728     = 95 ;
  Speed9600AtCrystal7k3728     = 47 ;
  Speed19k2AtCrystal7k3728     = 23 ;
  Speed38k4AtCrystal7k3728     = 11 ;
  Speed115k2AtCrystal7k3728    = 3 ;

procedure USARTHardwareInit ( Speed : word ) ;
procedure InitSerialBuffer ;
function SerialDataPresent : boolean ;
function GetSerial : byte ;
procedure SendSerial ( SerialCh : byte ) ;


implementation

type
  InpSerialBufferRecT   = record
                            RdDataPointer : byte ;
                            WrDataPointer : byte ;
                            Buffer        : array[0..RxBufferSize-1] of byte ;
                          end (* record *) ;
  OutSerialBufferRecT   = record
                            Busy          : boolean ;
                            RdDataPointer : byte ;
                            WrDataPointer : byte ;
                            Buffer        : array[0..TxBufferSize-1] of byte ;
                          end (* record *) ;

var
  UART0InpBuffer : InpSerialBufferRecT ; volatile ;
  UART0OutBuffer : OutSerialBufferRecT ; volatile ;

procedure USART0RX_ISR ( ) ; org IVT_ADDR_USART__RX ;
var
  RecCh : byte ;
begin
  RecCh := UDR ;
  UART0InpBuffer . Buffer [ UART0InpBuffer . WrDataPointer ] := RecCh ;
  inc ( UART0InpBuffer . WrDataPointer ) ;
  if UART0InpBuffer . WrDataPointer >= RxBufferSize then
    UART0InpBuffer . WrDataPointer := 0 ;
end (* USART0RX_ISR *) ;


procedure USART0TX_ISR ( ) ; org IVT_ADDR_USART__TX ;
begin
  if UART0OutBuffer . RdDataPointer = UART0OutBuffer . WrDataPointer then
    begin
      UART0OutBuffer . Busy := false ;
    end (* if ... *)
  else
    begin
      UDR := UART0OutBuffer . Buffer [ UART0OutBuffer . RdDataPointer ] ;
      inc ( UART0OutBuffer . RdDataPointer ) ;
      if UART0OutBuffer . RdDataPointer >= TxBufferSize then
        UART0OutBuffer . RdDataPointer := 0 ;
    end (* if ... else *) ;
end (* USART0TX_ISR *) ;


procedure InitSerialBuffer ;
begin
  UART0InpBuffer . RdDataPointer := 0 ;
  UART0InpBuffer . WrDataPointer := 0 ;
  UART0OutBuffer . Busy := false ;
  UART0OutBuffer . RdDataPointer := 0 ;
  UART0OutBuffer . WrDataPointer := 0 ;
end (* InitSerialBuffer *) ;


procedure USARTHardwareInit ( Speed : word ) ;
begin
(*
  RXCIE_bit := 1 ;
  TXCIE_bit := 1 ;
  RXEN_bit := 1 ;
  TXEN_bit := 1 ;
*)
  UCSRB := (1 shl RXCIE ) or (1 shl TXCIE ) or (1 shl RXEN ) or (1 shl TXEN );
(*
  UCSZ1_bit := 1 ;
  UCSZ0_bit := 1 ;
*)
  UCSRC := ( 1 shl UCSZ1 ) or ( 1 shl UCSZ0 ) ;
  UBRRH := hi ( Speed ) ;
  UBRRL := lo ( Speed ) ;
  InitSerialBuffer ;
end (* USARTHardwareInit *) ;


function SerialDataPresent : boolean ;
begin
  if UART0InpBuffer . RdDataPointer = UART0InpBuffer . WrDataPointer then
    begin
      SerialDataPresent := false ;
      exit ;
    end (* if ... *)
  else
    begin
      SerialDataPresent := true ;
      exit ;
    end (*( if ... else *) ;
end (* SerialDataPresent *) ;


function GetSerial : byte ;
var
  LocalData : byte ;
begin
  LocalData := 0 ;
  if SerialDataPresent then
  begin
    asm
      cli;
    end (* asm *) ;
    LocalData := UART0InpBuffer . Buffer [ UART0InpBuffer . RdDataPointer ] ;
    inc ( UART0InpBuffer . RdDataPointer ) ;
    if UART0InpBuffer . RdDataPointer >= RxBufferSize then
      UART0InpBuffer . RdDataPointer := 0 ;
    asm
      sei;
    end (* asm *) ;
  end (* if *) ;
  GetSerial := LocalData ;
end (* GetSerial *) ;


procedure SendSerial ( SerialCh : byte ) ;
var
  NextInx : byte ;
begin
  asm
    cli;
  end (* asm *) ;
  if not UART0OutBuffer . Busy then
    begin
      UART0OutBuffer . Busy := true ;
      UDR := SerialCh ;
    end (* if ... *)
  else
    begin
      repeat
        NextInx := UART0OutBuffer . WrDataPointer + 1 ;
        if NextInx >= TxBufferSize then
          NextInx := 0 ;
        if NextInx <> UART0OutBuffer . RdDataPointer then
          break ;
        asm
          sei ;
          nop ;
          nop ;
          nop ;
          cli ;
        end (* asm *) ;
      until false ;
      UART0OutBuffer . Buffer [ UART0OutBuffer . WrDataPointer ] := SerialCh ;
      inc ( UART0OutBuffer . WrDataPointer ) ;
      if UART0OutBuffer . WrDataPointer >= TxBufferSize then
        UART0OutBuffer . WrDataPointer := 0 ;
    end (* if ... else *) ;
  asm
    sei;
  end (* asm *) ;

end (* SendSerial *) ;

end.

Środowisko do eksperymentów z mikrokontrolerem wygląda następująca:
upascal-p2-11.png
Sam program ma następującą postać:

Kod: Zaznacz cały

program Prog2 ;

uses Serial ;

const
  TickLimit                    = 20 ;
  StatusLEDBit                 = 0 ;
 
var
  Counter                      : byte ;
  GlobalCounter                : byte ;
  Ch                           : byte ;
  StatusLed                    : sbit at PORTB . StatusLEDBit ;
  StatusLedDir                 : sbit at DDRB. StatusLEDBit ;
 
procedure Timer0Overflow_ISR ( ) ; org IVT_ADDR_TIMER0_OVF ;
begin
  inc ( GlobalCounter ) ;
  if Counter >= TickLimit then
  begin
    StatusLed := not StatusLed ;
    Counter := 0 ;
  end (* if ... *)
  else
    Inc ( Counter ) ;
end (* Timer0Overflow_ISR *) ;

procedure HardwareInit ;
begin
  TOIE0_bit := 1 ;
  TCCR0A := 0 ;
  TCCR0B := 0x05 ;
  USARTHardwareInit ( Speed9600AtCrystal7k3728 ) ;
end (* HardwareInit *) ;

procedure EnvirInit ;
begin
  StatusLedDir := 1 ;  (* jako wyjście *)
  StatusLed := 0 ;     (* LED wyłączony *)
end (* EnvirInit *) ;

procedure SoftwareInit ;
begin
  Counter := 0 ;
  GlobalCounter := 0 ;
  InitSerialBuffer ( ) ;
end (* SoftwareInit *) ;

begin
  HardwareInit ;
  EnvirInit ;
  SoftwareInit ;
  asm
    SEI ;
  end ;
  SendSerial ( 0x0D ) ;
  SendSerial ( 0x0A ) ;
  SendSerial ( 'H' ) ;
  SendSerial ( 'e' ) ;
  SendSerial ( 'l' ) ;
  SendSerial ( 'o' ) ;
  SendSerial ( ':' ) ;
  SendSerial ( 0x0D ) ;
  SendSerial ( 0x0A ) ;
  repeat
    if SerialDataPresent then
    begin
      Ch := GetSerial ;
      SendSerial ( Ch ) ;
      if Ch = 0x0D then
        SendSerial ( 0x0A ) ;
    end (* if *) ;
  until false ;
end.

Wysyłając z emulatora terminala do AVR'a znaki, są one zwrotnie odsyłane i wyświetlane na ekranie. Naciśnięcia klawisza Enter (generującego znak o kodzie CR=0x0D) powoduje w programie dodatkowe wysłanie znaku LF=0x0A. Na ekranie wygląda to jak rzeczywiste przejście do nowego wiersza a nie jedynie na początek tego samego wiersza. I tak miało być.
upascal-p2-12.png


Załącznik:
upascal_p2.zip

Komentarz do prezentowanych programów:
upascal_p2-komentarz.pdf
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 16:44

tasza pisze:... to wystarczające, aby się nim na chwilę zająć

Mądre słowa. :)

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 17:06

tasza pisze:heh, no a ja, jako wychowana w kulcie języka C do takich wynalazków jak Pascal zawsze podchodziłam z nieufnością,

No właśnie, kto jak się wychował, tak egzystuje dalej. Mój pierwszy język to był FORTRAN jeszcze na ODRA 1305 z genialnym systemem GEORGE 3. Potem właśnie PASCAL również na oderce. Potem przyszły czasy PC-tów z turbo-pascalem, więc fortran odszedł w niepamięć. Później sporo pisałem w MODULA-2 (uważam, że genialny język). Rzeczywistość zmusiła mnie do pokonania C i C++, DELFI i C# (ale jak muszę coś robić w C#, to mnie boli już tydzień wcześniej i tydzień później, wolno mi go nie trawić :) ).

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 20:36

Mikropascal AVR - operacje na bitach

[coś, co mi się spodobało w tym narzędziu] :)

Kompilator języka Pascal dla mikrokontrolerów AVR oferuje możliwość utworzenia zmiennych, do których można uzyskać dostęp bitowy (operować na poszczególnych bitach jak na typowych zmiennych). Rozwiązanie opiera się na „nałożeniu” na istniejącą zmienną innej zmiennej bitowej (jej typem jest typ sbit) ze wskazaniem odpowiedniej pozycji bitowej. Poniższy przykładowy program pokazuje działanie prezentowanej możliwości.
Tekst programu jest następujący:

Kod: Zaznacz cały

program prog3;

var
  Flags              : byte ;

  Flag0              : sbit at Flags.B0 ;
  Flag1              : sbit at Flags.B1 ;
  Flag2              : sbit at Flags.B2 ;
  Flag3              : sbit at Flags.B3 ;
  Flag4              : sbit at Flags.B4 ;
  Flag5              : sbit at Flags.B5 ;
  Flag6              : sbit at Flags.B6 ;
  Flag7              : sbit at Flags.B7 ;

begin
  Flag0 := 1 ;
  Flag1 := 0 ;
  Flag2 := 1 ;
  Flag3 := 0 ;
  Flag4 := 1 ;
  Flag5 := 0 ;
  Flag6 := 1 ;
  Flag7 := 0 ;
  if ( Flags = 0x55 ) then
  begin
    nop ; (* zadziałało *)
  end (* if *) ;
  repeat
    nop ;
  until false ;
end.

W powyższym przykładzie:
Flags : byte ;
Utworzona jest zmienna będąca ciałem dla poszczególnych zmiennych bitowych.

Flag0 : sbit at Flags.B0 ; ................ i kolejne następne:
Kolejne zmienne są typu bitowego bazującego na wyżej utworzonej zmiennej Flags. Każda z wymienionych zmiennych jest lokowana na innej pozycji bitowej. Z syntaktyki języka wynika, że po kropce za nazwą zmiennej oczekiwana jest stała liczbowa. Powyższy fragment można zapisać w następującej formie:
Flag0 : sbit at Flags.0 ;
Flag1 : sbit at Flags.1 ;
Flag2 : sbit at Flags.2 ;
Flag3 : sbit at Flags.3 ;
Flag4 : sbit at Flags.4 ;
Flag5 : sbit at Flags.5 ;
Flag6 : sbit at Flags.6 ;
Flag7 : sbit at Flags.7 ;
Oba zapisy są poprawne ponieważ identyfikatory B0 .. B7 są zdefiniowane jako stałe o wartościach odpowiednio 0 .. 7.


Flag0 := 1 ; .................. i kolejne następne
Zaczynając od pozycji najmniej znaczącej, następuje wpis do poszczególnych zmiennych bitowych nowych wartości (wpisy stanowią naprzemienny ciąg 0 i 1). W wyniku realizacji należy spodziewać się, że zmienna bazowa Flags będzie zawierała odpowiedni ciąg naprzemienny 0 i 1.

Do sprawdzenia zostanie użyty wbudowany symulator. Realizując krokową symulację powyższego programu okazuje się, że odpowiedni warunek okazał się prawdziwy:
upascal-p3-01.png

Innym sposobem przekonania się o wartości określonej zmiennej jest w trakcie symulacji zatrzymanie kursora myszki na nazwie zmiennej (o nazwie Flags), gdzie po niewielkiej zwłoce czasowej zostanie wyświetlona zawartość wskazanej zmiennej.
upascal-p3-02.png

Pokazana liczba jest w zapisie dziesiętnym. Przeliczając ją na system szesnastkowy otrzymujemy liczbą 0x55 (85 dec = 55 hex).

Kod: Zaznacz cały

;prog3.mpas,14 ::                 begin
;prog3.mpas,15 ::                 Flag0 := 1 ;
        LDS        R27, _Flags+0
        SBR        R27, 1
        STS        _Flags+0, R27
;prog3.mpas,16 ::                 Flag1 := 0 ;
        LDS        R27, _Flags+0
        CBR        R27, 2
        STS        _Flags+0, R27
Analizując wygenerowany przez kompilator kod programu w języku asembler widać, że realizacja operacji dotyczących poszczególnych zmiennych bitowych jest optymalna i wykorzystuje sprzętowe możliwości mikrokontrolera.
Możliwości „nakładania” zmiennych bitowych nie dotyczą jedynie zmiennych w pamięci RAM. Na identycznej zasadzie mogą być nakładane zmienne bitowe na rejestry procesora. Załóżmy, że do portu D mikrokontrolera (ATTINY2313) na pozycji bitowej 0 i 1 przyłączone są diody LED jak pokazano na rysunku (rysunek prezentuje jedynie elementy istotne dla omawianego tematu):
upascal-p3-03.png
Zgodnie z powyższym rysunkiem zmienna LED1 jest „zaalokowana” na wyprowadzeniu portu D na pozycji bitowej numer 0 oraz zmienna LED2 na kolejnym bicie tego portu.

Kod: Zaznacz cały

program prog3;

var
  LED1               : sbit at PORTD.0 ;
  LED2               : sbit at PORTD.1 ;

begin
  DDRB := 0xFF ;
  LED1 := 0 ;
  LED2 := 1 ;
  repeat
    nop ;
  until false ;
end.

Wracając do początkowej wersji programu można wykonać jeszcze kilka interesujących eksperymentów. Przykładowo realizując pewną zachciankę:
zmienna bazowa dla poszczególnych bitów jest zmienną dwubajtową. W wyniku dalszego nałożenia na nią zmiennych bitowych, będą one nałożone na młodszą część struktury dwubajtowej

Kod: Zaznacz cały

program prog3;

var
  Flags              : word ;

  Flag0              : sbit at Flags.B0 ;
  Flag1              : sbit at Flags.B1 ;
  Flag2              : sbit at Flags.B2 ;
  Flag3              : sbit at Flags.B3 ;
  Flag4              : sbit at Flags.B4 ;
  Flag5              : sbit at Flags.B5 ;
  Flag6              : sbit at Flags.B6 ;
  Flag7              : sbit at Flags.B7 ;

begin
  Flag0 := 1 ;
  Flag1 := 0 ;
  Flag2 := 1 ;
  Flag3 := 0 ;
  Flag4 := 1 ;
  Flag5 := 0 ;
  Flag6 := 1 ;
  Flag7 := 0 ;
  if ( Flags = 0x55 ) then
  begin
    nop ; (* zadziałało *)
  end (* if *) ;
  repeat
    nop ;
  until false ;
end.
Całkowicie naturalną „zachcianką” jest oczekiwanie możliwości mapowania zmiennych bitowych na większych strukturach. Niestety kompilator nie podzielał oczekiwań. Poniższy program (we fragmencie) jest niepoprawny:

Kod: Zaznacz cały

var
  Flags              : word ;

  Flag0              : sbit at Flags.B0 ;
  Flag1              : sbit at Flags.B1 ;
  Flag2              : sbit at Flags.B2 ;
  Flag3              : sbit at Flags.B3 ;
  Flag4              : sbit at Flags.B4 ;
  Flag5              : sbit at Flags.B5 ;
  Flag6              : sbit at Flags.B6 ;
  Flag7              : sbit at Flags.B7 ;
  Flag8              : sbit at Flags.8 ;  <-------------------- tego kompilator nie strawił

Niemniej nie oznacza to, że nie jest możliwe w programie manipulowanie bitami spoza zakresu 8 bitów. Jak widać w powyższym przykładzie, kompilator zgłosił błąd kompilacji przy próbie nałożenia zmiennej bitowej na pozycję wykraczającą poza obszar bajtu. Niemniej w programie możliwe jest zastosowanie takiej konstrukcji w instrukcjach podstawienia oraz warunkowych. Pokazuje to następujący program:

Kod: Zaznacz cały

program prog3;

var
  Flags              : word ;

begin
  Flags := 0 ;
  if ( Flags . 15 = 1 ) then
  begin
    nop ; (* ten warunek nie powinien być spełniony *)
  end (* if *) ;
  Flags := 0x8000 ;
  if ( Flags . 15 = 1 ) then
  begin
    nop ; (* ten warunek powinien być spełniony *)
  end (* if *) ;
  repeat
    nop ;
  until false ;
end.

Po kompilacji i uruchomieniu symulacji działania programu, program zachował się zgodnie z przewidywaniami. W powyższym przykładzie badanie warunków bitowych zawierało w sobie stałe, liczbowe określenie numeru bitu. W mikroPascalu istnieje możliwość użycia takiej konstrukcji językowej, gdzie numer bitu nie jest określony w fazie kompilacji (jest określony przykładowo przez zawartość zmiennej). Pokazuje to następujący program, gdzie zamiast stałej „w wyrażeniu po kropce” użyta jest zmienna (różnice w stosunku do poprzedniej wersji są wyróżnione):

Kod: Zaznacz cały

program prog3;

var
  Flags          : word ;
  Inx            : byte ;

begin
  Flags := 0 ;
  Inx := 15 ;
  if ( Flags . Inx = 1 ) then
  begin
    nop ; (* ten warunek nie powinien być spełniony *)
  end (* if *) ;
  Flags := 0x8000 ;
  if ( Flags . Inx = 1 ) then
  begin
    nop ; (* ten warunek powinien być spełniony *)
  end (* if *) ;
  repeat
    nop ;
  until false ;
end.

Godne uwagi jest:
(...)
Flags : word ;
Inx : byte ;
(...)
Inx := 15 ;
if ( Flags . Inx = 1 ) then
(...)
if ( Flags . Inx = 1 ) then

lub bardziej finezyjny przykład programu:

Kod: Zaznacz cały

program prog3;


function Test ( Variable : word ; BitNo : byte ) : boolean ;
                                            (* Gdzie występuje funkcja badawcza, w której pierwszym parametrem wywołania jest dwubajtowa
                                            badana zmienna oraz drugi parametr wywołania informuje o numerze testowanego bitu (z zakresu 0
                                            [najmniej znaczący] .. 15 [najbardziej znaczący] ). *)
begin
  if Variable . BitNo = 1 then
    Test := true
  else
    Test := false ;
end (* Test *) ;

begin
  if Test ( 0x0001 , 0 ) then
  begin
    nop ; (* warunek jest spełniony *)
  end (* if *) ;
                                                     (* Użycie funkcji testującej, testowana jest liczba 0000000000000001 w zapisie binarnym i
                                                     operacja dotyczy bitu numer 0. W tym przypadku funkcja Test powinna zwrócić wartość true. *)
  if Test ( 0x0001 , 1 ) then
  begin
    nop ; (* warunek jest spełniony *)
  end (* if *) ;
  if Test ( 0x0080 , 7 ) then
  begin
    nop ; (* warunek jest spełniony *)
  end (* if *) ;
  if Test ( 0x0800 , 12 ) then
  begin
    nop ; (* warunek jest spełniony *)
  end (* if *) ;
  if Test ( 0x8000 , 14 ) then
  begin
    nop ; (* warunek jest spełniony *)
  end (* if *) ;
  if Test ( 0x8000 , 15 ) then
  begin
    nop ; (* warunek jest spełniony *)
  end (* if *) ;
  repeat
    nop ;
  until false ;
end.

Po kompilacji i uruchomieniu symulacji działania programu, program zachował się zgodnie z przewidywaniami.
Operacje bitowe mogą być również stosowane w przypadku instrukcji podstawienia. Pokazuje to poniższy przykład programu:

Kod: Zaznacz cały

program prog3;

var
  Variable           : word ;
  Inx                : byte ;

begin
  Variable := 0 ;
  Inx := 4 ;
  Variable . 1 := 1 ;          (* Po wykonaniu instrukcji, zmienna Variable zawiera
                                         wartość 2 (ustawiony bit numer 1). *)
  Variable . Inx := 1 ;       (* Po wykonaniu instrukcji, zmienna Variable zawiera
                                         wartość 18 (ustawiony bit numer 1 i 4).*)
  Variable . 4 := 0 ;          (* Po wykonaniu instrukcji, zmienna Variable zawiera
                                         wartość 2 (ustawiony bit numer 1). *)
  Inx := 1 ;
  Variable . Inx := 0 ;       (* Po wykonaniu instrukcji, zmienna Variable zawiera wartość 0. *)
  repeat
    nop ;
  until false ;
end.


Jak widać na przykładach, możliwości operacji bitowych oferowane przez mikroPascal mogą być przydatnym narzędziem przy tworzeniu oprogramowania dla mikrokontrolerów.

Załącznik:
upascal_p3.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 21:27

Mikropascal AVR - stałe umieszczone w segmencie kodu

W języku pascal istnieje możliwość stosowania stałych, które są zdefiniowane w programie jako const. Zastosowanie tak zdefiniowanych stałych jest różnorodne. Identyfikatorem stałej można przykładowo posługiwać się do definiowania typu zmiennej (jako parametryzacja wielkości tablicy). Najszerszym zastosowaniem stałych jest wykorzystanie ich w instrukcjach (przykładowo instrukcjach podstawienia). W większości przypadków typ stałej (jej reprezentacja w kodzie programu) wynika ze sposobu jej zapisu. W bardziej złożonych strukturach definiując stałą należy określić jej typ. Poniższe rozważania dotyczą wpływu stałych na generowany w języku maszynowym kod programu. Dodatkowym aspektem badawczym jest stwierdzenie, w jaki sposób radzi sobie kompilator ze stałymi umieszczonymi w obrębie kodu programu (tu warto zauważyć, że kompilator języka C jest w swej filozofii przystosowany do architektury von Neumann'a i korzystając z takich stałych generuje instrukcje operujące w przestrzeni pamięci RAM, nie jest świadomy, że różne typy segmentów wymagają odmiennych instrukcji na przesłanie zawartości pamięci do rejestru roboczego).
Prezentowany program jest następujący (część wydruków pochodzi z deasemblacji modułu, część z deasemblacji całości programu):

Kod: Zaznacz cały

program prog4;

type
  DaysType = array [ 1 .. 12 ] of byte ;
 
const
  TextConstInFlash : string [ 10 ] = 'ABCDEFGH' ;
  TextConst = '01234567' ;
  DaysInFlash : DaysType = ( 31 , 28 , 31 , 30 , 31 , 30 ,
                             31 , 31 , 30 , 31 , 30 , 31 ) ;
  WordConstInFlash : word = 1234 ;
  WordConst = 145 ;
 
var
  Inx : byte ;
  Year : word ;
  RamDays : DaysType ;
  Text : string [ 10 ] ;

function TestFun ( MDays : byte ) : boolean ;
begin
  if MDays = 28 then
    TestFun := true
  else
    TestFun := false ;
end (* TestFun *) ;


begin
  Year := WordConst ;
  Year := WordConstInFlash ;
  Text := TextConstInFlash ;
  Text := TextConst ;
  Text := 'jakis inny' ;
  for Inx := 1 to 12 do
  begin
    RamDays [ Inx ] := DaysInFlash [ Inx ] ;
  end (* for *) ;
  RamDays := DaysInFlash ;
  Year := 0 ;
  for Inx := 1 to 12 do
  begin
    Year := Year + DaysInFlash [ Inx ] ;
  end (* for *) ;
  if TestFun ( DaysInFlash [ 3 ] ) then
  begin
    asm
      nop ;
    end (* asm *) ;
  end (* if *) ;
  Inx := 3 ;
  if TestFun ( DaysInFlash [ Inx ] ) then
  begin
    asm
      nop ;
    end (* asm *) ;
  end (* if *) ;
  if TestFun ( RamDays [ 3 ] ) then
  begin
    asm
      nop ;
    end (* asm *) ;
  end (* if *) ;
  repeat
  until false ;
end.

Można zastosować następujące chwyty:
TextConstInFlash : string [ 10 ] = 'ABCDEFGH' ;
TextConst = '01234567' ;

Definicja stałych na potrzeby instrukcji podstawienia operujących na typie string[10] – łańcucha znakowego o odpowiedniej długości. W jednym przypadku łańcuch znakowy ma ściśle określony typ, w drugim przypadku łańcuch znakowy nie ma określonej długości.

DaysInFlash : DaysType = ( 31 , 28 , 31 , 30 , 31 , 30 , 31 , 31 , 30 , 31 , 30 , 31 ) ;
Definicja obszaru tablicowego (o typie zdefiniowanym wyżej).

WordConstInFlash : word = 1234 ;
WordConst = 145 ;

Definicja stałej o wielkości przekraczającej 1 bajt z narzuconym typem (word) oraz bez określonego typu (do automatycznego rozpoznania przez kompilator).

Year := WordConst ;
Year := WordConstInFlash ;

;prog4.mpas,30 :: Year := WordConst ;
PUSH R2
LDI R27, 145
STS _Year+0, R27
LDI R27, 0
STS _Year+1, R27

;prog4.mpas,31 :: Year := WordConstInFlash ;
LDI R27, 210
STS _Year+0, R27
LDI R27, 4
STS _Year+1, R27

W obu przypadkach kompilator postąpił identycznie (bez względu, czy stała była definiowana z określonym typem czy typ jest domyślny). Wartość stałej została rozpisana w kodzie, co implikuje, że nie została umieszczona jako stała w pamięci FLASH (w segmencie kodu programu). W jednym przypadku jako część młodsza występuje 145 oraz jako część starsza 0, co po złożeniu do liczby dwubajtowej daje 145. Analogicznie, w drugim podstawieniu odpowiednie części (młodsza 210 i starsza 4) dają po złożeniu wartość 1234. Nasuwający się wniosek jest taki, że stałe całkowitoliczbowe nie są umieszczane w pamięci programu jako obszary stałych.

Text := TextConstInFlash ;
;prog4.mpas,32 :: Text := TextConstInFlash ;
LDI R26, lo_addr(_Text+0)
LDI R27, hi_addr(_Text+0)
LDI R30, lo_addr(_TextConstInFlash+0)
LDI R31, hi_addr(_TextConstInFlash+0)
CALL ___CCS2S+0
LDI R16, 0
ST X+, R16

Instrukcja podstawienia spowodowała wygenerowanie kodu prowadzącego do wywołania jakiejś systemowej funkcji. Z przygotowania parametrów przed wywołaniem wydać, że do pary rejestrów XH:XL załadowany jest adres obszaru docelowego oraz do pary rejestrów ZH:ZL adres obszaru źródłowego. Po wywołaniu do obszaru docelowego jest dopisany na końcu łańcucha znakowego znak jego końca.
Sama realizacja procedury przepisującej jest następująca:
___CCS2S:
L_loopCCS2Ss:
0x00C6 0x9005 LPM R0, Z+
;__Lib_System.mpas,93 ::
0x00C8 0x2000 TST R0
;__Lib_System.mpas,94 ::
0x00CA 0xF011 BREQ l_CCS2Send
;__Lib_System.mpas,95 ::
0x00CC 0x920D ST X+, R0
;__Lib_System.mpas,96 ::
0x00CE 0xF7D9 BRNE L_loopCCS2Ss
;__Lib_System.mpas,97 ::
l_CCS2Send:
L_end___CCS2S:
0x00D0 0x9508 RET
; end of ___CCS2S

Jest to odczyt bajtu z pamięci FLASH (czyli adres źródłowy musi dotyczyć pamięci FLASH – tak działa instrukcja LPM) i po stwierdzeniu, że odczytane dane są różne od zera (nie jest to koniec łańcucha znakowego) są przepisywane do obszaru pamięci RAM (instrukcja ST). Pobranie bajtu z pamięci FLASH i zapis do pamięci RAM jest realizowany z autoinkrementacją adresów. Sam znacznik końca napisu nie jest przepisywany do pamięci RAM, co implikuje dodatkowe operacje w miejscu realizacji podstawienia.
Sam obszar będący informacją źródłową jest następujący:
TextConstInFlash : string [ 10 ] = 'ABCDEFGH' ;
0x0098 0x4241 ;_TextConstInFlash+0
0x009A 0x4443 ;_TextConstInFlash+2
0x009C 0x4645 ;_TextConstInFlash+4
0x009E 0x4847 ;_TextConstInFlash+6
0x00A0 0x0000 ;_TextConstInFlash+8
0x00A2 0x00 ;_TextConstInFlash+10

i zawiera obszar 10 bajtów plus znak końca łańcucha znakowego (w definicji wartości stałej jest zażądane, by stała była łańcuchem 10-znakowym plus zawsze znak końca łańcucha). Obszar definiujący wartość stałej został uzupełniony znakami o kodzie 0 do zadeklarowanej wielkości.

Text := TextConst ;
Instrukcja podstawienia łańcucha znakowego z innym wariantem (stała zdefiniowana z nieokreśloną długością łańcucha znakowego).
;prog4.mpas,33 :: Text := TextConst ;
LDI R26, lo_addr(_Text+0)
LDI R27, hi_addr(_Text+0)
LDI R30, lo_addr(_TextConst+0)
LDI R31, hi_addr(_TextConst+0)
CALL ___CCS2S+0
LDI R16, 0
ST X+, R16

Podobnie jak w poprzedniej instrukcji podstawienia pascalowego, nastąpiło załadowanie do odpowiednich rejestrów adresów obszaru źródłowego oraz docelowego i wywołanie procedury kopiującej obszar.
;prog4.mpas,8 :: _TextConst
0x00A3 0x3130 ;_TextConst+0
0x00A5 0x3332 ;_TextConst+2
0x00A7 0x3534 ;_TextConst+4
0x00A9 0x3736 ;_TextConst+6
0x00AB 0x00 ;_TextConst+8

Jednak w tym przypadku obszar źródłowy w pamięci FLASH zawierał jedynie te znaki, jakie wynikają z zapisu (nie występuje uzupełnienie znakami o kodzie 0 do określonej wielkości) . Na końcu obszaru występuje znak końca łańcucha znakowego. Z powyższego przykładu można wysnuć wniosek dotyczący sposobu zapisu wartości stałej łańcuchowej, który ma najbardziej optymalne (pod względem zajętości w pamięci FLASH) rozwiązanie.

Text := 'jakis inny' ;
Inny wariant podstawienia operującego na zmiennych łańcuchowych dotyczy bezpośredniego podania stałej.
;prog4.mpas,34 :: Text := 'jakis inny' ;
LDI R30, lo_addr(_Text+0)
LDI R31, hi_addr(_Text+0)
LDI R27, 106
ST Z+, R27
LDI R27, 97
ST Z+, R27
LDI R27, 107
ST Z+, R27
LDI R27, 105
ST Z+, R27
LDI R27, 115
ST Z+, R27
LDI R27, 32
ST Z+, R27
LDI R27, 105
ST Z+, R27
LDI R27, 110
ST Z+, R27
LDI R27, 110
ST Z+, R27
LDI R27, 121
ST Z+, R27
LDI R27, 0
ST Z+, R27

Jak widać na wydruku deasemblacji, do obszaru zmiennej w pamięci RAM zostaje wpisana wartość (wpis jest rozpisany na pojedyncze znaki). Te rozwiązanie absorbuje najwięcej pamięci FLASH.

for Inx := 1 to 12 do
begin
RamDays [ Inx ] := DaysInFlash [ Inx ] ;
end (* for *) ;

Realizacja przepisania tablicy (array) z pamięci FLASH do pamięci RAM jako operacja przesłania po jednym elemencie wykonana w pętli.
;prog4.mpas,37 :: RamDays [ Inx ] := DaysInFlash [ Inx ] ;
LDS R20, _Inx+0
SUBI R20, 1
LDI R16, lo_addr(_RamDays+0)
LDI R17, hi_addr(_RamDays+0)
MOV R18, R20
LDI R19, 0
ADD R18, R16
ADC R19, R17
LDI R16, lo_addr(_DaysInFlash+0)
LDI R17, hi_addr(_DaysInFlash+0)
MOV R30, R20
LDI R31, 0
ADD R30, R16
ADC R31, R17
LPM R16, Z
MOVW R30, R18
ST Z, R16
;prog4.mpas,38 :: end (* for *) ;
LDS R16, _Inx+0
CPI R16, 12
BRNE L__main32
JMP L__main9
L__main32:
LDS R16, _Inx+0
SUBI R16, 255
STS _Inx+0, R16
JMP L__main6
L__main9:

W każdym przypadku zostaje obliczony adres jednego elementy tablicy (bajtu) w pamięci FLASH oraz RAM oraz następuje przesłanie danych z użyciem rejestru. Istotne jest tu to, że kompilator zastosował właściwe instrukcje do realizacji przesłania (pobranie zawartości bajtu z pamięci FLASH oraz pamięci RAM realizowane jest poprzez odmienne instrukcje). Realizacja powyższych instrukcji pokazuje, że kompilator mikropascala rozróżnia miejsce źródłowe danych (pamięć FLASH/RAM) i nie ma potrzeby w jakiś specjalny sposób zaznaczania tego w tekście programu (w przypadku języka C taki automatyzm nie zachodzi).

RamDays := DaysInFlash ;
Inny wariant tej samej operacji (przepisania tablicy) jako operacja kopiowania obszaru z pamięci FLASH do pamięci RAM. Obszar źródłowy i docelowy jest tego samego typu (co implikuje identyczną zajętość pamięci).
;prog4.mpas,39 :: RamDays := DaysInFlash ;
LDI R26, lo_addr(_RamDays+0)
LDI R27, hi_addr(_RamDays+0)
LDI R30, lo_addr(_DaysInFlash+0)
LDI R31, hi_addr(_DaysInFlash+0)
LDI R24, 12
LDI R25, 0
CALL ___CCA2AW+0

Realizacja sprowadza się do załadowania do odpowiednich rejestrów adresów miejsca źródłowego (para rejestrów R27:R26), docelowego (para rejestrów R31:R30) oraz wielkości obszaru (para rejestrów R25:R24) i wywołania procedury kopiującej.
___CCA2AW:
L_loopCCA2A:
0x00AC 0x9005 LPM R0, Z+
0x00AE 0x920D ST X+, R0
0x00B0 0x9701 SBIW R24, 1
0x00B2 0xF7E1 BRNE L_loopCCA2A
L_end___CCA2AW:
0x00B4 0x9508 RET

Funkcja kopiująca zawiera typową realizację przepisania obszaru z pamięci FLASH do pamięci RAM z dekrementacją licznika pętli aż do jego wyzerowania. Taki zapis jest najbardziej optymalnym rozwiązaniem do przepisywania obszarów o identycznych rozmiarach (również w przypadku przepisywania z pamięci RAM do pamięci RAM, gdzie zapewne zostanie zastosowana inna procedura kopiująca, gdyż zastosowana w powyższym przykładzie służy do kopiowania FLASH → RAM).

Year := 0 ;
for Inx := 1 to 12 do
begin
Year := Year + DaysInFlash [ Inx ] ;
end (* for *) ;

Przykład zmuszający kompilator w operacjach arytmetycznych do korzystania z pojedynczych elementów tablicy zawartych w pamięci FLASH (jako przykładowo zsumowanie wszystkich elementów tablicy).
;prog4.mpas,40 :: Year := 0 ;
LDI R27, 0
STS _Year+0, R27
STS _Year+1, R27

;prog4.mpas,41 :: for Inx := 1 to 12 do
LDI R27, 1
STS _Inx+0, R27
L__main11:

;prog4.mpas,43 :: Year := Year + DaysInFlash [ Inx ] ;
LDS R18, _Inx+0
SUBI R18, 1
LDI R16, lo_addr(_DaysInFlash+0)
LDI R17, hi_addr(_DaysInFlash+0)
MOV R30, R18
LDI R31, 0
ADD R30, R16
ADC R31, R17
LPM R18, Z
LDS R16, _Year+0
LDS R17, _Year+1
ADD R16, R18
LDI R27, 0
ADC R17, R27
STS _Year+0, R16
STS _Year+1, R17

;prog4.mpas,44 :: end (* for *) ;
LDS R16, _Inx+0
CPI R16, 12
BRNE L__main33
JMP L__main14
L__main33:
LDS R16, _Inx+0
SUBI R16, 255
STS _Inx+0, R16
JMP L__main11
L__main14:

Również w tym przypadku kompilator zastosował właściwe instrukcje do przesłania danych z pamięci FLASH do rejestrów (czyli rozpoznał typ przestrzeni adresowej dla argumentów operacji).

if TestFun ( DaysInFlash [ 3 ] ) then
begin
asm
nop ;
end (* asm *) ;
end (* if *) ;

Kolejne badanie dotyczyło sposobu przekazywania parametru przy wywołaniu jakiejś funkcji.
;prog4.mpas,45 :: if TestFun ( DaysInFlash [ 3 ] ) then
LDI R27, 31
MOV R2, R27
CALL _TestFun+0

W tym przypadku, gdzie argument wywołania funkcji jest podany jednoznacznie (element stałej tablicy o stałym indeksie) kompilator „wykazał się przebiegłością” i realizował wywołanie funkcji z użyciem stałej (wiedział, że DaysInFlash[3] = 31).

Inx := 3 ;
if TestFun ( DaysInFlash [ Inx ] ) then
begin
asm
nop ;
end (* asm *) ;
end (* if *) ;

Identyczne zagadnienie jak w poprzednim przypadku, z tym, że obecnie nie można jednoznacznie na etapie kompilacji określić parametru wywołania funkcji (element tablicy jest zaindeksowany poprzez zmienną).
;prog4.mpas,51 :: Inx := 3 ;
LDI R27, 3
STS _Inx+0, R27

;prog4.mpas,52 :: if TestFun ( DaysInFlash [ Inx ] ) then
LDI R18, 2
LDI R16, lo_addr(_DaysInFlash+0)
LDI R17, hi_addr(_DaysInFlash+0)
MOV R30, R18
LDI R31, 0
ADD R30, R16
ADC R31, R17
LPM R16, Z
MOV R2, R16
CALL _TestFun+0

W tym przypadku musiało nastąpić typowe wyliczenie adresu argumentu i pobranie jego wartości z pamięci. Istotne jest tu to, że kompilator zastosował właściwe dla rodzaju pamięci instrukcje do przesłania.

if TestFun ( RamDays [ 3 ] ) then
Kolejny test dla kompilatora dotyczy przekazania parametru wywołania funkcji, gdzie jej argument jest ściśle określonym (jako wartość indeksu tablicy) a tablica jest umiejscowiona w pamięci RAM.
;prog4.mpas,58 :: if TestFun ( RamDays [ 3 ] ) then
LDS R2, _RamDays+2
CALL _TestFun+0

Jak można było się spodziewać, kompilator określił adres argumentu funkcji na etapie kompilacji (tablica RamDays zawiera elementy bajtowe, więc RamDays+2 jest adresem argumentu wywołania funkcji [+2 wynika z tego, że tablica RamDays jest indeksowana od 1 czyli adres RamDays odpowiada dla RamDays[1], adres RamDays+1 odpowiada dla RamDays[2], adres RamDays+2 odpowiada dla RamDays[3]).

Wnioski końcowe.
Generalnie można stwierdzić, że kompilator mikropascala sam rozpoznaje rodzaj przestrzeni adresowej związanej z argumentami różnych operacji (arytmetycznymi, logicznymi, wywołania funkcji i procedur). Pewna elastyczność zapisu występująca w instrukcjach języka ma wpływ na optymalność wygenerowanego kodu programu. Mając świadomość sposobu realizacji instrukcji pascalowych można odpowiednio je zapisując mieć wpływ na optymalność generowanego kodu.

Załącznik:
upascal_p4.zip
Nie masz wymaganych uprawnień, aby zobaczyć pliki załączone do tego posta.
Ostatnio zmieniony piątek 28 kwie 2017, 21:51 przez gaweł, łącznie zmieniany 2 razy.

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse

Awatar użytkownika
gaweł
Geek
Geek
Posty: 1259
Rejestracja: wtorek 24 sty 2017, 22:05
Lokalizacja: Białystok

Re: [Mikropascal] i procki AVR

Postautor: gaweł » piątek 28 kwie 2017, 21:32

wojtek pisze:a mikropascala nie trzeba kupować w mikroe?

Nie wiem, soft ściągnąłem z podanej strone by się pobawić, potestować, posmakować, zobaczyć ... wiedzieć. :)

Prawdziwe słowa nie są przyjemne. Przyjemne słowa nie są prawdziwe.
Lao Tse


Wróć do „Inne języki programowania”

Kto jest online

Użytkownicy przeglądający to forum: Obecnie na forum nie ma żadnego zarejestrowanego użytkownika i 8 gości