2016년 8월 9일 화요일

mbed RTOS

Overview


mbed RTOS는 사실 Keil RTX 코드의 C++ wrapper이다. Keil RTX에 관한 더 상세한 내용은 the Keil CMSIS-RTOS tutorialthe element14 introduction to Keil RTX 를 참고하면 된다. 이 자료들은 RTOS의 일반적인 원리를 소개하는데에도 사용될 수 있다. 이 가이드를 이해하기 위해서는 RTOS의 기본 컨셉에 익숙해지는게 중요하다.

mbed RTOS 코드는 mbed-os repository 의 rtos/rtos 서브디렉토리에서 찾을 수 있다.

Thread 

Thread 클래스는 시스템에서 스레드를 정의, 생성, 제어 할 수 있게 해 준다.

Thread는 다음의 상태를 가질 수 있다.



* Running: 현재 실행중인 스레드. 한번에 한개의 스레드만 이 상태에 있을 수 있음
* Ready: 실행할 준비가 된 스레드. 일단 running 스레드가 terminated 되거나 또는 waiting이 되면 ready 스레드들 중에 가장 높은 우선순위를 가진 스레드가 running 스레드가 됨
* Waiting: 이벤트가 발생하기를 기다리는 스레드
* Inactive: 만들어지지 않았거나 terminate된 스레드. 이 스레드들은 일반적으로 시스템 자원을 소비하지 않음

main() 함수





main 함수는 특별한 스레드 함수로 시스템 초기화시에 시작되고 최초 우선순위는 osPriorityNormal 이 된다. RTOS에 의해 가장 먼저 시작되는 스레드이다.




Thread example



아래 코드는 두개의 LED를 깜빡이기 위해 두개의 스레드를 사용한다. 첫번째 스레드는 자동으로 만들어져 main 함수를 실행한다. 두번째 스레드는 main 함수 안에서 명시적으로 만들어진다.


main.cpp

#include "mbed.h"
#include "rtos.h"

DigitalOut led1(LED1);
DigitalOut led2(LED2);

void led2_thread(void const *args) {
    while (true) {
        led2 = !led2;
        Thread::wait(1000);
    }
}

int main() {
    Thread thread(led2_thread);
   
    while (true) {
        led1 = !led1;
        Thread::wait(500);
    }
}
#include "mbed.h"
#include "rtos.h"

DigitalOut led1(LED1);
DigitalOut led2(LED2);

void led2_thread(void const *args) {
    while (true) {
        led2 = !led2;
        Thread::wait(1000);
    }
}

int main() {
    Thread thread(led2_thread);
   
    while (true) {
        led1 = !led1;
        Thread::wait(500);
    }
}



MUTEX

Mutex는 스레드들의 실행시 동기화를 위해 사용된다. 예를 들어 공유 자원에 동시에 억세스 하는걸 막기 위해 사용할 수 있다.

* 경고: ISR
현재 버젼의 mbed OS에서 Mutex 메소드는 ISR(Interrupt Service Routine)에서 호출될 수 없다. 만일 ISR 내에서 mutex를 사용하기 위해 시도하면 아무것도 일어나지 않는다. mutex를 lock하려고 시도하면 lock이 실제로 lock되어 있는지 아니면 사용 가능한지 여부에 상관 없이 곧바로 lock이 성공한다. 즉 ISR내에서 mutex lock을 얻으면 스레드 동기화 메커니즘을 깨 버리게 되어 정상적인 경우 안전한 코드가 비정상적으로 동작하게 된다. 차후 버젼의 mbed OS에서는 warning을 주고, 최종적으로는 이런 일이 발생하는걸 금지시킬 것이다.


Mutex example

printf()를 보호하기 위해 Mutex를 사용

main.cpp

#include "mbed.h"
#include "rtos.h"

Mutex stdio_mutex;

void notify(const char* name, int state) {
    stdio_mutex.lock();
    printf("%s: %d\n\r", name, state);
    stdio_mutex.unlock();
}

void test_thread(void const *args) {
    while (true) {
        notify((const char*)args, 0); Thread::wait(1000);
        notify((const char*)args, 1); Thread::wait(1000);
    }
}

int main() {
    Thread t2(test_thread, (void *)"Th 2");
    Thread t3(test_thread, (void *)"Th 3");
   
    test_thread((void *)"Th 1");
}
#include "mbed.h"
#include "rtos.h"

Mutex stdio_mutex;

void notify(const char* name, int state) {
    stdio_mutex.lock();
    printf("%s: %d\n\r", name, state);
    stdio_mutex.unlock();
}

void test_thread(void const *args) {
    while (true) {
        notify((const char*)args, 0); Thread::wait(1000);
        notify((const char*)args, 1); Thread::wait(1000);
    }
}

int main() {
    Thread t2(test_thread, (void *)"Th 2");
    Thread t3(test_thread, (void *)"Th 3");
   
    test_thread((void *)"Th 1");
}


* 주의 : C standard library Mutex
ARM 표준 라이브러리는 이미 stdio에 대한 억세스를 보호하기 위해 mutex를 사용하고 있다. 그러므로 LPC1768에서 위의 예제는 필요 없다. 하지만 LPC11U24의 경우는 디폴트로 stdio mutex를 지원하지 않기 때문에 위의 예제가 필요하게 된다.

* 경고 : ISR 내에서 stdio, malloc, new
ARM C standard library의 mutex 때문에 ISR내에서는 stdio(printf, putc, getc 등), malloc, new를 사용할 수 없다.

Semaphore


Semaphore는 스레드들이 특정 타입의 공유자원 풀에 억세스 하는걸 관리한다.



main.cpp

#include "mbed.h"
#include "rtos.h"

Semaphore two_slots(2);

void test_thread(void const *name) {
    while (true) {
        two_slots.wait();
        printf("%s\n\r", (const char*)name);
        Thread::wait(1000);
        two_slots.release();
    }
}

int main (void) {
    Thread t2(test_thread, (void *)"Th 2");
    Thread t3(test_thread, (void *)"Th 3");
   
    test_thread((void *)"Th 1");
}
#include "mbed.h"
#include "rtos.h"

Semaphore two_slots(2);

void test_thread(void const *name) {
    while (true) {
        two_slots.wait();
        printf("%s\n\r", (const char*)name);
        Thread::wait(1000);
        two_slots.release();
    }
}

int main (void) {
    Thread t2(test_thread, (void *)"Th 2");
    Thread t3(test_thread, (void *)"Th 3");
   
    test_thread((void *)"Th 1");


Signals


각 스레드는 시그널을 기다리고 이벤트 발생을 통보할 수 있다.

main.cpp

#include "mbed.h"
#include "rtos.h"

DigitalOut led(LED1);

void led_thread(void const *argument) {
    while (true) {
        // Signal flags that are reported as event are automatically cleared.
        Thread::signal_wait(0x1);
        led = !led;
    }
}

int main (void) {
    Thread thread(led_thread);
   
    while (true) {
        Thread::wait(1000);
        thread.signal_set(0x1);
    }
}

 

Queue and MemoryPool

Queue


Queue는 생산자 스레드가 소비자 스레드에게 보낼 데이터에 대한 포인터를 큐에 집어넣을 수 있게 해 준다.

Queue queue;

message_t *message;

queue.put(message);

osEvent evt = queue.get();
if (evt.status == osEventMessage) {
    message_t *message = (message_t*)evt.value.p;Queue queue;

message_t *message;

queue.put(message);

osEvent evt = queue.get();
if (evt.status == osEventMessage) {
    message_t *message = (message_t*)evt.value.p;



MemoryPool


MemoryPool 클래스는 고정된 크기의 메모리 풀을 정의하고 관리하는데 사용된다.

MemoryPool mpool;

message_t *message = mpool.alloc();

mpool.free(message);MemoryPool mpool;

message_t *message = mpool.alloc();

mpool.free(message);


Queue and MemoryPool example


#include "mbed.h"
#include "rtos.h"

typedef struct {
    float    voltage;   /* AD result of measured voltage */
    float    current;   /* AD result of measured current */
    uint32_t counter;   /* A counter value               */
} message_t;

MemoryPool<message_t, 16> mpool;
Queue<message_t, 16> queue;

/* Send Thread */
void send_thread (void const *args) {
    uint32_t i = 0;
    while (true) {
        i++; // fake data update
        message_t *message = mpool.alloc();
        message->voltage = (i * 0.1) * 33;
        message->current = (i * 0.1) * 11;
        message->counter = i;
        queue.put(message);
        Thread::wait(1000);
    }
}

int main (void) {
    Thread thread(send_thread);
   
    while (true) {
        osEvent evt = queue.get();
        if (evt.status == osEventMessage) {
            message_t *message = (message_t*)evt.value.p;
            printf("\nVoltage: %.2f V\n\r"   , message->voltage);
            printf("Current: %.2f A\n\r"     , message->current);
            printf("Number of cycles: %u\n\r", message->counter);
           
            mpool.free(message);
        }
    }
}

Mail


Mail은 Queue와 같은 식으로 동작하지만 메시지를 넣기 위한 메모리 풀을 할당해준다.


Mail example


main.cpp

#include "mbed.h"
#include "rtos.h"

/* Mail */
typedef struct {
  float    voltage; /* AD result of measured voltage */
  float    current; /* AD result of measured current */
  uint32_t counter; /* A counter value               */
} mail_t;

Mail<mail_t, 16> mail_box;

void send_thread (void const *args) {
    uint32_t i = 0;
    while (true) {
        i++; // fake data update
        mail_t *mail = mail_box.alloc();
        mail->voltage = (i * 0.1) * 33;
        mail->current = (i * 0.1) * 11;
        mail->counter = i;
        mail_box.put(mail);
        Thread::wait(1000);
    }
}

int main (void) {
    Thread thread(send_thread);
   
    while (true) {
        osEvent evt = mail_box.get();
        if (evt.status == osEventMail) {
            mail_t *mail = (mail_t*)evt.value.p;
            printf("\nVoltage: %.2f V\n\r"   , mail->voltage);
            printf("Current: %.2f A\n\r"     , mail->current);
            printf("Number of cycles: %u\n\r", mail->counter);
           
            mail_box.free(mail);
        }
    }
}


RtosTimer


시스템에서 타이머 함수를 만들거나 제어하기 위해 RtosTimer 클래스를 사용할 수 있다. Period가 expire되면 타이머 함수가 호출되므로 one-shot 또는 주기적으로 호출되도록 할 수 있다. 타이머는 시작, 재시작, 정지될 수 있다.

타이머는 osTimerThread 스레드에서 처리된다. 콜백함수는 이 스레드 하에서 실행되고 CMSIS-RTOS API 콜을 사용할 수도 있다.


RtosTimer example


4개의 LED 타이밍을 제어

main.cpp

#include "mbed.h"
#include "rtos.h"

DigitalOut LEDs[4] = {
    DigitalOut(LED1), DigitalOut(LED2), DigitalOut(LED3), DigitalOut(LED4)
};

void blink(void const *n) {
    LEDs[(int)n] = !LEDs[(int)n];
}

int main(void) {
    RtosTimer led_1_timer(blink, osTimerPeriodic, (void *)0);
    RtosTimer led_2_timer(blink, osTimerPeriodic, (void *)1);
    RtosTimer led_3_timer(blink, osTimerPeriodic, (void *)2);
    RtosTimer led_4_timer(blink, osTimerPeriodic, (void *)3);
   
    led_1_timer.start(2000);
    led_2_timer.start(1000);
    led_3_timer.start(500);
    led_4_timer.start(250);
   
    Thread::wait(osWaitForever);
}#include "mbed.h"
#include "rtos.h"

DigitalOut LEDs[4] = {
    DigitalOut(LED1), DigitalOut(LED2), DigitalOut(LED3), DigitalOut(LED4)
};

void blink(void const *n) {
    LEDs[(int)n] = !LEDs[(int)n];
}

int main(void) {
    RtosTimer led_1_timer(blink, osTimerPeriodic, (void *)0);
    RtosTimer led_2_timer(blink, osTimerPeriodic, (void *)1);
    RtosTimer led_3_timer(blink, osTimerPeriodic, (void *)2);
    RtosTimer led_4_timer(blink, osTimerPeriodic, (void *)3);
   
    led_1_timer.start(2000);
    led_2_timer.start(1000);
    led_3_timer.start(500);
    led_4_timer.start(250);
   
    Thread::wait(osWaitForever);
}


Interrupt Service Routine


ISR에서도 동일한 RTOS API를 사용할 수 있다. 단 두가지 주의할 점은 다음과 같다.

* Mutex를 사용할 수 없다.
* ISR내에서는 wait이 허용되지 않는다. 메소드 파라미터의 모든 timeout은 0으로 설정되어야만 한다.

ISR example


Interrupt를 발생시키기 위해 큐의 메시지를 사용

main.cpp

#include "mbed.h"
#include "rtos.h"

Queue<uint32_t, 5> queue;

DigitalOut myled(LED1);

void queue_isr() {
    queue.put((uint32_t*)2);
    myled = !myled;
}

void queue_thread(void const *args) {
    while (true) {
        queue.put((uint32_t*)1);
        Thread::wait(1000);
    }
}

int main (void) {
    Thread thread(queue_thread);
   
    Ticker ticker;
    ticker.attach(queue_isr, 1.0);
   
    while (true) {
        osEvent evt = queue.get();
        if (evt.status != osEventMessage) {
            printf("queue->get() returned %02x status\n\r", evt.status);
        } else {
            printf("queue->get() returned %d\n\r", evt.value.v);
        }
    }
}


Default Timeout


mbed RTOS API는 기본적으로 producer 메소드는 0 timeout(no wait)을, consumer 메소드는 osWaitForever(infinite wait)을 사용한다.

Producer의 일반적인 시나리오는 이벤트 발생을 통보하기 위해 인터럽트를 발생시키는 주변기기가 될 수 있다. 그에 해당하는 ISR은 기다릴 수가 없다. (기다리면 시스템 전체가 데드락이 걸릴수도 있음) 반면 consumer는 이벤트를 기다리는 백그라운드 스레드가 될 수 있다. 이 경우 바람직한 기본 행동은 이벤트가 발생할 때 까지는 CPU 사이클을 사용하지 않는 것이므로 osWaitForever가 된다.

* 주의 : ISR에서의 no wait
ISR에서 RTOS 오브젝트 메소드를 호출할 때 모든 timeout 라파미터는 0(no wait)이 되어야만 한다. ISR 내에서 wait은 허용되지 않는다.

Status and error code


CMSIS-RTOS 함수는 다음과 같은 상태값을 리턴한다.

* osOK: 함수 완료. 이벤트가 발생하지 않았음
* osEventSignal : 함수 완료. 시그널 이벤트 발생
* osEventMessage : 함수 완료. 메시지 이벤트 발생
* osEventMail : 함수 완료. 메일 이벤트 발생
* osEventTimeout : 함수 완료. 타임아웃 발생
* osErrorParameter : 필수 파라미터가 없거나 잘못된 오브젝트를 지정
* osErrorResource : 지정된 리소스를 사용할 수 없음
* osErrorTimeoutResource : 지정된 리소스가 타임아웃 시간동안 사용할 수 없음
* osErrorISR : 함수는 ISR에서 호출될 수 없음
* osErrorISRRecursive : ISR에서 함수가 같은 오브젝트에 대해 반복적으로 호출됨
* osErrorPriority : 시스템이 우선순위를 정할 수 없거나 스레드가 허용되지 않는 우선순위를 가지고 있음
* osErrorNoMemory : 시스템의 메모리 부족. 동작을 수행하기 위한 메모리 할당이나 예약이 불가능
* osErrorValue : 파라미터 값이 범위를 벗어남
* osErrorOS : 지정되지 않은 RTOS 에러 - 런타임 에러지만 다른 에러 메시지에 해당하지 않는 경우

API - RTOS header


rtos.h 

/* mbed Microcontroller Library

  * Copyright (c) 2006-2012 ARM Limited
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
  * in the Software without restriction, including without limitation the rights
  * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  * copies of the Software, and to permit persons to whom the Software is
  * furnished to do so, subject to the following conditions:
  *
  * The above copyright notice and this permission notice shall be included in
  * all copies or substantial portions of the Software.
  *
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  * SOFTWARE.
  */
 #ifndef RTOS_H
 #define RTOS_H

 #include "Thread.h"
 #include "Mutex.h"
 #include "RtosTimer.h"
 #include "Semaphore.h"
 #include "Mail.h"
 #include "MemoryPool.h"
 #include "Queue.h"

 using namespace rtos;

 /* Get mbed lib version number, as RTOS depends on mbed lib features
  like mbed_error, Callback and others.
 */
 #include "mbed.h"

 #if (MBED_LIBRARY_VERSION < 122)
 #error "This version of RTOS requires mbed library version > 121"
 #endif

 #endif


댓글 2개:

  1. 안녕하십니까

    * 경고 : ISR 내에서 stdio, malloc, new
    ARM C standard library의 mutex 때문에 ISR내에서는 stdio(printf, putc, getc 등), malloc, new를 사용할 수 없다.
    위에 따르면 mbed rtos에서 시리얼 수신 인터럽트에서 getc를 사용하지 못한다는 것인데 어떻게 처리해야되나요?

    답글삭제
    답글
    1. 별도의 시리얼 쓰레드를 돌려 mutex wait을 하고 있고 수신 ISR에서는 그 mutex를 unlock시켜만 주고 실제 getc는 시리얼 쓰레드에서 하는 방법이 있습니다.

      삭제