2014年12月16日火曜日

くるみラップライマー

概要

GR-KURUMIマイコンボードを使て、くるみラップタイマーを制作しました。


これは、マイコンカーレース等でマイコンカーのラップライムを計測するために使用します。赤外線距離センサーを使って、マイコンカーがラップタイマーの前を通過すると赤外線距離センサーの計測値が変化するので、それを利用してラップライムのオン・オフを制御します。くるみラップタイマーの前をマイコンカーが通過した時点からラップタイムがスタートし、またマイコンカーが返って来てセンサーの前を横切りるとタイマーがストップします。ラップライムクリアスイッチでリセットでき、カウントスタートの状態に戻ります。


赤外線距離センサーには、写真のような箱のような反射壁が必要で40cmほど離して置いてください。

プログラムは外部割込みやタイマー割込みは使用せず、millis( )タイマーでLEDをダイナミック点灯しています。赤外線距離センサーは,loop() 内で計測していますが、毎回1ms以内に計測しています。ソフトウェアの変更なしで、Arduino-ProMini等への移植も可能で確認済みです。


部品

赤外線センサー: SHARP GP2Y0A21YK
8セグメントLEDモジュール: WSN233 (www.wsnak.com)
プッシュスイッチ: A0ピン(アナログ値で読む)

ソースコード

// Applicable Segment LED Module: WSN233 (www.wsnak.com)
//  LED Pin <--> KURUMI Pin
#define COM1 11
#define COM2 12
#define COM3 13
#define COM4 5
#define SEG_A 2
#define SEG_B 3
#define SEG_C 4
#define SEG_D 6
#define SEG_E 7
#define SEG_F 8
#define SEG_G 9
#define DOT A3

// Applicable Infra-red Distance Sensor: Sharp GP2Y0A21YK
#define IRD A1      // Infeared Distance Sensor Port

// Super Sonic Distance Sensor
#define Echo 10
#define Trg A2



// Applicable Tact-switch: SFE-COM-00097 (Switch-Science)
#define PUSH_SW A0  // Push Switch Port.  Read the switch state by analog. Push < 10 
#define PUSH_SW1 A1

#define CALIB 0   // Calibration Mode
#define WAIT_CAR 1
#define GO 2      // Car cross the sensor and wrap-timer started
#define FINISH 3  // Car cross the sensor and wrap-timer stopped
#define AGAIN  4  // Switch to GO mode


unsigned long Blinking = 0;
unsigned long Interval = 0;
int Seg1 = 17, Seg2 = 17, Seg3=17, Seg4 = 17; // Display Value for Segment LEDs
int SegPoint = 1;
unsigned long Sec1 = 0;
int Mode;                               // Flow Control with SW and IRD
int IrdLevel = 0x100;
int CarIsHere = 0;                // 1 -- Car is Here


void setup( ) {    
  Serial.begin(9600);  
  Init_Led_Seg();
  Mode = CALIB;        // Set Caribration mode 
}

void loop() {      // Do not use delay()  in loop()
  LedDrive();        // Seg-LED driver. 
  RunMode();
}

void RunMode(){
  switch (Mode) {
    case CALIB:
      CalibrationMode();
      break;
    case WAIT_CAR:
      WaitCar();
      break;
    case GO:
      GoMode();
      break;
    case FINISH:
      FinishMode();
      break;
    default:
      break;
  }
}

void CalibrationMode(){
  Seg1 = 17;
  Seg2 = 17;
  Seg3 = 17;
  Seg4 = 17;
  if (analogRead(PUSH_SW) > 10) {
     if((millis() - Interval > 100) ) {
          Interval = millis();
          Serial.println(analogRead(IRD));
     }
     Mode = CALIB;
  } else {
   Seg1 = 0;
   Seg2 = 0;
   Seg3 = 0;
   Seg4 = 0;
   Mode = WAIT_CAR;
  } 
}
void WaitCar(){
  if (analogRead(IRD) > 200) {
    CarIsHere = 1;
  }
  if (analogRead(IRD) < 200 && CarIsHere == 1){
    CarIsHere = 0;
    Mode = GO;
  }
}
  
void GoMode() {
    if (analogRead(IRD)> 200){
      Mode = FINISH;
    } else {
      if((millis() - Interval > 10) ) {
          Interval = millis();
          if (++Seg1 > 9){
            Seg1 = 0;
            Seg2++;
          }
          if (Seg2 > 9){
            Seg2 = 0;
            Seg3++;
          }
          if(Seg3 > 9) {
            Seg3 = 0;
            Seg4++;
          }
          if (Seg4 > 6){
            Seg4 = 0;
          }
        }
    }
}

void FinishMode(){
  if (analogRead(PUSH_SW) < 10) {
    Mode = CALIB;  
  }
}
      
void LedDrive(){
  if(micros() - Blinking > 1000) {
    Blinking = micros();
    switch (SegPoint++){
      case 1:
        Led(1, Seg1);
        break;
      case 2:
        Led(2, Seg2);
        break;
      case 3:
        Led(3, Seg3);
        break;
      case 4:
        Led(4, Seg4);
        break;
      case 5:
        Led(4, Seg4);
        break;
      default:
        SegPoint = 1;
    }
  }
}

void Led(int com, int n){        // 'n' must be 0x0 to 0x10 (OFF)                            
  digitalWrite(COM1, LOW);          // 'com' must be 1 to 3, and other number is OFF  
  digitalWrite(COM2, LOW); 
  digitalWrite(COM3, LOW); 
  digitalWrite(COM3, LOW); 
  switch(n){
    case 0:
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, LOW);    
      digitalWrite(DOT, LOW);      
      break;
     case 1:
      digitalWrite(SEG_A, LOW);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, LOW);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, LOW);    
      digitalWrite(SEG_G, LOW);    
      digitalWrite(DOT, LOW);      
      break;
    case 2:
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, LOW);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, LOW);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 3:
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, LOW);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 4:
      digitalWrite(SEG_A, LOW);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, LOW);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 5:
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 6:
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 7:
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, LOW);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, LOW);    
      digitalWrite(SEG_G, LOW);    
      digitalWrite(DOT, LOW);      
      break;
    case 8:
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 9:
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 10:  // Charracter A
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, LOW);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, LOW);    
      digitalWrite(DOT, LOW);      
      break;
    case 11:    // Character B
      digitalWrite(SEG_A, LOW);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 12:    // Character C
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, LOW);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, LOW);    
      digitalWrite(DOT, LOW);      
      break;
    case 13:    // Character d
      digitalWrite(SEG_A, LOW);
      digitalWrite(SEG_B, HIGH);    
      digitalWrite(SEG_C, HIGH);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, LOW);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 14:    // Character E
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, LOW);    
      digitalWrite(SEG_D, HIGH);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 15:     // Character F
      digitalWrite(SEG_A, HIGH);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, LOW);    
      digitalWrite(SEG_D, LOW);
      digitalWrite(SEG_E, HIGH);    
      digitalWrite(SEG_F, HIGH);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;
    case 16:    // Chareacter .
      digitalWrite(SEG_A, LOW);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, LOW);    
      digitalWrite(SEG_D, LOW);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, LOW);    
      digitalWrite(SEG_G, LOW);    
      digitalWrite(DOT, HIGH);      
      break;
    case 17:    // '-' Minus
      digitalWrite(SEG_A, LOW);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, LOW);    
      digitalWrite(SEG_D, LOW);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, LOW);    
      digitalWrite(SEG_G, HIGH);    
      digitalWrite(DOT, LOW);      
      break;    
    default:    // OFF
      digitalWrite(SEG_A, LOW);
      digitalWrite(SEG_B, LOW);    
      digitalWrite(SEG_C, LOW);    
      digitalWrite(SEG_D, LOW);
      digitalWrite(SEG_E, LOW);    
      digitalWrite(SEG_F, LOW);    
      digitalWrite(SEG_G, LOW);    
      digitalWrite(DOT, LOW);      
      break;
  }
  switch(com){ 
    case 1:
      digitalWrite(COM1, HIGH); 
      digitalWrite(COM2, LOW); 
      digitalWrite(COM3, LOW); 
      digitalWrite(COM4, LOW);
      break; 
    case 2:
      digitalWrite(COM1, LOW); 
      digitalWrite(COM2, HIGH); 
      digitalWrite(COM3, LOW); 
      digitalWrite(COM4, LOW);
      break; 
    case 3:
      digitalWrite(COM1, LOW); 
      digitalWrite(COM2, LOW); 
      digitalWrite(COM3, HIGH); 
      digitalWrite(COM4, LOW);
      break; 
    case 4:
      digitalWrite(COM1, LOW); 
      digitalWrite(COM2, LOW); 
      digitalWrite(COM3, LOW); 
      digitalWrite(COM4, HIGH);
      break; 
    default:
      digitalWrite(COM1, LOW); 
      digitalWrite(COM2, LOW); 
      digitalWrite(COM3, LOW); 
      digitalWrite(COM4, LOW);
      break; 
    }
}
  
void  Init_Led_Seg(){
  pinMode(COM1, OUTPUT);  
  digitalWrite(COM1, LOW); 
  pinMode(COM2, OUTPUT);  
  digitalWrite(COM2, LOW); 
  pinMode(COM3, OUTPUT);  
  digitalWrite(COM3, LOW); 
  pinMode(COM3, OUTPUT);  
  digitalWrite(COM3, LOW); 
   
  pinMode(SEG_A, OUTPUT);  
  digitalWrite(SEG_A, LOW);   
  pinMode(SEG_B, OUTPUT); 
  digitalWrite(SEG_B, LOW);     
  pinMode(SEG_C, OUTPUT);  
  digitalWrite(SEG_C, LOW);       
  pinMode(SEG_D, OUTPUT); 
  digitalWrite(SEG_D, LOW);        
  pinMode(SEG_E, OUTPUT);  
  digitalWrite(SEG_E, LOW);     
  pinMode(SEG_F, OUTPUT); 
  digitalWrite(SEG_F, LOW);     
  pinMode(SEG_G, OUTPUT);  
  digitalWrite(SEG_G, LOW);       
  pinMode(DOT, OUTPUT); 
  digitalWrite(DOT, LOW);      
 }    




2014年10月23日木曜日

赤外線リモコンを受ける その3

それは、ここから赤外線リモコンを受信するアルゴリズムを説明します。

■ 使用する機能:
  • 外部割込み: attachInterrupt(1, Check_IR, CHANGE)
    • 1: GR-KURUMIの3番ピンです。 リモコン受信パルスの入力
    • CheckIR: 割込みが発生すると、void CheckIR(void) がコールされます。
    • CHANGE: 割込みは、3番ピンの「立ち上がり」と「立ち下がり」の両方のエッジで割込みが発生します。
  • 3番ピンのレベルセンス: digitalRead(IR_Pin)  
    • IR_Pin: #define IR_Pin 3 ・・・ 3番ピンをアサインしています。
    • LOW: 3番ピンの割込みが「立ち下り」エッジで起こったことになります。
    • HIGH: 3番ピンの割込みが「立ち上がり」エッジで起こったことになります。
  • フリーランライマー micros()
    • マイコンのリセット直後から、このタイマーが起動します。マイクロ秒単位でカウントします。
    • 外部割込みが発生し、次の割込みが発生しるまでの時間を計測します。これにより、赤外線リモコンのHIGHパルスとLOWパスルの幅を計測します。
■ 重要な変数
  • unsigned long IR_Low_Time ; // 赤外線パルスのLOWパルスの長さ(マイクロ秒)
  • unsigned long IR_High_Time;  // 赤外線パルスのHIGHパルスの長さ(マイクロ秒)
  • unsigned long IR_Data[Max_IR_Bits]; // 赤外線パルスのビット長(時間)を記録します。
  • int IR_State; // 赤外線パルスのどの位置のビットかを保持しています。
    • 0: ヘッダービットです。
    • 1 から 32: データビットです。
  • int IR_Active; // 赤外線パルスが有効で、そのストリーム中にいるかをチェックします。
    • 0: 赤外線パルスはない。
    • 1: 赤外線パルスのストリームの中にいる状態です。
■ 赤外線パルスの捕捉方法
  1. 赤外線パルスが来ると割込みがかかり、Check_ID()関数がコールされます。
  2. まず、digitalRead(IR_Pin) で、割込みが赤外線パスルの「立下り」なのか「立上り」なのかをチェックします。
  3. 赤外線パルスが「立上り」の場合: 直前のLOWパルスの時間「micros() - IR_Low_Time」を計測します。
    • ②の位置: この時間が「HEADER_Low_min」より長い場合、HEADERビットが来たと判定し「IR_Active = 1」を有効にっします。
    • ②の位置以外: データビットと見なせます。しかし、「Data_Low_min」よりパルスが短い場合は、ノイズによるLOWパルスですのでエラーと判断し「IR_Active = 0」とします。
    • 最後の立上り: 割込みの外、つまり Loop() 関数の中で、
      「if (IR_Active == 1 && micros() - IR_High_Time > IR_Tail_Max)  」を実行し赤外線パルスの終了を判定し、「IR_Active = 0」を実行します。
  4. 赤外線パルスが「立下り」の場合: ストリーム中にいるか「IR_Active == 1」 をチェックします。
    • もし、ストリーム中にある場合は、「IR_Data[IR_State] = micros() -  IR_Low_Time;」を実行しパルス幅(時間)を保存して、「IR_State++」を実行しビット位置を先に進めます。

■ 全体プログラム:
/*

Infra-red IR-remote capture program version 1.0
 by Kaz Kinoshita, Insutitute of Daiichi Technologies
 date of 2014-10-23

*/

#include <RLduino78.h>
#define LED_R 22    // LED Red is assigned to Pin 22
#define LED_G 23    // LED Green is assigned to Pin 23
#define LED_B 24    // LED Blue is assigned to Pin 24
#define LED_OFF 1   // LED turns off when LED port is set to LOW.
#define LED_ON 0    // LED turn on whrn the port is set to HIGH

#define IR_Pin 3  
// Pin 3 is set to capture infra-red remote control signal.
// Set the interrupt polarity of the pin to low-active and high-active edge senses.

#define HEADER_Low_min 8000
// The low state of IR-HEADER is about 9000 usec (micro-second).
// I specify it must be longer than 8000 usec.

#define Data_Low_min 200
// Low state of IR data bit about 500 usec..
// Noise pulses are usually 10 to 50 mic

#define IR_Tail_Max 5000
// defines the end of IR data stream

#define Max_IR_Bits 35
// Reserve enough length of bits. This IR remote has 32 bits

unsigned long IR_Low_Time = 0;    // holds IR bursting time
unsigned long IR_High_Time = 0;   // holds IR high idling time
unsigned long IR_Data[Max_IR_Bits] = {      
  0};                    // holds the bit length of each data bit by micro-sec.
unsigned long IR_Bits;   // 32 bits of IR data
byte IR_ID = 0;          // the first 8-bit of IR data.
byte IR_ID_N = 0;        // the second 8-bit of IR data, IR_ID_N = !IR_ID
byte IR_CMD = 0;         // the 3rd 8-bit of IR data
byte IR_CMD_N = 0;       // the 4th 8-bit of IR data, IR_CMD_N = !IR_CMD
int IR_Active = 0;       // when 1, the capturing IR data steam is valid
int IR_State = 0;              
// defines the bit position of IR bit stream.
// valid IR bit steam must be 0 to 32.

void Check_IR(){      // Infra-red remote signal detection interrupt
  if (digitalRead(IR_Pin) == LOW) {
    if (IR_Active == 1) {
      digitalWrite(LED_R, LED_ON);
      IR_Data[IR_State] = micros() -  IR_Low_Time;
      IR_State++;  
      if (IR_State > Max_IR_Bits - 1) IR_State = Max_IR_Bits - 1;
    }
    IR_Low_Time = micros();
    return;
  }
  else {
    digitalWrite(LED_R, LED_OFF);
    IR_High_Time = micros();
    if(micros() - IR_Low_Time < Data_Low_min){
      IR_Active = 0;
      return;
    }
    if (micros() - IR_Low_Time > HEADER_Low_min) {    // Detect the low state of HEADER
      IR_Active = 1;
      IR_State = 0;
    }
  }
}

void setup() {
  Serial.begin(9600);
  pinMode(LED_R, OUTPUT);
  pinMode(LED_G, OUTPUT);
  pinMode(LED_B, OUTPUT);
  pinMode(IR_Pin, INPUT);
  attachInterrupt(1, Check_IR, CHANGE);    // 1 sets Pin 3 for interrupt with low/high-active edges

  digitalWrite(LED_R, LED_OFF);
  digitalWrite(LED_G, LED_OFF);
  digitalWrite(LED_B, LED_OFF);
  Serial.println("Ready for capturing IR stream!");
}

void loop() {

  // Find the tail of IR bit stream
  if (IR_Active == 1 && micros() - IR_High_Time > IR_Tail_Max) {
    Serial.println("-- Pulse length of each IR-bit (micro seconds) --");
    Serial.print("bit 0 (HEADER): ");
    Serial.println(IR_Data[0]);
    IR_ID = 0;
    IR_ID_N = 0;
    IR_CMD = 0;
    IR_CMD_N = 0;  
    for (int i = 1; i <= 32; i++) {
      if (i <= 8) {
        IR_ID = IR_ID << 1;
        if ( IR_Data[i] > 1500 ) IR_ID = IR_ID | 0x01;
        else IR_ID = IR_ID & 0xFE;
      }
      if (i > 8 && i <= 16) {
        IR_ID_N = IR_ID_N << 1;
        if ( IR_Data[i] > 1500 ) IR_ID_N = IR_ID_N | 0x01;
        else IR_ID_N = IR_ID_N & 0xFE;
      }
      if (i > 16 && i <= 24) {
        IR_CMD = IR_CMD << 1;
        if ( IR_Data[i] > 1500 ) IR_CMD = IR_CMD | 0x01;
        else IR_CMD = IR_CMD & 0xFE;
      }
      if (i > 24 && i <= 32) {
        IR_CMD_N = IR_CMD_N << 1;
        if ( IR_Data[i] > 1500 ) IR_CMD_N = IR_CMD_N | 0x01;
        else IR_CMD_N = IR_CMD_N & 0xFE;
      }
      Serial.print("bit ");
      Serial.print(i);
      Serial.print(" = ");
      Serial.println(IR_Data[i]);
      IR_Data[i] = 0;              // initialize the capture data
    }
    Serial.println("-- ID Value --");
    Serial.print("ID = ");
    Serial.println(IR_ID, HEX);
    Serial.print("ID_N = ");
    Serial.println(IR_ID_N, HEX);
    if (IR_ID == byte(~IR_ID_N) ) Serial.println("No ID Error");
    else Serial.println("ID Error");
    Serial.println("--CMD Value --");
    Serial.print("CMD = ");
    Serial.println(IR_CMD, HEX);
    Serial.print("CMD_N = ");
    Serial.println(IR_CMD_N, HEX);
    if (IR_CMD == byte(~IR_CMD_N) ) Serial.println("No CMD Error");
    else Serial.println("CMD Error");

    IR_Active = 0;    // Close IR-bit decoding session and wait for the next stream
    IR_State = 0;
  }

}

■ 実行結果

2014年10月17日金曜日

赤外線リモコンを受ける その2

赤外線リモコンの送受信双方の信号タイミングを1つの図にしてみました。

それでは、この赤外線リモコン信号(受信側)のデータフォーマットはどのようになっているのでしょう?

この赤外線リモコンのデータフォーマットはNECプロトコルに準拠しています。特徴として
  • 8ビットID、8ビットコマンド
  • IDビット(8 bits) とコマンドビット(8 bits)は、それぞれ0と1を反転したパーターンで再送されます。これにより受信データに誤りがあるかの判定が可能になり、またデータのパターンが変化しても送信に要する時間が一定になります。
  • 1ビットの1/0判定にはパルスポジションモジュレーション方式が使われています。
  • ビットの「1」は、Lパルス(0.56ms)と、その後のHパスル(1.69ms)で構成されています。
  • ビットの「0」は、Lパルス(0.56ms)と、Hパスル(0.56ms)で構成されています。
  • 搬送波(送信時のバースト信号)は38KHzの矩形波です。
  • 8ビットデータはLSBファーストで転送されます。つまり、ビット0(LSB: Least Significant Bit)から先に送られ、ビット7(MSB: Most Significant Bit)で完結します。
これで、赤外線リモコンのデータフォーマットと受信タイミングパターンが判明しましたので、次回からはGR-KURUMIでどのようにこの信号をキャプチャーするか説明します。

赤外線リモコンを受ける その1

赤外線リモコンは、TVやエアコンをはじめ多くの家電製品で使われています。ここでは赤外線リモコンがどのような仕組みで動作しているかを調べ、GR-KURUMIで赤外線リモコンの信号を受信するプログラムを作ってみましょう。

簡単な赤外線リモコンを入手しましたので、赤外線リモコンのLED部分の信号をオシロスコープで見てみましょう。


さて、LEDの発光パターンをオシロスコープで取って見ました。

0V付近で波うっている信号はノイズ信号です。1V付近で、約70ミリ秒の区間にLEDが点滅を繰り返しているのがリモコンの送信データです。

これをGR-SAKURAに取付けた赤外線受信モジュール(VISHAY 1838T)の出力信号をオシロスコープで取って見ました。


赤外線受信モジュール(VISHAY 1838T)は、0V付近のノイズやLED発光時の搬送波(LEDのバースト状のオン・オフ発光)をきれいに除去して、0Vと5Vのオン・オフパターンにしてGR-KURUMIの入力ピン(3
番ピン)に送り込んでいるのが分るでしょうか?

GR-KURUMIの3番ピンは、外部割込み機能があります。3番ピンの入力信号がHIGH → LOW に変化するタイミングで外部割込みが働くようにするようにします。



これは、下記左端のフォトダイオードで受信した赤外線信号をAGC (Automatic Gain Control)で増幅してBand Passフィルターでノイズを除去し、搬送波をDemodulatorで0V・5Vのオン・オフパターンにしてOUTピンより出力します。



2014年6月21日土曜日

KURUMIボードのリセット回路

マイコンの基本回路の最後はリセット回路です。KURUMIボードのリセット回路は下記のようになっています。KURUMIマイコンはボード上の3つの端子からリセットをかけることができます。
KURUMIボードのリセット回路とボード上の位置

  1. リセットボタン(くるみアイコンのすぐ下の白いスイッチ)
  2. FTDIモジュールから来るDTR信号
  3. KURUMIボード右上部のRST端子
通常はRESET端子(KURUMIマイコンの40番ピン)はハイ・レベル(Vcc端子と同電圧)に保持しておきます。この端子をロウ・レベル(GNDと同電位)に下げるとRESETが有効になります。KURUMIマイコンにRESETがかかると、電源起動時とどうように書込まれたプログラムを最初から実行します。
KURUMIボードのリセット回路はスイッチと抵抗とうきわめて簡単な回路ですが、KURUMIマイコンの内部では、供給される電源電圧を監視して、KURUMIマイコンに供給されている電圧に応じて、いろいろな対応をとれるようになっています。

供給電圧が降下し動作が不安定になることをブラウン・アウトと呼ぶことがあります。KURUMIの血圧が下がって貧血を起こすような状態ですが、これを検知する回路を内蔵しています。
RL78/G13ハードウェアマニュアル(832ページ)
電池で動作するシステムは、ちょっとモータを回したりすると、すぐに電池が電源供給不足になってで電圧が下がってしまうので意外とシステムを正常動作させるためには苦労します。マイコンに電圧検出回路が内蔵されているのは大変重宝します。

もう1つの電源

KURUMIボードには、もう1つ電源を供給する方法があります。それは、KURUMIマイコンにプログラムを書込む時に使うFTDIモジュールです。これは、パソコンのUSBインターフェースとKURUMIマイコンのシリアルI/O(UART)のブリッジ(橋渡し)します。一般的にはUSBシリアル変換モジュール (USB-To-Serial Converter Module) とも呼びます。パソコンのUSB 2.0規格では5V・500mAを接続先へ給電することができます。下記のFTDI Basicモジュールは、これを3.3Vに降電(デフォルト仕様)してKURUMIボードに供給しています。もし、KURUMIボードを5V動作で使いたい場合は、裏の3つのパッド短絡設定を変更する必要があるようです。回路図をここにリンクします。
KURUMIボードのFTDIコネクタ回路

KURUMIの電源回路をいじめてみた

マンガン電池以外の電池やその本数を変えて、KURUMIボードの電源回路(昇圧回路)がどのように振る舞うか確認してみました。KURUMIにはちょっとストレスかもしれませんが、ガマンしてもらましょう。

① ニッケル水素電池を1本接続した場合
eneloop (充電式ニッケル水素電池)は定格1.2Vと記載してあるが、満充電直後の場合1.35Vの電圧を供給している。これをKURUMIのRAW端子に接続し、KURMUMIマイコンのVcc電圧を測定したところ、しっかり3.3Vが供給されている。KURUMIの昇圧回路はなかなかのすぐれモンですね。

② ニッケル水素電池を3本接続した場合
それでは、eneloopを3本接続するとどうなるでしょう。 eneloopを3本直列につないだ場合、RAW=4.04Vを供給します。KURUMIマイコンのVccは4.03Vになっています。KURUMIボードの電源回路(昇圧回路)は昇圧するための回路ですから、4.04V→4.03Vと努力の様子はうかがえますか、出力したい電圧(3.3V)よりも高い電圧が入力されるとうまく対応できないことがわかります。あまり長くこのような状態を維持すると電源回路が壊れる可能性があるので、この辺でやめることにします。

KURUMIの電源回路

どんな電子回路でも、必要な電力が供給されないとそのシステムは動作できませんよね。さて、KURUMIボードの電源回路はどうなっているのでしょう。KURUMIボードに搭載しているマイコンRL78G13の動作電圧はVDD = 1.6 - 5.5V とかなり低電圧から動作します。 この推奨動作電圧より低い電圧の場合、マイコンやシステムは正しく動作することができません。多くの場合、発振回路が動作しないとか、RAMの内容変ってしまうとか、リセットが機能せずプログラムの暴走(プログラムの意図した順序で実行されない)といった不具合が発生します。

さて下記の図は、KURUMIボードの電源回路です。
KURUMIボード上の電源回路・電池端子・切替スイッチ
電池を使用する場合は、ディップスイッチJP8をON側にセットします。そして電池のプラス端子をRAWピンに接続します。電池のマイナス端子はGNDピンに接続します。黄色い破線部分がKURUMIボードの電源回路です。下記がその回路図です。
KURUMIボードの電源回路(昇圧回路)
この電源回路は、RAW端子に電池を接続すると昇圧されVccには電池が供給する電圧(VRAW)
よりも高い電圧(VCC)が供給されます。実際に電池を接続して、どんな電圧が供給されているのか測定してみましょう。

① まずマンガン電池の電圧を測定しました。
マンガン電池の電圧を測定した様子
② マンガン電池(1本)をKURUMIボードに接続して、KURUMIマイコンに供給される電圧を測定しました。
電池電圧は昇圧されVccには3.3Vが供給されている
KURUMIボードは何も修正していませんので、工場出荷状態での電源回路はVccに3.3Vが供給されるように設定されているようです。

Vcc =5Vを供給するには、電源回路のP2端子(未接続のジャンパー)をハンダで短絡する必要があるようです。