RTOS czyli Real Time Operating System. Coś z czym predzej czy później przyjdzie nam się zetknąć programując mikrokontrolery. Choś pojęcie system operacyjny w mikrokontrolerach wydawać by się mogło , że to jakiś kosmos.
Ale kosmosem to nie jest a systemy RTOS stają się obecnie chlebem powszednim w systemach wbudowanych.
W artykule przedstawiam moje pierwsze spotkanie z RTOS i próbę poznania tego zagadnienia przy pomocy mikrokontrolera PIC. Dokąd mnie ta droga zaprowadzi nie mam zielonego pojęcia :)
RTOS używany jest tam gdzie wymagana jest pełna kontrola czasowa i synchronizacja wielu procesów tak aby działały one bez wzajemnego zakłócania. Przy czym za proces rozumiemy np miganie diodą LED :). Problemu nie ma jak migamy jedną ale jak chcemy migać kilkoma i każda np z inną częstotliwością to zaczynamy drapać się w głowę i szukać rozwiązań typu timery programowe, operacje modulo etc. Można i tak ale mądrzy ludzie ogarneli tego typu problemy i stworzyli RTOS-a po co zatem głowić się nad wymyśleniem powtórnie koła ? skoro mamy do dyspozycji cała furę na kołach.
Dopóki jesteśmy na etapie testowania pojedyńczych procesów czyli np. miganie jedną diodą LED , wyświetlanie napisu na LCD itp to w kierunku RTOS-a nie mamy potrzeby patrzeć. Przyjdzie jednak kiedyś taka chwila , że wyjdziemy z etapu mono wątkowości w aplikacji/programie i będziemy chcieli aby wiele procesów połaczyć ze sobą tak aby nie blokowały się wzajemnie czy też nie zakłócały swojej pracy. Wtedy jest czas na zaznajomienie się z tym co ma do zaoferowania RTOS.
Mylne można odnieść wrażenie , że skoro system "operacyjny" to zapcha nam zasoby mikrokontrolera tak, że zostanie tylko miejsce na mruganie jedną diodą LED :) . RTOS potrzebuje od kilkuset bajtów do kilku kB pamięci programu w zależności od stopnia skomplikowania implementacji systemu. Czyli jest możliwość dobrania systemu do możliwości mikrokontrolera i potrzeb.
Naukę RTOS-a zaleca się zacząć od konkretnych przykładów. Dlatego każdy szanujący się producent aplikuje system wraz z przykładami.
Jednym z bardziej popularnych RTOS-ów na rynku jest freeRTOS.
Jest on dostępny do większości popularnych mikrokontrolerów w tym do mikrokontrolerów PIC.
Należy tu nadmienić, że nie znajdziemy za wiele materiałów w jezyku Polskim na ten temat, stąd prosty wniosek , że trzeba się uczyć języków. Dokumentacja do freeRTOS-a jest dosyć obszerna i dla zielonego jak szyczypiorek w temacie może być nie lada wyzwaniem. Możemy też szybko się do tematu zrazić z racji przerostu przytłaczającej wiedzy w dokumentacji :)
Dlatego ja swoją przygodę z RTOS postanowiłem zacząć nie od kobyły freeRTOS-a ale od źrebaczka czyli od zwiewnej i lekkiej biblioteki RTOS dla mikrokontrolerów PIC24/dsPIC33 oferowanej za darmo przez Microchipa.
Dla porównania dokumentacja freeRTOS to ponad 400 stron a dokumentacja do picRTOS (moja nazwa) to tylko 15 stron :). freeRTOS zajmuje ok 6-12 kB Flash-a a picRTOS nie cały 1 kB.
Oczywiście coś za coś o ile freeRTOS posiada mocno rozbudowane wątki do sterowania procesami i mnóstwo opcji i podopcji o tyle picRTOS posiada jedynie podstawowe możliwości w zakresie sterowania procesami. Ale czy picRTOS jest zabawką ?? zdecydowanie nie, jak podkreśla Microchip to bardzo lekkie i profesjonalne narzędzie dla programistów systemów wbudowanych.
Biblioteka od Microchipa została napisana w asemblerze co daje gwarancję działania prawie z prędkością światła :). To co fajnego jest w tej bibliotece to m.in zaimplementowana możliwość podglądu działania procesów w skali czasu za pomocą dowolnego wielokanałowego zewnętrznego analizatora stanów logicznych. A nic lepiej nie przemawia do człowieka jak pismo obrazkowe :) Mamy również wgląd w przyjaznej formie za pomocą jednej tablicy w dane o wykorzystaniu stosu przez każdy z procesów. Wielkość stosu rezerwowanego dla każdego procesu jest parametrem ważnym a nawet bardzo ważnym , tę dane musimy podać jawnie przy inicjalizacji każdego procesu.
Sterowanie procesami w picRTOS odbywa się w oparciu o algorytm round robin. Co to jest i czym to się je ? . Posłużymy się tutaj informacjami z neta :
Algorytm RR
Algorytm planowania rotacyjnego (ang. round-robin - RR) jest oparty na algorytmie FCFS, jednakże różni się od FCFS faktem, że możliwe jest wywłaszczanie. Algorytm ten jest chętnie stosowany w systemach z podziałem czasu.
Zasada działania algorytmu jest następująca. Z każdym procesem powiązany jest kwant czasu, najczęściej 10 do 100 ms. Procesom znajdującym się w kolejce (traktowanej jako cykliczna) procesów gotowych do wykonania, przydzielane są odpowiednie odcinki czasu, nie dłuższe niż jeden kwant czasu.
Do implementacji algorytmu, podobnie jak w przypadku FCFS, używa się kolejki FIFO - brany jest pierwszy proces z wyjścia kolejki, ustawiany jest timer na przerwanie po upływie 1 kwantu czasu i proces jest wysyłany do procesora. Jeżeli czas trwania fazy procesu jest krótszy od 1 kwantu czasu, proces sam zwolni procesor i rozpocznie się obsługiwanie kolejnego procesu z kolejki. Jeżeli proces ma dłuższą fazę od 1 kwantu czasu, to nastąpi przerwanie zegarowe systemu operacyjnego, nastąpi przełączenie kontekstu, a proces do niedawna wykonywany, trafi na koniec kolejki procesów gotowych do wykonania.
Zobaczmy, ile wynosi średni czas oczekiwania w porównaniu z metodą FCFS dla następujących danych:
Proces | Czas trwania fazy |
P1 P2 P3 | 21 ms 6 ms 3 ms |
Średni czas oczekiwania wynosi więc: (0+13+16)/3 = 9,67 ms.
Wydajność algorytmu rotacyjnego zależy od kwantu czasu. W przypadku, gdy kwant czasu jest bardzo długi (dąży do nieskończoności) metoda RR działa jak FCFS. Natomiast w przypadku, gdy kwant czasu jest bardzo krótki (dąży do zera) planowanie RR nazywamy dzieleniem procesora (ang. processor sharing), gdyż zachodzi złudzenie, że każdy z n procesów ma własny procesor działający z prędkością 1/n prędkości prawdziwego procesora [1].
W przypadku algorytmu RR, podczas mówienia o wydajności, należy zastanowić się nad zagadnieniem przełączania kontekstu. Im mniejszy jest kwant czasu, tym system bardziej jest narażony na częste przełączenia kontekstu. Jeżeli czas przełączania kontekstu wynosi 10 procent kwantu czasu, to niemalże 10 procent czasu procesora jest tracone w wyniku przełączania kontekstu.
Algorytm jak algorytm ma swoje wady i swoje zalety o których trzeba mieć pojęcie. Nazwa algorytmu podoba mi się bo jej skrót kojarzy się z dobranocką o brygadzie RR.
Każdą bibliotekę RTOS trzeba przystosować do współpracy z konkretnym mikrokontrolerem i ten proces wymaga skupienia bo można tu polec. Przystosowanie polega na zainkludowaniu do biblioteki plików nagłówkowych konkretnego mikrokontrolera i ewentualnie przekonfigurowanie inicjalizacji sprzętowej. Nie chcę nawet myśleć jak by to przebiegało w przypadku ARM-ów.
Od strony sprzętowej swoje rozważania oprę na mikrokontrolerze 16-bitowym PIC24HJ128GP502 rozpędzony zegarem wewnętrznym do ok 40 MHz. Mikrokontrolery 16-bitowe od Microchipa są bardzo wdzięczne do programowania w stosunku do 8-bitowców znacznie elastyczniejsze nie wspominając już o większej mocy i zasobach a w stosunku do 32-bitowców łatwiejsze do okiełznania (patrząc na rejestr 32 bitowy dostajemy oczopląsu a przy 16-bitach ten efekt jeszcze nie występuje :) ). W przyszłości mam zamiar jak nie połamię zębów na postawieniu RTOS-a na mikrokontrolerze 32-bitowym PIC32MM lub MX
Zabawę z picRTOS zaczynamy od ściągnięcia biblioteki : Biblioteka RTOS
Po rozpakowaniu mamy dosyć sporo plików. Nas interesuje tylko samo core biblioteki i tu będziemy potrzebować następujące pliki :
multitask.h (z katalogu Include)
PosixTypes.h (z katalogu Include)
multitask.s (z katalogu Source)
Example_1.c (z katalogu Source)
W katalogu bibliotecznym Source znajdują się przykłady od Example_1.c do Example_5.c . Dołączamy do projektu jeden przykład i zmieniamy jego nazwę na main.c.
W projekcie będziemy potrzebować jeszcze trzech plików nagłówkowych do konkretnego mikrokontrolera w naszym przypadku do PIC24HJ128GP502. Plików tych nie ma w bibliotece RTOS, musimy je poszukać i dołaczyć do projektu z katalogu kompilatora XC16.
W LINUX-ie ścieżki do potrzebnych plików to :/opt/microchip/xc16/v1.30/support/PIC24H/h/p24HJ128GP502.h
/opt/microchip/xc16/v1.30/support/PIC24H/inc/p24HJ128GP502.inc
/opt/microchip/xc16/v1.30/support/PIC24H/inc/p24Hxxxx.inc
Te trzy pliki dołączamy do projektu jako pliki nagłówkowe. Nasze IDE to MPLABX od Microchipa dla przypomnienia.
Po stworzenia w MPLABX projektu, struktura katalogów powinna wyglądać następująco :
Przy pierwszym uruchomieniu program bedzie zgłaszał szereg błędów, głównie wynikających np z konfiguracji sprzętowej mikrokontrolera. Samo przystosowanie biblioteki i przykładów pod konkretny procek nie sprawiło mi większych problemów, kompilator bardzo klarownie sygnalizował miejsca błędów (brawo dla kompilatora XC16).
Po ogarnięciu etapu przystosowania biblioteki do konkretnego mikrokontrolera PIC24HJ128GP502, czas na przeczytanie instrukcji do biblioteki :) Spróbujemy to przetłumaczyć z chińsko-angielskiego na polski w końcu to nie 400 stron jak w freeRTOS. Instrukcję znajdziemy w katalogu Doc w ściągnietej bibliotece.
Na wstępie instrukcji znajdziemy informację , że na potrzeby obsługi jądra picRTOS zostanie zajęty jeden timer, standardowo jest to Timer1. Można użyć innego Timera np Timer2 , na stronie 14, jest infomacja jak to zmienić. Timer1 ustawiony jest w bibliotece na najwyższy priorytet, przy użyciu innych timerów w programie należy ich priorytety ustawiać poniżej 1.
Z instrukcji wynika, że musimy ustawić parametry czasowe systemu w pliku multitask.h. Są to :
#define FREQ_FCY 40.0 (częstotliwość w MHz zegara z jakim odpalamy nasz mikrokontroler)
#define TIME_SLICE 5 (ta wartość ustawia częstotliwość przełączania pojedyńczego zadania/procesu w kolejce, typowe wartości w przedziale 1-25 ms, w naszym przypadku 5ms)
Ale uwaga maksymalną częstotliwość przełączania zadania jaką możemy ustawić skorelowana jest z zegarem procka i preskalerem i tak dla 40 MHz możemy TIME_SLICE ustawić maksymalnie na ok 13 ms ((1/40MHz) * 8 * 65536)) . Jeśli chcemy więcej to trzeba zmienić preskaler na większy od 8 np na 64. Ustawiamy to w wierszu :
#define PRESCALE 8.0
Teraz na podstawie instrukcji skupimy się na funkcjach jakie nam oferuje biblioteka , 5 przykładów zawartych w bibliotece stara się przedstawić po co są te funkcje i jak ich używać :
typedef void(*Taskptr)(void);
void CreateTask(Taskptr task, uint16_t stack_size);Funkcja tworzy nam nowe zadanie/proces. Argumenty jakie przekazujemy to wskaźnik na funkcję zadania/procesu i wielkość zagnieżdzenia stosu dla procesu)
void Multitask(uint16_t tmr_period);
Funkcja uruchamia nam sterowanie zadaniami/procesami . Argumentem jest TMR_PERIOD zdefiniowany jak poniżej :
#define TMR_PERIOD (((TIME_SLICE)*(1000.0))/((PRESCALE)/(FREQ_FCY)))
void TaskSleep(uint16_t count);
Funkcja wstrzymująca wykonanie zadania/procesu na podaną w argumencie ilość obiegów kolejki. Przy każdym wywołaniu zadania licznik odlicza w dół.
void TaskYield(void);
Wywołanie tej funkcji wewnątrz zadania/procesu , przerywa bieżący proces i przekazuje sterowanie do następnego w kolejce zadania/procesu.
void DisableInterrupts (void);
Ta funkcja wyłacza przerwania. Używa się w krytycznych miejscach gdzie nie możemy sobie pozwolić aby wywołanie przerwania , zakłóciło nam wykonanie zadania.
void EnableInterrupts (void);
wznawia działanie przerwań.
void WaitForEvent(uint16_t event);
Ta funkcja ustawia flagę zdarzeń lub kombinację flag zdarzeń a następnie zawiesza działanie zadania/procesu do chwili skasowania flagi. System obsługuje 16 szt flag. Flagi mogą być kasowane w przerwaniach.
void TriggerEvent(uint16_t event);
Ta funkcja jest wywoływana w celu wywołania zdarzenia i usunięcia flagi zdarzenia. Może być używany jako
środki komunikacji między zadaniami lub wywołane z przerwania systemu.
void SetSemaphore(uint16_t *sem);
void ClearSemaphore(uint16_t *sem);
Te dwie funkcje współpracują ze sobą, aby kontrolować dostęp do
zasobów lub funkcji. Semafor to taki przełącznik zadań w dostępie do jakiejś funkcji lub innego zadania. Przykładowo jeśli jakaś funkcja jest wywołana lub współużytkowana przez co najmniej dwa zadania, w takim przypadku musi być jakaś kontrola dostępu aby dwa zadania równocześnie nie miały dostępu do jednej i tej samej funkcji lub zasobu. Semafory umożliwiają taką kontrolę. Jeśli semafor jest ustawiony to zadanie jest wstrzymywane , jeśli semafor skaskujemy to zadanie jest gotowe do realizacji.
Semafor binarny jest wykorzystywany do synchronizacji oraz wzajemnego wykluczania zadań.
W picRTOS funkcje semafora służą do sterowania zadaniami , które nie mogą być wywołany z peryferyjnego przerwania. We freeRTOS semafory są znacznie bardziej rozbudowane i mają możliwość współpracy z przerwaniami tak to zrozumiałem na obecnym etapie.
void CreateMessage(uint8_t MsgID, uint8_t message_size);
Bool MessageWrite(uint8_t MsgID, void *message);
Bool MessageWaiting(uint8_t MsgID);
Bool MessageRead(uint8_t MsgID, void *message);
Bool ByteWrite(uint8_t MsgID, uint8_t index, uint8_t byte);
uint8_t ByteRead(uint8_t MsgID, uint8_t index);
Te funkcje działają w połączeniu ze sobą w celu przekazania wiadomości pomiędzy zadaniami. Jest to metoda komunikacji między zadaniami. Wiadomość może mieć dowolny typ lub strukturę.
void TraceStack(uint16_t *StackTop);
void TraceTask(uint16_t TaskID, volatile uint16_t *port, uint16_t port_bit);
Funkcje te są używane podczas debugowania kodu jako pomoc w śledzeniu zajętości stosu przez poszczególne zadania.
hmm... jakby to powiedzieć i to już koniec jesli chodzi o funkcje.
I tak z tego nic nie rozumiemy jesli nie pomacamy na przykładach ....
Zreasumujmy na tym etapie co z grupsza wiemy o picRTOS. Możemy sobie wyobrazić , że jest to taka ciuchcia z wagonikami jadąca po okręgu. Na okręgu mamy jedną stację załadunkową. Ciuchcia to core odbowiedzialna za motorykę całego składu. Wagoniki to pojemniki do których wrzucamy węgiel a węgiel to nasze zadania. Na załadunek każdego wagonika w punkcie załadunkowym mamy skończony czas np 5 ms :) Jeśli uda nam się załadować węgiel poniżej 5 ms to wysyłamy komendę do ciuchci (TaskYeld()) aby skład podstawił kolejny wagonik do załadunku. Jeśli załadunek przekroczy czas 5 ms to nastąpią jakieś kombinacje na końcu składu aby nie załadowany węgiel zabrać :). Do zobrazowania przebiegu działania picRTOS bardzo by się przydał analizator stanów logicznych, który umożliwi podgląd jak to wszystko czasowo wygląda. Na razie nie mam takowego. Biblioteka ma w standardzie ustawione piny PB0...PB4 na których zachodzą zmiany stanów w tak uruchomienia zadania i zakończenia, czyli możemy monitorować naraz 5 zadań.
Istotna jeszcze rzecz, że każde zadanie ma przydzielony kawałek swojego stosu czyli jest jakby niezależnym innobytem.
Wiemy , że musimy projekt /program rozważać jak gdyby z innej perspektywy tzn dzielić funkcjonalnie na kawałki czyli zadania. Zadania mogą się ze sobą komunikować ale to nie zmienia faktu , że są funkcjonalnie niezależnymi innobytami.
Zerknijmy na przykłady dołączone do biblioteki czyli Example_1.c do Example_5.c. Pamiętajmy, że każdy z przykładów dodajemy oddzielnie do projektu i zmieniamy jego nazwę na main.c.
Example_1.c - ten przykład obrazuje jak tworzymy zadania, jak wygląda struktura programu etc czyli taka piaskownica. Mamy tutaj utworzonych 5 zadań, które zmienią tylko wartość zmiennych , delaye modelują czas trwania zadania. Wszystko tutaj jest proste klarowne i zrozumiałe. Przykład ten jest idealny do zobrazowania na analizatorze stanów logicznych.
Example_2.c - przykład pokazuje jak stosować Eventy. Event ustawiony w przerwaniu Timer2 steruje zadaniem czyli albo blokuje wykonanie zadania albo odblokowuje. Przykład prosty i czytelny.
Example_3.c - w tym przykładzie można zrozumieć działanie semaforów w picRTOS. Mamy tutaj trzy zadania które odpalają jedną i tą samą funkcję zewnętrzną. Semafor jest ustawiany i dezaktywowany w funkcji zewnętrznej. Jeśli np zadanie nr 0 odpali funkcję zewnętrzną , działania w funkcji się nie skończą a przyjdzie wywołanie tej funkcji z zadania nr 1 to dopóki nie zostanie dezaktywowany semafor ustawiony na końcu funkcji dopóty żadne inne zadanie nie otrzyma dostępu do funkcji.
Dopóki nie zobaczyłem tego przykładu nie kumałem do końca o co chodzi z tymi semaforami, brawo dla przykładu.
Example_4.c - tutaj zobaczymy jak działają wiadomości. Przesyłana wiadomość może być dowolnego typu np może to być cała struktura. Za pomocą wiadomości zadania mogą się komunikować ze sobą i wymieniać dane.
Dumałem nad tym jak wykorzystać picRTOS w praktycznym aspekcie ? Wpadłem na myśl , że niegłupim pomysłem byłoby wykorzystanie go do sterowania warstwami w wyświetlaczu znakowym LCD. Z grubsza chodzi o to , żeby stworzyć sobie kilka warstw w pamięci RAM jako oddzielne zadania i jedno zadanie , które będzie sklejać te warstwy i wysyłać do pamięci wyświetlacza LCD. Uzyskamy w ten sposób wielowątkowe sterowanie wyświetlaczem i niesamowitą elastykę. Na poszczególnych warstwach możemy aplikować np menu, czas, animację etc, warstwy mogą być widoczne lub nie nakładać się na siebie przesuwać etc. Ponieważ wszystkie operacje na warstwach następują w buforach w pamięci RAM a tylko efekt końcowy wysyłamy do LCD ,zwiększamy prędkość operacji z wyświetlaczem. Hmm... myślę , że to niegłupi pomysł.
picRTOS jest naprawdę genialnym kawałkiem zoptymalizowanego softu . Potężną zaletą jest prostota implementacji i mały rozmiar biblioteki (1kB), można z jego pomocą zrobić naprawdę niezawodnie działającą multiwątkową aplikację.
Jest idealnym sposobem na łagodne wejście w tematykę RTOS-ów. Zaczynając przygodę od np potężnego freeRTOS-a można połamać zęby przy pierwszym podejściu i się zrazić do tematu.
Kurcze naprawdę się zakochałem w tej bibliotece :) .Gorąco polecam picRTOS.
Pozdrawiam
picmajster.blog@gmail.com
Linki :
Poniżej pliki core biblioteki niezbędne do działania picRTOS. Pliki zostały przystosowane do współpracy z mikrokontrolerem PIC24HJ128GP502. Plik example_1.c to nasz main.c. Trzon biblioteki to plik asemblerowy multitask.s :
/*--------------------------------------------
Title : Header file for multitask.s V3.3
Filename : multitask.h
Copyright @ Bob The Bass 2012
----------------------------------------------*/
/*--- Frequency of Instruction Cycle Clock in Megahertz (Fcy) ---*/
#define FREQ_FCY 40.0
/*--- Task time slice in mili Seconds ---*/
#define TIME_SLICE 5.0
/*--- Macro to calculate Timer period (8:1 Prescale value) ---*/
#define PRESCALE 8.0
#define TMR_PERIOD (((TIME_SLICE)*(1000.0))/((PRESCALE)/(FREQ_FCY)))
/*--- System configuration parameters --*/
#ifndef MULTITASK_H
#define MULTITASK_H
#include "PosixTypes.h"
/*--- Function pointer type define ---*/
typedef void(*Taskptr)(void);
/*--- Multitask function prototypes ---*/
void CreateTask(Taskptr task, uint16_t stack_size);
void Multitask(uint16_t tmr_period);
void TaskSleep(uint16_t count);
void TaskYield(void);
void DisableInterrupts(void);
void EnableInterrupts(void);
/*--- Event flag functions ---*/
void WaitForEvent(uint16_t event);
void TriggerEvent(uint16_t event);
/*--- Semaphore functions ---*/
void SetSemaphore(uint16_t *sem);
void ClearSemaphore(uint16_t *sem);
/*--- Message passing functions ---*/
void CreateMessage(uint8_t MsgID, uint8_t message_size);
Bool MessageWrite(uint8_t MsgID, void *message);
Bool ByteWrite(uint8_t MsgID, uint8_t index, uint8_t byte);
Bool MessageWaiting(uint8_t MsgID);
Bool MessageRead(uint8_t MsgID, void *message);
uint8_t ByteRead(uint8_t MsgID, uint8_t index);
/*--- Debug and Trace functions ---*/
#ifdef __DEBUG
void TraceStack(uint16_t *StackTop);
void TraceTask(uint16_t TaskID, volatile uint16_t *port, uint16_t port_bit);
#endif
#endif /* Include gaurd */
/*--- End of File ---*/
/*--------------------------------------------------------------------
File Name : PosixTypes.h
Description : ISO (Posix) typedef file for Pic
: Misra Rule 6.3 (advisory)
Revision : 1.0
Date : 21/08/05
----------------------------------------------------------------------*/
#ifndef POSIXTYPES_H
#define POSIXTYPES_H
/*--- Standard type definitions. ---*/
/* Plain char, only to be used for the storage and
use of character values. Misra Rule 6.1 (Required) */
typedef unsigned char uint8_t; /* unsigned 8 bit type definition */
typedef signed char int8_t; /* signed 8 bit type definition */
typedef unsigned int uint16_t; /* unsigned 16 bit type definition */
typedef signed int int16_t; /* signed 16 bit type definition */
typedef unsigned long uint32_t; /* unsigned 32 bit type definition */
typedef signed long int32_t; /* signed 32 bit type definition */
typedef unsigned int Bool; /* Bool type definition */
/*--- Standard constant definition. ---*/
#define False ((Bool)0x0000U)
#define True ((Bool)0x0001U)
#endif
/*--- End of file. ---*/
/*-------------------------------------------------
Title : Multitasking kernel V3.3
Filename : multitask.s
Copyright @ Bob The Bass 2012
---------------------------------------------------*/
/* ONLY! uncomment this line for PIC24F types with DSRPAG and DSWPAG instead of PSVPAG */
;.equiv NO_PSV_PAGE,1
/*--- Select include files ---*/
.ifdef __dsPIC30F
.include "p30fxxxx.inc"
.equ VALID_INC,1
.equiv DSPIC_TYPE,1
.endif
.ifdef __dsPIC33F
.include "p33Fxxxx.inc"
.equ VALID_INC,1
.equiv DSPIC_TYPE,1
.endif
.ifdef __PIC24F
.include "p24Fxxxx.inc"
.equ VALID_INC,1
.endif
.ifdef __PIC24H
.include "p24Hxxxx.inc"
.equ VALID_INC,1
.endif
.ifndef VALID_INC
.error "Generic include file not found?"
.endif
/*--- Macros for Timer interrupt ---*/
.macro StartTaskTimer ;Initialise task timer
mov w0,PR1 ;TMR_PERIOD to timer period register
clr TMR1 ;Clear timer
bset IPC0,#T1IP0 ;Set timer priority to 1
bclr IPC0,#T1IP1
bclr IPC0,#T1IP2
bclr IFS0,#T1IF ;Clear interrupt flag
bset IEC0,#T1IE ;Enable interrupt
mov #0x8010,w0
mov w0,T1CON ;Start Timer, Prescale 8:1
.endm
.macro ClearInterrupt
bclr IFS0,#T1IF ;Clear interrupt flag
.endm
.macro StartTasking ;Enable Tasking interrupt
bset IEC0,#T1IE ;Set interrupt enable flag
.endm
.macro StopTasking ;Disable Tasking interrupt
bclr IEC0,#T1IE ;Clear interrupt enable flag
.endm
/*--- Global Task Functions ---*/
.global __T1Interrupt
.global _CreateTask
.global _Multitask
.global _TaskSleep
.global _TaskYield
.global _DisableInterrupts
.global _EnableInterrupts
/*--- Global Debug and Trace functions ---*/
.ifdef __DEBUG
.global _TraceTask
.global _TraceStack
.endif
/*--- Global support functions ---*/
.global _WaitForEvent
.global _TriggerEvent
.global _SetSemaphore
.global _ClearSemaphore
/*--- Global message functions ---*/
.global _CreateMessage
.global _MessageWrite
.global _ByteWrite
.global _MessageWaiting
.global _MessageRead
.global _ByteRead
/*------ Task state constants ------*/
.equiv READY, 0
.equiv BLOCKED, 1
.equiv WAITING, 2
.equiv ASLEEP, 3
/*--- Offsets into task structure ---*/
.equiv STATUS, 4
.equiv PENDING, 6
.equiv EVENTFLAGS, 8
.equiv SLEEPING, 10
.ifdef __DEBUG
.equiv TRACEPORT, 12
.equiv TRACEBIT, 14
.endif
/*--- Offsets into message structure ---*/
.equiv MSGID, 2
.equiv MSGSIZE, 4
.equiv MESSAGE, 6
/*--- Uninitialized ram allocation. ---*/
.bss
StackTop: .space 2
Critical: .space 2
/*--- Initialized ram allocation. ---*/
.data
TaskList: .int 0
MessageList: .int 0
.ifdef __DEBUG
TraceBuffer: .int 0
.endif
/*--- Program entry ---*/
.text
/*--- Create Task structure and initialise task stack ---*/
_CreateTask:
sl w1,w1 ;Allign, stack depth to bytes
mov w15,w14 ;Save stack pointer
sub w14,#4,w14 ;Point to callers return address
mov w14,w4 ;Start of free memory
add w1,w15,w15 ;Add stack size to stack pointer
mov w15,w12 ;Save top of task stack
repeat #1
mov [w14++],[w15++] ;Shift callers return address and SR
mov [w14],[w15] ;To new stack pointer
mov w4,Critical ;Use variable to save task pointer
cp0 TaskList ;First stack in list?
bra nz,1f
mov w4,TaskList ;Intialise TaskList
clr w5 ;Clear ID counter
1: mov w12,[w4++] ;Pointer to next task
mov w4,StackTop ;Save pointer to stack top
mov w4,[w4++]
mov w5,w2 ;ID counter
inc w5,w5 ;Increment ID counter
swap w2 ;ID to hi-byte
mov.b #READY,w2
mov w2,[w4++] ;Task Status to ready
add #6,w4 ;Task flags space
.ifdef __DEBUG
add #4,w4 ;Trace bits space
.endif
mov w0,[w4++] ;Pointer to task function
clr [w4++] ;High byte of function pointer
mov #36,w1 ;Register stack space
.ifdef DSPIC_TYPE
add #22,w1 ;Add dsp register stack space
.endif
add w1,w4,w4 ;Add size of registers to task structure
mov _CORCON,w1
mov w1,[w4++] ;Intialise CORCON
.ifdef NO_PSV_PAGE ;PIC24F types with no PSVPAG
mov _DSRPAG,w1
mov w1,[w4++] ;Intialise DSRPAG
mov _DSWPAG,w1
mov w1,[w4++] ;Intialise DSWPAG
.else
mov _PSVPAG,w1
mov w1,[w4++] ;Intialise PSVPAG
.endif
mov StackTop,w1 ;Get pointer to stack top
mov w4,[w1] ;Intialise stack pointer
return
/*--- Start Timer and Multitasking ---*/
_Multitask:
StartTaskTimer ;Initialise task timer
mov Critical,w2 ;Save last task address
mov TaskList,w0 ;Pointer to first Task
mov w0,[w2] ;Create linked list
.ifdef __DEBUG
rcall SetPortBit ;Set Trace bit
.endif
mov w0,StackTop ;Save Stack address
inc2 StackTop ;Point to Task structure
add #16,w0 ;Offset to Task function pointer
.ifdef __DEBUG
add #4,w0 ;Trace Port and Trace Bit
.endif
mov w0,w15 ;Initialise stack with Task function address
retfie ;Start Multitasking
/*--- Task sleep ---*/
_TaskSleep:
mov StackTop,w2 ;Load task pointer
dec2 w2,w2 ;Point to task structure
mov w0,[w2+SLEEPING] ;Save sleep time
mov #ASLEEP,w0 ;Change status to Sleeping
mov.b w0,[w2+STATUS] ;Fall through to yield
/*--- Task yield ---*/
_TaskYield:
bset SR,#5 ;Set interrupt priority level to 1
clr TMR1 ;Clear timer for next task
/*--- Task scheduler Timer interrupt ---*/
__T1Interrupt:
ClearInterrupt ;Clear interrupt flag
push SR
push w0
mov #0xe0,w0
ior.b SR ;Disable interrupts
push w1
push.d w2
push.d w4
push.d w6
push.d w8
push.d w10
push.d w12
push w14
push RCOUNT
push TBLPAG
.ifdef DSPIC_TYPE
push ACCAL
push ACCAH
push ACCAU
push ACCBL
push ACCBH
push ACCBU
push DCOUNT
push DOSTARTL
push DOSTARTH
push DOENDL
push DOENDH
.endif
push CORCON
.ifdef NO_PSV_PAGE
push DSRPAG
push DSWPAG
.else
push PSVPAG
.endif
mov StackTop,w0 ;Task top of stack
mov w15,[w0] ;Move top of stack to pointer
/*--- Task scheduler ---*/
dec2 w0,w0 ;Pointer to current task
.ifdef __DEBUG
rcall ClearPortBit ;Clear Trace bit
cp0 TraceBuffer ;Test for trace buffer
bra z,1f ;Trace buffer not allocated
sub w15,w0,w1 ;Get stack depth
rcall SaveStackDepth ;Save maximum stack depth
.endif
/*--- Loop to find next ready task ---*/
1: mov [w0],w0 ;Pointer to next task
mov.b [w0+STATUS],w1 ;Task status
cp.b w1,#READY ;Test if ready state
bra z,StartTask ;Start task
cp.b w1,#ASLEEP ;Test if sleeping
bra nz,1b
mov [w0+SLEEPING],w1 ;Get timer value
cp0 w1
bra z,2f ;Check if timed out
dec w1,w1 ;Decrement timer
mov w1,[w0+SLEEPING] ;Update timer value
bra 1b ;Next task
2: mov #READY,w1 ;Status to ready
mov.b w1,[w0+STATUS] ;Start task
/*--- Start next Task ---*/
StartTask:
.ifdef __DEBUG
rcall SetPortBit ;Set tasks trace bit
.endif
inc2 w0,w0 ;Pointer to stack top
mov w0,StackTop ;Save pointer for next time
mov [w0],w15 ;Initialise stack pointer
/*--- Restore context ---*/
.ifdef NO_PSV_PAGE
pop DSWPAG
pop DSRPAG
.else
pop PSVPAG
.endif
pop CORCON
.ifdef DSPIC_TYPE
pop DOENDH
pop DOENDL
pop DOSTARTH
pop DOSTARTL
pop DCOUNT
pop ACCBU
pop ACCBH
pop ACCBL
pop ACCAU
pop ACCAH
pop ACCAL
.endif
pop TBLPAG
pop RCOUNT
pop w14
pop.d w12
pop.d w10
pop.d w8
pop.d w6
pop.d w4
pop.d w2
pop.d w0
pop SR
retfie
/*--- Disable interrupts ---*/
_DisableInterrupts:
push w0
mov SR,w0
mov w0,Critical ;Save status register
mov #0xe0,w0 ;Set interrupt bits
ior.b SR ;Disable interrupts
pop w0
return
/*--- Enable interrupts ---*/
_EnableInterrupts:
push w0
mov Critical,w0 ;Restore status register
mov w0,SR
pop w0
return
/*--- Set Wait for event flag ---*/
_WaitForEvent:
StopTasking ;Stop Task switching
mov StackTop,w2 ;Load task pointer
dec2 w2,w2 ;Point to task structure
mov w0,[w2+EVENTFLAGS] ;Save Flags
mov #WAITING,w0
mov.b w0,[w2+STATUS] ;Change status to Waiting
StartTasking ;Resume task switching
rcall _TaskYield ;Force task switch
return
/*--- Trigger event ---*/
_TriggerEvent:
StopTasking ;Stop Task switching
mov StackTop,w1 ;Load running task pointer
dec2 w1,w1 ;Pointer to task struture
mov w1,w2 ;w2 as task list end
mov [w1],w1 ;Next task in list
com w0,w0 ;Flags to clear mask
1: mov.b [w1+STATUS],w3 ;Get task status
cp.b w3,#WAITING ;Test if waiting
bra nz,2f ;Not waiting
mov [w1+EVENTFLAGS],w3 ;Get task flags
and w0,w3,w3 ;Mask flags
mov w3,[w1+EVENTFLAGS] ;Save result
bra nz,2f ;Test if all clear
mov #READY,w3 ;All flags clear
mov.b w3,[w1+STATUS] ;Change status to Ready
2: mov [w1],w1 ;Next task in list
cp w1,w2 ;Task list end?
bra nz,1b
StartTasking ;Resume task switching
return
/*--- Set Semaphore ---*/
_SetSemaphore:
StopTasking ;Stop Task switching
mov StackTop,w1 ;Load running task pointer
dec2 w1,w1 ;Pointer to current task
mov w0,[w1+PENDING] ;Semaphore passed in w0
cp0 [w0] ;Dereference pointer
bra z,2f ;Semaphore is free?
mov #BLOCKED,w2 ;Semaphore is not free
mov.b w2,[w1+STATUS] ;Change status to Blocked
1: StartTasking ;Restart kernel
rcall _TaskYield ;Force task switch
StopTasking ;Stop Task switching
cp0 [w0] ;Check if semaphore clear
bra z,2f ;Semaphore is clear
mov #BLOCKED,w2 ;Semaphore is not free
mov.b w2,[w1+STATUS] ;Keep status Blocked
bra 1b ;Task yield
2: inc [w0],[w0] ;Set Semaphore
StartTasking ;Resume Task switching
return
/*--- Clear Semaphore ---*/
_ClearSemaphore:
StopTasking ;Stop Task switching
mov StackTop,w1 ;Load running task pointer
dec2 w1,w1 ;Pointer to current task
mov w1,w4 ;Save as task list end
mov [w1],w1 ;Pointer to next task
clr [w0] ;Clear Semaphore
1: mov.b [w1+STATUS],w2 ;Task state
cp.b w2,#BLOCKED ;Is task blocked?
bra nz,2f ;Not blocked
mov [w1+PENDING],w2 ;Task semaphore
cp w0,w2 ;This Semaphore?
bra nz,2f ;Not this semaphore
mov #READY,w2 ;Change status to Ready
mov.b w2,[w1+STATUS]
2: mov [w1],w1 ;Pointer to next task
cp w1,w4 ;Task list end?
bra nz,1b
StartTasking ;Resume Task switching
return
/*--- Create Message Box ---*/
_CreateMessage:
ze w1,w1 ;Clear high byte of message size
btsc w1,#0 ;Test message size for allignment
inc w1,w1 ;Allign, make even
add w1,#6,w3 ;Add size of message structure
mov SPLIM,w2 ;Stack limit
inc2 w2,w2 ;Last message address
sub w2,w3,w2 ;Subtract from last message address
mov MessageList,w4 ;Pointer to next message
mov w4,[w2] ;Save pointer
mov w2,MessageList ;Update start of list
dec2 w2,w4 ;Set new stack limit
mov w4,SPLIM
ze w0,w0 ;Clear high byte of Message ID
mov w0,[++w2] ;Save message ID
mov w1,[++w2] ;Save message size
return
/*--- Write message ---*/
_MessageWrite:
rcall FindMessage ;Get pointer to message
push SR ;Save status register
rcall Message_SetUp ;Set up for write
repeat w3 ;Message size counter
mov.b [w1++],[w0++] ;Message in w1
mov #1,w0 ;Set write flag
mov.b w0,[w4+MSGID+1] ;Save flag
pop SR ;Restore status register
return
/*--- Write a byte to the message buffer ---*/
_ByteWrite:
rcall FindMessage ;Get pointer to message
push SR ;Save status register
rcall Message_SetUp ;Set up for write
cp.b w1,w3 ;Check index within bounds
bra gtu,1f ;Index out of bounds
add.b w1,w0,w0 ;Add index to message pointer
mov.b w2,[w0] ;Write byte
mov #1,w0 ;Return 1
bra 2f
1: clr w0 ;Byte not written
2: pop SR ;Restore status register
return
/*--- Read message ---*/
_MessageRead:
rcall FindMessage ;Get pointer to message
push SR ;Save status register
rcall Message_SetUp ;Set up for read
repeat w3 ;Message size counter
mov.b [w0++],[w1++] ;Move message to w1
clr w3 ;Clear read flag
mov.b w3,[w4+MSGID+1] ;Save flag
mov #1,w0 ;Return 1
pop SR ;Restore status register
return
/*--- Read a byte from the message buffer ---*/
_ByteRead:
rcall FindMessage ;Get pointer to message
push SR ;Save status register
rcall Message_SetUp ;Set up for read
cp.b w1,w3 ;Check index within bounds
bra gtu,1f ;Index out of bounds
add.b w1,w0,w0 ;Add index to message pointer
mov.b [w0],w0 ;Read byte
bra 2f
1: clr w0 ;Byte not read
2: pop SR ;Restore status register
return
/*--- Check message box status ---*/
_MessageWaiting:
rcall FindMessage ;Get pointer to message
clr w0
mov.b [w4+MSGID+1],w0 ;Message flag value
return
/*--- Find message from ID ---*/
FindMessage:
mov MessageList,w4 ;Pointer to message list
1: mov.b [w4+MSGID],w3 ;Message ID
cp.b w0,w3 ;This message?
bra z,2f ;Message found
mov [w4],w4 ;Next message in list
cp0 w4 ;List end?
bra nz,1b
pop.d w0 ;skip return address
clr w0 ;Message not found
2: return
/*--- Set up message size and pointers ---*/
Message_SetUp:
mov #0xe0,w0
ior.b SR ;Disable interrupts
add w4,#MESSAGE,w0 ;Pointer to message
mov [w4+MSGSIZE],w3 ;Message size
dec w3,w3 ;Adjust size for repeat loop
return
/*--- Debug and trace functions ---*/
.ifdef __DEBUG
/*--- Initialise Trace buffer address ---*/
_TraceStack:
mov w0,TraceBuffer ;Pointer to Stack Trace array
return
/*--- Save maximum used stack depth ---*/
SaveStackDepth:
mov [w0+STATUS],w2
swap w2 ;Get task ID
ze w2,w2 ;Clear Hi-byte
sl w2,w2 ;Allign
mov TraceBuffer,w3
add w2,w3,w3 ;Buffer address
mov [w3],w2 ;Last used stack depth
sl w2,w2 ;Change to bytes
cp w1,w2 ;Current used stack depth
bra leu,1f ;Compare
asr w1,#1,w1 ;If greater change to depth size
mov w1,[w3] ;Save largest stack depth
1: return
/*--- Initialise Task trace port and bit ---*/
_TraceTask:
mov TaskList,w3 ;First task address
cp0 w0 ;Task ID
bra z,2f
1: mov [w3],w3 ;Next task in list
dec w0,w0
bra nz,1b
2: mov w1,[w3+TRACEPORT] ;Address of Port
mov #1,w4
sl w4,w2,w4 ;Port trace Bit
mov w4,[w3+TRACEBIT]
return
/*--- Set Trace port bit ---*/
SetPortBit:
mov [w0+TRACEPORT],w1 ;Address of Port
cp0 w1
bra z,1f ;Test if valid
mov [w0+TRACEBIT],w2 ;Port Bit
mov [w1],w3 ;Port value
ior w3,w2,w3 ;Set bit with mask
mov w3,[w1] ;Update port value
1: return
/*--- Clear Trace port bit ---*/
ClearPortBit:
mov [w0+TRACEPORT],w1 ;Address of Port
cp0 w1
bra z,1f ;Test if valid
mov [w0+TRACEBIT],w2 ;Port Bit
com w2,w2 ;Clear bitmask
mov [w1],w3 ;Port value
and w3,w2,w3 ;Clear bit with mask
mov w3,[w1] ;Update port value
1: return
.endif ;End of conditional debug compile
.end ;End of program code
/*--- End of file. ---*/
/*--------------------------------------------------------------------
Title : Create Task Demo
Filename : Example_1.c
Copyright @ Bob The Bass 2012
--------------------------------------------------------------------*/
/*--- Using a Pic24HJ128GP502 ---*/
#include "xc.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h> /*dyrektywy uint8_t itp*/
#include <string.h>
#include "ustaw_zegar.h" /*tutaj m.in ustawione FCY*/
#include <libpic30.h> /*dostep do delay-i,musi byc po zaincludowaniu ustaw_zegar.h*/
#include "multitask.h"
/*--- Configuration fuses ---*/
/*--- Trace maximum stack usage ---*/
#ifdef __DEBUG
#define NUM_TASKS 5U
uint16_t UsedStack[NUM_TASKS];
#endif
/*--- Task function prototypes ---*/
void task_0(void);
void task_1(void);
void task_2(void);
void task_3(void);
void task_4(void);
/*--- Local function prototypes ---*/
void init_io(void);
void Delay(uint16_t delay);
/*--- Global variables ---*/
uint16_t var0 = 0;
uint16_t var1 = 0;
uint16_t var2 = 0;
uint16_t var3 = 0;
uint16_t var4 = 0;
/*--- Program Entry ---*/
int main(void)
{
ustaw_zegar(); /*odpalamy zegar wewnetrzny na ok 40MHz*/
// __delay_ms(50); /*stabilizacja napiec*/
init_io();
CreateTask(task_0, 56); /* All task create functions must be called from main() */
CreateTask(task_1, 56); /* in one unbroken sequence */
CreateTask(task_2, 56);
CreateTask(task_3, 56);
CreateTask(task_4, 56);
#ifdef __DEBUG
TraceStack(&UsedStack[0]);
TraceTask(0, &PORTB, 0);
TraceTask(1, &PORTB, 1);
TraceTask(2, &PORTB, 2);
TraceTask(3, &PORTB, 3);
TraceTask(4, &PORTB, 4);
#endif
Multitask(TMR_PERIOD);
return 0;
}
/*--- Task 0 ---*/
void task_0(void)
{
while(1)
{
Delay(5);
var0++;
}
}
/*--- Task 1 ---*/
void task_1(void)
{
while(1)
{
Delay(5);
TaskSleep(2);
var1++;
}
}
/*--- Task 2 ---*/
void task_2(void)
{
while(1)
{
Delay(15);
var2++;
}
}
/*--- Task 3 ---*/
void task_3(void)
{
while(1)
{
Delay(5);
var3++;
}
}
/*--- Task 4 ---*/
void task_4(void)
{
while(1)
{
Delay(20);
var4--;
}
}
/*--- Re-Entrant delay function ---*/
void Delay(uint16_t delay)
{
uint16_t timer;
while(delay--)
{
timer = 1000;
while(timer--){
Nop();
}
}
}
/*--- Initialise io port ---*/
void init_io(void)
{
AD1PCFGL = 0x1E3F;
PORTB = 0;
TRISB = 0; /* All outputs */
}
/*--- Stack underflow/overflow trap ---*/
#ifdef __DEBUG
void __attribute__((__interrupt__, no_auto_psv)) _StackError(void)
{
for(;;){
Nop();
}
}
/*--- Address error trap ---*/
void __attribute__((__interrupt__, no_auto_psv)) _AddressError(void)
{
for(;;){
Nop();
}
}
#endif
/*--- End of File ---*/
Jestem pod wrażeniem. Bardzo fajny wpis.
OdpowiedzUsuńDziękuję za uznanie , sam z ciekawością przeczytałem po 3 latach ten artykuł.
OdpowiedzUsuńInny ciekawy small RTOS dla MCU PIC - http://wiki.pic24.ru/doku.php/en/tnkernel/ref/intro z pełną dokumentacją.
OdpowiedzUsuń