2020年4月30日木曜日

STM32のmbed環境でSPIに使えるピンは?

STM32のペリフェラルピンはやや特殊で、SPIやI2Cみたいな特殊機能は特定の数種類のピンにのみリマップできます。さらにわかりにくいことに、SPIユニットが内部に1~4まであり、それぞれ繋がっているバスが違ったりします…

データシートを熟読しながらcubeMXでリソースを割り当てる覚悟があれば問題ないんですが、手っ取り早くSPIを使いたいときにどのピンを使っていいのかよくわからなくて困ります。

で、調べたところ、mbedのSPIライブラリで対応しているピンならどれでも、いちいち対応するユニット番号を覚えてなくてもSPIとして使うことができます。単に

SPI device(D4, D5, D3);

みたいに書くだけで、MOSIがD4, MISOがD5, ClockがD3として動きます。対応してないピンを指定するとエラーをUSBシリアルポートに吐いて止まります。じゃあどのピンなら対応しているのかというと、nucleoを買うと付いてくるピンアウト表(こんなの↓)を見て、SPI3_MISOとか書いてあるところなら対応しています。
とはいえSPIxのxのところはそのピンに割り当てるときにライブラリ内部で使われるSPIユニット番号を挿しているので、宣言時に与える3本のピンが全て同じユニット番号に属している範囲でしか割り当ては許されません。(SPI1_MISOとSPI2_MOSIを同じライブラリ宣言に突っ込んで使うようなことはできません)

あと、この図を見ていてわからないのはピン番号の記述方法でして、SPI2_MISOといった名前はカラフルな表にそう書かれていますが、mbedライブラリで宣言はされていません。(なんせ選択肢が複数ピンあるので宣言しようもない)
宣言されているのはD2,D3 (nucleoピン番号)とかPA_3, PA_4 (GPIO番号)なので、これらを使ってピンを指定します。


さらにわかりにくいことに、SPI_MISOとかSPI_MOSIというピンは宣言されています。それって何番ピンのことなのよ!と思いますが、それは各CPUのPinNames.hに記述されていいまして、STM32F446REならここを参照するとそれぞれPA_6, PA_7であると書かれています。
まあSPI1ユニットのデフォルト割り当てピンなので妥当ではありますが… 事情をよく知らない状態で参考にしたコードがこの記法のピン指定を使っていると、他のピンにリマップする方法がさっぱりわかりませんよね。


さらに!nucleo基板の落とし穴というのがあって、いくつかのピンは既に割り当てがあるので注意が必要です。マニュアルに書いてはありますが、ありがたいことにF446REについて日本語情報でまとめてくれている方がいます。
たとえばSPI1の割り当てなんて、LEDにかぶるわSWDにかぶるわで…まあ機能はするので気にしなくても最初は困らないんですが…

2020年4月17日金曜日

STM32F446RE めいっぱい速くPWM出力

IOトグルだけで最高速が出ることがわかったので別に必要ないんですが、PWMでめいっぱい速くIOを上げ下げする方法を調べてみました。

どうもSTM32は、どのピンにでも機能を割り当てられるわけではなく、タイマ番号ごとにいくつか割り当てられるピンの種類が決まっていて、その中から選択するみたいです。しかもそのタイマも対照的にずらっと並んでいるのではなくそれぞれ個性があるみたいです。趣味的には楽しそうだけど学習コスト高そう…

まあ、とにかく見やすいところでLEDピンのPA5にPWM出力をしてみることにしました。
nucleoのパッケージにもあるピンアサイン画像によると、PA5にPWMを出す場合はタイマ2か1らしいです。1は特殊らしいのでまあタイマ2を選んでみます。
次にマニュアルを確認すると、タイマ2はAPB1に繋がっています。

APB1のクロックは残念ながら最大45MHzなのですが、cubeMXのクロック設定画面でよく見るとAPB1であってもタイマークロックだけは2倍の90Mhzが使われるようです。しかしAPB2なら2倍の90Mhz/180Mhzが使えるのか…まあ後でわかるように今回の場合別のボトルネックがあるので意味ないんですが。


で、cubeMXでTIM2設定のChannnel1をちまちまと入れていきます。下の画像ではプリスケーラPSC=1, カウンタ周期1, パルス幅カウント値1です。それぞれ0が最小なので、これらの意味するところはプリスケーラが2分周、カウンタ周期が0~1の2クロック、パルス幅はそのうちの1クロック、てことでデューティ比50%で最速のPWM(を2分周してるのでその半分速)ということです。
タイマのPWM出力設定を入力して、具体的にどのピンに出すかは同じ入力欄のGPIO Settingsタブでも選べますが、CPUの絵のピン番号のところで左クリックでこうやって選ぶこともできます。



ここでコード生成をするとcubeMXが上記の設定コードを吐いてくれるので、あとはmain関数の中にタイマスタートのHAL関数コールを書くだけで動きます。 
  if (HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1) != HAL_OK)
{
  Error_Handler();
}
その結果がこちら。確かに45Mhzで上げ下げして22.5MhzのPWMが出ていますが、残念ながらGPIOモードの時と違ってIOの速度が自動的にLOWにセットされちゃうので立ち上がりが非常にトロいです。

プリスケーラを0にすることで本当の最高速である、90Mhz上げ下げの45Mhz矩形波が出るのですが…

さすがにこれじゃもう波形が出せてるとは言えません。

ピンによって違うかどうかまでは全部試してませんが、数種類のタイマーで試した限り、PWM出力モードの時はどれもピンのIO出力速度がLOWになっていて変更できませんでした。CPU負荷なしで波形が出せるのはいいんですが、ちょっと残念です。

参考:cubeMXが出力したタイマ設定のコード
static void MX_TIM2_Init(void)
{
  /* USER CODE BEGIN TIM2_Init 0 */
  /* USER CODE END TIM2_Init 0 */
  TIM_ClockConfigTypeDef sClockSourceConfig = {0};
  TIM_MasterConfigTypeDef sMasterConfig = {0};
  TIM_OC_InitTypeDef sConfigOC = {0};
  /* USER CODE BEGIN TIM2_Init 1 */
  /* USER CODE END TIM2_Init 1 */
  htim2.Instance = TIM2;
  htim2.Init.Prescaler = 0;
  htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
  htim2.Init.Period = 1;
  htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
  htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
  if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
  if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
  {
    Error_Handler();
  }
  if (HAL_TIM_PWM_Init(&htim2) != HAL_OK)
  {
    Error_Handler();
  }
  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
  if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
  {
    Error_Handler();
  }
  sConfigOC.OCMode = TIM_OCMODE_PWM1;
  sConfigOC.Pulse = 1;
  sConfigOC.OCPolarity = TIM_OCPOLARITY_LOW;
  sConfigOC.OCFastMode = TIM_OCFAST_ENABLE;
  if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
  {
    Error_Handler();
  }
  /* USER CODE BEGIN TIM2_Init 2 */
  /* USER CODE END TIM2_Init 2 */
  HAL_TIM_MspPostInit(&htim2);
}

2020年4月16日木曜日

Lチカ最速を求めて STM32F446RE

久々にcubeMXまで引っ張り出してSTM32をいじっているのには理由があって、ちょっと短かいパルスのゲート信号を好きなように生成したいのです。クロック生信号やPWM出力とかなら簡単に高周波を出せますが、1発だけとか、少しずつディレイを変えながら2発とか、そういったちまちましたことをやるのにマイコンは向いています。

そう考えると、単なるベンチマークみたいなIOトグルのLチカ最速周波数が、自由に出力できるパルスの最短を示しているわけで、どうでもいい数字ではないわけです。

arduino勢の速度を丁寧に計ってくれているひとがいて、それによると矩形波の周波数でarduino UNOが500Hz, DUEが17.85Mhz, ESP8266が6.25Mhzだそうです。やっぱりDUEは速いです。

STM32F446のハードウェアを振り返ると、まずCPUクロックが168Mhz~180Mhzです。そしてGPIOはAH1Bに繋がっています。そしてなんとAH1Bの上限はCPUと同じ180Mhzなのです!

あとは、ポートアクセスで無駄なくたたけば1クロックで状態が変わるはず。ということでcubeMXの助けを借りて下のように書いてみました。

IO初期化
  /*Configure GPIO pins : LD2_Pin D7_Pin */
  GPIO_InitStruct.Pin = LD2_Pin|D7_Pin;
  GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
  GPIO_InitStruct.Pull = GPIO_NOPULL;
  GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
  HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);

IO出力部
  while (1)
  {
    /* USER CODE END WHILE */
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);   //LED
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);   //D7
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET);
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
   
    GPIOA->ODR = 65535;
    GPIOA->ODR = 0;
    GPIOA->ODR = 65535;
    GPIOA->ODR = 0;
    GPIOA->ODR = 65535;
    GPIOA->ODR = 0;
    GPIOA->ODR = 65535;
    GPIOA->ODR = 0;

    /* USER CODE BEGIN 3 */
  }
その結果がこちら。HALでトグルした遅いパルス2発と、ポートたたきで出した高速パルス4発が見えます。負荷の影響有無も確認したかったのでLEDポートと空きポートD7の2か所に出しています。(特に違い無かったです)

高速パルス4発の拡大がこれ。パルス数で88Mhzということは、確かに2倍の180MhzでIOの上げ下げができているということです。STM32F4すげー。波形はほぼsin波にしか見えないほどなまってるけど、そもそも立ち上がり設定の最速が100Mhzとされてるので文句は言えません。
ちなみにレジスタ操作のところを "GPIOA->ODR ^= GPIO_ODR_5" みたいにオシャレに書く方法がありますが、これはコンパイルするとIO状態リード→論理演算→ライトの3工程に分かれるので数倍遅くなります。

Lチカ速度を調べたいだけだったのに、がんばってググってもいまいちぴったりの例が無くて実演する羽目になりました。でもすっきりして良かった。STM43F4は速いですよ。

STM32 やってます nucleo F446RE

やっつけ組み込み仕事で、初めてつかうデバイスをSPIとかI2Cで使うときによくarduinoを使うことがあります。サンプルコードが付いてたりしますし。

だんだんマイコン側にいろいろやらせたくなり、さすがにarduino UNOでは速度的にきつくなってくると次に何に移行するかが問題です。石自体はarduino DUEが高性能だったんですが、ほぼディスコンだし意外と高いし、今後標準的に使っていくには不安。

他にメジャーどころではESP32が安くて速いですがクセがありすぎる… 優等生なところでは、Teensy3.2~3.6がとても高性能で、海外では流行っています。arduinoIDEに独自命令やらライブラリを追加しまくっているのでもはやarduinoではない気もしますが、実用的なライブラリが揃っていてとても使いやすい。

…が、我々ジャパニーズとしては、秋月でいつでも安く買えちゃうSTM32の、マイコン系では最高速のnucleoF446REが自由に使えるとより良いのです。


というわけでnucleoF446を何かというと使ってるのですが、開発環境が基本的にmbedのWEBコンパイラです。arduinoほどユーザーは多くないですがまあそれなりに普及していて、使い方も簡単で重宝しています。

唯一の問題は、STM32F4特有のIOを直に叩いて最高速を出したり、DMAであれやこれやしたときには、mbedを使ってる人の参考資料は劇レアで、たいていHALライブラリやcubeMXを使った環境なのです。

cubeMXは今では↓こんなIDEになっていて、巨大ですがこれをインストールしてあればコード生成だけじゃなくビルドもデバッグもできちゃいます。いいんです。いいんですが、こんな巨大なツールをどのマシンにでも入れてあるわけではないので、たいしたことないコードならmbedでちゃちゃっと済ませちゃいたいのです。

ふだんはmbedで楽をして、たまに難しいことをやるときだけcubeMXの自動生成コードをコピペしてきてうまうましたい…という虫のいいことが、基本的に可能です。というか、単にcubeMXで生成したプログラムのmain.cとmain.hをまるごと持ってきてmbed環境でビルドすればそのままビルド通ります。main以外のライブラリソースは持ってこなくてもmbedコンパイラ側にあります。(cubeMXはHALを呼びまくるコードを生成するので、それには当然対応したcpuのstm32f4xx_hal.hが必要ですが、それはmain.hの先頭で呼ばれているので問題ありません)


ただしビルドできるからと言ってそのまま動くとは限らないので、いくつか注意が必要です。えらいひとがこのような教えを書き残しているので従いましょう。確実にハマるのはcubeMXが生成したSystemClock_Config()です。クロック設定がよく見えないmbedレベルでどこからか呼ばれてしまっているので、わたくしはとりあえずcubeMXが吐いたSystemClock_Config()を使わないようにコメントアウトしました。

一応mbed側のソースをぐぐって確認したら、mbedは180Mhz、cubeMXは(USBホスト機能に配慮して)168Mhzで初期化されているみたいですが、それ以外には今のところ大きな違いは無かったです。ペリフェラルをどんどん使うようにしたらどこに初期化コードが書かれるのかまだわからないので、いずれは単にコメントアウトってわけにはいかなくなるのかも。

こんな具合でちまちまと使ってみて、F446REならではのことができたら書き残していきたいと思います。

2020年4月15日水曜日

久々の投稿 Hantekの200Mhzオシロ

久々の投稿です。しばらく在宅勤務でやることがないのと、今年度のおしごとは久々に電子工作系が多いので調べてわかったことをちびちび書いてきます。

しばらく前に職場でUSBオシロの Hantek 6204BD というのを買いました。てきとうにamazonを眺めていたら、200Mhzの4chで2万円台というかなり破格なおねだんで、SDKもあるようなのでロガーとして使えたらいいなあ、もし万が一本当に200Mhzの実力があったらもうけもん、と思って。今は新型の250Mhzのモデルが同価格くらいで売られています。
ちなみにUSBオシロで定評があるのはPicoscopeで、SDK経由でコントロールする使い方も実績があります。だいぶ安くなったけどそれでも200Mhzモデルは秋月で13万円します。なおハイエンドは等価サンプリングオシロですが25Ghzモデルまで出ています。


まず、SDKについては期待できなさそう。一応オフィシャルにSDK一式がありますが、ビルドにVC6とか信じられない環境されるし、ドキュメントも貧弱。もっと新しいモデルはC#のサンプルもあるみたいですが、picoscopeみたいに割り込みをちゃんと使ってソフトが書ける気がまったくしません。labviewで使うくらいならできるかも。

アナログ帯域については、うれしい誤算できっちり200Mhz出ています。サイン波で試したら200Mhzはほとんど減衰無しで、300Mhzくらいまでは減衰しながらついていきます。400Mhzは無理。

下の画像はToFライダーのパルス光を1Ghzのフォトダイオードで眺めた波形。波形の立ち上がりはもっとシャープなはずですが200Mhzすなわち2nsecくらいになまって見えています。2万円なら立派なものです。

SDKに手を出さず、単純にUSBオシロとして使うにはおすすめです。添付ソフトも画面は安っぽいですが良く見たら機能はかなりあるし、反応がきびきびしていて使い勝手は実オシロより良いです。だいぶ前に買ったRIGOLのDS1054Zは要らなかったかもなぁ…


追記:一つ異常動作を発見。3チャンネル以上を同時取り込みすると、画面の横軸スケール表記が正しくない値で表示されます(2チャンネル時500MSPS ⇒ 3チャンネル時250MSPSなのに、それが考慮されてないかんじ)
品評なに見てんの!