EOS USBコントロール再び(続) | Nature | Photography | Music | Art

Nature | Photography | Music | Art

日々好奇心の趣くまま

サイト内の写真の使用ならびに無断転用を禁じます。

前回の続きの実践編で、ほぼ自身の備忘録なので非常に長いです。

(参考にされる方は自己責任・No Question・No Supportでお願いします。)

細かい説明は割愛して「習うより慣れろ」方式で。

まず、オリジナルのPTPライブラリに存在していないアクションをライブラリに一個追加します。

それは「ボタン半押し」で、撮影前に露出計測の結果などほしい場合やオートフォーカスしたい場合など、個人的に多用する機能なので追加。

念のためバックアップを忘れずに。

方法は前回インストールしたPTPライブラリのフォルダにあるcanoneos.cの最後に以下の関数を追加。




// canoneos.cpp

uint16_t CanonEOS::HalfPush()
{
uint32_t params[2];

params[0] = 0x00000001;
params[1] = 0x00000000;

Operation(0x9128, 2, params);
delay(500);
Operation(0x9129, 1, params);
return PTP_RC_OK;
}





またcanoneos.hにその宣言を追加。以下の行を探して後ろに赤の部分を追加。



//canoneos.h

uint16_t StopBulb();
uint16_t HalfPush();




以上でライブラリの改造は完了。

任意のフォルダを作成。以下の3つのソースコードを置く。



// EOSSerialCtrl.ino
#include <inttypes.h>
#include <avr/pgmspace.h>

#include <Usb.h>
#include <usbhub.h>
#include <ptp.h>
#include <canoneos.h>
#include <valuelist.h>
#include <eosvaluetitles.h>
#include <eoseventparser.h>

#include "eoseventhandlers.h"

int Aperture = 0;
int ShutterSpeed = 0;
int Iso = 0;
int ExposureCompensation = 0;
int ShootingMode = 0;
int DriveMode = 0;

class CanonEos : public CanonEOS
{
public:
CanonEos(USB *pusb, PTPStateHandlers *pstates) : CanonEOS(pusb, pstates)
{
};

virtual void RecStart() {
uint16_t ptp_error = PTP_RC_GeneralError;
Serial.println("#RecStart");
SetProperty(EOS_DPC_VideoRecord, 4);
}
virtual void RecStop() {
uint16_t ptp_error = PTP_RC_GeneralError;
Serial.println("#RecStop");
SetProperty(EOS_DPC_VideoRecord, 0);
}

};

class CamStateHandlers : public EOSStateHandlers
{
enum CamStates { stInitial, stDisconnected, stConnected };
CamStates stateConnected;

void SetAperture(PTP *ptp,int pValue) {
((CanonEos*)ptp)->SetProperty(EOS_DPC_Aperture,pValue);
return;
}

void SetShutterSpeed(PTP *ptp,int pValue) {
((CanonEos*)ptp)->SetProperty(EOS_DPC_ShutterSpeed,pValue);
return;
}

void SetExposureCompensation(PTP *ptp,int pValue) {
((CanonEos*)ptp)->SetProperty(EOS_DPC_ExposureCompensation,pValue);
return;
}

void SetIso(PTP *ptp,int pValue) {
((CanonEos*)ptp)->SetProperty(EOS_DPC_Iso,pValue);
return;
}

public:
CamStateHandlers() : stateConnected(stInitial) {};

virtual void OnDeviceDisconnectedState(PTP *ptp);
virtual void OnDeviceInitializedState(PTP *ptp);

};

CamStateHandlers CamStates;
USB Usb;
USBHub Hub1(&Usb);
CanonEos Eos(&Usb, &CamStates);

void setup()
{
byte adrs, i;

Serial.begin( 115200 );
Serial.println("#Start");
if (Usb.Init() == -1)
Serial.println("#ERR OSC did not start.");

delay( 200 );
}

void loop()
{
Usb.Task();
}

void CamStateHandlers::OnDeviceDisconnectedState(PTP *ptp)
{
int incomingByte = 0;

if (stateConnected == stConnected || stateConnected == stInitial)
{
stateConnected = stDisconnected;
Serial.println("#Disconnected");
}
}

void CamStateHandlers::OnDeviceInitializedState(PTP *ptp)
{
int incomingByte = 0,num,i,val,numargs;
static uint32_t next_time = 0;
static char cmd[128],arg[3][32];
static int cmdidx;
EosEventHandlers hnd;
EOSEventParser prs(&hnd);
char *lexeme;

if (stateConnected == stDisconnected || stateConnected == stInitial)
{
stateConnected = stConnected;
Serial.println("#Connected");
cmdidx = 0;
}

if (stateConnected == stConnected) {
uint32_t time_now = millis();

if (time_now >= next_time)
{
next_time = time_now + 300;
Eos.EventCheck(&prs);
}

if ((num = Serial.available()) > 0) {
for(i = 0;i < num;++i) {
incomingByte = Serial.read();
cmd[cmdidx++] = incomingByte;
if(incomingByte == 0xd) {
cmd[cmdidx++] = '\0';
cmdidx = 0;
break;
}
return; // in case of imcomplete cmd. wait for next loop.
}

for(numargs = 0,i = 0,lexeme = strtok(cmd, " \n"); lexeme; numargs++, i++,lexeme = strtok(NULL, " \n")) {
if(i >= 2)
break;


strcpy(arg[i],lexeme);

if(i) {
sscanf(lexeme,"%x",&val);
//Serial.print("val=");
//Serial.print(val,HEX);
} else {
//Serial.print("#Command ");
//Serial.print(lexeme);
//Serial.print(" ");
}

}

//Serial.println("");

switch(arg[0][0]) {
case 'l':
((CanonEos*)ptp)->SwitchLiveView(true);
//Serial.println("#LiveView on");
delay(50);
break;
case 'o':
((CanonEos*)ptp)->SwitchLiveView(false);
//Serial.println("#LiveView off");
delay(50);
break;
case 'f':
if(numargs < 2) break;
((CanonEos*)ptp)->MoveFocus(val);
//Serial.print("#Focus ");
//Serial.println("val",HEX);

delay(50);
break;
case 'c':
((CanonEos*)ptp)->Capture();
delay(1500);
break;
case 'h':
((CanonEos*)ptp)->HalfPush();
delay(1500);
break;
case 'a':
if(numargs < 2) break;
SetAperture(ptp,val);
delay(50);
break;
case 's':
if(numargs < 2) break;
SetShutterSpeed(ptp,val);
delay(50);
break;
case 'e':
if(numargs < 2) break;
SetExposureCompensation(ptp,val);
delay(50);
break;
case 'i':
SetIso(ptp,val);
delay(50);
break;
case 'm':
((CanonEos*)ptp)->SetProperty(EOS_DPC_ShootingMode,val);
delay(50);
break;
case '1':
((CanonEos*)ptp)->RecStart(); // movie rec start
delay(50);
break;
case '0':
((CanonEos*)ptp)->RecStop(); // movie rec stop
delay(50);
break;

}
}
}

}





// eoseventhandlers.cpp

#include "eoseventhandlers.h"
#include <eosvaluetitles.h>

extern int Aperture ;
extern int ShutterSpeed;
extern int Iso ;
extern int ExposureCompensation;
extern int ShootingMode ;
extern int DriveMode ;

char str[32];

template <class ValueType, const uint8_t TitleSize>
void FindTitleVal(uint8_t size, const ValueTitle<ValueType, TitleSize> *p, ValueType val)
{
for (int i=0; i<size; i++)
{
if (pgm_read_byte(&(p[i].value)) == val) {
for(int j = 0;;j++) {
str[j] = pgm_read_byte(&(p[i].title[j]));
if(str[j] == 0)
return;
}
return;
}
}
str[0] = 'N';
str[1] = '/';
str[2] = 'A';
str[3] = 0;

return ;
}

void printAperture(int value) {

}

void EosEventHandlers::OnPropertyChanged(const EOSEvent *evt)
{
switch(evt->propCode) {
case EOS_DPC_Aperture:
Aperture = evt->propValue;
Serial.print("#EOS_DPC_Aperture Changed ");
Serial.print(evt->propValue,HEX);
Serial.print(" ");
FindTitleVal<VT_APERTURE, VT_APT_TEXT_LEN>(VT_APT_COUNT, ApertureTitles, Aperture);
Serial.println(str);
break;
case EOS_DPC_ShutterSpeed:
ShutterSpeed = evt->propValue;
Serial.print("#EOS_DPC_ShutterSpeed Changed ");
Serial.print(evt->propValue,HEX);
Serial.print(" ");
FindTitleVal<VT_SHSPEED, VT_SHSPEED_TEXT_LEN>(VT_SHSPEED_COUNT, ShutterSpeedTitles, ShutterSpeed);
Serial.println(str);
break;
case EOS_DPC_Iso:
Iso = evt->propValue;
Serial.print("#EOS_DPC_Iso Changed ");
Serial.print(evt->propValue,HEX);
Serial.print(" ");
FindTitleVal<VT_ISO, VT_ISO_TEXT_LEN>(VT_ISO_COUNT, IsoTitles, Iso);
Serial.println(str);
break;
case EOS_DPC_ExposureCompensation:
ExposureCompensation = evt->propValue;
Serial.print("#EOS_DPC_ExposureCompensation Changed ");
Serial.print(evt->propValue,HEX);
Serial.print(" ");
FindTitleVal<VT_EXPCOMP, VT_EXPCOMP_TEXT_LEN>(VT_EXPCOMP_COUNT, ExpCompTitles, ExposureCompensation);
Serial.println(str);
break;
case EOS_DPC_ShootingMode:
ShootingMode = evt->propValue;
Serial.print("#EOS_DPC_ShootingMode Changed ");
Serial.print(evt->propValue,HEX);
Serial.print(" ");
FindTitleVal<VT_MODE, VT_MODE_TEXT_LEN>(VT_MODE_COUNT, ModeTitles, ShootingMode);
Serial.println(str);
break;
case EOS_DPC_DriveMode:
DriveMode = evt->propValue;
Serial.print("#EOS_DPC_DriveMode Changed ");
Serial.println(evt->propValue,HEX);
break;
}

}

void EosEventHandlers::OnAcceptedListSize(const EOSEvent *evt, const uint16_t size)
{
}

void EosEventHandlers::OnPropertyValuesAccepted(const EOSEvent *evt, const uint16_t index, const uint32_t &val)
{
switch(evt->propCode) {
case EOS_DPC_Aperture:
Serial.print("#EOS_DPC_Aperture Accepted ");
Serial.print(index,HEX);
Serial.print(" ");
Serial.println(val,HEX);
break;
case EOS_DPC_ShutterSpeed:
Serial.print("#EOS_DPC_ShutterSpeed Accepted ");
Serial.print(index,HEX);
Serial.print(" ");
Serial.println(val,HEX);
break;
case EOS_DPC_Iso:
Serial.print("#EOS_DPC_Iso Accepted ");
Serial.print(index,HEX);
Serial.print(" ");
Serial.println(val,HEX);
break;
}

}

void EosEventHandlers::OnObjectCreated(const EOSEvent *evt) {
Serial.println("#OnObjectCreated");
};




// eoseventhandlers.h

#ifndef __EOSEVENTHANDLERS_H__
#define __EOSEVENTHANDLERS_H__

#include <inttypes.h>
#include <avr/pgmspace.h>
#include <canoneos.h>
#include <eoseventparser.h>

#define PTPDEBUG

class EosEventHandlers : public EOSEventHandlers
{
public:
virtual void OnPropertyChanged(const EOSEvent *evt);
virtual void OnAcceptedListSize(const EOSEvent *evt, const uint16_t size);
virtual void OnPropertyValuesAccepted(const EOSEvent *evt, const uint16_t index, const uint32_t &val);
virtual void OnObjectCreated(const EOSEvent *evt) ;
};

#endif // __EOSEVENTHANDLERS_H__




前回と同様にPC、Arduino、EOSとUSB接続、EOSの電源はOFFのまま。

EOSSerialCtrl.inoをArduino Softwareでオープン

スケッチ->検証・コンパイル
ファイル->マイコンボードに書き込む

を実行

以上でプログラムがArduinoに書き込まれる。

PCのデバイスマネージャーを開きポートの箇所を見るとCOM5がArduinoであることが分かる。(PCによって番号は異なる)

puttyを以下の設定でOpen

Serial COM5

Connection->Serialにて
Speed 115200
Data bits 8
Stop bits 1
Parity None
Flow control None

Terminal->Local echo をForce on

ここでうまくつながっていれば、ターミナル上に


#Start
#Disconnected


と表示されるはず。駄目なら設定や接続を見直す。

EOSの電源をONにすると以下のようにズラズラと表示されるはず。


#Connected
#EOS_DPC_ShootingMode Changed 3 M
#EOS_DPC_DriveMode Changed 0
#EOS_DPC_Aperture Changed 40 11
#EOS_DPC_ShutterSpeed Changed 30 2"
#EOS_DPC_Iso Changed 60 800
#EOS_DPC_ExposureCompensation Changed 0 0
#EOS_DPC_Aperture Accepted 0 25
#EOS_DPC_Aperture Accepted 1 28
#EOS_DPC_Aperture Accepted 2 2B
#EOS_DPC_Aperture Accepted 3 2D
#EOS_DPC_Aperture Accepted 4 30
#EOS_DPC_Aperture Accepted 5 33
#EOS_DPC_Aperture Accepted 6 35
#EOS_DPC_Aperture Accepted 7 38
#EOS_DPC_Aperture Accepted 8 3B
#EOS_DPC_Aperture Accepted 9 3D
#EOS_DPC_Aperture Accepted A 40
#EOS_DPC_Aperture Accepted B 43
#EOS_DPC_Aperture Accepted C 45
#EOS_DPC_Aperture Accepted D 48
#EOS_DPC_Aperture Accepted E 4B
#EOS_DPC_Aperture Accepted F 4D
#EOS_DPC_Aperture Accepted 10 50
#EOS_DPC_Aperture Accepted 11 53
#EOS_DPC_Aperture Accepted 12 55
#EOS_DPC_ShutterSpeed Accepted 0 10
#EOS_DPC_ShutterSpeed Accepted 1 13
#EOS_DPC_ShutterSpeed Accepted 2 15
#EOS_DPC_ShutterSpeed Accepted 3 18
#EOS_DPC_ShutterSpeed Accepted 4 1B
.
.
.


これは、現在のEOSの内部設定をPC側に知らせているので、よく読むとEOSを触っている人なら想像がつく箇所もあるかと思います。

参考までに

…Changed… というメッセージは絞りやシャッタースピードをはじめカメラの内部パラメータが変化したときに送られる。
…Accepted… は現状受け入れ可能なパラメータ一覧を送り返している。例えばAvモードでは絞りは制御できるが、シャッタースピードはできない。
またTvモードではその逆だったり、状況によって受け入れ可能なパラメータは変化します。
EOSはこれらの受け入れ可能情報一覧をそのたびにホスト側に送り返しており、ホスト側ではこの情報を元にEOSに適切なコマンドを送ることができるわけです。

ここでうまくいったら、ターミナル上で

c[enter]

と入れてみてください。([enter]とはリターンキーをヒットのことです。以下同様)
一回撮影が行われたかと思います。
また、ターミナル上に

#OnObjectCreated

と表示されたかと思いますが、これは一枚の写真が撮影されたという合図となります。
測光できない場合、フォーカスが決まらない場合は撮影が行われずこのメッセージも来ません。

次に

l[enter]

と入れると、ライブビューを開始(デフォルトではスクリーンに絵は表示されません。)
レンズのオートフォーカスをONにして

f 3[enter]

と入れると、フォーカスリングが廻るかと思います。

更に

f 8003[enter]

で逆方向

ちなみにこの数字

3,2,1と小さくなるほど1回あたりの回り量が減っていきます。(相対量)
同様に8003,8002,8001も同様です。
これら6つ以外の数字は受け付けないようです。
EOSの仕様のため、フォーカス調整はライブビューON時しか受け付けないようです。

ライブビューを終わらせるには

o[enter]

ボタン半押しは(AvかTvモードで)

h[enter]

この後、自動露出モードの場合は計測された適正露出の値が帰ってくるはずです。

次に、より複雑な制御に関してミソとなるのがPTPライブラリにあるeosvaluetitles.hとなります。
読むのに少々慣れが必要ですが、例えば絞りの設定値である ApertureTitlesはこんな感じです。



const ValueTitle<VT_APERTURE, VT_APT_TEXT_LEN> ApertureTitles[] PROGMEM =
{
{0x00, {' ', ' ', '0', 0 } },
{0x08, {' ', ' ', '1', 0 } },
{0x0B, {'1', '.', '1', 0 } },
{0x0C, {'1', '.', '2', 0 } },
{0x0D, {'1', '.', '2', 0 } },
{0x10, {'1', '.', '4', 0 } },
{0x13, {'1', '.', '6', 0 } },
{0x14, {'1', '.', '8', 0 } },
{0x15, {'1', '.', '8', 0 } },
{0x18, {'2', '.', '0', 0 } },
{0x1B, {'2', '.', '2', 0 } },
{0x1C, {'2', '.', '5', 0 } },
{0x1D, {'2', '.', '5', 0 } },
{0x20, {'2', '.', '8', 0 } },
{0x23, {'3', '.', '2', 0 } },
{0x24, {'3', '.', '5', 0 } },
{0x25, {'3', '.', '5', 0 } },
{0x28, {'4', '.', '0', 0 } },
{0x2B, {'4', '.', '5', 0 } },
{0x2C, {'4', '.', '5', 0 } },
{0x2D, {'5', '.', '0', 0 } },
{0x30, {'5', '.', '6', 0 } },
{0x33, {'6', '.', '3', 0 } },
{0x34, {'6', '.', '7', 0 } },
{0x35, {'7', '.', '1', 0 } },
{0x38, {'8', '.', '0', 0 } },
{0x3B, {'9', '.', '0', 0 } },
{0x3C, {'9', '.', '5', 0 } },
{0x3D, {' ', '1', '0', 0 } },
{0x40, {' ', '1', '1', 0 } },
{0x43, {' ', '1', '3', 0 } },
{0x44, {' ', '1', '3', 0 } },
{0x45, {' ', '1', '4', 0 } },
{0x48, {' ', '1', '6', 0 } },
{0x4B, {' ', '1', '8', 0 } },
{0x4C, {' ', '1', '9', 0 } },
{0x4D, {' ', '2', '0', 0 } },
{0x50, {' ', '2', '2', 0 } },
{0x53, {' ', '2', '5', 0 } },
{0x54, {' ', '2', '7', 0 } },
{0x55, {' ', '2', '9', 0 } },
{0x58, {' ', '3', '2', 0 } },
{0x5B, {' ', '3', '6', 0 } },
{0x5C, {' ', '3', '8', 0 } },
{0x5D, {' ', '4', '0', 0 } },
{0x60, {' ', '4', '5', 0 } },
{0x63, {' ', '5', '1', 0 } },
{0x64, {' ', '5', '4', 0 } },
{0x65, {' ', '5', '7', 0 } },
{0x68, {' ', '6', '4', 0 } },
{0x6B, {' ', '7', '2', 0 } },
{0x6C, {' ', '7', '6', 0 } },
{0x6D, {' ', '8', '0', 0 } },
{0x70, {' ', '9', '1', 0 } }
};





つまりはEOS内部では各パラメータを独自の数値を割り振っており、たとえばF8.0に設定したい場合は

これを見つけて 0x38がそれに相当する数値(16進)となるわけです。

これを踏まえて、絞りをF8.0にしたい場合は、

a 38[enter]

となるわけです。もちろんTVモードの時はこの設定は無視されます。

同様にシャッタースピードはShutterSpeedTitlesを参照。
シャッタースピードを1/100にしたい場合は


{0x6D, {' ','1','0','0',0} },



なので

s 6d[enter]

ISOはIsoTitles参照。
ISOを800にしたい場合は


{0x85, {' ','8','0','0',0} },


なので

i 85[enter]

露出補正はExpCompTitles参照
補正を-1にしたい場合


{0xf8, {'-','1',' ',' ',' ',' ',0} },


なので

e f8[enter]

露出モードはModeTitles参照
マニュアル露出にしたい場合


{3, {'M',' ',' ',0} }, // Manual


なので
m 3[enter]

こんな感じで、アルファベット小文字 数値(必要であれば) [enter]の組み合わせで上記のパラメータを自由に操れるようになります。
ここでインプリメントしたのは必要最低限のパラメータ操作ですが、他にも外部からコントロールできるパラメータは数多く存在します。

今回はPCからコントロールするような例にしましたが、もちろんArduino単体動作するようなプログラムを組むことも可能です。
ただ他機器との連携などを考えたときに、非力でOSの載っていないArduinoに全部任せてしまうと力不足・リソース不足になるのは明らかなので、あえてArduinoをEOS制御専用として外部からコントロールする形にしました。

また、PCではなく軽量なRaspberryPiやBeagleBoneなどのボードLinuxからコントロールすれば重いPCを持ち歩く必要がなくなりアウトドアで快適に撮影できるようになる(はず)。

ここまで5D2と5D3、PCはWin7 64bitで動作確認していますが、最近のEOSであればおそらく他機種でも動作すると思われます。