フォト
2018年2月
        1 2 3
4 5 6 7 8 9 10
11 12 13 14 15 16 17
18 19 20 21 22 23 24
25 26 27 28      
無料ブログはココログ

2018年2月17日 (土)

Arduino Unoの内部EEPROMの利用(書込みの高速化)に関するメモ

Arduino Unoの内部EEPROMへのデータ書き込みに関するメモです。


先日「豊四季タイニーBASIC for MW25616L実験用表示モジュール」の実装で、
プログラムを内部EEPROMに保存しています。

方法としては、Arduino標準クラスライブラリEEPROM を使っています。
(Arduino リファレンス - EEPROM Library

  #include <EEPROM.h>

  // 内部EEPROMメモリへの保存
  topAddr = EEPROM_PAGE_SIZE*prgno;
  for (uint16_t i=0; i < SIZE_LIST; i++) {
    EEPROM.write(topAddr+i,listbuf[i]); 
  }

512バイトのデータ書き込みは、上のような感じでやっています。
クラスライブラリEEPROM は1バイト単位でしか書き込みが出来ないためか、
書込みに時間がかかります(感覚的に2,3秒くらい?)。

改善策はないかと、EEPROM.h のソースを読むと、
#include <avr/eeprom.h> を読み込んでおり、内部EEPROM用のAPIが存在することが
分かりました。

eeprom.hのソースを読むと、ブロック単位の書込みが行えるAPI等色々と用意されています。
内部EEPROM用のAPIを使う方法に修正しました。

  #include <avr/eeprom.h>

  // 内部EEPROMメモリへの保存
  topAddr = EEPROM_PAGE_SIZE*prgno;
  eeprom_update_block(listbuf,topAddr,SIZE_LIST);

書込みには下記のAPIを使っています。

  void eeprom_update_block (const void *__src, void *__dst, size_t __n);

試してみると、ほぼ瞬時に書き込みを行うことが出来ました。
コードも短くなり、フラッシュメモリの消費量も減りました。
内部EEPROM用のAPIは、#include <avr/eeprom.h> でヘッダーファイルをインクルード
することで利用出来ます。

クラスライブラリEEPROM を使うよりも、APIを使った方がお手軽に利用できます。

用意されているAPIはeeprom.hを直接覗いてみれば確認できます。
arduino-1.8.5\hardware\tools\avr\avr\include\avr\eeprom.h

2018年2月15日 (木)

アクティブマトリクス蛍光表示管の実験用表示モジュールの試用 (2)

間が空きましたが、
アクティブマトリクス蛍光表示管(CL-VFD) MW25616L 実験用表示モジュールの試用の続きです。

VFD

前回の続きからは方向を変え、
「豊四季タイニーBASICを乗せて制御すれば面白いのでは?」と思い、やってみました。

モジュール的にはArduino互換なので、先日メモリー節約したバージョンに
micro:bit版の機能を一部を結合して実装しました。
micro:bit版の流用で2、3日くらいで実装できました。

下記のサイトにてとりあえず公開いたします。
興味のある方は参照願います。
・豊四季タイニーBASIC for MW25616L実験用表示モジュール
   https://github.com/Tamakichi/ttbasic_MW25616L

リファレンス・マニュアルも付けました。
文法やコマンド等の詳細については、リファレンス・マニュアルを参照して下さい。
 
作成には、豊四季タイニーBASICのオリジナル版を利用しています。
また、VFDの制御には、「VFDふぁん」さんが公開している参考プログラムを利用しています。

さて、このバージョンで、どんなことが出来るかというと、

シリアル接続して、直接コマンドを打って、
VFDにメッセージ文の表示が可能です。全角文字もそのまま入力出来ます。

02

コマンドで簡単に表示できるのは、やはり良いです!
従来(micro:bit版)のコマンドにVDF制御のために、以下の専用コマンドを用意しました。

専用コマンド一覧

VBRIGHT :輝度を0(0%)~255(100%)の範囲で設定します。
  例:VBRIGHT 100

VDISPLAY :表示のオン・オフを設定します。
  例:VDISPLAY OFF

VMSG :指定したメッセージ文を指定速度でスクロール表示します。 
   例:VMSG 10,"日本語のメッセージ表示テスト"

VSCROLL :指定長の空白を指定速度でスクロール表示します。
   例:VSCROLL 256,10

VPUT :表示バイトデータを指定速度で直接指定します。
  例:VPUT MEM,16,10

逆に、メモリ的制約からいくつかのコマンドを削除しています。

プログラム例

コマンドを使ったプログラム作成して、ちょっとした動きのある表示も出来ます。
プログラム作成はシリアル接続のスクリーンエディタにて入力出来ます。

10 'VDF表示サンプル
20 TONE 440,100:TONE 880,100
30 @(0)="熱いぞ!埼玉!"
40 @(1)="豊四季タイニーBASIC"
50 @(2)="VFD対応バージョン"
60 @(3)="只今開発中!"
70 @(4)="ちょっとメモリー的に厳しい.."
80 VBRIGHT 255
90 FOR S=0 TO 4
100 VCLS:VMSG 10,STR$(@(S))
110 FOR I=0 TO 255 STEP 15
120 VBRIGHT 255-I:WAIT 40
130 NEXT I
140 WAIT 500
150 VBRIGHT 255:WAIT 500
160 VSCROLL 256,3
170 WAIT 500
180 NEXT S
190 GOTO 80
I2C接続のRTCモジュールを使って時刻をリアルタイム更新表示するプログラム

03

10 VCLS:VBRIGHT 100
20 @(0)="時","分","秒"
30 M=ARRAY+6:POKE M,0
40 R=I2CR($68,M,1,M+1,7)
50 VMSG -1,"現在の時刻は"
60 FOR I=0 TO 2
70 VMSG -1,HEX$(PEEK(M+3-I),2);STR$(@(I))
80 NEXT I
90 VMSG 0,"です"
100 WAIT 1000
110 VSCROLL 256,-1
120 GOTO 40

スライド抵抗器でVFD上に表示しているバーをドット単位で左右に動かすプログラム

05

10 @(0)=$FFFF
20 A=ANA(20)>>2:IF A=0 A=1
30 VPUT ARRAY,2,-1
40 VSCROLL A,0
50 VSCROLL 256,-1
60 WAIT 20
70 GOTO 20

色々と機能てんこ盛りの結果、フラッシュメモリの利用率は99%!
メモリ容量的に、micro:bit版と比べると色々と制約が発生しました。
  ・プログラムサイズは 512バイトまで
  ・内部EEPROMに保存できるプログラムは2本まで

  ・変数はA~Z、配列変数 @(0)@(15)まで
  ・スクリーンエディタの画面サイズは 横48文字x縦8行 固定

他に色々と制約があります。

暫く、動作確認をして修正を行っていきます。
スケッチは一部を修正すれば、Arduino UNOでも利用出ます。

参考サイト
・VFDふぁん - MW25616L 実験用表示モジュールを使ってみる(2)


追記 2017/02/15(夜)

メモリーの制約からフルスクリーンエディタの画面サイズが横48文字x縦8行はとなり、
長い行のプログラムの作成においては、視野の狭い範囲でのスクロール編集となり、
ちょっと使いづらいです。

そこで、フルスクリーンエディタを有効・無効設定するコマンドを追加しました。

Photo

スクリーンエディタ機能を無効にすると、行単位での編集しか出来ないですが、
これはこれで良いかも。

無効にすると、逆スクロール編集、任意の位置にカーソルを移動しての編集、
画面制御を行うコマンド(LOCATE等)が機能しなくなりますが、
スクリーンエディタ機能を臨機応変に切り替えて利用することで、
とりあえずは実用としてしのげそうです。

2018年2月11日 (日)

豊四季Tiny BASIC for ArduinoのSRAM消費軽減対応

豊四季Tiny BASIC for Arduino(オリジナル版)のSRAM消費を軽減する修正を行ってみました。

Arduino UNOでは、SRAMが2kバイトしかありません。
オリジナル版のまま利用した場合、グローバル変数が1481バイト(72%)を利用しており、
残りが567バイトです。機能拡張するにはちょっと辛いです。

そこで、キーワード、エラーメッセージ等のグローバルな固定データをフラッシュメモリ上に
配置する修正をおこない、SRAMの消費を少々押さえました。

参考にしたサイト

・Arduinoで遊ぶページ - Arduino Uno編 - メモリの種類
・武蔵野電波 - Arduino 日本語リファレンス - PROGMEMとFマクロ
・Arduino - PROGMEM

良く知られているテクニックですが、
Arduino(AVRマイコン)でPROGMEMキーワードを使って変数の宣言を行うことで、
SRAMではなく、フラッシュメモリ上にデータを配置することが出来ます。

ただし、データの参照がちょっと面倒で、専用の参照を行う関数を利用する必要があります。
データ構造にも制約があり、キーワードテーブル等で使う2次元配列は対応されていません。
入れ子になる1次元配列を定義してから、その一次元配列のアドレスを配列として宣言して
2次元配列っぽい構造を作成します。

修正前のキーワードテーブル(オリジナル版よりソースを引用しています)

// Keyword table
const char *kwtbl[] = {
  "GOTO", "GOSUB", "RETURN",
  "FOR", "TO", "STEP", "NEXT",
  "IF", "REM", "STOP",
  "INPUT", "PRINT", "LET",
  ",", ";",
  "-", "+", "*", "/", "(", ")",
  ">=", "#", ">", "=", "<=", "<",
  "@", "RND", "ABS", "SIZE",
  "LIST", "RUN", "NEW"
};

修正したキーワードテーブル

// Keyword table
#define KW(k,s) const char k[] PROGMEM=s  // キーワード定義マクロ
KW(k000,"GOTO"); KW(k001,"GOSUB"); KW(k002,"RETURN");
KW(k003,"FOR"); KW(k004,"TO"); KW(k005,"STEP"); KW(k006,"NEXT");
KW(k007,"IF"); KW(k008,"REM"); KW(k009,"STOP");
KW(k010,"INPUT"); KW(k011,"PRINT"); KW(k012,"LET");
KW(k013,","); KW(k014,";");
KW(k015,"-"); KW(k016,"+"); KW(k017,"*"); KW(k018,"/"); KW(k019,"("); KW(k020,")");
KW(k021,">=");KW(k022,"#"); KW(k023,">"); KW(k024,"="); KW(k025,"<="); KW(k026,"<");
KW(k027,"@"); KW(k028,"RND"); KW(k029,"ABS"); KW(k030,"SIZE");
KW(k031,"LIST"); KW(k032,"RUN"); KW(k033,"NEW");

const char*  const kwtbl[] PROGMEM = {
 k000,k001,k002,
 k003,k004,k005,k006,
 k007,k008,k009,
 k010,k011,k012,
 k013,k014,
 k015,k016,k017,k018,k019,k020,
 k021,k022,k023,k024,k025,k026, 
 k027,k028,k029,k030,
 k031,k032,k033,
};

KW()マクロは、

const char k000[] PROGMEM="GOTO";
const char k001[] PROGMEM="GOSUB";

と定義するのを省略するのに利用しています。
テーブルの参照はちょっと面倒です。
キーワード比較を行う部分は、一旦、SRAM上のメモリーにコピーして利用しています。

char* pkw = 0; //ひとつのキーワードの内部を指すポインタ
char kwtbl_str[16]; // コマンド比較用

// オリジナル
pkw = (char *)kwtbl[i]; //キーワードの先頭を指す

// 修正版
pkw = strcpy_P(kwtbl_str, (char*)pgm_read_word(&(kwtbl[i])));

ARM cortex-M系のgccでは、constキーワードを付ければ、
フラッシュメモリ上に配置してくれて、かつ参照はSRAMと同等に行えるのと比べると
ちょっと面倒ですね。

とりあえず、この修正によりSRAM消費量を712バイト抑えることが出来ました。
SRAMは1279バイトほど余っているので、ちょっとした機能追加は出来ると思います。

もしかしたら、もっと効率の良いやり方があるかもしれませんが、これで良しとします。

Arduino IDE 1.8.15

ちょっといじった感じでは、オリジナル版と同じ感じで動いています。

ttbasic

この修正版は、下記にて公開いたいます。
https://github.com/Tamakichi/ttbasic_arduino_uno


2018年2月 7日 (水)

micro:bitをArduino環境で使う (7) シリアル通信

micro:bitではハードウェアシリアル通信ポートが1つ利用出来ます。

Arduino環境で利用する場合、USB経由のシリアル通信専用に割り付けれています。

具体的にはポート21、22に割り付けられていているのですが、このポートは
外部端子には接続されていせん。

しかし、ハードウェア的にmicro:bitではシリアル通信ポートを任意の外部端子に
割り付けることが出来ます。

Arduino環境でも任意のポートに割り付けられないかとやってみました。
まずは、Serialインスタンスを実装しているUartクラスを使って試してみます。

スケッチ(注意:このスケッチはダメだったケースです)
//
// micro:bit シリアルポートの利用
// 任意のポートを使ってシリアル通信を行う
//

#define Rxpin 12         // シリアル RxDピン(任意指定可能)
#define Txpin 13         // シリアル TxDピン(任意指定可能)
#define baudRate 9216000 // 通信速度(1200 ~ 921600bps)

// Uartインスタンスの生成
Uart Serial2(NRF_UART0, UART0_IRQn, Rxpin, Txpin); 
//extern "C"  void UART0_IRQHandler() {  Serial2.IrqHandler(); };

void setup() {
  Serial2.begin(baudRate); // 通信開始
}

void loop() {
  Serial2.println("Hello,world");
  delay(1000);
}

上記の例では、Uartクラスを使ってデフォルトのSerialオブジェクトとは
別のインスタンスを生成しています。
インスタンス生成時に利用するポートを指定することが出来ます。

外部端子を使う利点として、通信速度に115200以上の指定が可能です。
(USB経由だと115200までしか利用出来ない)

試しに、上限の921600bpsを指定してみると、TeraTermにで問題なく出力できました。
(パソコンに接続にはUSB-Serialモジュールを利用しています)

送信については、任意のポートに割り付けて利用することが出来ました。

01

ところが、受信処理を実装するとデータ受信を行うことが出来ません。
問題点を調べると、
シリアル受信を行うコールバック関数(割り込み処理)が
デフォルトのSerialインスタンスに固定されています。

Uart.cpp
extern "C"

{
  void UART0_IRQHandler()
  {
    Serial.IrqHandler();
  }
}

Arduinoのコア部分で上記のように固定されており、
これを修正することが出来ません。
本来ならば
  Serial.IrqHandler();

  Serial2.IrqHandler();
に変更しないといけません。

IrqHandler()の実体は下記のようになっており、
Serialインスタンスのリングバッファにデータを格納しています。
これでは、後から生成したSerail2インスタンスでのデータ受信を行うことが出来ません。


void Uart::IrqHandler()
{
  if (nrfUart->EVENTS_RXDRDY)
  {
    rxBuffer.store_char(nrfUart->RXD);
    nrfUart->EVENTS_RXDRDY = 0x0UL;
  }
}

この方法は諦めて、Arduino環境のUart.h、Uart.cppに修正を入れることにしました。
ポート設定を行うメンバー関数setPort()を追加しました。

Uart.hの修正:下記の2つのメンバー関数の宣言の追加

// <-- add by Tamakichi 2018/02/07
    void setPort(uint8_t _pinRX, uint8_t _pinTX);
    void setPort(uint8_t _pinRX, uint8_t _pinTX, uint8_t _pinCTS, uint8_t _pinRTS );
// -->>     

Uart.hの修正:下記の2つのメンバー関数の本体の追加

// <-- add by Tamakichi 2018/02/07
void Uart::setPort(uint8_t _pinRX, uint8_t _pinTX)
{
  uc_pinRX = g_ADigitalPinMap[_pinRX];
  uc_pinTX = g_ADigitalPinMap[_pinTX];
  uc_hwFlow = 0;
}

void Uart::setPort(uint8_t _pinRX, uint8_t _pinTX, uint8_t _pinCTS, uint8_t _pinRTS)
{
  uc_pinRX = g_ADigitalPinMap[_pinRX];
  uc_pinTX = g_ADigitalPinMap[_pinTX];
  uc_pinCTS = g_ADigitalPinMap[_pinCTS];
  uc_pinRTS = g_ADigitalPinMap[_pinRTS];
  uc_hwFlow = 1;
}
// -->>

この追加したメンバー関数を利用したスケッチ

//
// micro:bit シリアルポートの利用
// 任意のポートを使ってシリアル通信を行う
// 本スケッチはArduinoのUart.h、Uart.cppの修正が必要
//

#define Rxpin 12         // シリアル RxDピン(任意指定可能)
#define Txpin 13         // シリアル TxDピン(任意指定可能)
#define baudRate 9216000 // 通信速度(1200 ~ 921600bps)

void setup() {
  Serial.setPort(Rxpin, Txpin);
  Serial.begin(baudRate); // 通信開始
  Serial.println("Hello,world");
}

void loop() {
  if (Serial.available()) {
    Serial.write(Serial.read());
  }
}

実行結果

02

これで、とりあえずは任意のポートを利用した送受信を行うことが出来ました。
ただし、Arduino環境に修正を加える必要があります。

2018年2月 4日 (日)

豊四季Tiny BASIC for micro:bit をV0.07に更新しました

豊四季Tiny BASIC for micro:bit をV0.07に更新しました。
公開サイト
  https://github.com/Tamakichi/ttbasic_microbit

V0.06からの変更点
 ・スクリーンエディタの機能強化
    - 全角文字(シフトJIS)対応
    - [F7]キー : 行の分割
    - [F8]キー : 行の結合
    - [DEL]、[BS]でブランク行の削除

  Windows 10上のTeraTermでしか動作確認していませんが、
  IMEでの全角入力に対応しました。
  プログラム中のコメントや文字列に全角文字が利用出来ます。

  弊害としては、日本語コードを通すためにキーコードの一部を変更しました。
  INKEY()でキー判定を行うプログラムでは修正が必要です。
   - [UP]、[DOWN]、[RIGHT]、[LEFT]、[ページUP]、[ページDOWN]、[HOME]、[END]
     等のキーコードを変更しました。

  今後の修正もありうるので、キーコード用の定数を用意しました。
   - KUP、KDOWN、KRIGHT、KLEFT、KSPASE、KENTER   

・全角文字列用関数・コマンドの追加
  半角文字関連を扱う関数の全角対応版を用意しました。
   - WLEN()        : 文字数の取得
   - WCHR$()      : SJISコードから文字への変換
   - WASC()     : SJIS文字のSJISコードの取得
   - WCSTR$() : 変数が参照している文字列の出力

・美咲フォント(8x8ドット 教育漢字)対応
  フラッシュメモリに教育漢字(+英数記号・ひらがな・カタカナ)を乗せました。
  利用頻度の高い1710文字が利用出来ます。
  SJISコードにて該当するフォントデータを参照することが出来ます。
   - WADR(SJISコード)  : 指定したSJISコードに対応するフォントデータ格納アドレス取得

・文字列入力関数の追加
  文字列(全角を含む)入力を行う関数を追加しました。
   - GETS()     : 入力した文字列を指定アドレスに格納しそのアドレスを返す。

・不具合対応
  - BIN$(0)がブランク表示となる不具合の対応
  - MATRIX OFFでのポート初期化しわすれの対応
  - LIST表示のIF文で空白がつまる現状の対応
     (新:IF X=CW  X=X-1 旧:IF X=CWX=X-1)


今回の修正で出来るようになった例です。
下記のプログラム実行例は、コメントや文字列に全角を利用しています。
また、"あ"に対応する美咲フォントのデータを参照してそのパターンを表示しています。

  02

次に、Neopixcelマトリックスタイプを使って、文字を表示する例です。
 

プログラムの次のような感じです。

10 'NeoPixelで文字表示
20 SETFONT 0,$50,$A8,$88,$88,$70
30 MSG TOP,0,CHR$(0)
40 NPBEGIN 12,64
50 NPCLS
60 S="こんにちは さい玉":C0=RGB8(0,2,3)
70 FOR I=1 TO WLEN(S)
80 A=WADR(WASC(S,I))
90 FOR Y=0 TO 7
100 D=PEEK(A+Y)
110 FOR X=0 TO 7
120 IF D&($80>>X) C=C0 ELSE C=0
130 IF Y&1 POKE MEM+Y*8+X,C ELSE POKE MEM+Y*8+7-X,C
140 NEXT X
150 NEXT Y
160 NPPUT 0,MEM,64,1
170 WAIT 400
180 NEXT I
190 GOTO 70

機能拡張は、このあたりで一旦止めて、サンプルプログラム等を充実させていきたいと思います。

2018年1月27日 (土)

豊四季Tiny BASIC for micro:bit をV0.06に更新しました

豊四季Tiny BASIC for micro:bit をV0.06に更新しました。

公開サイト
    https://github.com/Tamakichi/ttbasic_microbit

PLAYコマンドによる音楽演奏をサポートしました。

プログラムソース

10 'ネコフンジャッタ
20 'リュウヨウモト http://astr.me.land.to/tool/mabi/mml/nekof.htm
30 SETFONT 0,$50,$A8,$88,$88,$70
40 MSG TOP,0,CHR$(0)
50 TEMPO 140
60 PLAY "L16D+C+R8F+RF+RD+C+R8F+RF+RD+C+L8RF+RF+RL16FRFRD+C+R8FRFRD+C+R8FRFRD+C+"
70 PLAY "L8RFRFRL16F+RF+RD+C+R8F+RF+RD+C+R8F+RF+RD+C+L8RF+RF+RL16FRFRD+C+R8FRFRD+C+R8FR"
80 PLAY "L16FRD+C+L8RFRFRL16F+RF+RD+C+L8RF+RF+RF+RF+RF+RF+RL16FRFRD+C+L8RFRFRFRFRFRFR"
90 PLAY "L16F+RF+RD+C+R8F+RF+RD+C+R8F+RF+RD+C+L8RF+RF+RL16FRFRD+C+R8FRFRD+C+R8FRFRD+C+"
100 PLAY "L8RFRFRL16F+RF+R8.F+RC+C+D8C+8.FRF+"

演奏データは、下記のサイトに公開されているのもを流用させて頂きました。

・主体性の無いページ http://astr.me.land.to
   ねこふんじゃった! http://astr.me.land.to/tool/mabi/mml/nekof.htm

その他の変更点

  ・サウンド機能対応
    TONE、NOTONE、SETTONE、PLAY、TEMPOコマンドの追加

  ・LEDマトリックスのグラフィック描画の不具合対応
    色コードに2を指定して反転表示出来ない不具合の対応

  ・I2Cのサポート

  ・SHIFTIN、SHIFTOUTコマンドのサポート

  ・WIDTHコマンドの不具合対応

  ・リファレンス・マニュアルの追加
   01

音の再生には先日実装したPPIによる方式を組み込みました。
バックグランドでもならせるよう、検討中です。
 

2018年1月25日 (木)

micro:bitをArduino環境で使う (6) PPIを使った単音出力

PPI(Programmable Peripheral Interconnect)を使って圧電スピーカーにて
単音出力を実装しました。

01

スケッチ

//
// micro:bit PPIを使ったTone by たま吉さん
//

#include "nrf.h"
#define TIMERFREQ 1000000L    // タイマー基本周期
#define TONE_TIMER NRF_TIMER0 // 利用タイマー資源

//
// 音の停止
// 引数
//
void dev_notone() {
    TONE_TIMER->TASKS_STOP = 1;  // タイマストップ
}

//
// 音出し
// 引数
//  pin     : PWM出力ピン 
//  freq    : 出力周波数 (Hz) 15~ 50000
//  duration: 出力時間(msec)
//
void dev_tone(uint8_t pin, uint16_t freq, uint16_t duration) {
  uint32_t ulPin;
  uint32_t f =TIMERFREQ/freq;
  
  // GPIOTEの設定:LEDピン・トグルタスクを定義する
  ulPin = g_ADigitalPinMap[pin];   // TonePinの実ピン番号の取得 
  NRF_GPIOTE->CONFIG[0] =          // チャネル0に機能設定
    (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |            // タスクモード
    (ulPin << GPIOTE_CONFIG_PSEL_Pos) |                              // ピン番号設定
    (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) |  // 動作指定:トグル
    (GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos);        // ピン出力初期値
  NRF_GPIOTE->POWER = 1;                                             // GPIOTE有効
    
  //タイマ設定
  TONE_TIMER->TASKS_STOP = 1;                          // タイマストップ
  TONE_TIMER->TASKS_CLEAR = 1;                         // カウンタクリア
  TONE_TIMER->MODE = TIMER_MODE_MODE_Timer;            // モード設定:タイマモード
  TONE_TIMER->PRESCALER   = 4;                         // プリスケーラ設定:16分周(1MHz)
  TONE_TIMER->BITMODE = TIMER_BITMODE_BITMODE_16Bit;   // カウンタ長設定:16ビット長指定
  TONE_TIMER->CC[0] = f/2;                             // コンパレータ0の設定(出力周波数設定)

  TONE_TIMER->SHORTS =                                 // ショートカット設定:クリアタスク指定
      (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos);

  // PPIの設定(チャネル0を利用)
  //   TIMER0 コンパレータ0一致イベント と GPIOTE(ch0)LEDピン・トグルタスク を結び付ける
  NRF_PPI->CH[0].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];       // PPI.ch0 にLEDピン・トグルタスク設定
  NRF_PPI->CH[0].EEP  = (uint32_t)&TONE_TIMER->EVENTS_COMPARE[0];  // PPI ch0 にコンパレータ0一致イベント設定  
  NRF_PPI->CHENSET   |= PPI_CHENSET_CH0_Enabled;                   // PPI ch0 有効

  TONE_TIMER->TASKS_START = 1;   // タイマスタート

  if (duration) {
    delay(duration);
    TONE_TIMER->TASKS_STOP = 1;  // タイマストップ
  }
}

void setup() {
  uint32_t ulPin;
  uint16_t freq = 440;  // 出力周波数 (Hz)

  uint32_t f =TIMERFREQ/freq;
  Serial.begin(115200); 
  Serial.println("microbit is ready!");

  dev_tone(12,523,1000); // ド
  dev_tone(12,587,1000); // レ
  dev_tone(12,659,1000); // ミ
  dev_tone(12,698,1000); // ファ   
  dev_tone(12,784,1000); // ソ  
  dev_tone(12,880,1000); // ラ  
  dev_tone(12,988,1000); // シ  
  dev_tone(12,1047,1000);// ド   
}

void loop(){
    __SEV();
    __WFE();
    __WFE();
}

dev_tone()関数にて単音出力が出来ます。
CPUを使わずにタイマー、GPIOE、PPIだけで音を出しています。

音階に対応する周波数は下記の通りです。

02

これも豊四季Tiny BASIC for micro:bitに組み込む予定です。

2018年1月21日 (日)

アナログ入力を使った16ボタン入力

以前自作したアナログ入力を使ったボタン入力と同様のキーパッドが
Aliexpressで安価に販売されているので入手しました。
GND、VCC、アナログ入力の3ピンで簡単に16ボタン入力が実装出来ます。

Button Keypad module 4x4. One analog out. Simple connection to Compatible for Arduino, Raspberry, STM. Keypad

ついでに3x4タイプも入手

Button Keypad 3x4 module. One analog out. Compatible for Arduino, Raspberry, STM. Keypad

到着した製品

意外としっかりとした製品です。
透明キャップを外してボタンにラベルを付けることが出来ます。

04_2

マニュアルは無いのですが、裏に10ビット解像度時のアナログ入力値が記載されています。

05

この情報を元に、豊四季タイニーBASIC for micro:bitにて動作確認してみました。

動作確認プログラム

10 'Keypad 4x4
20 @(10)=1013,920,840,780,670,630,590,560,502,477,455,435,400,320,267,228
30 G0=G
40 G=GRADE(ANA(1),10,16)
50 IF G>=0 ?"KEY=[";G+1;"]":MSG TOP,0,CHR$(65+G)
60 GOTO 30

ボタンの判定は誤差と揺らぎを考慮し、裏面の値の10を引いた値で判定しています。

GRADE() 関数は配列に格納された値を閾値として等級判定する関数です。
第1引数には、判定する値、第2引数には配列先頭番号、第3引数にはデータ数を指定します。
アナログ入力ANA(1)の値が1013以上の場合は0、920以上の場合は1..と判定します。
範囲外の場合は-1を返します。
比例関係に無い値を等級判定する場合に利用するとプログラムを短く出来ます。

押した番号をコンソールに表示し、ボタン1~16をA~PとしてLEDマトリックスに表示させます。

06

とりあえず、動作しました。

ただし、瞬間的に誤判定し、正常判定の状態になる場合があります。
ログを見ると、ボタン[1]を押したのに、一瞬ボタン[6]と判定されています。

02

この原因は、ボタンを押したときのチャタリングと思うのですが、念のため波形を見てみました。

01

波形はボタンを離した時のものですが、やはりチャタリングが発生しています。
このチャタリングに対する誤動作防止にはいくつか方法があります。

今回は、
  ChaNさんの 「テクニカル ノート チャタリング対策のしかた
が大変参考になりました。

ChaNさんの対策案でプログラムで対応できる「ディレイ方式」が一番簡単そうです。
さっそく試してもました。

対策

まず、測定波形を見ると1回あたりのチャタリングの幅は0.4msec(マス1msec幅)程度です。

そこで、アナログ値の測定のよるボタン判定において、
  「前回の値と今回の値が異なる場合、今回の値を捨て1msec後にもう一度測定する、
   同じなら値として採用する」


という対策を施したところ、誤判定を無くすことが出来ました。
う~ん、なかなかいい感じで利用できるようになりました。

修正対応したプログラム
10 'Keypad 4x4
20 G0=-1
30 @(10)=1013,920,840,780,670,630,590,560,502,477,455,435,400,320,267,228
40 G0=G
50 G=GRADE(ANA(1),10,16)
60 IF G<>G0 WAIT 1 GOTO 40
70 IF G>=0 ?"KEY=[";G+1;"]":MSG TOP,0,CHR$(65+G)
80 GOTO 40

さて、以前自作したキーパッドをIchigoJamで利用した時には、チャタリングによる
誤判定は気になりませんでした。なぜでしょう?

この理由としては、IchigoJamの処理速度が遅いため、チャタリングによる変化を
検知出来なかったのだと思われます。

今回試したmicro:bitの豊四季Tiny BASICは、ARM的にはIchigoJamと同スペックですが、
実装上の工夫により、IchigoJamより余裕で15~30倍の処理能力があります。

Arduino UNOでこのボードの利用する場合も、何らかのチャタリング対策は必要と思います。

2018年1月16日 (火)

豊四季Tiny BASIC for micro:bit をV0.05に更新しました

豊四季Tiny BASIC for micro:bit をV0.05に更新しました。

変更点
 ・Neopixel対応
 ・PCG(LEDマトリックス用フォント書き換え)対応
 ・RTC(時刻管理)対応(誤差は数分/日)
 ・2進数定数対応
 ・PWM暫定対応(Arduinoの制約により同時3チャンネル迄)
 ・CHR$()の複数キャラ指定対応(?CHR$(65,66,67) => "ABC")
 ・GRADE(値, 配列No,個数)関数の追加:等級判定関数

ダウンロード直リンク
https://github.com/Tamakichi/ttbasic_microbit/archive/master.zip

micro:bitで豊四季Tiny BASIC - 現在時刻の表示


プログラムソース

1 'トケイ
10 MATRIX ON
20 SETDATE 2018,1,16,12,0,0
30 IF !IN(BTNA) GOSUB "@ShowTime"
40 WAIT 200
50 GOTO 30
60 "@ShowTime"
70 GETTIME T1,T2,T3
80 MSG LEFT,80,#-2,T1;":";T2;":";T3;" "
90 RETURN

micro:bitで豊四季Tiny BASIC - Neopixelの制御



プログラムソース

10 'Neopixel(1)
20 NPBEGIN 0,16
30 NPCLS
40 FOR I=0 TO 7
50 NPRGB I,0,0,(2<<I)-1
60 NEXT I
70 NPSHIFT 1
80 WAIT 50
90 GOTO 70

2018年1月12日 (金)

micro:bitをArduino環境で使う (5) PPIを使ったLチカ

今回はPPI(Programmable Peripheral Interconnect)を使ったLチカを実装してみました。

micro:bitをArduino環境で使う (2)GPIOTEを使ったLチカ」では、
タイマー割り込みを使って、タスク(GPIOEを使って定義したLEDをトグルでON・OFF)を
実行していました。

今回はPPIを使って、イベント(カウンター値がコンパレーターと一致)発生時に
タスク(GPIOEを使って定義したLEDをトグルでON・OFF)を自動で実行させます。

この方法により、CPUが介在することなく、LEDを点滅させることが出来ます。

micro:bitをArduino環境で使う (2)GPIOTEを使ったLチカ」のスケッチの
割り込み処理部分をPPIに置き換えたスケッチを下記に示します。

スケッチ

//
// micro:bit PPIを使ったLチカ by たま吉さん
//

#include "nrf.h"

const int COL1 = 3;         // Column #1 control
const int LED = 26;         // 'row 1' led
uint8_t sw =0;

/*
extern "C" void TIMER2_IRQHandler(void) {
    NRF_TIMER2->EVENTS_COMPARE[0] = 0;  // 割り込みイベントクリア
    NRF_GPIOTE->TASKS_OUT[0] = 1;       // タスク実行
    //sw =!sw;
    //digitalWrite(LED, sw);
}
*/

void setup() {
  uint32_t ulPin;
  Serial.begin(115200); 
  Serial.println("microbit is ready!");
  
  // GPIOピンの設定
  pinMode(COL1, OUTPUT);  digitalWrite(COL1, LOW);   // COL1ピンの設定
  //pinMode(LED, OUTPUT);                            // LEDピンの設定

  // GPIOTEの設定:LEDピン・トグルタスクを定義する
  ulPin = g_ADigitalPinMap[LED];  // LEDの実ピン番号の取得 
  NRF_GPIOTE->CONFIG[0] =         // チャネル0に機能設定
    (GPIOTE_CONFIG_MODE_Task << GPIOTE_CONFIG_MODE_Pos) |            // タスクモード
    (ulPin << GPIOTE_CONFIG_PSEL_Pos) |                              // ピン番号設定
    (GPIOTE_CONFIG_POLARITY_Toggle << GPIOTE_CONFIG_POLARITY_Pos) |  // 動作指定:トグル
    (GPIOTE_CONFIG_OUTINIT_Low << GPIOTE_CONFIG_OUTINIT_Pos);        // ピン出力初期値
  NRF_GPIOTE->POWER = 1;                                             // GPIOTE有効
    
  //タイマ設定
  NRF_TIMER2->TASKS_STOP = 1;                          // タイマストップ
  NRF_TIMER2->TASKS_CLEAR = 1;                         // カウンタクリア
  NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;            // モード設定:タイマモード
  NRF_TIMER2->PRESCALER   = 8;                         // プリスケーラ設定:128分周(125KHz)
  NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_16Bit;   // カウンタ長設定:16ビット長指定
  NRF_TIMER2->CC[0] = 62500/2;                         // コンパレータ0の設定:0.5秒周期

/*
  NRF_TIMER2->INTENSET =                               // 割り込み設定:コンパレータ0と比較
      (TIMER_INTENSET_COMPARE0_Enabled << TIMER_INTENSET_COMPARE0_Pos);
*/

  NRF_TIMER2->SHORTS =                                 // ショートカット設定:クリアタスク指定
      (TIMER_SHORTS_COMPARE0_CLEAR_Enabled << TIMER_SHORTS_COMPARE0_CLEAR_Pos);

/*
  // タイマ割り込み設定
  NVIC_SetPriority(TIMER2_IRQn, 3);   // 割り込み優先度設定
  NVIC_ClearPendingIRQ(TIMER2_IRQn);  // 保留割り込みクリア
  NVIC_EnableIRQ(TIMER2_IRQn);        // 割り込み許可
*/  

  // PPIの設定(チャネル0を利用)
  //   TIMER2 コンパレータ0一致イベント と GPIOTE(ch0)LEDピン・トグルタスク を結び付ける
  NRF_PPI->CH[0].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];       // PPI.ch0 にLEDピン・トグルタスク設定
  NRF_PPI->CH[0].EEP  = (uint32_t)&NRF_TIMER2->EVENTS_COMPARE[0];  // PPI ch0 にコンパレータ0一致イベント設定  
  NRF_PPI->CHENSET   |= PPI_CHENSET_CH0_Enabled;                   // PPI ch0 有効

  NRF_TIMER2->TASKS_START = 1;   // タイマスタート
  
}

void loop(){
    __SEV();
    __WFE();
    __WFE();
}


前回からの修正は、
・ 割り込み関数 TIMER2_IRQHandler(void) のコメントアウト
・ タイマー割り込み設定、割り込み関数登録のコメントアウト
・ PPI設定の追加
を行っています。

PPIの設定は簡単です(下記3行で設定)。
  // PPIの設定(チャネル0を利用)
  //   TIMER2 コンパレータ0一致イベント と GPIOTE(ch0)LEDピン・トグルタスク を結び付ける
  NRF_PPI->CH[0].TEP  = (uint32_t)&NRF_GPIOTE->TASKS_OUT[0];       // PPI.ch0 にLEDピン・トグルタスク設定
  NRF_PPI->CH[0].EEP  = (uint32_t)&NRF_TIMER2->EVENTS_COMPARE[0];  // PPI ch0 にコンパレータ0一致イベント設定  
  NRF_PPI->CHENSET   |= PPI_CHENSET_CH0_Enabled;                   // PPI ch0 有効

PPIのチャネル0~15のうち、今回はチャネル0を利用しています。
CH[0].TEPにタスク、CH[0].EEPにイベントのレジスタのアドレスを登録しています。
これにより、タスクとイベントを結び付けることが出来ます。

タイマーのカウンターが指定した値になったら、指定したタスクを実行します。

PPIは、周辺機器の様々なイベント(状態変化)発生のタイミングで、指定したタスクを
実行することが出来る機構です。

micro:bitに採用されているMCU: Nordic nRF51822 ARM Cortex-M0 は、
ハードウェアによる直接PWMを生成する機能は無いのですが
PPIを使うことで、同様のことを行うことが出来ます(たぶん)。
(PWMのHIGH期間、LOW期間の調整はコンパレータを2つ用いて出来ると思います)

«ルミちゃんの毛づくろい