2016年10月01日

Arduino(AVR)でのグローバル変数の扱い

先日Arduinoへ移植したNT-Shellですが、どうも動作が不安定なので調べたところ、NT-Shellのせいではなくグローバル変数の初期化ミスでした。

これまで、グローバル変数として
Arduboy arduboy;

と宣言していたのですが、これだと初期化されていないと見なされるのか、変数arduboyに割り当てられたメモリが開放されてしまうようです。
対策としてはstatic宣言するか初期化するかですが、今回は複数ファイルに分けたソースコードからarduboyを参照しているため、static宣言できないので、以下のように初期化しました。
Arduboyはクラスなので、インスタンスを作成した時点で暗黙の内に初期化されていると思っていたのですが、どうも違うようです。
いずれにせよ、これでどうやら安定しました。
Arduboy arduboy = Arduboy();


また、RAMが逼迫しているという警告が出るので、グローバル変数の状況を調べてみました。
調べ方ですが、avr-objdump.exeというコマンドを使います。
このコマンドはArduinoのフォルダ内の「hardware\tools\avr」に入っています。
使用状況を調べるには、ソースコードをコンパイルして作成される.elfファイルが必要です。
(通常はテンポラリフォルダの中に保存されています。)

avr-objdumpの使い方は以下の通りです。

avr-objdump -S -j .bss project.elf

avr-objdump -S -j .data project.elf


初期化されない変数は.bss、初期化される変数は.dataで調べます。

これで調べてみたら、コマンド名の配列(入力されたコマンドを調べるために使っていた)が意外と領域を食っていたので、ちょっと手間ですがフラッシュのほうへ格納し直しました。
これで現時点ではRAM使用率が72%まで下がり、警告も出なくなりました。
posted by boochow at 16:55| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする

2016年09月29日

Arduboyでスケッチが書き込めなくなったときは

単なるTipsです。

Natural Tiny Shell(NT-Shell)をArduinoに移植してみたの記事ではシリアルポートを常時使っています。

開発中、ときどきスケッチが書き込めなくなることがありました。
スケッチの書き込みもシリアルポート経由なので、スケッチの動作がおかしいと書き込みモードに切り替わらない場合があるようです。

Arduboyはハードリセットボタンがありますが、Arduboyのライブラリにも対策がされています。

それは、「電源オンのときにUPボタンとLEFTボタンを押しているとセーフモードになる」というものです。
セーフモードでは、ユーザのスケッチは実行されません。
そのため、スケッチ書き込みの邪魔をするようなコードがArduboyに書き込まれてしまっていても、それを回避することができます。

これはライブラリのコードを見ていたら偶然見つけました。
Arduboy-1.1.1/src/core/core.hに以下のような記載があります。
    /// Safe mode
/**
* Safe Mode is engaged by holding down both the LEFT button and UP button
* when plugging the device into USB. It puts your device into a tight
* loop and allows it to be reprogrammed even if you have uploaded a very
* broken sketch that interferes with the normal USB triggered auto-reboot
* functionality of the device.
*
* This is most useful on Devkits because they lack a built-in reset
* button.
*/
void static inline safeMode() __attribute__((always_inline));


safeMode() の中身はcore.cppにありますが、何もしない命令(nop)の無限ループです。

これでもだめだったら、ハードウェアリセットという手段があります。
posted by boochow at 00:11| Comment(2) | Arduino | このブログの読者になる | 更新情報をチェックする

2016年09月27日

Natural Tiny Shell(NT-Shell)をArduinoに移植してみた

ntshell.jpg

前回から、Arduboyの画面にPCからデータを送って絵を表示させようと考えていますが、これには当然Arduboy側にもソフトが必要です。
以前から使ってみようと思っていた「NT-Shell」を、この機会にArduboyで動かしてみることにしました。

今回はArduboyに移植しましたが、処理はシリアルポート経由の通信とデータ処理がメインです。
従って、今回の内容は通常のArduinoでもそのまま使えると思います。(なので、記事タイトルもArduboyではなくArduinoとしました。)
Arduboy用のコードは、例によってGitHubにアップロードしてあります。

boochow/abshell: a comman shell for arduboy

NT-Shellは10KB ROM/1KB RAMといったマイコンクラスのシステムで動作するシェルです。
VT100コンパチブルですので、コマンドのパラメータを打ち間違えた際にカーソルを移動してそこだけ修正する、といったことができます。

Natural Tiny Shell (NT-Shell)

使い方は、こちらのページにあるサンプルを見れば分かります。
また、上記のリンク先からダウンロードできるファイルの中にもサンプルが入っており、これを見ればコマンドの追加方法なども分かります。

以下、簡単に今回の移植のポイントを書いておきます。
まず、上記のサンプルではmain関数が以下のようになっています。

nt main(void)
{
ntshell_t ntshell;
chip_init();
uart_init();
ntshell_init(
&ntshell,
func_read,
func_write,
func_callback,
(void *)&ntshell);
ntshell_set_prompt(&ntshell, "BlueTank>");
ntshell_execute(&ntshell);
return 0;
}


ntshell_initでコールバック関数func_read、func_write、func_callbackの3つを渡しています。
そしてntshell_executeでシェルを実行しています。

まず、Arduinoにおけるコールバック関数ですが、func_readとfunc_writeは単にシリアルポートの読み書きができれば良いので、以下のようにしました。
static int func_read(char *buf, int cnt, void *extobj) {
if (Serial.available())
return Serial.readBytes(buf, cnt);
else
return 0;
}

static int func_write(const char *buf, int cnt, void *extobj) {
return Serial.write(buf, cnt);
}

何の芸も無いですね・・・。

func_callbackは、コマンド文字列へのポインタを受け取って処理する関数ですが、これはサンプル(lpc_monitor)に入っていた関数をそのまま使わせていただきました。
static int func_callback(const char *text, void *extobj) {
return usrcmd_execute(text);
}

usrcmd_executeはtextで与えられた文字列を解析してコマンド名と引数の配列(Cでお馴染みのargc, **argv)を取得し、各コマンドを実行する関数へジャンプします。
Arduino用に、メッセージ用文字列をプログラム領域へ保存するように手を加えたものが、冒頭のGitHubリポジトリのusrcmd_arduino.cppに入っています。


基本的な移植は以上なのですが、このままではArduinoでは動作しません。

まず一つ目の問題は、ntshell_executeが無限ループを内包していることです。
一方、Arduinoはloop()を繰り返し呼び出す前提になっており、特にArduboyはこの構造に強く依存しています。

そこで、ntshell_execute自体は使用せず、その中身をloop()内に移植することにしました。
中身といっても、以下の最後の3行がwhile(1){}の内側に入っていたのを、whileループを取り去っただけです。この関数はntshell_arduino.cの中にあります。

そして、スケッチファイルのloop()から、このntshell_execute_arduinoを呼び出します。
void ntshell_execute_arduino(ntshell_t *p)
{
if (p->initcode != INITCODE) {
return;
}

unsigned char ch;
SERIAL_READ(p, (char *)&ch, sizeof(ch));
vtrecv_execute(&(p->vtrecv), &ch, sizeof(ch));
}


次の問題はSRAMの不足です。
Arduboyは2.5KBのSRAMのうち、1KBをディスプレイバッファとして使っています。
そのため、1KB以上は余裕があるはずなのですが、実際にはコンパイルしたらRAM容量不足になってしまいました。

対策として、まずヒストリ機能はあきらめました。
この機能により、コマンドを途中まで入力してtabを押すと、過去の履歴によって残りの部分が補完されます。
ヒストリ機能を使うと、ヒストリ回数×1行あたりの最大文字数 分のRAMが必要になります。
この設定はlib/core/ntconf.hに書かれています。
元の設定にあった、1行64文字はそのままとして、8回分のヒストリ機能は1回に削減しました。
せっかくある機能ですから、直前のコマンドくらいは呼び出したいということです。

そして、最大のメモリ食いは状態遷移テーブルでした。
これはlib/core/vtrecv.cで実装されています。
ただ、静的なテーブルですので、RAM上に置く必要はありません。
以下のようにして、テーブルをプログラム領域へ移しました。
テーブルの読み出し部分も、プログラム領域から読むように書き換えました。
#ifdef __AVR__
static const state_table_t table[] PROGMEM = {
#else
static const state_table_t table[] = {
#endif


#ifdef __AVR__
state_table_t ts;
ts.state = pgm_read_word_near(tp);
ts.code_start = pgm_read_byte_near((char *)tp + 2);
ts.code_end = pgm_read_byte_near((char *)tp + 3);
ts.state_change = pgm_read_byte_near((char *)tp + 4);
if (ts.state == state) {
if ((ts.code_start <= ch) && (ch <= ts.code_end)) {
return ts.state_change;
}
}
tp++;
#else
if (tp->state == state) {
if ((tp->code_start <= ch) && (ch <= tp->code_end)) {
return tp->state_change;
}
}
tp++;
#endif

これで大体動くようになったので、あとは淡々とArduboyのコマンドを実装していきます。
線、矩形、円、角丸矩形、三角形、などなど一通り実装しました。
機械的に実装したので、中身は綺麗ではありません。
これらはusrcmd_arduboy.cppにあります。

とりあえず、これでターミナルソフトから
print hello world

とか
rect 20 10 50 50

とか入力すればArduboyの画面に表示できるようになりました。

ビットマップ関係だけは、仕様をちょっと考えたいので、まだ実装していません。
posted by boochow at 00:39| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする

2016年09月22日

ProcessingとopenFrameworksを試してみた

of03.png

Arduboyの画面に、PCで選択した画像ファイルを表示するようなツールを作ってみたいと思います。
この場合はArduboyとPC間で通信をすることになります。
PC側を何で開発するかちょっと迷ったのですが、画像処理(2値化)などもすることを考えると、とりあえずProcessing(Proce55ing)かなあと思い、ちょっと使ってみました。

Processing.org

ProcessingはArduinoの兄弟のような開発環境で、IDEも似ています。
ベースの言語はJavaです。
画像処理関係の機能が充実しており、画像処理フィルタ機能が標準で使えます。

filter() \ Language (API) \ Processing 2+

IDEそのものはArduinoにそっくりなのですが、Arduinoと違い、現在のバージョン(v3)ではステップ実行や変数の確認ができるデバッガが付いています。

まずは画像ファイルを読み込んでウインドウに表示させるプログラムを作ってみました。
思ったより手こずりましたが、できたのが以下です。
スペースキーを押すとファイル選択ダイアログが立ち上がり、画像ファイルを選択すると表示できます。
PImage img;
String fName;

void setup()
{
size( 512, 512 );
background(0, 0, 0);
}

void keyReleased() {
if (key == ' ') {
noLoop();
selectInput("Select an image file", "fileSelected");
while(fName==null) {delay(200);}
if (fName != null) {
img = loadImage(fName);
clear();
redraw();
}
loop();
}
}

void fileSelected(File selection) {
if (selection == null) {
println("Window was closed or the user hit cancel.");
fName = null;
} else {
fName = selection.getAbsolutePath();
}
}

void draw()
{
if (img != null) {
image(img, 0, 0 );
}
}


ただ・・・このプログラム、一応は動くのですが、2回目以降に画像を開くとき、画像を開いた直後には表示されません。
redraw()をコールすれば、直後でdraw()で新しい画像が描かれると思ったのですが、何かタイミングが合っていないようです。

また、私にとっては言語として、どうも気持ちの悪い点がいくつかあります。たとえば

・keyReleased()で使用しているkeyのように、グローバル変数が多い。
・手続き型言語の印象が強い。img.drawじゃなくてimage(img)とか。
・ループの一時停止と再開を意味するnoLoop(), loop()という表現も古臭い。
・ライブラリを見る限り、GUI関係がちょっと弱そう。

などなど・・・
また、最近Arduboyシミュレータを使うためにVisual Studioばかり使っていたためか、ProcessingのIDEも不便に感じるようになってしまいました。


そんなわけで他の選択肢を探してネットを見ていたところ、「openFrameworks」に行き当たりました。Processingの影響を受けたC++ライブラリですが、Mac、Linux、WindowsのほかiOSやAndroidにも対応しているようです。
Windowsでは、VisualStudioの上で開発が行えます。

openFrameworks

日本語の情報はProcessingに比べると少ないのですが、嬉しいことに日本語の書籍で、無料でダウンロードできるものがあります。

Beyond Interaction メディアアートのためのopenFrameworksプログラミング入門 | 株式会社ビー・エヌ・エヌ新社

というわけで、こちらを使ってみることにしました。
ライブラリはダウンロードして展開するだけですが、それ以外にVisual Studio用のプラグインが必要です。

これはVisual Studio内からダウンロードできます。
まず「ツール」→「拡張機能と更新プログラム…」を選択します。
of01.png


そして、openFrameworksを検索してダウンロードします。
of02.png


これで完了です。

ダウンロードしたライブラリには、サンプルが沢山付属しています。
GUIツールキットも用意されています。
of03.png


プラグインをインストールしたので、新規のプロジェクトを作成する際にはテンプレートの選択肢にopenFrameworksが出てきます。
of04.png


なお初回は、openFrameworksを展開したディレクトリの場所を指定するよう求められました。
of05.png


テンプレートには最初から必要なファイルが含まれています。
プログラム本体はofApp.cpp / ofApp.hに記述します。
of06.png


openFrameworksで、先ほどと同様に画像ファイルを選択して表示するプログラムを書いてみました。
テンプレートがあるので、実際に書いた部分は青字の部分だけです。

#pragma once

#include "ofMain.h"

class ofApp : public ofBaseApp{

public:
void setup();
void update();
void draw();

void keyPressed(int key);
void keyReleased(int key);
void mouseMoved(int x, int y );
void mouseDragged(int x, int y, int button);
void mousePressed(int x, int y, int button);
void mouseReleased(int x, int y, int button);
void mouseEntered(int x, int y);
void mouseExited(int x, int y);
void windowResized(int w, int h);
void dragEvent(ofDragInfo dragInfo);
void gotMessage(ofMessage msg);
void processOpenFileSelection(ofFileDialogResult openFileResult);
ofImage img;

};


#include "ofApp.h"

//--------------------------------------------------------------
void ofApp::setup(){
ofSetWindowShape(512, 512);
ofBackground(0,0,0);

}

//--------------------------------------------------------------
void ofApp::update(){

}

//--------------------------------------------------------------
void ofApp::draw(){
if (img.isAllocated())
img.draw(0, 0);
}

//--------------------------------------------------------------
void ofApp::keyPressed(int key){

}

//--------------------------------------------------------------
void ofApp::keyReleased(int key){
if (key == ' ') {
ofFileDialogResult openFileResult = ofSystemLoadDialog("Select a jpg or png");
if (openFileResult.bSuccess) {
processOpenFileSelection(openFileResult);

}
}


}

void ofApp::processOpenFileSelection(ofFileDialogResult openFileResult) {

ofFile file(openFileResult.getPath());

if (file.exists()) {
string fileExtension = ofToUpper(file.getExtension());
if (fileExtension == "JPG" || fileExtension == "PNG") {
img.load(openFileResult.getPath());
}
}

}
//--------------------------------------------------------------
void ofApp::mouseMoved(int x, int y ){

}

//--------------------------------------------------------------
void ofApp::mouseDragged(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mousePressed(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseReleased(int x, int y, int button){

}

//--------------------------------------------------------------
void ofApp::mouseEntered(int x, int y){

}

//--------------------------------------------------------------
void ofApp::mouseExited(int x, int y){

}

//--------------------------------------------------------------
void ofApp::windowResized(int w, int h){

}

//--------------------------------------------------------------
void ofApp::gotMessage(ofMessage msg){

}

//--------------------------------------------------------------
void ofApp::dragEvent(ofDragInfo dragInfo){

}

とりあえず、openFrameworksを使ってみようかと思います。
posted by boochow at 21:04| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする

2016年09月11日

インベーダーゲームを作ってみる(6)サウンド

インベーダーゲームの解説、最後はサウンドです。

インベーダーの効果音は7種類しかありません。

@インベーダーの行進音
A自機弾の発射音
Bインベーダー命中音
CUFO出現音
DUFO命中音
E自機被弾音
F1500点での自機+1の音

オリジナルのインベーダーゲームは、これらの音を全て専用の回路で生成しています。
以下の記事にそれぞれの回路図が出ています。

Space Invaders sound emulation

これによると、@Fは有名なタイマーICのNE556、CはTIのサウンドチップSN76477、ABDEはオペアンプLM3900を使っているそうです。また、AEはノイズ音が混じりますが、これはCMOS ICの発振回路とシフトレジスタで生成しているそうです。
説明には4066とありますが、シフトレジスタなので4006の間違いでしょう。こちらのマニュアルのP8のパーツリストにも4006とあります。)

俗にゲームの音を「ピコピコ音」と言いますが、インベーダーゲームのサウンドは後にピコピコ音の代名詞となるファミコンなどよりも、アナログ回路で生成した電子音の比率が高かったようです。
以下のビデオはおそらく実機だと思いますが、今聴いても味わいがありますね。



まずはオリジナルの効果音を分析してみましょう。

最初は行進音です。

上のビデオの音声トラックを取り出し、Audacityで増幅・ノイズ除去したものを聴いてみます。
前半はそのまま、後半は2オクターブ上にトランスポーズしています。



音階は、「ラ・ソ・ファ・ミ」あるいは「ミ・レ・ド・シ」というように聞こえなくもありません。
いずれにせよ短調ですね。

インベーダーが減るに連れてテンポが上がりますが、音長(ゲートタイム)自体は変化せず、発音の間隔だけが狭まっていきます。
演奏速度としては、最後は最初の約10倍になるように設定しました。


次に、インベーダーに命中したときの「プチュン」という音です。
これも上のビデオから取り出したものを聴いてみます。
最初は通常再生、2番目は1/2倍速のスロー再生です。



波形を図にすると、下のようになっています。
iv10.png

全体で0.3秒ほどの音ですが、前半0.1秒くらいが低い音がさらに低くなる「プン」という音、その後は高い音からだんだん下がっていく「チューーーン」という音になっています。
また、末尾のところで同じ「チューーン」という音が、残響のようにもう一度小さく入っています。
なので、続けて鳴らすと「プチュンチュン」という具合に聴こえます。

このほか、UFO飛来音は4オクターブくらいの音域をサイン波でピッチを上げ下げするとそれらしくなります。
命中したときの音も、音程が低くなるだけで基本的には同様です。
なお、自機弾の発射音や自機爆発音は、ノイズ成分が必要なのでArduboyでは作るのは難しいです。


プログラム内での音声処理は、以下の通りです。
まず、各効果音の周波数の時間変化を配列に入れます。
データの末尾は「-1」とします。
int16_t snd1[5] = {   // laser
NOTE_B6, NOTE_C7, 0, NOTE_B6, -1
};


これとは別に、構造体で音の特性(ゲートタイム、再生速度、リピートするか等)と状態(再生中か等)を保持します。
enum SoundState {
SoundReady,
SoundPlaying,
SoundDone
};

struct sound_fx_t {
int16_t *data; // array of frequencies, use -1 as end marker
uint8_t gate_time; // note gate time in msec
uint8_t clk; // ticks per note
boolean loop; // true for loop play
uint8_t idx;
uint8_t clk_cnt;
enum SoundState status;
};


サウンドの状態は「Ready」→「Playing」→「Done」と遷移します。
それぞれ、再生前、再生中、再生完了を表します。

Playing状態では、再生速度(clk)ごとに配列のデータを1つずつ再生します。
clkが大きくなれば、演奏速度はゆっくりになります。


サウンド関連の関数は以下の6つを用意しています。

void sound_play(struct sound_fx_t *s)
loop()から各サウンドにつき1回ずつ呼び出すことでサウンドの状態を更新します。
音声チャンネルは1つだけですので、全ての効果音の処理をした結果として、基本的には最後に再生された音だけが聴こえます。
逆に言えば、サウンドの優先順位はsound_playを呼び出す順番で制御できます。後に呼び出されたものが優先です。

void sound_start(struct sound_fx_t *s)
サウンドの再生を開始します。(再生前→再生中へ遷移)

void sound_stop(struct sound_fx_t *s)
サウンドの再生を強制的に完了させます。(再生中→再生完了へ遷移)

void sound_restart(struct sound_fx_t *s)
再生が完了または中止されたサウンドを再び再生可能にします。(再生完了→再生前へ遷移)

boolean sound_ready(struct sound_fx_t *s)
サウンドが再生前ならtrue、そうでなければfalseを返します。

void sound_set_tempo(struct sound_fx_t *s, uint8_t t)
サウンドの再生テンポを指定します。再生はt+1 ticks(1/60秒)ごとに1データずつ進行します。0が最速です。
posted by boochow at 23:43| Comment(0) | Arduino | このブログの読者になる | 更新情報をチェックする
人気記事