I2CとSCCBの違いを理解しながらFPGAのソフトコアCPU(MicroBlaze)でOV5642カメラモジュールを動かす

# はじめに
[前回の記事](https://qiita.com/Rohira/items/6d0b8e9b848bc4b9db6c)で作成したMicroBlaze & FreeRTOSのシリアルコンソールソフトに機能追加してOmniVisionのOV5642を搭載したイメージセンサモジュールを制御しました。その過程で勉強になったことがあったため備忘録として記載します。特にI2CとSerial Camera Contorl Bus (SCCB)の仕様の違いとか。認識違いがあればご指摘よろしくお願いします。

# 本記事の要点
- I2CのSlave Address (7bit)は、SCCBの仕様では8bit目のR/Wビットを含めたIDとして表現される
- ちゃんとドキュメントを読みましょう(自戒を込めて)

# 背景
[前回の記事](https://qiita.com/Rohira/items/6d0b8e9b848bc4b9db6c)のシリアル通信コンソールからI2CのMaserとしてSlave deviceを制御するコードを書き、手元にあったOmniVisionのOV5642のイメージセンサーモジュールを動作させようとしたところ、SCCBのバスの仕様の理解ができていなかったため3時間ほどハマってしまいました。理解すると「あーなるほど」という感じなのですが日本語でまとまった記事があまりなかったためメモとして残したいと思います。

# MicroBlazeのI2C制御ソフト
Block DesignにAXI-IICのIPを利用してソフトからI2C制御を行います。作成したのは下記のようなもの。シリアルコンソールで入力したASCIIをトークン解析し、BSPのAXI-IICのドライバに対してSlave AddressやDataを設定します。

```c:I2C_driver.c
#include "i2c_driver.h"

static volatile BaseType_t SendComplete;
static volatile BaseType_t ReceiveComplete;
static volatile BaseType_t AbortCommunication;

/*-----------------------------------------------------------------------------
 *  @fn
 *  	I2C_SoftReset
 *
 *  @return		None.
 *
 ----------------------------------------------------------------------------*/
void I2C_SoftReset( void )
{
	XIic_Reset( &xIicInstance );
}

/*-----------------------------------------------------------------------------
 *  @fn
 *  	I2C_SelfTest
 *
 *  @return		XST_SUCCESS or XST_FAILURE.
 *
 ----------------------------------------------------------------------------*/
BaseType_t I2C_SelfTest( void )
{
	return XIic_SelfTest( &xIicInstance );
}

/*-----------------------------------------------------------------------------
 * @fn
 * 		I2C_Write
 *
 * @brief
 * 		This is the function to write binary data to i2c slave device.
 *
 *
 * @param	DeviceAddress
 * 				I2C/SCCB device address (7bit)
 *
 * @param	SendBufferPtr
 * 				Pointer indicating the start address of binary array
 * 				which stores sub-address and data to write.
 *
 * @param	SendByteCount
 * 				Number of byte to write.
 *
 *
 * @return		XST_SUCCESS or XST_FAILURE.
 *
 ----------------------------------------------------------------------------*/
BaseType_t I2C_Write( BaseType_t DeviceAddress, u8 *SendBufferPtr, BaseType_t SendByteCount )
{
	BaseType_t Status = XST_FAILURE;

	SendComplete = FALSE;
	AbortCommunication = FALSE;

	Status = XIic_Start( &xIicInstance );
	if(Status != XST_SUCCESS)
	{
		return XST_FAILURE;
	}

	Status = XIic_SetAddress( &xIicInstance, XII_ADDR_TO_SEND_TYPE, DeviceAddress);

	if(Status != XST_SUCCESS)
	{
		return XST_FAILURE;
	}


	Status = XIic_MasterSend( &xIicInstance, SendBufferPtr, SendByteCount );

	if( Status != XST_SUCCESS )
	{
		return XST_FAILURE;
	}

	/*
	 * Wait till data is transmitted.
	 */
	while( ( SendComplete == FALSE ) || ( XIic_IsIicBusy( &xIicInstance ) == TRUE ) )
	{
		if( AbortCommunication == TRUE )
		{
			XIic_IntrGlobalDisable(xIicInstance.BaseAddress);
			Status = XIic_Stop( &xIicInstance );
			return XST_FAILURE;
		}
	}

	Status = XIic_Stop( &xIicInstance );
	if(Status != XST_SUCCESS)
	{
		return XST_FAILURE;
	}

	return XST_SUCCESS;
}

/*-----------------------------------------------------------------------------
 * @fn
 * 		I2C_Read
 *
 * @brief
 * 		This is the function to read binary data from ov5642.
 *
 *
 * @param	DeviceAddress
 * 				I2C/SCCB device address (7bit)
 *
 * @param	SendBufferPtr
 * 				Pointer indicating the start address of binary array
 * 				which stores sub-address to read (16bit)
 *
 * @param	SendByteCount
 * 				2 byte for 16bit sub-address or 1 byte for 8bit sub-address
 *
 * @param	RecvBufferPtr
 * 				Pointer indicating the start address of binary array
 * 				to store received binary data.
 *
 * @param	RecvByteCount
 * 				Number of byte to read.
 *
 * @return		XST_SUCCESS or XST_FAILURE.
 *
 ----------------------------------------------------------------------------*/
BaseType_t I2C_Read( BaseType_t DeviceAddress, u8 *SendBufferPtr, BaseType_t SendByteCount, u8 *RecvBufferPtr, BaseType_t RecvByteCount )
{
	BaseType_t Status = XST_FAILURE;

	SendComplete = FALSE;
	ReceiveComplete = FALSE;
	AbortCommunication = FALSE;

	Status = XIic_Start( &xIicInstance );
	if( Status != XST_SUCCESS )
	{
		return XST_FAILURE;
	}

	Status = XIic_SetAddress( &xIicInstance, XII_ADDR_TO_SEND_TYPE, DeviceAddress);
	if(Status != XST_SUCCESS)
	{
		return XST_FAILURE;
	}

	/* Write sub-address(4byte for OV5642) before read operation */
	Status = XIic_MasterSend( &xIicInstance, SendBufferPtr, SendByteCount );

	if(Status != XST_SUCCESS)
	{
		return XST_FAILURE;
	}

	/*
	 * Wait till slave address is transmitted.
	 */
	while( ( SendComplete == FALSE ) || ( XIic_IsIicBusy( &xIicInstance ) == TRUE ) )
	{
		if( AbortCommunication == TRUE )
		{
			XIic_IntrGlobalDisable(xIicInstance.BaseAddress);
			Status = XIic_Stop( &xIicInstance );
			return XST_FAILURE;
		}
	}

	/* Read data */
	Status = XIic_MasterRecv( &xIicInstance, RecvBufferPtr, RecvByteCount );
	if(Status != XST_SUCCESS)
	{
		return XST_FAILURE;
	}

	/*
	 * Wait till data is received.
	 */
	while( ( ReceiveComplete == FALSE ) || ( XIic_IsIicBusy( &xIicInstance ) == TRUE ) )
	{
		if( AbortCommunication == TRUE )
		{
			XIic_IntrGlobalDisable(xIicInstance.BaseAddress);
			Status = XIic_Stop( &xIicInstance );
			return XST_FAILURE;
		}
	}

	Status = XIic_Stop( &xIicInstance );
	if(Status != XST_SUCCESS)
	{
		return XST_FAILURE;
	}

	return XST_SUCCESS;
}

/*****************************************************************************/
/**
* This send/receive handler is called asynchronously from an interrupt context
* and indicates that data in the specified buffer was received. The byte count
* should equal the byte count of the buffer if all the buffer was filled.
*
* @param	CallBackRef is a pointer to the IIC device driver instance which
*			the handler is being called for.
*
* @param	ByteCount indicates the number of bytes remaining to be received of
*			the requested byte count. A value of zero indicates all requested
*			bytes were received.
*
* @return	None.
*
* @notes	None.
*
****************************************************************************/
void vIicRecvISR( XIic *IicInstPtr, BaseType_t ByteCount )
{
	ReceiveComplete = TRUE;
}

void vIicSendISR( XIic *IicInstPtr, BaseType_t ByteCount )
{
	SendComplete = TRUE;
}

/*****************************************************************************/
/**
* This status handler is called asynchronously from an interrupt context and
* indicates that the conditions of the IIC bus changed. This  handler should
* not be called for the application unless an error occurs.
*
* @param	CallBackRef is a pointer to the IIC device driver instance which the
*			handler is being called for.
*
* @param	Status contains the status of the IIC bus which changed.
*
* @return	None.
*
* @notes	None.
*
****************************************************************************/
void vIicStatusISR( XIic *IicInstPtr, BaseType_t xEvent )
{
	AbortCommunication = TRUE;
}
/* EOF */

```

AXI-IICのデバイスや割込みの初期化は前回実装していたHardwara初期化タスクに追加しました。

```c:main.cにあるHardware初期化タスク
static void prvSetupHardwareTask( void *pvParameters )
{

	(void) pvParameters;

	BaseType_t xStatus;
	const TickType_t xBlockTime = pdMS_TO_TICKS(5);

	/* Interrupt Service Routine */
	extern void vSerialRecvISR( void *CallbackRef, UBaseType_t uxByteCount );
	extern void vIicRecvISR( XIic *IicInstPtr, BaseType_t ByteCount );
	extern void vIicSendISR( XIic *IicInstPtr, BaseType_t ByteCount );
	extern void vIicStatusISR( XIic *IicInstPtr, BaseType_t xEvent );

	for (;;)
	{
		/**************************************************
		 *  Initialize MicroBlaze GPIO
		 *************************************************/
		xStatus = XGpio_Initialize( &xGpioOutputInstance, XPAR_GPIO_0_DEVICE_ID );
		if ( xStatus == XST_SUCCESS )
		{
			XGpio_SetDataDirection( &xGpioOutputInstance, 0x01, 0x00 ); // channel1, 8-bit all output
		}

		/***************************************************
		 *  Initialize MicroBlaze UART-Lite IP
		 **************************************************/
		xStatus = XUartLite_Initialize( &xUartLiteInstance, XPAR_UARTLITE_0_DEVICE_ID );
		if (xStatus == XST_SUCCESS)
		{
			/* clear UART FIFOs */
			XUartLite_ResetFifos( &xUartLiteInstance );
		}

		XUartLite_SetRecvHandler( &xUartLiteInstance, (XUartLite_Handler) vSerialRecvISR, NULL );
		XUartLite_SetSendHandler( &xUartLiteInstance, (XUartLite_Handler) vSerialSendISR, NULL );
		XUartLite_EnableInterrupt( &xUartLiteInstance );

		/***************************************************
		 * 	Initialize MicroBlaze I2C IP
		 **************************************************/
		XIic_Initialize( &xIicInstance, XPAR_IIC_0_DEVICE_ID);
		XIic_SetRecvHandler( &xIicInstance, &xIicInstance, (XIic_Handler) vIicRecvISR );
		XIic_SetSendHandler( &xIicInstance, &xIicInstance, (XIic_Handler) vIicSendISR );
		XIic_SetStatusHandler( &xIicInstance, &xIicInstance, (XIic_StatusHandler) vIicStatusISR );

		/***************************************************
		 * 	Connect interrupt port
		 **************************************************/
		if (xStatus == XST_SUCCESS)
		{
			xStatus = xPortInstallInterruptHandler( XPAR_INTC_0_UARTLITE_0_VEC_ID, (XInterruptHandler)XUartLite_InterruptHandler, &xUartLiteInstance );
			if (xStatus != pdPASS)
			{
				xil_printf("UART Interrupt handler initialization failed\r\n");
			}

			xStatus = xPortInstallInterruptHandler( XPAR_INTC_0_IIC_0_VEC_ID, (XInterruptHandler)XIic_InterruptHandler, &xIicInstance );
			if (xStatus != pdPASS)
			{
				xil_printf("I2C Interrupt handler initialization failed\r\n");
			}
		}
		/* Enable UART Interrupt */
		vPortEnableInterrupt( XPAR_INTC_0_UARTLITE_0_VEC_ID );
		vPortEnableInterrupt( XPAR_INTC_0_IIC_0_VEC_ID );

		/* clear UART FIFOs */
		XUartLite_ResetFifos( &xUartLiteInstance );
		I2C_SoftReset();

		BaseType_t i = 0;

		vTaskPrioritySet( xSetupHardwareTask, tskIDLE_PRIORITY );

		while( startup_msg[i]){
			if ( xStreamBufferIsEmpty( xSerialSendBuffer ))
			{
				xStreamBufferSend( xSerialSendBuffer, startup_msg[i], strlen(startup_msg[i]), xBlockTime );
				++i;
			}
		}
		vTaskDelete( xSetupHardwareTask );
	}
}
```

# 実際にハマった箇所: Slave Address
OV5642のデータシートを眺めたところ、7項に

- Slave Addressは、レジスタWriteするときは0x78, Readするときは0x79を指定してね

との記載があります。「へー、WriteとReadでアドレスが違うのか、珍しい」などとぼんやりとしていましたが、これが認識が甘かったところで、この意味するところは **I2Cの仕様でいうところの7bit Slave Addressは0x3Cである。I2CバスではSlave Addressの後の8bit目がRead(↑High)/Write(↓Low)を示すが、このOV5642のデータシートではその1bitをLSBに含めた8bitとしてSlave Address (ID)を表現している**ということです。[OmniVisionのSCCBの仕様書](http://www4.cs.umanitoba.ca/~jacky/Teaching/Courses/74.795-LocalVision/ReadingList/ov-sccb.pdf)を読むと、通常I2CのSlave Addressのビットがある箇所を"ID"と表現していて8bitで記載していますね。分かると簡単なんですが、1時間ぐらい0x78からACKが返ってこないなーとオシロスコープと睨めっこしてしまいました。また、SCCBの9bit目はDon't careのためNACKが返ってきても無視しないとダメだっけ？と本質ではないところでも時間を浪費してしまいました。

# 注意点: Read制御の16bit Sub-Address
SCCBの仕様書を読むと、ReadはTwo-Phase(ID, DATA)のオペレーションが記載されていて、なおかつ、**事前にReadしたいレジスタのアドレス(Sub-Address)をWirteしておいてね**と注意書きがあります。また、SCCBの仕様書(古い…)では8bitのSub-Addressが前提で書かれていますが、OV5642は16bitのSub-Addressという点もややこしいです。まぁ普通に考えるとMSBから順に2byte送れば大丈夫だろうと予想をつけて実装したところ正常に動くようになりました。

# コンソールはこんな感じ
![mbz_console_0215.png](https://qiita-image-store.s3.amazonaws.com/0/207297/2ea6bf31-1a61-c0c8-890b-1b2ee911db54.png)

0x300Aから2Byte ReadしてチップIDが読めています。0x3200はグループID？だったかな。

# 最後に人の募集です
aptpod Inc.では**Internet of Behavior**の世界の実現のためメンバーを募集しています。高粒度時系列データ(Fast Data)を処理するためのセンサ/基板・回路/FPGA/DSP/SoC/組込みソフト(ベアメタル、RTOS、Linux)など低レイヤをはじめ、インフラ・フロントエンド・UX・モバイル、データ解析・機械学習などのエンジニア職や、ソリューションアーキテクト・セールスといったビジネス開発の役割まで広く募集中です。
https://www.wantedly.com/companies/aptpod

デバイス設計からクラウド、UXまでの全てのレイヤを50人ぐらいの規模感で作り込めるチームってなかなか無いですし、アプリケーションも車載をはじめ、ロボット、ドローン、農業機械、産業機械などなど実に多彩です。ちょっと気になるなー、という方はぜひ一度オフィスにお越しください。Twitterで私にご一報頂けると色々とスムーズにお話できるかと思います。よろしくお願いします！