RTOS3時間クッキング

これはカーネルVM Advent Calendar51日目の担当として書いた記事です。

前置き

i386規模になると経験者でなければ無理ですが、AVR/PIC/M16C等の8bit〜16bitマイコンならば日曜日の午後の空いた時間を使ってRTOSっぽいものが作れると主張しているのですが、中々賛同が得られません。
OSを作るのは簡単でしかも楽しいと言うことを主張するために、実際にAVR上に3時間程度で簡単なRTOSを作ってみました。

制約等

今回は3時間程度で作るために以下の部分で手を抜いています。

  • タスクのスタックエリアはグローバル変数領域を使います。
  • スケジューリングは静的有線順位付きのプリエンプティブスケジューリングです。
  • 優先度はタスクのIDを用います。N個のタスクの場合、ID:0のタスクがが最も高い優先度のINITタスクとなり、id:N-1が最も低い優先度を持つDIAGタスクとなります。
  • タスク数はコンパイル時に決定します。動的な生成は行いません。
  • システムコールはタスクの起動、終了、休眠、復帰のみです。
  • 動作の確認は以下の環境で行っています。
    • ATMega328P
      • 自作評価ボード
      • AVRStudio4.19付属シミュレータ
    • ATMega186
      • Arduino Duemilanove (ATMega186)
      • AVRStudio5付属シミュレータ

開発環境

  • Windows XP Professional SP3
  • AVRStudio 4.18.684
  • WinAVR-20090313

ソースコード

#include <avr/interrupt.h>

#define TASK_NUM (4)
#define NULL ((void*)0)
#define STACK_SIZE (64)
#define KERNEL_STACK_SIZE (64)

typedef void (*task_proc_t)(void* param);
unsigned char stack[TASK_NUM][STACK_SIZE];
extern const task_proc_t task_proc[TASK_NUM];

typedef enum {
    NON_EXISTS,     /**< 未初期化状態  */
    DORMANT,        /**< 休止状態:未起動、実行可能 */ 
    READY,          /**< 実行状態:起動済み、実行可能 */
    PAUSE,          /**< 待ち状態:起動済み、実行不可能 */
} state_t;

typedef struct tcb_t {
    unsigned char*    stack_pointer;   /**< スタックポインタ */
    state_t            state;          /**< タスクの状態 */
} tcb_t;

typedef struct context_t {
    unsigned char R31, R30, R29, R28, R27, R26, R25, R24, R23, R22, R21, R20, R19, R18, R17, R16, 
                  R15, R14, R13, R12, R11, R10, R9 , R8 , R7 , R6 , R5 , R4 , R3 , R2 , R1; /**< R1〜R31までの汎用レジスタ */
    unsigned char RSTATUS;    /**< ステータスフラグ */
    unsigned char R0;        /**< 汎用レジスタR0 */
    unsigned char return_address_high;    /**< 戻り先アドレスの上位8bit */
    unsigned char return_address_low;    /**< 戻り先アドレスの下位8bit */
} context_t ;

tcb_t tcb[TASK_NUM];

tcb_t *currentTCB;

#define SAVE_CONTEXT()                        \
asm volatile (                                \
    "push    r0                     \n\t"     \
    "in      r0   ,__SREG__         \n\t"     \
    "cli                            \n\t"     \
    "push    r0                     \n\t"     \
    "push    r1                     \n\t"     \
    "clr     r1                     \n\t"     \
    "push    r2                     \n\t"     \
    "push    r3                     \n\t"     \
    "push    r4                     \n\t"     \
    "push    r5                     \n\t"     \
    "push    r6                     \n\t"     \
    "push    r7                     \n\t"     \
    "push    r8                     \n\t"     \
    "push    r9                     \n\t"     \
    "push    r10                    \n\t"     \
    "push    r11                    \n\t"     \
    "push    r12                    \n\t"     \
    "push    r13                    \n\t"     \
    "push    r14                    \n\t"     \
    "push    r15                    \n\t"     \
    "push    r16                    \n\t"     \
    "push    r17                    \n\t"     \
    "push    r18                    \n\t"     \
    "push    r19                    \n\t"     \
    "push    r20                    \n\t"     \
    "push    r21                    \n\t"     \
    "push    r22                    \n\t"     \
    "push    r23                    \n\t"     \
    "push    r24                    \n\t"     \
    "push    r25                    \n\t"     \
    "push    r26                    \n\t"     \
    "push    r27                    \n\t"     \
    "push    r28                    \n\t"     \
    "push    r29                    \n\t"     \
    "push    r30                    \n\t"     \
    "push    r31                    \n\t"     \
    "lds     r26  , currentTCB      \n\t"     \
    "lds     r27  , currentTCB + 1  \n\t"     \
    "in      r0   , __SP_L__        \n\t"     \
    "st      x+   , r0              \n\t"     \
    "in      r0   , __SP_H__        \n\t"     \
    "st      x+   , r0              \n\t"     \
);

#define RESTORE_CONTEXT()                          \
asm volatile (                                     \
    "lds     r26        , currentTCB     \n\t"     \
    "lds     r27        , currentTCB + 1 \n\t"     \
    "ld      r28        , x+             \n\t"     \
    "out     __SP_L__   , r28            \n\t"     \
    "ld      r29        , x+             \n\t"     \
    "out     __SP_H__   , r29            \n\t"     \
    "pop     r31                         \n\t"     \
    "pop     r30                         \n\t"     \
    "pop     r29                         \n\t"     \
    "pop     r28                         \n\t"     \
    "pop     r27                         \n\t"     \
    "pop     r26                         \n\t"     \
    "pop     r25                         \n\t"     \
    "pop     r24                         \n\t"     \
    "pop     r23                         \n\t"     \
    "pop     r22                         \n\t"     \
    "pop     r21                         \n\t"     \
    "pop     r20                         \n\t"     \
    "pop     r19                         \n\t"     \
    "pop     r18                         \n\t"     \
    "pop     r17                         \n\t"     \
    "pop     r16                         \n\t"     \
    "pop     r15                         \n\t"     \
    "pop     r14                         \n\t"     \
    "pop     r13                         \n\t"     \
    "pop     r12                         \n\t"     \
    "pop     r11                         \n\t"     \
    "pop     r10                         \n\t"     \
    "pop     r9                          \n\t"     \
    "pop     r8                          \n\t"     \
    "pop     r7                          \n\t"     \
    "pop     r6                          \n\t"     \
    "pop     r5                          \n\t"     \
    "pop     r4                          \n\t"     \
    "pop     r3                          \n\t"     \
    "pop     r2                          \n\t"     \
    "pop     r1                          \n\t"     \
    "pop     r0                          \n\t"     \
    "out     __SREG__,    r0             \n\t"     \
    "pop     r0                          \n\t"     \
);

unsigned char kernel_stack[KERNEL_STACK_SIZE];

#define SET_KERNEL_STACKPOINTER()                         \
    asm volatile (                                        \
        "ldi    r28     , lo8(kernel_stack+%0)     \n\t"  \
        "ldi    r29     , hi8(kernel_stack+%0)     \n\t"  \
        "out    __SP_H__, r29                      \n\t"  \
        "out    __SP_L__, r28                      \n\t"  \
        : : "M"((unsigned char)(KERNEL_STACK_SIZE-1))        \
    );

typedef enum syscallid_t {
    SVCID_startTASK,
    SVCID_exitTASK,
    SVCID_pauseTASK,
    SVCID_resumeTASK,
} syscallid_t;

typedef struct syscallparam_t {
    syscallid_t id;
    int         result;
} syscallparam_t;

typedef struct PAR_startTASK_t {
    syscallparam_t syscallparam;
    int id;
    void *arg;
} PAR_startTASK_t;

typedef struct PAR_exitTASK_t {
    syscallparam_t syscallparam;
} PAR_exitTASK_t;

typedef struct PAR_pauseTASK_t {
    syscallparam_t syscallparam;
} PAR_pauseTASK_t;

typedef struct PAR_resumeTASK_t {
    syscallparam_t syscallparam;
    int id;
} PAR_resumeTASK_t;

__attribute__ ((naked))
void syscall(register struct syscallparam_t* param) {
    (void)param;
    asm volatile(
        "    sbi    0x0B,    2      \n\t" /**< 外部割り込み0番のポートに1を出力すると割り込み発生。 */
        "    ret                    \n\t" /**< 外部割り込みから戻ってくるとここから実行が再開 */
    );
}

int startTASK(int id, void* arg) {
    PAR_startTASK_t param;
    param.syscallparam.id = SVCID_startTASK;
    param.id = id;
    param.arg = arg;
    syscall((syscallparam_t*)&param);
    return     param.syscallparam.result;
}

int exitTASK(void) {
    PAR_exitTASK_t param;
    param.syscallparam.id = SVCID_exitTASK;
    syscall((syscallparam_t*)&param);
    return     param.syscallparam.result;
}

int pauseTASK(void) {
    PAR_pauseTASK_t param;
    param.syscallparam.id = SVCID_pauseTASK;
    syscall((syscallparam_t*)&param);
    return     param.syscallparam.result;
}

int resumeTASK(int id) {
    PAR_resumeTASK_t param;
    param.syscallparam.id = SVCID_resumeTASK;
    param.id = id;
    syscall((syscallparam_t*)&param);
    return     param.syscallparam.result;
}


/** 外部割り込み0の割り込み処理を記述する */
__attribute__ ( ( signal, naked ) )  void INT0_vect(void);
void INT0_vect(void) {
    extern void syscall_entry(void);

    /* 割り込みの直前まで実行されていたコンテキストを保存 */
    SAVE_CONTEXT();

    /* カーネルスタックに切り替え */
    SET_KERNEL_STACKPOINTER();

    /* EXT0割り込みをリセット */
    PORTD &= ~_BV(PORTD2);

    /* システムコール呼び出し処理に移動 */
    syscall_entry();
}

int SVC_startTASK(struct PAR_startTASK_t *par) {
    if (par == NULL) { return -1; }
    if (par->id <  0) { return -1; }
    if (par->id >= 8) { return -1; }
    if (tcb[par->id].state == DORMANT) {
        context_t* ctx = (context_t*)(tcb[par->id].stack_pointer+1);
        tcb[par->id].state = READY;
        ctx->R25 = ((unsigned short)par->arg) >> 8;
        ctx->R24 = ((unsigned short)par->arg) & 0xFF;
        return 1;
    } else {
        return 0;
    }
}

int SVC_exitTASK(struct PAR_exitTASK_t *par) {
    if (par == NULL) { return -1; }
    if (currentTCB == NULL) { return -1; }
    if (currentTCB->state == READY) {
        context_t* ctx = NULL;
        int i = currentTCB - tcb;
        currentTCB->stack_pointer = (&stack[i][STACK_SIZE-1]);
        currentTCB->stack_pointer -= sizeof(context_t);
        ctx = (context_t*)(currentTCB->stack_pointer+1);
        ctx->return_address_high = (unsigned char)(((unsigned short)task_proc[i]) >> 8);
        ctx->return_address_low  = (unsigned char)(((unsigned short)task_proc[i]) & 0xFF);
        ctx->R1 = 0x00;
        currentTCB->state = DORMANT;

        return 1;
    } else {
        return 0;
    }
}

int SVC_pauseTASK(struct PAR_pauseTASK_t *par) {
    if (par == NULL) { return -1; }
    if (currentTCB == NULL) { return -1; }
    if (currentTCB->state == READY) {
        currentTCB->state = PAUSE;
        return 1;
    } else {
        return 0;
    }
}

int SVC_resumeTASK(struct PAR_resumeTASK_t *par) {
    if (par == NULL) { return -1; }
    if (par->id <  0) { return -1; }
    if (par->id >= 8) { return -1; }
    if (tcb[par->id].state == PAUSE) {
        tcb[par->id].state = READY;
        return 1;
    } else {
        return 0;
    }
}

void syscall_entry(void) {
    extern void schedule();

    context_t* ctx;
    syscallparam_t *par;

    /* 呼び出し元のコンテキストからシステムコールの引数を得る */
    ctx = (context_t*)(currentTCB->stack_pointer+1);
    par = (syscallparam_t *)((ctx->R25 << 8) | (ctx->R24));

    /* システムコールのIDに応じた処理 */
    switch (par->id) {
        case SVCID_startTASK   : par->result = SVC_startTASK((PAR_startTASK_t*)par); break;
        case SVCID_exitTASK    : (void)        SVC_exitTASK((PAR_exitTASK_t*)par); break;
        case SVCID_pauseTASK   : par->result = SVC_pauseTASK((PAR_pauseTASK_t*)par); break;
        case SVCID_resumeTASK  : par->result = SVC_resumeTASK((PAR_resumeTASK_t*)par); break;
        default                : par->result = -1; break;
    }

    schedule();
    RESTORE_CONTEXT();
    asm volatile ("reti");
}

void schedule(void) {
    int i;
    for (i=0; i<TASK_NUM; i++) {
        if (tcb[i].state == READY) {
            currentTCB = &tcb[i];
            return;
        }
    }
    currentTCB = NULL;
}

__attribute__ ((naked)) 
void start(void) {
    extern void reset();

    /* 外部割り込み INT0(PD2) は出力モード */
    DDRD  |=  _BV(PORTD2);
    PORTD &= ~_BV(PORTD2);

    /* 外部割り込み条件: INT0(PD2) の立ち上がりで発生 */
    EICRA |= (_BV(ISC01)|_BV(ISC00));

    /* 外部割り込みマスクレジスタ: INT0(PD2) の割り込みを許可 */
    EIMSK |= _BV(INT0);

    reset();    
    schedule();
    RESTORE_CONTEXT();
    asm volatile ("reti");
}

void reset(void) {
    int i;
    for (i=0; i<TASK_NUM; i++) {
        context_t *ctx;
        tcb[i].stack_pointer = (&stack[i][STACK_SIZE-1]);
        tcb[i].stack_pointer -= sizeof(context_t);
        ctx = (context_t*)(tcb[i].stack_pointer+1);
        ctx->return_address_high = (unsigned char)(((unsigned short)task_proc[i]) >> 8);
        ctx->return_address_low  = (unsigned char)(((unsigned short)task_proc[i]) & 0xFF);
        ctx->R1 = 0x00;
        tcb[i].state = DORMANT;
    }

    tcb[TASK_NUM-1].state = READY;
}

/* 以下はユーザー機能として実装する部分 */

#include <stdio.h>

#define LED_DDR  DDRB
#define LED_PORT PORTB
#define LED_PIN  PINB
#define LED      PINB5

void delay(volatile unsigned long n) {
    while (n > 0) {
        n--;
    }
}

void task0_proc(void* param) {
    cli();
    printf("task0 enter\r\n");
    sei();

    /* turn on LED */
    LED_PORT |= _BV(LED);
    delay(800000);
    LED_PORT &= ~_BV(LED);
    delay(800000);

    cli();
    printf("task0 leave\r\n");
    sei();
    exitTASK();
}

void task1_proc(void* param) {
    cli();
    printf("task1 enter\r\n");
    sei();
    startTASK(0, NULL);
    cli();
    printf("task1 leave\r\n");
    sei();
    exitTASK();
}

void task2_proc(void* param) {
    cli();
    printf("task2 enter\r\n");
    sei();
    startTASK(1, NULL);
    cli();
    printf("task2 leave\r\n");
    sei();
    exitTASK();
}

void task3_proc(void* param) {
    cli();
    printf("task3 enter\r\n");
    sei();
    for (;;) {
        startTASK(2, NULL);
    }
}

const task_proc_t task_proc[TASK_NUM] = {
    task0_proc,
    task1_proc,
    task2_proc,
    task3_proc,
};

int uart_putchar(char c, FILE *fp) {
    loop_until_bit_is_set(UCSR0A, UDRE0);
    UDR0 = c;
    return 0;
}

int main(void) {
    LED_DDR |= _BV(LED);

    fdevopen( uart_putchar, NULL);

    printf("Start Kernel.\n");

    start();

    for (;;) {}    /* ここが実行されることはない */
}

実装解説

コンテキストスイッチの実装

手順は以下のようになっています。

  1. 現在の実行コンテキストに関する情報をスタック上に保存
  2. スタックポインタをタスクコントロールブロックに保存
  3. 切り替え先のタスクのスタックポインタを設定
  4. 実行コンテキストを復帰
タスクの起動

タスクを起動する際には、つじつまが合うようにスタック上のコンテキスト構造体を埋めてから復帰させればいいことになります。

スケジューラ

TCB配列の先頭から最初のREADY状態のタスクを選択しているだけです。

カーネルモードとシステムコールの追加

通常通りソフトウェア割り込み経由でカーネルモードに切り替えようと思いましたが、AVRにはソフトウェア割り込みがありません。通常はここで頭を抱えるのですが、AVRでは面白い仕掛けが施されています。通常、外部割り込みのポートは入力専用になっているのですが、AVRではこれを出力モードに設定でき、この状態の外部割り込みのポートに'1'を出力するとソフトウェアから外部割り込みを発生させることができます。そこで、これをソフトウェア割り込みの代用として利用しています。

まとめ

このように、素直なアーキテクチャのCPUならば3時間程度でOSの骨組みが作れてしまいます。さらに、Arduino Duemilanoveのように安価で簡単な開発環境も存在します。興味を持たれた方は、これをきっかけにOS自作やTOPPERS-ASPなどの世界に踏み込んでみませんか?

最後に

元々は教育用として作成した教材の派生物ですが、もう少し多機能にしてCortex-M3(NXP Xpresso 1343)に移植したりしたRTOSBSDライセンスとして以下のURLで公開しています。
http://www.rts.soft.iwate-pu.ac.jp/mono/kernel/ukernel-s/index.html

STM32 Value line discovery

ET2010にてSTマイクロエレクトロニクス様から STM32 Value line discovery(STM32VLD)を一ついただきました。

STM32VLDはCortex-M3JTAGを搭載した安価・高機能・開発環境の導入が容易なマイコン評価ボードで、同様の製品にNXP様のLPCXpressoがあります。

ブースでお話を伺った際に価格は1000円くらいとのことで、秋月電子通商様で1100円で販売されておりますが、これは、AVRの自作ライタ部品一式とMega644を纏めて購入する際の価格に匹敵します。流石にペリフェラルの数やクロック数などでは劣りますが、同様の趣旨で販売されているLPCXpresso 1343は2800円のため半額以下です。
従来のホビーユースであれば、8bitマイコンや16bitマイコンが不要となってしまうほどのインパクトを持っていると言っていいでしょう。

LPCXpresso 1343 と比較

STM32VLD LPCXpresso 1343
クロック 24Mhz 72MHz
プログラム用Flash 128KB 32KB
ワーク用SRAM 8KB 8KB
タイマー 4ch 4ch
SPI
I2C
ADC 16ch/12bit 8ch/10bit
DAC 12bit/2ch -
USB - MSC/HID対応
JTAG ST-Link(付属・分離不可) NXP-Link(付属・分離可)
開発環境 IAR Embedded Workbench(無償版)/KEIL(評価版) Eclipse(LPCXpresso専用IDE)
実装済み部品 LED/2個 LED/1個
スイッチ/2個 -
ピンヘッダ 実装済 未実装

フラッシュの差が気になりますが、無償版の制約で 32KB 以上のコードを生成できないため、
gcc等の環境を使わない限り関係はありません。

開発環境選択

付属してきた日本語小冊子ではサンプルコードを含めてIAR Embedded Workbench(無償版)/KEIL(評価版)を奨励していますが、
これらにはコードサイズに制約などがあり、128KBのFLASH領域を活用できません。

従来のARM開発では CodeSourcery もしくは YAGARTO の arm-none-eabi-gcc を利用するのが定石となっていますが、
こちらも以下の理由で今回は使用しません。

  • 開発環境構築に手間がかかる
    • 手軽に使えるボードの開発環境構築に手間をかけては本末転倒である
  • 大多数の方々が導入方法レポートを掲載している
    • わざわざ同じ事を行う必要は無い。
  • ST-LINKを用いたデバッグに難がある

今回は、手軽に導入できるARM開発環境の一つである Atollic TrueSTUDIO/STM32 Lite を利用しました。

  • よくあるEclipse ベースの IDE
  • インストーラを実行するだけで導入可能
    • 個人情報を登録して利用コードの発行が必要
  • STM32VLD に標準対応しており、プロジェクトウィザードでSTM32VLDを選ぶだけでサンプルコード付きのプロジェクトが生成される
  • ST-LINK経由での書き込みやソースコードデバッグIDE上から可能
  • Lite版はC++言語が使えず、ST-LINK 以外のデバッグインタフェースには対応しない
    • 従来のARM開発には向かないが、TM32VLDではST-LINKが標準搭載のため、この制約がマイナスにならない
  • IAR Embedded Workbenchの無償版やKEILの評価版と違い、生成されるコードサイズに制約がない
  • 利用期間の期限がない
    • インストール後30日はPro版と同等の機能が使える
    • あるタイミングでPro版の広告が表示される

開発環境導入

  1. Atollic社のダウンロードページ から Free版をダウンロード
  2. ダウンロードしたインストーラを起動
  3. 途中で認証用のキーと認証用のページが表示されるので、個人情報とキーを登録
  4. メールで送られてくる登録用のキーを入力してインストールを継続
  5. 以上

プロジェクト作成

ウィザードに答えるだけでサンプルコード付きのプロジェクトが生成されます。

  1. スタートメニューからAtollic TrueSTUDIO STM32 Liteを起動
  2. File > New > C Project と選択
  3. プロジェクト名を適当に設定し、Projet type: は Executable の STM32 C Projectを選択して Nextを選択
  4. TargetのEvaluation boardからSTM32_Discoveryを選択してNextを選択
  5. JTAG Probe がST-LINKになっていることを確認してFinishを選択
  6. 以上

デバッグ

非常に簡単です。

  1. STM32VLDとPCをUSBケーブルで接続する
  2. Project Explorer 上で現在のプロジェクトを右クリックし、Debug as > Embedded C/C++ Application を選択
  3. 広告が表示されるのでボタンをクリックして閉じる
  4. コンパイルされたバイナリがダウンロードされて実行され、mainの最初でブレークする。

まとめ

Atollic TrueSTUDIO STM32 Liteを用いることで、非常に制約の少ない開発環境を容易に構築できることが確認できました。

謝辞

FatFS R0.08a で現在のディレクトリ名を取得する

本文書の取り扱い

クリエイティブ・コモンズ・ライセンスこの文書は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。

免責事項

本カスタマイズは無保証です。
使用は全て自己責任で行ってください。

概要

ChaN氏の作成されたFatFs には R0.08a でカレントディレクトリパスの取得を行う f_getcwd() が実装された。しかし、バッファサイズが満たない場合は、エラーとなるほか、カレントディレクトリ名のみが欲しい場合は、パス全体を取得して解析する必要があるため、AVRマイコンのようなメモリ量に制約のある環境では不満が残る。

小生もAVR上でFatFsを用いているが、LFNを有効にするとf_getcwd()のバッファに割り当てるSRAMの不足に悩まされた。

そこで、f_getcwd() を改変し、バッファに満たせるだけのカレントディレクトリ名のみを返す f_getcwdname() 関数を作成した。

謝辞

劇的に簡単にFATファイルシステムを扱うことが可能なFatFsを実装/公開されたChaN氏とFatFsを用いた作例や記事を公開されている方々に心よりの感謝の意を表します。

機能と制約

  • 現在のディレクトリ名をバッファにコピーする
  • バッファ・サイズが不足している場合はバッファ・サイズ-1文字をコピーし、終端にヌル文字を書き込む(strlcpy()関数相当の動作)
    • この場合の戻り値は f_getcwd() と同様にFR_NOT_ENOUGH_CORE となる

動作確認環境

  • PC環境
  • AVR環境
    • ATmega644P
    • ヒロセSDカードソケット
    • SanDisk 2.0GB SDカード
    • AVRStudio 4.18
    • WinAVR WinAVR-20100110

ソースコード

/* ff.c 2540行目と2541行目の間に挿入 */
FRESULT f_getcwdname (
    TCHAR *path,    /* Pointer to the directory path */
    UINT sz_path    /* Size of path */
)
{
    FRESULT res;
    DIR dj;
    UINT n;
    DWORD ccl;
    TCHAR *tp;
    FILINFO fno;
    DEF_NAMEBUF;


    *path = 0;
    res = chk_mounted((const TCHAR**)&path, &dj.fs, 0);    /* Get current volume */
    if (res == FR_OK) {
        INIT_BUF(dj);
        dj.sclust = dj.fs->cdir;            /* Start to follow upper dir from current dir */
        if ((ccl = dj.sclust) != 0) {    /* Repeat while current dir is a sub-dir */
            res = dir_sdi(&dj, 1);            /* Get parent dir */
            if (res != FR_OK) goto FAILED;
            res = dir_read(&dj);
            if (res != FR_OK) goto FAILED;
            dj.sclust = LD_CLUST(dj.dir);    /* Goto parent dir */
            res = dir_sdi(&dj, 0);
            if (res != FR_OK) goto FAILED;
            do {                            /* Find the entry links to the child dir */
                res = dir_read(&dj);
                if (res != FR_OK) break;
                if (ccl == LD_CLUST(dj.dir)) break;    /* Found the entry */
                res = dir_next(&dj, 0);    
            } while (res == FR_OK);
            if (res == FR_NO_FILE) res = FR_INT_ERR;/* It cannot be 'not found'. */
            if (res != FR_OK) goto FAILED;
#if _USE_LFN
            fno.lfname = path;
            fno.lfsize = sz_path;
#endif
            get_fileinfo(&dj, &fno);        /* Get the dir name and push it to the buffer */
            tp = fno.fname;
            if (_USE_LFN && *path) tp = path;

            for (n = 0; tp[n] && n < sz_path-1; n++) {
                path[n] = tp[n];
            }
            path[n] = '\0';
            if (tp[n] != '\0') res = FR_NOT_ENOUGH_CORE;
        } else {
            if ( sz_path >= 2 ) {
                path[0] = '/';
                path[1] = '\0';
            } else {
                if ( sz_path == 1 ) {
                    path[0] = '\0';
                }
            	res = FR_NOT_ENOUGH_CORE;
            }
        }
FAILED:
        FREE_BUF();
    }

    LEAVE_FF(dj.fs, res);

}
/* ff.h 430行目と431行目の間に挿入 */
FRESULT f_getcwdname (TCHAR*, UINT);

スキャナから画像をPDFとして取り込む scan2pdf

免責事項

scan2pdfはフリー・ソフトウェアであり、また無保証です。
使用は全て自己責任で行ってください。

これはなに?

TWAIN対応スキャナから読み取った画像をBMPとPDF形式で保存します。
Haru Free PDF Library IIを利用しているため Acrobat等のPDF作成ソフトが不要です。

動作確認環境

WindowsXP SP3 + Canon FB636U
Windows Vista X64 + EPSON PM-A900

ライセンス

  • Haru Free PDF Library II は Haru Free PDF Library II のライセンスに従います。
  • twain.h は TWAIN のライセンスに従います。
  • それ以外の部分は NYSL Version 0.9982 (以下に記載) に従います。

ファイル

NYSL Version 0.9982
A. 本ソフトウェアは Everyone'sWare です。このソフトを手にした一人一人が、
   ご自分の作ったものを扱うのと同じように、自由に利用することが出来ます。

  A-1. フリーウェアです。作者からは使用料等を要求しません。
  A-2. 有料無料や媒体の如何を問わず、自由に転載・再配布できます。
  A-3. いかなる種類の 改変・他プログラムでの利用 を行っても構いません。
  A-4. 変更したものや部分的に使用したものは、あなたのものになります。
       公開する場合は、あなたの名前の下で行って下さい。

B. このソフトを利用することによって生じた損害等について、作者は
   責任を負わないものとします。各自の責任においてご利用下さい。

C. 著作者人格権は sukunabikona に帰属します。著作権は放棄します。

D. 以上の3項は、ソース・実行バイナリの双方に適用されます。

AVRのUSARTを用いた3線シリアル通信

本文書の取り扱い

クリエイティブ・コモンズ・ライセンスこの文書は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。

概要

  • フロー制御を用いない三線シリアル通信をUSART割り込みとリングバッファで実装

実装方針

  • 受信可能割り込みと送信可能割り込みに対応して受信と送信を行う
  • 送受信データはリングバッファ上に格納
    • 受信可能割り込み発生時は受信データのリングバッファに受信を行う
    • 送信可能割り込み発生時は送信データのリングバッファから送信を行う
  • 割り込み禁止時間を出来る限り小さくする。
  • C言語のみで実装
  • 文字列以外のデータの送信にも対応できるように read(), write() 相当の関数として実装

開発環境

  • WindowsXP SP3
  • AVR Studio 4.18 SP2
  • TeraTermPro
  • ATmega644P

評価

  • ATmega644P (20Mhz) のUSARTを2チャンネル使用して38400bpsでPC同士で通信。
    • 送受信に用いたデータはThe Book of Genesis(聖書の創世記)
    • 送信と受信を独立テスト
      • TeraTermを用い、一方からファイル送信を実行し、反対側でファイルを受信。その後diffで比較
    • 送信と受信の混合テスト
      • TeraTermを用い、両方からほぼ同時にファイル送信と受信を実行。その後diffで比較
  • 送受信での欠落や多重受信の発生は確認されなかった。

ソースコード

  • USART0のみを利用
usart.h
/**
 * @file  usart.h
 * @brief USART通信のヘルパー関数や設定処理
 */
#ifndef __USART__H__
#define __USART__H__

/**
 * @def USART0_BAUD_RATE
 * @brief USART通信のポーレート
 */
#define USART0_BAUD_RATE 19200


/**
 * @def BUF_SIZ 
 * @brief 送受信バッファサイズ(2の倍数でUINT8で表現可能な範囲とする)
 */
#define BUF_SIZ (64)

/**
 * @typedef UINT8
 * 8bit無符号整数型
 */
typedef unsigned char UINT8;

/**
 * @brief USART通信初期化
 */
extern void usart_open(void);

/**
 * @brief USART通信終了
 */
extern void usart_close(void);

/**
 * @brief USART通信の入出力バッファをクリアする
 */
extern void usart_clear( void );

/**
 * @brief Cのread相当の動作
 * @param [in] buf 受信したバイト列を格納するバッファ
 * @param [in] count 受信したバイト列を格納するバッファのデータ長
 * @return 実際に受信したバイト長
 */
extern int usart_read(void *buf, int count);

/**
 * @brief Cのwrite相当の動作
 * @param [in] buf 送信するバイト列
 * @param [in] count 送信するバイト列のデータ長
 * @return 送信したバイト長
 */
extern int usart_write(void *buf, int count);

#endif
usart.c
/**
 * @file  usart.c
 * @brief USARTを用いた三線シリアル通信
 */

#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#include "usart.h"

/**
 * @typedef usart_t 
 * @brief USART通信が用いるワークエリアの型
 */
typedef struct {
	UINT8 recv_data[BUF_SIZ];	/** 受信データを格納するリングバッファ */
	UINT8 recv_size;		/** 受信データのサイズ */
	UINT8 recv_read;		/** 受信データの読み込み位置 */
	UINT8 send_data[BUF_SIZ];	/** 送信データを格納するリングバッファ */
	UINT8 send_size;		/** 送信データのサイズ */
	UINT8 send_read;		/** 送信データの読み込み位置 */
} usart_t;

/**
 * @var usart
 * @brief USART通信が用いるワークエリア
 */
static volatile usart_t usart;

/**
 * @brief USART通信初期化
 */
void usart_open(void) {
	/* 割り込み禁止 */
	cli();

	/* UBRR0の算出 */
	#define UBRR_USART0 (((F_CPU>>4)+(USART0_BAUD_RATE>>1))/USART0_BAUD_RATE-1)

	/* USART0の送受信と割り込みを全て禁止 */
	UCSR0B = 0;

	/* ボーレート(16bit値)を8bitづつ設定 */
	UBRR0H = (UINT8)(UBRR_USART0>>8);	/* 上位8bit */
	UBRR0L = (UINT8)(UBRR_USART0   );	/* 下位8bit */

	/* ストップ:1ビット, データ:8bit, ノンパリティを設定 */
	UCSR0C = 3 << UCSZ00;

	/* 入出力バッファをクリア */
	usart_clear();

	/* USART0の送受信と割り込みを許可 */
	UCSR0B = (1 << RXEN0) | (1 << TXEN0) | (1 << RXCIE0) | (1 << TXCIE0);

	#undef UBRR_USART0

	/* 割り込み禁止解除 */
	sei();
}

/**
 * @brief USART通信終了
 */
void usart_close(void) {
	/* 割り込み禁止 */
	cli();

	/* USART0の送受信と割り込みを全て禁止 */
	UCSR0B = 0;

	/* 割り込み禁止解除 */
	sei();
}


/**
 * @brief USART通信の入出力バッファをクリアする
 */
void usart_clear( void ) {
	cli();
	usart.recv_size = 0;
	usart.recv_read = 0;
	usart.send_size = 0;
	usart.send_read = 0;
	sei();
}

/**
 * @brief C言語のread()関数相当の動作
 * @param [in] buf 受信するバイト列
 * @param [in] count 受信するバイト列のデータ長
 * @return 受信したバイト長
 */
int usart_read(void *buf, int count) {
	UINT8 *p = (UINT8*)buf;
	while ( count-- > 0 ) {
		UINT8 s;
		cli();
		s = usart.recv_size;
		if (s == 0) {
			sei();
			break;
		} else {
			UINT8 r = usart.recv_read;
			UINT8 data = usart.recv_data[r];
			usart.recv_read = (r+1) & (BUF_SIZ-1);
			usart.recv_size = s-1;
			sei();
			*p++ = data;
		}
	}
	return p-((UINT8 *)buf);
}

/**
 * @brief Cのwrite相当の動作
 * @param [in] buf 送信するバイト列
 * @param [in] count 送信するバイト列のデータ長
 * @return 送信したバイト長
 */
int usart_write(void *buf, int count) {
	UINT8 *p = (UINT8 *)buf;
	UINT8 flag = 1;
	while ( count-- > 0 ) {
		UINT8 data = *p;
		UINT8 s;
		cli();
		s = usart.send_size;
		if ( s >= BUF_SIZ ) {
			sei();
			break;
		} else {
			UINT8 r = usart.send_read;
			UINT8 w = (s + r) & (BUF_SIZ-1);
			usart.send_data[w] = data;
			usart.send_size = (s + 1) & (BUF_SIZ-1);
			sei();
			p++;
			flag = 0;
		}
	}

	if (flag == 0) {
		cli();
		if (UCSR0A & (1<<UDRE0)) {
			/* 送信可能なら1文字送信する */
			UINT8 s = usart.send_size;
			if (s > 0) {
				UINT8 r = usart.send_read;
				UDR0 = usart.send_data[r];
				usart.send_read = (r + 1) & (BUF_SIZ-1);
				usart.send_size = s - 1;
			}
		}
		sei();
	}

	return p-((UINT8 *)buf);
}

/**
 * @brief USARTのデータ受信時に発生する割り込みの処理
 * @note USART割り込みベクタに登録されるルーチン
 */
ISR(USART0_RX_vect) {
	UINT8 s = usart.recv_size;
	if (s < BUF_SIZ) {
		UINT8 w = (s + usart.recv_read) & (BUF_SIZ-1);
		usart.recv_data[w] = UDR0;
		usart.recv_size = s+1;
	}
}

/**
 * @brief USARTが送信可能になった時に発生する割り込みの処理
 */
ISR(USART0_TX_vect) {
	UINT8 s = usart.send_size;
	if (s > 0) {
		UINT8 r = usart.send_read;
		UDR0 = usart.send_data[r];
		usart.send_read = (r + 1) & (BUF_SIZ-1);
		usart.send_size = s - 1;
	}
}
サンプルコード
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>

#include "./usart.h"

__attribute__ ((naked)) 
int main(void) {
	usart_open();

	sei();

	for(;;) {
		UINT8 buf[BUF_SIZ];
		int n;
		n = usart_read(buf, sizeof(buf));
		usart_write(buf, n);
	}

	return 0;
}

HIDaspx制作 (第2回)

本文書の取り扱い

クリエイティブ・コモンズ・ライセンスこの文書は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。

HIDaspxファームウェアの書き込み

事前準備

  1. 千秋ゼミから "hidspxとHIDaspx(2種類のGUIを同梱)" をダウンロード
    • 執筆時の動作確認に用いたのは hidspx-2010-0602.zip
  2. hidspx-2010-0602.zip を解凍し、中身をc:\workに展開

AVRISP-mkIIを用いたファームウェアの書き込み手順


ハードウェアの接続


  1. HIDaspxのUSBコネクタに何も繋がっていないことを確認
  2. HIDaspxのジャンパピンを左図のように設定する

  1. AVRISP-mkIIとHIDaspxを左図のようにクロス接続する

ファームウェアの書き込み
  1. PCとAVRISP mkIIをUSBケーブルで接続
  2. PCとHIDaspxをUSBケーブルで接続
    • AVRISP mkII のLEDが両方緑色に点灯しない場合は接続に問題がある
  3. AVR Studioを起動し、AVRISP mkII に接続
  4. Programタブを選択し、 Flash に c:\work\bin\firmware中の main-12.hex を指定して Program で書き込む。
  5. Fusesタブを選択し、 下のペインのヒューズビットを以下のように
      • Low:FF, High:DB, Extended:FF
  6. 完了

HIDaspxを用いたファームウェアの書き込み手順


ハードウェアの接続
  • 二台のHIDaspxの混同を避けるために呼称を定める
    • ライター側
      • 書き込み時にライターとして用いるHIDaspx
    • ターゲット側


  1. ライター側のUSBコネクタに何も繋がっていないことを確認
  2. ライター側のジャンパピンを左図のように設定する

  1. ターゲット側の両方のUSBコネクタに何も繋がっていないことを確認
  2. ターゲット側のジャンパピンを左図のように設定する

  1. ライター側とターゲット側を左図のように接続する

ファームウェアの書き込み
  1. PCとライター側をUSBケーブルで接続
  2. PCとターゲット側をUSBケーブルで接続する
    • 誤った接続をすると、ICやUSBコネクタが発熱することがあるので注意して触ること
  3. c:\work\bin の hidspx-GUI.exeを起動
  4. Readボタンを押してデバイス情報を取得
    • 問題がなければ ATTiny2313 と表示される。
    • 表示されない場合は何か問題があるので接続などを確認する。
  5. ファームウェア main-12.hex を書き込む
    1. Flash のファイル選択ボタンから c:\work\bin\firmware の main-12.hex を選択
    2. Writeボタンを押して書き込む。
    3. 書き込みが完了したら、Verifyボタンを押して検証。
  6. ヒューズビットを書き換える
    1. 画面上部の Fuse(HEX)を以下のように設定
      • Lo:FF, Hi:DB, Ex:FF
    2. Writeを押して書き込む
  7. 完了

FT232RL USBシリアル変換モジュールとavrdudeを用いたファームウェアの書き込み手順

ハードウェアの接続



  1. ブレッドボードにAE-UM232RとATtiny2313を刺す
  2. 左図のようにAE-UM232RとATtiny2313をジャンパワイヤーで接続

ファームウェアの書き込み
  1. AE-UM232RをUSBケーブルでPCと接続
    • ドライバを求められた場合はFTDI ChipからVirtual COM Port Drivers(VCP)をダウンロードして導入する
  2. すzのAVR研究: FT245R/FT232R で avrdudeから serjtag-0.3.zip をダウンロード
  3. serjtag-0.3.zip を解凍し、binフォルダの中身を c:\work に配置。
  4. コマンドプロンプトを開き、c:\work に移動
  5. 次のコマンドを実行
    • avrdude.exe -c ft232r -P ft0 -p t2313 -B 57600
    • libusb0.dllが求められた場合はlibusb-win32からコンパイル済みパッケージをダウンロード
      • 執筆時の動作確認に用いたのは libusb-win32-bin-1.2.0.0.zip
    • 解凍後、bin\x86フォルダ内のlibusb0.dllをc:\workに配置
  • 正しく接続されている場合は以下のような出力が得られる
c:\work>avrdude.exe -c ft232r -P ft0 -p t2313 -B 57600
avrdude.exe: BitBang OK
avrdude.exe: pin assign miso 1 sck 0 mosi 2 reset 4
avrdude.exe: drain OK

 ft245r:  bitclk 38400 -> ft baud 19200
avrdude.exe: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.01s

avrdude.exe: Device signature = 0x1e910a

avrdude.exe: safemode: Fuses OK

avrdude.exe done.  Thank you.
    • こちらの出力の場合は正しく接続されていないので結線などを確認する
c:\work>avrdude.exe -c ft232r -P ft0 -p t2313 -B 57600
avrdude.exe: BitBang OK
avrdude.exe: pin assign miso 1 sck 0 mosi 2 reset 4
avrdude.exe: drain OK

 ft245r:  bitclk 38400 -> ft baud 19200
avrdude.exe: ft245r_program_enable: failed
avrdude.exe: initialization failed, rc=-1
             Double check connections and try again, or use -F to override
             this check.


avrdude.exe done.  Thank you.

以降は正しく接続されていることを前提とする

  1. 次のコマンドでhidaspxファームウェアを書き込む

    avrdude.exe -c ft232r -P ft0 -p t2313 -B 57600 -e -U flash:w:main-12.hex:i
  2. 次のコマンドでt2313に接続後、コンソールモードに入る

    avrdude.exe -c ft232r -P ft0 -p t2313 -B 57600 -t
  3. コンソールモードで次のコマンドをそれぞれ実行し、ヒューズビットを設定


    w lfuse 0 0xff
    w hfuse 0 0xdb
    w efuse 0 0xff
  4. quitコマンドを入力し、コンソールモードを抜ける
  5. USBケーブルをPCから外す
  6. ATtiny2313をブレッドボードから取り外し、作成したhidaspx基板に差し込む

正しく書き込めない場合

  1. 速やかにPC側のUSBコネクタを抜く
    • 部品が発熱している可能性があるのでHIDaspx側をいきなり触らない
  2. ISPケーブルの結線や回路を確認
  3. テスターなどでショートやケーブルの接続不良を検査
よくある誤り
  • 書き込み元(ライター側)と書き込み先(ターゲット側)のピン同士の接続を確認
    • 大抵はクロス接続になっていないことが原因
  • 書き込み元(ライター側)と書き込み先(ターゲット側)のジャンパピンの設定を確認
    • 両方のJP2をショートさせている場合は電流過多状態で大変危険
    • 両方のJP2を外している場合は電源が供給されていない状態
  • ATTiny2313の向きが正しいか確認
    • 逆に差している場合は発熱している。
    • マイコンが破損する可能性が大

HIDaspx制作 (第1回)

本文書の取り扱い

クリエイティブ・コモンズ・ライセンスこの文書は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。

HIDaspx作成に当たって

  • 部品の実装はすべて表面に行う
    • 半田付けは裏面から行うことになる
  • 基板上には実装する部品の番号や極性が記されているので、それに従って実装を行う
  • R4とR8以外の抵抗、およびツェナーダイオードZD1,ZD2は スペースの関係で、立てて実装する。

作成手順


  1. 抵抗R4(茶,黒,茶,金)と抵抗R8 (茶,黒,橙,金)を実装
  2. セラミックコンデンサC1,C2(22pF)を実装
  3. X1に12Mhzクリスタルを実装

  1. 丸ピンICソケット(シングル20P)を中央で切断し、U1とU2に実装
  2. CN1にUSBコネクタミニBを実装
    • 半田付けの際に隣の穴とショートしないように注意が必要

  1. 抵抗R1,R2(紫,緑,黒,金)を実装
  2. ツェナーダイオードZD1,ZD2を実装
    • ツェナーダイオードには極性があるので方向を確認して実装すること
      • 部品上では帯がある方がカソード(Cathode)で反対側がアノード(Anode)
      • 回路図では三角形の先端方向がカソード(Cathode)で反対側がアノード(Anode)
  3. セラミックコンデンサC3(0.1uF)を実装

  1. 抵抗R3(赤,赤,赤,金)を実装
  2. 抵抗R5,R6,R7 (茶,黒,茶,金)を実装
  3. JP1,JP2,JP3,CN2にピンヘッダを実装
    • ピンヘッダ(オス) 40pin をニッパーで 2pin2個、3pin1個、6pin1個に切り分けて使う

  1. 電解コンデンサC4(22uF)を実装
    • 電解コンデンサには極性があるので方向を確認して実装すること。
      • マイナス側にマークがついており、マイナス側の足が短くなっている。
  2. ポリスイッチPS1を実装。
  3. U1にATTiny2313を実装。
    • ICには向きがあるので、方向を確認して実装すること。
      • 上から見たときに、切り欠けがある短辺がUSBコネクタ側に来るように実装する。

  1. ハードウェア部分の完成

ISPケーブルの作成


作成にあたって
  • 通常は純正のISPケーブルを真似て作る
    • 本作例ではブレッドボードでの扱いやすさなどを考慮してジャンパワイヤーを改造して作成
  • ケーブルの長さは最大でも20cm以内とする
    • 長いとノイズの影響を受けやすくなる
    • 短すぎても不便なので今回は15cm
作成手順


  1. ジャンプワイヤーを一本手に取る
  2. ニッパーを使ってジャンプワイヤーの一方の根本を金属コネクタが完全になくなるように切断する

  1. ジャンプワイヤーの皮膜を4mm剥がして芯線を露出させる
    • 作例での皮膜剥きにはワイヤーストリッパーを利用している
    • デザインナイフやカッターでも代用可能だが、力を入れすぎると芯線を切断してしまうので注意が必要

  1. 露出した芯線部にコンタクトピンをあてがう
  2. コンタクトピンの根本側をかしめる。
    • 作例でのかしめにはラジオペンチを利用しているが、本来はQIコネクタの圧着には専用の工具を用いる。
    • かしめが不十分だと、ケーブルが抜ける可能性がある。

  1. コンタクトピンの芯線側をラジオペンチでかしめる
    • かしめが不十分だと、接触不良となる。
    • 出っ張りが無いように作るとQIコネクタに挿入しやすい。

  1. コンタクトピンをコネクタに挿入する
    • きちんと奥まで挿入できればかちりという感触がある。

  1. 同様の作業を残りの5本にも行えば完成