日 | 月 | 火 | 水 | 木 | 金 | 土 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 |
« 2011年11月 | トップページ | 2012年1月 »
ロボット作ろう:シェーキー製作記
【後日追加】このプログラムの設定ではまれにAD変換値が非常に大きな値になることがあります。この対策はこちらの投稿を見てください。
AD変換をテストします。アプリケーションで変換を行うのではなく、このマイコンの特徴でもあるDMAで独立に行うようにします。今回は3つあるAD変換機のなかで一番シンプルそうな、ADC3を利用し、12bit・2チャンネルのAD変換を行ってみます。
ブログにコードを貼り付けると非常に読みにくいので、下記main.cをエディタで開いて読んでください。
今回もDISCOVERYのサンプルコードを元に実験しました。PC1,PC2の電圧を読みようになっています。
テストは2チャンネルですが、基板に何も乗っていなければ8チャンネルまでは拡張できます。ただ、このボードでは他の部品が接続されているので5チャンネルまでのようです。
実験は、このようにブレッドボードに半固定抵抗を2個取り付け、各チャンネルの可変電圧源として使用しました。
プログラムをデバッガで実行し、一時停止して下記のようにグローバル変数(配列)の中身を読みます。(これはデバッガ画面の右上部分を抜き出したものです)
グローバル変数を見るためには、右上のめがねのようなものが書いてあるボタンをクリックすると表示可能なグローバルの一覧が表示されるので、見たいものにチェックを入れます。
半固定抵抗をまわしながら、何回か実行・一時停止を行って、そのときの値を確認しました。問題なしです。
AD変換速度ですが、サンプルコードのReadMeによると次の式で計算されるそうです。
(12 + サンプリングタイム) / (APB2クロック / プリスケーラー分周比)
サンプリングタイムはサンプルアンドホールドにかけるクロック数で、各チャンネルごとに決めます。127、128行の3番目のパラメータがそれです。これは信号源のインピーダンスや静電容量によって決める必要があると思いますが、今回はサンプルコードのままです。
APB2クロックはシステムクロック168MHzの半分で84MHz、分周比は109行で2(ADC_Prescaler_Div2)に設定しています。
これで計算すると、(12 + 3) / (84 / 2) = 0.35us(チャンネル当たり) となります。あとDMAの時間が幾許か必要なのかどうかわかりませんが、ともあれ僕が使うには十分すぎるほどの速度です。
もちろんこれはAD変換をハード的に行う速度で、アプリケーションソフトはこの変換結果を任意のタイミングで参照する訳ですから、実際のサンプリングレートはこれに依存します。
ソースコードで気になることが一つ。32行でADC3_DR_ADDRESSとしてADC3のデータレジスタのアドレスを定義していますが、これはどこかですでに定義済みのマクロがないものでしょうか?探したのですが、見つかりませんでした。この部分はサンプルプログラムそのままなので、案外ないのかも知れません。データシートから目的のレジスタのアドレスを計算するのは結構厄介です。
ともあれ、これでADも使えます。単純なロボット制御の構成要素はあらかたテストできたようです。
ロボット作ろう:シェーキー製作記
今回はPWM機能を使ってラジコンサーボを動かしてみます。
これはSTM32F4-Discovery_FW_V1.1.0のProject→Peripheral_ExamplesのTIM_PWM_Outputというサンプルプログラムを調整・整理しただけです。TIMER3のPWMチャネルを4つ使い、4台のサーボを直接駆動できるようになっています。
各サーボのパルス幅は下記のようにservopos1-4というグローバル変数を定義しておき、初期値として4500を入れておきます。これでパルス幅が約1500usになります。
uint16_t servopos1 = 4500;
uint16_t servopos2 = 4500;
uint16_t servopos3 = 4500;
uint16_t servopos4 = 4500;
まず、GPIOポートの初期化です。これはTIM3のPWM出力を有効にするものですが、1-2チャネルと3-4チャネルが別のポートに割り当てになっているため、それぞれ同じような処理をするので長いコードになっています。
void TIM_Port_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
//TIM3にクロック供給開始
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
//GPIOCとGPIOBにクロック供給開始
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC | RCC_AHB1Periph_GPIOB, ENABLE);
//GPIOCのPin6とPin7をオルタネィテブファンクション(OUT)に設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOC, &GPIO_InitStructure);
//GPIOBのPin0とPin1をオルタネィテブファンクション(OUT)に設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ;
GPIO_Init(GPIOB, &GPIO_InitStructure);
//設定したピンをTIM3の出力として設定
GPIO_PinAFConfig(GPIOC, GPIO_PinSource6, GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOC, GPIO_PinSource7, GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOB, GPIO_PinSource1, GPIO_AF_TIM3);
}
次が肝心カナメのTIM3の設定です。この設定は繰返し周期20ms、PWMの分解能0.3333usに設定します。したがって、サーボのパルス幅を 800us(左最大)-1500us(中立)-2200us(右最大)としたとき、PWMの設定値は2400-4500-6600となります。角度やパーセンテージなどお望みの単位系からこの値に変換する関数を作れば、簡単に使えますね。設定は次のようになりました。
void TIM_Param_Config(){
//タイムベースの設定
TIM_TimeBaseStructure.TIM_Period = 0xe8ff;
TIM_TimeBaseStructure.TIM_Prescaler = 8;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//PWM1 Modeの設定・チャネル1
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = servopos1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
//PWM1 Modeの設定・チャネル2
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = servopos2;
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
//PWM1 Modeの設定・チャネル3
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = servopos3;
TIM_OC3Init(TIM3, &TIM_OCInitStructure);
TIM_OC3PreloadConfig(TIM3, TIM_OCPreload_Enable);
//PWM1 Modeの設定・チャネル4
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = servopos4;
TIM_OC4Init(TIM3, &TIM_OCInitStructure);
TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_ARRPreloadConfig(TIM3, ENABLE);
//TIM3を有効化
TIM_Cmd(TIM3, ENABLE);
}
実はこの設定、マニュアルを読まずにオシロで波形を見ながらトライアルアンドエラーで値を決めました。いただきものの吟醸酒をお供に、30分ほどで設定値を決めることができました。快適です。やっぱりわれわれアマチュアにオシロは必需品だなあと再認識した次第です。
テストはチャネル1(PC6)に接続したサーボを、約1秒おきにニュートラルと約45度の位置へ交互に動かして行いました。そのmain関数がこれです。ここで呼んでいるmyLongDelayは、以前の時間待ち関数の定数を4000000にして1秒ほど待たせるものです。
int main(void)
{
//タイマー用I/Oの設定
TIM_Port_Config();
//タイマーのパラメータを設定
TIM_Param_Config();
//チャネル1のサーボを動かす
while (1)
{
//チャネル1(PC6)のサーボをニュートラル(1500us)から333us動かす
//分解能は0.3333us。↓では0.3333 * 1000 = 333.3us
servopos1 += 1000;
TIM_OCInitStructure.TIM_Pulse = servopos1;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
myLongDelay(); //約1秒待つ
//ニュートラルに戻す
servopos1 -= 1000;
TIM_OCInitStructure.TIM_Pulse = servopos1;
TIM_OC1Init(TIM3, &TIM_OCInitStructure);
myLongDelay(); //約1秒待つ
}
}
ほかのチャネルも同様に値を設定すればサーボを動かすことができます。今回は4つまでです。ほかのタイマーを同様に設定すればもっと数を増やせそうですが、ヒューマノイドロボットを作るわけではないので、今回は検討しません。シェーキーでは頭のパン・チルトだけなのでサーボは二つしか使いません。
今回はこの写真のように、電池ボックスの乾電池から6Vを供給したラジコンサーボの信号に、 3.3V系のSTM32F4を直接つないで動かしました。サーボの信号スレショルドが何ボルトかわかりませんが、GWSのサーボはこれで動くようです。サーボによっては5V近くの電圧が必要になるかもしれません。その場合は5V系の電圧を設け、ポート設定をオープンドレインにして10Kくらいで5Vへプルアップすればよいでしょう。
GPIO、シリアル、タイマー割り込み、サーボ、と試しましたので、次はADですね。これをテストすればロボットの制御に使えそうです。
【後日追記】初期化構造体などをグローバルにとっているので、この説明だけではわかりにくいですね。下のmain.c(他のファイルと重複しないようファイル名はservo_main.cになっていますが、プロジェクトのmain.cファイルです)を見てもらえば、もっとわかりやすいと思います。
「servo_main.c」をダウンロードロボット作ろう:シェーキー製作記
割り込みの使い方がわかってきたところで、以前、ポーリングで実装したシリアル通信のエコーバックを、割り込みを使った実戦的な形で実装してみます。
割り込みを使うので、stm32f4xx_it.cにハンドラを追加します。これは雛形には用意されていません。
void USART2_IRQHandler(void)
{
uint16_t i;
//送信終了(今回は使っていない)
if(USART_GetITStatus(USART2, USART_IT_TXE) == SET)
{
}
//受信終了
if(USART_GetITStatus(USART2, USART_IT_RXNE) == SET)
{
i=USART_ReceiveData(USART2); //受信したデータ読み込み
USART_SendData(USART2, i); //そのデータを送信
}
}
送信終了や1キャラクタ受信などの割り込み要因を設定しておくと、それらが発生したときにこのハンドラが実行されます。上の例では、送信終了と、受信終了(1キャラクタ受信)への対応が記述されています。ただし、送信終了のアクションは、このプログラムでは使用しないので空になっています。
さて、main.cのmain関数はこうです。
int main(void)
{
//GPIOAとUSART2にクロック供給
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
//グローバル割り込み初期化の構造体を作る
NVIC_InitTypeDef NVIC_InitStructure;
//USART2のグローバル割り込みを設定する
NVIC_InitStructure.NVIC_IRQChannel = USART2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//GPIO初期化用構造体を作る
GPIO_InitTypeDef GPIO_InitStructure;
//GPIOAのPIN2をオルタネイテブファンクションで出力に設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIOAのPIN3をオルタネイテブファンクションに設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIOAのPIN2とPIN3をオルタネィテブファンクションのUSART2に割り当て
GPIO_PinAFConfig(GPIOA , GPIO_PinSource2 , GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA , GPIO_PinSource3 , GPIO_AF_USART2);
//USART初期化用構造体を作る
USART_InitTypeDef USART_InitStructure;
//USART2を9600bps,8bit,ストップビット1,パリティなし,フロー制御なし,送受信有効に設定
USART_InitStructure.USART_BaudRate = 9600*3;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStructure);
//受信割り込みを設定する
USART_ITConfig(USART2, USART_IT_RXNE, ENABLE);
//USART2を有効化
USART_Cmd(USART2, ENABLE);
//無限ループ(ダイナミックストップ)
while (1)
{
}
}
割り込みの設定は2箇所でやっています。
まず最初のほう、「USART2のグローバル割り込みを設定する」でグローバル割り込みコントロールに、USART2の割り込みを許可するよう設定します。
次に、最後のほう「受信割り込みを設定する」のあと1行で、USART2の割り込み設定に受信割り込みを設定します。
たったコレだけで、割り込み設定ができました。ややこしいレジスタのビット操作なしなので助かります。一時的に割り込み禁止にしたいときは、USART_ITConfig(USART2, USART_IT_RXNE, DISABLE);とやればよいようです。
次はタイマーを使ってラジコンサーボを動かしてみたいと思います。ようやくロボットのブログらしくなってきました。
ロボット作ろう:シェーキー製作記
一番基本的な割り込みをテストしましょう。SysTickタイマを使った周期割り込みです。
割り込みハンドラはstm32f4xx_it.cに記述します。すでにDISCOVERYで使いそうなものは記述済みです。
SysTick_Handler(void)というのが、SysTick割り込みのハンドラになります。
ここに青いLEDをトグルするプログラムを書きます。
//割り込みハンドラ stm32f4xx_it.cに記述
void SysTick_Handler(void)
{
//青いLEDをトグルする
if(GPIO_ReadOutputDataBit(GPIOD, GPIO_Pin_15)==Bit_SET){
GPIO_ResetBits(GPIOD,GPIO_Pin_15);
}
else{
GPIO_SetBits(GPIOD,GPIO_Pin_15);
}
}
実行するたびに点灯→消灯→点灯・・ と切り替わるプログラムですね。この割り込みを周期的に発生させればいいわけですね。
割り込みを設定はmain.cに書きます。こんな感じです。
int main(void)
{
//PORTDの初期設定(LEDを使用するため)
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 |GPIO_Pin_13 | GPIO_Pin_14 |GPIO_Pin_15;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOD, &GPIO_InitStructure);
//PORTDの初期値設定(LEDをすべて消灯)
GPIO_ResetBits(GPIOD,GPIO_Pin_12 |GPIO_Pin_13 | GPIO_Pin_14 |GPIO_Pin_15);
//SysTickによる割り込みの初期設定はたったコレだけ
//10800000で約200ms周期、5370で約100us周期
if (SysTick_Config(10800000)) //systickによる割り込み周期を設定
{
//初期設定に失敗したら赤色LEDを点灯!
GPIO_SetBits(GPIOD,GPIO_Pin_14);
}
//ダミーの無限ループ
while (1){
}
}
ほとんどLEDの初期設定で、SysTickの割り込み設定はSysTick_Config(10800000)だけです。これはif文とセットで使用するようで、このようにしないとエラーになります。もちろんifの中身は何でもよく、何も書かなくともOKです。
これを実行すると200ms周期で青色LEDが点滅します。これは割り込みで実行されているので、無限ループ内に別のプログラムを入れて、見かけ上二つのプログラムが同時に走っているようにできます。
ついでにクロックの設定がどうなっているかも調べました。下記のコードを初期化部分に入れ、プログラムを一時停止して構造体RCC_ClockFreqの中身をデバッガで確認しました。
RCC_ClocksTypeDef RCC_ClockFreq;
RCC_GetClocksFreq(&RCC_ClockFreq);
やってみると構造体の中身はこうなっています。
SYSCLK_Frequency 168000000
HCLK_Frequency 168000000
PCLK1_Frequency 42000000
PCLK2_Frequency 84000000
startupあたりで最高速度に設定されているようです。あやふやですが。
ロボット作ろう:シェーキー製作記
送信がうまくいったので、次は受信したキャラクタをそのまま送信するエコーバックをプログラムしてみます。
写真のように装置をセットアップしました。隣の基板がRS232Cへのレベルコンバータです。このICを使い、単三電池4本を内蔵し、電源電圧を3.3Vと約5V(電池の電圧)に切り替えて使えるようになっています。もう5,6年も前に製作したものですが、電源供給もできるので重宝しています。でも、今ならこれの方がいいですよね。
これを経由して
(USART2のTXD)PA2 → パソコンのCOMポートのRXD
(USART2のRXD)PA3 → パソコンのCOMポートのTXD
と、なるように接続します。
プログラムはこうなりました。
例によって、ブログに貼り付けるとインデントが崩れてしまうので読みにくいですが。
int main(void)
{
uint16_t i;
//GPIOAとUSART2にクロック供給
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
//GPIO初期化用構造体を作る
GPIO_InitTypeDef GPIO_InitStructure;
//GPIOAのPIN2をオルタネイテブファンクションで出力に設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIOAのPIN3をオルタネイテブファンクションに設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIOAのPIN2とPIN3をオルタネィテブファンクションのUSART2に割り当て
GPIO_PinAFConfig(GPIOA , GPIO_PinSource2 , GPIO_AF_USART2);
GPIO_PinAFConfig(GPIOA , GPIO_PinSource3 , GPIO_AF_USART2);
//USART初期化用構造体を作る
USART_InitTypeDef USART_InitStructure;
//USART2を9600bps,8bit,ストップビット1,パリティなし,フロー制御なし,送受信有効に設定
USART_InitStructure.USART_BaudRate = 9600*3;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStructure);
//USART2を有効化
USART_Cmd(USART2, ENABLE);
//無限ループを回りながら、受信フラグをチェック。受信したらエコーバック
while (1)
{
if(USART_GetFlagStatus(USART2, USART_FLAG_RXNE)==SET){
//データ受信あり
USART_ClearFlag(USART2, USART_FLAG_RXNE); //フラグをリセット
i=USART_ReceiveData(USART2); //受信したデータ読み込み
USART_SendData(USART2, i); //そのデータを送信
}
}
}
TeraTermを9600bpsに設定しキーを叩くと叩いた文字がエコーバックされます。
このプログラムは割り込みを使わずにポーリングで処理しています。本来は割り込みで処理したいところですね。
というわけで、次はSTM32F4の割り込みをテストしてみようとおもいます。
ロボット作ろう:シェーキー製作記
ちょっと順番が狂ってしまいました。この続きはひとつ前の投稿になります。作成したプログラムを実行するには、ボードをパソコンにUSBケーブルで接続した上で、ツールバーの虫のボタンをクリックします。虫っぽいボタンがいくつかありますが、ピンクの○で囲んだ一番左の虫です。
ソースコードに変更があれば、保存するかどうか聞いたうえで、再ビルドを自動的にやってくれます。ただし、新規作成したプロジェクトを初めてコンパイルするとき、ビルド設定確認の大きなダイアログが開きますが、これは"OK"をクリックで問題ありません。次からは表示されません。
それからもうひとつ。僕のようにコメントに日本語を書くと、保存やビルドの時に、文字コードの確認のためのダイアログが開きます。これは"UTF-8"のボタンをクリックすれば、以降現れません。この開発環境はいろいろ神経質なところがありますが、売り物なので致し方ないところでしょう。信頼性第一ですからね。
ソースコードにエラーがなければ次のようなデバッガが表示されます。
1・リセットボタン: ボードをリセットした状態で停止させます
2・実行ボタン: 現在のアドレスからプログラムを実行します
3・一時停止ボタン: 実行中のプログラムをその部分で中断します。ソースコード表示に実行中の行がハイライトされます。
4・デバッガ終了ボタン: デバッガを抜け、メイン画面に戻ります。
実行するには実行ボタンをクリックします。
ボード上のユーザーボタンを押すと、押している間オレンジのLEDが点滅するはずです。めでたしめでたしというわけです。
さて、Lite版のデバッガでは次の機能がサポートされています。
1・ブレークポイントをひとつだけ設定可能
・ブレークしたいソースコードの行番号をダブルクリック
・実行ボタンで実行
・その行を実行したら停止
という風に使います。ブレークポイントは一つだけしか設定できないので、別のブレークポイントを設定したいときは、右上のフィールドのBreakpointsタブをクリックして、表示されているブレークポイントを右クリック、Removeで削除する必要があります。
2・変数(グローバル変数も)の読み取り
プログラムの停止中は、右上のフィールドのVariablesタブをクリックすると停止位置のローカル変数の中身が見えます。グローバル変数も見たいときは、右上の小さなめがねのようなアイコンをクリックすると、見たいグローバル変数を追加することができます。
まあ、こんだけですが、小さなプログラムには十分ではないかとおもいます。なんたって設備投資が2.1K円ですから。そうそう、最近秋月でもSTM32F4 DISCOVERYを販売し始めましたね。しかも1.65k円とは!
次はシリアルポートのテストに挑戦です。
ロボット作ろう:シェーキー製作記
ちょっと順番が狂ってしまいました。ひとつ後の投稿を先に読んでください。
GPIOを使う目処は立ったので、次はシリアル通信をテストします。PCと通信が出来れば、変数をリポートさせたりすることで機能デバッグがやり易くなります。場合によってはデバッガよりずっと便利です。
ここでやっている機能チェックは、このページのDesign supportタブの下の方にあるFARMWAREをダウンロードし、Periphral_Examplsフォルダの中のサンプルコードを参考にしてコードを書いています。残念ながらUARTによるシリアル通信のサンプルはありません。USBもEtherも付けてやったんだからそっちでやれよというわけではないでしょうが、裏道を行ってるなあという感慨ひとしおです。トホホ。
気を取り直して作ったプログラムがこれです。USART2を使って9600bpsでAを約260ms周期で送信し続けます。先ずはこの辺から確認しましょう。
int main(void)
{
//GPIOAとUSART2にクロック供給
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART2, ENABLE);
//GPIO初期化用構造体を作る
GPIO_InitTypeDef GPIO_InitStructure;
//GPIOAのPIN2を出力に設定
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 ;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
//GPIOAのPIN2をオルタネィテブファンクションのUSART2に割り当て
GPIO_PinAFConfig(GPIOA , GPIO_PinSource2 , GPIO_AF_USART2);
//USART初期化用構造体を作る
USART_InitTypeDef USART_InitStructure;
//USART2を9600bps,8bit,ストップビット1,パリティなし,フロー制御なし,送受信有効に設定
USART_InitStructure.USART_BaudRate = 9600*3;
USART_InitStructure.USART_WordLength = USART_WordLength_8b;
USART_InitStructure.USART_StopBits = USART_StopBits_1;
USART_InitStructure.USART_Parity = USART_Parity_No;
USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;
USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;
USART_Init(USART2, &USART_InitStructure);
//USART2を有効化
USART_Cmd(USART2, ENABLE);
//Aを繰り返し送信
while (1)
{
USART_SendData(USART2, 'A');
myDelay();
}
}
UARTを設定する前に当該ポートも初期化します。今回はTXDだけを使用しています。そのため出力に設定しています。時間待ち関数myDeley()は前と同じです。
ボーレートの設定が変ですね。最初普通に9600を設定し、オシロで測定したところ、3倍のボーレートになっていました。そこで、このように目的の3倍の値を設定することで調整しています。
後日調べてみたらここの投稿が目に留まりました。ヘッダファイルを修正すればいいようですが、まだ、正式にクロック周りを設定していないので、これは後回しに。
レベル変換機をPA2に入れてパソコンに接続、無事にボードからの送信を確認しました。次回は送受信をやってみます。
ロボット作ろう:シェーキー製作記
いよいよプログラムです。今回はまず、基板上のユーザーボタンを押している間オレンジのLEDを点滅させるプログラムを作ってみます。
GPIOを使う前に、本来ならばクロックの初期設定が必要ですが、今回はこれを後回しにします。どのマイコンでもリセット後の初期状態でマイコンが動作しないということはありませんので、下手にいじって動かなくなっては大変、とにかく確実に動く状態でテストしようというわけです。そのため、最高速度で動いているのかどうかはわかりません。
STMのマイコンのペリフェラルプログラムは、普通のマイコンとはちょっと違います。PICなどではレジスタに直接値を書き込んで設定を変更しますが、基本的にこのマイコンは定義済みの関数を使って設定やデータのやり取りを行います。もちろん、レジスタに直接パラメータを書き込むことも出来ますが、プログラムの見通しを良くし、バグが入り難くするためにこのようになっているようです。
従って、どんな関数があるかを知らないと、とてもやり難いことになります。
ペリフェラル関係のヘッダファイルはLibrariesの中のSTM32F4xx_StdPeriph_Driverの中にあります。TrueSTUDIOのメイン画面のProject Explorerからアクセスしてみてください。
このフォルダの中のincを開くとたくさんのヘッダファイルがあります。GPIO関係のヘッダファイルはstm32f4xx_gpio.hです。コレを覗いてみると、ずらりと#defineが並び、最後のほうにいくつか関数が定義されています。これがGPIOを操作する関数です。
さて、ともあれプログラムを作ってみました。main関数はこんな風になりました。(インデントが崩れて見にくいですが)
int main(void)
{
//GPIO初期化################################
//GPIO初期化のための構造体GPIO_InitStructureを作る
GPIO_InitTypeDef GPIO_InitStructure;
//PORTAの設定開始(INPUT)---------------------------------
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
//初期化用構造体にパラメータをセットしていくため、いったん初期値に戻す
GPIO_StructInit(&GPIO_InitStructure);
//設定するピンを指定する(スイッチのピン・アクティブハイ)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
//指定したピンを入力に指定する
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
//プルアップを使用しない
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//GPIOのスピードを100MHz(最高速)にセットする
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
//PORTA設定入力終了。これでGPIOAを設定する
GPIO_Init(GPIOA, &GPIO_InitStructure);
//PORTAの設定終了----------------------------------------
//PORTDの設定開始(OUTPUT)--------------------------------
//PORTDにクロックの供給を開始
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOD, ENABLE);
//初期化用構造体にパラメータをセットしていくため、いったん初期値に戻す
GPIO_StructInit(&GPIO_InitStructure);
//設定するピンを指定する(4つのLED)
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_12 |GPIO_Pin_13 | GPIO_Pin_14 |GPIO_Pin_15;
//指定したピンを出力に指定する
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_OUT;
//出力ポートのタイプをプッシュプルに指定する
GPIO_InitStructure.GPIO_OType = GPIO_OType_PP;
//プルアップを使用しない
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
//GPIOのスピードを100MHz(最高速)にセットする
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
//PORTD設定入力終了。これでGPIODを設定する
GPIO_Init(GPIOD, &GPIO_InitStructure);
//PORTDの初期値設定(LEDをすべて消灯)
GPIO_ResetBits(GPIOD,GPIO_Pin_12);
GPIO_ResetBits(GPIOD,GPIO_Pin_13);
GPIO_ResetBits(GPIOD,GPIO_Pin_14);
GPIO_ResetBits(GPIOD,GPIO_Pin_15);
//PORTDの設定終了----------------------------------------
//ここからプログラム本体################################
//ボタンを押している間LEDを点滅する無限ループ
while (1)
{
if(GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0)){
//ボタンが押されて1になっていたら以下を実行
GPIO_SetBits(GPIOD,GPIO_Pin_13); //LED点灯
myDelay();
GPIO_ResetBits(GPIOD,GPIO_Pin_13); //LED消灯
myDelay();
}
}
}
詳しくは後ほど解説したいと思いますが、ポートAとポートDを初期設定するのはこんなもんだという見本です。時間遅延関数myDelay()は下記のようになっています。百万回ループをまわしています。
//時間遅延関数
void myDelay(){
uint32_t ii;
ii=0;
//1,000,000回ループを回る
while(ii<1000000){
ii++;
}
}
このプログラムはユーザーボタンを押している間、約260mS(オシロで計測)ごとに点灯・消灯を繰り返します。ビルドとダウンロードは次回投稿します。
ロボット作ろう:シェーキー製作記
ポートの構成がわかったところで、新規プロジェクトを作成してみましょう。
以下、TrueSTUDIOでの新規プロジェクトの製作法です。プロジェクトは現状のワークスペースに作られます。
1・FileメニューからNewをプルダウン、C Projectを選択
2・プロジェクト設定ダイアログ
下記のようなダイアログが開きます。プロジェクト名と構成を入力します。
Project Nameにプロジェクト名を英文で入力。Project TypeのEnbedded C Projectをクリックして選択。Nextをクリックします。
3・ビルド設定ダイアログ
下記のようなダイアログが開きます。ビルドの設定を入力します。
Evalution BoardからSTM32F4 DISCOVERYを選択。Floating pointは浮動小数点演算をソフトでやるかハードでやるかを選ぶものです。今回はSoftware implementationでソフトを選択しましたが、本来はハードを選択するべきのようです。こっちのほうが高速のはずですからね。
設定したらNextをクリック
4・デバッグツールの選択ダイアログ
下記の画面が開くのでST-LINKを選択。FINISHで終了です。
これで新規のプロジェクトができました。メイン画面左側のProject Explorerでプロジェクト名を開き、srcフォルダの下のmain.cをダブルクリックして開くと、main関数をはじめ、必要なものはすべて出来上がっているのがわかると思います。
次回はこれをタネにしてプログラムを作成します。
日曜日に大岡山の東工大で開催されていたMTM07へ行ってきました。
思ったより人出が多く、学園祭並みの混雑ぶり。この写真をどうぞ。
これは一番大きな会場、体育館の様子です。すごいですね。
いろいろ面白いものがありましたが、これは僕好み。ペットボトルの蓋に組込んだマイクロロボットです。
外側の振動モーターで移動します。なんらかのセンサが組込まれているようですし、個体間の通信も出来るようです。数があるので眺めていても面白いです。出展者の青年にもっと詳しく聞いてくればよかったですね。
それからこれ。カラークラシックの箱に組込んだコミュニケーションロボットだそうです。
仕様はよくわかりませんが、いい感じですね。マックのアイコンがそのままロボットになったみたい。ただ残念ながらカラクラで制御しているわけでは無いようです。
それから写真を撮るのをわすれましたが、アクエストのブースでアクエストークの新情報が。組込み用にはいままでこれを使うしかありませんでしたが、28pinDIPの新デバイスをデモしてました。1K円でサンプルを販売してましたが、既に売り切れ。近日中にリビジョンアップして販売をする予定だそうです。これはアマチュアにとってはとても使い易いデバイスなので期待大です。
ここに詳しい記事があります。
お土産はこれ
ミュージックシンセサイザのペーパーモデル。世界初の音の出ないミュージックシンセサイザだそうです。1.5K円でした。
ロボット作ろう:シェーキー製作記
開発環境がインストールできたところで、いよいよプログラミングです。本来なら、デモソフトをビルドして加速度センサやらオーディオ機能(なんといってもDSP内蔵ですから)を試してみるところですが・・ 僕はこれを「安くて早い制御マイコンボード」として使いたいので、これはパスします。本筋はやらない、メーカーの思う壺にはまるもんか、潔いが裏道人生ではありますね。♪なにかぁらぁ なにまぁで 真っ暗闇よぉ~という鶴田浩二の「傷だらけの人生」が脳裏に流れます。トホホ。
さて、気を取り直して先へ進みましょう。
まずは、もっとも単純なGPIOを試してみることにします。ボードのポート構成を調べなければなりません。ボードのマニュアルはここにあります。これでGPIOの空きを調べてみます。
STM32のGPIOは16ビットです。豪華ですね。空いているのは
■PORTA PA1-3、PA8、PA15
■PORTB PB0-2、PB4-5、PB7-8、PB11-15
■PORTC PC1-PC3、PC4-6、PC8-9、PC13-15(注)
■PORTD PD0-3、PD6-11
■PORTE PE3-15
(注)とあるのは、リアルタイムクロック用の32KHzの水晶(未実装)と共用のポートがあり、実装しないならコレだけ使える、という意味です。水晶を載せる場合はPC13だけが空きポートになります。
そのほかのポートはオーディオDAC、モーションセンサ、USBなどでふさがっています。
それから、我々下々が使えるデバイスとしては、下記が実装されています。
■押しボタン(アクティブロー) PA0
【後日訂正:押しボタン(ユーザーボタン)は回路でプルダウンされており、アクティブハイです】
■緑LED PD12
■オレンジLED PD13
■赤LED PD14
■青LED PD15
ありがたい限りです。この辺を使ってGPIOをテストしてみようと思います。