1. Arduino 에 freeRTOS 설치하기 및 사용하기
들어가기 전
임베디드 SW에서는 크게 3가지로 나눌 수 있습니다.
첫 번째는 운영체제가 없이 사용하는 것으로 가벼우며, 직접 일처리에 대한 순서를 지정해줄 수 있습니다.
두 번째는 Real Time OS를 사용하는 것으로 우선순위로 일을 처리합니다.
세 번째는 Linux 혹은 범용 운영체제를 사용하는 것으로 한 번에 여러 일을 처리합니다.
라즈베리파이의 경우 RAM이 2GB 이상일 경우 Ubuntu 22.04를 설치해서 사용할 수 있으며, 아두이노의 경우에는 freeRTOS라고하는 운영체제를 다운로드 받아 사용할 수 있습니다.
아두이노에 freeRTOS 설치
이번시간에는 아두이노에 freeRTOS 운영체제를 설치하는 것을 목표로 진행하도록 하겠습니다.
우선 아두이노용 Free RTOS를 설치하기 위해서는 라이브러를 다운로드해주어야합니다.
깃헙에서 가장 인기가 좋은 파일을 사용하도록 합시다.
https://github.com/search?q=Arduino_FreeRTOS
https://github.com/feilipu/Arduino_FreeRTOS_Library
해당 파일을 Download zip으로 다운로도 한 후에
압축을 해제하지 말고, ~/arduino 폴더에 library 라는 폴더를 만들어주고 이 안에 넣어줍니다.
(별 의미는 없지만, 그냥 모아보기 쉽게 해당 위치로 넣었습니다.)
그리고 arduino ide을 실행한 후에 sketch -> include library -> Add .ZIP Library 를 클릭한 후 zip파일을 지정해줍시다.
이렇게 표시되면 성공입니다.
아두이노 FreeRTOS 사용하기
https://feilipu.me/2015/11/24/arduino_freertos/
이를 만들어준 분께서 해당 블로그에 설명을 해주셨습니다. 이를 보면서 접근을 해보도록 합시다.
FreeRTOS에는 스케줄러를 트리거하여 어떤 작업이 CPU를 사용해야하는지 확인하고 동등한 우선 순위 작업간에 처리 시간을 공정하게 분배하기 위한 인터럽트 타이머가 필요하다고 합니다.
아두이노에는 보통 AVR ATMega 장치에 128kHz 내부 발진기에 의해 구동되는 감시 타이머가 있어서. 이를 통해서 타이머를 사용할 수 있다고 합니다.
아두이노 코드의 동작과정
보통의 아두이노 코드는 심플하게 2개의 함수로 구성됩니다.
setup과 loop입니다. 위 함수는 .ino 파일에 작성되며 이를 전송하게 되면 Arduino 라이브러리에 있는 main()함수와 연결됩니다. 그래서 보드가 시작되면 initVariant()라고 하는 함수가 먼저 호출됩니다. 이 함수는 아두이노 프레임워크 내에서 내부적으로 사용되는 함수로 이 함수는 아두이노의 시작과 함께 호출되는 초기화 함수입니다. 주요한 작업 중 하나는 C/C++ 런타임 라이브러리를 초기화하는 것입니다.
이 함수의 호출이 완료되면 위의 setup() 함수가 호출됩니다. setup함수는 아두이노가 실행될 때, 한 번 실행되어 초기화 작업, 핀 모드 설정, 라이브러리 초기화 등의 작업을 수행하는 데, 사용됩니다. 그 후에 loop 함수를 호출해서 작업을 하게됩니다.
setup작업이 끝나게 되면 loop()함수가 반복적으로 실행되게 됩니다. loop함수를 통해 반복적인 작업을 하게되는 것입니다.
main함수가 라이브러리 형태로 제공이 되니, freeRTOS 환경을 쉽게 아두이노에 적용할 수 있다고 블로그는 설명합니다. 블로그의 설명에 의하면 FreeRTOS를 스케줄러로 시작하면 아두이노의 실행 환경이 FreeRTOS로 전환되고, intiVariant() 함수는 아두이노 프레임워크의 초기화 작업이 아닌 FreeRTOS의 초기화 작업으로 대체됩니다.
보통 FreeRTOS에서 아무 작업을 수행하고 있지 않으면, 대기 상태에 들어가 시스템 자원을 절약합니다. 그런데, 일부 시나리오에서는 FreeRTOS 유휴 작업이 실행될 때마다 Loop()함수를 실행하는 것이 유용할 수 있습니다.
그래서 FreeRtos는 위와 같은 요구사항을 지원하기 위해 loop함수를 사용합니다. FreeRTOS 라이브러리의 VariantHooks.cpp 에 loop함수가 정의되어 있습니다 .
loop의 주기는 스케줄러 인터럽트에 의해 제어되는데, 스케줄러 인터럽트는 FreeRTOS가 실행되는 동안 발생하는 인터럽트로 주기적으로 발생합니다. 여기서 스케줄러 인터럽드의 주기를 조정해서 loop 함수의 호출 주기를 설정할 수 있습니다.
FreeRTOS 사용해보기
다음처럼 나오면 됩니다. 이를 이제 가지고 코딩을 하도록 하겠습니다.
그러면 sketch -> include Library -> freeRTOS 선택 그러면 다음과 같은 코드가 등장하게 됩니다.
그러면 이제 예시코드를 작성해보도록 합시다.
아두이노의 기본 LED를 이용해서 확인하려고 하였는데, 문제가 보여서 LED를 연결하여 실험을 진행합니다.
대충 8번포트에 연결한 LED입니다. 코드를 조금 수정하도록 하죠.. Idle 상태에서는 LOW를 계속 주도록 하고, Task1은 LED를 ON시키고, Task2는 LED를 OFF 시킵니다.
#include <Arduino_FreeRTOS.h>
void setup()
//Initialize the Serial Monitor with 9600 baud rate
{
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(8, OUTPUT);
xTaskCreate(MyTask1, "Task1", 100, NULL, 2, NULL);
xTaskCreate(MyTask2, "Task2", 100, NULL, 1, NULL);
xTaskCreate(MyIdleTask, "IdleTask", 100, NULL, 0, NULL);}
void loop()
{
//There is no instruction in the loop section of the code.
// Because each task executes on interrupt after specified time
}
//The following function is Task1. We display the task label on Serial monitor.
static void MyTask1(void* pvParameters)
{
while(1)
{
digitalWrite(8,HIGH);
Serial.println(F("Task1"));
vTaskDelay(5000/portTICK_PERIOD_MS);
}
}
//Similarly this is task 3
static void MyTask2(void* pvParameters)
{
while(1)
{
digitalWrite(8,LOW);
Serial.println(F("Task2"));
vTaskDelay(5000/portTICK_PERIOD_MS);
}
}
//This is the idle task which has the lowest priority and calls when no task is running.
static void MyIdleTask(void* pvParameters)
{
while(1)
{
digitalWrite(8,LOW);
Serial.println(F("Idle state"));
delay(500);
}
}
대충 LED가 task1 실행에 맞춰 불이들어오고, task2 실행에 맞춰 불이 꺼지는 것을 알 수 있습니다.
잠시 위의 코드를 조금 살펴보도록 합시다.
다음의 글에서 RTOS에 대해 자세히 정리할 예정이지만, 여기서 맛보기로 RTOS를 들어가봅시다.
#include <Arduino_FreeRTOS.h>
void setup()
//Initialize the Serial Monitor with 9600 baud rate
{
Serial.begin(9600);
pinMode(LED_BUILTIN, OUTPUT);
pinMode(8, OUTPUT);
xTaskCreate(MyTask1, "Task1", 100, NULL, 2, NULL);
xTaskCreate(MyTask2, "Task2", 100, NULL, 1, NULL);
xTaskCreate(MyIdleTask, "IdleTask", 100, NULL, 0, NULL);}
RTOS real time OS는 우선순위에 따라서 일을 처리하는 운영체제입니다. 그리고 Task란 RTOS에서는 최소한의 실행단위입니다. 범용 운영체제의 스레드와는 다른 개념입니다.
여튼 RTOS는 이 Task에 우선순위를 부여하여 작업을 처리합니다. 여기서 Task1은 LED를 켜는 일을 처리합니다. Task2는 LED를 끄는 일을 처리합니다.
여기서 우선순위는 숫자가 높은 것입니다. 그래서 MyTask1이 2로 가장 높은 우선순위를 가집니다.
void loop()
{
//There is no instruction in the loop section of the code.
// Because each task executes on interrupt after specified time
}
loop가 비어있는 이유는 인터럽트의 느낌으로 task가 실행된다고 하는데, chatgpt에 의하면 이는 틀리다고 합니다. 인터럽트의 개념이 아니라
RTOS 운영체제의 스케줄러에 의해 각각의 테스크를 실행하게 되니 loop 함수안에 코드를 적을 필요가 없다는 뜻인거 같습니다.
그러면 Task1을 한 번 살펴보겠습니다.
static void MyTask1(void* pvParameters)
{
while(1)
{
digitalWrite(8,HIGH);
Serial.println(F("Task1"));
vTaskDelay(5000/portTICK_PERIOD_MS);
}
}
Task1의 경우입니다. 여기서 주목할 것은 vTaskDelay라는 부분입니다.
이게 없다면 어떤일이 벌어질까요? Task1은 우선순위가 가장높기 때문에 Task1을 완료하면 바로 Task1이 다시 실행됩니다.
실험을 한 번 해보도록하죠.
다음처럼 Task1만 실행됩니다. 이처럼 vTaskDelay의 역할은 Task1의 일처리가 완료되었고, Task1은 잠시 쉬는 시간을 주는 것입니다.
그러면 스케줄러는 Task1은 쉬는 상태이니 그 다음 우선순위를 가지는 Task2로 가게 됩니다.
RTOS는 이런식으로 우선순위를 기반으로 테스크 스케줄링을 관리합니다.
우선 다음 시간에 임베디드와 RTOS에 대한 기초 개념을 더 깊게 파보도록 하겠습니다.