2024年5月17日金曜日

STM32で1bit DAC出力できるかな(4)完結編 設計資料

秋月でSTM32C011J4M7という8ピンマイコンを買いました.@140円.
これでも32bitCPUでTIMER, UART, ADC, DMAなどが一通り入ってます.
これを使って1bit DAC型のsin波発生器を作りました.USB-C給電です.
ミニマムシステムなのでいろいろな問題を克服できずに終了しました.今回はそんな設計事情をたくさん書いてゆきます.

設計情報詰め合わせをこちらに置きます.
回路図、gerber、3Dプリンタ、写真 などが入っています.


なんで発振器をつくるの?
audio周波数のsin波をいくつも欲しい場合があります.周波数も電圧もアバウトで構いません.そうゆう時は古いスマホを充電して「sin波発生アプリ」でstereo信号を出して使います.わざわざスマホを使うのは面倒なので、小さな発振器が欲しいと思っていました.
↓それで一気に5ヶ作ります.


ピンとRAMとDAC interface
CPUは8pinです.VDD+VSS+programming portで4本確定です.user pinは4本しか残りません.4本にDAC IFをアサインするにはどうすればいいか?

まず考えるのはSPI DACでしょう.接続性は鉄板です.

STM32C011のSPIの仕様はこんな感じです.
 最高bit周波数24MHz
 ヘッダ8bit+データ16bit=24bit
 ゆえにサンプリングレートは24M÷24≒1Msps
1Mspsならばaudio周波数には十分です.

つぎにSTM32C011のRAM容量6kBという制約下でSPIを動かすことを考えます.
やり方は、sine1波分のtableをRAMに用意して、DMAでSPIへ転送します.stack領域などを圧迫しないようにRAMの割り当てを3.6kBにします.
 3.6kB=1800 word(1wordは16bit)
不本意ながら、RAM容量制限のため1800sampleが限度ということです.
1Mspsで1800sampleをグルグル回すと、555HzがDAC出力周波数になります.
もっと低い周波数を欲しければSPI bit周波数を下げる必要があります.こんな計算になります.audio周波数を網羅できそう.合格です.
 SPI bit周波数24MHz →DAC最小周波数555Hz
 SPI bit周波数12MHz →DAC最小周波数278Hz
 SPI bit周波数6MHz  →DAC最小周波数139Hz
 SPI bit周波数3MHz  →DAC最小周波数67Hz
 SPI bit周波数1.5MHz →DAC最小周波数35Hz
 SPI bit周波数750kHz →DAC最小周波数17Hz

ほいだば秋月で売ってるシリアルIF DACを物色してみるなり.
 MCP4921 12bit SPI bit周波数20MHz max
 MCP4922 12bit SPI bit周波数20MHz max
 MCP4725 12bit I2C bit周波数3.4MHz max
 MCP4726 12bit I2C bit周波数3.4MHz max
 PT8211  16bit    bit周波数18MHz max
数100円するわりにSPECがいまいちなんだよなぁ...

SPIやI2CなどのシリアルDACはやめました.

というわけで1bit DACを自作する方向に転換します.
1bit DACですから、余計なヘッダは邪魔ですから、SPIやI2Cは自動的に候補から外れます.
かといって、CPUがGPIOへ出力するのでは速度が足りません.
そこで、IFはI2S一択になります

I2SはDACのIFに使われるもので、STM32シリーズにはなぜか必ず載ってます.I2Sは簡単な3線IFです.SPIと似てます.hardware的にはSPIをちょちょいと改造して実現しているみたいよ.
 LRCK:サンプル周波数.LchRchの切り替えもします
 BCK:16bitモードならばLRCKの32倍周波数
 DATA:シリアルPCMデータ
ここではDATAをPWMとして利用するので、LRCKとBCKは使いません.DATAをLPFするとsin波になります.

3.6kB=28800bitのsine tableとI2Sの組み合わせで実現できるDAC出力周波数を計算します.audio周波数を網羅できそう.合格です.
 I2S Fs 192kHz bit rate 6144kHz →DAC最小周波数213Hz
 
I2S Fs 96kHz  bit rate 3072kHz →DAC最小周波数107Hz
 I2S Fs 48kHz  bit rate 1536kHz →DAC最小周波数53Hz
 I2S Fs 32kHz  bit rate 1024kHz →DAC最小周波数36Hz
 I2S Fs 16kHz  bit rate 512kHz  →DAC最小周波数18Hz


1bit DAC streamの作り方
1bit streamとは、たとえばこの図のようなものです.sin波をPWMで表現したものです.ここでは、CPU内部で数値生成したsin波を元に、PWMに変換する作業になります.
変換作業は技術的にはデルタシグマ変調というものです.デルタシグマの解説はこちらに書きました.
↓天下り的ですが、こうゆうブロック図でsin波→PWM変換できます.
ここでやるsine→PWM変換をこのように定義します.
 ・1波分のPWMデータを生成するのが目的
 ・1波を28800bitとする  (sin波周波数に応じて短くすることもある)
 ・1波分を3.6kBのRAMに書き込む (DMA都合でuint16とする)
 ・float sin()でsineを発生させる
 ・sin(x)のxのstepは、2π/28800 radianである

こうして生成したPWMデータをI2Sで出力すると何Hzのsin波になるのか?
 ・I2SのFs=192kHzとすると、
 ・PWM bit rate=192kHz*32=6144kHz
 ・28800bitで一巡するので、6144k/28800=213Hz

STM32 firmwareのsource codeはこうなってます.変数名はブロック図記載の通りにしてありますので、気長に理解してください.
// sine table
float v2d=0, outd=1;
uint32_t val=0; // 32bit serial to parallel work area
int k=0;
for(int i=0; i<=28800-1; i++){
  // delta sigma operation
  float vin = sinf ( 2 * PI * i / 28800);  sine生成
  float dac = 1.1f * outd;
  float v1 = vin - dac;
  float v2 = v1 + v2d;
  int out = sign(v2);      sign():正なら+1、負なら-1
  v2d = v2;   遅延
  outd = out;   遅延

  // put sine buffer
  int dig = i % 16;
  int out_ = out==1 ? 1 : 0;
  val = val | (out_ << dig);
  if(dig==15) {
    h_bufSin[k++] = val;  16bitに区切ってRAMにストア
    val = 0;
  }
}

以上のようにしてデルタシグマ変調で生成したPWMをフーリエ変換してみました.
左端のpeakがsin波です.それ以外の右側全部がノイズです.LPFでノイズをcutすればsin波だけを残せます.1bit DACの完成です.
ここでノイズをよーく見ると、高域に偏っています.これがデルタシグマ変調の恩恵であるところの「ノイズシェイピング」です.量子化ノイズを減らす効果があります.


回路図
改修がありますがそれは後で説明します.
USB-Cで給電.CPUは3.3V.push SWは周波数変更.LPF cut-offは20kHzぐらい.


プリント基板
57x29mm もっと小さく作りたかったけど.....
A面 パターンカットあり
B面 ジャンパーあり


3Dプリンタで作るケース
2枚貝型ケース.62x34x15mm
ネジ穴は設けてないので接着で対応する前提.わたしはホットメルトが好きです.


周波数変更方法
push SWを押す毎にcyclicに周波数を変更します.周波数は飛び飛びの値です.
下記tableに14種類の{ ①sin波周波数、②I2S Fs、③sin tableのbit数 }が記載されています.③に応じてsin tableを再計算し、②をI2Sに設定し、③をDMAの長さに設定します.
uint32_t   h_freqTable[]={
  18,    I2S_AUDIOFREQ_16K,  16*1800,
  36,    I2S_AUDIOFREQ_32K,  16*1800,
  53,    I2S_AUDIOFREQ_48K,  16*1800,
  107,   I2S_AUDIOFREQ_96K,  16*1800,
  213,   I2S_AUDIOFREQ_192K, 16*1800,
  333,   I2S_AUDIOFREQ_192K, 16*1152,
  500,   I2S_AUDIOFREQ_192K, 16*768,
  1000,  I2S_AUDIOFREQ_192K, 16*384,
  2000,  I2S_AUDIOFREQ_192K, 16*192,
  3310,  I2S_AUDIOFREQ_192K, 16*116,
  5052,  I2S_AUDIOFREQ_192K, 16*76,
  10105, I2S_AUDIOFREQ_192K, 16*38,
  19200, I2S_AUDIOFREQ_192K, 16*20,
  24000, I2S_AUDIOFREQ_192K, 16*16  };


周波数精度
sin波周波数はピッタリ理論値にはならず、数%誤差ります.原因は2つ.
1)RC発振器誤差
部品点数削減のためXtalを省いています.STM32の内蔵RC発振器を使って源発振clk (48MHz)を得ています.RC発振器は数%ズレています.
2)I2S Fs誤差
192kHz,96kHz,48kHz,32kHz,16kHzを作るためにSTM32内蔵PLLで源発振clkを掛けたり割ったりします.整数の掛け算割り算では必ずしもFsとピッタリにならず、数%ズレます.


source code
STM32CubeMXが生成したsource codeを変更します.
変更したfileはmain.cとstm32c0xx_it.cです.
新規追加fileはh_1bit_dac.cとh_1bit_dac.hです.

STM32CubeIDEは1.15.0を使っています.

STM32C011の出荷時設定のままだとI2Sが動きません.reset pin=I2Sと衝突しているからです.回避するためにoption byteの変更が必要です.→こちらを参照

main.c
初期化の後に追記.
h_freq_init();   sine tableを計算
HAL_I2S_Transmit_DMA(......);  I2SをDMA modeで起動

stm32c0xx_it.c
外部pin割り込みハンドラに追記します.
push SWが押されたら割り込みがかかるので周波数を変更します.
void EXTI4_15_IRQHandler(void) {
  HAL_I2S_DeInit(&hi2s1);   I2Sを停める
  h_freq_init();   sine table再計算
  HAL_I2S_Init(&hi2s1);  I2Sを再設定
  HAL_I2S_Transmit_DMA(......);   I2SをDMAモードで再起動
  HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
}

h_1bit_dac.c、h_1bit_dac.h
sine tableを計算する関数h_freq_init()が記載されています.
sin→PWM変換と周波数tableを実装してあります.


push SWチャタリング防止回路
netで見つけた回路で良さそうに動いてます.
要は0.1u+100Ωで平滑化してるってことで.


sin波形
ミニマム構成ゆえに問題はあります.
↓18kHz (I2S Fs=192kHz) 滑らか
↓500Hz (I2S Fs=192kHz) 滑らか
↓200Hz (I2S Fs=192kHz) 滑らか

↓100Hz (I2S Fs=96kHz) 少しバサつき
↓54Hz (I2S Fs=48kHz) バサつき
↓36Hz (I2S Fs=32kHz) バサつき
↓17Hz (I2S Fs=16kHz) バサバサ
問題点は、Fsが低くなるにつれて波形がバサバサになってゆくところです.
この原因は、LPFをFs=192kHzに最適化したためです.Fs=16kHzにもなるとPWMの不要帯域をカットしきれなくてバサバサになってしまいます.本当はLPFもFsに応じて切り替えなくちゃいけないのだけど、CPUのpinが余ってないので無理です.
CPU内部RAMが64kBぐらいあれば、sine tableを長大にできるのでFs=192kHzでsin波=20Hzぐらいまで下げられるんですがね.
ミニマムシステムゆえに許してくれ.


基板改修
OPAMP電源を3.3V→5Vに変えています.
OPAMPには3.3V rail to railが必要です.
秋月で売られているNJU77552なら3.3V電源のままで動きます.
しかし秋月で売られているNJM2746Mだと5V電源にしないと動きません.
NJM2746Mは入力についてはrail to railでないので、電源を5Vにして逃げます.
↓A面でパターンカット.
↓B面でジャンパー(A面から透視図)


かしこ

23 件のコメント:

  1. これ、同じ事が、コレ
    https://akizukidenshi.com/catalog/g/g115178/
    Seeeduino XIAO
    でも出来ますね。(こっちは、@850(税込)なので、価格は高い。まぁそれでも「1000円以下」ではありますが。)
    スペック的には、
    ・ROM:256kB
    ・RAM:32kB
    なので、多分おk
    あと、10bit ですが、D/Aも内蔵してるので、下手すると、ホントに
    ・繋ぐだけ(CRフィルタ位で良い?)
    で、動いちゃいそうです・・・
    ちなみにコレのCPUは、「SAMD21G18」なんですが、チップ単体では、@530 (微妙な値段・・・)
    あと、Arduino 以外の開発環境は?(まぁ、Arduino で充分ですが。)
    ※ちなみに私はコレを3つほど持ってます。出た当初は「安っ!!」と、思ったけど、後から「中華RISC-V」とか出てきたら、あんまし安いと思わなくなったとさ。

    返信削除
    返信
    1. 秋月ってこれも売ってるのかぁ.
      Aliexpressでよく見かけるんですが、なんだろこれみたいに思ってたやつです.
      USB IF標準装備なのでお値段は1ワンランクupってとこなのでしょう.

      削除
    2. 送料を含めるとAliよりも秋月で買うのがベターみたいです

      削除
  2. 秋月扱いは 高くない盲点?

    返信削除
    返信
    1. 円安になってから、Aliexpressは送料をとるようになって割高になってることがよくあります

      削除
    2. こんにちは
      リフォームの続報
      天井はずしたいが ごみが積もり外すと大変なことになりそう
      どうしたものか

      削除
    3. Aliは送料かかるようになったのは高い 一ヶ月待ちで送料無料ならよいですね

      削除
    4. 天井たいへんです。
      ジプトーンならなんとか。
      点検口で済ませられればモアベター。

      削除
    5. それがですね
      断熱材をいれて 埋め込みライトをつけて 石膏ボードをはるという壮大な計画
      ジプトーンはよいらしい

      削除
    6. 夏の空調の効きは大きく改善するでしょう

      削除
    7. そうなんです
      昔の風呂がま みたいに
      沸いたかな?
      ザブーン うわー下半分 水でつめたーい
      で クーラー中は 立ち上がると
      首から上は熱帯ですわ

      削除
    8. ウチは屋根裏と2階天井裏にスタイロフォームを貼りまして、エアコンの効きが改善しました

      削除
    9. コンクリの家でしたか?木造で屋根裏断熱で空気流れがないと屋根裏木材が腐るのブログ見て天井断熱にしようかと
      屋根裏換気口ないので穴開け足場ともいかず 換気ファン予定
      スタイロフォームはなんミリでしたか?
      北海道は130ミリ東京は40ミリらしい
      40ミリ厚さは2050円
      予算が許せば130ミリとか理想主義
      50ミリ2550円二つと30ミリ1550円で6650円
      6畳なら六倍 壁も床もあと熱交換換気扇もと
      予算が膨れ上がる
      10万円コース
      冷房暖房費用削減で元をとるのに5年かかるかな
      夏一万円冬一万円として

      削除
    10. >北海道は130ミリ東京は40ミリらしい

      東京の建築現場を見るとそんなもんに見えますね.

      わたしは25mmを使いました.鉄骨造+ALCです.

      建材値上がり.

      削除
    11. 点検口の大きさ
      木材は45センチ間隔
      30センチ点検口は頭しかはいらなさそう
      45センチまたは60センチ点検口ははいりません
      木材を削って45センチ点検口をつけるか
      木材を切り取り60センチ点検口をつけて補強の木材をつける方法を考えました
      30センチ45センチ60センチ
      どのサイズを使うべきですか
      6畳部屋の3箇所くらい点検口つけようと考えてます
      天井に乗るには体重を支える木材の補強が必要で高さもないので上にあがるには計ってにないですが高さは50センチくらい
      体育座りならもしかしての高さ
      あとで電線やらlanケーブルやら配線するかも?と考え点検口三箇所と思うました

      削除
    12. 木ピッチ455mmとして、木幅が30〜40mmあると思いますので、実際に455点検口の寸法を慎重に合わせてみないと決断は難しい気はします。まぁ木を斬りまくるつもりがあればokokですが。
      300mmなら鉄板だとは思います。

      455だと上半身を天井裏につっこんで電気配線など出来るのが便利です。300だと首しか入らないですよね。

      削除
    13. 600点検口は不要だと思います。

      削除
    14. 天井裏に乗るとしたら、軽鉄の天井なら強度はOKでしょう。
      木の天井だったら荷重的に持たない可能性が高いと思います。
      とは言ってもケースバイケースで、木天井裏に乗って作業した経験はあります。目視で強度判断する事になりんす。

      削除
    15. 木材のセンターからセンターで45センチですので木材の厚み15ミリと15ミリで点検口45センチははいらないですね
      削るの面倒なので木材切ってつなぎ直しですね
      これで点検口を買うサイズがきまりましたのでどもありがとうございます

      削除
    16. 天井から吊るすパイプをつけるため 天井の屋根裏の梁から大きめの木材を取り付けて天井までさげて、そこにパイプをつけて服をぶら下げようかと
      かなり太い材木がいりそうですが上に載っても折れないくらいの太さの、感じでよいでしょうか

      削除
    17. 補強として30x40の垂木材を複数本装着する感じですかね

      削除
  3. 木のわくが45cm間隔で 点検口つけられる大きさかしら?
    天井のベニヤははずす計画。ごみを綺麗にしたいです
    電気工事の圧着ペンチもかわないとかなあ 高そう

    返信削除
    返信
    1. 部屋の荷物をがらんどうにするという倉庫番ゲームのリアルをやらねば
      荷物の受け入れ場所の整理からという 難儀です

      削除