フォト
2018年5月
    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年5月22日 (火)

NeoPixel(WS2812B)の制御 その2

前回行った、ArduinoによるNeoPixel(WS2812B)の制御の続きです。
前回の処理の一部をSPIを利用する方式に書き換えました。

SPIを使っているため、出力ピンはMOSIピン(D11)固定となりますが、
前回よりも安定したクロックでの出力となりました。

  04

前回の信号生成の条件を考慮し、

02

03

SPIのクロックを8MHzとした場合、1クロック幅は0.125μ秒となります。
このクロック幅を元に利用して、

   T0H、T1L : 0.375μ秒 (3クロック分)
   T0L、T1H : 0.625μ秒 (5クロック分)

として利用します。値としては、「Data transfer time」の表の有効範囲から
少々ずれていますが、問題無いようです。

ちょうど0、1の送信とも8クロック分になりSPIの送信単位の8ビットにマッチします。

01
NeoPixelに1ビット送信するのに、SPIを利用して1バイト送れば良いことになります。
    0 : 0b11100000
    1 : 0b11111000



修正したスケッチ

前回のスケッチでNeoPixelの初期化とデータ送信を行っている
NeoInit() 、NeoUpdate()をSPIを利用する方式に修正しました。

//
// Neopixelの制御 SPIバージョン by たま吉さん  2018/05/22
//

#include <SPI.h>

//***************
// 定数
//***************

#define PIXCELNUM   16           // Neopixel ピクセル数(LED数)
#define PIN         11           // Neopixel 制御用ピン番号
#define NEOSPI_0    0b11100000   // 1ビット 値0
#define NEOSPI_1    0b11111000   // 1ビット 値1
#define NEOSPI_RST  0b00000000   // REST

//***************
// グローバル変数
//***************
uint8_t buf[PIXCELNUM*3];     // Nexpixel用ピクセル色データ(ピクセル数 x 24ビット)

//***************
// 関数
//***************
// Neopixel初期化
void NeoInit() {
  memset(buf, 0, PIXCELNUM*3); // バッファの初期化

  // SPIの初期化
  SPI.setBitOrder(MSBFIRST);            // 最上位ビットから送信
  SPI.setClockDivider(SPI_CLOCK_DIV2);  // クロック 8MHz
  SPI.setDataMode(SPI_MODE1);           // アイドル時 LOW、立上りエッジ時送信
  SPI.begin();                          // 開始
}

// Neopixelへのデータ送信
void NeoUpdate() {
  // RESET送信
  SPDR = NEOSPI_RST;              // SPIデータ送信
  while(!(SPSR & (1 << SPIF))) ;  // 送信完了待ち
  delayMicroseconds(50);

  // ピクセル数x24ビット送信
  for (uint8_t i = 0; i < PIXCELNUM*3; i++) {
    for (uint8_t j = 0; j < 8; j++) {
      SPDR = buf[i] & (0x80>>j) ? NEOSPI_1:NEOSPI_0; // SPIデータ送信
      while(!(SPSR & (1 << SPIF))) ;                 // 送信完了待ち
    }
  }
}

// Neopixelの表示クリア
void NeoCLS() {
    memset(buf, 0, PIXCELNUM*3); // バッファの初期化
    NeoUpdate();                 // 表示更新
}

// 指定したピクセルの色を設定
void NeoSetRGB(uint8_t no, uint8_t R, uint8_t G, uint8_t B, uint8_t flgUpdate=false) {
  if (no < PIXCELNUM) {
    buf[no*3+0] = R;
    buf[no*3+1] = G;
    buf[no*3+2] = B;    
  }
  if (flgUpdate)
    NeoUpdate();
}

// ピクセルのシフト
void ShiftPixel() {
  uint8_t tmpbuf[3];
  memmove(tmpbuf,buf,3);
  memmove(buf, buf+3, (PIXCELNUM-1)*3);
  memmove(buf+(PIXCELNUM-1)*3,tmpbuf,3);
  NeoUpdate();
}

void setup() {
  
  NeoInit();  // Neopixcelの初期化
  NeoCLS();   // Neopixcelの表示クリア
  
  NeoSetRGB(0, 128,0,0,true); // No.0のピクセルを赤
  NeoSetRGB(1, 0,128,0,true); // No.1のピクセルを緑
  NeoSetRGB(2, 0,0,128,true); // No.2のピクセルを青
}

void loop() {
 delay(80);
 ShiftPixel(); // ピクセルをシフトして更新表示
}

SPIによるデータ送信は、関数呼び出しだと処理が間に合わないと判断し、
データレジスタSPDRに送信データをセットし、ステータスレジスタSPSRを参照して
送信完了待ちを行っています。

次回は8x8ドットマトリックスタイプのNeopixelを使ってもう少し複雑なことをやろうかと思います。


参考にしたサイト
Todotaniのはやり物Log - SPIの基本動作とArduinoでの使い方
しなぷすのハード製作記 - 「SPI」の解説
garretlab - Arduinoで遊ぶページ - SPI関連レジスタ
Stupiddog - ArduinoでSPI通信を行う方法
株式会社インデペンデンスシステムズ横浜 - Arduino UnoでSPI通信(その1)Arduino Uno2台で通信
QEEWiki - SPI (Serial Peripheral Interface)

2018年5月20日 (日)

NeoPixel(WS2812B)の制御

Arduino UnoでNeoPixel(WS2812B)の制御をライブラリ無しで行ってみました。
まずは手持ちのリング形状16個LEDのタイプを制御してみました。

Dscn7924

動いている様子



スケッチ

//
// Neopixelの制御 by たま吉さん  2018/05/20
//

//***************
// 定数
//***************

#define PIXCELNUM   16        // Neopixel ピクセル数(LED数)
#define PIN         2         // Neopixel 制御用ピン番号

//***************
// グローバル変数
//***************
uint8_t buf[PIXCELNUM*3];     // Nexpixel用ピクセル色データ(ピクセル数 x 24ビット)
volatile uint8_t * NeoOutReg; // Neopixcel出力レジスタ
uint8_t  NeoBitOut;           // Neopixcelセットビット
uint8_t  NeoBitMask;          // Neopixcelクリア用マスク

//***************
// 関数
//***************

// Neopixel初期化
void NeoInit() {
  memset(buf, 0, PIXCELNUM*3); // バッファの初期化

  // 出力ピンの初期化
  pinMode(PIN, OUTPUT);
  digitalWrite(PIN, LOW);
  NeoOutReg  = portOutputRegister(digitalPinToPort(PIN));  // Neopixcel出力レジスタ
  NeoBitOut  = digitalPinToBitMask(PIN);  // Neopixcelセットビット
  NeoBitMask = ~NeoBitOut;                // Neopixcelクリア用マスク
 }

// Neopixelへ1を出力
inline void NeoOut_1() {
  *NeoOutReg |= NeoBitOut;   // HIGHの出力
  asm volatile(
     "nop"    "\n\t"
     "nop"    "\n\t"     
     "nop"    "\n\t"
     "nop"    "\n\t"     
  );
  *NeoOutReg &= NeoBitMask;  // LOWの出力
}

// Neopixelへ0を出力
inline void NeoOut_0() {
  *NeoOutReg |= NeoBitOut;   // HIGHの出力
  *NeoOutReg &= NeoBitMask;  // LOWの出力
  asm volatile(
     "nop"    "\n\t"
     "nop"    "\n\t"     
     "nop"    "\n\t"
     "nop"    "\n\t"     
     "nop"    "\n\t"
     "nop"    "\n\t"     
     "nop"    "\n\t"     
     "nop"    "\n\t"
  );  
}

// Neopixelへのデータ送信
void NeoUpdate() {
  uint8_t testbit = 0b10000000;
  *NeoOutReg &= NeoBitMask;  // LOWの出力
  delayMicroseconds(50);
  cli(); 
  for (uint8_t i = 0; i < PIXCELNUM*3; i++) {
    if (buf[i] & 128) NeoOut_1(); else  NeoOut_0();
    if (buf[i] &  64) NeoOut_1(); else  NeoOut_0();
    if (buf[i] &  32) NeoOut_1(); else  NeoOut_0();
    if (buf[i] &  16) NeoOut_1(); else  NeoOut_0();
    if (buf[i] &   8) NeoOut_1(); else  NeoOut_0();
    if (buf[i] &   4) NeoOut_1(); else  NeoOut_0();
    if (buf[i] &   2) NeoOut_1(); else  NeoOut_0();
    if (buf[i] &   1) NeoOut_1(); else  NeoOut_0();
  }
 sei();
}

// Neopixelの表示クリア
void NeoCLS() {
    memset(buf, 0, PIXCELNUM*3); // バッファの初期化
    NeoUpdate();                 // 表示更新
}

// 指定したピクセルの色を設定
void NeoSetRGB(uint8_t no, uint8_t R, uint8_t G, uint8_t B, uint8_t flgUpdate=false) {
  if (no < PIXCELNUM) {
    buf[no*3+0] = R;
    buf[no*3+1] = G;
    buf[no*3+2] = B;    
  }
  if (flgUpdate)
    NeoUpdate();
}

// ピクセルのシフト
void ShiftPixel() {
  uint8_t tmpbuf[3];
  memmove(tmpbuf,buf,3);
  memmove(buf, buf+3, (PIXCELNUM-1)*3);
  memmove(buf+(PIXCELNUM-1)*3,tmpbuf,3);
  NeoUpdate();
}

void setup() {
  NeoInit();  // Neopixcelの初期化
  NeoCLS();   // Neopixcelの表示クリア
  
  NeoSetRGB(0, 128,0,0,true); // No.0のピクセルを赤
  NeoSetRGB(1, 0,128,0,true); // No.1のピクセルを緑
  NeoSetRGB(2, 0,0,128,true); // No.2のピクセルを青
}

void loop() {
 delay(80);
 ShiftPixel(); // ピクセルをシフトして更新表示
}

制御を行うためのプロトコル自体は非常に簡単です。
1ポートからLOW or HIGHを指定したタイミングで出力するだけです。

リセット(RET)コード送信後、1つのLED(ピクセル)毎に24ビット分の0 or 1の送信、
複数のLEDの場合、24ビットxLED数分のデータを送信します。

02

リセット(RET)コードは、Treset(50μ秒)間 LOWを出力、
1ビット 0 を送信(0 code)は、T0H間HIGHを出力後、T0L間LOWを出力、
1ビット 1 を送信(1 code)は、T1H間HIGHを出力後、T1L間LOWを出力、
します。

だだし、このタイミングがシビアです。

03

作成したスケッチでは、
   T0H、T1L : 0.375μ秒 (6クロック分)
   T0L、T1H : 0.625μ秒 (10クロック分)

をとしました。
値としては、「Data transfer time」の表の有効範囲から少々ずれていますが、
問題無いようです。

Arduino Unoはシステムクロックが16MHzで動作しています。
1クロックは 1/16000000 = 0.0625μ秒 となります。

わずか、6~10クロックというシビアなタイミングで信号を出力する必要があります。
今回は6~10クロックの待ち時間の調整はインラインアセンブラ命令でNOPを入れて
調整しました。

  *NeoOutReg |= NeoBitOut;   // HIGHの出力
が6クロック
  *NeoOutReg &= NeoBitMask;  // LOWの出力
が3クロック
要していることを考慮しています。

時間待ち中は割り込みを禁止しています。
割り込みが入ると動作に外乱が入り、データが化けます。
また、デジタル出力をdigitalWire()で行うと処理が間に合いません。
そこで、出力レジスタに直接、値を書き込んでいます。

まあ、これだとシリアル通信等、他に何も出来ません。
そこで、次の対策としてSPIを使って何とかしようと思います。

参考にしたサイト
uratan - WS2812B の駆動タイミングの限界調査
PICマイコンの小部屋 - 秋月でWS2812B買ってしまったので動かないかも知れないけれど作ってみた

2018年5月15日 (火)

micro:bitで8x8ドットNeopixelを使ったメッセージ表示

豊四季Tiny BASIC for micro:bit で 8x8ドットNeopixelによるメッセージ表示を実装しみました。

01

8x8ドットNeopixelは以前Aliexpressにて購入したものを使いました。

02

64個のNeopixelを駆動させるので、電源は電池から供給しています。

動いている様子



プログラムソース

10 'Neopixelで文字のスクロール表示
20 NPBEGIN 12,64
30 GOSUB "@CLSM"
40 S="こんにちは さいたま":N=RGB8(0,1,1) B=0:W=70:GOSUB "@MSG"
50 WAIT 500:NPCLS
60 S="これはNeoPixelによるメッセージ出力サンプルです"
70 N=RGB8(2,1,0):B=0:W=70:GOSUB "@MSG"
80 WAIT 500:GOTO 30
90 "@SCRL":'左スクロール
100 FOR Y0=0 TO 7
110 IF Y0&1 GOTO 170
120 FOR X0=6 TO 0 STEP -1
130 POKE MEM+Y0*8+X0+1,PEEK(MEM+Y0*8+X0)
140 NEXT X0
150 POKE MEM+Y0*8,0
160 GOTO 210
170 FOR X0=1 TO 7
180 POKE MEM+Y0*8+X0-1,PEEK(MEM+Y0*8+X0)
190 NEXT X0
200 POKE MEM+Y0*8+7,0
210 NEXT Y0
220 RETURN
230 "@INSC":'1文字分挿入スクロール
240 A=WADR(C)
250 FOR X1=0 TO 7
260 GOSUB "@SCRL"
270 FOR Y1=0 TO 7
280 IF PEEK(A+Y1)&($80>>X1) N0=N ELSE N0=B
290 IF Y1&1 POKE MEM+Y1*8+7,N0 ELSE POKE MEM+Y1*8,N0
300 NEXT Y1
310 NPPUT 0,MEM,64,1,1
320 WAIT W
330 NEXT X1
340 RETURN
350 "@MSG":'メッセージのスクロール表示
360 FOR I2=1 TO WLEN(S)
370 C=WASC(S,I2)
380 GOSUB "@INSC"
390 NEXT I2
400 RETURN
410 "@CLSM":'表示のクリア
420 FOR I3=0 TO 63:POKE MEM+I3,0:NEXT I3
430 NPPUT 0,MEM,64,1,1
440 RETURN

表示するメッセージは日本語に対応しています。
フォントは美咲フォントを利用しています。

8x8のピクセルは次のような順番で並んでいるため、、文字の表示やスクロール処理が
ちょっと面倒です。奇数行・偶数行でピクセル点灯の処理が異なります。

03

プログラムでは20行から80行で指定したメッセージを表示しています。
20行のNPBEGIN 12,64 は制御するピンとして12ピン、ピクセル数として64を指定、
30行のGOSUB "@CLSM" は表示用バッファの消去
40行、60行は次の変数にメッセージ等を設定し、GOSUB "@MSG"にてメッセージを
表示しています。

  変数
    S: メッセージ文字列
    N: 表示する文字色(RGB8関数で各R・G・Bの値を指定)
    B: 表示する文字の背景色(ここでは0:黒を指定)
    W: スクロール速度

50行、80行は消去及び時間待ちを行っています。
サブルーチンについては、説明を省略します。

2018年5月14日 (月)

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

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

V0.07からの変更点
・シリアル通信をI/Oピン経由で行うUARTコマンドの追加
・キャラクタで直線・矩形・塗りつぶし四角を描画するCLINEコマンドの追加
・プログラム保存領域の増量(8本 ⇒ 16本)
・リファレンスマニュアルの見直し(問題点修正・追記)

今回の修正で、任意のピンにシリアル通信用のTxD、RxDを割り当ててシリアル通信を
行うことが出来るようになりました。

次の写真はUART 12,13,"921600"を実行して12ピンにRxD、13ピンにTxDを割り付けています。01

USB-シリアル変換モジュールが対応していれば、921600bpsの高速通信が利用出来ます。
ついでに今回追加したCLINEコマンドを使って矩形描画を行っています。

02


2018年4月24日 (火)

シフトレジスタ 74HC595の考察

電子工作で、8ビットシフトレジスタ74HC595をよく使います。
しかし、仕様を完全に理解していないで、他ブログや自分の過去の利用を流用し、
その場しのぎで利用していました。

Snapshot000003

「これじゃぁ、アカン、ちゃんと仕様と機能をちゃんと理解しよう」と思い、
調査しまとめることにしました。


まずは仕様の再確認

ピンレイアウト

Photo
メーカー毎に微妙にピン名称が異なります。
MC74HC595Aが分かり易い名称なので、データシートからピンレイアウトを
拝借し、修正しました。


ブロック図

Photo

内部的には、シフトレジスタストレージレジスタの構成になっています。

変化するシフトレジスタの8ビットパラレル出力をストレージレレジスタが
ラッチにて取り込んで、パラレル出力の状態を保持&出力します。

ストレージレジスタQAQHは3ステート(HIGHLOW、ハイインピーダンス)です。
SQHは2ステート(HIGHLOW)であるに注意)

Arduino Unoを使った実験

ブレッドボード上の8個のLEDを74HC595を使って制御します。

Dscn7854

接続は、74HC595のパラレル出力QAQHSQHにLEDを接続します。
各LEDには電流調整用に330Ωの抵抗を入れてGNDに接続しています。
各LEDはQAQHSQHの出力がHIGHの場合点灯し、LOWの場合に消灯します。

OUTPUT ENABLESERIAL DATA INPUTRESETSHIFT CLOCK, LATCH CLOCK
制御用の入力端子は、ArduinoのD10D11D2D13D3に接続します。

Photo_2

LEDの簡単な点灯制御

つぎの動画のように、8つLEDをチカチカさせる制御です。



スケッチ

// 利用ピンの定義
#define OUT_ENABLE   10
#define SERIAL_OUT   11
#define HC595_RESET   2
#define SHIFT_CLK    13
#define LATCH_CLK     3

// シフトレジスタへの8ビットデータのセット
void shift_dataOut(uint8_t data) {
  for (uint8_t i=0; i < 8; i++) {
    digitalWrite(SHIFT_CLK,LOW);
    digitalWrite(SERIAL_OUT, (data & (0x80>>i)));
    digitalWrite(SHIFT_CLK,HIGH);
  }
}

// シフトレジスタのデータリセット
void shift_reset() {
  digitalWrite(HC595_RESET,LOW);
  digitalWrite(HC595_RESET,HIGH);  
}

// シフトレジスタからストレージレジスタへのデータセット
void strage_update() {
  digitalWrite(LATCH_CLK,LOW);
  digitalWrite(LATCH_CLK,HIGH);  
}

// ストレージレジスタの出力有効
void strage_enable() {
  digitalWrite(OUT_ENABLE, LOW);
}

// ストレージレジスタの出力無効
void strage_disable() {
  digitalWrite(OUT_ENABLE, HIGH);
}

void setup() {
  // 利用ピンの初期化
  pinMode(OUT_ENABLE, OUTPUT);
  pinMode(SERIAL_OUT, OUTPUT);
  pinMode(HC595_RESET,OUTPUT);
  pinMode(SHIFT_CLK,  OUTPUT);
  pinMode(LATCH_CLK,  OUTPUT);

  // SHIFT CLOCK、LATCH CLOCKの設定
  digitalWrite(SHIFT_CLK,LOW);
  digitalWrite(LATCH_CLK,LOW);

  shift_reset();   // シフトレジスタの値リセット
  strage_update(); // シフトレジスタからストレージレジスタへの値セット
  strage_enable(); // ストレージレジスタの出力有効
  delay(2000);
}

void loop() {
  shift_dataOut(0b10101010);
  strage_update();
  delay(500);
  shift_dataOut(0b01010101);
  strage_update();
  delay(500);
}


スケッチの説明


①初期化 setup()
    (1)利用ピンの初期化
       74HC595の制御を行う5つのピンの出力設定を行っています。

    (2)SHIFT CLOCKLATCH CLOCKの設定
        初期値としてLOWの設定を行います。

    (3)シフトレジスタの値リセット
        shift_reset()関数を呼び出してシフトレジスタの値のリセットを行います。
        shift_reset()関数は次のように定義しています。

       // シフトレジスタのデータリセット
       void shift_reset() {
         digitalWrite(HC595_RESET,LOW);
         digitalWrite(HC595_RESET,HIGH);
       }   

        74HC595のリセットピンのLOWを設定してリセットを行った後、HIGHに戻しています。

    (4)ストレージレジスタへの値設定
        strage_update()関数を呼び出して、シフトレジスタの値をストレージレジスタに設定
        しています。
        strage_update()関数は次のように定義しています。    

      // シフトレジスタからストレージレジスタへのデータセット
      void strage_update() {
        digitalWrite(LATCH_CLK,LOW);
        digitalWrite(LATCH_CLK,HIGH);  
      }

        74HC595LATCH CLOCKに対して、LOWHIGHを行うことで、
        シフトレジスタの値をラッチしてストレージレジスタに取り込みます。

    (5)ストレージレジスタの出力有効
        strage_enable()関数を呼び出して、ストレージレジスタのパラレル出力QAQHSQH
        を有効にします。

        strage_enable()関数は次のように定義しています。

      // ストレージレジスタの出力有効
      void strage_enable() {
        digitalWrite(OUT_ENABLE, LOW);
      }

       74HC595OUTPUT ENABLELOWに設定することにより、
       出力を有効にしています。
       delay(2000)は、LEDの出力がリセットされていることを確認するために入れています。
       Arduinoのリセットボタンを押した直後、2秒間8個のLEDが消灯していることが確認できます。

②LEDの点灯ループ loop()
    8個のLEDに対して、2つのパターンの出力を0.5秒間隔で繰り返しています。
    shift_dataOut()関数は8個のLEDの点灯パターンを設定しています。
    shift_dataOut()関数は、シフトレジスタへの値の設定のみを行います。
    この関数を実行しても、LEDの表示には影響しません。
    shift_dataOut()関数は次のように定義しています。

   // シフトレジスタへの8ビットデータのセット
   void shift_dataOut(uint8_t data) {
      for (uint8_t i=0; i < 8; i++) {
        digitalWrite(SHIFT_CLK,LOW);
        digitalWrite(SERIAL_OUT, (data & (0x80>>i)));
        digitalWrite(SHIFT_CLK,HIGH);
     }
   }

    8ビットの値をシフトレジスタにセットするために8回繰り返し処理を行っています。
    1回毎の処理で1ビットの値をシフトレジスタにセットしています。
        (1)74HC595SHIFT CLOCKLOWにした状態にする
        (2)74HC595SERIAL OUTにセットしたい値をセットする
        (3)74HC595SHIFT CLOCKHIGHにして、SERIAL OUTの状態をラッチして
           1ビットをシフトレジスタにシフトインして取り込む。

    次にstrage_update()関数にてシフトレジスタの値をストレージレジスタにセットします。
    このタイミングでLEDの表示が更新されます。


LEDのPWMを使った明るさの制御

74HC595OUTPUT ENABLEPWMで制御することでLEDの明るさを制御することが出来ます。

スケッチのloop()deley(500)を次のように修正して、PWMにて徐々に暗くなるように
修正してみます。
実行すると、「ぽわー、ぽわー」を暗くなってからパターンが切り替わります。 

void loop() {
  shift_dataOut(0b10101010);
  strage_update();  
  for (int16_t i=0; i<256; i++) {
    analogWrite(OUT_ENABLE,i);
    delay(5);
  } 
  shift_dataOut(0b01010101);
  strage_update();
  for (int16_t i=0; i<256; i++) {
    analogWrite(OUT_ENABLE,i);
    delay(5);
  } 
}


ちょっとした疑問

74HC595
は連結(SQHを連結先の74HC595SERIAL DATA INPUTに接続)して
8ビット、16ビット、24ビットと桁数を増やすことが出来る仕様なのですが、
QHSQHが同じ値なので、
  「1個目の8ビット目と2個目の1ビット目が同じ値になってしまうのでは?」
と非常に疑問を感じます。

シフトレジスタ部の内部ロジック構造を見てみることにします。
シフトレジスタ部はDフリップ・フロップの連結構造です。

    Photo_3

(ロジック図はMC74HC595Aからの引用です)

74HC595内部の個々のDフリップ・フロップの連結と
連結する2個目の74HC595のDフリップ・フロップへの連結は
よく考えたら、差異はなくまったく同じになりますね。

下記の図は1個目の74HC595のシフトレジスタ部内のSRHと、2個目の74HC595
シフトレジスタ部内のSRAを接続したイメージです。

Dff_2

SERIAL CLOCKの立上りのタイミングでSERIAL DATA INPUTから読み込むデータを
D0、D2、D3...とすると、SQHSQAの状態は次のような感じにまります。
(時間軸は左が過去)

SQHがD5をラッチするタイミングでSQAのDにはD4が入力されているため、取り込むデータの
値は当然D4になります。

Photo_2

うぁ、良く考えたら当たり前で、こんな図を描いて考える必要もなかった。
  「Dフリップ・フロップ の連結で受け取るデータは前段のDフリップ・フロップの
   1クロック遅れたデータを受け取る」

ってことです。

まあ、これで疑問が解けたので良しとします。

2018年4月16日 (月)

ATtiny13AでI2C接続キャラクタLCDを利用する(4)

以前、挑戦ていた「ATtiny13AでI2C接続キャラクタLCDを利用する」を
Arduino 1.8.5 + MicroCore環境にてやってみました。

Dscn7826

キャラクタLCDは、秋月電子で購入したACM1602NI-FLW-FBW-M01を利用しています。
  ・I2C接続キャラクタLCD 16x2行白色バックライト付(ACM1602NI-FLW-FBW-M01)

以前コメントで頂いた、問題点の対応とRaulさんオリジナル版の
   Coding Laboratory - I2C on an AVR using bit banging

で指摘されている点の対応修正を行いました。
それと、可能な限り高速化をおこないました。

動いている様子

スケッチサイズは、MicroCoreのcore_settings.hにて利用しない機能を無効化することで
660バイトに抑えることが出来ました。

Ide

I2Cが使えると、電子工作の幅が広がりますね。

このスケッチは下記からダウンロードできます。
ダウンロード ATtiny13_I2CLCD.zip (4.7K)

2018年4月15日 (日)

ATtiny13Aで赤外線リモコン受信センサーを使う(2)

以前、「ATtiny13Aで赤外線リモコン受信センサーを使う」で行ったことを、
Arduino IDEATtiny13AMicroCoreパッケージの環境で動作確認しました。

以前のバージョンでは、micros() の精度が悪くて動作しなかったので、別パッケージを
利用していましたが、最新版では問題なく動作しました。

02

実行結果

01

ただしデフォルトでは、micros()は利用出来ないので、
core_settings.hの中のENABLE_MICROS の定義を有効にする修正が必要です。

スケッチ

//
// ATtiny13Aで赤外線リモコン受信
//
// ATMEL ATTINY13 / ARDUINO
// 利用パッケージ:https://github.com/MCUdude/MicroCore
// 注意: core_settings.h の#ENABLE_MICROS を有効にする必要あり
//
//                 +-V-+
// Reset      PB5 1|    |8 Vcc
// Tx         PB3 2|    |7 PB2 IR(IN)
// Rx         PB4 3|    |6 PB1
//            GND 4|    |5 PB0
//                 +----+
//

// シリアル通信定義
#define BAUD_RATE 38400
#include "BasicSerial3.h"
#define Tx      PB3 // (変更する場合、BasicSerial3.S修正が必要)
#define Rx      PB4 // (変更する場合、BasicSerial3.S修正が必要)

//IR受信定義
#define IR            PB2
#define IR_DDR        DDRB
#define IR_PORT       PORTB
#define IR_PININ      PINB
#define IRbitRead()   (IR_PININ&_BV(IR))
#define RC_RDH_TS     9000    // リーダコードOFF間隔  9ms判定用
#define RC_RDL_TS     3800    // リーダコードON間隔   4.5ms判定用
#define RC_BITLOW_TS  1000    // ビットデータON間隔   1.69ms判定用  
#define RC_TMOVER     8000    // タイムオバー

//
// 赤外線リモコンコード取得
// 4バイトのデータを返す
// CCCCDDdd
//    CCCC カスタムコード
//    DD   データコード
//    dd   データコードのビット反転(データチェック用)
// ただし、
//    リピートコードの場合  0
//    エラーの場合          0xFFFFFFFF
//  を返す.
//
uint32_t Read_IR() {
  uint8_t  repeat = 0;  // リピートコード検出フラグ
  uint32_t  dt    = 0;  // 赤外線リモコン読み取りデータ
  unsigned long t ;     // 信号長計測用

  // リード部の取得
  // 受信データはH/L反転で読まれる
  while(1) {
    while(IRbitRead());  // OFF検出受信待ち   
    t = micros();        // OFF検出時刻取得
    while(!IRbitRead()); // ON受信検出待ち 
    t = micros() -t;     // OFF->ONの時間間隔取得
    if (t > RC_RDH_TS) { // 9ms以上ならリーダコードとみなす
        t = micros();      // ON検出時刻取得
        while(IRbitRead());// OFF検出待ち
        t = micros() -t;   // ON->OFF時間間隔取得
        break;
    }
  }
  
  // データ部取得
  if (t < RC_RDL_TS) {
    // 0N->OFF がリピートコードの場合、データ取得はスキップ
    repeat = 1;          
  } else {
    // 0N->OFF がリダーコードの場合、データを取得
     for (uint8_t i = 0; i <32; i++) {  //32ビット分取得ループ
        // ビット開始待ち
        while(!IRbitRead());  // ON待ち
        t = micros();
        while(IRbitRead());  // OFF待ち
        t = micros() -t;
        if (t>RC_TMOVER)
          return 0xFFFFFFFF;  // エラー
        dt<<=1;
        dt |= (t>RC_BITLOW_TS) ? 1:0;     
    }
  }
  // ストップビットの待ち
  while(IRbitRead());  // OFF待ち  
  if (repeat)
    return 0;
  return dt;
}

// 文字列出力
void serOut(const char* str) {
   while (*str) TxByte (*str++);
}

// 整数を16進数で出力
void serOutHex(uint16_t h) {
  uint8_t c;
  for (int8_t i=12; i>=0; i-=4) {
    c = h>>i & 0xf; 
    c = c >9 ? c+'A'-10: c+'0';
    TxByte ((uint8_t)c);
  }
}

void setup() {
  IR_DDR &= ~_BV(IR);  // IRピンのみ入力設定する
}

void loop() {
  uint32_t rc =  Read_IR();  // IR受信
  if (rc) {
    if (rc != 0xFFFFFFFF) {
      serOut("custom="); serOutHex(rc>>16);serOut(" ");
      serOut("data=");serOutHex(rc>>8&0xff);serOut("\n\r");
    }
  } else {
    //serOut("Repeat\n\r");
  }
}

2018年4月14日 (土)

ATtiny13AでHC-SR04を使った距離計測

Arduino IDE 1.8.5の環境で、ATtiny13Aの開発が出来るようになったので、
試しに、HC-SR04を使った距離計測を実装してみました。

スケッチは、アルゴリズム雑記さんの下記の記事を参考にさせていただきました。
・【Arduino】超音波距離センサ(HC-SR04)の測定精度を向上(気温考慮)
  https://algorithm.joho.info/arduino/ultrasonic-distance-sensor-hcsr04-temp/

実装

09

実行結果

08

測定結果は、シリアルコンソールに出力します。
とりあえず、それらしく動いています。

スケッチサイズは、ジャスト1024バイト、ぎりぎり書き込めました。

09_2

スケッチ

(修正) 2018/04/14 ライブラリBasicSerial3をlibraliesに配置するように修正しました。

//
// 超音波距離センサHC-SR04を使った距離計測  
//
// ATMEL ATTINY13 / ARDUINO
// 利用パッケージ:https://github.com/MCUdude/MicroCore
//
//                 +-V-+
// Reset      PB5 1|    |8 Vcc
// Tx         PB3 2|    |7 PB2 
// Rx         PB4 3|    |6 PB1 ECHO
//            GND 4|    |5 PB0 TRIG
//                 +----+
//

// シリアル通信ライブラリ
//   AVR half-duplex software UART supporting single pin operation
//   http://nerdralph.blogspot.jp/2014/01/avr-half-duplex-software-uart.html
//
#define BAUD_RATE 38400
#include "BasicSerial3.h"

#define Tx    PB3 // (変更する場合、BasicSerial3.S修正が必要)
#define Rx    PB4 // (変更する場合、BasicSerial3.S修正が必要)

// HC-SR04
#define TRIG  PB0 // 出力ピン
#define ECHO  PB1 // 入力ピン
#define TEMP  180 // 温度補正 気温(℃)*10

// 整数を10進数で出力
void OutDEC(uint16_t d) {
int8_t n =-1;
  uint16_t v = 10000;
  for (uint8_t i=0; i<5; i++) {
    if (d >= v) {
      TxByte(d/v + '0');
      d %= v;
      n=i;
    } else {
      if (n!=-1||i==4) TxByte ('0');
    }
    v/=10;
  }
}
void setup() {
  pinMode(TRIG, OUTPUT);
  pinMode(ECHO, INPUT);
}

void loop() {
  // 超音波の出力終了
  digitalWrite(TRIG,LOW);
  delayMicroseconds(1);

  // 超音波を出力
  digitalWrite(TRIG,HIGH);
  delayMicroseconds(11);
 
  // 超音波を出力終了
  digitalWrite(TRIG, LOW);

  // 出力した超音波が返って来る時間を計測
  uint16_t d = (uint16_t)pulseIn(ECHO, HIGH, 200000);

  // 計測した時間と音速(気温を考慮)から反射物までの距離を計算
  uint16_t k=(3315+TEMP*6)/20; // 係数
  uint16_t v=d/100*k;
  uint16_t h=v/100;
  uint16_t l=v%100;

  // 計算結果をシリアル通信で出力
  OutDEC(h); TxByte('.'); OutDEC(l);
  TxByte('\n');
  delay(250);
}

シリアル出力はNerd Ralphさんが公開しているライブラリ、BasicSerial3 を利用しています。
  ・Nerd Ralph - AVR half-duplex software UART supporting single pin operation
   http://nerdralph.blogspot.jp/2014/01/avr-half-duplex-software-uart.html

   ライブラリをダウンロードし、BasicSerial3を\librariesに配置する必要があります。
   さらに、BasicSerial3.Sのピン指定を修正しています。
     #define UART_Tx 3
     #define UART_Rx 4   

スケッチの全ソースは、下記のサイトに置いています。
https://github.com/Tamakichi/Arduino_ATtiny13_HC_SR04

50円マイコン、容量の制約が厳しいですが、それなりに実装でき、十分楽しめますね。

Arduino 1.8.5環境でATtiny13Aを利用する

Arduino IDEのATtiny13A用MicroCoreパッケージを久しぶりにチェックしたところ、
いい感じになっているので、最新版1.03を利用してみました。

パッケージ公開サイト

  MicroCore - An optimized Arduino hardware package for ATtiny13

以前はmicros()等の精度が悪く、タイミングをとるプロトコル実装に使えず難儀したのですが、
更新履歴をみると改善されたみたいです。

早速Arduino 1.8.5にインストールしてみました。
インストールは公開サイトの「How to install」に従ってインストールしました。
ボードマネージャのURL追加の方法で行いました。
下記の1行を環境設定追加のボードマネージャのURLに登録し、

  https://mcudude.github.io/MicroCore/package_MCUdude_MicroCore_index.json

URL

ボーマネージャにて、最新版をインストールします。

01


インストール後の書込み確認

とりあえず、Lチカですね。
ATtiny13Aへのスケッチの書込みには、書き込み装置が必要です。
安価な書込み装置としては、次のものが利用されることが多いようです。
  ・USBasp
  ・USBtinyISP
  ・Arduino as ISP(Arduino Unoを使う方法)

今回はUSBtinyISPを利用しました。
USBaspも安くて良いのですが、Windows 10対応の署名付きのドライバーがないようで、
開発とサポートが止まっている感があります。

一方、USBtinyISPはAdafruit製もあり、ドライバー等もそこから入手出来ます。
  Adafruit - USBtinyISP :AVR programmer & SPI interface
  https://learn.adafruit.com/usbtinyisp/


書込みのための結線はUSBtinyISPMicroCoreのHPのMinimal setupを参考にしてしました。
Lチカ用の出力ピンは書込みに利用していないPB3を利用しました。

Dscn7818

スケッチ

void setup() {
  pinMode(PB3, OUTPUT);
}

void loop() {
  digitalWrite(PB3, HIGH);
  delay(500);
  digitalWrite(PB3, LOW);
  delay(500);
}
 

ピン指定は、ピン名称のPB0PB5がそのまま利用出来るようです。

04_2

ボード指定と書き込み装置を設定し、スケッチをコンパイル&書き込み実施。
とりあえず、上記の設定では問題なくLチカ出来ました。
ヒューズビットの設定も同時に行われているようです。


128kHzでのLチカで問題発生

次にクロックを落としての利用。
低速書込みでは過去の経験から色々と問題が発生するのですが、やはり問題発生。

スケッチの書込みは出来るのですが、ヒューズビットの設定が出来ていないようです。
本来ならスケッチの書込みと同時に、ヒューズビットの設定もおこなわれるはずです。

公式サイトを見ると、書込み装置として「USBtinyISP(slow)」を設定しないといけないようです。

試して見ると、書込み時に下記のエラーが発生しました。
  avrdude: Can't find programmer id "{upload.protocol}"

05

これのエラーは、"upload.protocol"が未定義ということです。

MicroCore
の設定ファイル
  C:\Users\ユーザー\AppData\Local\Arduino15\packages\MicroCore\hardware\avr\1.0.3\

を調べてみるとプロトコルの設定をprogrammers.txtでは"protocol"のidを使っているのに
platform.txtの定義では"upload.protocol"のidを参照しています。
idがミスマッチのようです。

このエラーの対応として、
  platform.txt内の"upload.protocol"を"protocol"に変換
idのエラーは解決できました。


しかし、新たなエラーが発生

06

このエラーはSCKのクロックタイミングがマズイようです。
USBtinyISPのSCKのクロックパルス速度ではATtiny13が応答出来ないようです。

対応としてprogrammers.txt-B32をところを-B64 -Fに修正

usbtinyisp.name=USBtinyISP (slow)
usbtinyisp.protocol=usbtiny
usbtinyisp.program.tool=avrdude
#usbtinyisp.program.extra_params=-B32
usbtinyisp.program.extra_params=-B64 -F

ついでに、platform.txt-B32設定の箇所を-B64に修正
下記はヒューズビットの書込みに関連する設定です。

tools.avrdude.erase.params.verbose=-v
tools.avrdude.erase.params.quiet=-q -q
tools.avrdude.erase.pattern="{cmd.path}" "-C{config.path}" -v -p{build.mcu} -c{protocol} -B64 {program.extra_params} -e -Ulock:w:{bootloader.unlock_bits}:m -Uhfuse:w:{bootloader.high_fuses}:m -Ulfuse:w:{bootloader.low_fuses}:m

tools.avrdude.bootloader.params.verbose=-v
tools.avrdude.bootloader.params.quiet=-q -q
tools.avrdude.bootloader.pattern="{cmd.path}" "-C{config.path}" {bootloader.verbose} -p{build.mcu} -c{protocol} -B64 -Ulock:w:{bootloader.lock_bits}:m

    (備考)
     programmers.txt、platform.txtはサクラエディタで開くとUTF-7と誤判断され、
    上書き保存すると、Arduino IDEで正しく読めなくなりました。
    明示的にUTF-8指定して保存する必要があります。

修正後、Arduino IDEを再起動して動作確認すると、問題無く書き込めるようになりました。

07



まとめ

・Arduino 1.8.5でMicroCoreパッケージを使うと、ATtiny13Aのアプリ開発が出来る
・書き込み装置は、USBtinyISPがおススメ
・9.6MHzのクロックを利用する場合は、デフォルトのままで利用しても問題ない
・クロックを変更して利用する場合、現段階では定義ファイルの修正が必要っぽい

問題はあるようですが、Arduino 1.8.5でATtiny13Aのアプリが開発できるのは有難いですね。
開発者さんに感謝です。


2018年4月 3日 (火)

Arduinoのブートローダーの有無による起動時間の違いについて

SNSにて、「Arduinoを電源を入れてからスケッチが実行するまでの時間を早くしたい」という
旨の話題があり、ブートローダーが無ければ早くなるのではと思い、調べてみました。

まずArduino Unoで採用しているブートローダー optibootのソースを見てみると、
(参考:Optiboot/optiboot https://github.com/Optiboot/optiboot

ホストからのスケッチ書き込み要求のためのシリアル通信の監視を1.5秒間行った後、
書き込まれているスケッチの実行を行っていることが分かりました。
(この監視する待ち時間をタイムアウト時間と呼ぶことにします)

実際に、次のようなスケッチを実行してリセットからD2ピンにLOW・HIGHが出力されるまでの
時間を計測してみました。

void setup() {
  pinMode(2,OUTPUT);
  digitalWrite(2,0);
  digitalWrite(2,1);
}

void loop() {
}

ブートローダーありの場合
ロジックアナライザでリセットピンD2ピンの出力を調べてみると次のような感じになりました。

Boot
(クリックすると拡大表示します)

リセットボタンを押して、離した後からD2ピンがLOW・HIGHのエッジ間は1.579秒でした。
まあ、だいたい予想通りの時間でした。


ブートローダー無しの場合
次に、同じスケッチをプログラマを使って直接書き込み、測定していました。
スケッチをブートローダー経由ではなく、プログラマで直接書き込む場合は、
Arduino IDEのメニュー [スケッチ]  - [書込み装置を使って書き込む]にて書き込みます。
この際、事前にメニュー [ツール] - [書込み装置]にて利用する書込み装置を指定しておきます。

リセットボタンを押して、離した後からD2ピンがLOW・HIGHのエッジ間は67ミリ秒でした。
ブートローダーのタイムアウト時間ない分、早く起動できていることがわかりました。

Noboot
(クリックすると拡大表示します)


1.5秒が長いか、短いかは微妙ですがブートローダーを利用しない場合は、
リセット後のスケッチの起動がタイムアウト時間を待たない分、早く起動できます。

ただし、スケッチを直接書き込んだ場合、ブートローダーは無くなってしまうため、
Arduinoとしてのお手軽さが無くなってしまいます。
とりあえず、1.5秒の待ちは我慢して利用したほうが良いですね。

ちなみに、スケッチの直接書込みはこんな感じで行っています。

aitendoのATTiny ISPシールドきっと [K-TINY-SLD]を利用しました。

Writing

上が書込み装置(スケッチ Arduino ISPを書込み)のArduino Uno互換機です。
下が書き込み対象のArduino Uno互換機です。




より以前の記事一覧