2018年3月4日日曜日

CubeMXでプロジェクトの作り方

 久しぶりにCubeMXで新規プロジェクトを作ったらとっても簡単だったのでメモ。

 ターゲット:STBee F4mini
 目標:Lチカ他

 コマンドラインでビルドするので、arm-none-eabiその他必要なものはあらかじめ入れておくこと(Win10ならWSLのUbuntuでaptから入れれる。古いけど)。



 New ProjectでMCU選択画面を出す。
 Part Number SearchでMCUを入力する。STBee F4miniはSTM32F405RGが乗ってるので、それを入力する。
 そうするとMCUs Listに出てくるので、ダブルクリックで選択する。

***

 メニューのProject→Settingsで設定画面を開く。


 Project NameとProject Locationを選択する。Toolchain / IDEをMakefileにする。
 Project NameとLocationは最初に設定すると変更できないのでたいぽとかしないように。CubeMXのプロジェクトをコピーすれば使い回しができるけど、Project Nameにtemplateと入れてしまったのはやっちゃったパターン。ここは後から変更ができないから、ある程度潰しの効く名前にしておく必要がある。

 それと、Code GeneratorタブでGenerate peripheral initialization as a pair of '.c/cf' files per peripheralにチェックを入れておく。
 あとはOkで設定を閉じる。

***
 

 左のPeripheralsからRCCを開き、High Speed ClockとLow Speed ClockをCrystalにしておく。今回LSEは使わないけど、誤ってPC14/PC15をGPIOとして使って水晶を破壊しないようにするため、設定しておく。

*** 



 Clock Configurationタブを開く。
 Input frequencyをボードの水晶の値(12MHz)に設定する。PLL Source MuxをHSEに設定し、/Mを12にする。*Nを336に設定する。System Clock MuxをPLLCLKに設定する。APB1 Prescalerを4に、APB2 Prescalerを2に設定する。
 赤いところがなければOK。HCLK to AHB bus, core, memory and DMAが168になっていれば大体OK。

***

 次にGPIOの設定を行う。オンボードのLEDとスイッチを使いたい。LEDはPD2に、SWはPA0に接続されている。


 Pinoutタブに戻り、左下のPA0をクリックしてGPIO_Inputを選択し、右クリックでEnter User Labelを選択する。テキストボックスに"USER_SW"と入力する。
 同じように右上のPD2を、GPIO_Outputに設定し、USER_LEDとする。
 また、左のツリーからMiddleWaresのFREERTOSを開き、Enabledにチェックを入れる

***

 Configurationタブを開き、青字に白でMiddlewaresと書いてあるところの、FREERTOSと書いてあるボタンをクリックする。


 Task and Queueというタブを開く(最初に開くとこのタブのはず)。中段のAddボタンを押して、New Task画面を出す。
 Task NameとEnter Functionに適当な名前を設定し、PriorityにosPriorityBelowNormalを設定する。
 New TaskをOKを閉じ、FREERTOS Configuration画面もOkで閉じる。

***

 メニューのProject→Generate Codeをクリックする。Warning: Code Generationというメッセージが出てきて、When FreeRTOS is used, it is(以下略 と表示されるが、とりあえず気にせずYesを押す。ちなみにこれはいつまでたっても出て来る。
 プログレスバーが進んでThe Code is successfuly(以下略 というダイアログが出たら、Closeで閉じる。
 さっき設定したLocationの中にNameのフォルダができてるはず。

 以降はソースコード類の編集作業になる。

***

 とりあえず、Makefileを開いて$(BINPATH)/$(PREFIX)gccみたいな行を探す。この$(BINARY)の後ろにあるスラッシュが邪魔なので、取り除く。
 また、下の方にLDFLAGS = $(MCU) -space=nano.space (以下略 という行があるが、この内-space=nano.spaceを除去する。これは開発環境による。ウチの環境では消したほうがうまくいく。特にprintfでのfloatの表示可否に関係する部分。

 とりあえずMakefileの修正はこの2点。
 これでビルドが通るようになるので、makeを実行する。うまくビルドできるとbuildフォルダにProject Nameで設定した名前の.binとか.hexとか.mapが生成される。
 ただ、まだLチカとか書いてないので、このバイナリを焼いても特に何も起きない。

***

 いよいよLチカのコードを書く。
 Srcフォルダのfreertos.cを開き、LED_blink_funcという関数を探す。
 for(;;)の中にosDelay(1);とだけ書いてあるはずなので、以下の2行に置き換える。
HAL_GPIO_TogglePin(USER_LED_GPIO_Port, USER_LED_Pin);
osDelay(HAL_GPIO_ReadPin(USER_SW_GPIO_Port, USER_SW_Pin) ? 100 : 200);

 それと、上の方に戻り、USER CODE BEGIN Includesというところが有るので、その下に#include "stm32f4xx.h"を書き加える。
 ちなみに、USER CODE BEGIN hogeとUSER CODE END hogeの間に書かれたコード以外は、CubeのGenerate Codeで削除される。基本的にBEGINとENDの間に全て書くようにする。

 とりあえず、この2点の変更をしたら、ファイルを保存して再びビルドする。
 これでLチカのバイナリができたので、何らかの方法でSTBee F4miniに書き込む。そしてリセットすると、LEDが点滅するはず。また、スイッチを押している間は点滅が早くなるはず。

 ということで、以上でLチカは終了。

***

 次はUSBを使ってシリアルポートを開くようにする。

 Cubeに戻って、PinoutからUSB_OTG_FSを開き、ModeにDevice_Onlyを設定する。MiddleWaresのUSB_DEVICEにあるClass For FS IPにCommunication Device Classを設定する。
 Clock Configurationタブを開くとダイアログが出てくるはずなので、とりあえずNoで閉じる。
 Main PLLブロックの、/Qが赤くなっているはずなので、これを7に設定する。右側の48MHz clocksが48になっていて、赤くなければOK。

 Configurationタブで再びFREERTOSの設定を開く。Task and Queueタブで、defaultTaskをダブルクリックする。これの名前を変更する。とりあえず名前はUSB_CDC_stdoutにした。同じようにEntry FunctionもUSB_CDC_stdout_funcにした。

 各ダイアログを閉じて、Generate Codeで生成する。

***

 再びfreertos.cを開き、USB_CDC_stdout_func関数に移動する。
 MX_USB_Device_Init()というのが追加されてるはず。

 とりあえず、for(;;)の中のosDelay(1)を以下の3行に変更する。
osDelay(500);
uint8_t str[] = "hello USB CDC!\n";
CDC_Transmit_FS(str, sizeof(str));

 再びUSER CODE BEGIN Includeに移動し、#include "usbd_cdc_if.h"を追加する。

 またビルドし、書き込む。リセットすると、OSが何らかのUSBデバイスを検知したと音を出すはず。ドライバのインストールが正常に行われれば、新しいCOMポートが追加されるので、teratermとかでそれを開く。
 そうすると、1秒毎に先程設定した文字列が表示されるはず。

 ということで、めでたくUSB CDCで文字を表示できるようになった。

***

 しかし、いちいちCDC_Transmit_FSを呼ぶのは面倒だし、あらかじめ決めておいた文字列しか表示できないのは使い勝手が悪い。ということで、printfを使えるようにする。


 CubeでFREERTOSの設定画面を開き、こんどは下の方に有るAddを押す。するとNew Queueダイアログが出てくるので、NameやSizeを設定する。

 続いて、上のLED_blinkをダブルクリックし、Edit Taskを表示する。Stack Sizeは128になっているはずだが、これを大きくする。とりあえず1024にした。

 そしてGenerate Codeで生成。

***

 またまたfreertos.cを開く。
 USB_CDC_stdout_funcのforの中身を、以下のように書き換える。
uint8_t ch = 0;
xQueueReceive(stdout_queueHandle, &ch, portMAX_DELAY);
CDC_Transmit_FS(&ch, 1);
osDelay(1);

 LED_blink_funcのforの前でint counter = 0;で変数を宣言し、forの中でprintf("%d\n", counter++);のようにしてprintfで変数を出力する。

 下の方のUSER CODE BEGIN Applicationの中に以下のコードを追加する。
_ssize_t _write_r(
  struct _reent *r,
  int file,
  const void *ptr,
  size_t len)
{
  uint8_t *p = (uint8_t *)ptr;
  int i = 0;

  if (__get_IPSR() == 0)
  {
    for (i = 0; i < len; i++)
    {
      xQueueSend(stdout_queueHandle, p++, portMAX_DELAY);
    }
  }
  else
  {
    for (i = 0; i < len; i++)
    {
      xQueueSendFromISR(stdout_queueHandle, p++, 0);
    }
  }

  return (i);
}

 これでビルドして実行すると、ターミナルにカウンタが表示される。スイッチを押せば、表示頻度が高くなる。

 これでprintfが使えるようになった。整数や浮動小数点の表示もできる。
 ちなみに、上記ではCDC_Transmit_FSは1バイトだけ転送しているが、この方法では最大でも1秒間に1000文字しか転送できない。しかしこれを改善しようとするとコード量が増えてしまうので、今回は扱わないことにした。気が向いたら別のエントリで書くかも。

***

 printfがあるのだから、scanfとかも使いたい。ということで、再びCubeへ。
 New Queueでstdin_queueを作る。Queue SizeとItem Sizeはstdout_queueと同様。
 New Taskで新しいタスクを作る。とりあえずNameはconsoleとでもしておき、Entery Functionはconsole_funcとしておいた。Stack Sizeは1024にする。PriorityはosPriorityLowとした。

 再びジェネレートッ!

***

 またまたfreertos.cに戻ってまいります。

 とりあえず#include "string.h"を追加する。

 さきほどの_write_r関数の下に、以下のコードを追加する。
_ssize_t _read_r(
  struct _reent *r,
  int file,
  void *ptr,
  size_t len)
{
  uint8_t *p = (uint8_t *)ptr;
  uint8_t data;
  int i;

  for (i = 0; i < len;)
  {
    xQueueReceive(stdin_queueHandle, &data, portMAX_DELAY);
    p[i++] = data;

    if (data == '\n')
    {
        break;
    }
  }

  return (i);
}

 
 console_funcのforの中身を以下のようにする。
char str[100];
scanf("%99[^\n]%*[^\n]", str);
getchar();

if(!strcmp(str, "hoge"))
{
  printf("piyo!\n");
}

if(!strncmp(str,"sum ", 4))
{
  int a,b;
  if(2==sscanf(str+4,"%d %d", &a,&b))
  {
    printf("result:%d\n", a+b);
  }
}

 あとついでなので、さっき追加したint counterとprintfは削除しておく。

***

 次にusbd_cdc_if.cを開く。
 CDC_Receive_FS関数のreturnの前に以下のコードを追加する。
extern osMessageQId stdin_queueHandle;
uint32_t i = 0;
for (i = 0; i < *Len; i++)
{
  xQueueSendFromISR(stdin_queueHandle, &Buf[i], 0);
}

 それと上の方に#include "cmsis_os.h"を追加する。


 これでターミナルに入力した文字を取り出せるようになった。ということでビルドして書き込んでみよう。
 ターミナルで"sum 1 2"と入力すれば"result:3"と表示される。hugaと挨拶すれば、piyoと返事をしてくれる。
 なお、sumは動くがhugaが動かない、という場合はターミナルソフトの改行コードを確認してほしい。今回のプログラムはLFでしか動作しない。

***

 さて、最初はLEDが点滅するだけだったが、いよいよ挨拶ができるようになったし、電卓としても使えるようになった。

 前述の通り、送信速度はそれほど早くないし、改行コードの問題も残っている。しかしこれらを修正するのはさほど大変な作業ではない。


 今まではUSARTの初期化とかが面倒だったので、昔作ったプロジェクトを使いまわしていたのだけど、ライブラリが古いまま使われ続ける、という問題が有った。
 それと昔のCubeではMakefileやリンカスクリプトは自分で用意する必要があったはず。
 だけどその必要がなくなったなら、その都度プロジェクトを作り直すのもありだなぁと思い始めた次第。


追記:2018/03/05
 [fix]_write_rのポインタの扱いを修正
 [update]_write_rで割り込みから呼ばれている場合はFromISRを使うように変更

 割り込みからprintfなんて使わねぇよ、と思ってFromISRには対応してなかったんだけど、CubeのUSBデバッグ出力がISRからprintfを使っていたので、それに対応。


追記:2018/03/05
 上記の手順に従って、STBee(STM32F103VE)も試してみた。ちゃんとUSB CDCとかも使えた。
 ただしSTBeeはUSBのプルアップを内蔵していないので、PD4をLOWにしてプルアップを有効にする必要がある。これはUSB_CDC_stdout_funcのループの前でWritePinでUSBを有効にするか、あるいは最初からLOWで初期化するか。
 それとリンカスクリプト(STM32F103VETx_FLASH.ld)のMEMORYブロックでFLASHの行を FLASH (rx)      : ORIGIN = 0x8000000 + 12k, LENGTH = 512K - 12k のように書き換える必要がある。これはSTBeeの先頭12KiBはUSB DFUのプログラムが入っているため。またmain.cのUSER CODE BEGIN SysInitで SCB->VTOR = 0x08003000; として割り込みベクタを変更する必要もある。

 STBeeってF4miniより数百円高い割にクロックが低かったり、RAMやROMが少ないけど、GPIOの数は魅力的だよなぁ。

 ちなみにROMは68kほど、RAMは18kほど使っているらしい。ROMは400k、RAMは46kほど使える。
 F4の方は、ROM70kほど、RAM30kほどを使っている(USB Host MSCも含む)。残りはROM954k、RAM162kくらいか。

0 件のコメント:

コメントを投稿