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


WiringPiの作者であるGordonsさんがこ ちらのページで「Drogon Remote Control(DRC)」という
シリアルでArduinoを操作するプロトコルを紹介しています。
DRCを使えばProMiniやATtinyなど、USBを持っていないAVRマイコンをRaspberryから操作することができます。

オリジナルのArduinoのスケッチはこ ちらからダウンロードできますが、これをProMini用に改造してみました。
また、オリジナルのArduinoのスケッチでは、たとえばアナログのピン番号に10を指定したり(A10は存在しない)
PWMのピン番号に7を指定したり(D7はPWMに使えない)したときに、無視されてしまいます。
そこで、このように不正なピン番号が指定されたときにRaspberry側にエラーを返すようにしました。
従ってオリジナルのスケッチとの互換性はありません。

RaspberryとProMiniとの結線は以下の様になります。
ProMiniは5V仕様のものを使いましたので、ProMiniのTXはレベルシフト(5V→3.3V)してRaspberryのRXに接続し ています。(黄色の線)
レベルシフトには2つの抵抗を使いますが、抵抗値の比率が1対2であればΩ数は何でもかまいません。




【ProMini側】

ProMini側のスケッチは以下の通りです。
ProMiniへのスケッチの書き込みはこちらで 紹介しています。
MAX_APINは3(A0からA3)、MAX_DPINは17(D2からD17)にしていますが、PC4 PC5 ADC6 ADC7のピン(リセットボタンの横にあるピン)も使えば、
MAX_APINは7(A0からA7)に、MAX_DPINは19(D2からD19)にすることができます。

/*
 * drcProMini:
 *  The Drogon Remote Control for Pro Mini.
 *  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.
 * 
 */

// 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

#define MIN_APIN        0
#define MAX_APIN        3
#define MIN_DPIN        2
#define MAX_DPIN       17
#define MAX_PWMPIN      6
int PWM_PIN[] = {3, 5, 6, 9, 10, 12};

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

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

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

void loop ()
{
  unsigned int pin;
  bool pinChk;
  unsigned int aVal, dVal;
 
  for (;;)
  {
    if (Serial.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 ;
      }
    }
  }
}


【Raspberry側】

最初にraspi-configを使ってシリアルからのログインを無効にします。



また、Jessieではこれだけではダメなようで、こ ちらで 紹介されているようにサービスを止める必要があります。

Raspberry側のコードは以下のようになります。
DRCのコマンド毎に以下の関数を用意しています。
各関数の引数や戻り値は以下のソースにコメントで記入しています。
wiringPiSetup()は一見不要そうに見えますが、タイムアウトの検出にmillis()を使っています。

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

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

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

#define TIMEOUT 1000
#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
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", 9600)) < 0) {
  if ((fd = serialOpen ("/dev/ttyAMA0", 115200)) < 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("DRC for ProMini\n");
// sendPing
  printf("sendPing=%d\n",sendPing(fd));
// setOutput setON setOff
  ret=setOutput(fd,8);
  printf("setOutput[8]=%d\n",ret);
  if (ret == 1) {
    for(i=0;i<5;i++) {
      setOn(fd,8);
      delay(500);
      setOff(fd,8);
      delay(500);
    } // end for
  } // end if
// setPwm setPwmValue
  ret=setPwm(fd,9);
  printf("setPwm[9]=%d\n",ret);
  if (ret == 1) {
    for(i=0;i<255;i=i+10) {
      setPwmValue(fd,9,i);
      delay(200);
    } // end for
  } // end if
  setPwmValue(fd,9,0);
// setInput readDigital
  printf("setInput[10]=%d\n",setInput(fd,10));
  ret=readDigital(fd,10);
  printf("readDigital[10]=%d\n",ret);
  printf("setInput[11]=%d\n",setInput(fd,11));
  ret=readDigital(fd,11);
  printf("readDigital[11]=%d\n",ret);
// readAnalog
  ret=readAnalog(fd,0);
  printf("readAnalog[A0]=%d\n",ret);
  ret=readAnalog(fd,1);
  printf("readAnalog[A1]=%d\n",ret);
  ret=readAnalog(fd,2);
  printf("readAnalog[A2]=%d\n",ret);
  ret=readAnalog(fd,3);
  printf("readAnalog[A3]=%d\n",ret);
  return 0 ;
}

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



ProMiniと正常にシリアル通信できないときは以下のエラーを表示します。



引き続きDRCをATtinyで使うサンプルを紹介します。