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付属シミュレータ
- ATMega328P
開発環境
- 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*)¶m); return param.syscallparam.result; } int exitTASK(void) { PAR_exitTASK_t param; param.syscallparam.id = SVCID_exitTASK; syscall((syscallparam_t*)¶m); return param.syscallparam.result; } int pauseTASK(void) { PAR_pauseTASK_t param; param.syscallparam.id = SVCID_pauseTASK; syscall((syscallparam_t*)¶m); return param.syscallparam.result; } int resumeTASK(int id) { PAR_resumeTASK_t param; param.syscallparam.id = SVCID_resumeTASK; param.id = id; syscall((syscallparam_t*)¶m); 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 (;;) {} /* ここが実行されることはない */ }
実装解説
コンテキストスイッチの実装
手順は以下のようになっています。
- 現在の実行コンテキストに関する情報をスタック上に保存
- スタックポインタをタスクコントロールブロックに保存
- 切り替え先のタスクのスタックポインタを設定
- 実行コンテキストを復帰
タスクの起動
タスクを起動する際には、つじつまが合うようにスタック上のコンテキスト構造体を埋めてから復帰させればいいことになります。
スケジューラ
TCB配列の先頭から最初のREADY状態のタスクを選択しているだけです。
まとめ
このように、素直なアーキテクチャのCPUならば3時間程度でOSの骨組みが作れてしまいます。さらに、Arduino Duemilanoveのように安価で簡単な開発環境も存在します。興味を持たれた方は、これをきっかけにOS自作やTOPPERS-ASPなどの世界に踏み込んでみませんか?
最後に
元々は教育用として作成した教材の派生物ですが、もう少し多機能にしてCortex-M3(NXP Xpresso 1343)に移植したりしたRTOSをBSDライセンスとして以下の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-M3とJTAGを搭載した安価・高機能・開発環境の導入が容易なマイコン評価ボードで、同様の製品に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版の広告が表示される
開発環境導入
- Atollic社のダウンロードページ から Free版をダウンロード
- ダウンロードしたインストーラを起動
- 途中で認証用のキーと認証用のページが表示されるので、個人情報とキーを登録
- メールで送られてくる登録用のキーを入力してインストールを継続
- 以上
プロジェクト作成
ウィザードに答えるだけでサンプルコード付きのプロジェクトが生成されます。
- スタートメニューからAtollic TrueSTUDIO STM32 Liteを起動
- File > New > C Project と選択
- プロジェクト名を適当に設定し、Projet type: は Executable の STM32 C Projectを選択して Nextを選択
- TargetのEvaluation boardからSTM32_Discoveryを選択してNextを選択
- JTAG Probe がST-LINKになっていることを確認してFinishを選択
- 以上
デバッグ
非常に簡単です。
まとめ
Atollic TrueSTUDIO STM32 Liteを用いることで、非常に制約の少ない開発環境を容易に構築できることが確認できました。
謝辞
- 評価ボードをお譲りくださったSTマイクロエレクトロニクス様に感謝いたします。
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環境
- Windows Vista x64
- USBメモリ (4GB)
- VisualStudio 2008
- 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 (以下に記載) に従います。
ファイル
- こちら(Windows Live SkyDrive)で公開しています。
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ファームウェアの書き込み
- 既存のAVRライタを用いる
- AVRISP-mkIIを用いる方法
- 完成品のHIDaspxを用いる方法
- AVRライタを所有していない場合
- FT232RL USBシリアル変換モジュールとavrdudeを用いる方法
事前準備
AVRISP-mkIIを用いたファームウェアの書き込み手順
HIDaspxを用いたファームウェアの書き込み手順
ハードウェアの接続
- 二台のHIDaspxの混同を避けるために呼称を定める
- ライター側
- 書き込み時にライターとして用いるHIDaspx
- ターゲット側
- これからHIDaspxファームウェアを書き込む側のHIDaspx
- ライター側
|
|
|
|
|
ファームウェアの書き込み
FT232RL USBシリアル変換モジュールとavrdudeを用いたファームウェアの書き込み手順
- FTDI BitBang AVR-Writerを参考に実施
- 秋月電子通商で販売されているFT232RL USBシリアル変換モジュール(又は同等品)をAVRライタとして用いる
- HIDaspxの部品代を含めてもAVRISP-mkIIより安価に作成可能
- FT232RL USBシリアル変換モジュール自体はシリアル通信で多用するため、ライタ作成後も活用頻度が高い
ファームウェアの書き込み
- AE-UM232RをUSBケーブルでPCと接続
- ドライバを求められた場合はFTDI ChipからVirtual COM Port Drivers(VCP)をダウンロードして導入する
- すzのAVR研究: FT245R/FT232R で avrdudeから serjtag-0.3.zip をダウンロード
- serjtag-0.3.zip を解凍し、binフォルダの中身を c:\work に配置。
- コマンドプロンプトを開き、c:\work に移動
- 次のコマンドを実行
- 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.
以降は正しく接続されていることを前提とする
- 次のコマンドでhidaspxファームウェアを書き込む
avrdude.exe -c ft232r -P ft0 -p t2313 -B 57600 -e -U flash:w:main-12.hex:i
- 次のコマンドでt2313に接続後、コンソールモードに入る
avrdude.exe -c ft232r -P ft0 -p t2313 -B 57600 -t
- コンソールモードで次のコマンドをそれぞれ実行し、ヒューズビットを設定
w lfuse 0 0xff
w hfuse 0 0xdb
w efuse 0 0xff - quitコマンドを入力し、コンソールモードを抜ける
- USBケーブルをPCから外す
- ATtiny2313をブレッドボードから取り外し、作成したhidaspx基板に差し込む
正しく書き込めない場合
- 速やかにPC側のUSBコネクタを抜く
- 部品が発熱している可能性があるのでHIDaspx側をいきなり触らない
- ISPケーブルの結線や回路を確認
- テスターなどでショートやケーブルの接続不良を検査
HIDaspx制作 (第1回)
本文書の取り扱い
この文書は、クリエイティブ・コモンズ・ライセンスの下でライセンスされています。
HIDaspx作成に当たって
- 部品の実装はすべて表面に行う
- 半田付けは裏面から行うことになる
- 基板上には実装する部品の番号や極性が記されているので、それに従って実装を行う
- R4とR8以外の抵抗、およびツェナーダイオードZD1,ZD2はスペースの関係で、立てて実装する。
作成手順
|
|
|
|
|
|
|
ISPケーブルの作成
作成にあたって
- 通常は純正のISPケーブルを真似て作る
- 本作例ではブレッドボードでの扱いやすさなどを考慮してジャンパワイヤーを改造して作成
- ケーブルの長さは最大でも20cm以内とする
- 長いとノイズの影響を受けやすくなる
- 短すぎても不便なので今回は15cm
作成手順
|
|
|
|
|
|
|
|
|
|
|