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

2017년 3월 9일 목요일

RIOT OS 소개

이번 posting에서는 이례적으로 Linux가 아닌 다른 OS 즉, RIOT OS를 소개하는 시간을 가져 보고자 한다.

RIOT aims at a Linux-like openness

The friendly Operating System for the Internet of Things


ARM Cortex-M3 STM32F103RB 보드 + OpenOCD(ST-Link/V2-1) + RIOT OS
a) Linux보다 lightweight한 RIOT OS를 통해 RTOS의 특징을 한눈에 파악해 볼 수 있다.
b) Keil MDK, IAR EWARM 혹은 GCC 기반의 IDE 없이도 vi(m)와 openocd, eclipse만으로 충분히 개발할 수 있다.

<목차>
1. RIOT OS 개요
2. Docker를 이용한 RIOT OS build 방법 소개
3. RIOT OS sample code 소개 
4. Nucleo F103 보드에서의 RIOT OS 동작 시험
5. Nucleo F103 보드 Boot Flow 분석


1. RIOT OS 개요
RIOT OS는 IoT(Internet of Things - 사물인터넷)를 겨냥한 real-time OS이다. IoT를 위한 OS로는 linux를 쉽게 생각해 볼 수도 있겠으나, RIOT OS가 target으로 삼는 platform은 linux로는 도저히 cover하기 힘든 초소형 시스템(8, 16, 32bit platform, 32bit의 경우는 RAM 20KB, Flash 128KB 정도 혹은 그 보다 작은 platform)이다.

<IoT platform world>
초소형 platform(RIOT OS) ---- Linux(소형/중형) ---- Android Things(UI가 필요한 환경)
(*) 물론, 다른 OS(예: Windows IoT Core, Apple HomeKit)도 생각해 볼 수 있다.


그림 1.1 RIOT OS의 영역(target)

현재 IoT를 위한 초 경량 OS로는 RIOT OS외에도 Contiki, Tiny OS 등이 있다(참고로 Apple iOS 기반의 HomeKit, Google Android Things 등도 IoT를 겨냥한 OS 이지만, target 시장에 차이가 있다고 보아야 함). 여기에 Linux를 포함시킨 상태에서 이들과 RIOT OS를 비교해 보면 다음과 같다.

그림 1.2 다른 OS(Contiki, Tiny OS, Linux)와의 비교표

여기서 잠깐 !  RTOS 중에 어떤 녀석을 사용해야 할까 ?
아래 site 내용을 보면, open source RTOS와 관련하여 자세한 사항을 확인할 수 있을 것이다.

https://www.linux.com/news/open-source-operating-systems-iot

https://www.osrtos.com/
----------------------------------------------------------------------------------------------------------------------------------------------------

RIOT OS의 특징을 몇가지로 요약해 보면 다음과 같다(자세한 사항은 그림 1.3 참조).
  a) C/C++ programming(마치 linux 상에서 application 코드를 작성하는 느낌이랄까 ?)
  b) Real-time 능력 보유(ultra-low interrupt latency, 우선 순위 기준 scheduling, multi-threading 지원)
  c) 8-bit, 16-bit, 32-bit platform 지원(현재 많은 board에서 동작이 검증되었으며, 새로운 platform을 계속해서 지원할 예정임) 
  d) 6LoWPAN, IPv6, RPL, UDP 등 network stack 지원 등
  e) 개발 환경이 잘 갖추어진 느낌이다.
  ...

그림 1.3 RIOT OS 주요 features

아래 그림 1.4는 RIOT OS의  전체 구조를 하나의 그림으로 표현해 놓은 것이며, 그림 1.5는 이 중 network stack 부분을 따로 떼어 정리해 놓은 것이다.

그림 1.4 RIOT OS 아키텍쳐


그림 1.5 RIOT OS network stack

마지막으로 그림 1.6은 RIOT OS가 현재 지원하는 hardware platform을 간략히 정리해 놓은 것이다.


그림 1.6 RIOT OS가 지원하는 hardware



2. Docker를 이용한 RIOT OS build 방법 소개
이번 절에서는 RIOT OS를 내려 받아 build하는 방법을 소개해 보고자 한다. 이 대목에서 docker가 언급되는 이유는 RIOT OS가 현재 Ubuntu 14.10(OSX, FreeBSD 등에서도 build 가능함)까지만 지원하고 있기 때문(필자는 Ubuntu 16.04를 사용하고 있음)으로, RIOT OS는 공식적으로 docker를 사용하여 개발할 수 있는 환경(Dockerfile)을 함께 제공하고 있다. 다시 한번 docker의 강력함을 깨닫게 되는 순간이다 :)

<How to download and build the RIOT OS source code>
git clone git://github.com/RIOT-OS/RIOT.git

그림 2.1 RIOS OS source tree

Ubuntu 14.04 등 현재 RIOT OS가 지원하는 환경을 사용하는 경우라면 아래와 같이 곧바로 build 작업을 진행할 수 있다. RIOT OS build와 관련해서는 아래 site의 내용을 참조하기 바란다.


$ cd RIOS
./dist/tools/tapsetup/tapsetup
creating tapbr0
[sudo] password for chyi:
creating tap0
creating tap1

(*) ifconfig -a 명령을 통해 tapbr0, tap0, tap1 interface가 생성되었음을 확인할 수 있음.
(*) 주의 사항: ubuntu host에 bridge-utils package가 미리 설치되어 있어야 함.

$ cd examples/default
$ make all
=> 'default' example code를 build한다.
$ make term
=> terminal(pyterm)을 띄운다.

<How to build the RIOT OS with docker>

$ git clone https://github.com/RIOT-OS/riotdocker
  => RIOT docker source(Dockerfile)를 내려 받는다.

$ cd riotdocker
chyi@earth:~/IoT/RIOT_OS/docker/riotdocker$ ls -la
합계 52
drwxrwxr-x 3 chyi chyi  4096  3월  8 21:33 .
drwxrwxr-x 3 chyi chyi  4096  3월  8 21:33 ..
drwxrwxr-x 8 chyi chyi  4096  3월  8 21:33 .git
-rw-rw-r-- 1 chyi chyi  4238  3월  8 21:33 Dockerfile
-rw-rw-r-- 1 chyi chyi    43  3월  8 21:33 README-short.txt
-rw-rw-r-- 1 chyi chyi    83  3월  8 21:33 README.md
-rw-rw-r-- 1 chyi chyi   355  3월  8 21:33 create_user.c
-rw-rw-r-- 1 chyi chyi 12370  3월  8 21:33 logo.png
-rwxrwxr-x 1 chyi chyi  1163  3월  8 21:33 run.sh

$ docker build -t riotbuild .
  => Dockerfile을 토대로 riotbuild를 위한 docker image를 생성해 내자. 시간이 꽤 오래 걸림 !

$ docker images
  => docker image가 제대로 생성되었는지 확인 !
REPOSITORY              TAG                 IMAGE ID            CREATED             SIZE
riotbuild               latest              c379d9d10e82        14 hours ago        2.78 GB
ubuntu                  xenial              0ef2e08ed3fa        9 days ago          130 MB
chyi/udoo               latest              e3229a376acc        2 weeks ago         63.6 GB
ubuntu                  latest              f49eec89601e        6 weeks ago         129 MB
ubuntu                  trusty              b969ab9f929b        6 weeks ago         188 MB
hello-world             latest              48b5124b2768        7 weeks ago         1.84 kB

$ docker run -i -t -u $UID -v $(pwd):/data/riotbuild riotbuild ./dist/tools/compile_test/compile_test.py
  => RIOT OS가 지원하는 모든 target board에 대한 build 테스트를 진행한다.
  => 너무 시간이 오래 걸려 중간에 강제로 멈추게 함. :(

$ docker run -i -t -u $UID -v $(pwd):/data/riotbuild --env BOARD=native riotbuild make -C examples/default clean all
  => native용(no target board, x86용) example code(default 디렉토리 코드) build 시도 ! 
  => 재밌게도 docker image로 login하지 않고, 외부(Ubuntu 16.04)에서 docker image를 이용하여 build를 진행함.
  => 주의: RIOT source code top 위치(즉, RIOT/ 디렉토리)에서 위의 명령을 실행해 주어야 함.

make: Entering directory '/data/riotbuild/examples/default'
Building application "default" for "native" with MCU "native".

"make" -C /data/riotbuild/boards/native
"make" -C /data/riotbuild/boards/native/drivers
"make" -C /data/riotbuild/core
"make" -C /data/riotbuild/cpu/native
"make" -C /data/riotbuild/cpu/native/netdev2_tap
"make" -C /data/riotbuild/cpu/native/periph
"make" -C /data/riotbuild/drivers
"make" -C /data/riotbuild/drivers/netdev2_eth
"make" -C /data/riotbuild/drivers/saul
"make" -C /data/riotbuild/sys
"make" -C /data/riotbuild/sys/auto_init
"make" -C /data/riotbuild/sys/auto_init/netif
"make" -C /data/riotbuild/sys/auto_init/saul
"make" -C /data/riotbuild/sys/fmt
"make" -C /data/riotbuild/sys/net/crosslayer/netopt
"make" -C /data/riotbuild/sys/net/gnrc
"make" -C /data/riotbuild/sys/net/gnrc/netapi
"make" -C /data/riotbuild/sys/net/gnrc/netif
"make" -C /data/riotbuild/sys/net/gnrc/netif/hdr
"make" -C /data/riotbuild/sys/net/gnrc/netreg
"make" -C /data/riotbuild/sys/net/gnrc/pkt
"make" -C /data/riotbuild/sys/net/gnrc/pktbuf_static
"make" -C /data/riotbuild/sys/net/gnrc/pktdump
"make" -C /data/riotbuild/sys/net/gnrc/link_layer/netdev2
"make" -C /data/riotbuild/sys/od
"make" -C /data/riotbuild/sys/phydat
"make" -C /data/riotbuild/sys/ps
"make" -C /data/riotbuild/sys/saul_reg
"make" -C /data/riotbuild/sys/shell
"make" -C /data/riotbuild/sys/shell/commands
   text   data    bss    dec    hex filename
  75797    588  72016 148401  243b1 /data/riotbuild/examples/default/bin/native/default.elf
make: Leaving directory '/data/riotbuild/examples/default'

./dist/tools/tapsetup/tapsetup
creating tapbr0
[sudo] password for chyi:
creating tap0
creating tap1
$ ./examples/default/bin/native/default.elf tap0
  => 앞서 build한 결과 파일(default.elf)를 실행 !
  => target board가 없는 경우에는 RIOS OS s/w를 이런 식으로 개발할 수 있다.
RIOT native interrupts/signals initialized.
LED_RED_OFF
LED_GREEN_ON
RIOT native board initialized.
RIOT native hardware initialization complete.

main(): This is RIOT! (Version: 2017.04-devel-153-ge38329e-330fdc5e09a5)
Native RTC initialized.
Welcome to RIOT!

> help
help
Command              Description
---------------------------------------
reboot               Reboot the node
ps                   Prints information about running threads.
rtc                  control RTC peripheral interface
ifconfig             Configure network interfaces
txtsnd               Sends a custom string as is over the link layer
saul                 interact with sensors and actuators using SAUL

  => 잠시 후, 아래 packet이 수신됨 ...
PKTDUMP: data received:
~~ SNIP  0 - size:  88 byte, type: NETTYPE_UNDEF (0)
000000 60 0f f5 44 00 30 11 01 fe 80 00 00 00 00 00 00
000010 cc 5f e5 ff fe a4 e1 75 ff 02 00 00 00 00 00 00
000020 00 00 00 00 00 00 00 fb 14 e9 14 e9 00 30 fa aa
000030 00 00 00 00 00 01 00 00 00 00 00 00 0b 5f 67 6f
000040 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f
000050 63 61 6c 00 00 0c 00 01
~~ SNIP  1 - size:  20 byte, type: NETTYPE_NETIF (-1)
if_pid: 4  rssi: 0  lqi: 0
flags: 0x0
src_l2addr: ce:5f:e5:a4:e1:75
dst_l2addr: 33:33:00:00:00:fb
~~ PKT    -  2 snips, total size: 108 byte
PKTDUMP: data received:
~~ SNIP  0 - size:  88 byte, type: NETTYPE_UNDEF (0)
000000 60 0f f5 44 00 30 11 01 fe 80 00 00 00 00 00 00
000010 cc 5f e5 ff fe a4 e1 75 ff 02 00 00 00 00 00 00
000020 00 00 00 00 00 00 00 fb 14 e9 14 e9 00 30 fa aa
000030 00 00 00 00 00 01 00 00 00 00 00 00 0b 5f 67 6f
000040 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f
000050 63 61 6c 00 00 0c 00 01
~~ SNIP  1 - size:  20 byte, type: NETTYPE_NETIF (-1)
if_pid: 4  rssi: 0  lqi: 0
flags: 0x0
src_l2addr: ce:5f:e5:a4:e1:75
dst_l2addr: 33:33:00:00:00:fb
~~ PKT    -  2 snips, total size: 108 byte
PKTDUMP: data received:
~~ SNIP  0 - size:  88 byte, type: NETTYPE_UNDEF (0)
000000 60 0f f5 44 00 30 11 01 fe 80 00 00 00 00 00 00
000010 cc 5f e5 ff fe a4 e1 75 ff 02 00 00 00 00 00 00
000020 00 00 00 00 00 00 00 fb 14 e9 14 e9 00 30 fa aa
000030 00 00 00 00 00 01 00 00 00 00 00 00 0b 5f 67 6f
000040 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f
000050 63 61 6c 00 00 0c 00 01
~~ SNIP  1 - size:  20 byte, type: NETTYPE_NETIF (-1)
if_pid: 4  rssi: 0  lqi: 0
flags: 0x0
src_l2addr: ce:5f:e5:a4:e1:75
dst_l2addr: 33:33:00:00:00:fb
~~ PKT    -  2 snips, total size: 108 byte
PKTDUMP: data received:
~~ SNIP  0 - size:  88 byte, type: NETTYPE_UNDEF (0)
000000 60 0f f5 44 00 30 11 01 fe 80 00 00 00 00 00 00
000010 cc 5f e5 ff fe a4 e1 75 ff 02 00 00 00 00 00 00
000020 00 00 00 00 00 00 00 fb 14 e9 14 e9 00 30 fa aa
000030 00 00 00 00 00 01 00 00 00 00 00 00 0b 5f 67 6f
000040 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f
000050 63 61 6c 00 00 0c 00 01
~~ SNIP  1 - size:  20 byte, type: NETTYPE_NETIF (-1)
if_pid: 4  rssi: 0  lqi: 0
flags: 0x0
src_l2addr: ce:5f:e5:a4:e1:75
dst_l2addr: 33:33:00:00:00:fb
~~ PKT    -  2 snips, total size: 108 byte
PKTDUMP: data received:
~~ SNIP  0 - size:  88 byte, type: NETTYPE_UNDEF (0)
000000 60 0f f5 44 00 30 11 01 fe 80 00 00 00 00 00 00
000010 cc 5f e5 ff fe a4 e1 75 ff 02 00 00 00 00 00 00
000020 00 00 00 00 00 00 00 fb 14 e9 14 e9 00 30 fa aa
000030 00 00 00 00 00 01 00 00 00 00 00 00 0b 5f 67 6f
000040 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f
000050 63 61 6c 00 00 0c 00 01
~~ SNIP  1 - size:  20 byte, type: NETTYPE_NETIF (-1)
if_pid: 4  rssi: 0  lqi: 0
flags: 0x0
src_l2addr: ce:5f:e5:a4:e1:75
dst_l2addr: 33:33:00:00:00:fb
~~ PKT    -  2 snips, total size: 108 byte
PKTDUMP: data received:
~~ SNIP  0 - size:  88 byte, type: NETTYPE_UNDEF (0)
000000 60 0f f5 44 00 30 11 01 fe 80 00 00 00 00 00 00
000010 cc 5f e5 ff fe a4 e1 75 ff 02 00 00 00 00 00 00
000020 00 00 00 00 00 00 00 fb 14 e9 14 e9 00 30 fa aa
000030 00 00 00 00 00 01 00 00 00 00 00 00 0b 5f 67 6f
000040 6f 67 6c 65 63 61 73 74 04 5f 74 63 70 05 6c 6f
000050 63 61 6c 00 00 0c 00 01
~~ SNIP  1 - size:  20 byte, type: NETTYPE_NETIF (-1)
if_pid: 4  rssi: 0  lqi: 0
flags: 0x0
src_l2addr: ce:5f:e5:a4:e1:75
dst_l2addr: 33:33:00:00:00:fb
~~ PKT    -  2 snips, total size: 108 byte

3. RIOT OS sample code 소개 
이번 절에서는 RIOT OS sample code 중 하나를 택하여, RIOT OS가 어떤 형태로 구현되는지를 확인해 보고자 한다.

----------------------------------------------------------------------------------------------------------------------------------------------------
<ipc_pingpong/main.c> /* 마치 linux에서 thread application을 만드는 것과 유사함 */
/*
 * 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     examples
 * @{
 *
 * @file
 * @brief       IPC pingpong application
 *
 * @author      Kaspar Schleiser <kaspar@schleiser.de>
 * @author      Hauke Petersen <hauke.petersen@fu-berlin.de>
 *
 * @}
 */

#include <stdio.h>

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

void *second_thread(void *arg)   /* main thread에서 생성한 두번째 thread(함수) */
{
    (void) arg;

    printf("2nd thread started, pid: %" PRIkernel_pid "\n", thread_getpid());
    msg_t m;  /* message 변수를 하나 선언한다 */

    while (1) {
        msg_receive(&m);   /* main thread에서 오는 message를 수신한다 */
        printf("2nd: Got msg from %" PRIkernel_pid "\n", m.sender_pid);
        m.content.value++;
        msg_reply(&m, &m);  /* main thread에게 응답 message를 보낸다 */
    }

    return NULL;
}

char second_thread_stack[THREAD_STACKSIZE_MAIN];

int main(void)   /* main thread */
{
    printf("Starting IPC Ping-pong example...\n");
    printf("1st thread started, pid: %" PRIkernel_pid "\n", thread_getpid());

    msg_t m;   /* message 변수를 하나 선언한다 */

    kernel_pid_t pid = thread_create(second_thread_stack, sizeof(second_thread_stack),
                            THREAD_PRIORITY_MAIN - 1, THREAD_CREATE_STACKTEST,
                            second_thread, NULL, "pong");   /* thread를 하나 만든다 */

    m.content.value = 1;

    while (1) {
        msg_send_receive(&m, &m, pid);   /* 메시지를 다른 thread로 보낸다. 또한 다른 thread로 부터 도착한 메시지를 수신한다 */
        printf("1st: Got msg with content %u\n", (unsigned int)m.content.value);
    }
}
----------------------------------------------------------------------------------------------------------------------------------------------------
<Makefile>
# name of your application
APPLICATION = ipc_pingpong

# 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)/../..

# 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

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

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

<How to build it>
$ docker run -i -t -u $UID -v $(pwd):/data/riotbuild --env BOARD=native riotbuild make -C examples/ipc_pingpong clean all
make: Entering directory '/data/riotbuild/examples/ipc_pingpong'
Building application "ipc_pingpong" for "native" with MCU "native".

"make" -C /data/riotbuild/boards/native
"make" -C /data/riotbuild/boards/native/drivers
"make" -C /data/riotbuild/core
"make" -C /data/riotbuild/cpu/native
"make" -C /data/riotbuild/cpu/native/periph
"make" -C /data/riotbuild/drivers
"make" -C /data/riotbuild/sys
"make" -C /data/riotbuild/sys/auto_init
   text   data    bss    dec    hex filename
  23137    372  60004  83513  14639 /data/riotbuild/examples/ipc_pingpong/bin/native/ipc_pingpong.elf
make: Leaving directory '/data/riotbuild/examples/ipc_pingpong'

<How to run it>
$ ./examples/ipc_pingpong/bin/native/ipc_pingpong.elf tap0
This is RIOT! (Version: xxx)
kernel_init(): jumping into first task...
Starting IPC Ping-pong example...
1st thread started, pid: 1
2nd thread started, pid: 2
2nd: Got msg from 1
1st: Got msg with content 2
2nd: Got msg from 1
1st: Got msg with content 3
2nd: Got msg from 1
1st: Got msg with content 4
2nd: Got msg from 1
1st: Got msg with content 5
2nd: Got msg from 1
1st: Got msg with content 6
2nd: Got msg from 1
1st: Got msg with content 7
2nd: Got msg from 1
1st: Got msg with content 8
2nd: Got msg from 1
1st: Got msg with content 9
2nd: Got msg from 1
1st: Got msg with content 10

4. Nucleo F103 보드에서의 RIOT OS 동작 시험
이번 절에서는 실제 target 보드인 Nucleo F103(MCU: STM32F103RB)에서 RIOT OS 코드를 실제로 돌려 보는 실습을 진행하도록 하겠다. Nucleo F103 보드는 ST-Link V2-1 debugger(programmer)가 내장(아래 그림 4.1에서 MCU part와 ST-Link V2-1 part가 구분되어 있다)되어 있어, 별도로 ST-Link V2-1 programmer를 구입할 필요가 없다. 뿐만 아니라 USB port를 통한 console 출력이 가능한 장점이 있다(즉, 별도의 serial cable이 필요치 않다).

그림 4.1 Nucleo F103RB 보드(ST-Link V2-1이 on board되어 있음 - 그림 상단 부분)

그림 4.2 Nucleo F103RB 보드 block diagram

그림 4.3 Nucleo F103RB Top layout


그림 4.4 Nucleo F103RB 보드 주요 Spec

그림 4.5 Nucleo F103RB 보드 external pinout

자, 그럼 먼저 docker 환경을 기반으로 nucleo f103 보드에 맞게 cross compile을 진행해 보도록 하자. 테스트에 사용할 example program은 앞서 3절에서 살펴 본 ipc_pingpong이 되겠다.

<How to build it>
$ docker run -i -t -u $UID -v $(pwd):/data/riotbuild --env BOARD=nucleo-f103 riotbuild make -C examples/ipc_pingpong clean all
  => clean build를 진행한다.
make: Entering directory '/data/riotbuild/examples/ipc_pingpong'
Building application "ipc_pingpong" for "nucleo-f103" with MCU "stm32f1".

"make" -C /data/riotbuild/boards/nucleo-f103
"make" -C /data/riotbuild/core
"make" -C /data/riotbuild/cpu/stm32f1
"make" -C /data/riotbuild/cpu/cortexm_common
"make" -C /data/riotbuild/cpu/cortexm_common/periph
"make" -C /data/riotbuild/cpu/stm32_common
"make" -C /data/riotbuild/cpu/stm32_common/periph
"make" -C /data/riotbuild/cpu/stm32f1/periph
"make" -C /data/riotbuild/drivers
"make" -C /data/riotbuild/drivers/periph_common
"make" -C /data/riotbuild/sys
"make" -C /data/riotbuild/sys/auto_init
"make" -C /data/riotbuild/sys/isrpipe
"make" -C /data/riotbuild/sys/newlib
"make" -C /data/riotbuild/sys/pm_layered
"make" -C /data/riotbuild/sys/tsrb
"make" -C /data/riotbuild/sys/uart_stdio
   text   data    bss    dec    hex filename
   9324    136   4292  13752   35b8 /data/riotbuild/examples/ipc_pingpong/bin/nucleo-f103/ipc_pingpong.elf
make: Leaving directory '/data/riotbuild/examples/ipc_pingpong'
  => build 결과, ipc_pingpong.elf, ipc_pingpong.hex 파일 등이 생성된다.


ST-Link/V2-1 programmer를 통해 Nucleo F103 보드(정확히는 flash memory)에 code를 올리기(flash writing) 위해서는 OpenOCD를 사용(Atollic TrueSTUDIO, IAR EWARM, Keil MDK-ARM, TASKING VX-toolset 등을 사용할 수도 있겠으나, 여기서는 OpenOCD를 사용해 보기로 하겠음) 해야 한다. 필자의 경우는 docker 환경을 사용하고 있는 관계로, openocd를 docker 환경에 직접 설치해 주어야만 했다(Ubuntu Host에 openocd를 설치해야 하는 것으로 생각하고 테스트해 보았으나, 동작에 문제가 있었음).

참고) OpenOCD는 open source JTAG debugging S/W로 고가의 debugging tool을 대신할 수 있는 훌륭한 도구이다.

<How to setup the OpenOCD>
  => RIOT OS Dockerfile에는 OpenOCD가 포함되어 있지 않으므로 직접 설치를 진행하도록 하자.

docker run -i -t -u root riotbuild /bin/bash
  => docker OS(Ubuntu 14.04)로 진입한다.
# apt-get update
# apt-get -y install build-essential autoconf automake libtool libusb-dev libusb-1.0-0-dev libhidapi-dev
  => 필요에 따라서는 위의 패키지를 설치해 주어야 한다.
# apt-get -y install openocd
  => openocd package를 설치해 준다.
# openocd --version
  => openocd 0.9.0 version이 설치되었다(참고: recent version은 0.10.0이지만, 테스트에는 전혀 문제가 없음)
Open On-Chip Debugger 0.9.0 (2015-09-02-10:42)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
# exit
$ docker commit -m "add openocd" -a "Chunghan Yi" 976136ceb63e riotbuild
sha256:d3281c459278304f0b279f41586dfe8d495ef349482a224f9cd7562c871caa86
  => 지금까지 수정한 사항(docker 상에서 수정한 내용)을 반영(commit)하도록 한다.

여기서 잠깐 ! openocd를 Host OS에서 직접 제어하려면 ....
$ sudo openocd -f interface/stlink-v2-1.cfg -f target/stm32f1x.cfg 

그림 4.6 Host PC에서 openocd 실행 모습

<다른 terminal>
$ telnet localhost 4444

그림 4.7 telnet으로 openocd 제어하는 모습
---------------------------------------------------------------------------------------------------------------------------------------

<How to flash it with OpenOCD>
$ docker run -i -t -u $UID -v $(pwd):/data/riotbuild --env BOARD=nucleo-f103 riotbuild make -C examples/ipc_pingpong flash
   => 이 경우, "Error: libusb_open() failed with LIBUSB_ERROR_NO_DEVICE" 에러가 발생하였고, 여러가지 해결책을 강구해 보았으나, 여유치 않았음.

BOARD=nucleo-f103 make -C examples/ipc_pingpong flash
   => flash 명령의 경우는 docker 환경이 굳이 필요 없으므로, Ubuntu Host(16.04)에서 직접 진행하도록 하자.
make: 디렉터리 '/home/chyi/IoT/RIOT_OS/RIOT/examples/ipc_pingpong' 들어감
Building application "ipc_pingpong" for "nucleo-f103" with MCU "stm32f1".

"make" -C /home/chyi/IoT/RIOT_OS/RIOT/boards/nucleo-f103
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/core
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/cpu/stm32f1
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/cpu/cortexm_common
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/cpu/cortexm_common/periph
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/cpu/stm32_common
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/cpu/stm32_common/periph
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/cpu/stm32f1/periph
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/drivers
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/drivers/periph_common
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/sys
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/sys/auto_init
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/sys/isrpipe
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/sys/newlib
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/sys/pm_layered
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/sys/tsrb
"make" -C /home/chyi/IoT/RIOT_OS/RIOT/sys/uart_stdio
   text   data    bss    dec    hex filename
   9276    136   4292  13704   3588 /home/chyi/IoT/RIOT_OS/RIOT/examples/ipc_pingpong/bin/nucleo-f103/ipc_pingpong.elf
/home/chyi/IoT/RIOT_OS/RIOT/dist/tools/openocd/openocd.sh flash
### Flashing Target ###
Open On-Chip Debugger 0.9.0 (2015-09-02-10:42)
Licensed under GNU GPL v2
For bug reports, read
http://openocd.org/doc/doxygen/bugs.html
Info : The selected transport took over low-level target control. The results might differ compared to plain JTAG/SWD
adapter speed: 1000 kHz
adapter_nsrst_delay: 100
none separate
srst_only separate srst_nogate srst_open_drain connect_deassert_srst
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : Unable to match requested speed 1000 kHz, using 950 kHz
Info : clock speed 950 kHz
Info : STLINK v2 JTAG v28 API v2 SWIM v17 VID 0x0483 PID 0x374B
Info : using stlink api v2
Info : Target voltage: 3.246781
Info : stm32f1x.cpu: hardware has 6 breakpoints, 4 watchpoints
    TargetName         Type       Endian TapName            State       
--  ------------------ ---------- ------ ------------------ ------------
 0* stm32f1x.cpu       hla_target little stm32f1x.cpu       running
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x080003f0 msp: 0x20000200
auto erase enabled
Info : device id = 0x20036410
Info : flash size = 128kbytes
target state: halted
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000003a msp: 0x20000200
wrote 10240 bytes from file /home/chyi/IoT/RIOT_OS/RIOT/examples/ipc_pingpong/bin/nucleo-f103/ipc_pingpong.hex in 0.609044s (16.419 KiB/s)
target state: halted
target halted due to debug-request, current mode: Thread 
xPSR: 0x01000000 pc: 0x080003f4 msp: 0x20000200
target state: halted
target halted due to breakpoint, current mode: Thread 
xPSR: 0x61000000 pc: 0x2000002e msp: 0x20000200
verified 9412 bytes in 0.151603s (60.628 KiB/s)
shutdown command invoked
Done flashing
make: 디렉터리 '/home/chyi/IoT/RIOT_OS/RIOT/examples/ipc_pingpong' 나감
  => 정상적으로 flashing(image writing to flash memory)이 진행되었다.

<pyterm으로 동작 확인하기>
  => RIOT OS는 python으로 작성된 pyterm이라는 terminal emulator를 자체적으로 제공한다.
sudo pip install pyserial
  => pyterm 실행 시 문제(import시 serial이 없다는 에러 발생)가 있어 설치를 진행함.

$ BOARD=nucleo-f103 make -C examples/ipc_pingpong term -p /dev/ttyACM0 -b 115200
  => /dev/ttyUSB0가 아님에 주의^^
  => 그림 4.9처럼 debug message가 console로 출력되고 있음.
  => $ ./pyterm -p /dev/ttyACM0 -b 115200 이렇게 해도 됨.


그림 4.8 dmesg 내용 - /dev/ttyACM0 관련 확인


그림 4.9 pyterm 실행 모습

<gdb로 debugging하기>
$ BOARD=nucleo-f103 make -C examples/ipc_pingpong debug

그림 4.10 gdb로 debugging하는 모습 

참고 사항: gdb로 debugging이 가능하다는 얘기는 eclipse로도 가능하다는 얘기가 됨.

아래 그림은 ipc_pingpong이 동작 중인 Nucleo F103RB 보드의 모습을 보여준다.

그림 4.11 Nuceo F103RB 보드 - ipc_pingpong program으로 실행한 모습


5. Nucleo F103 보드 Boot Flow 분석
이번 절에서는 Nucleo F103 보드의 부팅 코드(보드 및 cpu 초기화, thread 생성 및 main 함수 호출까지의 과정)를 대략적으로 분석해 보고자 한다.
----------------------------------------------------------------------------------------------------------------------------------------------------
ENTRY(reset_handler_default)                ... cpu/cortexm_common/ldscripts/cortexm_base.ld
 |
V
reset_handler_default( )                          ... cpu/cortexm_common/vectors_cortexm.c
{
/* load data section from flash to ram */
for (dst = &_srelocate; dst < &_erelocate; ) {
        *(dst++) = *(src++);
     }
     /* default bss section to zero */
     for (dst = &_szero; dst < &_ezero; ) {
        *(dst++) = 0;
     }

board_init( )                                      ... boards/nucleo-f103/board.c
{
cpu_init( )                                    ... cpu/stm32f1/cpu.c
{
cortexm_init( )
clk_init( )
periph_init( )
}
gpio_init( )
}

     kernel_init( )                                   ... core/kernel_init.c
{
thread_create(idle_stack ... idle_thread ...)   /* idle thread 생성 */
               |
              V
               idle_thread( )
               {
                     while (1) {
                             pm_set_lowest();
                     }
               }

thread_create(main_stack ... main_trampoline ...)    /* main thread 생성 */
              |
             V
              main_trampoline( )           ... core/kernel_init.c
              {
                     main( )                  /* 여기서 main 함수가 호출됨 */
              }

cpu_switch_context_exit( )   .... cpu/atmega_common/thread_arch.c
              |
             V
              {
                     __asm__ volatile (
                     "bl     irq_arch_enable              \n"    /* enable IRQs to make the SVC interrupt is reachable */
                     "svc    #1                                   \n"    /* trigger the SVC interrupt */
                     "unreachable%=:                      \n"    /* this loop is unreachable */
                     "b      unreachable%=               \n"    /* loop indefinitely */
                     :::);
              }
}
}
----------------------------------------------------------------------------------------------------------------------------------------------------

이상의 흐름이 정확한 것인지를 확인하기 위해서는 추후 Eclipse와 JTAG emulator를 사용하여 trace해 보아야 할 것 같다(TBD).


References
1. https://github.com/RIOT-OS/RIOT/wiki
2. http://riot-os.org/api/
3. UM1724 User manual STM32 Nucleo-64 board, www.st.com
4. RM0008 Reference manual STM32F101xx, STM32F102xx, STM32F103xx, STM32F105xx and STM32F107xx advanced ARM®-based 32-bit MCUs, www.st.com
5. UM1075 User manual ST-LINK/V2 in-circuit debugger/programmer for STM8 and STM32, www.st.com
6. The definitive guide to ARM Cortex-M3 and Cortex-M4 processors, 3rd edition, Joseph Yiu


Slowboot