■STM32 エフェクター

■STM32 I2C接続OLEDディスプレイを使う

03s_241_1oled.jpg
秋月電子等で取り扱いがある、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で置き換えるようにします。
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ビットを入れ替えると正しいデータになることがわかります。
03s_240_1o32.png

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

■タグ : マイコン

■HAL_I2SEx_TransmitReceive_DMA使用時の割り込み修正

Owm Pedal ソフトウェア編で書いていた通り、「HAL_I2SEx_TransmitReceive_DMA」使用時に「HAL_I2SEx_TxRxCpltCallback」が起こらないというバグがあります。STM32のコミュニティで質問されていましたがその後のバージョンでも修正されていないので、自力で修正することにします。



プロジェクトフォルダ内のファイルDrivers/STM32F4xx_HAL_Driver/Src/stm32f4xx_hal_i2s_ex.cを編集します。「I2SEx_TxRxDMACplt」関数がDMA転送完了時に呼び出されるようになっています。この関数を見ると、「if (hdma->Init.Mode == DMA_NORMAL)」以下のカッコ内に「/* Call user TxRx complete callback */」以下の記述が入っているため、DMAサーキュラーモードでは何も起きないことがわかります。なのでこの記述をカッコ外に移動すれば解決するはずです。(同フォルダ内にあるstm32f4xx_hal_i2s.cの「I2S_DMATxCplt」関数を見ると正しいコードがわかります。)

細かい修正をすると間違えそうなので、私は単純にHAL_I2SEx_TxRxCpltCallback関数を追加記載することにしました。
static void I2SEx_TxRxDMACplt(DMA_HandleTypeDef *hdma)
{
I2S_HandleTypeDef *hi2s = (I2S_HandleTypeDef *)((DMA_HandleTypeDef *)hdma)->Parent;

HAL_I2SEx_TxRxCpltCallback(hi2s); // ←追加!CubeMXでコード出力すると元に戻るので注意

/* if DMA is configured in DMA_NORMAL mode */
if (hdma->Init.Mode == DMA_NORMAL)
{
if (hi2s->hdmarx == hdma)
{
/* 中略 */
}

if (hi2s->hdmatx == hdma)
{
/* 中略 */
}
}
}

<データ処理方法変更>
1つのブロックにつき「HAL_I2SEx_TxRxHalfCpltCallback」と「HAL_I2SEx_TxRxCpltCallback」の2回割り込みが使えるようになったので、Owm Pedalの信号処理を下図のように変更しました。GitHubのmain.cファイルも変更しています。
03s_239_1owmI2S.png
最初の割り込みでグループA前半の信号処理と送信バッファへの代入を開始し、次の割り込みでグループA後半を処理開始します。処理後のグループA(A')を送信するときには送信バッファへの代入が余裕をもって終わっていると考えられるので、安定した動作が見込めます。ただし、実質的にブロックサイズ半分で処理しているという点には注意が必要です。

■タグ : マイコン

■Owm Pedal ソフトウェア編

自作デジタルエフェクター「Owm Pedal」の各設定、内部プログラミングをしていきます。
Owm Pedal ハードウェア編はこちら
内部データ、STM32CubeMX用iocファイルはGitHub



<ピン、クロック設定>
・ST-LINK接続
STM32CubeMX設定:System Core→SYS→Debug [Serial Wire]

上写真右上に見える2つのジャンパを外し、NucleoボードのST-LINK部分のみ使用します。SWDピンをそのままの順番でOwmボードへ接続できます。今回は外部から電源供給するため、3.3Vピンは接続しません。

・クロック源として外部水晶振動子を使用
STM32CubeMX設定:System Core→RCC→High Speed Clock (HSE) [Crystal/Ceramic Resonator]

▽クロック設定図

I2Sクロックは、オーディオサンプリングレート48kHzに対する誤差が少なくなるような設定としました(48×2048=98304)。

▽全ピン設定図

PC2はデバッグ時オシロスコープを接続する場合に利用しています。

<スイッチ>
割り込みではなくメインループで処理しています。今までAVRマイコンでやっていたカウントを増やす方式なので、長押しにも対応できます。

<ADC>
設定方法は過去記事(→NucleoボードのADCを使う)と同じで、チャンネル数が増えるだけです。ポットの値の取得は低速で構わないため、Sampling Time : 144 Cycles としています。

<I2S>
設定方法は過去記事(→NucleoボードとオーディオコーデックICとの通信)と同じですが、I2Cは不使用です。信号処理を優先するため、DMAのPriorityは[Very High]に、他の割り込みの優先度は下記の通り変更しました。
STM32CubeMX設定:System Core→NVIC→
 Time base: System tick timer→Preemption Priority [10]
 DMA2 stream0 global interrupt→Preemption Priority [5]

今回は受信したデータを信号処理後に送信することになります。I2Sの受信バッファに半分データがたまると「HAL_I2SEx_TxRxHalfCpltCallback」という関数(以下Half関数)が呼び出されます。しかしデータが全て溜まったときの「HAL_I2SEx_TxRxCpltCallback」は呼び出されないバグがあるようです。修正方法がわからない(あまり調べていません)ので、このままHalf関数のみで処理するようにしました。Half関数は送信と受信で2回起こってしまうので、送信側(DMA1 Stream4)の割り込みは無効化しています。

※処理方法を変更しました。→HAL_I2SEx_TransmitReceive_DMA使用時の割り込み修正(2019年3月25日追記)

下図はブロックサイズを32(ステレオなのでバッファ配列の要素数は64)で処理する場合のものです。配列の1グループは16...31, 0...15の順番となります。
03s_238_4owmI2S.png
最初の割り込みでグループAを一旦全て信号処理配列へ移し、次の割り込みで信号処理後のグループA(A')を送信バッファに送ります。このときA'[16]は送信されずA'[17]から送信されてしまいます。そこで処理後データA'を送信バッファへ代入するとき、1サンプルずらしています。もっとスマートに解決する方法(FIFO?)がありそうですが、現状問題が起こっていないので追求していません。遅延時間(レイテンシ)実測値は、ブロックサイズ16で1.7ms、32で2.4ms程度でした。

通常のやり方は、最初の割り込みの時にグループAのデータを1つずつ信号処理と送信を行うというものだと思います。しかしその場合たまに波形が乱れることがあったので、今回は少し回りくどい方法を採用しています。遅延が余計に発生してしまいますが…


<I2Sエラー対処>
たまにI2S通信がうまくいかないことがありましたが、フレームエラーというのが起こっていました。マスター(オーディオコーデックV4220M)がクロック送信する前にスレーブ側(マイコン)のI2S設定をする方がいいようです。デバッグ時は先にV4220Mが起動しているためかエラーが起こりやすく、エラー時はソフトリセットで対応するようにしました。本来はI2Sのみのリセットで済むかもしれませんが、DMAを使っているため全リセットするのが確実でしょう。電源を入れなおした際はV4220Mが後から起動するため、エラーはほぼ発生しません。とはいえオーディオコーデックのリセットピンはマイコンと繋げておくべきだったと思います。

<エフェクト処理>
main.cとmain.hのユーザーコード部分を編集する以外に、2つのファイル(fx.c、overdrive.c)を追加しています。詳細はコード内のコメントをご参照ください。エフェクトはとりあえずオーバードライブで、操作はフットスイッチ(バイパス)、左上ポット(LEVEL)、右上ポット(GAIN)、中央LEDのみ使っています。

overdrive.cでフィルタを使っていますが、過去のデータ(x1、y1)を利用するため、static修飾子を付けて前の計算結果を残したままにしておく必要があります。その結果、フィルタの数だけ関数を準備しておく状態となっています。今後複雑なエフェクトに対応するために、C++言語を使う必要性が出てきそうです。

■Owm Pedal ハードウェア編

03s_237_1owmp.jpg
STM32F405という32ビットマイコンを搭載した自作デジタルエフェクター「Owm Pedal」です。名前は同じマイコンを搭載した既存のペダル「OWL Pedal」をもじってつけました。(Owm Pedal ソフトウェア編はこちら

オーディオコーデックはCS4220のセカンドソース品V4220Mです。サンプリングレートが48kHzまでですが差動入出力で価格が安く(秋月電子で240円)、エフェクターに最適だと考えました。

▽回路図
03s_237_2owms.png
<V4220M周辺>
データシートに入出力の回路が記載してあるのですが、抵抗値はよく使う値へと変更しました。バイアス電圧用に8.25kΩの抵抗があるところは、10kΩと100kΩ2個を並列にして8.33kΩとしています。

V4220Mのデータシートでは音量等をコントロールできそうに書いてありますが、実際はできないようです。マスターモードで動作させる場合は8番(DOUT)ピンに47kΩのプルダウン抵抗を入れます。また、CS4220のデータシートには電源オン時に27番(RSTN)ピンを10msの間LOWにしておくように書いてあるため、10uFのコンデンサを入れました。4・5・8・9番ピンからマイコンへ接続しますが、通信線の長さが短いためダンピング抵抗は不要かと思います。

<電源>
電源はレギュレータで以下のように分けました。
・マイコン用→デジタル3.3V(100mA)
・V4220M用→デジタル5V(20mA) デジタル3.3V(5mA) アナログ5V(60mA)
・OPA1678×3用→アナログ5V(20mA)
アナログ5V電源は通常分離する必要はありませんが、万一問題があったとき基板発注し直すのが嫌なので分けています。

▽DSP基板(Owm Borad)レイアウトについて(KiCadデータはGitHubへ)
真ん中あたりに電源、上側がデジタル、下側がアナログという配置となっています。GNDは裏面を一面プレーンにしました。入力のカップリングコンデンサはPMLCAPを使っており、やや大きくて高価ですが歪率は下がるでしょう。残念ながらBIASに接続すべきところをGNDにつなぐというありがちな間違いをしてしまいましたので、内部写真では妙なジャンパー線が写っています(KiCadデータは修正済)。

ピン間隔が狭いICはパッドを1mm程度長くすると半田付けしやすいです。マイコンのピンはほとんど使えるように引き出しました。一応I2C用にプルアップ抵抗の取り付けもできます。マイコン上側のLEDはデバッグ用のつもりです。水晶振動子周りのパターン設計は下記ページを参考にしました。
水晶振動子 ガイド - RSオンライン

▽ポット類基板レイアウト
03s_237_3owml.png
ポット類基板と筐体はRasPd4のものを使いまわしました。回路図は描いていません。無理やりジャンパーを飛ばしてチャタリング対策の抵抗やコンデンサを入れました。下写真のように基板を合体させます。
03s_237_4owmpp.jpg

とりあえず何もエフェクトをかけないスルー音が出るようにプログラミングし、周波数特性と歪率を測定しました。歪率は、クリップしない最大入力約0.7Vrmsでの結果です。
03s_237_5owmf.png
100Hzの歪率が思ったより悪いですが問題ないでしょう。ノイズも測定限界以下だったので、歪み系エフェクトでもおそらく大丈夫だと思います。

ブログ内検索

メールフォーム

当ブログに関するお問い合わせはこちらからお願いします。 ※FAQ(よくある質問)もお読みください。

お名前
メールアドレス
件名
本文

アクセスカウンター