2020年5月24日日曜日

STM32でDCCを作る方向で (17) 再生→停止→再生できん件

STM32でDCCを作ろうシリーズ.今回はわりとサラッと終わる.重箱の隅つつきレベルBB+である.

前々回で音が出たところまでレポした.
だが問題が2つあった.
1)win10で動かない     →win10のデバドラを削除更新したら何故か治った
2)起動→再生→停止→再生     2度目以降の再生がうまくいかない

2番目の問題について、丸3日悩んでやっと治った.

2度目の再生をする直前に、バッファのポインタをゼロにしなくちゃいけない.
これを怠るとbitがズレて音がめちゃくちゃになってしまう.
なので3日間もバッファの挙動を調べていた.

余談だが、EZ-USBはstop/startの繋ぎが楽だった.理由はhardwareが剥き出しなのでbuffer emptyなどのstatus signalが1 clock精度で外部pinへ出てくるからだ.ところがSTM32はそうゆうのが全然出てこない.転送完了割り込みはあるけど、割り込みがかかった時点で数clock進んでしまっているはずなのでアテにならない.かったるい.

------
ポインタをゼロにする必要のあるbufferとre-start時の取り扱い
根元から順に.

1)16bit buffer
書く人:   USB IF
re-start時:     write pointer=0にする
方法:   packet受信準備関数 USBD_LL_PrepareReceive() が用意されており、それでptrを引数渡しできるので無問題

2)32bit buffer
書く人:    16bit buf→32bit bufへコピーするprogram
re-start時:    pointer=0
方法:   自分でそのようにprogramすればよいので無問題

3)SPI DMA
作業内容:    32bit bufから読んでSPIへ渡す
re-start時:     read pointer=0
方法:    DMA start関数 HAL_SPI_Transmit_DMA()はptr=0から開始するようになっているので無問題

4)SPI DMA転送完了割り込み
割り込み条件:   32bit bufのを半分/全部消化したら割り込み
re-start時:     read pointer=0
方法:     3番で一蓮托生にptr=0になるので無問題

ここまで検証するためにbuffer dumpとか色々なことをやった.
全部無問題なのだが、2度目の再生で音が破壊される症状は変わらなかった

1~4は、byte単位のズレの無き事を確認したのだったが、実はその先にbit単位のズレが在ったのだ.

5)SPI が再起動する時に、MSB 1stになってない      原因はこれ
SPIはMSB 1stでdataを出力してくれるのが大前提.これが担保されないと、bit streamをブツ切りにする役割のLRCKが不正になってしまい音が破壊されてしまう.しかし2度目の再生時には何bit目から出てくるのかが不定なのだ.腰が抜けるぜ.1度目の再生時の末尾のdataがshift registerに残っているからだろう.
不満が募る.DeInit()をcallすればSPIはdisableにされる(EN=0).ならばEN=0にされた時点でSPI内部FlipFlopもclearしてもらいたい.2度目の利用時にはhardware resetを叩かなくちゃいけないっていうのはかなり不気味だ.
もっともSPI Slave TXなんつう珍妙な使い方をするからこういう地雷を踏むのだろうが.
後日考えるに、1度目の再生の末尾でSPI busyのままブチ切っているのが作法に反しているのだろうと思う.

------
5番を修正するための試行錯誤
要はSPIをどうやって初期化=resetするのか?である.

対策1)2度目の再生の先頭でSPIを初期化する.
HAL_SPI_INIT()みたいな関数があるのでそれをcallしてみる.   →ダメ

対策2)2度目の再生の先頭でSPIをDeInit()Init()する.
DeInit()は構造体やbufferの解放をケアするためである.    →ダメ

対策3)2度目の再生の先頭で、SPIのregisterを強制的にゼロにする.  →ダメ
DeInit()Init()と絡めてみたがダメ.

ここまでやって、registerレベルで初期化してもまだSPI hardwareのどこかにresetされていない場所が密かに在って、それが原因でSPIの再起動時にMSB 1stで出てこないのだと推測された.
そこでDeInit()のsourceを見ると、hardware resetは一切やってないのだと判った.

datasheetには、各peripheral独立のresetが可能だと書かれている.

対策4)2度目の再生の先頭で、SPIをhardware resetする.   →治った
具体的にはこんなcodeでSPIのresetと初期化を行う.
  while(HAL_SPI_DeInit(&hspi3)!=HAL_OK);      ←リソース開放
  __HAL_RCC_SPI3_FORCE_RESET();          ←hard reset
  __HAL_RCC_SPI3_RELEASE_RESET();      ←reset解除
  MX_SPI3_Init();       ←通常の初期化

peripheralはブラックボックスで使いにくいな.

かしこ


0 件のコメント:

コメントを投稿