教育向けマイコンシミュレータを GUI 連携させる


概要

今回,教育向けマイコンシミュレータをもう少し利用しやすいように GUI を作ってみましたので
その内容をご紹介します.

これまでマイコン向けの教育として,CUI ベースでのマイコンシミュレータ(TOPPERS/Athrill)
を使って,プログラムの動きをCPU命令レベルで確認したり,メモリの変化状況を理解できるような
演習プログラムを用意していました.

しかし,デバイス周りの演習については,タイマ程度であればなんとか理解してもらえましたが,
デジタル入出力やシリアル入出力を CUI ベースで理解してもらうには限界があると感じていました.

そこで,今回,これらのデバイスを専用 GUI で連携させて,athrill 動作させることにしました.

Athrillについて

教育用途として,Athrill の記事を以下で紹介しています.

ただ,これらの記事は C 言語基礎,CPU 命令セット,メモリ更新/参照周りを理解するには良いのですが,
デバイス周りを理解していくには向いていません.

一方,AthrillはリアルタイムOSを動作させることができるため,下図に示すように周辺デバイスの
エミュレーションもできるようになっています.

問題はこれら周辺デバイスの動きを可視化するためのツール類が不足している点だと考えています.

チャレンジ

そこで,チャレンジとして単純な電子レンジ(下図)を制御するマイコンとそれを可視化するツールを
連携させてみてはどうかと考えてみました.
※下図の左側がGUIツール側で,右側がマイコンシミュレータで実現します.
※なお,電子レンジが良いかどうかについて深入りしないでください・・・

ここで利用しているデバイスは「シリアル」と「デジタル」です.各役割は以下の通りです.

  • シリアル
    • 制御プログラムの内部状態をシリアルを通して外部出力するために利用する
    • そして,シリアルの出力情報をツール側(上図:表示器)に反映させる
  • デジタル
    • 電子レンジ側のスイッチ押下状態(上図:入力,ドア状態)をデジタル入力を通して
    • マイコンに伝えるために利用する
    • デジタル入力値は押されてもとに戻るような挙動をツール側で作り込み,
    • デジタル入力値の時間変化をマイコン側で監視する

ツールとマイコン間の通信を仮想化するために,下図のように外部ファイルを通してデータの
やり取りすることにしました.

デジタルについては,MMAP 機能を利用して Athrillのデバイスアドレス(0x07FF0000)に1バイト割り当てました.
シリアルについては,文字列データを格納する外部ファイルの入出力操作で実現しています.

GUIツール

GUIツールは,開発スピード重視のため,C#で実装することにしました(下図).

ソースコードは以下で公開しています.

マイコン側

マイコン側のプログラムについては,ソフトウェア設計はほとんどせずにえいやで作りました(汗).
教育用途としてソフトウェア設計してもらって,きれいにしてもらうことを狙っています・・・.

ソースコードは以下で公開しています.

デモ

それでは,本ツールを使ったデモを行ってみましょう.
デモ内容は以下の通りです.

  1. マイコン側のプログラムを作成/ビルドする
  2. Athrillを起動する
  3. GUIツールを起動する
  4. STARTボタンを押下して,マイコン側で計算されている残り時間が変化するか確認する

マイコン側のプログラムを作成/ビルドする

マイコン側のプログラムは先ほどご説明した main.c です.
このファイル内に timer_interrupt_handler() という関数があり 100msec 周期で定期的に呼び出されます.
ここに必要な処理を入れます(汚いですが最低限必要な処理は入れてあります).

ビルド環境は以下にあります.

ビルド方法は以下の通りです.

$ make clean;make
rm -f start.o vector.o interrupt.o interrupt_table.o timer.o interrupt_asm.o main.o legacy.elf *.map *.dump
v850-elf-gcc -c -I. -I../../common -I../../../../../trunk/src/config/target/v850esfk3 -O0 -mdisable-callt -mno-app-regs -mtda=0 -gdwarf-2 -Wall -Wno-unused-label -Wpointer-arith  -mv850e2 -Wa,-mno-bcond17 -Wa,-mwarn-signed-overflow -Wa,-mwarn-unsigned-overflow ../../common/start.S
v850-elf-gcc -c -I. -I../../common -I../../../../../trunk/src/config/target/v850esfk3 -O0 -mdisable-callt -mno-app-regs -mtda=0 -gdwarf-2 -Wall -Wno-unused-label -Wpointer-arith  -mv850e2 -Wa,-mno-bcond17 -Wa,-mwarn-signed-overflow -Wa,-mwarn-unsigned-overflow ../../common/vector.S
v850-elf-gcc -c -I. -I../../common -I../../../../../trunk/src/config/target/v850esfk3 -O0 -mdisable-callt -mno-app-regs -mtda=0 -gdwarf-2 -Wall -Wno-unused-label -Wpointer-arith  -mv850e2 -Wa,-mno-bcond17 -Wa,-mwarn-signed-overflow -Wa,-mwarn-unsigned-overflow ../../common/interrupt.c
v850-elf-gcc -c -I. -I../../common -I../../../../../trunk/src/config/target/v850esfk3 -O0 -mdisable-callt -mno-app-regs -mtda=0 -gdwarf-2 -Wall -Wno-unused-label -Wpointer-arith  -mv850e2 -Wa,-mno-bcond17 -Wa,-mwarn-signed-overflow -Wa,-mwarn-unsigned-overflow ../../common/interrupt_table.c
v850-elf-gcc -c -I. -I../../common -I../../../../../trunk/src/config/target/v850esfk3 -O0 -mdisable-callt -mno-app-regs -mtda=0 -gdwarf-2 -Wall -Wno-unused-label -Wpointer-arith  -mv850e2 -Wa,-mno-bcond17 -Wa,-mwarn-signed-overflow -Wa,-mwarn-unsigned-overflow ../../common/timer.c
v850-elf-gcc -c -I. -I../../common -I../../../../../trunk/src/config/target/v850esfk3 -O0 -mdisable-callt -mno-app-regs -mtda=0 -gdwarf-2 -Wall -Wno-unused-label -Wpointer-arith  -mv850e2 -Wa,-mno-bcond17 -Wa,-mwarn-signed-overflow -Wa,-mwarn-unsigned-overflow ../../common/interrupt_asm.S
v850-elf-gcc -c -I. -I../../common -I../../../../../trunk/src/config/target/v850esfk3 -O0 -mdisable-callt -mno-app-regs -mtda=0 -gdwarf-2 -Wall -Wno-unused-label -Wpointer-arith  -mv850e2 -Wa,-mno-bcond17 -Wa,-mwarn-signed-overflow -Wa,-mwarn-unsigned-overflow ../../legacy/main.c
v850-elf-gcc -O0 -mdisable-callt -mno-app-regs -mtda=0 -gdwarf-2
-Wall -Wno-unused-label -Wpointer-arith  -mv850e2 -Wa,-mno-bcond17 -Wa,-mwarn-signed-overflow -Wa,-mwarn-unsigned-overflow -nostdlib -T ../../common/v850esfk3.ld -o legacy.elf start.o vector.o interrupt.o interrupt_table.o timer.o interrupt_asm.o main.o -Wl,-Map,legacy.elf.map -lm -lgcc -lc
v850-elf-objdump -d legacy.elf > legacy.elf.dump
cp legacy.elf ../../bin/legacy/

Athrillを起動する

ビルドが成功すると,以下のフォルダにlegacy.elf ファイルが作成されます.

次に,本ディレクトリに移動し,athrill-runコマンドを実行します.

$ athrill-run
OK: found device_config.txt
OK: found memory.txt
OK: found legacy.elf
core id num=1
ROM : START=0x0 SIZE=512
RAM : START=0x6ff7000 SIZE=512
RAM : START=0xdead0000 SIZE=1
 :
[DBG>[NEXT> pc=0x0 vector.S 9

その後,「c」コマンドを入力することでマイコン側のプログラムが動き始めます.

c
[CPU>SERIAL_FILE_PATH = /mnt/c/project/esm/athrill/sample/edu/microwave/bin/serial
WARNING: Found invalid data write on not variable region(addr=0x6ff70f9 size=1) : bss_clear(0x15dc)@stack_data
WARNING: Found invalid data write on not variable region(addr=0x6ff70fa size=1) : bss_clear(0x15dc)@stack_data
WARNING: Found invalid data write on not variable region(addr=0x6ff70fb size=1) : bss_clear(0x15dc)@stack_data
SERIAL_FILE_PATH = /mnt/c/project/esm/athrill/sample/edu/microwave/bin/serial

GUIツールを起動する

GUIツールを起動すると下図のように起動時間が変化していきます.

マイコン起動後6秒経過 マイコン起動後110秒経過

これはシリアルを通して,マイコン側の起動時間をツール側に通知して実現しています.
※シリアル用の外部ファイルは以下に配置されています.
https://github.com/tmori/athrill/blob/master/sample/edu/microwave/bin/serial/serial_in.txt

シリアル用の外部ファイルの中をのぞいてみると,下図のように文字列が入っていることがわかります.

:
sys_time
1
sys_time
2
sys_time
3
:

※sys_timeがマイコン起動してからの時間を表す項目で,そのあとにくる数字が起動時間そのものです.

STARTボタンを押下して,マイコン側で計算されている残り時間が変化するか確認する

さらに,STARTボタンを押下してみましょう.残り時間が減っていくことが見て取れます.

STARTボタン押下前 STARTボタン押下後

これについてもシリアル用の外部ファイルの中をのぞいてみると,下図のように残り時間を表すlast_timeという文字列が入っていることがわかります.

:
sys_time
587
last_time
86
sys_time
588
last_time
85
sys_time
589
last_time
84
:

また,STARTボタン押下時の外部ファイル(デジタル)の値の変化状況を定期監視した結果は以下の通りでした.
マイコン側でばっちりこの変化をキャッチしたと言えますね!

*
0010000
0000000 0000 ★スイッチ押下前
*
0010000
0000000 0001 ★スイッチ押下された
*
0010000
0000000 0000 ★スイッチ押下後,状態が元に戻った

まとめ

以上,今回作成したものの全体像をまとめると下図のようなものになります.

今後,教育用途で使いこんでいくうちに内容を精査していきます.