こんにちは、TMNTです。
今回はnucleof446reでDCMIを使ってOV7670から画像取得に成功したので、そのことについて書いていこうと思います。

___事の発端______________

11月16日、私はET展に行きました。

そこでこんなものを貰いました。
CxbYAM4UcAAxZYx

中身を色々読んでみると、今使っている(nucleo)f446reにDCMIが搭載されているとのことでした。
性格上、あまりデータシートを読まないのでこのことを私は恥ずべきことに存じ上げず、f401reで行っていたDMA等を使う方法をそのままf446reに用いていました。
GPIO自体の入力を読み込む仕様のため、同GPIO上の上位8ピンは未使用でなければならないというのがこの手法の最大のネックでした。
しかし、DCMIを用いればこのような事態を解決できます。


ということで、
今回はnucleof446reでDCMIを使ってOV7670から画像取得してみます。

___開発環境とかその他諸々___________

毎回同じですが開発環境はmbed,
使用するものは
NUCLEO f446re
OV7670
ILI9341(取得画像確認用)
です

___データシートを読んでみよう!__________

まずはDCMIの対応ピンが一体どこなのか知る必要がありますね!
とういうことでまずはf446のデータシートをみてみます。

f446のデータシート

こちらに載っているpinmapを見てみると
2016-11-20 (1)
2016-11-20 (2)
2016-11-20 (3)


のように対応していることがわかります。
DCMIはデータ幅を(8~12bit)まで対応しています。
今回使用するカメラモジュールOV7670は

データ幅は8bitなので、
D0~D7、
HSYNC(水平同期信号)
VSYNC(垂直同期信号)
PCLK(画素毎のクロック)
の対応ピンを決めてやればいいと思います。

今回私は、以下のように決めました。

PA_4 - HSYNC
PA_6 - PCLK
PB_9 - D7
PB_8 - D6
PB_7 - VSYNC
PB_6 - D5
PC_11 - D4
PC_9 - D3
PC_8 - D2
PC_7 - D1
PC_6 - D0


___GPIOIの初期化________

使用する対応ピンが決まったら、とりあえずGPIOの初期設定をしましょう。

上の通りで初期化するのをHALで書くとこんな感じになります。

  GPIO_InitTypeDef dcmipinsa,dcmipinsb,dcmipinsc;

    dcmipinsa.Pin = GPIO_PIN_4|GPIO_PIN_6;
    dcmipinsa.Mode = GPIO_MODE_AF_PP;
    dcmipinsa.Pull = GPIO_PULLDOWN;
    dcmipinsa.Speed = GPIO_SPEED_HIGH;
    dcmipinsa.Alternate =  GPIO_AF13_DCMI;
    HAL_GPIO_Init(GPIOA, &dcmipinsa);
   
    __GPIOB_CLK_ENABLE();
    dcmipinsb.Pin = GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9;
    dcmipinsb.Mode = GPIO_MODE_AF_PP;
    dcmipinsb.Pull = GPIO_PULLDOWN;
    dcmipinsb.Speed = GPIO_SPEED_HIGH;
    dcmipinsb.Alternate =  GPIO_AF13_DCMI;
    HAL_GPIO_Init(GPIOB, &dcmipinsb);
   
    __GPIOC_CLK_ENABLE();
    dcmipinsc.Pin = GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_11;
    dcmipinsc.Mode = GPIO_MODE_AF_PP;
    dcmipinsc.Pull = GPIO_PULLDOWN;
    dcmipinsc.Speed = GPIO_SPEED_HIGH;
    dcmipinsc.Alternate =  GPIO_AF13_DCMI;
    HAL_GPIO_Init(GPIOC, &dcmipinsc);

__DMAの初期化__________

次にDMAの初期設定です。
こちらのリファレンスマニュアルを読んでみると
2016-11-20 (4)
DMA2のstream1_channel1がDCMIに対応していることがわかります。
これをもとに、HALで書くとこんな感じになります。

  DMA_HandleTypeDef    dcmi_Dma;
  __DMA2_CLK_ENABLE();
    dcmi_Dma.Instance = DMA2_Stream1;
    dcmi_Dma.Init.Channel = DMA_CHANNEL_1;
    dcmi_Dma.Init.Direction = DMA_PERIPH_TO_MEMORY;
    dcmi_Dma.Init.PeriphInc = DMA_PINC_DISABLE;
    dcmi_Dma.Init.MemInc = DMA_MINC_ENABLE;
    dcmi_Dma.Init.PeriphDataAlignment = DMA_PDATAALIGN_WORD; 
    dcmi_Dma.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD ;
    dcmi_Dma.Init.Mode =   DMA_CIRCULAR;
    dcmi_Dma.Init.Priority = DMA_PRIORITY_HIGH;
    dcmi_Dma.Init.FIFOMode = DMA_FIFOMODE_ENABLE;
    dcmi_Dma.Init.FIFOThreshold =DMA_FIFO_THRESHOLD_FULL;
    dcmi_Dma.Init.MemBurst = DMA_PBURST_SINGLE;
    dcmi_Dma.Init.PeriphBurst = DMA_PBURST_SINGLE;
    HAL_DMA_Init(&dcmi_Dma);


__DCMIの初期化_____

  最後にメインのDCMIの初期設定です。
  こちらはHALのマニュアルや海外makerさんの記事を参考に適当に書いてみました。
  
  DCMI_HandleTypeDef  dcmi;

  __DCMI_CLK_ENABLE();
    dcmi.Instance = DCMI;
    dcmi.Init.ExtendedDataMode = DCMI_EXTEND_DATA_8B;
    dcmi.Init.CaptureRate = DCMI_CR_ALL_FRAME;  
    dcmi.Init.VSPolarity = DCMI_VSPOLARITY_HIGH; 
    dcmi.Init.HSPolarity = DCMI_HSPOLARITY_LOW;          
    dcmi.Init.PCKPolarity = DCMI_PCKPOLARITY_RISING;       /
    dcmi.Init.SynchroMode = DCMI_SYNCHRO_HARDWARE;    
    dcmi.DMA_Handle = &dcmi_Dma;
    HAL_DCMI_Init(&dcmi);
     __HAL_DCMI_ENABLE(&dcmi);


__その他設定___________
  OV7670は入力クロック(XCLK)を必要とします。
なので、MCO出力ピンを設定します。

     GPIO_InitTypeDef GPIO_InitStruct;
   
  /*Configure GPIO pin : PA8 MCO1 for cam XCLK*/ 
    HAL_RCC_MCOConfig(RCC_MCO1, RCC_MCO1SOURCE_HSE, RCC_MCODIV_1);  
    __GPIOA_CLK_ENABLE();
    GPIO_InitStruct.Pin = GPIO_PIN_8;   
    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;   
    GPIO_InitStruct.Pull = GPIO_NOPULL;   
    GPIO_InitStruct.Speed = GPIO_SPEED_LOW;   
    GPIO_InitStruct.Alternate = GPIO_AF0_MCO;   
    HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

また転送先のメモリとして適当なサイズ(この場合は145x150x2)の二次元配列を宣言します。
  
  uint8_t frame_buffer[145][300];

またSCCB通信、DMAを使用したSPIでili9341に
画像取得を表示していきます。(詳しくは前回の記事)


__DCMIを使う_________

初期設定が終わったら早速DCMIの恩恵を授かりましょう。

DCMIを動作させるにはHAL_DCMI_Start_DMA()を実行させましょう。

HAL_DCMI_Start_DMAの引数は

(DCMI_HandleTypeDef * hdcmi, uint32_t DCMI_Mode, uint32_t pData, uint32_t Length)
HAL_DCMI_Start_DMA(&dcmi, DCMI_MODE_CONTINUOUS, (uint32_t)&frame_buffer,43500/2);
となっております

* hdcmiにはそのままDCMIで初期化したdcmiのアドレス&dcmi,
DCMI_Modeにはスナップショットモードとコンティニューモードが選べますが、今回はフレームレートも一応確認したいのでコンティニューモードのDCMI_MODE_CONTINUOUSを選択します。
pDataには転送先の二次元配列,
Lengthには1フレーム文の転送回数を代入します。例えば今回は145x300でRGB565なので、145x300/2

__動作確認________________


こんな感じで動きました。いやぁ、HALってちょっと難しいですね!
他にも
DCMIには
CROP機能と呼ばれるものがあり、画像をリサイズできるそうです。
こちらも暇なときにまた試してみようと思います。

__ソースコード____________________
mbed  : https://developer.mbed.org/users/tmnt/code/NUCLEO-F446RE_testDCMI/
github : 
随時公開