Drogon Remote Control(DRC)でArduinoを操作する(ATtiny編)


前回、シリアル通信を使ってArduinoからProMiniを操作する方法を紹介しまし た。
そこで、今回はシリアル通信を使ってArduinoからATtiny84を操作する方法を紹介します。

RaspberryとATtiny84との結線は以下の様になります。
ProMiniと同様に、ATtinyへの給電は5Vとしましたので、ATtiny84のTXはレベルシフト(5V→3.3V)して RaspberryのRXに接続し ています。(黄色の線)
ATtinyへの給電を3.3Vにすれば、レベルシフトは不要になります。




【ATtiny84側】

ATtiny84側のスケッチは以下の通りです。あまり使い道はありませんが、ATtiny85でも動きました。
ATtinyへのスケッチの書き込みはこちらで 紹介しています。
ATtinyでSoftwareSerialを使う場合は4800Bpsが限界のようです。

/*
 * drcATtiny:
 *  The Drogon Remote Control for ATtiny84/85.
 *  Allow another device talking to us over the serial port to control the IO pins.
 *
 * Full details at:
 *  http://projects.drogon.net/drogon-remote-control/drc-protocol-arduino/
 *
 * Commands:
 *  @: 0x40 Ping  Send back #: 0x23
 *  0: 0x30 0xNN  Set Pin NN OFF
 *  1: 0x31 0xNN  Set Pin NN ON
 *  i: 0x69 0xNN  Set Pin NN as Input
 *  o: 0x6F 0xNN  Set Pin NN as Output
 *  p: 0x70 0xNN  Set Pin NN as PWM
 *  v: 0x76 0xNN  Set PWM value on Pin NN
 *  r: 0x72 0xNN  Read back digital Pin NN  Send back 0: 0x30 or 1: 0x31
 *  a: 0x61 0xNN  Read back analogue pin NN Send back binary 2 bytes, Hi first.
 * 
 */

#include <SoftwareSerial.h>

// Serial commands

#define CMD_PING        '@'
#define CMD_PIN_0       '0'
#define CMD_PIN_1       '1'
#define CMD_PIN_I       'i'
#define CMD_PIN_O       'o'
#define CMD_RD_PIN      'r'
#define CMD_RA_PIN      'a'
#define CMD_PWM_PIN     'p'
#define CMD_PWM_VAL_PIN 'v'

#define ANS_OK          1
#define ANS_NG          2

#if defined(__AVR_ATtiny84__)
  #define rxPin           10 // PB0
  #define txPin            9 // PB1
  #define MIN_APIN         0
  #define MAX_APIN         7
  #define MIN_DPIN         0
  #define MAX_DPIN         8
  #define MAX_PWMPIN       4
#elif defined(__AVR_ATtiny85__)
  #define rxPin            0 // PB0
  #define txPin            1 // PB1
  #define MIN_APIN         1
  #define MAX_APIN         3
  #define MIN_DPIN         2
  #define MAX_DPIN         4
  #define MAX_PWMPIN       2
#endif

int dmy = 0; //これを宣言しないとエラーになる
// 理由は http://memo.tank.jp/archives/1437
#if defined(__AVR_ATtiny84__)
int PWM_PIN[] = {5, 6, 7, 8};
#elif defined(__AVR_ATtiny85__)
int PWM_PIN[] = {3, 4};
#endif

SoftwareSerial mySerial(rxPin, txPin); // RX, TX

void setup ()
{
  int pin ;
 
  mySerial.begin(4800);
  for (pin = MIN_DPIN ; pin < MAX_DPIN ; ++pin) {
    digitalWrite (pin, LOW) ;
    pinMode (pin, INPUT) ;
  }
  analogReference (DEFAULT) ;
}

void myPutChar (int byte)
{
  mySerial.write (byte);
}

int myGetChar ()
{
  int x ;
  while ((x = mySerial.read ()) == -1)
    ;
return x ;
}

void loop ()
{
  unsigned int pin;
  bool pinChk;
  unsigned int aVal, dVal;
 
  for (;;)
  {
    if (mySerial.available () > 0)
    {
      switch (myGetChar ())
      {
        case CMD_PING:
          myPutChar (ANS_OK) ;
          continue ;

        case CMD_PIN_O:
          pin = myGetChar () ;
          if ((pin >= MIN_DPIN) && (pin <= MAX_DPIN)) {
            pinMode (pin, OUTPUT) ;
            myPutChar (ANS_OK) ;
          } else {
            myPutChar (ANS_NG) ;
          }
         continue ;

        case CMD_PIN_I:
          pin = myGetChar () ;
          if ((pin >= MIN_DPIN) && (pin <= MAX_DPIN)) {
            pinMode (pin, INPUT) ;
            myPutChar (ANS_OK) ;
          } else {
            myPutChar (ANS_NG) ;
          }
          continue ;

        case CMD_PIN_0:
          pin = myGetChar () ;
          if ((pin >= MIN_DPIN) && (pin <= MAX_DPIN)) {
            digitalWrite (pin, LOW) ;
            myPutChar (ANS_OK) ;
          } else {
            myPutChar (ANS_NG) ;
          }
          continue ;

        case CMD_PIN_1:
          pin = myGetChar () ;
          if ((pin >= MIN_DPIN) && (pin <= MAX_DPIN)) {
            digitalWrite (pin, HIGH) ;
            myPutChar (ANS_OK) ;
          } else {
            myPutChar (ANS_NG) ;
          }
         continue ;

        case CMD_RD_PIN:
          pin = myGetChar () ;
          if ((pin >= MIN_DPIN) && (pin <= MAX_DPIN)) {
            dVal = digitalRead (pin) ;
            myPutChar ((dVal == HIGH) ? 1 : 0) ;
          } else {
            myPutChar (ANS_NG) ;
          }
          continue ;

        case CMD_RA_PIN:
          pin = myGetChar () ;
          if ((pin >= MIN_APIN) && (pin <= MAX_APIN))
            aVal = analogRead (pin) ;
          else
            aVal = 0;
          myPutChar ((aVal & 0xFF00) >> 8) ;        // High byte first
          myPutChar ( aVal & 0x00FF      ) ;
          continue ;

        case CMD_PWM_PIN:      
          pin = myGetChar () ;
          pinChk = 0;
          for (int i=0;i<MAX_PWMPIN;i++) {
            if (pin == PWM_PIN[i]) pinChk = 1;
          }
          if (pinChk) {
            pinMode (pin, OUTPUT) ;
            myPutChar (ANS_OK) ;
          } else {
            myPutChar (ANS_NG) ;
          }
          continue ;

        case CMD_PWM_VAL_PIN:
          pin  = myGetChar () ;
          dVal = myGetChar () ;
          pinChk = 0;
          for (int i=0;i<MAX_PWMPIN;i++) {
            if (pin == PWM_PIN[i]) pinChk = 1;
          }
          if (pinChk) {
            analogWrite (pin, dVal) ;
            myPutChar (ANS_OK) ;
          } else {
            myPutChar (ANS_NG) ;
          }
          continue ;
      }
    }
  }
}

一部トリッキーなコードがあります。
理由はこちらに解説がありますが、とっても 悩みました。
「Arduino のスケッチでは、いきなりプリプロセッサ命令文を書いてはイケナイ」そうです。

(前略)

int dmy = 0; //これを宣言しないとエラーになる
// 理由は http://memo.tank.jp/archives/1437
#if defined(__AVR_ATtiny84__)
int PWM_PIN[] = {5, 6, 7, 8};
#elif defined(__AVR_ATtiny85__)
int PWM_PIN[] = {3, 4};
#endif

(後略)

【Raspberry側】

Raspberry側のコードは以下のようになります。
ATtiny85でも同じコードが使えるようになっています。

/*
 * Drogon Remote Control for Raspberry Pi
 *
 * cc -o DRCATtiny DRCATtiny.c -lwiringPi
 */

#include <stdio.h>
#include <string.h>
#include <errno.h>

#include <wiringPi.h>
#include <wiringSerial.h>

#define TIMEOUT 1000
#define ATTINY84
//#define ATTINY85

#if defined(ATTINY84)
#define outPin  8
#define pwmPin  7
#define in1Pin  6
#define in2Pin  5
#define an1Pin  4
#define an2Pin  3
#define an3Pin  2
#define an4Pin  1
#define BRate   4800
#define Hello   "DRC for ATtiny84"
#endif

#if defined(ATTINY85)
#define outPin  2
#define pwmPin  3
#define in1Pin  4
#define in2Pin  4
#define an1Pin  4
#define an2Pin  4
#define an3Pin  4
#define an4Pin  4
#define BRate   4800
#define Hello   "DRC for ATtiny85"
#endif

#define    DEBUG 0

int readSerial(int fd,int timeout) {
  unsigned long endTime;
  int ch;

if(DEBUG)printf("millis=%d\n",millis());
  endTime = millis () + timeout;
if(DEBUG)printf("endTime=%d\n",endTime);

  while (1) {
    if (millis () > endTime) return -1;
    if (serialDataAvail (fd)) {
      ch = serialGetchar (fd);
if(DEBUG)printf (" -> %02x\n", ch);
        if (ch == 2) return -2;
        return ch;
     return (ch-1);
    } // end if
  } // end while
}


// Send Ping (0x40)
//  Input Parameters
//   fd:File Descriptor
//   timeout:time out(sec)
//  Return Value
//   -1:time out
//    1:ok
int sendPing(int fd) {
  serialPutchar(fd, 0x40);
  return readSerial(fd, TIMEOUT);
}


// Set Pin NN OFF (0x30)
//  Input Parameters
//   fd:File Descriptor
//   pin:Pin Number
//  Return Value
//   -1:time out
//   -2:illegal pin number
//    1:ok
int setOff(int fd, int pin) {
  serialPutchar(fd, 0x30);
  serialPutchar(fd, pin);
  return readSerial(fd, TIMEOUT);
  return;
}

// Set Pin NN ON (0x31)
//  Input Parameters
//   fd:File Descriptor
//   pin:Pin Number
//  Return Value
//   -1:time out
//   -2:illegal pin number
//    1:ok
int setOn(int fd, int pin) {
  serialPutchar(fd, 0x31);
  serialPutchar(fd, pin);
  return readSerial(fd, TIMEOUT);
  return;
}

// Set Pin NN as Input (0x69)
//  Input Parameters
//   fd:File Descriptor
//   pin:Pin Number
//  Return Value
//   -1:time out
//   -2:illegal pin number
//    1:ok
int setInput(int fd, int pin) {
  serialPutchar(fd, 0x69);
  serialPutchar(fd, pin);
  return readSerial(fd, TIMEOUT);
  return;
}

// Set Pin NN as Output (0x6F)
//  Input Parameters
//   fd:File Descriptor
//   pin:Pin Number
//  Return Value
//   -1:time out
//   -2:illegal pin number
//    1:ok
int setOutput(int fd, int pin) {
  serialPutchar(fd, 0x6F);
  serialPutchar(fd, pin);
  return readSerial(fd, TIMEOUT);
  return;
}

// Set Pin NN as PWM (0x70)
//  Input Parameters
//   fd:File Descriptor
//   pin:Pin Number
//  Return Value
//   -1:time out
//   -2:illegal pin number
//    1:ok
int setPwm(int fd, int pin) {
  serialPutchar(fd, 0x70);
  serialPutchar(fd, pin);
  return readSerial(fd, TIMEOUT);
  return;
}

// Set PWM value on Pin NN (0x76)
//  Input Parameters
//   fd:File Descriptor
//   pin:Pin Number
//   value:PWM value
//  Return Value
//   -1:time out
//   -2:illegal pin number
//    1:ok
int setPwmValue(int fd, int pin, int value) {
  serialPutchar(fd, 0x76);
  serialPutchar(fd, pin);
  serialPutchar(fd, value);
  return readSerial(fd, TIMEOUT);
  return;
}

// Read back Digital Pin NN (0x72)
//  Input Parameters
//   fd:File Descriptor
//   pin:Pin Number
//   timeout:time out(sec)
//  Return Value
//   -1:time out
//   -2:illegal pin number
//    0:Digital Value
//    1:Digital Value
int readDigital(int fd, int pin) {
  serialPutchar(fd, 0x72);
  serialPutchar(fd, pin);
  return readSerial(fd,TIMEOUT);
}

// Read back Analog Pin NN (0x61)
//  Input Parameters
//   fd:File Descriptor
//   pin:Pin Number
//   timeout:time out(sec)
//  Return Value
//   -1:time out
//   0-1023:Analog Vlaue
// return 0-1023
int readAnalog(int fd, int pin) {
  unsigned long endTime;
  int ch;
  int flag = 0;
  int ret;

if(DEBUG)printf("millis=%d\n",millis());
  endTime = millis () + TIMEOUT;
if(DEBUG)printf("endTime=%d\n",endTime);
  serialPutchar(fd, 0x61);
  serialPutchar(fd, pin);
  while (1) {
    if (millis () > endTime) return -1;
    if (serialDataAvail (fd)) {
      ch = serialGetchar (fd);
if(DEBUG)printf (" -> %02x\n", ch);
      if (flag == 0) {
        ret=ch<<8;
        flag=1;
      } else {
        ret=ret+ch;
        return ret;
      }
    } // end if
  } // end while
}


int main ()
{
  int fd;
  int ret,i;

  if ((fd = serialOpen ("/dev/ttyAMA0", BRate)) < 0) {
    fprintf (stderr, "Unable to open serial device: %s\n", strerror (errno)) ;
    return 1 ;
  }

  if (wiringPiSetup () == -1) {
    fprintf (stdout, "Unable to start wiringPi: %s\n", strerror (errno)) ;
    return 1 ;
  }

  printf("%s\n",Hello);
// sendPing
  printf("sendPing=%d\n",sendPing(fd));
// setOutput setON setOff
  ret=setOutput(fd,outPin);
  printf("setOutput[%d]=%d\n",outPin,ret);
  if (ret == 1) {
    for(i=0;i<5;i++) {
      setOn(fd,outPin);
      delay(500);
      setOff(fd,outPin);
      delay(500);
    } // end for
  } // end if
// setPwm setPwmValue
  ret=setPwm(fd,pwmPin);
  printf("setPwm[%d]=%d\n",pwmPin,ret);
  if (ret == 1) {
    for(i=0;i<255;i=i+10) {
      setPwmValue(fd,pwmPin,i);
      delay(200);
    } // end for
  } // end if
  setPwmValue(fd,pwmPin,0);
// setInput readDigital
  printf("setInput[%d]=%d\n",in1Pin,setInput(fd,in1Pin));
  ret=readDigital(fd,in1Pin);
  printf("readDigital[%d]=%d\n",in1Pin,ret);
  printf("setInput[%d]=%d\n",in2Pin,setInput(fd,in2Pin));
  ret=readDigital(fd,in2Pin);
  printf("readDigital[%d]=%d\n",in2Pin,ret);
// readAnalog
  ret=readAnalog(fd,an1Pin);
  printf("readAnalog[A%d]=%d\n",an1Pin,ret);
  ret=readAnalog(fd,an2Pin);
  printf("readAnalog[A%d]=%d\n",an2Pin,ret);
  ret=readAnalog(fd,an3Pin);
  printf("readAnalog[A%d]=%d\n",an3Pin,ret);
  ret=readAnalog(fd,an4Pin);
  printf("readAnalog[A%d]=%d\n",an4Pin,ret);
  return 0 ;
}

Raspberry側のコードを実行すると以下の様になります。
ATtiny84のPB2に接続されているLEDが0.5秒間隔で点滅し、PA7に接続されているLEDが徐々に明るくなります。
アナログ値(A1からA4)もほぼ正確に取れています。




ちなみに、Raspberryからの給電を3.3Vに変更しても、まったく同じ結果となりました。

ATtiny85ではピン数が少ないので、たいしたことはできませんが、RaspberryとATtiny85との結線は以下の様になります。
こちらも、ATtinyへの給電は5Vとしましたので、ATtiny85のTXはレベルシフト(5V→3.3V)して RaspberryのRXに接続し ています。(黄色の線)
ATtinyへの給電を3.3Vにすれば、レベルシフトは不要になります。



Raspberry側のコードを実行すると以下の様になります。
ATtiny85のPB2に接続されているLEDが0.5秒間隔で点滅し、PB3に接続されているLEDが徐々に明るくなります。
PB4はGNDなのでreadDigitalもreadAnalogも結果は0です。



このように、DRCを使うとProMiniやATtinyをPWM機能つきのA/Dコンバータとして使うことができる様になります。
ProMiniやATtinyを5Vで使うと0から5VのA/Dコンバータとなります。