目次
前
次
カメラユニット開発
2月24日の懇親会で出た話題を思い出し
高校生がMCR_VCに参加できるようにカメラ
OV7670とマイコンSTM32M3Discoveryを合体
したカメラユニットを開発することに。
OV7670は¥1950で入手できますし、STM32M3Discoveryは
¥950です。送料を含めると¥3000を超えますが、高校生
が入手できる範囲でしょう。
ファームウエアから利用することを考えるとAPI
(Application Program Interface)を用意して
おいた方が、ハードウエアやデバイスドライバ
を定義するためのハードルが低くなります。
6つのAPIを考えます。
- void init_camera(void);
- UBYTE get_line_sensor_data(UWORD line_number);
- UWORD get_line_width(UWORD line_number);
- UWORD get_line_center(UWORD line_number);
- UWORD get_line_zp(UWORD line_number);
- UWORD get_line_pz(UWORD line_number);
各APIの内容を定義します。
init_camera
カメラを初期化します。
パラメータはないですが、別に配列を定義して
その内容をOV7670にSCCBインタフェースで転送
します。
get_line_sensor_data
line_numberで指定するラインの8ビットデータを出力します。
8ビットデータは、1ラインにあるピクセルから、生成します。
詳細は、別に定義します。
OV7670は、VGAサイズの画像データを取得するので
line_numberの最大値は、479となります。
get_line_width
line_numberで指定するラインの、センターライン中央
の幅を出力します。
幅は、0あるいは正の整数で最大65535とします。
OV7670は、VGAサイズの画像データを取得するので
line_numberの最大値は、479となります。
get_line_center
line_numberで指定するラインの、センターライン中央
のピクセル位置を出力します。
ピクセル位置は、0あるいは正の整数で最大639となります。
存在しない場合、9999を返します。
OV7670は、VGAサイズの画像データを取得するので
line_numberの最大値は、479となります
get_line_zp
line_numberで指定するラインの情報を2値化し
0→1になる位置を出力します。
0あるいは正の整数で最大9999とします。
OV7670は、VGAサイズの画像データを取得するので
line_numberの最大値は、479となります。
ピクセル位置は、0から639になりますが、範囲外が
あるので9999を使います。
get_line_pz
line_numberで指定するラインの情報を2値化し
1→0になる位置を出力します。
0あるいは正の整数で最大9999とします。
OV7670は、VGAサイズの画像データを取得するので
line_numberの最大値は、479となります。
ピクセル位置は、0から639になりますが、範囲外が
あるので9999を使います。
ここまで考えて、秋月電子から基板を入手し
翼のごとく、拡張基板を半田付けします。
3.3V、5Vの電源が基板から供給されると
緑、赤のLEDが点灯します。
電源を供給すると、サークル状に配置された8個の
LEDが点滅していきます。これでハードウエアには
問題がないことがわかります。
開発環境
STM32F3Discoveryの開発環境を入手しなければ
ファームウエアを開発できません。入手可能な
開発環境を調べてみました。
開発環境は、以下の4種あるとか。
- IAR Embedded Workbench for ARM
- MDK-ARM Microcontroller Development Kit by Keil
- TASKING
- Atollic TrueStudio
評価版であるため、KeilとAtollicの場合
コンパイル、リンクできるコードサイズが
最大32kバイトに制限されています。
ADuC7026(Analog DevicesのARM)で、Keilの
開発環境を利用した経験から、これを入手し
使うことにしました。最大容量の制限までの
コードにはならないだろうと、期待して動作
を確認することに。
開発環境のダウンロード時に、氏名、所属企業、Email
アドレス等を入力しなければなりません。所属企業に
関しては、学校名でも大丈夫でした。
STM32F3DiscoveryのARMは、Cortex-Mシリーズなので
ダウンロード前にサポートされているかを確認です。
ダウンロードし、インストールすると以下のショート
カットができました。
ショートカットをクリックすると、KeilのIDEが
開きます。
GUIは、ADuC7026(Analog DevicesのARM)で使っている
Version3と同じで、MicrosoftのVC++、XilinxのISEと
似ていて、自分には慣れた環境です。
最初は、円形に配置されているLEDの点滅プログラム
を作成してみます。
プロジェクトを作成します。
ゼロからプロジェクトを作成するのは、面倒なので
Demoの中に入っていた拡張子がuvprojであるファイル
の名前と内容を変更して対応します。
Demo.uvprojからknigt.uvprojへと内容をそっくり
コピーしておきます。
プロジェクトを統括するプロジェクトファイルの
内容は、必要項目をタグを使い設定しています。
テキストエディタを利用して、次の項目を
修正します。
- TargetName = knight
- ToolsetNumber = 0x4
- TargetCommonOptionのDevice = STM32F303VC
- TargetCommonOptionのVendor = STMicroelectronics
- TargetCommonOptionのStartupFile = "Startup\ST\STM32F30x\startup_stm32f30x.s" ("STM32F30x Startup Code")
StartupFileが、指定ディレクトリに存在しないときは
インストールした開発環境の中にあるディレクトリから
startup_stm32f30x.sを探し出し、指定ディレクトリに
コピーします。
プロジェクトのディレクトリに、必要なファイルを
コピーします。
knightというプロジェクトには、ディレクトリknightと
そのサブディレクトリknightを用意します。
サブディレクトリknightには、stm32f3discovery_fw.zipの中にある
次のディレクトリ中のCソースコードとヘッダファイルをコピーします。
STM32F3-Discovery_FW_V1.1.0/Project/Demonstration
さらに次のディレクトリにあるCソースコードとヘッダファイルをコピーします。
STM32F3-Discovery_FW_V1.1.0/Libraries/STM32_USB-FS-Device_Driver
STM32F3-Discovery_FW_V1.1.0/Libraries/STM32F30x_StdPeriph_Driver
STM32F3-Discovery_FW_V1.1.0/Libraries/CMSIS/Device/ST/STM32F30x
STM32F3-Discovery_FW_V1.1.0/Utilities/STM32F3_Discovery
こうして準備したファイルのうち、main.cだけは「読み取り専用」の
属性チェックを外しておきます。
stm32f3discovery_fw.zipは、STMicroelectronicsのサイトから
ダウンロードできます。
すべての準備が整ったなら、次の手順でコンパイル、リンクして
HEX形式ファイルを作成します。
開発環境の起動
プロジェクト選択
メニューバー中の「Project」をクリック。
ディレクトリを移動し、対象となるプロジェクト
ファイルを探します。
プロジェクトファイルを開くと、左に次の
グループ分けした構成が表示されます。
コンパイル、リンク
メニューバーで、「Project→Build target」とクリック
します。
HEXファイルを生成するためには、次のように
メニューバーで、「Project→Options」と
クリックして、表示されるタブからOutputを
選んで、チェックを入れます。
LED点滅に利用したソースコードは、以下。
/* Includes ------------------------------------------------------------------*/
#include "main.h"
/* define data types */
typedef unsigned char UBYTE ;
/* Private variables ---------------------------------------------------------*/
RCC_ClocksTypeDef RCC_Clocks;
__IO uint32_t TimingDelay = 0;
__IO uint32_t UserButtonPressed = 0;
__IO uint8_t DataReady = 0;
__IO uint8_t PrevXferComplete = 1;
__IO uint32_t USBConnectTimeOut = 100;
#define CNTMAX 16
#define OFF 0
#define ON OFF+1
/* function prototype */
void init_leds(void) ;
void clear_leds(void) ;
void update_leds(UBYTE x) ;
int main(void)
{
UBYTE count ;
/* SysTick end of count event each 10ms */
RCC_GetClocksFreq(&RCC_Clocks);
SysTick_Config(RCC_Clocks.HCLK_Frequency / 100);
/* Initialize LEDs on STM32F3-Discovery board */
init_leds() ;
/* Infinite loop */
clear_leds() ;
count = 0 ;
/* endless loop */
while (ON)
{
/* impress */
update_leds(count) ;
/* increment */
count++ ;
/* judge */
if ( count == CNTMAX ) { count = 0 ; }
/* delay */
Delay(5) ;
}
}
/* set directions */
void init_leds(void)
{
STM_EVAL_LEDInit(LED3);
STM_EVAL_LEDInit(LED5);
STM_EVAL_LEDInit(LED7);
STM_EVAL_LEDInit(LED9);
STM_EVAL_LEDInit(LED10);
STM_EVAL_LEDInit(LED8);
STM_EVAL_LEDInit(LED6);
STM_EVAL_LEDInit(LED4);
}
/* turn off all LEDs */
void clear_leds(void)
{
STM_EVAL_LEDOff(LED3);
STM_EVAL_LEDOff(LED5);
STM_EVAL_LEDOff(LED7);
STM_EVAL_LEDOff(LED9);
STM_EVAL_LEDOff(LED10);
STM_EVAL_LEDOff(LED8);
STM_EVAL_LEDOff(LED6);
STM_EVAL_LEDOff(LED4);
}
void update_leds(UBYTE x)
{
/* turn off all LEDs */
clear_leds();
/* turn on target LED */
if ( x == 0 || x == 15 ) { STM_EVAL_LEDOn(LED3) ; }
if ( x == 1 || x == 14 ) { STM_EVAL_LEDOn(LED5) ; }
if ( x == 2 || x == 13 ) { STM_EVAL_LEDOn(LED7) ; }
if ( x == 3 || x == 12 ) { STM_EVAL_LEDOn(LED9) ; }
if ( x == 4 || x == 11 ) { STM_EVAL_LEDOn(LED10); }
if ( x == 5 || x == 10 ) { STM_EVAL_LEDOn(LED8) ; }
if ( x == 6 || x == 9 ) { STM_EVAL_LEDOn(LED6) ; }
if ( x == 7 || x == 8 ) { STM_EVAL_LEDOn(LED4) ; }
}
/**
* @brief Inserts a delay time.
* @param nTime: specifies the delay time length, in 10 ms.
* @retval None
*/
void Delay(__IO uint32_t nTime)
{
TimingDelay = nTime;
while(TimingDelay != 0);
}
/**
* @brief Decrements the TimingDelay variable.
* @param None
* @retval None
*/
void TimingDelay_Decrement(void)
{
if (TimingDelay != 0x00)
{
TimingDelay--;
}
}
/**
* @brief Basic management of the timeout situation.
* @param None.
* @retval None.
*/
uint32_t LSM303DLHC_TIMEOUT_UserCallback(void)
{
return 0;
}
/**
* @brief Basic management of the timeout situation.
* @param None.
* @retval None.
*/
uint32_t L3GD20_TIMEOUT_UserCallback(void)
{
return 0;
}
#ifdef USE_FULL_ASSERT
/**
* @brief Reports the name of the source file and the source line number
* where the assert_param error has occurred.
* @param file: pointer to the source file name
* @param line: assert_param error line source number
* @retval None
*/
void assert_failed(uint8_t* file, uint32_t line)
{
/* User can add his own implementation to report the file name and line number,
ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
/* Infinite loop */
while (1)
{
}
}
#endif
ライブラリにある関数を利用しているので、オブジェクト
指向の記述になっています。また、デバッグ用処理コード
が入っているので、少し冗長になっています。
基板にダウンロードし、動作確認します。
ST-LINK Utility
STMicroelectronicsのサイトから、次のZip形式ファイル
をダウンロードし、展開後、インストールします。
st-link_v2_usbdriver.zip
インストールすると、デスクトップに次のアイコンが
できます。
アイコンのダブルクリックで、次のGUIが開きます。
ボードと接続
USBケーブルを接続し、リンクするには
ソケットアイコンをクリックします。
リンク接続に成功すると、諸元を表示します。
HEXファイル指定
メニューバーのキャビネットアイコンを
クリックします。
ディレクトリをたどって、目的のHEXファイル
を探します。
HEXファイルをクリックすると、ST-LINK
の環境にロードされます。
ファイル転送
メニューバーから「Target→Program...」をクリック。
ダウンロード動作画面が現れたなら、「start」をクリック。
ステータスで、ダウンロードの成功、失敗を確認できます。
これで、STM32F3Discovery基板上のLEDが次のように
点滅を繰り返すことを確認できます。
リンク切断には、メニューバーの「disconnect」アイコンを
クリックします。
カメラとの接続
環境操作がわかったなら、早速、コンパイル、リンクして
となりそうですが、OV7670をどこに接続するか決めてから
半田付けになります。
STM32F3Discoveryのチップは、STM32F303VCBT6と
型番がついたいます。
このチップにはピンが100本あります。
ポートはPA、PB、PC、PD、PE、PFでした。
PAからPEが各16ピン、PFが8ピンになっています。
ワンチップマイコンでは、兼用ピンになっている
ことが多いので、GPIO(General Purpose Input and
Output)で利用できるポートを、データシートから
拾ってみると、PDの16ピンをまるまる使えそうです。
OV7670は、16ピンのコネクタで接続するので電源を
除いて、14ピンあれば使えます。16ピンを用意して
おけば、大丈夫だろうと判断しました。
動作周波数は、最大72MHzとなっていますが、OV7670
と同期を取りながら処理するには、タイマー割込み
を多用するのが自分流。タイマーに関しての調査を
しました。
タイマーは、全部で13あります。この中で12個の
タイマーは16ビット、ひとつだけが32ビットでした。
PWM波形生成、外部信号キャプチャに使うには、充分
な個数のタイマーを持っていると言えるでしょう。
他の周辺モジュールに関してもデータシートでみると
12ビットのA/Dコンバータ、12ビットのD/Aコンバータ
DMACがあります。DMACを使うと、内蔵SRAMにデータを
入れながら、他の処理もという並列動作ができそう。
OV7670とSTM32F3Discoveryを接続するために
ピンアサインを決めます。
16ピンの割当てを次のようにしてみます。
- PD15 no connection
- PD14 no connection
- PD13 SIC(SCL) // output
- PD12 SID(SDA) // output
- PD11 VSYNC // input
- PD10 HREF // input
- PD9 PCLK // input
- PD8 CLK // output
- PD7 D7 // input
- PD6 D6 // input
- PD5 D5 // input
- PD4 D4 // input
- PD3 D3 // input
- PD2 D2 // input
- PD1 D1 // input
- PD0 D0 // input
CLKは20MHzほどが必要です。
タイマー割込みを利用するのは、面倒なので
24.576MHzのクリスタルを発振させ、ここに
接続します。
また、PCLKはOV7670が出力してくるので
一度設定がうまくいけば、不要になるので
接続なしとします。
もう一度、ピン割当てを見直し、次のように
しました。
- PD15 no connection
- PD14 no connection
- PD13 no connection
- PD12 no connection
- PD11 SIC(SCL) // output
- PD10 SID(SDA) // output
- PD9 VSYNC // input
- PD8 HREF // input
- PD7 D7 // input
- PD6 D6 // input
- PD5 D5 // input
- PD4 D4 // input
- PD3 D3 // input
- PD2 D2 // input
- PD1 D1 // input
- PD0 D0 // input
ピン割当てを確定したので、インタフェース
回路を考え、次のようにしました。
実際に半田付けする段になると、拡張コネクタは
不規則な配置になっていたので、次のように変更
しました。
- PF10 SIC(SCL) // output
- PF9 SID(SDA) // output
- PE0 VSYNC // input
- PC13 HREF // input
- PD7 D7 // input
- PD6 D6 // input
- PD5 D5 // input
- PD4 D4 // input
- PD3 D3 // input
- PD2 D2 // input
- PD1 D1 // input
- PD0 D0 // input
PE0は、外部割込みピンに割当てられて
いるので、VSYNCの変化を捕らえて同期
処理するのによいと判断しました。
この変更で、接続は次のようにしました。
基板に必要な部品を接続すると、以下の様に。
ハードウエアが出来たので、3レイヤーで処理
コードを考えていきます。
(under construction)
目次
前
次