STM32 計算速度データ
デジタルエフェクターでリアルタイムにデジタル信号処理をする場合、計算が間に合わずまともな音が出なくなるという可能性があります。そこで、様々な計算方法についてどの程度処理時間がかかるのか確認しておくことにしました。使ったのはNUCLEO-F401REで、FPU(浮動小数点演算処理装置)を備えています。開発環境はSTM32CubeIDE(1.0.2)です。
経過時間を確認するには、CPUのクロックサイクルのカウント数(以下サイクル数)を利用すると便利です。下記の記載をしておくと、サイクル数を確認できるようになります。(参考ページ→ARMのData Watchpoint and Trace Unitを使って処理時間計測をしてみよう)
そして下記のようにコードを書きます。「DWT->CYCCNT」に現在のサイクル数が入ります。コンパイル時に最適化されないように、volatileを入れ、サイクル数を変数として使うことにします。
上記「変数の代入」を基準として、処理を追加した場合にどのぐらいサイクル数が増えるかを確認しました。例えば乗算の場合、「ivar[1] = start * stop;」のようにして、変数の乗算を1つ追加するといった具合です。定数を使うと事前に計算されてしまう場合があるため、全て変数を使います。
コンパイル時の最適化オプション「O0(最適化なし)」と「Ofast(最速最適化)」での違いも確認します。1000回平均値ですが結果が結構変わるときがあり、あまり正確ではなさそうです。
<32ビット整数(uint32_t)>
変数の呼び出しに数サイクル必要なのか、代入だけで結構時間がかかるようです。剰余演算がそんなに遅くないというのが意外でした。
<32ビット浮動小数点数(float)>
定数での除算の場合は最適化で乗算と同じ速さになりますが、変数だと遅いままとなってしまいます。
C言語標準ライブラリmath.hにある数学の関数については以下の通りです。
デシベル計算に使うpowfがかなり遅いので、ルックアップテーブルの利用を考える必要がありそうです。
CMSIS-DSPライブラリを導入し、とりあえず2つの関数を使ってみました。(参考ページ→STM32 CubeIDE環境で、CMSIS-DSPを使う方法)
今後、BiQuadフィルタやフーリエ変換等の活用について検討したいと思います。
経過時間を確認するには、CPUのクロックサイクルのカウント数(以下サイクル数)を利用すると便利です。下記の記載をしておくと、サイクル数を確認できるようになります。(参考ページ→ARMのData Watchpoint and Trace Unitを使って処理時間計測をしてみよう)
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
そして下記のようにコードを書きます。「DWT->CYCCNT」に現在のサイクル数が入ります。コンパイル時に最適化されないように、volatileを入れ、サイクル数を変数として使うことにします。
volatile uint32_t start = 0;
volatile uint32_t stop = 0;
volatile uint32_t ivar[50] = {};
volatile uint32_t cnt[50] = {};
start = DWT->CYCCNT; // 開始時サイクル数
ivar[0] = start; // 計測したい処理
stop = DWT->CYCCNT; // 終了時サイクル数
cnt[0] = stop - start; // 処理にかかったサイクル数
上記「変数の代入」を基準として、処理を追加した場合にどのぐらいサイクル数が増えるかを確認しました。例えば乗算の場合、「ivar[1] = start * stop;」のようにして、変数の乗算を1つ追加するといった具合です。定数を使うと事前に計算されてしまう場合があるため、全て変数を使います。
コンパイル時の最適化オプション「O0(最適化なし)」と「Ofast(最速最適化)」での違いも確認します。1000回平均値ですが結果が結構変わるときがあり、あまり正確ではなさそうです。
<32ビット整数(uint32_t)>
サイクル数(O0) | サイクル数(Ofast) | |
何もしない | 8 | 4 |
代入(=)※基準 | 16 | 7 |
加算(+) 減算(-) 乗算(*) | 5 | 3 |
除算(/) | 9 | 7 |
剰余演算(%) | 11 | 8 |
各ビット演算(& | ! ^ >> <<) | 5 | 3 |
<32ビット浮動小数点数(float)>
サイクル数(O0) | サイクル数(Ofast) | |
代入(=)※基準 | 16 | 7 |
加算(+) 減算(-) 乗算(*) | 5 | 4 |
除算(/) | 17 | 17 |
C言語標準ライブラリmath.hにある数学の関数については以下の通りです。
サイクル数(O0) | サイクル数(Ofast) | |
sqrtf | 51 | 16 |
logf | 189 | 189 |
expf | 216 | 207 |
powf | 565 | 571 |
sinf | 82 | 79 |
sinhf | 286 | 283 |
atanf | 189 | 178 |
CMSIS-DSPライブラリを導入し、とりあえず2つの関数を使ってみました。(参考ページ→STM32 CubeIDE環境で、CMSIS-DSPを使う方法)
サイクル数(O0) | サイクル数(Ofast) | |
arm_sqrt_f32 | 91 | 13 |
arm_sin_f32 | 56 | 52 |
タグ : マイコン
STM32 SPI接続タッチパネル付ディスプレイを使う

Amazon等で取り扱いがある、320×240ドットのSPI接続タッチパネル付2.8インチディスプレイモジュールを使ってみます。前回とは違うILI9341という制御ICです。
Nucleoボードとディスプレイモジュールとの接続は下図の通りです。消費電流は実測50mA程度でした。

※電源に5Vではなく3.3Vを供給する場合は、モジュール裏面のジャンパー(J1)を繋ぎます。最初気づいていませんでしたが、一応動作していました。
<STM32CubeIDE(1.0.2) iocファイル Pinout & Configurationタブ>
・右側列 IC画像
設定済ピンを変更しておく
(21)-PA5 : SPI1_SCK
ピン設定
(38)-PC7 : GPIO_Output
(42)-PA9 : GPIO_Output
(43)-PA10 : GPIO_Output
(56)-PB4 : GPIO_Input
(58)-PB6 : GPIO_Output
左側列のConnectivity→SPI1を開く
・中央列上側 Mode
Mode : Full-Duplex Master
・中央列下側 Configuration→Parameter Settingsタブ
Data Size : 8 bits
Prescaler (for Baud Rate) : 64 ※通信がうまくいかない場合は値を大きくして速度を落とします。
<STM32CubeIDE(1.0.2) main.cファイル他>
下記ページのライブラリを利用します。
github.com/afiskon/stm32-ili9341(右側の[Clone or download]ボタンから全ファイルをダウンロードできます。)
Lib/ili9341フォルダ内の7ファイルを自分のプロジェクトの各フォルダへコピーします。
Incフォルダ→fonts.h ili9341.h ili9341_touch.h testimg.h
Srcフォルダ→fonts.c ili9341.c ili9341_touch.c
STM32F3シリーズのNucleoボードを使う場合、ili9341.cとili9341_touch.c最上部に記載されている"stm32f4xx_hal.h"を"stm32f3xx_hal.h"へ変更します。STM32F4シリーズの場合は、そのままでOKです。使用するGPIOピンはili9341.hとili9341_touch.hに#defineで定義されています。
ライブラリのmain.cから以下の3箇所のコードを自分のプロジェクトのmain.cへコピーします。
/* USER CODE BEGIN Includes */〜/* USER CODE END Includes */
/* USER CODE BEGIN 0 */〜/* USER CODE END 0 */
/* USER CODE BEGIN WHILE */~/* USER CODE END WHILE */
実行しディスプレイに画像等が表示されれば成功です。最後の黒い画面ではタッチパネルのテストができます。前回のディスプレイは描画した後毎回画面を更新していましたが、今回はその必要はありません。
タグ : マイコン
Srempy

デジタルエフェクターというとディスプレイをつけたくなるもので、ポットなしでスイッチのみを使って操作するというエフェクターを製作しました。精度よく角穴加工するのはかなり大変なので、Tabby's工房さんにお願いしました。
▽ディスプレイ・スイッチ基板レイアウト

いつものようにチャタリング防止を行っています。OLEDディスプレイ・基板用押ボタンスイッチは秋月電子で購入しました。
DSP基板はOwm Pedalと同じものを使用しています。下写真のように合体させます。

ディスプレイの詳細は前回記事をご覧ください。スイッチ操作は割り込みではなくメインループで処理しています(GitHubはこちら)。エフェクトオフ時にはスイッチの反応が速くなりますが、実用上問題ないので特に対処していません。▲▼スイッチ長押しでフラッシュメモリにパラメータ保存できます。下記ページの内容を使わせていただきました。
・sora lab - STM + HAL Flashの書き込み・読み込み
エフェクトプログラムはもちろん変え放題ですが、今回私が考えたものを紹介したいと思います。※信号処理は旧処理方法(TxRxCpltCallback関数利用なし、16ビット)のままです。
アナログ回路でのフィルタ設計は複雑になる場合が多く、コンデンサの容量値も限られています。また、リアルタイムに動かすとなると可変抵抗やフォトカプラを使うぐらいしか選択肢がありません。デジタル信号処理でのフィルタについては計算方法が確立しているため、専門知識がなくとも設計可能で、任意の定数を動かすこともできます。計算式は下記ページのものを利用させていただきました。
・C++でVST作り - 簡単なデジタルフィルタの実装
フェイザーはノッチフィルタがかかったような周波数特性で、谷となる周波数が動いています(参考ページ→MXR Phase 90 Analysis)。今回はこの特性をピーキングフィルタを使って再現、発展させたエフェクトを考えました。フェイザーとワウを合わせたような効果で、5つのモード(STD、SOFT、INV、HIGH、LOW)があります。名前の由来は3つ(Three)のピーキングフィルタ(Peaking Filter)です。
<STD・SOFTモード>

SOFTモードでは、単純に谷となるフィルタを2つかけています。谷と谷の間は減衰し、スッキリとした感じとなります。STD(スタンダード)モードは、Phase 90の特性に近づけています。うねり感を得るためには、2つの谷の間に山が必要なようです。ちなみに2つの谷の間隔が広いとユニヴァイブのコーラスモードっぽい音になります。谷となる周波数の揺れ幅や高低は、出音への影響は少なかったです。
<INV・HIGH・LOWモード>

フィルタを谷ではなく山にした場合、山が1つだとオートワウのようになります。山を2つにすると、クセは強いもののフェイザーっぽいシュワシュワ感が現れます。INV(インバース)モードでは、2つの山の間にさらに谷を追加しています。HIGH/LOWモードは谷と山を混ぜており、通常のフェイザーの高音域/低音域が強調された感じとなります。
<計算高速化>
BiQuadフィルタの係数は三角関数の計算が入っていて、そのままでは処理に時間がかかり過ぎると思われます。そのため表計算ソフトで近似式を算出するなどして対応しています。それでも実際のCPU使用率は70%程度となってしまいましたので、計算高速化についてはもう少し検討していく予定です。
STM32 I2C接続OLEDディスプレイを使う

秋月電子等で取り扱いがある、128×64ドットのI2C制御有機ELディスプレイモジュールを使ってみます。使われているSSD1306は定番の制御ICらしいです。接続は上写真の通り、GND、VCC(3.3V)、SCL、SDAをつなぎます。SCL、SDAのプルアップ抵抗はモジュールに搭載されているようです。
<STM32CubeMX(5.1.0) Pinout & Configurationタブ>
左側列のConnectivity→I2C1を開く
・中央列上側 Mode
I2C : I2C
・中央列下側 Configuration→Parameter Settingsタブ
Rise Time (ns) : 1000
Fall Time (ns) : 300
・右側列 IC画像
ピン位置を変更
(61)-PB8 : I2C1_SCL
(62)-PB9 : I2C1_SDA
<TrueSTUDIO(9.3.0)>
下記ページのライブラリを利用します。必要最小限となっているので使いやすいと思います。
github.com/4ilo/ssd1306-stm32HAL(右側の[Clone or download]ボタンから全ファイルをダウンロードできます。)
以下の4ファイルを自分のプロジェクトへコピーします。
Incフォルダ→fonts.h ssd1306.h
Srcフォルダ→fonts.c ssd1306.c
私のNucleoボードはSTM32F3シリーズなので、fonts.hとssd1306.h最上部に記載されている"stm32f4xx_hal.h"を"stm32f3xx_hal.h"へ変更します。STM32F4シリーズを使う場合は、そのままでOKです。
ライブラリのmain.cから以下の2箇所のコードを自分のmain.cへコピーします。
/* USER CODE BEGIN Includes */〜/* USER CODE END Includes */
/* USER CODE BEGIN 2 */〜/* USER CODE END 2 */
実行しディスプレイに文字が表示されれば成功です。以下は各関数の簡単な説明です。円などの図形を描く関数はないので、必要なものはssd1306_DrawPixel関数を使って描画することになります。
ssd1306_Init…初期化
ssd1306_Fill…画面全体塗りつぶし
ssd1306_DrawPixel…1ピクセル描画
ssd1306_SetCursor…文字描画位置を設定
ssd1306_WriteString…文字列を描画
ssd1306_UpdateScreen…描画を反映して画面を更新
<画面端の文字を強制描画>
ディスプレイ端のピクセル数が足りない場合、文字描画しないようになっていますが、文字が切れてでも描画するようにします。ssd1306_DrawPixel関数で画面外は描画しないよう制限されているので、たぶん問題ないでしょう。
ssd1306.c内、ssd1306_WriteChar関数の以下の記述を削除
// Check remaining space on current line
if (SSD1306_WIDTH <= (SSD1306.CurrentX + Font.FontWidth) ||
SSD1306_HEIGHT <= (SSD1306.CurrentY + Font.FontHeight))
{
// Not enough space on current line
return 0;
}
<フォント自作>
font.cにフォントのデータが入っており、自分で文字データを作成することができます。例として7x10ピクセルの「2」を0と1で描きます。横は左側7列だけ使いますが、後々のために16列準備します。
0011100000000000
0100010000000000
0100010000000000
0000010000000000
0000100000000000
0001000000000000
0010000000000000
0111110000000000
0000000000000000
0000000000000000
1行ずつを16桁の2進数とみなし、16進数へ変換します。→0x3800, 0x4400, 0x4400, 0x0400, 0x0800, 0x1000, 0x2000, 0x7C00, 0x0000, 0x0000
これを「2」のフォントデータとすればOKです。自動計算できる表計算ファイル(LibreOfficeやOpenOfficeで開けるodsファイル)をGitHubに置いています。
タグ : マイコン
STM32 I2S DMA利用時の32ビット対応
たいていのオーディオコーデックICでは、24ビットや32ビットのデータを取り扱うことができます。Owm Pedalは16ビットに設定していましたが、32ビットに対応できるよう変更します。
リファレンスマニュアルによると、I2Sで32ビットデータを取り扱うには16ビットずつデータ転送する必要があります。DMAは16ビット(Half Word)設定のままでうまく処理するようです。しかしここで問題があり、上位16ビットと下位16ビットが入れ替わって送受信されてしまいます。STM32のコミュニティで質問されていましたが、修正するには自分でビットを入れ替えるコードを書くしかないようです。
単純に元の数を左シフトしたものと右シフトしたものを合わせればいいのですが、負の数を右シフトすると左側が1で埋まるので、0で置き換えるようにします。
符号なし整数へ型変換してから右シフトする方法もあります。処理速度は上記と変わりませんでした。
その他の変更部分については下記の通りです。
・STM32CubeMX設定
Multimedia→I2S2→Configuration→Parameter Settings
→Data and Frame Format [32 Bits Data on 32 Bits Frame]
・送受信バッファを32ビット整数へ
volatile int32_t RX_BUFFER[BLOCK_SIZE*2] = {}; // 受信バッファ
volatile int32_t TX_BUFFER[BLOCK_SIZE*2] = {}; // 送信バッファ
・float変換時の乗除算に使う数を変更(32ビット符号付き整数の範囲は-2147483648~+2147483647)
32768.0f→2147483648.0f
Owm Pedalに正弦波を入力し、実際のデータで確認しました。DMAで受信したデータはバラバラの値ですが、上下16ビットを入れ替えると正しいデータになることがわかります。

GitHubのmain.cファイルも変更しています。オーディオコーデックV4220Mでは上位24ビット分のみ有効なので、下位8ビット分は0として取り扱われますが特に支障はないでしょう。
リファレンスマニュアルによると、I2Sで32ビットデータを取り扱うには16ビットずつデータ転送する必要があります。DMAは16ビット(Half Word)設定のままでうまく処理するようです。しかしここで問題があり、上位16ビットと下位16ビットが入れ替わって送受信されてしまいます。STM32のコミュニティで質問されていましたが、修正するには自分でビットを入れ替えるコードを書くしかないようです。
単純に元の数を左シフトしたものと右シフトしたものを合わせればいいのですが、負の数を右シフトすると左側が1で埋まるので、0で置き換えるようにします。
int32_t swap16(int32_t x)
{
return (0x0000FFFF & x >> 16) | x << 16;
}
符号なし整数へ型変換してから右シフトする方法もあります。処理速度は上記と変わりませんでした。
int32_t swap16(int32_t x)
{
return (int32_t)((uint32_t) x >> 16) | x << 16;
}
その他の変更部分については下記の通りです。
・STM32CubeMX設定
Multimedia→I2S2→Configuration→Parameter Settings
→Data and Frame Format [32 Bits Data on 32 Bits Frame]
・送受信バッファを32ビット整数へ
volatile int32_t RX_BUFFER[BLOCK_SIZE*2] = {}; // 受信バッファ
volatile int32_t TX_BUFFER[BLOCK_SIZE*2] = {}; // 送信バッファ
・float変換時の乗除算に使う数を変更(32ビット符号付き整数の範囲は-2147483648~+2147483647)
32768.0f→2147483648.0f
Owm Pedalに正弦波を入力し、実際のデータで確認しました。DMAで受信したデータはバラバラの値ですが、上下16ビットを入れ替えると正しいデータになることがわかります。

GitHubのmain.cファイルも変更しています。オーディオコーデックV4220Mでは上位24ビット分のみ有効なので、下位8ビット分は0として取り扱われますが特に支障はないでしょう。
タグ : マイコン