Vol.842 8.Apr.2022

撮影用LED照明スタンド USB接続PICでI2CスレーブPICの制御

L 撮影用LED照明スタンド 〜Vlog_Starter_Kit_α

by fjk

 写真を撮影するときに影の出ない照明が欲しい時がある。そんな時に便利なのがスタンド付きリングライト照明装置である。と、家電ショップで探していると、Vlog_Starter_Kit_α(ETSUMI、\3,960)を見つけた。製品はスタンドにネジでカメラを固定でき、スマホ固定アダプタが付属し、カメラ用スタンドとしても利用できる(abc840でも使用)。コールドシューも付いているので撮影用フラッシュも取り付け可能である。自立でカメラアングルも自由に設定できるので、自撮りやセルフタイマー撮影にも使え、収納サイズも小さく、持ち運びにも便利。

【主な仕様】
収納サイス:23cmx5cmφ
最大伸長:23〜98cm高(6段)
照明LED:外形16cm、内径11cm
LED: 48個、10W(5V/2A)
発光色:白色、MIX、暖色
光 量:10段階(リモコンで)
電 源:USB(ケーブルのみ付属
収納サイズはコンパクト 自立型照明スタンド


P USB接続PICでI2CスレーブPICの制御

by fjk

 I2C(Inter-Integrated Circuit)は、フィリップス社が提唱した周辺デバイスとのシリアル通信の方式で、主にEEPROMメモリICなどとの高速通信用でしたが、2線でデバイス間の通信が実現できることから、低速な周辺機器をマザーボードへ接続したり、組み込みシステム、携帯電話などで使われている。
 I2Cマスターにはabc839の回路(PIC16F1459)を流用し、I2CスレーブにはPIC16F18325を用いた。
 スレーブのMCC設定は、I2C(MSSP)設定をI2C、slaveとする他はabc838とほぼ同じとした。また、スレーブのLEDはRA5(=LED)、AD入力はRA4(=AVr4)を使用(ピン設定、ADC設定はabc838を参考)。
 マスターはTMR2、USBとI2CのLCDも同時に使うので、abc840のスケッチを流用した。プロジェクトの流用は、projectでコピーしたいプロジェクトを選び右クリック、Copyを選び、新プロジェクト名を入力するとコピーが作成される。マスターではTimer2.cの割り込み処理でtmFlg=1を追加することも忘れずに(abc838参照)。
 マスターはI2C_master_example.cの関数で、スレーブアドレス、レジスタ、データを順に送ることができるが、これらの関数を使う場合は、スレーブ側でその対策が必要。特にレジスタ付きRead関数は、最初にアドレスが送信(W)モードで送り出され、その後、受信(R)モードとなり、スレーブ側では最初のアドレス受信時に受信か送信かの判断ができないので、マスターの受信はI2C_ReadNByte関数のみを使うことにした。
 スレーブのI2C通信スケッチはMCCで作成されたI2Cライブラリのみでも実現できそうだが、ライブラリの解析・確認に時間がかかりそうなので、その一部を利用し、送受信の基本関数と専用処理を含む独自スケッチ(i2c_slave_do_isr.c)を作成した。
 スレーブはマスターからの要求を割込によって認知し、処理を開始するので、スレーブの割込ハンドラー処理関数 i2c1_my_Isr() に実行関数を直接記述し、I2C_Open関数内の割り込みハンドラーを変更した。
 当初、スレーブのBFビット=0(空)で「送信完了」かどうか確認していたが、マスターに同じデータが2度送信され、「書き込み衝突」(送信終了前にバッファーに書込→次データが書き込まれない)が発生しているようなので、WCOLビットで衝突を確認することにしたところ、正常にデータが送信された

スレーブ回路図 I2C通信テスト風景(M-S)
ピン設定(RA4=AVr4、RA5=LED) I2C設定(slaveアドレスを設定)
i2c_slave.c

/*=================================================
 *   I2C Slave 割込処理の追加・設定変更      	i2c_slave.c
 *================================================*/
#include "../i2c_slave_do_isr.h"			//独自割込処理スケッチのヘッダーファイルを追加
		・・・(途中省略)
void I2C_Open() 
{
  		。。。(途中省略)
  //  I2C1_SlaveSetIsrHandler(I2C1_Isr);		//オリジナルの割込ハンドラーをコメントにして、
    I2C1_SlaveSetIsrHandler(I2C1_my_Isr); 		//代わりに自作割込ハンドラーを設定
  		。。。(途中省略)

static inline bool I2C1_SlaveOpen()
{
   if(!SSP1CON1bits.SSPEN)
    {      
        SSP1STAT = 0x00;
        SSP1CON1 = 0x06;
        SSP1CON2 = 0x01;				//SEN=1、ストレッチを有効に
		。。。(以下省略)
 
i2c_slave_do_isr.c

/*==================================================
 *   I2C Slave 制御実行ライブラリ			i2c_slave_do_isr.c
 *=================================================*/
#include "mcc_generated_files/mcc.h"
#include "i2c_slave_do_isr.h"
#define I2CBFSIZE 16
static uint8_t	  i2cBf[I2CBFSIZE];			//I2C通信用データーバッファー
volatile uint8_t  Ix;					//バッファーデーターインデックス
volatile uint16_t aVal;					//AD変換データ

/**-----  受信要求処理(マスターからのI2Cデータを受信しLEDをON/OFF)*/ 
void i2c_s_do_read(void){
    uint8_t len;
    len = i2c_s_rcv(i2cBf, 3);				//受信データをrBufに読み出す
    SSP1CON1bits.CKP = 1; 				//ストレッチを解除
    switch ( i2cBf[2] ) {				//受信文字の3番目(文字データ)が・・
	case 'H': case 'h':
	    LED_SetHigh();
	    break;
    	case 'L': case 'l':
     	    LED_SetLow();
    	    break; 
    }
}

/**-----  送信要求処理 (AD変換後、そのデータをマスターにI2C送信)*/
void i2c_s_do_write(void){
    uint8_t len;
    len = iSSP1BUF;					//アドレスデータを読み出しBFフラグをクリア
    aVal = ADC_GetConversion(AVr4);			//ANA4(AVr4)入力をAD変換  (strech中のまま)
    i2c_s_snd(&aVal, 2);				//2バイト(10ビット)のAD変換データを返信
}

/**-----	指定バイト数をI2Cで受信 */
uint8_t i2c_s_rcv(uint8_t *buf, uint8_t len){
    Ix = 0;
    do{
    	if ( SSP1STATbits.BF) {				//I2Cバッファーに受信データがあれば
    	    buf[Ix++] = SSP1BUF; 			//受信データを取り出す
 	    SSP1CON1bits.CKP = 1;			//まだデータがあるならストレッチ解除(受信再開)
	    PIR1bits.SSP1IF = 0; 			//割込フラグクリア
	}
    }while ( Ix < len );				//指定文字数に達したら受信終了
    return Ix;						//受信バイト数を返す
}

/**-----	指定バイト数をI2Cで送信 */
void i2c_s_snd(uint8_t *buf, uint8_t len){
    uint8_t i;;
    for (i = 0; i < len; i++){
        while(SSP1STATbits.BF);				//I2Cバッファーが空になるまで待つ
        do{
            SSP1CON1bits.WCOL = 0;			//書込衝突フラグをクリア
            SSP1BUF = buf[i];				//送信データをI2Cバッファに書き込む
        }while(SSP1CON1bits.WCOL);			//書込衝突が無くなる(送信終了)まで待つ
        SSP1CON1bits.CKP = 1;				//ストレッチを解除(送信開始)
        PIR1bits.SSP1IF = 0;				//割込フラグクリア
    }	
}

/**---	I2C割込ハンドラ(ユーザー独自作成) **/
void I2C1_my_Isr() { 
    if(SSP1STATbits.R_nW == 0){				//マスターの書込要求か?
	i2c_s_do_read();				//スレーブがデータを受け取る(S=Read)
    }else{
	i2c_s_do_write();				//スレーブがデータを送る(S=write)
    }
    PIR1bits.SSP1IF = 0;				//割込フラグクリア
}
 
i2c_slave_do_isr.h

/*=================================================
 *   I2C Slave 制御実行ライブラリヘッダー	i2c_slave_do_isr.h
 *================================================*/
// <受信要求処理 > I2Cから1バイトデータを受信し、'H“ならLED点灯、'L‘ならLED消灯
// 入出力:なし 
void i2c_s_do_read(void);						

// <送信要求処理>	10ビットAD変換後、下位、上位バイトの順に2バイトをマスターに送信
// 入出力:なし
void i2C_s_do_write(void);

// <I2Cで指定バイト数の取得 > 指定されたバイト数の受信データを取得
// 入 力:*buf=受信データ格納ポインタ(アドレス、レジスタ、データ、・・の順に格納される)
//	    len=受信バイト数
//  戻り値:受信バイト数
uint8_t i2c_s_rcv(uint8_t *buf, uint8_t len);

// <	I2Cで指定バイト数の送信> 指定されたバイト数のデータを送信
// 入 力:*buf=送信データ格納ポインタ
//	    len=送信バイト数
//  戻り値:なし	
void i2c_s_snd(uint8_t *buf, uint8_t len);

// <	I2C割込ハンドラー処理関数> マスターからの通信があれば割込が発生し、処理を実行
// 入出力、なし
void I2C1_my_Isr(void); 

// ※スレーブのアドレス・マスクはMSSP1設定で指定
 
main.c (slave)

/*=========================================
 *   I2C Slave メイン	for PIC16F18325		main.c
 *========================================*/
/**----	main.c	-----**
#include  "mcc_generated_files/mcc.h"

main(){
    SYSTEM_Initialize();		 	// initialize the device
    INTERRUPT_GlobalInterruptEnable();		// Enable the Global Interrupts
    INTERRUPT_PeripheralInterruptEnable();	// Enable the Peripheral Interrupts
	I2C1_Open();				// I2C通信を開始する
    while(1){					// 割込があるまでなにもしないで待ち、 
    }						// 割込があれば、割り込み処理を実行する
)
 

/*==================================================
 *   I2C Master Exampleの主な送受信関数 i2c_master_example.c
 *=================================================*/
// <1バイト送信>  ・・・マスターの送信には2バイト、Nバイト関数も使用可
// 入 力:	address=スレーブI2Cアドレス
//		reg=スレーブデバイスレジスタ番号
//		data=送信データ(1バイト)
// 戻り値:	なし /
void I2C_Write1ByteRegister(i2c_address_t address, uint8_t reg, uint8_t data);

// <Nバイト受信>・・・マスターの受信にはこの関数のみに対応
// 入 力:	address=スレーブI2Cアドレス
//		*data=受信データバッファーポインタ
// 		len= 受信要求バイト数
// 戻り値:	受信データ(*dataにNバイト)
uint8_t  I2C_ReadNByte(i2c_address_t address, uint8_t *data, uint8_t len);
 
main.c (master)

/*================================================ 
 *   I2C Master メイン(with USB_lib&LCD_lib) main.c 
 *=============================================== */

#include "mcc_generated_files/mcc.h"
#include <stdio.h>
#include "USB_1459_CDC.h"
#include "i2cLCD_AQM0802A.h"

#define  BFSIZE 32
#define SV1 0x18	  				// Slave I2C Devie Address

char NEWLINE[] = "\r\n";
static char rBuf[BFSIZE]; 
static char wBuf[BFSIZE];
int tmFlg; 
uint16_t aVal;  

void main(void)
{
    SYSTEM_Initialize();	
    INTERRUPT_GlobalInterruptEnable();	
    INTERRUPT_PeripheralInterruptEnable();

    uint8_t swFlg = 0; 
    uint8_t adFlg = 0;  
    int num, i;

    LCD_init();
    char msg[] = "Hello!";
    LCD_str(msg);
    while(USBGetDeviceState() < CONFIGURED_STATE);	//USB接続の準備ができるまで待つ
    SendUSB_str(msg);
    SendUSB_str(NEWLINE);

    while (1) {
        num = RecvUSB(rBuf, BFSIZE);
        if(num > 0) {
            for(i=0; i < num; i++){
                switch (rBuf[i]){
                    case 0x0A:
                    case 0x0D:
                    	wBuf[i] = 0x00;
                    	break;
                    default:
                   	wBuf[i] = rBuf[i];
                   	break;
                }
            }
            LCD_clr();
            LCD_str(wBuf); 
            SendUSB_str("PIC> ");
            SendUSB_str(wBuf);
            SendUSB_str(NEWLINE);
            if(( rBuf[0] & 0x01) == 0){
               	LED_SetLow();
		I2C_Write1ByteRegister(SV1, 0, 'L');	//追加(スレーブSV1に'L'を送る)
            }else{
               	LED_SetHigh();
		I2C_Write1ByteRegister(SV1, 0, 'H');	//追加(スレーブSV1に'H'を送る)
	    }
        }
        if((SWC3_GetValue() == 0) & (swFlg == 0)){
           swFlg = 1;
           if(adFlg == 0){
             	sprintf(wBuf,"ADC Start");
		SendUSB_str(wBuf);
         	SendUSB_str(NEWLINE);
          	adFlg = 1;
           }else{
		sprintf(wBuf,"ADC Hold");
		SendUSB_str(wBuf);
		SendUSB_str(NEWLINE);
               	adFlg = 0;
           }
        }else if((SWC3_GetValue() > 0) & (swFlg == 1)){
            swFlg = 0;
        }
    //    aVal = ADC_GetConversion(3);			//マスターのADCは使わないのでコメントに
        if((tmFlg > 0) && (adFlg > 0)){			//tmFlgとadFlgが1なら1秒ごとに以下を実行
	    I2C_ReadNBytes(SV1, &aVal, 2);		//追加(スレーブからADCデータ取得)
	    sprintf(wBuf,"ADC:%4d",aVal);
	    LCD_cursor(0,1);
	    LCD_str(wBuf);
	    SendUSB_str(wBuf);
	    SendUSB_str(NEWLINE);
	    tmFlg = 0;
    	}
    } 
}

◆◆◆◆◆◆ おまけ ◆◆◆◆◆
 I2CはPICでもよく使われ、I2Cマスターの利用・報告例は多いが、I2Cスレーブの利用例は少ない。
 そこで、I2Cシーケンスについておさらいする(Microchip社のPIC16F1764 日本語DataSheetより)
【I2Cで使われる主なレジスタ】
    

===<7ビットアドレスでの送受信シーケンス>===
【 Master:代表的な送信シーケンス】  M−(data)ー>S

  1. (M)ユーザが、SSPxC0N2レジスタのSENビットをセットしてスタート条件を生成する。
  2. (M)スタート条件の完了時にSSPxlFビットがハードウェアによりセットされる。
  3. (M)ソフトウェアでSSPxlFビットをクリアする。
  4. (M)モジュールは、新しい動作を開始する前に必要なスタート時間が経過するまで待機する。
  5. (M)ユーザは送信先のスレーブアドレスをSSPxBUFに書き込む。
  6. (M) 8ビット全てが送信されるまで、アドレスがSDAピンにシフト出力される。SSPxBUFへの書き込みが完了すると、送信を開始する。
  7. (M)モジュールは、スレーブテバイスから ACKビットを受信して、その値をSSPxC0N2レジスタのACKSTATビットに書き込む。
  8. (M)モジュールは、9番目のクロックサイクルの最後にSSPxlFビットをセットして割リ込みを生成する。
  9. (M)ユーザはSSPxBUFに8ビットの送信テータを書き込む。
  10. (M)8ビット全てが送信されるまで、テータがSDAビンにシフト出力される。
  11. (M)モジュールは、スレーブテバイスからACKビットを受信して、その値をSSPxCON2レジスタのACKSTATビットに書き込む。
  12. 全ての送信データバイトに対して、ステップ8〜11を繰り返す。
  13. (M)ユーザが、SSPxCON2レジスタのPENまたはRSENビットをセツトしてストップまたは反復スタート条件を生成する。ストップ/反復スタート条件が完了すると、割り込みが生成される。

【 Master:代表的な受信シーケンス 】 Mー>S−(data)ー>M

  1. (M)ユーザが、SSPxC0N2レジスタのSENビットをセットしてスタート条件を生成する。
  2. (M)スタート条件の完了時にSSPxlFビットがハードウェアによりセットされる。
  3. (M)ソフトウェアでSSPxlFビットがクリアする。
  4. (M)ユーザが送信先スレーブアドレスをSSPxBUFに書き込み、R/Wビットがセットされる。
  5. (M)8ビット全てが送信されるまで、アドレスがSDAピンにシフト出力される。SSPxBUFへの書き込みが完了すると、送信を開始する。
  6. (M)モジュールは、スレーブテバイスからACKビットを受信して、その値をSSPxC0N2レジスタのACKSTATビットに書き込む。
  7. (M)モジュールは、9番目のクロックサイクルの最後にSSPxlFビットをセットして割リ込みを生成する。
  8. (M)ユーザがSSPxC0N2レジスタのRCENビットをセットし、マスタがスレーブからバイトデータを受信する。
  9. (M)SCLの8番目の立ち下がリエッジ後、SSPxlFと BFがセットされる。
  10. (M)マスタが SSPxlFビットをクリアし、SSPxBUFから受信バイトを読み出してBFビットをクリアする。
  11. (M)マスタが、SSPxCON2レジスタのACKDTビットを使って、スレーブへ送信するACK値を設定し、 ACKENビットをセットしてACKを送信する。
  12. (M)マスタからスレーブへACK信号が送信され、 SSPxlFビットがセットされる。
  13. (M)ユーザがSSPxlFをクリアする。
  14. スレーブから1バイトを受信するたびに、ステップ8〜13を繰り返す。
  15. (M)マスタがNOTACKを送信するか、ストップ条件を送信すると通信が終了する。

【 Slave:代表的な送信シーケンス 】 Mー>S−(data)ー>M

  1. (S)スタートビットを検出する。
  2. (S)SSPxSTATのSビットがセットされる。スタート検出割り込みが有効の場合はSSPxlFビットもセットされる。
  3. (S)R/Wビットがセットされ、一致アドレスをスレーブが受け取る
  4. (S)スレーブハードウェアがACKを生成してSSPxlFビットをセットする。
  5. (S)ソフトウェアでSSPxlFビットをクリアする。
  6. (S)ソフトウェアでSSPxBUFから受信アドレスを読み出して、BFをクリアする。
  7. (S)R/Wがセットされているため、CKPビットはACKの後に自動的にクリアされている。
  8. (S)スレーブソフトウェアでSSPxBUFに送信データを書き込む。
  9. (S)CKPビットがセットされてSCLが解放されると、マスタはスレーブからデータを読み出す事ができる。
  10. (S)マスタからのACK応答がACKSTATレジスタに書込まれ、SSPxlFビットがセットされる。
  11. (S)ソフトウェアでSSPxlFビットがクリアする。
  12. (S)スレーブソフトウェアでACKSTATビツトをチェックしてマスタに後続の送信テータがあるかどうか確認する。(マスタがACKを生成すると、クロックがストレッチされる)
  13. 1バイトを送信するたびに、ステップ9〜13を繰リ返す。
  14. (M)マスタがNOTACKを送信した場合、クロックはホールド(ストレッチ)されないが、SSPxlFビットはセットされる。
  15. (M)マスタが反復スタート条件またはストップ条件を送信する。
  16. (M)スレーブのアドレス指定が解除される。

【 Slave:代表的な受信シーケンス 】 M−(data)ー>S

  1. (S)スタートビットを検出する。
  2. (S)SSPxSTATのSビットがセットされる。スタート検出割り込みが有効の堤合は SSPxlFビットもセットされる。
  3. (S)R/Wビットがクリアされ、一致アドレスをスレーブが受信する。
  4. (S)スレーブがSDAをLowに駆動してマスタにACKを送り、SSPxlFビットをセットする。
  5. (S)ソフトウェアでSSPxlFビットをクリアする。
  6. (S)ソフトウェアでSSPxBUFから受信アドレスを読み出すと、BFフラグがクリアされる。
  7. (S)SEN=1の堤合、ソフトウェアでCKPビットをセットしてSCLラインを解放する。
  8. (M)マスタがデータバイトを送信する。
  9. スレーブがSDAをLowに駆動してマスタにACKを送信し、SSPxlFビットをセットする。
  10. (S)ソフトウェアでSSPxlFをクリアする。
  11. (S)ソフトウェアでSSPxBUFから受信バイトを読み出すと、BFがクリアされる。
  12. マスタからの全ての受信バイトに対して、ステップ 8〜12を繰り返す。
  13. (M)マスタがSSPxSTATのPビットをセットしてストップ条件を送信し、バスがアイドル状態に移行する。

【slave:AHENとDHENを使う7ビット受信】  (アドレスホールド機能)

  1. (S)SSPxSTATのSビットがセットされる。スタート検出割り込みが有効の場合は SSPxlFビットもセットされる。
  2. (S)R/Wビットがクリアの状態で一致アドレスが取り込まれる。SSPxlFがセットされ、SCLの8番目の立ち下がりエッジの後にCKPがクリア(ストレッチON)される。
  3. (S)プログラムでSSPxlFをクリアする。
  4. (S)プログラムで、SSPxC0N3レジスタのACKTIMビッ卜を確認する事で、SSPxlFビットがACKの前後いずれであるかを判断できる。
  5. (S)プログラムでSSPxBUFからアドレス値を読み出し、BFフラグがクリアされる。
  6. (S)プログラムでACKDTをセツトして、ACK値をマスタに送出する。
  7. (S)プログラムでCKPビツトをセットしてクロックを芭放する(ストレッチOFF)。
  8. (S)ACKの後にSSPxlFがセットされる(NACKの場合はセットされない)。
  9. (S)SEN=1の堤合、スレーブハードウェアはACKの後にクロックをストレッチする。
  10. (S)プログラムでSSPxlFをクリアする。
  11. (S)受信データバイトでSCLの8番目の立ち下がリエッジ後にSSPxlFビットがセットされて CKPビットがクリアされる(ストレッチON)。
  12. (S)プログラムでSSPxC0N3レジスタのACKTIMビッ卜を読み出して割り込み要因を判定する。
  13. (S)プログラムでSSPxBUFから受信データを読み出すと、BFがクリアされる。
  14. 1データバイトを受信するたびに、ステップ7〜14を繰り返す。
  15. (S)プログラムでACK = 1を送るか、マスタがストップ条件を送ると、通信が終了する(ストップ検出割リ込みが無効に設定されている状態でストップ条件が送信された場合、スレーブが通信終了を認識する唯一の方法はSSP1STATレジスタのPビッ卜をポーリングする事です)。


撮影用LED照明スタンド USB接続PICでI2CスレーブPICの制御