2017년 3월 17일 금요일

RIOT OS 소개 2

RIOT source code를 보면 examples/ 디렉토리 말고도 tests/ 디렉토리에 매우 유용한 example code가 많이 포함되어 있다. 이번 posting에서는 tests/ 디렉토리의 내용을 토대로 RIOT OS를 좀 더 구체적으로 분석해 보는 시간을 가져 보도록 하겠다. RIOT OS 시험을 위해서는 지난 시간과 마찬가지로 Nucleo F103RB(STM32F103RB) 보드를 사용하기로 하자.
RIOT OS는 다른 RTOS와 비교해 볼 때 Linux와 닮은 점이 많이 있다(RIOT OS의 설계 의도하고도 연관이 있어 보임). 이는 곧 Linux에 익숙한 programmer에게는 그만큼 RIOT OS가 배우기에 수월하다는 얘기가 된다. (개인적인 생각으로는) Open source RTOS 중에서는 FreeRTOS가 상대적으로 널리 알려져 있는데, RIOT OS가 가진 여러 장점에 비추어 볼 때, 머지 않아 RIOT OS가 크게 주목 받는 날이 올 것으로 믿는다^^.

<RIOT OS의 장점>
 - 쉽다(마치 linux application을 작성하는 느낌이다. sample code를 보고 쉽게 따라해 볼 수 있었다. kernel code도 매우 간결하고, linux kernel과 유사한 점이 보인다).
 - Linux에서 개발하기에 편하다. 별도의 개발 tool(IDE, jtag s/w 등)이 필요치 않다.
 - 많은 device(resource constrained device)를 지원(driver가 잘 설계되어 있음)하며, network stack(IPv6 only)이 우수한 듯 보인다.
 - 쓸만한 package도 많이 porting되어 있는 듯 하다.
 - shell 기능도 쓸만하고, module 단위로 분리되어 있어, Makefile 수정만으로 기능 추가가 가능하다.
 - 2008년 부터 시작했으니, 나름 안정적인 느낌이다.


참고: Ostro는 RTOS는 아니고 Yocto project를 기반으로 하여 만든 IoT OS(Linux 기반) project이며, 위 그림에는 없으나 Linux Foundation에서 최근에 release한 Zephyr project(Wind River Rocket OS와 관련이 있음)도 주목할 만한 OS로 보인다.

<목차>
1. STM32 & Cortex-M3 개요
2. GPIO 예제 소개(LED, Button Interrupt)
3. UART 예제 소개
4. Thread & xtimer 예제 소개
5. Network(IPv6, UDP 통신) 예제 소개


1. STM32 & Cortex-M3 개요 
Kernel & device driver programming을 하려면 system(chip)을 늘 제대로 이해해하고 있어야만 한다. 아래 내용은 Cortex-M3 programming 관련 문서(참고 문헌[3])에서 발췌한 것들로 추가 설명이 필요한 부분이기는 하지만, (시간 관계상) 보다 자세한 분석은 독자 여러분의 몫으로 남기도록 하겠다. Cortex-M3/M4 programming과 관련해서는 특별히 참고 문헌 [4]를 참조해 볼 것을 권해 드린다.

그림 1.1 STM32 Cortex-M3 implementation


그림 1.2 Processor core registers


그림 1.3 Memory map


그림 1.4 Vector table


2. GPIO 예제 소개(LED, Button, Interrupt)
이번 절에서는 device driver 중 가장 기초라 할 수 있는 GPIO & interrupt 사용 예제(LED와 user button을 사용)를 소개해 보도록 하겠다. Linux kernel에서 LED를 제어하고, 사용자 button에 대해 interrupt 처리를 하는 부분을 생각해 보면서 아래 내용을 살펴 보면 좋을 듯 보인다.

---------------------------------------------------------------------------------------------------------------------------------------------------------------------
<tests/periph_gpio/main.c>
/*
 * Copyright (C) 2014 Freie Universität Berlin
 *
 * This file is subject to the terms and conditions of the GNU Lesser General
 * Public License v2.1. See the file LICENSE in the top level directory for more
 * details.
 */

/**
 * @ingroup     tests
 * @{
 *
 * @file
 * @brief       Manual test application for GPIO peripheral drivers
 *
 * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
 *
 * @}
 */

#include <stdio.h>
#include <stdlib.h>

#include "shell.h"
#include "periph/gpio.h"

static void cb(void *arg)   /* gpio interrupt 발생 시 호출되는 handler(함수) */
{
    printf("INT: external interrupt from pin %i\n", (int)arg);
}

static int init_pin(int argc, char **argv, gpio_mode_t mode)   /* shell cmd 함수 - gpio pin 초기화 담당 */
{
    int po, pi;

    if (argc < 3) {
        printf("usage: %s <port> <pin>\n", argv[0]);
        return 1;
    }

    po = atoi(argv[1]);
    pi = atoi(argv[2]);

    if (gpio_init(GPIO_PIN(po, pi), mode) < 0) {   /* gpio pin 초기화 함수 */
        printf("Error to initialize GPIO_PIN(%i, %02i)\n", po, pi);
        return 1;
    }

    return 0;
}

static int init_out(int argc, char **argv)   /* 해당 pin을 gpio output으로 설정 */
{
    return init_pin(argc, argv, GPIO_OUT);
}

static int init_in(int argc, char **argv)  /* 해당 pin을 gpio input으로 설정 */
{
    return init_pin(argc, argv, GPIO_IN);
}

static int init_in_pu(int argc, char **argv)  /* 해당 pin을 gpio input(input with pull-up)으로 설정 */
{
    return init_pin(argc, argv, GPIO_IN_PU);
}

static int init_in_pd(int argc, char **argv)   /* 해당 pin을 gpio input(input with pull-down)으로 설정 */
{
    return init_pin(argc, argv, GPIO_IN_PD);
}

static int init_od(int argc, char **argv)   /* 해당 pin을 gpio output(open-drain w/o pull R)으로 설정 */
{
    return init_pin(argc, argv, GPIO_OD);
}

static int init_od_pu(int argc, char **argv)  /* 해당 pin을 gpio output(open-drain with pull-up => not supported by HW)으로 설정 */
{
    return init_pin(argc, argv, GPIO_OD_PU);
}

static int init_int(int argc, char **argv)   /* gpio interrupt 지정 함수 */
{
    int po, pi;
    gpio_mode_t mode = GPIO_IN;
    gpio_flank_t flank;
    int fl;

    if (argc < 4) {
        printf("usage: %s <port> <pin> <flank> [pull_config]\n", argv[0]);
        puts("\tflank:\n"
             "\t0: falling\n"
             "\t1: rising\n"
             "\t2: both\n"
             "\tpull_config:\n"
             "\t0: no pull resistor (default)\n"
             "\t1: pull up\n"
             "\t2: pull down");
        return 1;
    }

    po = atoi(argv[1]);
    pi = atoi(argv[2]);

    fl = atoi(argv[3]);
    switch (fl) {
        case 0:
            flank = GPIO_FALLING;
            break;
        case 1:
            flank = GPIO_RISING;
            break;
        case 2:
            flank = GPIO_BOTH;
            break;
        default:
            puts("error: invalid value for active flank");
            return 1;
    }

    if (argc >= 5) {
        int pr = atoi(argv[4]);
        switch (pr) {
            case 0:
                mode = GPIO_IN;
                break;
            case 1:
                mode = GPIO_IN_PU;
                break;
            case 2:
                mode = GPIO_IN_PD;
                break;
            default:
                puts("error: invalid pull resistor option");
                return 1;
        }
    }

    if (gpio_init_int(GPIO_PIN(po, pi), mode, flank, cb, (void *)pi) < 0) {   /* gpio pin 초기화 및 gpio interrupt 발생 시, cb callback 함수가 호출되도록 등록해 줌 */
        printf("error: init_int of GPIO_PIN(%i, %i) failed\n", po, pi);
        return 1;
    }
    printf("GPIO_PIN(%i, %i) successfully initialized as ext int\n", po, pi);

    return 0;
}

static int read(int argc, char **argv)   /* gpio 값 read(input) 함수 */
{
    int port, pin;

    if (argc < 3) {
        printf("usage: %s <port> <pin>\n", argv[0]);
        return 1;
    }

    port = atoi(argv[1]);
    pin = atoi(argv[2]);

    if (gpio_read(GPIO_PIN(port, pin))) {
        printf("GPIO_PIN(%i.%02i) is HIGH\n", port, pin);
    }
    else {
        printf("GPIO_PIN(%i.%02i) is LOW\n", port, pin);
    }

    return 0;
}

static int set(int argc, char **argv)  /* gpio 값 설정(output) 함수 */
{
    if (argc < 3) {
        printf("usage: %s <port> <pin>\n", argv[0]);
        return 1;
    }

    gpio_set(GPIO_PIN(atoi(argv[1]), atoi(argv[2])));

    return 0;
}

static int clear(int argc, char **argv)  /* gpio 값 clear 함수 */
{
    if (argc < 3) {
        printf("usage: %s <port> <pin>\n", argv[0]);
        return 1;
    }

    gpio_clear(GPIO_PIN(atoi(argv[1]), atoi(argv[2])));

    return 0;
}

static int toggle(int argc, char **argv)   /* gpio output 값 toggle 함수 */
{
    if (argc < 3) {
        printf("usage: %s <port> <pin>\n", argv[0]);
        return 1;
    }

    gpio_toggle(GPIO_PIN(atoi(argv[1]), atoi(argv[2])));

    return 0;
}

static const shell_command_t shell_commands[] = {
    { "init_out", "init as output (push-pull mode)", init_out },
    { "init_in", "init as input w/o pull resistor", init_in },
    { "init_in_pu", "init as input with pull-up", init_in_pu },
    { "init_in_pd", "init as input with pull-down", init_in_pd },
    { "init_od", "init as output (open-drain without pull resistor)", init_od },
    { "init_od_pu", "init as output (open-drain with pull-up)", init_od_pu },
    { "init_int", "init as external INT w/o pull resistor", init_int },
    { "read", "read pin status", read },
    { "set", "set pin to HIGH", set },
    { "clear", "set pin to LOW", clear },
    { "toggle", "toggle pin", toggle },
    { NULL, NULL, NULL }
};

int main(void)
{
    puts("GPIO peripheral driver test\n");
    puts("In this test, pins are specified by integer port and pin numbers.\n"
         "So if your platform has a pin PA01, it will be port=0 and pin=1,\n"
         "PC14 would be port=2 and pin=14 etc.\n\n"
         "NOTE: make sure the values you use exist on your platform! The\n"
         "      behavior for not existing ports/pins is not defined!");

    /* start the shell */
    char line_buf[SHELL_DEFAULT_BUFSIZE];
    shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);   /* shell commands 등록 */

    return 0;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
<Makefile>
APPLICATION = periph_gpio
include ../Makefile.tests_common

FEATURES_REQUIRED = periph_gpio

USEMODULE += shell   # shell module을 포함시킴, 이 처럼 module 개념으로 처리함으로써 main.c의 코드가 매우 간결(간단)해지게 된다.

include $(RIOTBASE)/Makefile.include
---------------------------------------------------------------------------------------------------------------------------------------------------------------------

그림 2.1 STM32F103 interrupt vector table - isr_exti( ) 함수에서 gpio interrupt handler 호출함

<How to build, flash and run it>
$ docker run -i -t -u $UID -v $(pwd):/data/riotbuild --env BOARD=nucleo-f103 riotbuild make -C tests/periph_gpio clean all
  => docker 환경을 이용하여 tests/periph_gpio code를 build한다.

$ BOARD=nucleo-f103 make -C tests/periph_gpio flash
  => OpenOCD를 이용하여, periph_gpio.hex file을 nucleo-f103 board(flash memory)에 write한다.

$ BOARD=nucleo-f103 make -C tests/periph_gpio term
  => pyterm으로 periph_gpio program의 동작을 확인한다.
  => flashing 후, reset 버튼을 눌러 시스템을 재부팅해 주어야 한다.

그림 2.1 periph_gpio test program 실행 모습

아래 그림 2.2를 보면 알 수 있듯이, Nucleo F103RB 보드에는 3개의 LED가 있는데, 그 중 STM32 I/O PA5(pin 21)가 사용자 LED 로 사용된다.

그림 2.2 STM32 Nucleo-64 board 매뉴얼 중 LED, Push-button 관련 내용 발췌

PA5(A이므로 port 0, 5번 pin) pin을 gpio output으로 설정한 후, 값을 0(Low) -> 1(High) -> 0 -> 1 식으로 변경함으로써 LED가 깜빡이는 것을 확인해 보면 다음과 같다(실제 보드에서 LED가 깜빡이는 모습은 생략함).

> init_out 0 5
   => PA5(0: A, 1: B, 2: C ...) 번 pin을 gpio output 설정한다.
> read 0 5
   => PA5번 pin의 현재 값을 읽어 들인다.
> set 0 5
   => PA5번 pin의 값을 High로 설정한다.
> toggle 0 5
   => PA5번 pin의 값을 toggle한다(Low -> High 혹은 High -> Low)

그림 2.3 user LED 실험 모습(PA05 pin)

한편, 아래 그림 2.4는 Nucleo F103RB 보드의 사용자 버튼(PC13 pin - C이므로 port 2, 13번 pin)을 누를(falling edge) 경우, interrupt가 발생(단순히 console로 메시지 출력)하는 모습을 capture한 것이다.

> init_int 2 13 0
  => PC13(2: C, 13번 pin)번 pin을 falling mode(= 0)로 설정한다.
       flank:
               0: falling
               1: rising
               2: both
               pull_config:
                    0: no pull resistor (default)
                    1: pull up
                    2: pull down
그림 2.4 gpio interrupt 실험 모습(PC13 pin) - User button을 누를 경우, console에 메시지 출력됨.

아래 그림은 user button을 눌렀을 때(falling-edge)의 파형 변화를 DS201 oscilloscope로 잡은 모습을 보여준다(PC13 pin을 오실로스코프 프로브에 연결하고, 나머지 한쪽을 ground에 연결).

그림 2.5 User 버튼을 누를 때 DS201 pocket oscilloscope로 파형을 잡은 모습

참고 사항: 재밌게도 위의 그림 2.5의 DS201 oscillopscope를 만들기 위해 사용된 CPU(MPU)는 STM32F103이다.


3. UART 예제 소개
이번 절에서는 UART A <-> UART B 간의 상호 통신과 관련한 예제 program을 소개해 보고자 한다.

USART2: PORT A3(rx)/A2(tx) <-----> USART1: PORT A10(rx)/A9(tx)
참고: PORT A3/A2는 ST-LINK MCU에 연결되어 있음.


그림 3.1 USART2(좌측 USB port)와 USART1(상단) port 연결 모습

---------------------------------------------------------------------------------------------------------------------------------------------------------------------
<tests/periph_uart/main.c>
/*
 * Copyright (C) 2015 Freie Universität Berlin
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup     tests
 * @{
 *
 * @file
 * @brief       Manual test application for UART peripheral drivers
 *
 * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
 *
 * @}
 */

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "board.h"
#include "shell.h"
#include "thread.h"
#include "msg.h"
#include "ringbuffer.h"
#include "periph/uart.h"
#include "uart_stdio.h"

#define SHELL_BUFSIZE       (128U)
#define UART_BUFSIZE        (128U)

#define PRINTER_PRIO        (THREAD_PRIORITY_MAIN - 1)
#define PRINTER_TYPE        (0xabcd)

#ifndef UART_STDIO_DEV
#define UART_STDIO_DEV      (UART_UNDEF)
#endif

typedef struct {
    char rx_mem[UART_BUFSIZE];
    ringbuffer_t rx_buf;
} uart_ctx_t;

static uart_ctx_t ctx[UART_NUMOF];

static kernel_pid_t printer_pid;
static char printer_stack[THREAD_STACKSIZE_MAIN];

static int parse_dev(char *arg)   /* CLI로 입력 받은 dev 값이 지정된 범위 내에 있는지 확인하는 함수 */
{
    unsigned dev = (unsigned)atoi(arg);
    if (dev >= UART_NUMOF) {
        printf("Error: Invalid UART_DEV device specified (%u).\n", dev);
        return -1;
    }
    else if (UART_DEV(dev) == UART_STDIO_DEV) {
        printf("Error: The selected UART_DEV(%u) is used for the shell!\n", dev);
        return -2;
    }
    return dev;
}

static void rx_cb(void *arg, uint8_t data)   /* UART 관련 IRQ callback 함수 - 즉, UART 관련 interrupt 발생시 호출되는 handler(함수) */
{
    uart_t dev = (uart_t)arg;

#if 1 /* test code */
printf("<CHYI> (%s) called(%s).\n", __func__, ctx[dev].rx_buf.buf);
#endif
    ringbuffer_add_one(&(ctx[dev].rx_buf), data);
    if (data == '\n') {
        msg_t msg;
        msg.content.value = (uint32_t)dev;
        msg_send(&msg, printer_pid);   /* uart로 부터 입력 받은 message를 printer thread로 전달 */
    }
}

static void *printer(void *arg)  /* printer thread 함수 */
{
    (void)arg;
    msg_t msg;
    msg_t msg_queue[8];
    msg_init_queue(msg_queue, 8);

    while (1) {
        msg_receive(&msg);   /* uart interrupt handler로 부터 message 수신 */
        uart_t dev = (uart_t)msg.content.value;
        char c;

        printf("UART_DEV(%i) RX: ", dev);
        do {
            c = (int)ringbuffer_get_one(&(ctx[dev].rx_buf));
            if (c == '\n') {
                puts("\\n");
            }
            else if (c >= ' ' && c <= '~') {
                printf("%c", c);
            }
            else {
                printf("0x%02x", (unsigned char)c);
            }
        } while (c != '\n');
    }

    /* this should never be reached */
    return NULL;
}

static int cmd_init(int argc, char **argv)   /* CLI(shell) init 명령 */
{
    int dev, res;
    uint32_t baud;

    if (argc < 3) {
        printf("usage: %s <dev> <baudrate>\n", argv[0]);
        return 1;
    }
    /* parse parameters */
    dev = parse_dev(argv[1]);
    if (dev < 0) {
        return 1;
    }
    baud = (uint32_t)atoi(argv[2]);

    /* initialize UART */
    res = uart_init(UART_DEV(dev), baud, rx_cb, (void *)dev);    /* uart 초기화 함수, rx_cb 등록 */
    if (res == UART_NOBAUD) {
        printf("Error: Given baudrate (%u) not possible\n", (unsigned int)baud);
        return 1;
    }
    else if (res != UART_OK) {
        puts("Error: Unable to initialize UART device\n");
        return 1;
    }
    printf("Successfully initialized UART_DEV(%i)\n", dev);
    return 0;
}

static int cmd_send(int argc, char **argv)   /* CLI(shell)에서 uart로 message를 보내는 함수 */
{
    int dev;
    uint8_t endline = (uint8_t)'\n';

    if (argc < 3) {
        printf("usage: %s <dev> <data (string)>\n", argv[0]);
        return 1;
    }
    /* parse parameters */
    dev = parse_dev(argv[1]);
    if (dev < 0) {
        return 1;
    }

    printf("UART_DEV(%i) TX: %s\n", dev, argv[2]);
    uart_write(UART_DEV(dev), (uint8_t *)argv[2], strlen(argv[2]));   /* uart로 string을 내보냄 */
    uart_write(UART_DEV(dev), &endline, 1);
    return 0;
}

static const shell_command_t shell_commands[] = {
    { "init", "Initialize a UART device with a given baudrate", cmd_init },
    { "send", "Send a string through given UART device", cmd_send },
    { NULL, NULL, NULL }
};

int main(void)
{
    puts("\nManual UART driver test application");
    puts("===================================");
    puts("This application is intended for testing additional UART\n"
         "interfaces, that might be defined for a board. The 'primary' UART\n"
         "interface is tested implicitly, as it is running the shell...\n\n"
         "When receiving data on one of the additional UART interfaces, this\n"
         "data will be outputted via STDIO. So the easiest way to test an \n"
         "UART interface, is to simply connect the RX with the TX pin. Then \n"
         "you can send data on that interface and you should see the data \n"
         "being printed to STDOUT\n\n"
         "NOTE: all strings need to be '\\n' terminated!\n");

    puts("UART INFO:");
    printf("Available devices:               %i\n", UART_NUMOF);
    printf("UART used for STDIO (the shell): UART_DEV(%i)\n\n", UART_STDIO_DEV);

    /* initialize ringbuffers */
    for (unsigned i = 0; i < UART_NUMOF; i++) {
        ringbuffer_init(&(ctx[i].rx_buf), ctx[i].rx_mem, UART_BUFSIZE);
    }

    /* start the printer thread */
    printer_pid = thread_create(printer_stack, sizeof(printer_stack),   /* printer thread 생성 */
                                PRINTER_PRIO, 0, printer, NULL, "printer");

    /* run the shell */
    char line_buf[SHELL_DEFAULT_BUFSIZE];
    shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);    /* shell command 등록 */
    return 0;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------

그림 3.2 STM32F103 UART configuration - nucleo-f103/include/periph_conf.h

<How to build, flash and run it>
docker run -i -t -u $UID -v $(pwd):/data/riotbuild --env BOARD=nucleo-f103 riotbuild make -C tests/periph_uart clean all
  => docker 환경을 이용하여 tests/periph_uart code를 build한다.

BOARD=nucleo-f103 make -C tests/periph_uart flash
  => periph_uart.hex file을 nucleo-f103 board(flash memory)에 write한다.

BOARD=nucleo-f103 make -C tests/periph_uart term
  => pyterm으로 동작을 확인한다.
  => flashing 후, reset 버튼을 눌러 시스템을 재부팅해 주어야 한다.

> init 1 115200
  => USART1을 초기화함(이때 buadrate은 115200으로 함). 그림 3.2에 의해 dev 1이 USART1(PA10/9)에 해당함.

그림 3.3 periph_uart.hex 실행 모습 - uart 초기화 및 메시지 수신

<다른 terminal>
$ echo "hello\nword\n" > /dev/ttyUSB0
  => Ubuntu Host 입장에서 볼 때 /dev/ttyUSB0가 target board의 USART1에 연결된 장치에 해당함. 따라서 이 곳으로 message를 전달 시도하면, periph_uart program에 의해 USART2로 전달되게 되고, 이 내용이 pyterm을 통해 console로 출력되게 됨.

그림 3.4 /dev/ttyUSB0(target board USART1으로 연결된 port)로 메시지를 보내는 모습


4. Thread & xtimer 예제 소개
이번 절에서는 thread와  xtimer를 사용하는 예제 program을 소개해 보고자 한다. 역시 linux kernel의 thread 및 timer 사용법과 비교해 보는 것도 좋을 것 같다.

---------------------------------------------------------------------------------------------------------------------------------------------------------------------
<tests/xtimer_drift/main.c>
/*
 * Copyright (C) 2015 Kaspar Schleiser <kaspar@schleiser.de>
 * Copyright (C) 2015 Eistec AB
 *               2013 INRIA
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup tests
 * @{
 *
 * @file
 * @brief    xtimer_drift test application
 *
 * @author   Kaspar Schleiser <kaspar@schleiser.de>
 * @author   Oliver Hahm <oliver.hahm@inria.fr>
 * @author   Christian Mehlis <mehlis@inf.fu-berlin.de>
 * @author   Joakim Nohlgård <joakim.nohlgard@eistec.se>
 *
 * @}
 */

#include <stdio.h>
#include <time.h>

#include "xtimer.h"
#include "thread.h"
#include "msg.h"

/* We generate some context switching and IPC traffic by using multiple threads
 * and generate some xtimer load by scheduling several messages to be called at
 * different times. TEST_HZ is the frequency of messages being sent from the
 * main thread to the worker, all other message frequencies are derived from
 * TEST_HZ.
 * TEST_MSG_RX_USLEEP is a tiny sleep inside the message reception thread to
 * cause extra context switches.
 */
#define TEST_HZ (64LU)
#define TEST_INTERVAL (1000000LU / TEST_HZ)
#define TEST_MSG_RX_USLEEP (200LU)

char slacker_stack1[THREAD_STACKSIZE_DEFAULT];   /* slacker1용 stack buffer */
char slacker_stack2[THREAD_STACKSIZE_DEFAULT];   /* slacker2용 stack buffer */
char worker_stack[THREAD_STACKSIZE_MAIN];          /* worker용 stack buffer */

struct timer_msg {
    xtimer_t timer;
    uint32_t interval;
    msg_t msg;
};

struct timer_msg msg_a = { .interval = (TEST_INTERVAL / 2) };     //...............(A)
struct timer_msg msg_b = { .interval = (TEST_INTERVAL / 3) };
struct timer_msg msg_c = { .interval = (TEST_INTERVAL * 5) };
struct timer_msg msg_d = { .interval = (TEST_INTERVAL * 2) };

/* This thread is only here to give the kernel some extra load */
void *slacker_thread(void *arg)   /* slacker thread 함수 */
{
    (void) arg;
    timex_t now;

    printf("Starting thread %" PRIkernel_pid "\n", thread_getpid());

    /* we need a queue if the second message arrives while the first is still processed */
    /* without a queue, the message would get lost */
    msg_t msgq[4];
    msg_init_queue(msgq, 4);  /* message queue를 초기화 한다 */

    while (1) {
        msg_t m;
        msg_receive(&m);  /* main thread에서 전송한 message를 수신한다 */
        struct timer_msg *tmsg = m.content.ptr;   /* main thread에서 msg_a, msg_b, msg_c, msg_d를 각각 보내 옴 */
        xtimer_now_timex(&now);  /* 현재 시간을 계산한다 */
        xtimer_usleep(TEST_MSG_RX_USLEEP);   /* TEST_MSG_RX_USLEEP 만큼 sleep 한다 */

        tmsg->msg.type = 12345;
        tmsg->msg.content.ptr = tmsg;
        xtimer_set_msg(&tmsg->timer, tmsg->interval, &tmsg->msg, thread_getpid());   /* 자기 자신에게 tmsg->interval 경과 후 message를 전송한다, tmsg->interval은 msg_a, msg_b, msg_c, msg_d에서 사용하는 값임 - 위의 (A) 부분 참조 ! */
    }
}

/* This thread will print the drift to stdout once per second */
void *worker_thread(void *arg)   /* worker thread 함수 */
{
    (void) arg;
    uint32_t loop_counter = 0;
    uint32_t start = 0;
    uint32_t last = 0;

    printf("Starting thread %" PRIkernel_pid "\n", thread_getpid());

    while (1) {
        msg_t m;
        msg_receive(&m);   /* main thread에서 전송한 message를 수신한다 */
        xtimer_ticks32_t ticks = xtimer_now();   /* 현재 시간(정확히는 tick)을 구한다 */
        uint32_t now = xtimer_usec_from_ticks(ticks);   /* tick으로 부터 usec을 계산한다 */
        if (start == 0) {
            start = now;
            last = start;
            ++loop_counter;
            continue;
        }

        uint32_t us, sec;
        us = now % US_PER_SEC;
        sec = now / US_PER_SEC;
        if ((loop_counter % TEST_HZ) == 0) {
            uint32_t expected = start + loop_counter * TEST_INTERVAL;
            int32_t drift = now - expected;
            expected = last + TEST_HZ * TEST_INTERVAL;
            int32_t jitter = now - expected;
            printf("now=%" PRIu32 ".%06" PRIu32 " (0x%08" PRIx32 " ticks), ",
                sec, us, ticks.ticks32);
            printf("drift=%" PRId32 " us, jitter=%" PRId32 " us\n", drift, jitter);
            last = now;
        }
        ++loop_counter;
    }
}

int main(void)
{
    msg_t m;

    puts("xtimer_drift test application");
    puts("Make note of the PC clock when starting this test, let run for a while, "
        "compare the printed time against the expected time from the PC clock.");
    puts("The difference is the RIOT timer drift, this is likely caused by either: "
        "an inaccurate hardware timer, or bugs in the software (xtimer or periph/timer).");
    printf("This test will run a periodic timer every %lu microseconds (%lu Hz), ",
        (unsigned long)TEST_INTERVAL, (unsigned long)TEST_HZ);
    puts("The current time will be printed once per second, along with the "
         "difference between the actual and expected xtimer_now value.");
    puts("The first output variable, 'drift', represents the total offset since "
         "start between xtimer_now and the expected time.");
    puts("The second output variable, 'jitter', represents the difference in drift from the last printout.");
    puts("Two other threads are also running only to cause extra interrupts and context switches.");
    puts(" <====== PC clock if running in pyterm.");
    puts("");
    puts(" =======================");
    puts(" ===== Test begins =====");
    puts(" =======================");

    kernel_pid_t pid1 = thread_create(   /* slacker thread 생성 */
        slacker_stack1,
        sizeof(slacker_stack1),
        THREAD_PRIORITY_MAIN - 1,
        THREAD_CREATE_STACKTEST,
        slacker_thread,
        NULL,
        "slacker1");

    puts("sending 1st msg");
    m.content.ptr = &msg_a;
    msg_try_send(&m, pid1);  /* pid1 즉 slacker thread(1)에게 message를 전달한다 */

    puts("sending 2nd msg");
    m.content.ptr = &msg_b;
    msg_try_send(&m, pid1);  /* pid1 즉 slacker thread(1)에게 message를 전달한다 */

    kernel_pid_t pid2 = thread_create(   /* slacker thread 생성 - slacker_thread 함수를 공유하는 다른 thread 생성 */
        slacker_stack2,
        sizeof(slacker_stack2),
        THREAD_PRIORITY_MAIN - 1,
        THREAD_CREATE_STACKTEST,
        slacker_thread,
        NULL,
        "slacker2");

    puts("sending 3rd msg");
    m.content.ptr = &msg_c;
    msg_try_send(&m, pid2);  /* pid2 즉 slacker thread(2)에게 message를 전달한다 */

    puts("sending 4th msg");
    m.content.ptr = &msg_d;
    msg_try_send(&m, pid2);  /* pid2 즉 slacker thread(2)에게 message를 전달한다 */

    kernel_pid_t pid3 = thread_create(   /* worker thread 생성 */
                   worker_stack,
                   sizeof(worker_stack),
                   THREAD_PRIORITY_MAIN - 1,
                   THREAD_CREATE_STACKTEST,
                   worker_thread,
                   NULL,
                   "worker");

    xtimer_ticks32_t last_wakeup = xtimer_now();   /* 현재 시간(정확히는 tick)을 구한다 */
    while (1) {
        xtimer_periodic_wakeup(&last_wakeup, TEST_INTERVAL);   /* TEST_INTERVAL 만큼 sleep 후 깨어남 */
        msg_try_send(&m, pid3);   /* pid3 즉 worker thread에게 message를 전달한다 */
    }
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to build, flash and run it>

docker run -i -t -u $UID -v $(pwd):/data/riotbuild --env BOARD=nucleo-f103 riotbuild make -C tests/xtimer_drift clean all
  => docker 환경을 이용하여 tests/xtimer_drift code를 build한다.

BOARD=nucleo-f103 make -C tests/xtimer_drift flash
  => OpenOCD를 이용하여, xtimer_drift.hex file을 nucleo-f103 board(flash memory)에 write한다.

BOARD=nucleo-f103 make -C tests/xtimer_drift term
  => pyterm으로 xtimer_drift program의 동작을 확인한다.
  => flashing 후, reset 버튼을 눌러 시스템을 재부팅해 주어야 한다.



그림 4.1  xtimer_drift 실행 모습


5. Network(IPv6, UDP 통신) 예제 소개
마지막으로 이번 절에서는 network 예제인 examples/gnrc_networking code를 소개해 보도록 하겠다. RIOT OS는 IoT를 target으로 만들어진 OS이니 만큼, 각종 주변 장치(sensor류)에 대한 제어는 물론이고, IoT 장치간의 상호 통신을 위한 network 관련 코드(stack)가 잘 정비되어 있다. 특히 IoT 장비의 폭발적인 대수를 감안하여 IPv4 보다는 IPv6에 촛점을 맞추어 설계되어 있다.

RIOT machine(IPv6) <-> Linux Host(IPv6)
RIOT machine(IPv6) <-> RIOT machine(IPv6)
<이번 예제에서 확인 가능한 내용>

---------------------------------------------------------------------------------------------------------------------------------------------------------------------
<examples/main.c>
/*
 * Copyright (C) 2015 Freie Universität Berlin
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup     examples
 * @{
 *
 * @file
 * @brief       Example application for demonstrating the RIOT network stack
 *
 * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
 *
 * @}
 */

#include <stdio.h>

#include "shell.h"
#include "msg.h"

#define MAIN_QUEUE_SIZE     (8)
static msg_t _main_msg_queue[MAIN_QUEUE_SIZE];

extern int udp_cmd(int argc, char **argv);

static const shell_command_t shell_commands[] = {
    { "udp", "send data over UDP and listen on UDP ports", udp_cmd },
    { NULL, NULL, NULL }
};

int main(void)
{
    /* we need a message queue for the thread running the shell in order to
     * receive potentially fast incoming networking packets */
    msg_init_queue(_main_msg_queue, MAIN_QUEUE_SIZE);
    puts("RIOT network stack example application");

    /* start shell */
    puts("All up, running the shell now");
    char line_buf[SHELL_DEFAULT_BUFSIZE];
    shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);

    /* should be never reached */
    return 0;
}

---------------------------------------------------------------------------------------------------------------------------------------------------------------------
<examples/udp.c>
/*
 * Copyright (C) 2015 Freie Universität Berlin
 *
 * This file is subject to the terms and conditions of the GNU Lesser
 * General Public License v2.1. See the file LICENSE in the top level
 * directory for more details.
 */

/**
 * @ingroup     examples
 * @{
 *
 * @file
 * @brief       Demonstrating the sending and receiving of UDP data
 *
 * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
 *
 * @}
 */

#include <stdio.h>
#include <inttypes.h>

#include "net/gnrc.h"
#include "net/gnrc/ipv6.h"
#include "net/gnrc/udp.h"
#include "net/gnrc/pktdump.h"
#include "timex.h"
#include "xtimer.h"

static gnrc_netreg_entry_t server = GNRC_NETREG_ENTRY_INIT_PID(GNRC_NETREG_DEMUX_CTX_ALL,
                                                               KERNEL_PID_UNDEF);


static void send(char *addr_str, char *port_str, char *data, unsigned int num,
                 unsigned int delay)
{
    uint16_t port;
    ipv6_addr_t addr;

    /* parse destination address */
    if (ipv6_addr_from_str(&addr, addr_str) == NULL) {
        puts("Error: unable to parse destination address");
        return;
    }
    /* parse port */
    port = (uint16_t)atoi(port_str);
    if (port == 0) {
        puts("Error: unable to parse destination port");
        return;
    }

    for (unsigned int i = 0; i < num; i++) {
        gnrc_pktsnip_t *payload, *udp, *ip;
        unsigned payload_size;
        /* allocate payload */
        payload = gnrc_pktbuf_add(NULL, data, strlen(data), GNRC_NETTYPE_UNDEF);
        if (payload == NULL) {
            puts("Error: unable to copy data to packet buffer");
            return;
        }
        /* store size for output */
        payload_size = (unsigned)payload->size;
        /* allocate UDP header, set source port := destination port */
        udp = gnrc_udp_hdr_build(payload, port, port);
        if (udp == NULL) {
            puts("Error: unable to allocate UDP header");
            gnrc_pktbuf_release(payload);
            return;
        }
        /* allocate IPv6 header */
        ip = gnrc_ipv6_hdr_build(udp, NULL, &addr);
        if (ip == NULL) {
            puts("Error: unable to allocate IPv6 header");
            gnrc_pktbuf_release(udp);
            return;
        }
        /* send packet */
        if (!gnrc_netapi_dispatch_send(GNRC_NETTYPE_UDP, GNRC_NETREG_DEMUX_CTX_ALL, ip)) {
            puts("Error: unable to locate UDP thread");
            gnrc_pktbuf_release(ip);
            return;
        }
        /* access to `payload` was implicitly given up with the send operation above
         * => use temporary variable for output */
        printf("Success: sent %u byte(s) to [%s]:%u\n", payload_size, addr_str,
               port);
        xtimer_usleep(delay);
    }
}

static void start_server(char *port_str)
{
    uint16_t port;

    /* check if server is already running */
    if (server.target.pid != KERNEL_PID_UNDEF) {
        printf("Error: server already running on port %" PRIu32 "\n",
               server.demux_ctx);
        return;
    }
    /* parse port */
    port = (uint16_t)atoi(port_str);
    if (port == 0) {
        puts("Error: invalid port specified");
        return;
    }
    /* start server (which means registering pktdump for the chosen port) */
    server.target.pid = gnrc_pktdump_pid;
    server.demux_ctx = (uint32_t)port;
    gnrc_netreg_register(GNRC_NETTYPE_UDP, &server);
    printf("Success: started UDP server on port %" PRIu16 "\n", port);
}

static void stop_server(void)
{
    /* check if server is running at all */
    if (server.target.pid == KERNEL_PID_UNDEF) {
        printf("Error: server was not running\n");
        return;
    }
    /* stop server */
    gnrc_netreg_unregister(GNRC_NETTYPE_UDP, &server);
    server.target.pid = KERNEL_PID_UNDEF;
    puts("Success: stopped UDP server");
}

int udp_cmd(int argc, char **argv)
{
    if (argc < 2) {
        printf("usage: %s [send|server]\n", argv[0]);
        return 1;
    }

    if (strcmp(argv[1], "send") == 0) {
        uint32_t num = 1;
        uint32_t delay = 1000000;
        if (argc < 5) {
            printf("usage: %s send <addr> <port> <data> [<num> [<delay in us>]]\n",
                   argv[0]);
            return 1;
        }
        if (argc > 5) {
            num = (uint32_t)atoi(argv[5]);
        }
        if (argc > 6) {
            delay = (uint32_t)atoi(argv[6]);
        }
        send(argv[2], argv[3], argv[4], num, delay);
    }
    else if (strcmp(argv[1], "server") == 0) {
        if (argc < 3) {
            printf("usage: %s server [start|stop]\n", argv[0]);
            return 1;
        }
        if (strcmp(argv[2], "start") == 0) {
            if (argc < 4) {
                printf("usage %s server start <port>\n", argv[0]);
                return 1;
            }
            start_server(argv[3]);
        }
        else if (strcmp(argv[2], "stop") == 0) {
            stop_server();
        }
        else {
            puts("error: invalid command");
        }
    }
    else {
        puts("error: invalid command");
    }
    return 0;
}
---------------------------------------------------------------------------------------------------------------------------------------------------------------------
<examples/Makefile>
# name of your application
APPLICATION = gnrc_networking

# If no BOARD is found in the environment, use this default:
BOARD ?= native

# This has to be the absolute path to the RIOT base directory:
RIOTBASE ?= $(CURDIR)/../..

BOARD_INSUFFICIENT_MEMORY := airfy-beacon chronos msb-430 msb-430h nrf51dongle \
                          nrf6310 nucleo-f103 nucleo-f334 pca10000 pca10005 spark-core \
                          stm32f0discovery telosb weio wsn430-v1_3b wsn430-v1_4 \
                          yunjia-nrf51822 z1 nucleo-f072 nucleo-f030 nucleo-f070 \
                          microbit calliope-mini nucleo32-f042 nucleo32-f303

# Include packages that pull up and auto-init the link layer.
# NOTE: 6LoWPAN will be included if IEEE802.15.4 devices are present
USEMODULE += gnrc_netdev_default
USEMODULE += auto_init_gnrc_netif
# Specify the mandatory networking modules for IPv6 and UDP
USEMODULE += gnrc_ipv6_router_default
USEMODULE += gnrc_udp
# Add a routing protocol
USEMODULE += gnrc_rpl
USEMODULE += auto_init_gnrc_rpl
# This application dumps received packets to STDIO using the pktdump module
USEMODULE += gnrc_pktdump
# Additional networking modules that can be dropped if not needed
USEMODULE += gnrc_icmpv6_echo
# Add also the shell, some shell commands
USEMODULE += shell
USEMODULE += shell_commands
USEMODULE += ps
USEMODULE += netstats_l2
USEMODULE += netstats_ipv6
USEMODULE += netstats_rpl

# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
CFLAGS += -DDEVELHELP

# Comment this out to join RPL DODAGs even if DIOs do not contain
# DODAG Configuration Options (see the doc for more info)
# CFLAGS += -DGNRC_RPL_DODAG_CONF_OPTIONAL_ON_JOIN

# Change this to 0 show compiler invocation lines by default:
QUIET ?= 1

include $(RIOTBASE)/Makefile.include

# Set a custom channel if needed
ifneq (,$(filter cc110x,$(USEMODULE)))          # radio is cc110x sub-GHz
  DEFAULT_CHANNEL ?= 0
  CFLAGS += -DCC110X_DEFAULT_CHANNEL=$(DEFAULT_CHANNEL)
else
  ifneq (,$(filter at86rf212b,$(USEMODULE)))    # radio is IEEE 802.15.4 sub-GHz
    DEFAULT_CHANNEL ?= 5
    FLAGS += -DIEEE802154_DEFAULT_SUBGHZ_CHANNEL=$(DEFAULT_CHANNEL)
  else                                          # radio is IEEE 802.15.4 2.4 GHz
    DEFAULT_CHANNEL ?= 26
    CFLAGS += -DIEEE802154_DEFAULT_CHANNEL=$(DEFAULT_CHANNEL)
  endif
endif
---------------------------------------------------------------------------------------------------------------------------------------------------------------------

<How to build, flash and run it>
docker run -i -t -u $UID -v $(pwd):/data/riotbuild --env BOARD=nucleo-f103 riotbuild make -C examples/gnrc_networking clean all
  => docker 환경을 이용하여 examples/gnrc_networking code를 build한다.

BOARD=nucleo-f103 make -C examples/gnrc_networking flash
  => gnrc_networking.hex file을 nucleo-f103 board(flash memory)에 write한다.

BOARD=nucleo-f103 make -C examples/gnrc_networking term
  => pyterm으로 동작을 확인한다.
  => flashing 후, reset 버튼을 눌러 시스템을 재부팅해 주어야 한다.


그림 5.1 gnrc_networking example 실행 모습

<Ubuntu Host <-> Nucleo-F103 간의 UDP 통신>
<TBD>


이상으로 몇가지 예제 program을 통해 RIOT OS의 대략적인 모습을 다시한번 살펴 보았다. 여기에 소개한 내용은 어디까지나 빙산의 일각에 해당하며, 보다 자세한 사항(예를 들어, pthread 예제 - kernel에서 pthread를 지원함 -, malloc 예제, 각종 device driver 예제, networking 예제 등등)은 독자 여러분의 몫으로 남기도록 하겠다.


References
1. en.DM00105823.pdf - STM32 Nucleo-64 board, STmicroelectronics.
2. en.CD00171190.pdf - STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced ARM-based 32-bit MCUs, STmicroelectronics.
3. en.CD00228163.pdf - STM32F10xxx/20xxx/21xxx/L1xxxx Cortex-M3 programming manual, STmicroelectronics.
4. The definitive guide to ARM Cortex-M3 and Cortex-M4 processors, 3rd edition, Joseph Yiu
5. DS201Manual.pdf, 

Slowboot

댓글 없음:

댓글 쓰기