製作モジュール一覧

ラベル STM32CubeIDE の投稿を表示しています。 すべての投稿を表示
ラベル STM32CubeIDE の投稿を表示しています。 すべての投稿を表示

2022年10月27日木曜日

SSD1306 OLEDをSTM32で動かす。 afiskonライブラリ

afiskonライブラリafiskon/stm32-ssd1306はSSD1306は4iloライブラリからforkされたものの様で、SSD1306 OLEDのI2C版とSPI版に対応しています。

Nucleo-F446REとNucleo-G431KBで動作確認しました。

以下の記事を参考にさせていただきました。

「afiskon」さん(本家) https://github.com/afiskon/stm32-ssd1306
「hacksOnTable」さん 「STM32 OLED TUTORIAL(Youtube)

HALを使ったプログラミングはSTM32CubeIDEの登場でかなり便利になりましたが、MPUのシリーズによって特徴があり、設定やプログラミングが方法がそれぞれ少しずつ異なります。冗長になりますが、Nucleo-F446REとNucleo-G431GBで、SSD1306のI2C版とSPI版の使い方を説明します。

STM32CubeIDEのバージョン: 1.10.1

I2C版 Nucleo-F446RE


Board SelecotorでNuckeo-F446REを指定してDefult Modeで初期化。

MXの設定


Pinout & Configuration
  Connectivity
    I2C1
      Mode
        I2C: I2C
      Configuration
        Parameter Settings
          Master Features
            I2C Speed Mode: Standard Mode
        GPIO Settings
          PB8 I2C1_SCL
          PB9 I2C1_SDA

afiskonライブラリではデフォルトでI2C1を使う設定になっています。I2C2やI2C3などを使う場合は「ssd1306_conf_template.h」を適宜書き換えます。

ハードウェアの準備


SSD1306 F446RE Arduino Header
GND GND GND
VDD 3V3 3V3
SCK PB8 D15
SDA PB9 D14

ブレッドボード配線図

ライブラリの準備


GitHubのafiskon/stm32-ssd1306からZIPファイルをダウンロードします。
  • ssd1306.h
  • ssd1306_conf_templates.h
  • ssd1306_fonts.h
  • ssd1306_tests.h
を「<Projectフォルダ>\Core\Inc\」に
  • ssd1306.c
  • ssd1306_fonts.c
  • ssd1306_tests.c
を「<Projectフォルダ>\Core\Src\」にコピーします。IDEのProject ExplorerでProjectフォルダを右クリックし「Refresh」します。

「Core/Inc/ssd1306_conf_template.h」の内容を以下のように書き換えます。

// Choose a microcontroller family
//#define STM32F0
#define STM32F4

重要:「ssd1306_conf_template.h」を「ssd1306_conf.h」にRenameします。

ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include "ssd1306_tests.h"
/* USER CODE END Includes */

<>

  /* USER CODE BEGIN 2 */
  ssd1306_TestAll();
  /* USER CODE END 2 */

実行している様子

FPS表示値

Standard Mode 9
Fast Mode 34

F446REの駆動クロックはHSI/84MHz。

I2C信号波形

F446RE I2C Fast Mode 2.2kΩ Pullup


C1:SDA C2:SCL

I2C版 Nucleo-G431KB


Board SelecotorでNuckeo-G431KBを指定してDefult Modeで初期化。

MXの設定


Pinout & Configuration
  Connectivity
    I2C1
      Mode
        I2C: I2C
      Configuration
        Parameter Settings
          Master Features
            I2C Speed Mode: Standard Mode
        GPIO Settings
          PA15 I2C1_SCL
          PB7  I2C1_SDA

ブレッドボード配線図


SSD1306 G431KB Arduino Header
GND GND GND
VDD 3V3 3V3
SCK PA15 D5
SDA PB7 D4

ライブラリの準備


必要なファイルをコピーします。
  • Core/Inc/ssd1306.h
  • Core/Inc/ssd1306_conf_templates.h
  • Core/Inc/ssd1306_fonts.h
  • Core/Inc/ssd1306_tests.h
  • Core/Src/ssd1306.c
  • Core/Src/ssd1306_fonts.c
  • Core/Src/ssd1306_tests.c
「Core/Inc/ssd1306_conf_template.h」の内容を以下のように書き換えます。

// Choose a microcontroller family
//#define STM32F0
#define STM32G4

「#define STM32G4」はコメントアウトで用意されていないので書き加えます。「ssd1306.h」では

#elif defined(STM32G4)
#include "stm32g4xx_hal.h"

と、STM32G4を使用する場合の定義があります。

重要:「ssd1306_conf_template.h」を「ssd1306_conf.h」にRenameします。

ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include "ssd1306_tests.h"
/* USER CODE END Includes */

<>

  /* USER CODE BEGIN 2 */
  ssd1306_TestAll();
  /* USER CODE END 2 */

main.cへのソースコードの追加はI2C版 Nucleo-F446REと同じです。

実行している様子

FPS表示値

Standard Mode 9
Fast Mode 35
Fast Mode Plus 76

※Fast Mode Plusで使用する場合は2.2kΩ程度で外部Pullupする。

G431KBの駆動クロックはHSI/170MHz。

I2C信号波形

G431RB I2C Fast Mode Plus 2.2kΩ Pullup


C1:SDA C2:SCL

SPI版 Nucleo-F446RE


Board SelecotorでNuckeo-F446REを指定してDefult Modeで初期化。

MXの設定


Pinout & Configuration
  System Core
    Configuration
      GPIO
        PA10 : Output Push Pull : Very High : OLED_Res
        PB4  : Output Push Pull : Very High : OLED_CS
        PB5  : Output Push Pull : Very High : OLED_DC
  Connectivity
    SPI2
      Mode
        Mode: Half Duplex Master
      Configuration
        Parameter Settings
          Clock Parameters
            Prescaler(for Baud Rate): 64
            Clock Polarity(CPOL): High
            Clock Phase(CPHA): 2 Edge
        GPIO Settings
          PB10 SPI2_SCK
          PC1  SPI2_MOSI

Nucleo-F446REでは、SPI1はデフォルトでは使用不可になっているのでSPI2を使用します。MISOは使わないのでModeはHalf Duplex Masterにします。

動作確認のためClock Prescalerは大きめにしてクロック周波数を低く設定します。

重要:Clock Polarity(CPOL)、Clock Phase(CPHA)は上記の通り設定すると動作するようです。

またGPIOで制御信号線(CS、RES、DC)を割り当てます。右側のPinout図でPinをクリックしてGPIO_Outputを選択し、User Labelを設定します。User Labelはssd1306_conf_template.hで定義されているものを入力します。

ハードウェアの準備


SSD1306 F446RE Arduino Header 機能
GND GND GND GND
VDD 3V3 3V3 3.3V Power
D0 PB10 D6 SPI/SCK
D1 PC1 A4 SPI/MOSI
RES PA10 D2 Reset
DC PB5 D4 Data/Command
CS PB4 D5 SPI/CS

ブレッドボード配線図

ライブラリの準備


必要なファイルをコピーします。
  • Core/Inc/ssd1306.h
  • Core/Inc/ssd1306_conf_templates.h
  • Core/Inc/ssd1306_fonts.h
  • Core/Inc/ssd1306_tests.h
  • Core/Src/ssd1306.c
  • Core/Src/ssd1306_fonts.c
  • Core/Src/ssd1306_tests.c
Core\Inc\ssd1306_conf_template.hを以下のように変更します。

// Choose a microcontroller family
//#define STM32F0
//#define STM32F1
#define STM32F4
//#define STM32L0
//#define STM32L1
//#define STM32L4
//#define STM32F3
//#define STM32H7
//#define STM32F7
//#define STM32G0

// Choose a bus
//#define SSD1306_USE_I2C
#define SSD1306_USE_SPI

// I2C Configuration
//#define SSD1306_I2C_PORT        hi2c1
//#define SSD1306_I2C_ADDR        (0x3C << 1)

// SPI Configuration
#include "main.h"
#define SSD1306_SPI_PORT        hspi2
#define SSD1306_CS_Port         OLED_CS_GPIO_Port
#define SSD1306_CS_Pin          OLED_CS_Pin
#define SSD1306_DC_Port         OLED_DC_GPIO_Port
#define SSD1306_DC_Pin          OLED_DC_Pin
#define SSD1306_Reset_Port      OLED_Res_GPIO_Port
#define SSD1306_Reset_Pin       OLED_Res_Pin

SPI2を使うのでSSD_1306_SPI_PORTの定義をhspi2に変更します。

あまり感心できるものではありませんが、OLED_CS_GPIO_Portなどが定義されていないというエラーが出るので、直前に
#include "main.h"
という一行を追加して、定義されているヘッダーファイルをインポートします。

重要:「ssd1306_conf_template.h」を「ssd1306_conf.h」にRenameします。

ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include "ssd1306_tests.h"
/* USER CODE END Includes */

<>

  /* USER CODE BEGIN 2 */
  ssd1306_TestAll();
  /* USER CODE END 2 */

main.cのコード追加はI2C版と同じです。

実行している様子

FPS表示値

SPI Prescaler:64 (655.25KBits/s) 60
SPI Prescaler:16 (2.625MBits/s) 144
SPI Prescaler: 2 (21.0MBits/s) 217

F446REの駆動クロックはHSI/84MHz。

SPI信号波形

F446RE SPI Prescaler=16


C1:MOSI C2:SCK


SPI版 Nucleo-G431KB


Board SelecotorでNuckeo-G431KBを指定してDefult Modeで初期化。

MXの設定


Pinout & Configuration
  System Core
    Configuration
      GPIO
        PA11 : Output Push Pull : Very High : OLED_Res
        PB4  : Output Push Pull : Very High : OLED_CS
        PB5  : Output Push Pull : Very High : OLED_DC
  Connectivity
    SPI1
      Mode
        Mode: Half Duplex Master
      Configuration
        Parameter Settings
          Basic Parameters
            Data Size: 8bit
          Clock Parameters
            Prescaler(for Baud Rate): 256
            Clock Polarity(CPOL): High
            Clock Phase(CPHA): 2 Edge
        GPIO Settings
          PA5 SPI2_SCK
          PA7 SPI2_MOSI

Nucleo-G431KBではデフォルトでSPI1が使用できるのでSPI1を使います。MISOは使わないのでModeはHalf Duplex Masterにします。重要:Data Sizeがデフォルトでは4bitになっているので、8bitに変更します。

動作確認のためClock Prescalerは大きめにしてクロック周波数を低く設定します。

Clock Polarity(CPOL)、Clock Phase(CPHA)は上記の通り設定すると動作するようです。

またGPIOで制御信号線(CS、RES、DC)を割り当てます。右側のPinout図でPinをクリックしてGPIO_Outputを選択し、User Labelを設定します。User Labelはssd1306_conf_template.hで定義されているものを入力します。

ハードウェアの準備


SSD1306 G431KB Arduino Header 機能
GND GND GND GND
VDD 3V3 3V3 3.3V Power
D0 PA5 A4 SPI/SCK
D1 PA7 A6 SPI/MOSI
RES PA11 D10 Reset
DC PB5 D11 Data/Command
CS PB4 D12 SPI/CS

ブレッドボード配線図


ライブラリの準備


必要なファイルをコピーします。
  • Core/Inc/ssd1306.h
  • Core/Inc/ssd1306_conf_templates.h
  • Core/Inc/ssd1306_fonts.h
  • Core/Inc/ssd1306_tests.h
  • Core/Src/ssd1306.c
  • Core/Src/ssd1306_fonts.c
  • Core/Src/ssd1306_tests.c
Core\Inc\ssd1306_conf_template.hを以下のように変更します。

// Choose a microcontroller family
//#define STM32F0
//#define STM32F1
//#define STM32F4
//#define STM32L0
//#define STM32L1
//#define STM32L4
//#define STM32F3
//#define STM32H7
//#define STM32F7
//#define STM32G0
#define STM32G4

// Choose a bus
//#define SSD1306_USE_I2C
#define SSD1306_USE_SPI

// I2C Configuration
//#define SSD1306_I2C_PORT        hi2c1
//#define SSD1306_I2C_ADDR        (0x3C << 1)

// SPI Configuration
#include "main.h"
#define SSD1306_SPI_PORT        hspi1
#define SSD1306_CS_Port         OLED_CS_GPIO_Port
#define SSD1306_CS_Pin          OLED_CS_Pin
#define SSD1306_DC_Port         OLED_DC_GPIO_Port
#define SSD1306_DC_Pin          OLED_DC_Pin
#define SSD1306_Reset_Port      OLED_Res_GPIO_Port
#define SSD1306_Reset_Pin       OLED_Res_Pin

あまり感心できるものではありませんが、OLED_CS_GPIO_Portなどが定義されていないというエラーが出るので、直前に
#include "main.h"
という一行を追加して、定義されているヘッダーファイルをインポートします。

重要:「ssd1306_conf_template.h」を「ssd1306_conf.h」にRenameします。


ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。


/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "ssd1306_fonts.h"
#include "ssd1306_tests.h"
/* USER CODE END Includes */

<>

  /* USER CODE BEGIN 2 */
  ssd1306_TestAll();
  /* USER CODE END 2 */

main.cはいずれも同じコードです。

実行している様子

FPS表示値

SPI Prescaler:256 (654.062KBits/s) 69
SPI Prescaler: 64 (2.65625MBits/s) 199
SPI Prescaler: 8 (21.25MBits/s) 437
SPI Prescaler: 4 (42.5MBits/s) 動作せず

G431REの駆動クロックはHSI/170MHz。

SPI信号波形

G431KB SPI Prescaler=64


C1:MOSI C2:SCK


2022年10月24日月曜日

SSD1306 OLEDをSTM32で動かす。 4iloライブラリ

Nucleo-F446REとNucleo-G431KBで4lioライブラリを使ってみました。4iloライブラリはI2C版のみの対応です。SPI版のSSD1306 OLEDを使用するには派生ライブラリのafiskonライブラリafiskon/stm32-ssd1306を使います。(次回予定)

以下の記事を参考にさせていただきました。


使用したSTM32CubeIDEのバージョン: 1.10.1

なお、久しぶりにSTM32CubeIDEを立ち上げると延々とUpdateがかかるので事前に一度起動してUpdateを完了させると良いと思います。ST-LinkのファームウェアのアップデートもあるのでLチカなども走らせましょう。

Nucleo-F446RE


Board SelecotorでNuckeo-F446REを指定してDefult Modeで初期化。

MXの設定


STM32CubeIDEでは「Device Configuration Tool」と呼ばれるもので、「.ioc」ファイルを開くとIDE内で起動します。STM32CubeMXと機能的にはほぼ同等なので、このBlogではMXと呼んでいます。STM32CubeMXを別途立ち上げる必要はありません。

Pinout & Configuration
  Connectivity
    I2C1
      Mode
        I2C: I2C
      Configuration
        Parameter Settings
          Master Features
            I2C Speed Mode: Standard Mode
        GPIO Settings
          PB8 I2C1_SCL
          PB9 I2C1_SDA

Parameter Settingsはデフォルトのまま、Standard Mode (100kHz)でOKです。Fast Mode(400kHz)でも動作します。

GPIO Settingsは右側のPinout図で設定すると左下のConfigurationペインに反映されます。ピン割当はArduinoヘッダのD14(PB8)、D15(PB9)に変更しました。

ハードウェアの準備


ブレッドボード配線図
SSD1306 F446RE Arduino Header
GND GND GND
VDD 3V3 3V3
SCK PB8 D15
SDA PB9 D14

ライブラリの準備


4ilo/ssd1306-stm32HALからZIPファイルをダウンロード(右上の「Code」-「Download ZIP」をクリック)して展開しておきます。
  • ssd1306.h
  • fonts.h
を「<Projectフォルダ>\Core\Inc\」に
  • ssd1306.c
  • fonts.c
を「<Projectフォルダ>\Core\Src\」にコピーします。IDEのProject ExplorerでProjectフォルダを右クリックし「Refresh」します。

ソースコード


MXでコード生成して、Core\Src\main.cに以下のコードを追加します。

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include "ssd1306.h"
#include "fonts.h"
/* USER CODE END Includes */

<>

  /* USER CODE BEGIN 2 */
  // Init lcd using one of the stm32HAL i2c typedefs
  ssd1306_Init(&hi2c1);

  // Write data to local screenbuffer
  ssd1306_SetCursor(0, 36);
  ssd1306_WriteString("4ilo", Font_11x18, White);

  // Copy all data from local screenbuffer to the screen
  ssd1306_UpdateScreen(&hi2c1);
  /* USER CODE END 2 */

実行している様子

I2C信号波形


外部Pullupなし Fast Mode(400kHz)

C1:SDA C2:SCL

I2CのSCLとSDAは、SSD1306モジュール上でPullupされているので、外部Pullupしなくても動作します。

外部Pullup 10kΩ  Fast Mode(400kHz)

C1:SDA C2:SCL

外部Pullupすると信号波形がきれいになります。

外部Pullup 2.2kΩ  Fast Mode(400kHz)

C1:SDA C2:SCL

外部Pullupの抵抗値を小さくしても400kHzクロックではあまり変わりませんね。

Nucleo-G431KB


Board SelecotorでNuckeo-G431KBを指定して「Defult Mode」で初期化。

MXの設定


Pinout & Configuration
  Connectivity
    I2C1
      Mode
        I2C: I2C
      Configuration
        Parameter Settings
          I2C Speed Mode: Standard Mode
        GPIO Settings
          PA15: I2C1_SCL
          PB7:  I2C1_SDA

I2C Speed ModeはまずStandard Modeで動作確認し、Fast Mode、Fast Plus Modeに変更して動作確認しました。

ハードウェアの準備


ブレッドボード配線図
SSD1306 G431KB Arduino Header
GND GND GND
VDD 3V3 3V3
SCK PA15 D4
SDA PB9 D5

ライブラリの準備


Nucleo-F446REと同様にファイルをコピーします。

Core/Inc/fonts.h
Core/Inc/ssd1306.h
Core/Src/fonts.c
Core/Src/fonts.h

重要: STMG431KBを使うので、Core/Inc/ssd1306.hを以下のように変更します。

//#include "stm32f4xx_hal.h"
#include "stm32g4xx_hal.h"

main.cへのソースコードの追加はNucleo-F446REの場合と同じです。

実行している様子

I2C Speed Mode


10kΩの外部Pullupした場合、Standard Mode、Fast Modeでは動作しましたが、Fast Mode Plusでは動作しませんでした。外部Pullupを2.2kΩに変更するとFast Mode Plusでも動作しました。

Standard Mode(100kHz) 外部Pullup 10kΩ 

C1:SDA C2:SCL

Fast Mode(400kHz) 外部Pullup 10kΩ 

C1:SDA C2:SCL

Fast Mode Plus(1000kHz) 外部Pullup 10kΩ 

C1:SDA C2:SCL

9bit目でACKされておらず(HIGHに引っ張りきれていない)、以降のI2C通信が失敗しています。

Fast Mode Plus(1000kHz) 外部Pullup 2.2kΩ 

C1:SDA C2:SCL

Pullup抵抗の抵抗値を小さくすると、ACKが認識されて正常に通信が行われています。

注)オシロ波形の下に周波数を表示させていますが、C1:SDAの値です。クロック周波数を知るためにはC2:SCLの値を表示させるべきでした。クロック周波数はC2(青色)の波形数と目盛りから概算できます。

2021年9月5日日曜日

STM32CubeIDE サイクル・カウンタ(CYCLECOUNTER)を使って処理速度を測定

STMicroのWebサイトで、CYCLECOUNTERレジスタを使ってマイコンの処理速度を測定する方法が紹介されています。

マイコンの演算速度を測る(その2)https://www.stmcu.jp/technical/hint/no-085/

GPIOをHigh/Lowさせて計測するよりスマートです。サイクル・カウンタとGPIO両方で処理速度を測定し、比較してみました。サイクル・カウンタの計測とGPIOの出力をオシロで観測した結果はほぼ同じ値になるはずです。

STM32CubeIDEのバージョンは1.7.0です。

MXの設定


Clock
HSE→PLL
HCLK:180MHz

Pinout & Configuration
System Core
  GPIO
    PA10
      GPIO mode: Output Push Pull
      Maximum output speed: Very High
      User Label: PIN_CHK1

ソースコード(main.cに追加)


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_TogglePin(PIN_CHK1_GPIO_Port, PIN_CHK1_Pin);
  }
  /* USER CODE END 3 */

ConfigurationはDebugで、最適化なし(-O0)

最適化の設定方法は「STM32CubeIDE: 覚え書き」の「Buildの設定」で書きました。

サイクル・カウンタで計測


公式ページの説明は少し分かりづらかったので、注釈をつけてみました。


① Run-Debug(F11)でDebug開始


Debugを開始するとint main(void)のHAL_Init()で停止します。

② 希望の測定開始BreakPointまでコード実行(Resume,F8)


行頭をダブルクリックするとブレークポイントを切り替えられます。ブレークポイントを設定し、F8を押します。

③ Window-Show View-Memory Browserを選択し、0xE0001000番地に移動


Memory Browserのテキストボックスに「0xE0001000」と入力し「Go」をクリックします。

④ 0xE0001000のLSBビットを‘1’に変更(CYCLECOUNTERの開始)*初回のみ


Memory Browser内で右クリックしてメニューを表示→「Cell Size」で1byteを選択します。0xE0001000のセルをクリックし「01」と入力しEnter。



⑤ 希望の測定終了BreakPointまでコード実行(Resume,F8)


⑥ 0xE0001004の値を確認(= CYCLECOUNTERの値)


Memory Browser内で右クリックしてメニューを表示→「Cell Size」で2byteを選択、「Radix」でDecimal Unsignedを選択。16byte区切りで10進表示になります。



⑦ 以降の測定Pointについては、0xE0001004の値を‘0’に変えて、②⑤⑥の繰り返し(④の再設定は不要)


0xE0001004, 0xE0001005のセルに「00000」と入力し、Enter。


今回は、

HAL_GPIO_TogglePin(PIN_CHK1_GPIO_Port, PIN_CHK1_Pin);

にBreakPointを置いて実行しました。Resumeすると、Whileループ内で再び同じ行に戻ります。

私の環境では、whileループの1回目は54、2回目以降は50となりました。1回目は何か特別な処理が行われていると思います。

50cycleとすると、180MHz駆動なので、(1 / 180MHz) * 50cycle ≒ 277.7ns

オシロでD2(PA10)の出力を観測


BreakPointを外し、Run-Debug(F11)、Resume(F8)して計測しました


周期 T = 555.2nsなので、Toggle1回分は半分の277.6nsです。

CYCLECOUNTERでの計測の結果とオシロの観測結果がほぼ等しいので、CYCLECOUNTERで処理速度を測定できているようです。

2021年8月12日木曜日

STM32CubeIDE Nucleo-G431KBの実行速度を測定 I2S

I2Sのサンプリング周波数が192kHzの場合、周期 T = 1 / f ≒ 5.21us、L/Rの2chだとその半分の2.60usがリアルタイムな計算に使える時間となります。

前回、FPUを使って算術関数の実行速度を測定しました。G431KBの場合、expf()1000回で1msだったので、1回あたり1usとなります。リアルタイムに計算する場合せいぜいexpf()2回が限度となります。

さらに、I2SのDMA転送が完了するまでメモリの内容はいじれませんので、転送が完了するまでの待ち時間が発生します。転送完了の割り込みが発生してから次の転送開始までどれぐらいの猶予があるか測定しました。

ソースコード(main.cに追加)

/* USER CODE BEGIN PV */
int32_t data[2] = { 0 };
int32_t tx_buffer[2] = { 0 };
/* USER CODE END PV */

<中略>

int main(void)
{
  /* USER CODE BEGIN 2 */
  HAL_I2S_Transmit_DMA(&hi2s2, (uint16_t *)tx_buffer, 2);
  /* USER CODE END 2 */

<中略>

/* USER CODE BEGIN 4 */
static int32_t swap16(int32_t x)
{
	return ((uint32_t)x << 16) | ((uint32_t)x >> 16);
}

void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
	HAL_GPIO_WritePin(CK_HALF_CPLT_GPIO_Port, CK_HALF_CPLT_Pin, GPIO_PIN_SET);

	tx_buffer[0] = swap16(data[0]++);

	HAL_GPIO_WritePin(CK_HALF_CPLT_GPIO_Port, CK_HALF_CPLT_Pin, GPIO_PIN_RESET);
}

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{
	HAL_GPIO_WritePin(CK_CPLT_GPIO_Port, CK_CPLT_Pin, GPIO_PIN_SET);

	tx_buffer[1] = swap16(data[1]--);

	HAL_GPIO_WritePin(CK_CPLT_GPIO_Port, CK_CPLT_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 4 */

32bit値をインクリメント、デクリメントして出力しています。STM32のI2Sは32bitの場合上下16bitを入れ替える必要があり、swap16()で行っています。参考「STM32CubeIDE: I2Sを32bit長で使う

F303K8はSTM32CubeIDEではI2Sの設定ができませんので、F446RE、F767ZI、G431KBで測定しました。

F446REの設定


MXの設定

Pinout & Configuration
  System Core
    GPIO
      PC10
        Maximum output speed: Very High
        User Label: CK_HALF_CPLT
      PC12
        Maximum output speed: Very High
        User Label: CK_CPLT
  Multimedia
    I2S2
   Mode: Half-Duplex Master
      Parameter Settings
        Data and Frame Format: 32 Bits Data on 32 Bits Frame
        Selected Audio Frequency: 192KHz
        Real Audio Frequency: 195.312Khz
      DMA Settings
        SPI2_TX
          Mode: Circular
          Data Width: Half Word / Half Word

Clock Configuration
     HSE->PLL HCLK: 180MHz

プロジェクトの設定

Project - Properties
  C/C++ Build
    Settings
      Configuraion: Release
    Tool Settings
      MCU GCC Compiler
        Optimization
          Optimizaion level: Optimize for speed (-Ofast)

F767ZIの設定


Nucleo-F767ZIはデフォルトでETHが有効化されていて実行時にエラーが発生するので、MXで無効化します。

MXの設定

Pinout & Configuration
  System Core
    GPIO
      PB10
        Maximum output speed: Very High
        User Label: CK_CPLT
      PB11
        Maximum output speed: Very High
        User Label: CK_HALF_CPLT
  Connectivity
    ETH
      Mode: Disable
  Multimedia
    I2S2
   Mode: Half-Duplex Master
      Parameter Settings
        Data and Frame Format: 32 Bits Data on 32 Bits Frame
        Selected Audio Frequency: 192KHz
        Real Audio Frequency: 187.5Khz
      DMA Settings
        SPI2_TX
          Mode: Circular
          Data Width: Half Word / Half Word

Clock Configuration
     HSE->PLL HCLK: 216MHz

プロジェクトの設定

Project - Properties
  C/C++ Build
    Settings
      Configuraion: Release
    Tool Settings
      MCU GCC Compiler
        Optimization
          Optimizaion level: Optimize for speed (-Ofast)

G431GBの設定


Nucleo-G431KBはPF0、PF1を有効にするためにボード上のはんだジャンパSB8、SB11をブリッジしました。



MXの設定

Pinout & Configuration
  System Core
    GPIO
      PA12
        Maximum output speed: Very High
        User Label: CK_HALF_CPLT
      PB0
        Maximum output speed: Very High
        User Label: CK_CPLT
  Multimedia
    I2S2
   Mode: Half-Duplex Master
      Parameter Settings
        Data and Frame Format: 32 Bits Data on 32 Bits Frame
        Selected Audio Frequency: 192KHz
        Real Audio Frequency: 189.732dKhz
      DMA Settings
        SPI2_TX
          Mode: Circular
          Data Width: Half Word / Half Word

Clock Configuration
     HSI->PLL HCLK: 170MHz

プロジェクトの設定

Project - Properties
  C/C++ Build
    Settings
      Configuraion: Release
    Tool Settings
      MCU GCC Compiler
        Optimization
          Optimizaion level: Optimize for speed (-Ofast)

各Nucleoボードのデフォルトの設定で、I2Sを初期化すると割り当てられるピンは以下の通りです。

Board I2S2_CK I2S2_WS I2S2_SD
F446RE PB10 PB12 PC1
F767ZI PD3 PB12 PC3
G431KB PF1 PF0 PA11

実行結果


LchのDMA転送が完了したときに発生する割り込みHAL_I2S_TxHalfCpltCallback()から、RchのI2S通信が開始するWSがHighになる時点までの時間を計測しました。

WSがHighになってRchの通信が開始してからも、しばらくはLchのバッファをいじれるかもしれませんが、よくわかっていないので考慮にいれていません。少なくともRchのCPLTが呼び出されて、MPU本体に制御が戻る前にLchの仕事を完了させないといけません。

F446RE

F767ZI

G431KB


Board 猶予時間(us)
F446RE 1.59
F767ZI 1.1
G431KB 1.89


このテストもF767ZIの成績が芳しくありません。

おおよその目安として、192kHz/2chでI2S出力する場合、リアルタイムに算術関数を1回実行するが精一杯というところでしょうか。

2021年8月2日月曜日

STM32CubeIDE Nucleo-G431KBの実行速度を測定 FPU

Cortex-M4は単精度FPUを備えています。STM32F767ZIはCortex-M7で倍精度も使えます。G431KBとF303K8、F446RE、F767ZIについてsinf()、cosf()、expf()の実行速度を測定しました。

STM32CubeIDEのバージョンは1.7.0です。

ソースコード(Core/Src/main.cに追加)


/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <math.h>
/* USER CODE END Includes */

<中略>

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define PI_F  (3.1415926f)
#define DIV_N (1000)
#define DELTA (2.0f*PI_F/DIV_N)
/* USER CODE END PD */

<中略>

/* USER CODE BEGIN PV */
float v[DIV_N] = {0};
/* USER CODE END PV */

<中略>

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */
#ifdef __GNUC__
#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
#else
#define PUTCHAR_PROTOTYPE int fputc(int ch, FILE *f)
#endif
PUTCHAR_PROTOTYPE
{
	HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, 0xFFFF);
	return ch;
}
/* USER CODE END 0 */

<中略>

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	  volatile float phi = 0.0f;
	  HAL_GPIO_WritePin(PIN_CHK1_GPIO_Port, PIN_CHK1_Pin, GPIO_PIN_SET);
	  for (int i = 0; i < DIV_N; i++) {
		  // Uncomment out the functions to be used.
		  v[i] = sinf(phi);
		  //v[i] = cosf(phi);
		  //v[i] = expf(phi);

		  phi += DELTA;
	  }
	  HAL_GPIO_WritePin(PIN_CHK1_GPIO_Port, PIN_CHK1_Pin, GPIO_PIN_RESET);
	  HAL_Delay(1);

	  //for (int i = 0; i < DIV_N; i++)  printf("%f\r\n", v[i]);
  }
  /* USER CODE END 3 */
}

メインループ内で算術関数をDIV_N=1000回繰り返し、その前後でGPIOをHigh/Low出力しオシロで間隔を測定しました。算術関数の切り替えは原始的にコメントアウトをつけたり外したりしてBuildし直して行っています。

算術関数がちゃんと使えているかどうか、結果が入ったv[]をUART出力(printf())しています。UART出力はかなり時間がかかるので、測定時にはprintf()コメントアウトしました。

STM32CubeIDEでのprintf()については「STM32CubeIDE: UARTでprintfを使う(浮動小数点数型あり)」で書きました。

各Nucleoボードの設定


Clock:
G431KB HSI→PLL 170MHz
F303K8 HSI→PLL  64MHz
F446RE HSE→PLL 180MHz
F767ZI HSE→PLL 216MHz

Pinout & Configuration:
System Core
  GPIO
    <Arduino D2に割り当てられるPin>
        GPIO mode: Output Push Pull
        Maximum ouput speed: Very High (F303K8はHigh)
        User Label: PIN_CHK1

Arduino D2に割り当てられるPinは、F303K8とG431KBはPA12、F446REはPA10、F767ZIはPF15です。

Build Configurations: Release 

Build Configurationsについてですが、DebugとReleaseを切り替えて実行するには、メニューの[Run] - [Run As...] - [1 STM32 Cortex-M C/C++ Application]からDebugかReleaseを選んで実行する必要があります。

最適化: -Ofast

Release Configurationのデフォルトは-Os (サイズ優先)ですが-Ofast (スピード優先)に変更しました。

G431KBの実行結果


sinf()

cosf()

expf()

UART出力


UART出力したデータをRlogin(ターミナルソフト)で受信しグラフ化しました。

sinf()

expf()

実行結果比較


関数名 機種名 周期(ms) パルス幅+(ms) パルス-(ms)
sinf() G431KB 3.005 1.04 1.96
sinf() F303K8 6.01 4.62 1.4
sinf() F446RE 2 1 1
sinf() F767ZI 3 1.6 1.4
cosf() G431KB 3 1.04 1.96
cosf() F303K8 6.01 4.54 1.48
cosf() F446RE 3 1.12 1.88
cosf() F767ZI 3 1.5 1.5
expf() G431KB 2 1 1
expf() F303K8 6.01 4.24 1.76
expf() F446RE 2 0.96 1.04
expf() F767ZI 3 1.28 1.72

周期がほぼ1ms区切りになっていますが、HAL_Delay()を使っているためだと思います。1ms単位のタイマーなので、1ms区切りでHAL_Delay()から抜ける動作をするようです。したがって周期ではなく、出力がHighになっている時間で比較します。


G431GBとF446REが同程度で最も速く、ついでF767ZI、F303K8が最も遅い結果になりました。GPIOの場合ほどではないですが、最も高スペックのはずのF767ZIの成績があまり芳しくないですね。

他の開発環境

以前、mbed OS5とSW4STM32で浮動小数点演算の実行時間を測定しました。


同じF446REの1000回ループで

関数 CubeIDE(us) mbed-cli(us) SW4STM32(us)
sinf() 1000 569 525
cosf() 1120 621 591
expf() 960 958 998

と、sinf()、cosf()の結果がかなり悪くなっています。Build時の設定が何かおかしいのかもしれませんね。

2021年7月29日木曜日

STM32CubeIDE Nucleo-G431KBの実行速度を測定 GPIO

STM32は従来のFシリーズ、Lシリーズの他、Gシリーズ、Hシリーズなどバリエーションが増えています。

STMicroの分類表によると、STM32G4はミックスド・シグナル・アプリケーションに最適化されSTM32F3の上位に位置するようです。基本スペックはCortex-M4/170MHzとなっており、Cortex-M4/180MHzのF4に匹敵します。

Nucleo32のNucleo-G431GBを購入したので実行速度を測定しました。手持ちのF303K8、F446RE、F767ZIを比較対象としました。


左から、F303K8、G431KB、F446RE、F767ZI。

STM32CubeIDEのバージョンは1.7.0です。

G431KBのBuild

Clockの設定


STM32CubeIDEでTarget BoardをNucleo-G431KBとして初期化すると、デフォルトではHSE(外部クロック)が無効化されています。



ボード上にクリスタル(X2)が実装されているようですが、ボード上のはんだブリッジで設定する必要があります。


※赤点線で囲ったあたりがはんだブリッジ

UM2397: STM32G4 Nucleo-32 board User Manualより

HSEを使う場合はSB9とSB10をブリッジします。またMXでクロックのソースを指定するとHSEが使えるようになります。

Pinout & Configuration
  System Core
    RCC
      RCC Mode and Configuration
        High SpeedClock(HSE): Crystal/Ceramic Resonator

※Master Clock Outputを有効化するとPG10よりクロック信号が出力されます。

HSEが有効化された状態


ただし、はんだブリッジのパターン自体はそれほどでもないのですが、周りに微小なSMD部品が配置されているので、はんだ付けに注意が必要です。また、170MHzの信号を測定する環境もないため、今回はHSEは試さずHSIを使うことにしました。HSIでもデフォルトで170MHz駆動することができます。

GPIO出力のの実行速度を測定


実行速度を測定する場合、測定する処理の前後でGPIOをHigh/Low出力して、オシロで間隔を測定することが多いので、手始めとして何も処理をせず単にGPIOをHigh/Lowさせた場合にかかる時間を測定しました。

MXの設定


Target BoardにNucleo G431KBを指定してデフォルトで初期化した後、MXでGPIOを設定します。

Pinout & Configuration
  System Core
    GPIO
      Configuration
        GPIO
          PA12
            GPIO mode: Output Push Pull
            Maximum output speed: Very High
            User Label: PIN_CHK1

PA12はArduinoのD2です。

Projectの設定


Project - properties
  C/C++Build
    Settings
      Tool Settiongs
        MCU GCC Compiler
          Optimization
            Optimization Level: Optimize for speed (-Ofast)

最適化レベルを速度優先にします。なお「-Ofast」を指定すれば、CofigurationがDebugでもReleaseでも実行速度に差は無いようです。

ソースコード(Core/Src/main.cに追加)

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    HAL_GPIO_WritePin(PIN_CHK1_GPIO_Port, PIN_CHK1_Pin, GPIO_PIN_SET);
    HAL_GPIO_WritePin(PIN_CHK1_GPIO_Port, PIN_CHK1_Pin, GPIO_PIN_RESET);
  }
  /* USER CODE END 3 */

実行結果


ch1:PA12(D2)

F303K8のBuild

Pinoutの設定


GPIOの設定で、PA12(ArduinoのD2)をOutputに設定し、PIN_CHK1とラベル付けします。

Clockの設定


HSE→PLLを使って最大クロックの72MHzでBuildすると正常動作しませんでした。


Debuggerで追いかけると、SystemClock_Config()のHAL_RCC_OscConfig()内でエラーが発生し、ErrorHandler()に制御が移ってしまうようです。HSI→PLLを使うと64MHzまでしか設定できませんが、正常動作します。こちらで測定しました。


実行結果

CH1:PA12(D2)

F446REのBuild

Pinoutの設定


GPIOの設定で、PA10(ArduinoのD2)をOutputに設定し、PIN_CHK1とラベル付けします。

Clockの設定


クロックはHSE→PLLで180MHzです。


実行結果

CH1:PA10(D2)

F767ZIのBuild

Pinoutの設定


GPIOの設定で、PF15(ArduinoのD2)をOutputに設定し、PIN_CHK1とラベル付けします。

Clockの設定


クロックはHSE→PLLで216MHzです。


実行時にエラーが発生するのでMXでETHを無効化します。

Connectiviy
  ETH
    Mode: Disable

実行結果

CH1:PF15(D2)

実行速度比較


機種名 パルス幅(ns) 周波数(MHz) 周期(ns)
G431KB 64 7.716 129.6
F303K8 312 1.522 657
F446RE 55 7.825 127.8
F767ZI 272 1.49 671


G431KBはF446REと同程度の速度でGPIOをHigh/Low出来るのがわかります。しかしF303K8が遅いの当然ですが、F767ZIが異常に遅い結果となりました。Clockが216MHzでコアがCortex-M7なのに何故なのでしょうか。

APB1 peripheral clocksが54MHzとなっているのが原因かと思いましたが、F446REもAPB1 peripheral clocksは45MHzです。