2022年8月28日日曜日

FV01 マルチエフェクタの製作 その4 補足

FV01 Ver.1.0を製作してわかった問題点についての考察です。

パネルマウント


今回は秋月のロータリースイッチを使用しており、パネルは12HP(60.6mm)です。
小径ロータリースイッチを使えば10HP(50.5mm)にできそうです。

DRY/WET回路の改善

インターフェイス回路図
FV-1コア回路図

DRY/WET回路1


DRY/WET回路はRV2A/Bの可変抵抗器周辺です。可変抵抗を中点あたりに設定すると音量が低下する問題です。

DRY/WET回路部分のみシミュレーションしました。

シミュレーション回路図

R1、R2が50kΩの可変抵抗器です。R1+R3とR4の反転増幅回路とR2+R3とR4の反転増幅回路の重ね合わせと考えることができます。

過渡解析

シミュレーションで一番振幅が小さくなった場合(POT中点25kΩ:25kΩ)、±0.89Vで2 * 0.89 = 1.78Vp-pとなり実機での測定と一致します。

DRY/WET回路2


もう一つDRY/WET回路でよく使われる回路もシミュレーションしました。

シミュレーション回路図

R3とR4が50kΩの可変抵抗器です。

過渡解析

こちらは逆に可変抵抗器が中点になったとき振幅が最大になります。

DRY/WET回路1にバッファを入れる


分圧回路と反転増幅回路の干渉を避けるためにボルテージフォロワのバッファを入れてみました。

シミュレーション回路図

過渡解析

これは望み通り出力振幅が一定となります。

DRY/WET回路1を非反転増幅回路に変える


増幅率1だとボルテージフォロワになってしまうので、(1+3.3)倍の非反転増幅回路にしてシミュレーションしました。非反転増幅回路は入力インピーダンスが非常に高いので抵抗分圧回路の影響を受けません。

シミュレーション回路図

過渡解析

こちらも振幅が一定となります。インターフェイス回路の入力が反転増幅回路なので、全体として位相が反転してしまいます。この場合は入力も非反転増幅回路にしたほうが良さそうですが、非反転増幅回路は1倍以下の増幅率(減衰)が設定できないので、入力部は抵抗による分圧+バッファという構成になりそうです。

FV-1コア部の出力とDRY/WET回路の干渉


FV-1コア部の出力インピーダンスが高いため、DRY/WET回路でWET側に振り切ってもDRY(原音)が残ってしまう問題があります。

シミュレーション回路図

過渡解析

WET側(赤の線)で逆相の原音がMIXされるため振幅が小さくなっています。中点(青の線)でもDRY側の信号成分が少し残っています。

この問題もFV-1コア部の出力にバッファを入れれば改善されます。

シミュレーション回路図

過渡解析

DRY側、WET側ともに振幅が±1Vになっています。

中点(青の線)では多少波が残っていてDRY音、WET音が0Vでクロスするところで振幅が大きくなっているようです。

過渡解析(拡大)

これはWET側がRC一次HPF、LPFを通過しているため位相がずれるのが原因です。実機では問題ないでしょう。

フィルターを除去したシミュレーション回路図

過渡解析(拡大)

フィルターを介さない場合中点(青の線)の振幅がほぼ0になります。

まとめ

実機を使ってみると、DRY/WETの中点あたりで音量が下がってしまうのはかなり気になります。

WET側に振り切っても原音が残るのは、リバーブ等の普通のエフェクトの場合はそれほど気になりませんが、ピッチシフトで完全に音程を変えたい場合は困ります。

部品数が増えるのはあまり好みではないのですが、デジタルエフェクタということを考えると仕方ないかと思います。

OPアンプのボルテージフォロワは面積を取るので、トランジスタのエミッタフォロワとACカップリングで繋いでいくというのも、ギターエフェクタならまだしもシンセ用にちょっとどうかな、と思います。

2022年8月25日木曜日

FV01 マルチエフェクタの製作 その3 EEPROMのプログラミングにRaspberry Pi Picoを利用

FV-1はEEPROMに書き込んだ外部プログラムを利用できます。プログラム開発はSpinAsm IDEで行います。FV-1の公式ページの一番下あたりに「software :: SpinAsm assembler for the SPN1001V1.1.31 (Windows executable)」というリンクがあり、ダウンロードできます。

SpinAsm IDEから出力されるのはHEXファイル(Intel HEX形式)です。SPN1001-DEVBという開発ボードを使えばSpinAsm IDEから直接EEPROMに書き込み可能でスマートに開発できるようです。

あまりスマートではないですが、HEXファイルをバイナリに変換しEEPROMに書き込めば良いので、安価なマイコンボードであるRapsberry Pi Picoを利用することにしました。

ブロック図

太い線で囲ったところがRaspberry Pi picoを使って製作したPico Rom Writerです。

WindowsのターミナルソフトにはTeraTermがよく使われていますが、私は機能が豊富なRLoginを使用しています。今回もRLoginのファイル送信機能に頼っています。

ハードウェアPicoRomWriterの製作



回路図

I2CのSCLとSDAはFV-1で内部プルアップされ、Raspberry Pi picoでも内部プルアップされています。さらにPico Rom Writerでもピンソケットを使ってプルアップ可能としています。外部プルアップは信号線の波形を見て決めます。並列にプルアップされることになるのでプルアップする場合は抵抗値を大きめにします。

Raspberry Pi Picoはリセットボタンがないので、RESET(RUN)ボタンを外付けしています。

Raspberry Pi PicoでMicro Pythonを実行する準備


MicroPythonファームウェア(UF2ファイル)をダウンロードしておきます。


以下の手順でUF2ファイルをPicoに書き込みます。

  • PicoをBOOTSELボタンを押しながらパソコンにUSBケーブルで繋げる。
  • ストレージとして認識され、エクスプローラーが開く。
  • UF2ファイルをPicoのドライブにコピー。

EEPROMライティングプログラム SpinRomWriter


USBシリアル通信でパソコンから.hexファイルを受信し、バイナリに変換した後、I2C経由でFV01の外付けEEPROMに書き込むMicroPythonのプログラムです。

SpinRomWriter.py
#
# SpinRomWriter.py
#
# SpinAsmが出力したHEXファイルを24LC32A(EEPROM)に書き込む
#
# Target: Raspberry Pi pico / micropython
#
# 2022.08.20
#
import time
import sys
from machine import Pin, I2C

TITLE_STR = 'SpinRomWriter 20220820'

N_BYTES = 4         # 一度に読み書きするバイト数
DATA_LEN = 4096     # 32KBits
MAX_LINES = 1026    # シリアルで読み込む最大行数

PIN_LED = Pin(25, Pin.OUT)

# HEX文字列をバイナリに変換
def hex2bin(hexstr):
    if hexstr[0] != ':':
        print('Invalid header character')
        return -1
    hexstr = hexstr[1:]
    data = []
    for i in range(0, len(hexstr), 2):
        s = hexstr[i : i + 2]
        data.append(int(s, 16))
    return data
    
# チェックサム
def checksum(data):
    sum = 0
    for n in data:
        sum += n
    sum &= 0xff
    return sum

# シリアルからHEXファイルを受信
def receiveData():
    bArr = bytearray(DATA_LEN)
    for i in range(MAX_LINES):
        if i % 32 == 0:
            PIN_LED.value(not PIN_LED.value())
        hexStr = input()
        data = hex2bin(hexStr)
        if (data == -1):
            print('line:', i + 1, ': data is invalid')
            return -1
        # チェックサム
        if (checksum(data) != 0):
            print('line:', i + 1, ': checkusm is invalid')
            return -1
        # データエンドを検出
        if (data[3] == 1):
            return bArr;
        # データをbytearrayに格納
        n = data[0]
        addr = (data[1] << 8) | data[2]
        for i in range(n):
            bArr[addr + i] = data[4 + i]
    else:
        print('No data end detected.')
        return -1

# EEPROMに書き込み
def writeEEPROM(bArr, dataLen, nBytes):
    for memaddr in range(0, dataLen, nBytes):
        if memaddr % 32 == 0:
            PIN_LED.value(not PIN_LED.value())
        ba = bArr[memaddr:memaddr+nBytes]
        i2c.writeto_mem(0x50, memaddr, ba, addrsize=16)
        time.sleep_ms(5)

# EEPROMから読み込み
def readEEPROM(dataLen, nBytes):
    bArr = bytearray()
    for memaddr in range(0, dataLen, nBytes):
        ba = i2c.readfrom_mem(0x50, memaddr, nBytes, addrsize=16)
        bArr.extend(ba)
    return bArr

# EEPROMのベリファイ
def verifyEEPROM(bArr1, bArr2):
    bytesOK = 0
    for memaddr in range(len(bArr1)):
        b1 = bArr1[memaddr]
        b2 = bArr2[memaddr]
        if (b1 == b2):
            bytesOK += 1
        else:
            print('%04X:%02X:%02X Data is invalid.' % (memaddr, b1, b2))
    return bytesOK
    
# データを比較して表示
def showComparison(bArr1, bArr2):
    for memaddr in range(0, DATA_LEN, N_BYTES):
        ba1 = bArr[memaddr:memaddr+N_BYTES]
        ba2 = bArr2[memaddr:memaddr+N_BYTES]
        print('ORG:%04X:' % memaddr, end='')
        for b1 in ba1:
            print('%02X:' % b1, end='')
        print('')
        print('ROM:%04X:' % memaddr, end='')
        for b2 in ba2:
            print('%02X:' % b2, end='')
        print('')

# メインルーチン
time.sleep_ms(3000)
while True:
    print(TITLE_STR)
    print('Please send a HEX file.')
    # データを受信
    bArr = receiveData()
    PIN_LED.value(0)
    if (bArr == -1):
        continue
    print('HEX file received successfully.')

    i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=100000)

    # EEPROMに書き込み
    print("Write to EEPROM.")
    writeEEPROM(bArr, DATA_LEN, N_BYTES)
    PIN_LED.value(0)

    # EEROMから読み込み
    print("Read from EEPROM.")
    bArr2 = readEEPROM(DATA_LEN, N_BYTES)

    #showComparison(bArr, bArr2)

    print("Verify data.")
    bytesOK = verifyEEPROM(bArr, bArr2)

    print('Verified:', bytesOK, 'bytes')

SpinASM IDEはソースの.spnファイルを8個まとめてIntel Hex形式の.hexファイルを出力します。Intel Hex形式(.hex)はバイナリファイルをテキスト形式にしたものです。

一例をあげるとこのような形式になります。
:04003C001333014D2C
:00000001FF
フォーマットについては割愛しますが、SpinASM IDEが出力する.hexファイルの最終行(データの終了)は「:00000001FF」です。

プログラム中で「bytearray」クラスを使用しています。通常、配列には「リスト」を用いますが、Raspberry Pi PicoのMicroPythonでは大きなサイズ(4kB以上)の配列を「リスト」では使えないためです。

EEPROMライティングプログラム SpinRomWrter.pyをpicoに書き込む


EEPROMライティングプログラムの書き込みはThonnyを使います。Thonnyをダウンロードしてインストールします。

「ツール」-「options..」-「インタプリタ」でインタープリタに「MicorPython(Raspberry Pi Pico」を指定します。

Raspberry Pi PicoをパソコンにUSBケーブルで接続します。

上記プログラムをThonnyのエディタに入力し、以下の手順でPicoに書き込みます。

  • Thonnyのツールバーの「STOP」ボタンを押す。(ThonnyがPicoに接続)
  • 「ファイル」-「Save As..」
  • 「Where to save to?」ダイアログで「Raspberry Pi Pico」を選択。
  • 「File Name」を「main.py」として保存。

ファイル名を「main.py」にするとPicoの起動時に自動実行されます。

使用手順


RLoginをダウンロードしてインストールしておきます。

SpinASM IDEで「Project Dialog」を開きます。


Projectのスロットに.spnファイルを割当て、「Build」ボタンを押すと.hexファイルが「SpinAsm_IDE\hexout」フォルダに作られます。


※Thonnyが起動している場合は、「実行」-「Disconnect」で接続を切っておきます

以下の手順でFV-1のプログラムをEEPROMに書き込みます。
  • RLoginを起動

  • PicoRomWriterをUSBケーブルで接続

  • RLoginでPicoにシリアル接続(115200bps)



  • PicoRomWriterのRESET(RUN)ボタンを押す

  • Rloginで約3秒以内に再接続
    PicoRomWriterのRESET(RUN)ボタンを押すとUSB接続が一旦切れるので約3秒以内に再接続します。

  • ターミナルに「Please send a HEX file.」と表示されるのを確認

  • RLoginの「ファイル」-「XYZModemファイル転送」-「5ファイルアップロード」

  • 「変換処理」と「送信処理」を以下のように設定。




  • SpinASMで作成した.hexファイルを開くと送信が始まり、進捗ダイアログが表示されます。また、Picoの基板上のLEDが点滅します。



  • 「Verified: 4096bytes」と表示され、再び「Please send a HEX file.」と表示されるのを確認します。

以上でEEPROMにFV-1のプログラムが書き込まれます。

ブレッドボードを使ってEEPROMに書き込んでいる様子

EEPROMの電源はPicoRomWriterから供給できます。(3.3V)

FV01のオンボードのEEPROMに書き込んでいる様子

2022年8月18日木曜日

FV01 マルチエフェクタの製作 その2 インターフェイス回路

回路図

入力信号を1/3に減衰させ、FV-1に入力。FV-1の出力と1/3に減衰させた入力信号をMIXし、3倍に増幅して元の振幅に戻す回路です。

簡単な回路ですが、問題点が3点ありました。

  • モノラル入力で使用するとき、原音がMIXされない。
  • DRY/WET回路でWET側に振り切っても原音が残る。
  • DRY/WET回路で中間付近にしたとき出力振幅が低下する。

モノラル入力で使用する場合の問題


回路図を追ってください。「SW-1(SW-MONO)」でFV-1のRFXINに入力される信号を入力のL/Rどちらにするか切り替えています。LにするとFV-1のLFXIN/RFXINはどちらも同じ入力信号(LIN)が入力されモノラル入力→ステレオ出力となります。

ところが後段のミキサーのDRY側(原音)がRINのままであり、RINに入力がない場合は、DRY/WETを調節してもFV-1の出力振幅が変化するだけで、原音はMIXされないことになってしまいます。

したがって、SW_MONOは使用できず、モノラル入力で使用する場合は、外部でMultipleモジュールなどを使って信号を分岐させ、LIN/RIN両方に入力する必要があります。

直すとすれば、LIN入力を分岐させてR側に振ってしまうのが簡単でしょうか。

DRY/WET回路の問題


インターフェイス部の図面だとわからないのですが、コア部の図面を見ると出力にLPF/HPFが入っていて単純にRだけでも1kΩの出力インピーダンスがあります。

R15、R17が1kΩの出力インピーダンスとなる。

インターフェイス部の可変抵抗RV2で原音とエフェクト音を分圧して、U1/B、U2/Bの反転アンプに入力しているので、RV2をWET側に振り切っても50kΩ:1kΩの分圧になって、原音がいくらか残留してしまうことになります。

またDRY/WETを調節する可変抵抗R2を中点付近にすると、DRYのみ、WETのみの場合に比べてMIXされた信号の振幅が小さくなってしまうようです。

インターフェイス回路の特性


インターフェイス回路が思惑通りの特性が出ているかどうか測定しました。FV-1モジュールは挿入しない状態での測定です。

IN→FXIN 


入力からFV-1に渡すまで信号振幅が1/3になっているかどうか。POTはInput Level:最大、DRY/WET:DRY側としています。入力信号は1kHz/2Vp-p正弦波です。

IN→FXIN入出力(Lch)

C1:LIN C2:LFXIN

IN→FXIN入出力(Rch)

C1:RIN C2:RFXIN

正確には100kΩと330kΩなので、入力に対して1/3.3の振幅となります。

2.0Vp-p × (1 / 3.3) ≒ 0.61Vp-p

反転増幅アンプなので出力は逆位相です。

IN→FXIN周波数特性(Lch)

IN→FXIN周波数特性(Rch)

約1/3なので-10dBです。

FXOUT→OUT


FV-1の出力が3倍の信号振幅になって出力されているかどうか。POTはInput Level:最大、DRY/WET:WET側としています。入力信号は1kHz/2Vp-p正弦波です。

FXOUT→OUT入出力(Lch)

C1:LFXOUT C2:LOUT

FXOUT→OUT入出力(Rch)

C1:RFXOUT C2:ROUT

入力アンプとは逆に330kΩと100kΩなので、入力に対して3.3倍の振幅となります。

2.0Vp-p × 3.3 = 6.6Vp-p


反転増幅アンプなので出力は逆位相です。

FXOUT→OUT周波数特性(Lch)

FXOUT→OUT周波数特性(Rch)

約3倍なので+10dBです。低域の位相は暴れて見えますが、180°前後で少しふらついているだけです。

IN→OUT


原音が元の信号振幅のまま出力されているかどうか。POTはInput Level:最大です。FV-1からの出力を受けるJP2、JP3は短絡しています。FV-1モジュールは挿入していませんが、R16、R18により接地されていて、WET側の入力はGNDになります。

IN→OUT入出力 RV2:DRY側(Lch)

C1:LIN C2:LOUT

IN→OUT入出力 RV2:DRY側(Rch)

C1:RIN C2:ROUT

IN→OUT周波数特性 RV2:DRY側(Lch)

IN→OUT周波数特性 RV2:DRY側(Rch)

可聴帯域ではほぼ原音のままと言って良いと思います。

IN→OUT入出力 RV2:WET側(Lch)

C1:LIN C2:LOUT

IN→OUT入出力 RV2:WET側(Rch)

前述の通り、困ったことにWET側に振り切っても原音が出力されています。

IN×FVOUT→OUT


入力信号とエフェクト信号がMIXされて信号振幅が一定のまま出力されるかどうか。

IN端子とFXOUT(JP2、JP3)にそれぞれ1kHz/2Vp-pの正弦波を同相、逆相で入力し、OUTの出力を観測しました。

同相入力


IN×FVOUT→OUT 同相入力 DRY側(Lch)

C1:LIN C2:LOUT

IN×FVOUT→OUT 同相入力 DRY側(Rch)

C1:RIN C2:ROUT

IN×FVOUT→OUT 同相入力 DRY/WET中間付近(Lch)

C1:LIN C2:LOUT

IN×FVOUT→OUT 同相入力 DRY/WET中間付近(Rch)

C1:RIN C2:ROUT

少しややこしいですが、L/R INからの入力はU1A、U2Aの反転増幅アンプを通過しているのでMIXされる時点では逆相になります。逆相の信号を足し合わせると0となります。

IN×FVOUT→OUT 同相入力 WET側(Lch)

C1:LIN C2:LOUT

IN×FVOUT→OUT 同相入力 WET側(Rch)

C1:RIN C2:ROUT

WET側のエフェクト音はU1B、U2Bの反転増幅アンプのみを通っているので、WET側に振り切ると出力は逆相になります。

逆相入力


IN×FVOUT→OUT 逆相入力 DRY側(Lch)

C1:LIN C2:LOUT

IN×FVOUT→OUT 逆相入力 DRY側(Rch)

C1:RIN C2:ROUT

IN×FVOUT→OUT 逆相入力 DRY/WET中間付近(Lch)

C1:LIN C2:LOUT

IN×FVOUT→OUT 逆相入力 DRY/WET中間付近(Rch)

C1:RIN C2:ROUT

この場合、MIXされる時点では同相信号になっており、合成された出力信号は半分半分が足し合わされ元の振幅になって欲しいのですが、入力が2Vp-pなのに対し出力が1.79Vp-pに低下しています。

IN×FVOUT→OUT 逆相入力 WET側(Lch)

C1:LIN C2:LOUT

IN×FVOUT→OUT 逆相入力 WET側(Rch)

WET側は逆相入力で、反転アンプを通っているので結局DRY側と同相信号が出力されています。

長くなりました。

メモ


原音が残留する問題は、FV-1の出力フィルタをRCフィルタではなくアクティブフィルタにすれば解決しそうです。理想的にはアクティブフィルタの出力インピーダンスは0です。

MIXの中間付近で振幅が低下するのは、POTによる分圧と反転増幅回路の間にボルテージフォロワなどのバッファを入れれば改善されそうですが、抵抗だけで合成するのではなく、反転加算回路として扱ったほうがスッキリするかも?

DRY/WET回路についてはもう少し勉強した方がよさそうです。
 
現状の回路は、使えなくはないが文句言われたらすみませんでしたという感じです。