フォト
2018年1月
  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 29 30 31      
無料ブログはココログ

arduino

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は、IARM的には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/Tama…/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つ用いて出来ると思います)

2018年1月10日 (水)

micro:bit用Arduino環境にてrtcの精度が悪い - その対策

micro:bit をArduino環境で利用しています。
時計を実装したいと思い、mbed用の下記のライブラリを参考にしてやってみました。

参考にしたサイト
・Francis Schumacher /  nrf51_rtc
https://os.mbed.com/users/fxschumacher/code/nrf51_rtc/
・Francis Schumacher /  nRF51_rtc_example
https://os.mbed.com/users/fxschumacher/code/nRF51_rtc_example/file/c1f06d0a5e11/main.cpp/

一見、ちゃんと動作していると思いつつ、放置すると徐々に時間が進んでしまいます。
誤差を測定すると私のmicro:bitでは5%も速く時間を刻みます。
(誤差はmicro:bitの個体ごとに異なると思います)

Photo

原因を調べるとArduinoのrtc(リアルタイムクロック)に供給されているクロックソース
LFCLKSRC32.768 kHz RC oscillatorに設定されていました。

micros()、millis()、delay()ではrtc1が利用されており、これらも5%進んでしまいます。
このズレは、micros()、millis()、delay()でパルス幅の生成や測定を行っている場合には
影響を受けるかもしれません。

改善方法として、LFCLKSRCに指定するクロックソースを
16MHz crystal oscillatorをベースにしている LFCLK synthesizerに設定します。
RCオシレータよりクリスタル・オシレータの方が当然精度が良いですね。

具体的には、setup()の頭でLFCLKSRCのクロックソースを次のように設定します。

void setup() {
  NRF_CLOCK->TASKS_LFCLKSTOP = 1;
  NRF_CLOCK->LFCLKSRC = 
    (uint32_t)((CLOCK_LFCLKSRC_SRC_Synth << CLOCK_LFCLKSRC_SRC_Pos) &
    CLOCK_LFCLKSRC_SRC_Msk);
  NRF_CLOCK->TASKS_LFCLKSTART = 1;
   ・・・・

実際に試したところ、当初の5%よりもかなり改善されました。
一晩放置して誤差を調べてみます。

別の方法として、コンパイルオプション -DUSE_LFSYNTを付けることでも対応出来ます。
Arduinoのローカル設定ファイル platform.local.txtを
AppData\Local\Arduino15\packages\sandeepmistry\hardware\nRF5\0.4.0\に作成して
  compiler.c.extra_flags=-DUSE_LFSYNT
  compiler.cpp.extra_flags=-DUSE_LFSYNT

を定義で対応でいけると思います。

2018/01/11 追記


改善を施した場合の誤差は0.6%でした。
24時間で8分ずれます。思ったほど精度が改善できませんでしたが、
まあ、効果はあるのでこれで良しとします。

2018年1月 9日 (火)

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

豊四季Tiny BASIC for micro:bit をV0.04に更新しました。
MSGコマンドでテキストメッセージ表示、加速度センサーの値取得等に対応しました。

豊四季Tiny Basic for micro:bit V0.04
https://github.com/Tamakichi/ttbasic_microbit




動画は下記のプログラムを動かしている様子です。

10 CLS 1
20 MSG LEFT,200,"コンニチハ"
30 FOR I=O TO 30
40 MSG DOWN,50,I/10
50 WAIT 50
60 MSG LEFT,100,I%10
70 NEXT I
80 WAIT 500
90 GOTO 20

プログラムはシリアルコンソール上にて作成し、即実行することが出来ます。

01


2017年12月28日 (木)

micro:bitをArduino環境で使う(4)豊四季Tiny BASICを動かしてみる

Tetsuya Suzukiさんが開発・公開している「豊四季タイニーBASIC」をmicro:bitに移植しました。
まだまだ機能不十分ですが、下記のサイトにて公開しました。
先日作成した、内部フラッシュメモリへの保存機能も組み込みました。

豊四季Tiny Basic for micro:bit V0.03
https://github.com/Tamakichi/ttbasic_microbit

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

シリアル接続したターミナル上でプログラムを作成することが出来ます。
フルスクリーンエディタ対応で、操作は8ビットマイコン時代のBASIC風です。

02

上記のプログラムを動かした様子

01

機能的には、現時点で
  ・シリアルコンソール上でのPRINT、INPUT等の一般的なコマンドの利用
  ・プログラムの保存・読み込み・保存一覧表示:SAVE n、LOAD n、FILES
 ・LEDマトリックスの描画:PSET、LINE、RECT、CIRCLE
 ・GPIOを使ったデジタル入出力、アナログ入力:OUT、IN()、ANA()

に対応しています。

ファームウェアの書込みは、通常のプログラム書き込みと同様です。
公開サイトからダウンロードした圧縮ファイルを解凍し、
binフォルダ内のhexファイルをmicro:bitのフォルダに書き込みます。

マニュアル等はこれから整備していきます。

グラフィカルなインタフェースも良いのですが、修正の都度コンパイルと書き込みに
ちょっとフラストレーションがたまります。
その点、即実行できるインタープリタ言語のBASICは思ったら直ぐ実行できるので良いです。

2017年12月25日 (月)

micro:bitをArduino環境で使う (3)内部フラッシュへのデータ書き込み

256kバイトもあるフラッシュメモリにプログラムから自由にデータを書き込みたいと思い、ライブラリを作成しまた。

2017/12/27 更新
書込みの*addr++ = *ptr++;の両アドレスが4バイト境界でないとフォルトを起こしてしまうようです。
その防止のためのエラー処理を追加しました。

ライブラリ

//
// フラッシュメモリ書き込みライブラリ
// 2017/12/25 by たま吉さん
// 参考文献
//  nRF51 Series Reference Manual Version 3.0 「6 Non-Volatile Memory Controller (NVMC)」
//

#include <Arduino.h>
#include "nrf.h"

//
// フラッシュメモリ指定ページ消去
// フラッシュメモリアドレスは、0x00000000 ~ 0x0003FFFFの範囲とする
// 1ページのサイズは1024バイトとし、アドレスは
//
// 引数
//   addr 消去対象ページ先頭アドレス
// 戻り値
//   0:正常終了
//   1:異常終了(指定アドレスが正しくない)
//
uint8_t  flashPageErase(uint32_t addr) {
  if ( addr >= 0x00040000 )
    return 1;  // フラッシュメモリ領域外
  if ( addr % 1024 )
    return 1;  // ページ先頭アドレスでない
  
  // 書込みアンロック
  NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Een;
  while (NRF_NVMC->READY == NVMC_READY_READY_Busy);

  // 指定ページの消去
  NRF_NVMC->ERASEPAGE = addr;
  while (NRF_NVMC->READY == NVMC_READY_READY_Busy);

  // 書込みロック
  NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren;
  while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
  return 0;
}

// フラッシュメモリ32ビットワードデータ書き込み
// 引数
//  addr: 書込みアドレス(4バイト境界アドレスであること)
//  ptr : 書込みデータ格納アドレス(4バイト境界アドレスであること)
//  len : 書込みデータ長さ
// 戻り値
//   0:正常終了
//   1:異常終了(指定アドレスが正しくない)
//
uint8_t flashWriteData(uint32_t* addr, uint32_t *ptr, uint16_t len) {

  if ( ((uint32_t)addr) >= 0x00040000 )
    return 1;  // フラッシュメモリ領域外
  if ( ((uint32_t)addr) % 1024 )
    return 1;  // ページ先頭アドレスでない
  if (((uint32_t)ptr)%4)
    return 1;  // 4バイト境界アドレスでない
    
  // 書込み有効設定
  NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Wen;
  while (NRF_NVMC->READY == NVMC_READY_READY_Busy);

  // データ書き込みループ
  for (uint32_t i = 0; i<len; i++) {
    *addr++ = *ptr++;
    //Serial.println(i,DEC); 
    while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
  }

  // 書込み無効設定
  NRF_NVMC->CONFIG = NVMC_CONFIG_WEN_Ren;
  while (NRF_NVMC->READY == NVMC_READY_READY_Busy);
  return 0;
}


利用サンプルスケッチ
//
// micro:bit フラッシュメモリ書込みテスト 2017/12/25 by たま吉さん
//

uint8_t  flashPageErase(uint32_t addr);
void flashWriteData(uint32_t* addr, uint32_t *ptr, uint16_t len);

#define FLASH_PAGESIZE    1024         // ページサイズ
#define FLASH_PAGENUM     256          // 総ページ数
#define FLASH_TOPADR      0x00000000   // フレッシュメモリ先頭アドレス
#define FLASH_SAVESIZE    4096         // 保存データサイズ(ttbasicを想定)
#define FLASH_SAVEPAGENUM 4            // 
#define FLASH_SAVENUM     1
#define FLASH_SAVEPOP  ((FLASH_TOPADR+FLASH_PAGESIZE*256)-FLASH_SAVESIZE*FLASH_SAVENUM)

uint8_t buf[FLASH_SAVESIZE];
void setup() {
  uint8_t c;
  
  Serial.begin(115200);
  Serial.print("Save top adr=");
  Serial.println(FLASH_SAVEPOP,HEX);

  // テスト用ダミーデータ
  for (uint16_t i=0; i < FLASH_SAVESIZE; i++) {
    buf[i] = i%256;
  }

  Serial.print("Write(y/n)? ");
  while(!Serial.available());
  c = Serial.read();
  Serial.write(c);
  Serial.println();
  if (c == 'y') {
  
    // ページの消去
    Serial.println("ERASE PAGE");
    for (uint8_t i=0; i<FLASH_SAVEPAGENUM; i++) {
       flashPageErase(FLASH_SAVEPOP+FLASH_PAGESIZE*i);
    }
  
    // データの書込み
    Serial.println("Write Data");
    //for (uint8_t i=0; i<FLASH_SAVEPAGENUM; i++) {
      flashWriteData((uint32_t*)FLASH_SAVEPOP, (uint32_t*)buf, FLASH_SAVESIZE/4);
    //}
  }
  
  // データの確認
  Serial.println("Read Data");
  for (uint32_t i=0; i <FLASH_SAVESIZE; i++) {
    if (i%16 == 0) {
      Serial.print(FLASH_SAVEPOP+i,HEX);
      Serial.print(":");
    }
 
    c = *((uint8_t*)FLASH_SAVEPOP+i);
    if (c<0x10){
      Serial.print("0");Serial.print(c,HEX);
    } else {
      Serial.print(c,HEX);
    }
    if (i%16 == 15) 
      Serial.println();
    else
      Serial.print(" ");
   }
}

void loop() {
  // put your main code here, to run repeatedly:

}

実行結果

03

フラッシュメモリの書込みは、ページ単位(1024バイト)で行います。
データの書込みはワード単位(4バイト)で行います。

フラッシュメモリの256kバイト領域のアドレス範囲は、
   0x00000000 ~ 0x0003FFFF
です。

テストプログラムでは、ブートローダやプログラムを破壊しないよう、後半部に4kバイトを書き込んでいます。

このライブラリは、豊四季TinyBASICの移植時に利用する予定です。

2017年12月 9日 (土)

Arduino IDEのgccのバージョンの再認識

Arduino IDE 1.8.5の環境は、「C++11対応しているの?」とちょっと疑問に思い、試してみました。


サンプルスケッチ

//
// C++11対応の確認
//

void setup() {
  Serial.begin(9600);

  int x       {3};           // 新しい初期化形式
  int d[]     {0,1,2,3,4,5}; // 新しい初期化形式(配列)
  char *ptr   {nullptr};     // 新しい初期化形式、NULLの新しい形式
  uint16_t b  {0b11110001};  // 2進数対応

  Serial.print("x=");
  Serial.println(x,DEC);
  Serial.print("b=");
  Serial.println(b,BIN);
      
  for (auto a:d) {         // 新しい範囲指定形式 
    Serial.println(a,DEC);
  }
}

void loop() {

}

コンパイルは問題なく出来ました。実行結果も期待通りに動作しています。
う~ん、これは積極的に使っていった方がコーディング量も減り、不具合も回避できるかも..

01


裏付けを取るために、Arduinoのgccの環境周りを調べてみました。
まずは、利用しているgccのバージョンですが、
C:\Users\ユーザー\AppData\Local\Arduino15\staging\packagesの下には、
avr-gcc-4.9.2-atmel3.5.4-arduino2-i686-mingw32.zip
があります。

確認のため、インストールされているフォルダ \arduino-1.8.5\hardware\tools\avr\bin
のgcc++のバージョンを調べると4.9.2でした。

02

次にコンパイルオプション
\arduino-1.8.5\hardware\arduino\avr\platform.txt に定義されているコンパイルオプション
を調べると次の設定になっています。

compiler.c.cmd=avr-gcc
compiler.c.flags=-c -g -Os {compiler.warning_flags} -std=gnu11 -ffunction-sections -fdata-sections -MMD -flto -fno-fat-lto-objects


compiler.cpp.cmd=avr-g++

compiler.cpp.flags=-c -g -Os {compiler.warning_flags} -std=gnu++11 -fpermissive -fno-exceptions -ffunction-sections -fdata-sections -fno-threads

gcc4.9.2では機能的にc++11は完全対応であり、
cコンパイラはC11対応、C++コンパイラはC++11対応の指定が設定されています。

これでc++11の記述が堂々と利用出来ます。

(注意)
同じArduino IDE を利用していても、Arduino M0、DueのARMや、別のアーキテクチャのボードは、
利用しているコンパイラが異なります。この設定はAVRマイコン限定です。
他の環境については、別途調べる必要があります。

2017年11月25日 (土)

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

今後色々と利用するために、nRF51822のリファレンス・マニュアルをちびちびと読解しています。

今日はGPIOTE(GPIO tasks and events)を使ってLチカをやってみました。
GPIOTEは、GPIOのピンの入力に対するイベント、または出力に対するタスクを定義することが出来ます。

GPIOTEは便利なのですが、4チャンネル(4つしか定義出来ない)です。
Arduino IDE環境では、GPIOピンのピン入力割り込みに利用されています。
具体的には、attachInterrupt()、detachInterrupt()です。
4チャンネル(4つしか定義出来ない)の制約から、attachInterrupt()は同時に4つまで
の利用となります。

とりえず、GPIOTEの利用の練習として、前回のLチカで利用してみました。
GPIOTEの0チャンネルを利用してLEDのポートをトグルでHIGH、LOW出力するタスクを
定義して、割り込み関数でタスクを実行します。

スケッチ


#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);        // 割り込み許可
  
  NRF_TIMER2->TASKS_START = 1;        // タイマスタート
  
}

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


割り込み関数内で、前回のLED用のポート出力をdigitalWrite()で制御をコメントアウトして、
代わりにNRF_GPIOTE->TASKS_OUT[0] = 1 でチャネル0のタスクを実行しています。

そのタスクの定義は、下記で行っています。

  // 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有効

LEDピンのタスクを定義しています。

NRF_GPIOTE->TASKS_OUT[0] = 1によるタスク実行指示で、
動作指定をトグルにし、初期設定状態はLOWとしています。
この定義を行った場合、pinMode()によるピン機能の設定は不要となります。

割り込み関数で、NRF_GPIOTE->TASKS_OUT[0] = 1実行される度に
タスクが実行され、LEDピンの出力がトグルでHIGH,LOWと変化します。

次は、PPI(Programmable Peripheral Interconnect)を使って、
割り込みを利用せずにLチカをやってみます。
PPIは、イベントに対して実行するタスクを割り当てることが出来ます。

タイマ(TIMER2)のコンパレータとの一致イベントNRF_TIMER2->EVENTS_COMPARE[0]と
LEDの点滅タスクNRF_GPIOTE->TASKS_OUT[0] を結び付けてれば、Lチカするはずです。

前回のLチカスケッチでは、loop()で無駄に無限ループをしていました。
省電力対策を調べたところ、

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

でスリープモードになり、割り込み発生時にウェイクして処理を実行してくれるようです。
  参考にしたサイト
  How do you put the nrf51822 chip to sleep? [closed]

2017年11月24日 (金)

micro:bitをArduino環境で使う (1)タイマー割り込みでLチカ

前回の「micro:bitはじめました」では、microbitをArduino IDE環境で利用出来るようにしました。
今回はそのArduinoIDEを使ってタイマー割り込みを使ってLチカをやってみました。

Dscn7318


microbitに搭載されているARM Cortex-M0 nRF51822でタイマ割り込みを行う場合、
タイマかRTCを使って実現できるようです。
今回はタイマを使って5x5マトリックス左上LEDを0.5秒間隔で点滅させてみました。


スケッチ

#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;  // 割り込みイベントクリア
    sw =!sw;
    digitalWrite(LED, sw);
}

void setup() {
  Serial.begin(115200); 
  Serial.println("microbit is ready!");
  pinMode(COL1, OUTPUT);
  digitalWrite(COL1, LOW);     
  pinMode(LED, OUTPUT);

  //タイマ設定
  NRF_TIMER2->TASKS_STOP = 1;                          // タイマストップ
  NRF_TIMER2->TASKS_CLEAR = 1;                         // カウンタクリア
  NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;            // モード設定:タイマモード
  NRF_TIMER2->PRESCALER   = 8;                         // プリスケーラ設定:256分周(62.5kHz)
  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);        // 割り込み許可
  
  NRF_TIMER2->TASKS_START = 1;        // タイマスタート
  
}

void loop(){

}

解説

タイマー割り込みはTIMER2のレジスタを直接操作して実装しています。
(TIMER1は、PWM出力で利用されているため使わない方がよいでしょう)

まず、割り込みで呼び出される関数が、TIMER2_IRQHandler(void)です。
この関数名は固定で、extern "C"を付けてC言語互換にする必要があります。

呼び出された割り込み関数内では、割り込みイベントをクリアする必要があります。
   NRF_TIMER2->EVENTS_COMPARE[0] = 0;

にてそれを行っています。コンパレータ0を使った割り込みのため、対応するイベントを
クリアしています。

次に、タイマ設定についてすが、最初の2行はコメントの通りで、タイマをストップして
カウンタをクリアしてます。
  NRF_TIMER2->TASKS_STOP = 1;     // タイマストップ
  NRF_TIMER2->TASKS_CLEAR = 1;   // カウンタクリア

次の2行は、タイマのモード設定とプリスケーラ(分周)を設定しています。
  NRF_TIMER2->MODE = TIMER_MODE_MODE_Timer;   // モード設定:タイマモード
  NRF_TIMER2->PRESCALER   = 8;                            // プリスケーラ設定:128分周(125KHz)

タイマのモードには、タイマモードと、キャプチャモードがあります。
クロックを供給してカウントする動作を行う場合はタイマモードを指定します。

プリスケーラの設定は0~9まで指定可能です。次の表の感じでシステムクロックの
16MHzを分周して、カウンタのソースクロックの周波数を設定できます。
スケッチでは8を指定しているので、256分周で62,500Hzを利用しています。

  02

さらに次の2行はカウンタ長とコンパレータ0の値を設定しています。
   NRF_TIMER2->BITMODE = TIMER_BITMODE_BITMODE_16Bit;   // カウンタ長設定:16ビット長指定
   NRF_TIMER2->CC[0] = 62500/2;                                             // コンパレータ0の設定:0.5秒周期

TIMER2のカウンタ長は8ビット幅、16ビット幅の指定が可能です。
16ビットでは65535カウント(約1048ミリ秒)までカウントできます。

コンパレータはカウンタとの比較に利用するものです。
カウンタ値を一致した場合に割り込みを発生することが出来ます。
コンパレータは4つ利用出来ますが、ここではコンパレータ0を利用しています。

コンパレータ0のビット長はタイマのカウンタ長に準じます。
ここでは62500/2を指定しています。ちょうど0.5秒幅です。

次の4行は、割り込み条件の設定とショートカットの設定を行っています。
  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);

NRF_TIMER2->INTENSET にはコンパレータ0を一致したら割り込み発生するように指定、
NRF_TIMER2->SHORTS には割り込みのイベント発生時にカウントをクリア(0)にする
設定を行っています。この設定を行わないと、コンパレータ0を一致した後もカウンタが
クリアされないので、割り込み周期は16ビット幅のオーバーフロー発生の周期になります。
NRF_TIMER2->SHORTSを行わずに、割り込み関数内でカウンタをクリアしても良いです。

次の3行は、割り込み優先度の設定等です(コメント参照)
   NVIC_SetPriority(TIMER2_IRQn, 3);       // 割り込み優先度設定
   NVIC_ClearPendingIRQ(TIMER2_IRQn);  // 保留割り込みクリア
   NVIC_EnableIRQ(TIMER2_IRQn);           // 割り込み許可

最後に、タイマをスタートさせます。
   NRF_TIMER2->TASKS_START = 1;        // タイマスタート

タイマについては、PPI(Programmable Peripheral Interconnect)との連携方法がまだわかりません。
PPIを使えば、割り込みを使わずにLチカ出来ると思っているのですが。

このあたりのレジスタマップ定義はArduino IDEのnRF51環境にnRF51 SDKが
組み込まれているために利用出来ているようです。
まとまったドキュメントを探しているのですが、見つからないので
Arduino IDEのnRF51環境周りをdoxygenにかけてドキュメント化して利用しています。

03_2

2017/11/25 追記
Arduino IDEのnRF51環境周りのファイルを調べると、nRF5_SDKのうち、
ヘッダファイルの一部しか入っていないことが判明。SDKは実質レジスタ等の定義のみです。





より以前の記事一覧