2014年11月10日月曜日

Arduinoでエアコン制御 5-1 (送信編―タイマ割込み 失敗)

うまくいっていないが一旦まとめる。

準備

当然レジスタをいじる事になる為、Arduino UNOについてきたATmega328P-PUはバックアップ用にとっておく事にする。      参考 
本来はoptibootを書き込む方がソースの更新も新しく容量も小さくなろうが、Cをコンパイルできる環境にしなければならなさそうなので面倒な為一旦見送る。  参考  

あまりにも不案内なままいじるのが怖い為データシートのレジスタ一覧を目次にしてわからないなりに眺める。 有志による日本語版  本家

0x3E-SPH-上位ビットの数が違う等、記述の異なる部分に関わる時には気をつけよう。
(とりあえずスタックポインタの部分を触る事にはならないだろうが)
アドレスが降順なのは何故だろう…Arduinoリファレンス的に言えば"深遠な歴史的経緯に基づいて"いるのだろうか。
0x00~0x3EまでのLDとST命令を用いる際の0x20の加算というのも何なんだろう…同様な事なのか?
r0~31あるという汎用レジスタと汎用I/Oレジスタ0~2あたりはまったくわからないな…内部やI/Oの演算等の処理を行う為の空間…と解釈したけどなんで分かれてるの…
しかしこんなちっちゃい中に沢山の事が詰まっててすごい

前置き

ATmega328ではタイマが0~2までの3種類用意されている。
ATtiny85を使う前提でいる為16bitタイマは使用しない。
ちゃんと自分で検証していないがタイマ0の分周比によりdelay()やmicros()等へ影響があるらしい為、とりあえずはタイマ0の変更も避ける。
つまりタイマ2を使う事にする。

割込み処理をする関数はISR()又はSIGNAL()で定義するらしい。参考  
また先のサイトの通り"\hardware\tools\avr\avr\include\avr\iom328p.h"あたりに定義一覧がある。
(データシート内ではアドレスの一覧になっているが、それと_VECTOR()で変換される文字列との接続がよくわからなかった。アセンブラかなにかで書かれてる??)
まああまり深入りせず"iom328p.h"で定義された名前を使う事にする。

関連レジスタ

下の様なスケッチでレジスタの(Arduinoにとっての)初期値を確認する。
void setup() {
  Serial.begin(9600);
}
void loop() {
  Serial.print("0xB6 ASSR   : ");Serial.println(ASSR, BIN);
  Serial.print("0xB4 OCR2B  : ");Serial.println(OCR2B, BIN);
  Serial.print("0xB3 OCR2A  : ");Serial.println(OCR2A, BIN);
  Serial.print("0xB2 TCNT2  : ");Serial.println(TCNT2, BIN);
  Serial.print("0xB1 TCCR2B : ");Serial.println(TCCR2B, BIN);
  Serial.print("0xB0 TCCR2A : ");Serial.println(TCCR2A, BIN);
  Serial.print("0x70 TIMSK2 : ");Serial.println(TIMSK2, BIN);
  Serial.print("0x23 GTCCR  : ");Serial.println(GTCCR, BIN);
  Serial.print("0x17 TIFR2  : ");Serial.println(TIFR2, BIN);
  
  delay(10000);
}
データシート要約よりタイマ2の記述されているであろうページ番号を見ながら関連レジスタを拾う。
上ではそのアドレス順に表示させている。下はデータシートの説明順である為、並びが異なる。
アドレス 略称 初期値 Arduino
デフォ
概要
0xB0 TCCR2A 00000000 00000001 上位4ビットはOC2A(PB3 17pin)、OC2B(PD3 5pin)の出力に関する設定。0は切断。(出力されない)。
下位2ビットは動作種別。6モード――高速PWMモード等大別して4種類ある。
初期値はモード0:標準動作。
デフォルトはモード1:位相標準PWM(TOP値固定)。
0xB1 TCCR2B 00000000 00000100 比較一致の強制変更。動作種別の続き。分周比選択。
デフォルトは64分周。
0xB2 TCNT2 (11011011) タイマカウンタ。
カウンタ走行中の値が返っているのだと思われる。
0xB3 OCR2A 00000000 00000000 比較レジスタA。OC2A(PB3 17pin)用?
(データシートの通りならOCR2Bに関わらずTOP値を決めるのだろうか)
0xB4 OCR2B 00000000 00000000 比較レジスタB。OC2B(PD3 5pin)用?
0x70 TIMSK2 00000000 00000000 比較A、B、溢れ 割込み許可。
設定(1)すると各タイミングで割込みを発生させる。
0x17 TIFR2
(00000111) 比較A、B、溢れ フラグ。
各タイミングで設定(1)され、割込みを発生させた後解除(0)される。
任意に1を書くこともできる。
0xB6 ASSR 00000000 00000000 外部クロック、非同期動作許可、各フラグ。
よくわからないが今回は関係なさそう。
0x23 GTCCR 00000000 00000000 同期系、非同期系分周期リセット、同時動作。
これも関係なさそう。
"初期値"はデータシートに記載されている初期値を表す(工場出荷時のこと?)。
"Arduinoデフォ"は必ずしも"初期値"と一致しない。以下"デフォルト"と書く。

スケッチ

       //---一般Const---
const byte pin_irout = 5;
const byte pin_led = 13;  //送信確認用LED

       //---通信データ---
const byte snd_dt_idct[3] = {8, 4, 31};  //bit個数 reader(1),reader(0),repeat(0)
const byte snd_dt_cstm[5] = {B00100011, B11001011, B00100110, B00000001, B00000000};

const byte snd_dt_C[12] = {B00100000, B00011000, B00000101, B00110110,
                         B01000000, B00000000, B00000000, B00000000,
                         B00000000, B00000000, B00000000, B00000000};

volatile byte itpt_tmr = 0;
volatile byte snd_sw = 0;  //送信トグル

void setup() {
  pinMode(pin_irout, OUTPUT);
  pinMode(pin_led, OUTPUT);
}
//----------[2].

void loop() {
  digitalWrite(pin_led, HIGH);
  Ir_snd(snd_dt_C, sizeof(snd_dt_C));
  digitalWrite(pin_led, LOW);
  delay(3000);
}

//----------[3].各機能
      //---データ送信---
void Ir_snd(const byte dt[], byte s) {
  reg_set();

  for (byte i = 0; i < 2; i++){  //2回繰り返す
    itpt_tmr = 0;
    Ir_ns(snd_dt_idct[0], 1);                 //--Reader code
    Ir_ns(snd_dt_idct[1], 0);
    Ir_cs(snd_dt_cstm, sizeof(snd_dt_cstm));  //--Customer code
    Ir_cs(dt, s);                             //--Data code
    const byte chksm[1] = {Mk_chksm(snd_dt_cstm, sizeof(snd_dt_cstm), dt, s)};
    Ir_cs(chksm, 1);                          //--Check sum
    Ir_ns(1,1);                               //--Stop bit
    Ir_ns(snd_dt_idct[2], 0);                 //--Repeat code
  }
  reg_clr();
}
      //---回数送信---
void Ir_ns(const byte n, byte c) {  //回数 n, 1/0 c
  byte i = 0;
  while (i < n) {
    if (itpt_tmr == 1) {
      snd_sw = c;
      i += 1;
      itpt_tmr = 0;
    }
  }
  i = 0;
}
      //---変換送信---
void Ir_cs(const byte dt[], byte s) {
  byte lsb;
  byte i = 0;
  byte j = 0;

  while (i < s * 8) {  //ビットが1なら(1)(0)(0)(0)、0なら(1)(0)
    lsb = dt[i / 8] >> (i % 8) & 1;
    if (itpt_tmr == 1) {
      snd_sw = 1;
      i++;
      itpt_tmr = 0;
      while (j < (2 * lsb) + 1) {
        if (itpt_tmr == 1) {
          snd_sw = 0;
          j++;
          itpt_tmr = 0;
        }
      }
    }
    j = 0;
  }  //--while
}
       //---チェックサム生成---
byte Mk_chksm(const byte dt_c[], byte s_c, const byte dt[], byte s) {
  volatile byte chksm = 0;  //挙動がおかしいよくわからない

  for (int i; i < s_c; i++) { chksm += dt_c[i]; }
  for (int i; i < s; i++) { chksm += dt[i]; }
  return chksm;
}
       //---レジスタ設定---
void reg_set() {
  TCCR2A = B00000010;  //CTCモード
  TCCR2B = B00000011;  //CTCモード,32分周
  OCR2A = 214;  //TOP値(比較A)
  TIMSK2 = B00000010;  //割込許可(比較A)
}
       //---レジスタ設定を戻す--- 回りくどいがとりあえず毎回デフォルト値に戻す事にする
void reg_clr() {
  TCCR2A = B00000001;  //位相標準PWM(TOP値固定)
  TCCR2B = B00000100;  //64分周
  OCR2A = 0;  //TOP値
  TIMSK2 = 0;  //割込不許可
}

//----------[4].割込み

       //---タイマ2---
ISR(TIMER2_COMPA_vect) {
  bitWrite(PORTD, pin_irout, snd_sw);
  itpt_tmr = 1;
}
前置きが長いが結局は555の割込みをタイマ割込みに置き換えただけである。
またタイマ割込みを使う為のお膳立てを数行追加したのみである。

結果

画像上下はエアコンのリモコンからの信号(緑)を取った日が異なる。
Arduinoによる送信機からの信号(赤)は同一。
上下とも信号の前半――Stop bitまでであり、前半、後半を載せているものではない。
当然のことなのかもしれないが極めて正確に動作してくれる。
むしろエアコンのリモコンの方が、おそらく温度に依存して周期にぶれが出ている。
つまり少なくとも±0.4[ms]*2くらいの誤差は吸収してくれるのだろう。
これ以上ないくらい信号はぴったり一致しているが、赤外線LEDをエアコンの受光部につくくらい近づけても反応しなかった。

考察

今思いつく原因は1つしかない。
当初受信モジュールとエアコンのリモコンから得られる信号の周波数が倍違う事に気づいていた。
しかしそれは省電力化や電圧の降下を抑える目的なのではないかとスルーしていた。
もしかしたらエアコンの受信部分がその周波数に最適化されているのかもしれない。
ChaNさん秋月さんの資料にキャリア周波数について書いてある…。
きっとキャリア周波数に乗せてないからうまくいかないんだ…。

Arduinoでエアコン制御 5-2

0 件のコメント :

コメントを投稿