SSブログ

ATtiny85のタイマーを使う [Arduino]

生のATtiny85とDigisparkとではタイマーの使い方が違っていました。

C:\Users\(user)\AppData\Local\Arduino15\packages\digistump\hardware\avr\1.6.7\cores\tiny\core_build_options.h より、
/*
  For various reasons, Timer 1 is a better choice for the millis timer on the
  '85 processor.
*/
#define TIMER_TO_USE_FOR_MILLIS                   1

Digisparkでは、やんごとなき理由でミリ秒のカウント用にタイマー1を使っているそうです。

タイマー0を使うか、タイマー1の割り込みを殺すかになります。

PWM使用(8bit高速PWM(TOP=0xff), 分周なし)の時のATtiny85用設定の覚え。
// PLLCSR - PLL制御/状態レジスタ (PLL Control and Status Register)
PLLCSR = B00000110;             // 0x06 (初期値)

// TIMSK - タイマ/カウンタ割り込み許可レジスタ (Timer/Counter Interrupt Mask Register)
TIMSK &= ~_BV(TOIE0);           // Timer0の割り込みを殺す場合
TIMSK &= ~_BV(TOIE1);           // Timer1の割り込みを殺す場合

// TCCR0A - タイマ/カウンタ0制御レジスタA (Timer/Counter0 Control Register A)
//         COM0A1 | COM0A0 | COM0B1 | COM0B0 | - | - | WGM01 | WGM00
TCCR0A = B10100011;             // OC0x比較一致でLow (表11-3参照)
                                // WGM02-0:8ビット高速PWM動作 (TOP値 $FF) (表11-5参照)

// TCCR0B - タイマ/カウンタ0制御レジスタB (Timer/Counter0 Control Register B)
//         FOC0A | FOC0B | - | - | WGM02 | CS02 | CS01 | CS00
TCCR0B = B00000001;             // CS02-1:前置分周なし (表11-6参照)

// TCCR1 - タイマ/カウンタ1制御レジスタ (Timer/Counter0 Control Register)
//         CTC1 | PWM1A | COM1A1 | COM1A0 | CS13 | CS12 | CS11 | CS10
TCCR1  = B01100001;             //  PWM動作A許可, OC1A比較一致でLow, ~OC1Aピン接続断 (表12-1参照), CK×16 (PCK)
                                //  PWM1A=1, COM1A1=1, COM1A0=0, CS13=0, CS12=0, CS11=0, CS10=1

// GTCCR - 一般タイマ/カウンタ制御レジスタ (General Timer/Counter Control Register)
//         TSM | PWM1B | COM1B1 | COM1B0 | FOC1B | FOC1A | PSR1 | PSR0
GTCCR  = B01100000;             //  PWM動作B許可, OC1B比較一致でLow, ~OC1Bピン接続断 (表12-1参照)
                                //  PWM1B=1, COM1B1=1, COM1B0=0
//OCR1C - タイマ/カウンタ1 比較Cレジスタ (Timer/Counter1 Output Compare Register C)
OCR1C  = 255;                   //  OCR1Cはタイマ/カウンタ1の最大値(比較一致での解除値)

// 高速PWM動作時に COM0x1 を 0 で標準ポート動作 (OC0x切断) (WGM02=0のとき)
// 高速PWM動作時に COM1x1 を 0 で標準ポート動作 (OC1x切断)

// TIFR - タイマ/カウンタ割り込み要求フラグ レジスタ (Timer/Counter Interrupt Flag Register)
while( !(TIFR & _BV(TOV0)) );   // タイマー0がオーバーフローするのを待つ
TIFR |= _BV(TOV0);              // Timer/Counter0 Overflow Flag をクリア 
                                //   (論理1を書くことによってもTOV0は解除(0)できる)
while( !(TIFR & _BV(TOV1)) );   // タイマー1がオーバーフローするのを待つ
TIFR |= _BV(TOV1);              // Timer/Counter1 Overflow Flag をクリア
                                //   (論理1を書くことによってもTOV1は解除(0)できる)

// タイマーを殺したときのdelay
void delayCentiseconds16M5Hz( uint16_t ds ) {       // 16.5MHz動作時 0.01sec単位のdelay
  while( ds-- )  _delay_loop_2( 41250 );            // 1MHzなら 2500
}

void delayCentiseconds16MHz( uint16_t ds ) {        // 16.5MHz動作時 0.01sec単位のdelay
  while( ds-- )  _delay_loop_2( 40000 );            // 1MHzなら 2500
}

(参考) wiring_private.h の cbi(), sbi()
#ifndef cbi
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#ifndef sbi
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif

digispark_wav.jpg
DigisparkでWAVファイル配列化したものを再生する。
// Digispark(Default 16.5MHz)で WAVファイル(8bit, 4kHz, monoral) を配列化したものを再生する。
#include <util/delay_basic.h>   // _delay_loop_2() を使うため
#include <avr/pgmspace.h>       // PROGMEM使用時のお約束
#include "hello4.h"             // 音声データ hello4[]  =「こんにちは」8bit,  4kHz,  monoral

void setup() {
  pinMode( 1, OUTPUT);          //  OC1A(PB1)
  TCCR1 = B01100001;            //  8ビット タイマ/カウンタ1用レジスタ (PWM動作A許可, OC1A比較一致でLow, ~OC1Aピン接続断)
  OCR1C = 255;                  //  タイマ/カウンタ1 比較Cレジスタ
  TIMSK &= ~_BV(TOIE1);         //  タイマ/カウンタ1溢れ割り込み許可(TOIE1) 取り消し
}

void loop() {
  playWav( hello4 );            // hello4[]を再生
  delayCentiseconds16M5Hz(100); // 100センチ秒 = 1秒まつ 
}

void playWav( const uint8_t d[] ) { 
  uint8_t  cyc;                                     // 次の1byte読み込むまでのサイクル数カウンタ(cyc)
  uint16_t i, len = pgm_read_word_near(&d[0x28]);   // データサイズ
  for( i = 0x2c; i < len;) {
    if(!(cyc = ++cyc % 16))  OCR1A =  pgm_read_byte_near(&d[ i++ ]);  // 8kHzの音声なら 除数を16→8へ
    while( !(TIFR & _BV(TOV1)) );                   // タイマのオーバーフロー待ち (256cycle @ 16.5MHz≒15.5usec毎)
    TIFR |= _BV(TOV1);                              // Timer/Counter1 Overflow Flag をクリア
  }
}

void delayCentiseconds16M5Hz( uint16_t ds ) {       // 16.5MHz動作時 0.01sec単位のdelay
  while( ds-- )  _delay_loop_2( 41250 );            // 1MHzなら 2500
}

Arduinoとスピーカだけで音声を出す [Arduino]

ATmega328はフラッシュメモリーが32kBあるので、非圧縮 8bit 8kHz モノラルならプログラムに使う領域を除くと約4秒弱再生できるはずです。
短いように思いますが、ちょっとした効果音や音声くらいなら意外といけます。
探せば同様のプログラムはありそうですが練習も兼ねてちょっと作ってみました。
ATmega328搭載で16MHz駆動のArduinoで、8bit, モノラル, 4/8/16kHzのみの対応です。
hello4816.jpg
再生音声の素材は以下から。

効果音ラボ - フリー、商用無料、報告不用の効果音素材をダウンロード:
http://soundeffect-lab.info/

から、

日常セリフ(元気な女の子)|声素材|効果音ラボ:
http://soundeffect-lab.info/sound/voice/line-girl1.html

の「こんにちは」をいただきました。ありがとうございました。
これを、フリーのサウンド編集ソフト「Audacity」をつかって変換します。

Audacity:
http://www.audacityteam.org/

無音の部分があるので、波形のあるところだけトリミングして、左下にある「プロジェクトのサンプリング周波数(Hz)」を16000 / 8000 / 4000 のいずれかにします。(4000の場合は手入力)
「ファイル」→「オーディオの書き出し...」→「ファイルの種類:その他の非圧縮ファイル」→「ヘッダ:WAV (Microsoft)」→「エンコーディング:Unsigned 8-bit PCM」として「保存」します。ファイルの拡張子がaiffになりますが、中身はWAVファイルになっているようです。
これを先日の「PROGMEM作蔵さん」で変換します。変数名だけ適当に変えておきます。
それをヘッダファイルにして、以下のスケッチと同じフォルダに入れておきます。

・メインのスケッチ
// WAVファイル(8bit, 4/8/16kHz, monoral) を配列化したものを再生する。
#include <avr/pgmspace.h>       // PROGMEM使用時のお約束だが、実はなぜだか書かなくても動く
#include "hello16.h"            // 音声データ hello16[] =「こんにちは」8bit, 16kHz,  monoral
#include "hello8.h"             // 音声データ hello8[]  =「こんにちは」8bit,  8kHz,  monoral
#include "hello4.h"             // 音声データ hello4[]  =「こんにちは」8bit,  4kHz,  monoral

const uint8_t C16kHz[] = { 3,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4,4,3,4,4,4,4,4,4,4,4,4 };   // サンプリングレート調整用配列 (16kHz)
const uint8_t C8kHz[]  = { 7,8,8,8,8,7,8,8,8,8,7,8,8,8,8,8 };                                   // サンプリングレート調整用配列 (8kHz)
const uint8_t C4kHz[]  = { 15,16,16,15,16,16,15,16 };                                           // サンプリングレート調整用配列 (4kHz)

void setup() {
  pinMode( 9, OUTPUT);          // OC1A(PB1)
  pinMode(10, OUTPUT);          // OC1B(PB2)   逆位相波で出力を上げるより、同出力の2ピンをまとめることで電流を上げる
    // 8bit高速PWM(TOP=0xff), 分周なし, コンペアマッチでLow (256clock毎に割り込み)
  TCCR1A = B10100001;           // コンペアマッチでLow(COM1A1=1, COM1A0=0, COM1B1=1, COM1B0=0), タイマモード設定(一部)(WGM11=0, WGM10=1) 
  TCCR1B = B00001001;           // タイマモード設定(一部)(WGM13=0, WGM12=1), クロック分周の設定(CS12=0, CS11=0, CS10=1)
}

void loop() {
  playWav( hello16 );
  delay(1000);
  playWav( hello8 );
  delay(1000);
  playWav( hello4 );
  delay(2000);
}

void playWav( const uint8_t d[] ) { 
  uint8_t  c = 0, cyc = 1;                      // 次の1byte読み込むまでのサイクル数カウンタ(cyc)とサイクル数調整用配列のポインタ(c)
  uint16_t i, rate, len;
  rate = pgm_read_word_near(&d[ 0x18 ]);        // サンプリングレート (本来は32bitのデータだが下位の16bitで十分)
  len  = pgm_read_word_near(&d[ 0x28 ]);        // データサイズ       (本来は32bitのデータだがATmega328では32kB以下なので十分)
  for( i=0; i<128;) {  while(!(TIFR1 & _BV(TOV1)));  TIFR1|=_BV(TOV1);  OCR1A=OCR1B=i++;  }     // ポップノイズ軽減
  for( i = 0x2c; i < len;) {
    if( --cyc == 0 ) {                          // サイクル(16usec)のカウントダウンが終わったら次のデータへ
      OCR1A = OCR1B = pgm_read_byte_near( &d[ i++ ] ); 
      cyc = ( rate == 16000 ) ? C16kHz[ c++ & B11111 ] : ( rate == 8000 ) ? C8kHz[ c++ & B1111 ] : C4kHz[ c++ & B111 ];
    }
    while( !(TIFR1 & _BV(TOV1)) );              // タイマーがオーバーフローするのを待つ (256cycle @ 16MHz = 16usec毎)
    TIFR1 |= _BV(TOV1);                         // Timer/Counter1 Overflow Flag をクリア (論理1を書くことによってもTOV1は解除(0)できる)
  }
  for( i=127; i;  ) {  while(!(TIFR1 & _BV(TOV1)));  TIFR1|=_BV(TOV1);  OCR1A=OCR1B=--i;  }     // ポップノイズ軽減
}

・音声のヘッダファイル(.h) (ファイルを変換後、変数bindata[]のところは適当に名前を付ける)
// file name : hello16k.aiff
// file size : 12043 bytes

const uint8_t hello16[] PROGMEM = {
  0x52,0x49,0x46,0x46,0x03,0x2f,0x00,0x00,0x57,0x41,0x56,0x45,0x66,0x6d,0x74,0x20,
  0x10,0x00,0x00,0x00,0x01,0x00,0x01,0x00,0x80,0x3e,0x00,0x00,0x80,0x3e,0x00,0x00,
  0x01,0x00,0x08,0x00,0x64,0x61,0x74,0x61,0xdf,0x2e,0x00,0x00,0x80,0x80,0x80,0x80,
 ~ 略 ~ 
  0x80,0x80,0x80,0x80,0x80,0x7f,0x7f,0x7f,0x7f,0x7f,0x80
};

16 / 8 / 4 kHzの聞き比べでは、16kHzがいいのはもちろんですが、8kHzでもやや高音のキレがないものの十分聞くに堪えられます。4kHzはもごもごした感じでやや厳しいです。
タグ:音声 wav

バイナリファイルをC言語のデータ配列に変換する [Arduino]

大きなデータを扱うにあたり、ファイル操作が可能なシステムならファイルの読み書きで対応が可能ですが、Arduino単体ではプログラム領域上にデータを持つしかありません。
数~数10kBのFlashメモリにデータを置き、そこから読み込むということになります。
テキストデータならプログラム上に載せるのもなんとかなりそうですが、バイナリデータの場合にはデータをどうやって持ってくるか考えてしまいます。
通常大きなデータというのはファイルに保存されているので、それをそのまま配列に変換するのが現実的と思います。
というわけで、HTML5 + JavaScript で変換スクリプトつくりました。
サンプルプログラムの寄せ集め+スタイルシートが雑ですが、Drag & Dropするだけなので使いやすいと思います。

pgmsakuzo.png
次のコードをテキストエディタでhtmlとして保存します。HTML5なのでUnicode(UTF-8)です。

<!DOCTYPE html>
<html>
<meta charset="utf-8">
<title>PROGMEM作蔵さん</title>
<style>
  html, body {  height: 90%;  background:#ffffff;  }
  h1         {  border:dotted 2px;  border-radius:20px;  padding:20px;  }
  h1, h3, p  {  color:#776666;  text-align:center;  margin:50px;  }
  code       {  color:#444444;  font-size:75%;  }
</style>
<script>
function DragOver( event ) {
    event.preventDefault();                 // ブラウザデフォルト処理(禁止マーク)を阻止
}
function Drop( event ) {
    event.preventDefault();                 // ブラウザデフォルト処理(ページ移動)を阻止
    var f = event.dataTransfer.files[0];    // イベントに格納されたファイルを取り出し
    var code = "<pre><code>"
    code += "// " + "file name : " + f.name + "\n";
    code += "// " + "file size : " + f.size + " bytes\n\n";
    code += "const uint8_t bindata[] PROGMEM = {";
    var reader = new FileReader();
    reader.onloadend = function(){
        var uint8array = new Uint8Array(reader.result);
        for( var i = 0; i < f.size; i++) {
            if( i != 0 )    code += ",";
            if( !(i % 16) ) code += "\n  ";
            code += ( uint8array[i] > 15 ? "0x" : "0x0" );
            code += ( uint8array[i].toString(16) );
        }
        code += "\n};\n\n</code></pre>";
        document.getElementById("main").innerHTML = code;
    };
    reader.readAsArrayBuffer( f );
}
</script>
<body ondragover="DragOver(event);" ondrop="Drop(event);" id="main" >
<h1>PROGMEM作蔵さん</h1>
<h3>バイナリファイルをC言語のデータ形式に変換</h3>
<p>ここにファイルをドロップ</p>
</body>
</html>

const uint8_t bindata[] PROGMEM = { ... };
のかわりに、
var bindata = [ ... ]; (最近はvarよりlet, const?)
にすれば、JavaScript にも応用が可能です。

Arduino 1.6時代のATtiny開発 - その5 [Arduino]

あと環境整備としては ATtiny13 です。

AVRマイコン ATTINY13A-PU: マイコン関連 秋月電子通商 電子部品 ネット通販:
http://akizukidenshi.com/catalog/g/gI-02911/

tx30-13a-pu.jpg

1個 ¥50(税込)ということで魅力的ではあります。
これを1.6.x環境で使うためにいろいろ探していたら見つけてしまいました。

Arduino Playground - ArduinoOnOtherAtmelChips:
http://playground.arduino.cc/Main/ArduinoOnOtherAtmelChips

other_chips.png
ATtiny13はもちろん、いろんなAtmelチップに対応できるようです。
Boards Manager URLがついているものであれば簡単にセットアップが可能っぽい。
便利な時代になりました。
タグ:ATtiny13 ATtiny

Arduino 1.6時代のATtiny開発 - その4 [Arduino]

今回はoptibootについて、、
自分では試していません。とりあえず情報だけ。
そもそもですが、ISPで書き込めるのに、あえてシリアル通信で書き込む必要があるかどうか。

<メリット>
・既存のUSBシリアル変換器が使える
・もともとシリアル通信をするハードをつくる予定なら共用できて便利
・ISPよりI/Oピン数を減らせる(VCC, GND, Resetを除くと 3→2本ですむ)

<デメリット>
・リセットのパーツが必要
・プログラムの書き込み容量が減る
・optibootはあるか?ボードマネージャで簡単インストールできるか?

前回のボードマネージャでの追加で、ATtiny 841, 1634, 828, 167, 87 にはoptibootがありましたが、ATtiny85は、、、
optibootありました。

GitHub - TCWORLD/ATTinyCore: ATTiny Core for Arduino 1.0+:
https://github.com/TCWORLD/ATTinyCore

さらに探すとボードマネージャもありました。

Bootload Your ATtiny85 - Hackster.io:
https://www.hackster.io/porrey/bootload-your-attiny85-625387

どうやら、Snapduino というプロジェクトのようです。

Snapduino - Hackster.io:
https://www.hackster.io/porrey/snapduino-e5f0a5

Snapduino_bb.png

Arduino 1.6時代のATtiny開発 - その3 [Arduino]

ATtiny85での開発には、「Adafruit Trinket」や「8pino」や「Digispark USB Development Board」 というボードを使う手もあります。
「Adafruit Trinket」≒「8pino」ですが、≠「Digispark USB Development Board」なので注意が必要です。

一番安いのは「Digispark USB Development Board」の互換製品で、microUSBバージョンもあり、Amazon.co.jpやAliExpressにて安価で手に入ります。
digispark.jpg

Digispark USB Development Board - Digistump:
http://digistump.com/products/1

ここの「Resources」というタブを選択し、
「Software:Download Page」をクリックし移動すると、
http://digistump.com/wiki/digispark/tutorials/connecting
ページがかわります。Bootloaderは書き込んであるはずなので、
Installation Instructions:
をよく読んで設定していきます。

さて、この手のアイテムで気になるのは、「ピン・ソケット・表裏問題」。
ピンをつけるのか、ソケットをつけるのか、表につけるのか、裏につけるのか。

さらに問題を複雑化させているのが、I/Oピンと電源関連ピンが直交していること。

いろいろ考えた末、裏にピンをつけて、ブレッドボードで運用することにしました。
ただ、ピンのつけ方とブレッドボードの選択に注意が必要です。

電源関連ピンは、5VとGNDのみにピンをはんだ付けして、VINは使用しないことにしました。
(ボード上のレギュレータは無駄になります。)
digispark_m.jpg
よくあるブレッドボードは電源ラインのピッチが半分ずれているので使えませんが、サンハヤトのブレッドボード(ニューブレッドボード SAD-101)ならOKでした。
他にはボード上の極性の記号が逆にはなりますが「Cake Board」もささりそう。

ただこの Digispark で ISR(TIMER1_OVF_vect) を使おうと思ったらすでに使われているらしく使えませんでした。USBasp で ATtiny85 マイコンに直書きしたときには使えたのに、、。
若干の違いはあるみたいです。


サンハヤト SAD-101 ニューブレッドボード

サンハヤト SAD-101 ニューブレッドボード

  • 出版社/メーカー: サンハヤト
  • メディア: Tools & Hardware


Cake Board ケーキボード LEGOフレンドリー 新ブレッドボード

Cake Board ケーキボード LEGOフレンドリー 新ブレッドボード

  • 出版社/メーカー: BreadBoardManiac
  • メディア: エレクトロニクス