Mac & Blue PillでSTM32duinoを使うためにブートローダーを書き込む

STM32 で Arduino が使用できることが分かり、STM32 HAL Library からArduino に鞍替え(出戻り)。
まずはブートローダーを書き込む手順を備忘録として残します。

Blue Pill の購入

Aliexpress で STM32F103C8T6 が搭載された、Blue Pill と呼ばれるマイコンボードを購入しました(Blue Pillのクローンだと思う)。
本体価格が194円、送料込みで242円だった。
安い!

他に必要なモノ

  1. USBシリアル変換基板
    ブートローダーを書き込むのに必要。
    こちらも Aliexpress では200円で売っていました。
  2. ワイヤージャンパー
    Blue Pill と USBシリアル変換基板を接続するために使用します。
  3. Microsoft Windows が実行できる環境
    ブートローダーの書き込みにあたり使用します。
    ※WEBを確認する限りではMacでもブートローダーの書き込みができるようです
    が、当方の環境では書き込みできませんでした。
    VirtualBoxなどの仮想環境上で動作する Microsoft Windows でも大丈夫です。

使用するソフトウェア類のダウンロード

ブートローダー

STM32duino-bootloader から、ハードウェアに応じたファイルをダウンロードします。
当方が購入した Blue Pill では generic_boot20_pc13.bin で動作しました。

STM32 Flash loader demonstrator

ブートローダーを書き込むのに使用します。
STM32 Flash loader demonstrator からダウンロードします。

Blue Pill の抵抗の交換

Blue Pill の基板の裏側にある R10 の抵抗を確認します。
本来1.5KΩである必要がありますが、10KΩ(103)となっている場合は交換します。

書き込み回路

ブートローダーを書き込むために、下記のように Blue Pill と USBシリアルを接続します。

デフォルトではBOOT0, 1のジャンパーピンが左側に接続されていますが、BOOT1を右側に接続します。

■デフォルトの接続状態

■ブートローダー書き込み時

ブートローダーの書き込み

STM32 Flash loader demonstrator を起動します。
Port Name に関し、USBシリアルに割り当てられているポート名を選択します。
Nextボタンを押下します。

Nextボタンを押下します。

Target が「STM32F1_Med-density_64K」が選択されていますが、「STM32F1_Med-density_128K」を選択してみます。
エラーにならないようであれば、そのまま Nextボタンを押下します。
※エラーになるようでいれば「STM32F1_Med-density_64K」に戻して Nextボタンを押下します。

Download to device を選択します。
Download from file 右側にあるボタンを押下してダウンロードしたブートローダー(generic_boot20_pc13.bin)を指定したら、Nextボタンを押下します。

期待通り動作すると、 Download operation finished successfully と表示されるので Closeボタンを押下します。

 

STM32/開発は一時停止…

残念なことに、これまで続けていたSTM32L0によるGPSロガーの開発は中断しなければならなくなった。非常に悔しい思い。

理由はかなりシンプル。国内だけど駐在しなければならなくなったということ。状況としてはGPSロガーとして必要となるSTM32の機能の使い方がわかり、いったんブレッドボード上にハードを組み上げ、ソフトを追加していたところで起きた出来事だった。

ソフトウェアを作り上げていくことで気づいた点を幾つかあげてみる。

  1. AVRマイコン + Arduinoの構成と比較し、STM32 + HAL Driverよりもコードのサイズが大きい(!?)
分析が必要だが、以前作ったAVRマイコン + Arduinoライブラリよりもコードサイズが大きくなった。ソフトウェアの構造を変えたからなのか?それともMPUのコード効率の差なのか?それぞれのライブラリの大きさの差に起因するのか等、もう少し調べなければ分からないですが、少し意外だった。
  2. STM32シリーズにより使用できる機能に差異がある点に注意
この点をについては有識者にとっては当たり前なのかもしれないけど当方にとってはちょっとした「落とし穴」だった。EEPROMやCANの有無くらいかと思っていたが、SDIOの有無についても差があったとは!急遽SDカードへのアクセスはSPI経由で行うように変更したのだった。
  3. MPUの処理性能はAVRマイコンよりも凄そう
STM32L0の初期状態におけるMPUのクロック周波数が2MHzなのだが、この状態でも115200bpsで送受信を行ってもデータの取りこぼしがなかった。
  4. ソースコードデバッグができる環境は当たり前だが便利
当方の技術不足と言ってしまえばそれまでなのだが、AVRマイコンではMac上でソースコードデバッグ環境をEclipse上で構築できなかったが、STM32では比較的容易に構築できた。AVRもWindows上ならAtmel Studioなる統合環境がありソースコードデバッグができるのだがエミュレータ環境は使いたくないし…
  5. STM32/HAL Driverに関する日本語の情報が少ない
最も苦労したのがこの点。Arduinoではこの点は非常に充実している。STM32の場合、「Standard Peripheral Library」から「HAL Driver」に変更したことにより、更に情報が少なくなってしまった。この2つにライブラリの類似点はあるのだが、完全には一致しない。加えてSTM32ファミリーによっては機能に互換性がない部分もあるようで、HAL Driverの仕様が若干異なっている。当方はADCではまってしまった。STM32L0と比較的情報が多いSTM32F0では「Rank」等、若干差異があるようだ。
  6. HAL Driverは一部使いにくい
使用した機能のなかではUSARTやADCは同期I/Fは使いにくかかった。はっきり言って非同期I/F以外は使い物にならなかった。同期I/Fの場合、ADCは複数のチャンネルを扱えないし、USARTは頻繁にエラー発生により送受信できなくなるし…
  7. ユニバーサル基板上に実装しにくい…
STM32にはDiscoveryやNucleoという格安の評価基板が売られているのだがいかんせん大きい。STM32はDIPパッケージはないし、SSOPはピン数が少ない。QFPパッケージならピン数は多いが半田付けしにくいし変換基板を使うと大きくなるし…個人で扱いやすいパッケージを作って欲しいものです。

/STM32/STM32L0のADCで、複数チャンネルの入力でハマる

毎度痛感させられるのが、STM32のHAL Driver及びSTM32CubeMXに関する情報の少なさとそれに伴う苦労の多さ。
ホント、参ってしまいます。

更に、HAL DriverはSTM32のハード(あるいはレジスタの設定周り)をうまく隠蔽化して開発者の負担を下げるわけではなく、それは更にその上位レイヤで行う方針だろうということ。
それ故なのか、はたまた単なる設計ミスなのか、(あるいは当方の理解不足なのか)非常に使いにくい部分がある。
USARTがそうだったが、ADCもその傾向があるようだ。

前回1チャンネル分の動作についてサンプル実装と確認を行ったが、その時には何ら苦労はなかった。
今回アナログジョイスティックを購入したので早速試してみたところ、結構はまってしまった。

STM32CubeMXを使用したのだが、もしかしたらコード生成に不具合があるのかもしれない。

試しに以下のようにPB0, PB1 にADCを設定してみた。

Pinout

ADC Configuration は以下のような感じ。

Parameter Settings

DMA Settings にて DMA Channel1 を割り当て。

DMA Settings

NVIC Settings は以下のとおり。

NVIC Settings

これによりアナログ入力を定期的に行い、ADCの初期化時に指定した計測対象のチャンネルが全て計測できたらコールバック
HAL_ADC_ConvCpltCallback() が呼び出される。

注意する点としては、1チャンネルのみ使用する場合はDMAを使用せずとも計測値が得られたが、複数チャンネルの場合はDMAを使用しなければならないとのこと。
確かにSTM32L0のレジスタをみてもめぼしいものは見つからない。

以上からDMAを使用したコードを書いて動作確認を行ったのだが、どうしても1チャンネル分しか取得できなかった。

コードは以下のような感じ。

    :
uint32_t g_ADCValue;
int g_MeasurementNumber;
enum{ ADC_BUFFER_LENGTH = 1024 };
uint16_t g_ADCBuffer[ADC_BUFFER_LENGTH];
    :
int main(void)
{

  /* USER CODE BEGIN 1 */
  uint32_t adcValue = 0;
  uint32_t volt = 0;
  ADC_ChannelConfTypeDef sConfig;
  /* USER CODE END 1 */
    :
  memset(g_ADCBuffer, 0, sizeof(g_ADCBuffer));
  HAL_ADC_Start_DMA(&hadc, g_ADCBuffer, ADC_BUFFER_LENGTH);
    :
}

    :
    :
void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef* AdcHandle)
{
  printf("adc[0]:%04x, adc[1]:%04x\n", g_ADCBuffer[0], g_ADCBuffer[1]);
}

ジョイスティックをX/Y方向に倒しても、X方向のみ反映されるという状態に。

生成されたコードを含めて眺めてみる。
すると、不可解なコードが・・・

void MX_ADC_Init(void)
{

  ADC_ChannelConfTypeDef sConfig;
        :
    /**Configure for the selected ADC regular channel to be converted. 
    */
  sConfig.Channel = ADC_CHANNEL_9;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  HAL_ADC_ConfigChannel(&hadc, &sConfig);
}

ADC_IN8/ADC_IN9 の設定を行ったはずなのだが、インスタンスhadcに設定されるのは ADC_CHANNEL_9 のみとなっていた。

STM32のレジスタを確認すると、計測対象にするチャンネルを計測順序という形式で指定でき、同時に変換するチャンネル数を指定するようになっていた。

Reference

試しに以下のように ADC_CHANNEL_8 も設定してみる。

  sConfig.Channel = ADC_CHANNEL_8|ADC_CHANNEL_9;
  sConfig.Rank = ADC_RANK_CHANNEL_NUMBER;
  HAL_ADC_ConfigChannel(&hadc, &sConfig);

これにより、DMA転送先となる g_ADCBuffer には、X, Y方向の値が交互に格納されることを確認した。

別途HAL Driverのソースを確認する必要があると思われる。
が、力尽きたので今日はここまで。

STM32/USARTを使ってみる

STM32に搭載されているUSARTの機能を使ってみます。
ここまで出来れば、作りたいGPSロガーができそうです。

STM32CubeMX

USART1 を有効にします。
プロジェクトを読み込みます。
Peripherals >> USART1 にある ModeAsynchronous を選択します。

Chips

Configuration タブに移動し、 USART1 をクリックします。

Connectivity

Parameter Settings タブを選択します。 Baud Rate9600 Bits/s に、 Word Length8Bits (including Parity) に設定します。

Parameter Settings

ここまで設定したら、コードを生成します。

ハードウェアについて

Macと接続して通信するためには USBシリアル変換基板 が必要です。
FT232RL USBシリアル変換モジュールキット: 半導体 秋月電子通商 電子部品 ネット通販 あたりが無難と思われます。入手が容易だし安いです。電子工作をされている方でUSBシリアルが必要でこちらを選択している人は多そうです。
なお、当方は今回、過去大阪の日本橋にある共立電子で購入したものを使用しました。
電子ホビー – 赤フグ株式会社 – FTDI Adapter ←こちらだと思います。どのUSBシリアル変換基板を使用するとしても3.3Vに設定しましょう。

Microsoft Windowsの合はデバイスドライバが必要ですが、LinuxやMacの場合は不要です。
/dev/tty.usbserial-◯◯ などというファイル名で見えます。

Eclipse

Eclipseから以下のようなコードを記述します。受信した情報を送信するという単純なものです。

int main(void)
{

  /* USER CODE BEGIN 1 */
  HAL_StatusTypeDef stat;
  uint8_t c;
  /* USER CODE END 1 */
    :
    :
  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
    stat = HAL_UART_Receive(&huart1, &c, 1, 1);
    if (stat == HAL_OK) {
      stat = HAL_UART_Transmit(&huart1, &c, 1, -1);
    }
  }
  /* USER CODE END 3 */
}

ところで、こちらの動作確認を行う場合はUSBシリアルとの送受信が必要となります。真っ先に思い浮かぶのは通信ソフトを使用することでしょうが、LinuxやMacならターミナルから screen コマンドを使用する方法もあります。

screen  <通信速度>

例えば screen /dev//dev/tty.usbserial-◯◯ 9600 と入力します。
終了は(デフォルトでは) CTRL-a k です。つまり、CTRL-a の後、 k を押すということです。何かと便利なので、知っておいて損はないコマンドだと思います。

閑話休題

USARTもそうだけど、上記のタイムアウト時間を指定して送信/受信する関数以外にタイムアウト時間を指定しない関数、割り込みハンドラを内部を記述するものがあります。上記の場合は受信データが対向側から送信されない場合、 HAL_UART_Receive() は最終の引数で指定した時間(ms)だけ待たされます。今回記述したサンプルだと1msですね。

どのように処理するのが最適なのかについては、情報収集や試行錯誤が必要そうです。

  • 割り込みハンドラを使うのが一番効率的そうだけどたくさんの処理を行っても大丈夫なのか?
  • OS(FreeRTOS等)を使った場合にどれくらいROM/RAMが増えるのか?また消費電力の差は?
  • DMA転送を組み合わせた方が消費電力が減るのか?
  • 省電力(STOPモード)を組み合わせた通信は可能か?またそれを使用しない場合の消費電力の差は?

ともあれ、まずは動くモノを仕立て上げなければ。

更に閑話休題

実際にGPSモジュールからのシリアル送信されたデータをSTM32に入力させたところ、どうもうまく受信できませんでした。
どうも、HALドライバの出来がよろしくないようで。
シリアル送受信時の割り込み時に呼び出されるコールバックでバッファリングするという定石で作らないといけないよう。
HALドライバでUARTに関して動かないという情報はWEB上にいろいろ上がっているようだけど、対策についての記載があまりないみたい。
困ったことに、HALドライバについてくる付属してくるExampleもしょぼい…
ちょっと時間がかかりそうです。

STM32/STMicroelectronics は STM32CubeMXやHAL drivers を使って欲しいと考えているのだろうか!?

STM32マイコンを使い始めて約2週間が経過しました。これまで使用していた Arduino ではデバッガが使えなかったり Arduino の IDE をアップグレートする度に設定ファイルの構成が変わったり、ヘッダファイルが更新されてソフトウェアの互換性が損なわれたりしていました。また、Arduino内部の揉め事(あるいは裁判沙汰)もあり、辟易としていました。しかしながら、数多くの無料で使用できる有志が作成したライブラリや開発ドキュメントは明らかにArduinoを使うメリットと感じます。

STM32は素晴らしい性能や低価格(安いものだと1500円程度)で使用できる評価キット(Arduinoのメインボードのようなもの。STM32DiscoveryやSTM32Nucleoシリーズ)は魅力的で、しかも簡単にgdbベースのリモートデバッグ環境が構築できるところも惹かれるものがあります。一方で、STM32の標準的なライブラリとして位置付けられている STM32Cube についてはあまりにも情報が少ないです。ドキュメントは確かに存在するのですが、単にDoxygenで生成されたものでごく簡単な説明しか記載されていません。

例えば Description of STM32L0xx HAL drivers – STMicroelectronics に記載されている HAL_UART_Receive() の説明は Function Description: Receive an amount of data in blocking mode. という一文のみ。「えええぇぇ〜〜〜〜!」とびっくりしてしまいます。戻り値は Return values: HAL: status だけで何が返却されうるのか分からないし、引数 Timeout0 を指定した場合の挙動については触れられていませんし、時間単位の記述すらありません(ソースを確認したところ、Timeout値が0の場合は無条件にタイムアウトの扱いとなるようです。待ち時間なしで受信処理を行う場合は HAL_UART_Receive_IT() を使えということらしい)。ちなみに HAL_UART_Receive() に限らず他も含め全般に言えることです。APIについてはソースもありますしサンプル(Examples)もあるのでこれらを確認すれば分かるとは思いますが不親切ですね。 HAL drivers を開発者に使って欲しいと考えているのか疑ってしまいますし、STM32マイコンを少しでも沢山売りたいと思っているか甚だ疑問です。多分、小口の顧客は相手にしていないということでしょう。

電子工作相手にしているお店には Arduino と並んで STM32Discovery / STM32Nucleo も売っているのですが情報量には大きな開きがあります。ちなみに STM32Nucleo には Arduino とピン配置・レイアウトが同じものが備え付けられているのですが、 基本的には ソフトウェアには互換性がないので要注意です。

あと、 STMicroelectronics には一言言いたい。 STMicroelectronics という日本語のWEBページを作っているんだったらドキュメント類も日本語のものを用意してくれと。英語のドキュメントでも不親切なものが多いようだけどこのレベルでいいから日本語で書いたものを用意してくれたら、情報発信してくれる有志がもっと増えるのではないでしょうか。そうするとまわりまわってSTM32を使用したプロダクトがもっと増えるんじゃないかなあ。素人考えかな?

STM32/省電力モードへの遷移とGPIOからの入力による省電力モードの終了

STM32を使って作りたいのはGPSロガーに必要な技術的要素は以下の通り。

  1. デジタル出力
    • 電源の制御
    • LEDの点灯状態の制御
  2. デジタル入力
    • タクトスイッチの押下状態の取得
  3. アナログ入力
    • 電源電圧の監視
  4. SPI
    • SDカードへのNMEAの書き込み
  5. マイコンの省電力制御
  6. EXTI割り込み
    • タクトスイッチ押下により、省電力状態からの復帰
  7. USARTによる受信
    • GPSからのNMEA情報の受信

1〜4までは前回までに動作確認が終わりました。
今回は、5, 6を試します。

回路図

これまで組み上げた回路を使用します。ただ、今回はタクトスイッチにコンデンサーを追加しました。チャタリングを抑止するためです。
今回はSDカード(SPI)やADCは使用しません。タクトスイッチとLED制御のためのGPIOのみ使用します。

Circuit

STM32CubeMX

Chip

PB1を GPIO_Input から GPIO_EXTI1 に変更します。

Configuration

Configuration タブに移動し、 System >> NVIC を選択します。

NVIC

EXTI line 0 and line 1 interrupt にチェックを入れます。

ここまで設定したら、コードを生成します。

Eclipse

以下のようなコードを記述します。

/* USER CODE BEGIN 0 */
uint8_t gToggleSw = 1;
/* USER CODE END 0 */
    :
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    HAL_Delay(500);

    if (gToggleSw == 0) {
      // stop here
      HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
      // resume from here
      SystemClock_Config();
    }
  }
  /* USER CODE END 3 */
    :
    :
/* USER CODE BEGIN 4 */
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  gToggleSw ^= 1;
}
/* USER CODE END 4 */

タクトスイッチを押下するごとに点滅と点滅の一時停止が交互に行われます。

試していて困ったのが、ST-Linkを接続していつものように動作確認をしていたところ、タクトスイッチを押下するとプログラムは一時停止。 Resume により再開してもすぐに停止し、 Program received signal SIGINT, Interrupt. が出力され、以降動作しなくなります。

Temporary breakpoint 2, main () at L0SampleExti/Src/main.c:69
69      {

[Remote target] #1 stopped.
main () at L0SampleExti/Src/main.c:69
69      {
The target is not in the halted nor running stated, stepi/continue ignored.

Program received signal SIGINT, Interrupt.
main () at L0SampleExti/Src/main.c:69
69      {

STM32L053C8T6 に書き込んだ後、ST-Linkを接続せずに単体で起動すると期待通り動作します。追々調べることにしましょう。

STM32/STM32L053C8T6でSDカードにアクセスする

STM32を使って作りたいのはGPSロガーです。パーツとしておそらく以下のようなものを使うはず。

  1. デジタル出力
    • 電源の制御
    • LEDの点灯状態の制御
  2. デジタル入力
    • タクトスイッチの押下状態の取得
  3. アナログ入力
    • 電源電圧の監視
  4. SPI
    • SDカードへのNMEAの書き込み
  5. マイコンの省電力制御
  6. EXTI割り込み
    • タクトスイッチ押下により、省電力状態からの復帰
  7. USARTによる受信
    • GPSからのNMEA情報の受信

今のところ、3.までこなしました。続いて4.の通り、SPIを使用してSDカードへのアクセスを試します。

ところで・・・

無知とは恐ろしいです。
ARMマイコンでGPSロガーを作りたいな→8ビットマイコンの置き換えも狙っているCortex-M0マイコンで作ってみよう→NXPは使いにくそうだし(イメージ先行)STM32にしよう→STM32F0もいいけど、より省電力が期待できるSTM32L0にしよう。みたいな流れです。Arduino(あるいはAVRマイコン)のありがたいところは有志によるライブラリが沢山あり、ハードを動かすソフトウェアを一から作ることはほとんどないところにあります。しかし、STM32はそうはいかないようで、SDカードのアクセスの場合もちょっとだけ苦労しました。

同じSTM32マイコンでもF1やF4シリーズだと有志による実装をすぐに見つけることができたのですが、L0はそうはいきませんでした。STM32CubeMXではFatFsの選択ができるのに…ハードに近い部分の処理は開発者が実装しなければならない状態です。ウムム・・・

でも、気を取り直して STM32CubeL0 Embedded software for STM32L0 series (HAL low level drivers, USB, File system, RTOS, Touch Sensing – coming with examples running on ST boards: STM32 Nucleo, Discovery kits and Evaluation boards) – STMicroelectronics からSTM32Cubeをダウンロードしその内容確認したところ、使えそうなソースがありました。

get software

STM32Cube_FW_L0_V1.4.0/Drivers/BSP/Adafruit_Shieldstm32_adafruit_sd.cstm32_adafruit_sd.h があり、ハードを使いこなす処理をSDカードアクセス処理のレベルで抽象化したAPIを呼び出しています。その呼び出されるAPIを実装しており、使いやすそうなソースが STM32Cube_FW_L0_V1.4.0/Drivers/BSP/STM32L0xx_Nucleostm32l0xx_nucleo.cstm32l0xx_nucleo.h がありました。また、 STM32Cube_FW_L0_V1.4.0/Middlewares/Third_Party/FatFs/src/driverssd_diskio.csd_diskio.h があり、 stm32_adafruit_sd.c/h 提供のAPIを呼び出しており、FatFs本体とを つなぐ ようになっていました。

つまり、以下のような呼び出し関係になってそうです。

アプリ
  ↓
FatFs本体
  ↓
sd_diskio.c/h(FatFs Moduleの一部)
  ↓
stm32_adafruit_sd.c/h
  ↓
stm32l0xx_nucleo.c/h
  ↓
HALライブラリ

sd_diskio.c/h はほとんどそのまま使用できそうであり、stm32_adafruit_sd.c/h、stm32l0xx_nucleo.c/h はSDカードアクセスに必要な部分のみ使用し、他(JoyStick, LCD, LED)に関するソースを無効化することで動かせそうです。(実際動き、ファイルの作成・書き込みと読み込みができました)

回路図

SDカードをSPIによりアクセスするため、SPI1にSDカードを接続します。また、CSの制御のためGPIOを使用します。

Circuit

STM32CubeMX

これまで作成したSTM32CubeMXプロジェクトにSPIを追加します。またCS信号のため、GPIOを一つ割り当てます。SPIやGPIOの初期化コードは上記ソースに含まれており、STM32CubeMXで設定する必要は本来ないのですが、stm32l0xx_nucleo.c/h に #ifdef HAL_SPI_MODULE_ENABLED 〜 #endif というコードがあり、これによりSTM32CubeMXで設定していなければ必要な処理が無効化されます。includeファイル内で定義する、もしくは #ifdef HAL_SPI_MODULE_ENABLED#endif を削除することも考えられますが、一旦STM32CubeMXにて使用を宣言します。

Chip

Peripherals >> SPI1 を開き、 ModeFull-Duplex Master に設定します。

また、 PB5GPIO_Output に設定します。

PB5

SPIについて、BSP側のソースで設定しているため、これ以上の設定は行いません(GPSロガーを作るまでにこのやり方はやめるつもり)。

コードを生成し、以降Eclipse本体で作業を行います。

  • sd_diskio.c/h
  • stm32_adafruit_sd.c/h
  • stm32l0xx_nucleo.c/h

上記ソースをEclipseプロジェクトの適当な場所に格納します。

  • SW4STM32/L0SampleFatFs Configuration/Application/User/BSP
    • stm32_adafruit_sd.c
    • stm32_adafruit_sd.h
    • stm32l0xx_nucleo.c
    • stm32l0xx_nucleo.h
  • SW4STM32/L0SampleFatFs Configuration/Application/User/fatfs
    • sd_diskio.c
    • sd_diskio.h

当方では、上記の通り配置しました。

次にSDカード以外の処理を削除していきます。どれを削除すれば良いかは分かりやすいと思います。定義名の接頭辞や途中にが SD_ がついている定義や関数を残し、他を削除すれば良いのです。

diff --git a/SW4STM32/L0SampleFatFs Configuration/Application/User/fatfs/sd_diskio.c b/SW4STM32/L0SampleFatFs Configuration/Appl
ication/User/fatfs/sd_diskio.c
old mode 100755
new mode 100644
index cb16735..d798cf5
--- a/SW4STM32/L0SampleFatFs Configuration/Application/User/fatfs/sd_diskio.c
+++ b/SW4STM32/L0SampleFatFs Configuration/Application/User/fatfs/sd_diskio.c
@@ -28,6 +28,9 @@
 /* Includes ------------------------------------------------------------------*/
 #include 
 #include "ff_gen_drv.h"
+#ifndef USE_NUCLEO
+#include "../BSP/stm32_adafruit_sd.h"
+#endif /* USE_NUCLEO */

 /* Private typedef -----------------------------------------------------------*/
 /* Private define ------------------------------------------------------------*/
@@ -49,7 +52,11 @@ DRESULT SD_read (BYTE, BYTE*, DWORD, UINT);
   DRESULT SD_ioctl (BYTE, BYTE, void*);
 #endif  /* _USE_IOCTL == 1 */

+#ifdef USE_NUCLEO
 const Diskio_drvTypeDef  SD_Driver =
+#else  /* USE_NUCLEO */
+const Diskio_drvTypeDef  USER_Driver =
+#endif /* USE_NUCLEO */
 {
   SD_initialize,
   SD_status,
diff --git a/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.c b/SW4STM32/L0SampleFatFs Configuration
/Application/User/BSP/stm32l0xx_nucleo.c
index 3420c82..71ea981 100755
--- a/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.c
+++ b/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.c
@@ -101,12 +101,14 @@
 /** @defgroup STM32L0XX_NUCLEO_LOW_LEVEL_Private_Variables
   * @{
   */
+#ifdef USE_NUCLEO
 GPIO_TypeDef* LED_PORT[LEDn] = {LED2_GPIO_PORT};
 const uint16_t LED_PIN[LEDn] = {LED2_PIN};

 GPIO_TypeDef* BUTTON_PORT[BUTTONn] = {KEY_BUTTON_GPIO_PORT };
 const uint16_t BUTTON_PIN[BUTTONn] = {KEY_BUTTON_PIN };
 const uint8_t BUTTON_IRQn[BUTTONn] = {KEY_BUTTON_EXTI_IRQn };
+#endif // USE_NUCLEO

 /**
  * @brief BUS variables
@@ -118,9 +120,11 @@ static SPI_HandleTypeDef hnucleo_Spi;
 #endif /* HAL_SPI_MODULE_ENABLED */

 #ifdef HAL_ADC_MODULE_ENABLED
+#ifdef USE_NUCLEO
 static ADC_HandleTypeDef hnucleo_Adc;
 /* ADC channel configuration structure declaration */
 static ADC_ChannelConfTypeDef sConfig;
+#endif // USE_NUCLEO
 #endif /* HAL_ADC_MODULE_ENABLED */
 /**
   * @}
@@ -142,16 +146,20 @@ void                      SD_IO_WriteReadData(const uint8_t *DataIn, uint8_t *Da
 uint8_t                   SD_IO_WriteByte(uint8_t Data);

 /* LCD IO functions */
+#ifdef USE_NUCLEO
 void                      LCD_IO_Init(void);
 void                      LCD_IO_WriteData(uint8_t Data);
 void                      LCD_IO_WriteMultipleData(uint8_t *pData, uint32_t Size);
 void                      LCD_IO_WriteReg(uint8_t LCDReg);
 void                      LCD_Delay(uint32_t delay);
+#endif // USE_NUCLEO
 #endif /* HAL_SPI_MODULE_ENABLED */

 #ifdef HAL_ADC_MODULE_ENABLED
+#ifdef USE_NUCLEO
 static void               ADCx_Init(void);
 static void               ADCx_MspInit(ADC_HandleTypeDef *hadc);
+#endif // USE_NUCLEO
 #endif /* HAL_ADC_MODULE_ENABLED */
 /**
   * @}
@@ -171,6 +179,7 @@ uint32_t BSP_GetVersion(void)
   return __STM32L0XX_NUCLEO_BSP_VERSION;
 }

+#ifdef USE_NUCLEO
 /**
   * @brief  Configures LED GPIO.
   * @param  Led: Specifies the Led to be configured.
@@ -284,6 +293,7 @@ uint32_t BSP_PB_GetState(Button_TypeDef Button)
 {
   return HAL_GPIO_ReadPin(BUTTON_PORT[Button], BUTTON_PIN[Button]);
 }
+#endif // USE_NUCLEO


 #ifdef HAL_SPI_MODULE_ENABLED
@@ -444,12 +454,14 @@ void SD_IO_Init(void)


   /* Configure LCD_CS_PIN pin: LCD Card CS pin */
+#ifdef USE_NUCLEO
   GPIO_InitStruct.Pin = LCD_CS_PIN;
   GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
   GPIO_InitStruct.Pull = GPIO_NOPULL;
   GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
   HAL_GPIO_Init(SD_CS_GPIO_PORT, &GPIO_InitStruct);
   LCD_CS_HIGH();
+#endif // USE_NUCLEO

   /*------------Put SD in SPI mode--------------*/
   /* SD SPI Config */
@@ -507,6 +519,7 @@ uint8_t SD_IO_WriteByte(uint8_t Data)
 }

 /********************************* LINK LCD ***********************************/
+#ifdef USE_NUCLEO
 /**
   * @brief  Initializes the LCD
   * @param  None
@@ -645,10 +658,12 @@ void LCD_Delay(uint32_t Delay)
 {
   HAL_Delay(Delay);
 }
+#endif // USE_NUCLEO
 #endif /* HAL_SPI_MODULE_ENABLED */

 /******************************* LINK JOYSTICK ********************************/
 #ifdef HAL_ADC_MODULE_ENABLED
+#ifdef USE_NUCLEO
 /**
   * @brief  Initializes ADC MSP.
   * @param  None
@@ -789,6 +804,7 @@ JOYState_TypeDef BSP_JOY_GetState(void)
   /* Return the code of the Joystick key pressed */
   return state;
 }
+#endif // USE_NUCLEO
 #endif /* HAL_ADC_MODULE_ENABLED */

 /**
diff --git a/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.h b/SW4STM32/L0SampleFatFs Configuration
/Application/User/BSP/stm32l0xx_nucleo.h
index 20912f3..5021c1a 100755
--- a/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.h
+++ b/SW4STM32/L0SampleFatFs Configuration/Application/User/BSP/stm32l0xx_nucleo.h
@@ -66,6 +66,7 @@
 /** @defgroup STM32L0XX_NUCLEO_LOW_LEVEL_Exported_Types
   * @{
   */
+#ifdef USE_NUCLEO
 typedef enum
 {
   LED2 = 0,
@@ -94,6 +95,7 @@ typedef enum
   JOY_RIGHT = 4,
   JOY_UP    = 5
 } JOYState_TypeDef;
+#endif // USE_NUCLEO

 /**
   * @}
@@ -113,6 +115,7 @@ typedef enum
 /** @addtogroup STM32L0XX_NUCLEO_LOW_LEVEL_LED
   * @{
   */
+#ifdef USE_NUCLEO
 #define LEDn                               1

 #define LED2_PIN                           GPIO_PIN_5
@@ -122,6 +125,7 @@ typedef enum

 #define LEDx_GPIO_CLK_ENABLE(__INDEX__)    do {LED2_GPIO_CLK_ENABLE(); } while(0)
 #define LEDx_GPIO_CLK_DISABLE(__INDEX__)   LED2_GPIO_CLK_DISABLE())
+#endif // USE_NUCLEO
 /**
   * @}
   */
@@ -129,6 +133,7 @@ typedef enum
 /** @addtogroup STM32L0XX_NUCLEO_LOW_LEVEL_BUTTON
   * @{
   */
+#ifdef USE_NUCLEO
 #define BUTTONn                            1

 /**
@@ -150,6 +155,7 @@ typedef enum

 #define BUTTONx_GPIO_CLK_ENABLE(__INDEX__)     do {KEY_BUTTON_GPIO_CLK_ENABLE(); } while(0)
 #define BUTTONx_GPIO_CLK_DISABLE(__INDEX__)    (KEY_BUTTON__GPIO_CLK_DISABLE())
+#endif // USE_NUCLEO
 /**
   * @}
   */
@@ -198,10 +204,12 @@ typedef enum
 /**
   * @brief  LCD Control Lines management
   */
+#ifdef USE_NUCLEO
 #define LCD_CS_LOW()      HAL_GPIO_WritePin(LCD_CS_GPIO_PORT, LCD_CS_PIN, GPIO_PIN_RESET)
 #define LCD_CS_HIGH()     HAL_GPIO_WritePin(LCD_CS_GPIO_PORT, LCD_CS_PIN, GPIO_PIN_SET)
 #define LCD_DC_LOW()      HAL_GPIO_WritePin(LCD_DC_GPIO_PORT, LCD_DC_PIN, GPIO_PIN_RESET)
 #define LCD_DC_HIGH()     HAL_GPIO_WritePin(LCD_DC_GPIO_PORT, LCD_DC_PIN, GPIO_PIN_SET)
+#endif // USE_NUCLEO

 /**
   * @brief  SD Control Interface pins
@@ -211,6 +219,7 @@ typedef enum
 #define SD_CS_GPIO_CLK_ENABLE()                 __HAL_RCC_GPIOB_CLK_ENABLE()
 #define SD_CS_GPIO_CLK_DISABLE()                __HAL_RCC_GPIOB_CLK_DISABLE()

+#ifdef USE_NUCLEO
 /**
   * @brief  LCD Control Interface pins
   */
@@ -242,6 +251,7 @@ typedef enum
 #define NUCLEO_ADCx_GPIO_CLK_DISABLE()            __HAL_RCC_GPIOB_CLK_ENABLE()

 #endif /* HAL_ADC_MODULE_ENABLED */
+#endif // USE_NUCLEO

 /**
   * @}
@@ -259,6 +269,7 @@ typedef enum
   * @{
   */
 uint32_t         BSP_GetVersion(void);
+#ifdef USE_NUCLEO
 void             BSP_LED_Init(Led_TypeDef Led);
 void             BSP_LED_On(Led_TypeDef Led);
 void             BSP_LED_Off(Led_TypeDef Led);
@@ -269,6 +280,7 @@ uint32_t         BSP_PB_GetState(Button_TypeDef Button);
 uint8_t          BSP_JOY_Init(void);
 JOYState_TypeDef BSP_JOY_GetState(void);
 #endif /* HAL_ADC_MODULE_ENABLED */
+#endif // USE_NUCLEO
 /**
   * @}
   */

見ての通り、#ifdef USE_NUCLEO〜#endif で括っています。 USE_NUCLEOは定義していないので、結果として括られた部分の処理は無効となります。

/Src/fatfs.c にSDカードのマウント処理を追加します。また、semihostingを使用しているので、 initialise_monitor_handles(); も追加しています。

/* USER CODE BEGIN Variables */
extern void initialise_monitor_handles(void);
FATFS SDFatFs;  /* File system object for SD card logical drive */
/* USER CODE END Variables */    
  :
void MX_FATFS_Init(void) 
{
  /*## FatFS: Link the USER driver ###########################*/
  retUSER = FATFS_LinkDriver(&USER_Driver, USER_Path);

  /* USER CODE BEGIN Init */
  FRESULT res;
  initialise_monitor_handles();
  res = f_mount(&SDFatFs, (TCHAR const*)USER_Path, 0);
  printf("f_mount() res=%d\n", (int)res);
  /* USER CODE END Init */
}

main()関数にサンプルとしてファイルの書き込みを行い、その内容を読み出すソースを記述します。

int main(void)
{

  /* USER CODE BEGIN 1 */
  uint32_t byteswritten, bytesread;                     /* File write/read counts */
  FIL MyFile;     /* File object */
  uint8_t wtext[] = "This is STM32 working with FatFs"; /* File write buffer */
  uint8_t rtext[100];                                   /* File read buffer */
  FRESULT res;                                          /* FatFs function common result code */
  /* USER CODE END 1 */
    :
  /* USER CODE BEGIN 2 */
  res = f_open(&MyFile, "STM32.TXT", FA_CREATE_ALWAYS | FA_WRITE);
  printf("f_open() res=%d\n", (int)res);
  if (res == FR_OK) {
    res = f_write(&MyFile, wtext, sizeof(wtext), (void *)&byteswritten);
    printf("f_write() res=%d, byteswritten=%lu\n", (int)res, byteswritten);
    f_close(&MyFile);
  }

  res = f_open(&MyFile, "STM32.TXT", FA_READ);
  printf("open() res=%d\n", (int)res);
  if (res == FR_OK) {
    res = f_read(&MyFile, rtext, sizeof(rtext), (UINT*)&bytesread);
    printf("f_read() res=%d, bytesread=%lu, rtext=%s\n", (int)res, byteswritten, rtext);
    f_close(&MyFile);
  }
  FATFS_UnLinkDriver(USER_Path);
  /* USER CODE END 2 */

  while (1);
}
Open On-Chip Debugger 0.9.0 (2015-05-28-12:05)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
adapter speed: 300 kHz
adapter_nsrst_delay: 100
  :
semihosting is enabled
f_mount() res=0
f_open() res=0
f_write() res=0, byteswritten=33
open() res=0
f_read() res=0, bytesread=33, rtext=This is STM32 working with FatFs

STM32/アナログ入力を試す

今回は、STM32のADCを使用してみます。

回路について

以下のような回路としました。前回までの回路をベースにしていることもありごちゃごちゃとした印象を受けるかもしれません。しかし、今回やりたいことは単純明快で、ADCを使用して電圧を計測するというものです。これによりLi-Poの電圧を監視し、一定電圧を下回ったらマイコンをSTOP状態に落とし、過放電を防ぐ目的での使用を想定しています。

Circuit

100KΩ接続を計測対象の電源とGNDの間に2つ直列に接続し、その間に PA10 を接続します。抵抗分圧ですね。Li-Poは3.7Vありマイコンには3.3Vを提供し、基準電圧も3.3Vにします。このままでは3.3V以上の計測は出来ないため抵抗分圧で電圧を落としています。この場合は1/2の電圧になるはずです。

STM32CubeMXでの設定

Chip

まずはEclipseを起動し、これまで使用していたプロジェクトファイルを読み込みます。 Configuration >> Peripherals >> ADC にある IN0 にチェックを入れます。

Configuration

ADC をクリックします。

Parameter Settings に移動し、 Clock PrescalerSynchronous clock mode divided by 4 に、 Continuous Conversion ModeEnabled に設定してみます。

Parameter Settings

ここまでできたらコードを生成しましょう。

ソフトウェア

main()関数先頭に変数の定義を追加します。

uint32_t adcValue = 0;
uint32_t volt     = 0;
uint8_t adcCount  = 0;

main()関数にあるwhile()ループ内に以下のようなコードを記述します。

if (adcCount == 0) {
   HAL_ADC_Start(&hadc);
   if (HAL_ADC_PollForConversion(&hadc, 1000000) == HAL_OK) {
    adcValue = HAL_ADC_GetValue(&hadc);
    HAL_ADC_Stop(&hadc);
    volt = (adcValue * 3300 * 2) / 4096; // 4096 => 3.3V
    printf("ADC Value=%lu, Volt=%lu.%2lu\n", adcValue, volt / 1000, volt % 1000);
    adcCount++;
  }
} else {
  adcCount = (adcCount + 1) % 10;
}
HAL_Delay(50);

50ms毎にループさせていますが、10回ループする毎、つまり500ms毎に計測を開始します。電圧が取得できたらその値を表示します。

実行例

Open On-Chip Debugger 0.9.0 (2015-05-28-12:05)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
adapter speed: 300 kHz
adapter_nsrst_delay: 100
none separate
cortex_m reset_config sysresetreq
Info : clock speed 300 kHz
Info : SWD IDCODE 0x0bc11477
Info : stm32l0.cpu: hardware has 4 breakpoints, 2 watchpoints
Info : accepting 'gdb' connection on tcp/3333
Info : STM32L flash size is 64kb, base address is 0x8000000
undefined debug reason 7 - target needs reset
adapter speed: 300 kHz
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x080005a0 msp: 0x20002000
STM32L0: Enabling HSI16
adapter speed: 2500 kHz
Error: stm32l0.cpu -- clearing lockup after double fault
Warn : couldn't use loader, falling back to page memory writes
adapter speed: 300 kHz
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x080005a0 msp: 0x20002000
semihosting is enabled
hello world
ADC Value=2637, Volt=4.249
ADC Value=2641, Volt=4.255
ADC Value=2639, Volt=4.252
ADC Value=2656, Volt=4.279
ADC Value=2645, Volt=4.261
ADC Value=2645, Volt=4.261
ADC Value=2641, Volt=4.255

STM32/セミホスティングを使ってみる

OpenOCDでは、 Semihosting なるものをサポートしているとのこと。

ARM Information Center

これにより、デバッグの選択の余地が増えることを意味し、開発期間をより短くできる可能性を秘めていると思います。

Arduinoを使っていた場合はそのようなものはなく、

  1. USARTを使用して出力する
  2. SDカードにログとして保存する
  3. LCDなどに表示する

といったことをする必要があり、いずれも回路を追加する必要がありました。これはちょっと面倒です。1.はもっとも有力な選択肢になると思いますが他にUSARTを使用する場合はソフトウェアシリアルを追加で入れる必要がありました。2.と3.もできればしたくないです。

ともあれ、過去に構築方法を説明した SWSW4STM32 System Workbench for STM32 の環境でもSemihostingが使用できるので、活用しましょう。

Eclipseの設定

プロジェクトの設定

Project >> Properties を選択します。

Settings

左側のツリーは C/C++ Build >> Settings* を選択します。右側は Tool Settings タブを選択します。その下に表示されるツリーは MCU GCC Linker を選択します。 Linker flags に ** –specs=rdimon.specs -lrdimon** を追加します。

ソースの修正

Semihostingではprintf() などが使用できますが、その前に initialise_monitor_handles() を呼び出す必要があります。main() の先頭に記述しましょう。試しに、続けてprintf()を記述してみます。

/* USER CODE BEGIN 0 */
extern void initialise_monitor_handles(void);
/* USER CODE END 0 */
    :
int main(void)
{
  /* USER CODE BEGIN 1 */
  initialise_monitor_handles();
  printf("hello world\n");
  /* USER CODE END 1 */
    :

そうそう、試した限りでは、改行コード \n を記述しなければEclipseの Console タブに表示されませんでした。

あと、WEBページによっては -nostartfiles を削除する手順を紹介しているところもありましたが、特に実施しなくても文字列の出力ができました。もっとも誤った手順なのかもしれませんが…

ここまでできたら、ビルドします。

デバッグ時のオプション

Run >> Debug Configurations… を選択し、デバッグで使用している設定を選択します。

Commands Tab

Commands タブを選択し、 ‘Initialize’ commands に以下の1行を追加します。

monitor arm semihosting enable

実行

Debug ボタンを押下してみましょう。以下のように Console タブ上に以下のようなログが出力され、printf()で記述した内容も出力されると思います。

Open On-Chip Debugger 0.9.0 (2015-05-28-12:05)
Licensed under GNU GPL v2
For bug reports, read
    http://openocd.org/doc/doxygen/bugs.html
Info : FTDI SWD mode enabled
adapter speed: 300 kHz
adapter_nsrst_delay: 100
none separate
cortex_m reset_config sysresetreq
Info : clock speed 300 kHz
Info : SWD IDCODE 0x0bc11477
Info : stm32l0.cpu: hardware has 4 breakpoints, 2 watchpoints
Info : accepting 'gdb' connection on tcp/3333
Info : STM32L flash size is 64kb, base address is 0x8000000
adapter speed: 300 kHz
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x080005a0 msp: 0x20002000, semihosting
STM32L0: Enabling HSI16
adapter speed: 2500 kHz
Error: stm32l0.cpu -- clearing lockup after double fault
Warn : couldn't use loader, falling back to page memory writes
adapter speed: 300 kHz
target state: halted
target halted due to debug-request, current mode: Thread
xPSR: 0xf1000000 pc: 0x080005a0 msp: 0x20002000, semihosting
semihosting is enabled
hello world.

これで、よりお手軽にデバッグできます (^^)

STM32/タクトスイッチの状態を取得する

追加回路

PB1 にタクトスイッチを接続します。STM32マイコンはプルアップ抵抗の設定ができるので、抵抗は接続しません。

Circuit

STM32CubeMXでの設定

タクトスイッチの状態を取得するサンプルを取得します。タクトスイッチが押される毎にLEDの点滅・消灯の切り替えを行うサンプルを作ります。前回のタイマ割り込みによるLEDを点滅させるEclipseプロジェクトをベースに作ってみます。

まずはSTMCubeMXを起動し、前回使用したプロジェクトを読み込みます。
マイコンの PB1 をクリックし、 GPIO_Input を選択します。

Chip

Configuration タブから System >> GPIO をクリックします。

Configuration

PB1 をクリックし、 GPIO Pull-up/Pull-down のプルダウンメニューから Pull-up を選択します。

PB1

Project >> Generate Code にてコードを生成します。

Eclipseにてソース修正

main.c を更に修正します。挿入しているコードは以下の通り。

int main(void)
{
  /* USER CODE BEGIN 1 */
  GPIO_PinState state;
  uint8_t swOnCount = 0;
  /* USER CODE END 1 */
      :
  /* USER CODE BEGIN 2 */
  HAL_TIM_Base_Start_IT(&htim2);
  /* USER CODE END 2 */
      :
  while (1)
  {
  /* USER CODE END WHILE */

  /* USER CODE BEGIN 3 */
    state = HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_1);
    if (state == GPIO_PIN_RESET) {
      if (swOnCount < 0xff) {
        swOnCount++;
        if (swOnCount == 4) {
          gSw ^= 0xff;
          swOnCount = 0xff;
        }
      }
    } else {
      swOnCount = 0;
    }
    HAL_Delay(50);
  }
  /* USER CODE END 3 */
}

/* USER CODE BEGIN 4 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
  if (htim->Instance == htim2.Instance) {
    if (gSw == 0x00) {
      HAL_GPIO_WritePin(GPIOB, GPIO_PIN_0, GPIO_PIN_RESET);
    } else {
      HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_0);
    }
  }
}
/* USER CODE END 4 */

しょぼいコードですみません。
main() では50ms毎にタクトスイッチの状態を読み込みます。4回(つまり200ms)連続で押され続けていたらLEDを点滅←→消灯を切り替えます。これはチャタリング防止に加え、while()ループを止めたくなかったためにこのようにしています。

さて、ここまででGPIOを使用したデジタル値の入出力とタイマ割り込みができるようになりました。