Projekt oparty został na systemie czasu rzeczywistego Keil RTX w wersji 5 (RTX5) implementującego CMSIS-RTOS2 jako natywny interfejs RTOS dla urządzeń opartych na procesorach Arm Cortex-M.
Projekt do pobrania z: https://github.com/PiotrPoterala/stm32f429i-disco_stepper_motor
oraz biblioteki: https://github.com/PiotrPoterala/PP_Library
Utworzenie projektu opartego na RTX5
- Stworzyć nowy projekt i wybrać mikrokontroler, pod który będzie pisane oprogramowanie,
- W oknie Menage Run-Time Enviroment zaznaczyć Device::Startup, CMSIS::CORE oraz CMSIS::RTOS2(API)::KEIL RTX5,
- Kliknąć OK i upewnić się, że w drzewie projektu pojawiły się odpowiednie pliki w tym RTX_Config.h oraz RTX_Config.c
- Skonfigurować RTOS’a przy pomocy RTX_Config.h. Na potrzeby opisywanego projektu należy zwiększyć:
- rozmiar globalnej pamięci dynamicznej do 32kB,
- częstotliwość taktowania jądra do 100kHz (domyślnie jest 1kHz),
- domyślny rozmiar stosu zarezerwowany dla danego wątku do 1kB.
Widok pliku RTX_Config.h po przejściu do zakładki Configuration Wizard (dostępna w dolnym, lewym rogu okna edycji pliku):
- Skonfigurować startup_stm32f429xx.s. Plik służy do ustawienia:
- rozmiaru stosu używanego przez wyjątki i procedury obsługi przerwań (ISR),
- rozmiaru sterty używanej przez funkcje alokacji pamięci (ponieważ w projekcie wykorzystywany jest dynamiczny przydział pamięci do tworzenie egzemplarzy obiektów, należy zwiększyć rozmiar sterty do co najmniej 1kB) .
startup_stm32f429xx.s
; <h> Heap Configuration
; <o> Heap Size (in Bytes) <0x0-0xFFFFFFFF:8>
; </h>
Heap_Size EQU 0x00001024
AREA HEAP, NOINIT, READWRITE, ALIGN=3
__heap_base
Heap_Mem SPACE Heap_Size
__heap_limit
Uruchomienie systemu
W celu zainicjowania pracy RTX5 funkcja main() powinna implementować następujące elementy w podanej kolejności:
- Konfiguracja oraz aktualizacja zegara rdzenia systemu przy pomocy odpowiedniej funkcji CMSIS-Core (Cortex-M): wywołanie SystemCoreClockUpdate() wewnątrz funkcji RCC_Config(),
- Inicjalizacja i konfiguracja sprzętu, w tym urządzeń peryferyjnych, pamięci, pinów i systemu przerwań,
- Inicjalizacja jądra CMSIS-RTOS: osKernelInitialize,
- Utworzenie wątków bezpośrednio w main(),
- Uruchomienie scheduler’a RTOS: osKernelStart.
main.cpp
int main (void) {
//1) Configuration clocks and update the system core clock using the respective CMSIS-Core (Cortex-M)
RCC_Config();
//2) Initialization and configuration of hardware including peripherals, memory, pins and the interrupt system.
GPIO_Config();
NVIC_Config();
USART_Config();
//3) Initialize CMSIS-RTOS
osKernelInitialize();
...
//4) Create threads
Init_vSecondThread(osPriorityBelowNormal);
Init_vRealizationFunctionThread(osPriorityNormal);
Init_vReceiveAndInterpretDataFromComUartThread (osPriorityHigh);
//5) Start the RTOS scheduler
osKernelStart();
for (;;) {}
}
Wątki
W projekcie utworzono trzy wątki:
- vSecondThread: odpowiada za mruganie diodą z częstotliwością 1Hz. Mając najniższy priorytet, pełni dwie funkcje: sygnalizuje funkcjonowanie systemu oraz poprawność jego działania (jest najprostszą formą sprawdzenia czy, żaden z wątków nie został „zagłodzony”),
- vReceiveAndInterpretDataFromComUartThread: odpowiada za odbiór danych z portu USART, ich interpretację oraz przesłanie (z wykorzystaniem kolejki taskCommunicationQueues) do wątku „funkcyjnego”. Do vReceiveAndInterpretDataFromComUartThread, w pierwszej kolejności, trafiają AT komendy (przesyłane z PC) będące dla omawianego projektu formą interfejsu użytkownika,
- vRealizationFunctionThread: odpowiada za realizację podstawowych zadań projektu, a więc za obrót wirnika silnika o określony kąt, z określoną trajektorią prędkości obrotowej.
Aby wygenerować plik z szablonem wątku należy kliknąć prawym klawiszem na Source Group 1 w drzewie projektu, a następnie wybrać Add New Item to group. W pojawiającym się oknie, wybrać User Code Template -> CMSIS-RTOS2 Thread, wpisać nazwę tworzonego szablonu i kliknąć Add.
vSecondThread.cpp
#include "cmsis_os2.h" // CMSIS RTOS header file
#include "input_signals.h"
#include "RTX_Config.h"
#define FREQUENCY_OF_SECOND_THREAD 1
osThreadId_t tid_vSecondThread; // thread id
void vSecondThread (void *argument); // thread function
int Init_vSecondThread (osPriority_t priority) {
const osThreadAttr_t thread_attr = {
.stack_size = 256,
.priority = priority
};
tid_vSecondThread = osThreadNew(vSecondThread, NULL, &thread_attr);
osThreadSetPriority (tid_vSecondThread, priority);
if (tid_vSecondThread == NULL) {
return(-1);
}
return(0);
}
void vSecondThread (void *argument) {
while (1) {
PIN_TOG(PORT_LED, LED1);
osDelay(OS_TICK_FREQ/FREQUENCY_OF_SECOND_THREAD);
}
}