Owm Pedal H7 ハードウェア編

03s_253_1owmHp.jpg
STM32F405を搭載した「Owm Pedal」に引き続き、STM32H753とタッチパネル付ディスプレイを備えたデジタルエフェクターを製作しました。今回も穴加工はTabby's工房さんにお願いしました。(Owm Pedal H7 ソフトウェア編はこちら

▽入出力・電源部 回路図
03s_253_2owmHas.png
4回路入りオペアンプOPA1679を使用することにしたので、以前と違い入力部のローパスフィルタが2次になっています。デジタル電源は絶縁型DC-DCコンバータで分離するようにしました。コンバータなしにもできるようにしましたが、ノイズ面でよくないと思われるので非推奨です。

▽マイコン・オーディオコーデック部 回路図
03s_253_3owmHds.png
当初はSTM32F722を使っていたのですが、さらなる発展を見据えSTM32H7を使うことにしました。注文時の在庫の関係でSTM32H753にしましたが、STM32H743でも問題ないと思います。今回はオーディオサンプリングレート44.1kHzで、SAIペリフェラルを使います。

▽基板レイアウトについて(KiCadデータはGitHubへ)
上側のマイコン基板は4層にしてみました。JLCPCBの場合、50mm×50mm以内であれば4層でも13ドルで済みます。マイコンは可能な限りピンを引き出していますが、全部は使わないので内側は小さいパッドです。GNDは基本一面プレーンで、基板が2枚に分かれているのでコネクタ部分でGNDを接続することになります。DC-DCコンバータは高さを低くするため寝かせて配置しました。

▽ディスプレイ・スイッチ基板レイアウト
03s_253_4owmHl.png
念のためダンピング抵抗を入れています。消費電流がそれなりにあるので、マイコン用とは別に3.3Vを準備しました。ディスプレイの詳細は以前の記事(STM32 SPI接続タッチパネル付ディスプレイを使う)をご参照ください。下写真のように基板を合体させます。
03s_253_5owmHi.jpg

▽周波数特性・ノイズ
ZOOM MS-50Gと特性を比較しました。MS-50GはParaEQを6個並べて+20dBになるよう調節しています。ノイズは1kHz正弦波入力時のものです。
03s_253_6owmHf.gif
Owm PedalはMS-50Gより高域のノイズが多いように見えますが、MS-50Gはもともと高域が下がっているので、それを加味するとほぼ同じノイズレベルといってよいと思います。Owm Pedal H7では1dB程度ノイズが多いですが原因はよくわかりません。基板設計としては、部品配置に注意すればOwm PedalのようにGNDを1面プレーンにしても問題ないとわかりました。入出力のフィルタも、簡単な1次ローパスフィルタで充分かもしれません。

タグ : 自作エフェクター レイアウト 回路図 マイコン 周波数特性 

STM32 内蔵フラッシュメモリへのデータ保存

HALライブラリを利用し、STM32の内蔵フラッシュメモリへデータを保存します。これにより電源を切ってもデータを残したままにすることができます。下記参考ページにも詳細な内容がありますので、併せてご参照ください。
STM32 + HAL Flashの書き込み・読み込み



フラッシュメモリへのデータ保存時には、単純に書き込み関数を呼び出せばよいわけではなく、あらかじめ書き込む場所を消去しておく必要があります。さらに、消去等の操作前にはロックを解除しなければいけません。つまり下記の流れとなります。
・ロック解除→消去→書き込み→再度ロック

フラッシュメモリはセクタという単位に分割されており、消去操作時にはセクタごとに消去します。STM32F401REのセクタ7の場合、フラッシュメモリの終わり側128kB分が全て消えることになります。

<STM32F4、F7での書き込みサンプルコード>
テストとして配列を書き込みます。NUCLEO-F401REで動作確認しました。セクタの開始アドレスはそれぞれのICのリファレンスマニュアルをご確認ください。
const uint32_t flash_addr = 0x08060000; // データ保存先(セクタ7)開始アドレス
uint16_t testData[8] = {1, 3, 5, 7, 2, 4, 6, 8}; // 書き込むデータ

void saveData()
{
/* フラッシュ ロック解除 */
HAL_FLASH_Unlock();

/* フラッシュ 消去 */
FLASH_EraseInitTypeDef erase; // 消去に関する構造体を定義
uint32_t error = 0; // エラーコードを格納する変数
erase.TypeErase = FLASH_TYPEERASE_SECTORS; // 消去方法: セクタ消去
erase.Sector = FLASH_SECTOR_7; // 消去するセクタ: セクタ7
erase.NbSectors = 1; // 消去するセクタの数: 1
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 電圧設定
HAL_FLASHEx_Erase(&erase, &error); // 消去

/* フラッシュ 書き込み */
uint32_t addr = flash_addr; // 書き込み先アドレスを代入
for (int i = 0; i < 8; i++)
{
HAL_FLASH_Program(FLASH_TYPEPROGRAM_HALFWORD, addr, testData[i]); // 書き込み
addr += 2; // 2バイト(16ビット)分アドレスを進める
}

/* フラッシュ ロック */
HAL_FLASH_Lock();
}
今回は16ビットのデータを書き込んでいますが、8ビットであれば「FLASH_TYPEPROGRAM_BYTE」を使い、アドレスは1バイトずつ進めます(32ビットであれば「FLASH_TYPEPROGRAM_WORD」を使い、アドレスは4バイトずつ進めます)。

<データ読み出し>
保存したデータを読み出す場合は特別な操作は必要なく、下記のように記載します。
uint32_t addr = flash_addr;
testData[0] = *((uint16_t*)addr);
「(uint16_t*)」で書き込み先アドレスをポインタ型へキャストし、そのポインタ(アドレス)にあるデータを「*」で読み出しています。何も書き込まれていない場合は、ビットが全て1のデータ(uint16_tなら65535)が読み出されます。



<STM32H7での書き込みサンプルコード>
セクタの他にバンク1と2があるため、バンク指定が追加になっています。また、書き込みはFLASHWORD(256ビット)単位限定で、256ビットのデータを準備しなければいけないようです。データ読み出しについては上記と同じです。NUCLEO-H743ZI2で動作確認しました。
const uint32_t flash_addr = 0x081E0000; // データ保存先(バンク2 セクタ7)開始アドレス
uint16_t testData[16] = {1, 3, 5, 7, 2, 4, 6, 8, 9, 7, 5, 3, 0, 8, 6, 4}; // 書き込むデータ

void saveData()
{
/* フラッシュ ロック解除 */
HAL_FLASH_Unlock();

/* フラッシュ 消去 */
FLASH_EraseInitTypeDef erase; // 消去に関する構造体を定義
uint32_t error = 0; // エラーコードを格納する変数
erase.TypeErase = FLASH_TYPEERASE_SECTORS; // 消去方法: セクタ消去
erase.Banks = FLASH_BANK_2; // 消去するバンク: バンク2
erase.Sector = FLASH_SECTOR_7; // 消去するセクタ: セクタ7
erase.NbSectors = 1; // 消去するセクタの数: 1
erase.VoltageRange = FLASH_VOLTAGE_RANGE_3; // 電圧設定
HAL_FLASHEx_Erase(&erase, &error); // 消去

/* フラッシュ 書き込み */
uint32_t addr = flash_addr; // 書き込み先アドレスを代入
HAL_FLASH_Program(FLASH_TYPEPROGRAM_FLASHWORD, addr, (uint32_t)testData); // 書き込み
addr += 32; // 続けて他のデータを書き込む場合は32バイト(256ビット)分アドレスを進める

/* フラッシュ ロック */
HAL_FLASH_Lock();
}



<リンカスクリプトについて>
内蔵フラッシュメモリにはプログラムが書き込まれています。万が一大きなプログラムとなった場合には、データ保存したいフラッシュ領域と重なってしまうおそれがあります。そこで、プログラムの領域とデータ保存の領域を分けておきます。

STM32CubeIDEのプロジェクトフォルダ内に、リンカスクリプトと呼ばれるSTM32○○○○_FLASH.ldというファイルがあります。このファイル内のMEMORYの記述を変更すればOKです。例えばSTM32F401REのセクタ7(開始アドレス0x08060000)をデータ保存領域(DATA)とする場合は下記の記述とします。
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 96K
FLASH (rx) : ORIGIN = 0x08000000, LENGTH = 384K
DATA (rx) : ORIGIN = 0x08060000, LENGTH = 128K
}

タグ : マイコン 

STM32 計算速度データ

デジタルエフェクターでリアルタイムにデジタル信号処理をする場合、計算が間に合わずまともな音が出なくなるという可能性があります。そこで、様々な計算方法についてどの程度処理時間がかかるのか確認しておくことにしました。使ったのはNUCLEO-F401REで、FPU(浮動小数点演算処理装置)を備えています。開発環境はSTM32CubeIDE(1.0.2)です。



経過時間を確認するには、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)
何もしない84
代入(=)※基準167
加算(+) 減算(-) 乗算(*)53
除算(/)97
剰余演算(%)118
各ビット演算(& | ! ^ >> <<)53
変数の呼び出しに数サイクル必要なのか、代入だけで結構時間がかかるようです。剰余演算がそんなに遅くないというのが意外でした。



<32ビット浮動小数点数(float)>

サイクル数(O0)サイクル数(Ofast)
代入(=)※基準167
加算(+) 減算(-) 乗算(*)54
除算(/)1717
定数での除算の場合は最適化で乗算と同じ速さになりますが、変数だと遅いままとなってしまいます。

C言語標準ライブラリmath.hにある数学の関数については以下の通りです。
サイクル数(O0)サイクル数(Ofast)
sqrtf5116
logf189189
expf216207
powf565571
sinf8279
sinhf286283
atanf189178
デシベル計算に使うpowfがかなり遅いので、ルックアップテーブルの利用を考える必要がありそうです。

CMSIS-DSPライブラリを導入し、とりあえず2つの関数を使ってみました。(参考ページ→STM32 CubeIDE環境で、CMSIS-DSPを使う方法
サイクル数(O0)サイクル数(Ofast)
arm_sqrt_f329113
arm_sin_f325652
今後、BiQuadフィルタやフーリエ変換等の活用について検討したいと思います。

タグ : マイコン 

STM32 SPI接続タッチパネル付ディスプレイを使う

03s_246_1ilip.jpg
Amazon等で取り扱いがある、320×240ドットのSPI接続タッチパネル付2.8インチディスプレイモジュールを使ってみます。前回とは違うILI9341という制御ICです。

Nucleoボードとディスプレイモジュールとの接続は下図の通りです。消費電流は実測50mA程度でした。
03s_246_1ilis.png
※電源に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 */

実行しディスプレイに画像等が表示されれば成功です。最後の黒い画面ではタッチパネルのテストができます。前回のディスプレイは描画した後毎回画面を更新していましたが、今回はその必要はありません。

タグ : マイコン 

Tremolec

02_243_1tremolecp.jpg
東京エフェクター「第6回エフェクタービルダーズ・コンテスト」に向けて製作した、トレモロを周波数帯域別にかけるというエフェクターです。全てデジタル信号処理でやる方が楽だと思いますが、製作時は知識が足りずアナログ回路+マイコン制御の練習として取り組みました。タップテンポ付きで、LFO周期に合わせて左側のLEDが点滅します。名前の由来は、トレモロとイコライザ(Tremolo + EQ)を合わせたものです。

筐体は少し変わった形にしたかったので、タカチのHEN110312Sというケースです。上下側が放熱用の形状なので、DCジャックの穴を開けるのが大変でした。ラベルデザインはKiCadでやってみましたが、フォントを取り込むのは面倒なので標準フォントをそのまま使っています。

▽回路図
02_243_2tremolecs.png
一般的なグラフィックイコライザ回路の可変抵抗部分をフォトカプラLCR0202で置き換えたものとなります。DAコンバータMCP4922から出力される電圧によりフォトカプラのLEDの明るさを制御し、抵抗値を変えるという仕組みです。LFO周期の半分で増幅側のフォトカプラだけを動かし、もう半分で減衰側を動かすという動作となっています。電圧変化が直線的だと音量変化がスムーズでなかったため、独自にデータを取って電圧変化を指数カーブに修正しました。また、フォトカプラの特性はバラつきがあるので、増幅と減衰の幅が同じくらいになるようマッチングしました。

AVRマイコンATmega328Pは内部クロック8MHzで動作させています。ポットやスイッチの読取(ADコンバータ)、LFO(DAコンバータ制御)、タップテンポといった機能を担っています。

▽レイアウトについて(KiCadデータはGitHubへ)
隙間が多いので表面実装でなくてもよかったかもしれません。一応アナログGNDとデジタルGNDを分けるようにしました。デジタルのノイズは測定限界以下で問題ないようです。C7、C9は何も入れておらず、後からコンデンサの値を調節できるように設けた部分になります。

▽Arduinoスケッチ
#include <SPI.h>

// ピン設定 実際は高速処理するため該当レジスタを直接変更
const byte swPin = 2; // タップスイッチ
const byte ledPin = 3; // PD3 周期表示用LED
const byte SS1 = 10; // PB2 MCP4922 Low
const byte SS2 = 9; // PB1 MCP4922 Mid
const byte SS3 = 8; // PB0 MCP4922 Treble
const byte LDAC = 7; // PD7 MCP4922 電圧出力作動ピン

// LFO1周期を500分割 0.002周期経過するごとに電圧値を変更する
volatile int pwmCount = 0; // LFO用カウンタ 0〜499
volatile int waveCount = 0; // 波形用カウンタ 0~125

volatile unsigned long timeCount = 0; // タップ時間取得用カウンタ
unsigned long tapTime = 0; // タップ間隔時間 us
unsigned long timeTemp = 0; // タップ間隔時間一時保存用
unsigned long swCount = 0; // タップスイッチ用カウンタ

byte adcNum = 0; // ADC番号(配列の添字)Bass0 Mid1 Tre2 Wave3 Div4 Rate5
const byte adcPinArray[6] = {16, 17, 18, 19, 14, 15}; // ADCピン設定
unsigned int oldAdcValue[6] = {2000, 2001, 2002, 2003, 2004, 2005}; // 旧ADC値
unsigned int newAdcValue[6] = {1110, 1111, 1112, 1113, 1114, 1115}; // 新ADC値

unsigned int dacArray[626]; // 電圧値配列 626段階 セットアップ時計算
unsigned long dacDepth[3] = {0, 1, 2}; // 電圧値配列添字部分の倍率
volatile unsigned int dacValueA[3] = {4001, 4002, 4003}; // 出力電圧値 増幅側
volatile unsigned int dacValueB[3] = {4011, 4012, 4013}; // 出力電圧値 減衰側
byte waveAmp = 1; // 三角波→矩形波変換 増幅率
const byte SQ = 12; // 上記増幅率設定値
byte tapDiv = 1; // タップ時間分割値
//const float rateAdj = 1.000; // 周期補正 実測して設定→不要

const unsigned int ledOnTime = 40000; // 周期表示LED点灯時間設定値 us
unsigned int ledOffCount = 250; // 上記LEDが消灯するカウンタ値

void setup() {
TIMSK0 = 0; // Timer0割り込み停止 安定動作のため割り込みはTimer1のみ

// 電圧値の配列を計算 指数カーブ
for (int i = 0; i <= 625; i++) {
dacArray[i] = 4141 - 46 * exp(0.0072 * i);
}

pinMode(swPin, INPUT_PULLUP);
pinMode(ledPin, OUTPUT);
pinMode(SS1, OUTPUT);
pinMode(SS2, OUTPUT);
pinMode(SS3, OUTPUT);
pinMode(LDAC, OUTPUT);

SPI.begin();
SPI.beginTransaction(SPISettings(8000000, MSBFIRST, SPI_MODE0)); // SPI 8MHz

// Timer1 電圧値変更間隔時間管理・タップ時間計測
TCCR1A = 0b00000000; // Timer1 CTCモード
TCCR1B = 0b00001010; // Timer1 CTCモード クロック8分周
OCR1A = 100; // Timer1 比較Aの値 = 割り込み間隔 us
TIMSK1 |= (1 << OCIE1A); // Timer1 比較A割り込み許可
}

// LFO(Timer1 比較A割り込み)-----------------------------------------------------
ISR(TIMER1_COMPA_vect) {
timeCount++; // タップ時間取得用カウンタ
PORTD &= ~_BV(7); // LDAC LOW (最初に電圧出力)
PORTD |= _BV(7); // LDAC HIGH
if (pwmCount == 500) pwmCount = 0; // 0〜499 ループ
if (pwmCount <= 125) { // 三角波 増幅側上昇
waveCount = waveAmp * pwmCount; // 三角波を増幅後、
waveCount = min(waveCount, 125); // クリップし矩形波とする
dacA(waveCount);
} else if (pwmCount <= 250) { // 三角波 増幅側下降
waveCount = waveAmp * (250 - pwmCount);
waveCount = min(waveCount, 125);
dacA(waveCount);
} else if (pwmCount <= 375) { // 三角波 減衰側上昇
waveCount = waveAmp * (pwmCount - 250);
waveCount = min(waveCount, 125);
dacB(waveCount);
} else { // 三角波 減衰側下降
waveCount = waveAmp * (500 - pwmCount);
waveCount = min(waveCount, 125);
dacB(waveCount);
}
if (pwmCount == 125) PORTD |= _BV(3); // 周期表示LED点灯
if (pwmCount == ledOffCount) PORTD &= ~_BV(3); // 消灯
pwmCount++;
}

void loop() {
// タップテンポ -----------------------------------------------------------------
if (digitalRead(swPin) == LOW) {
swCount++;
if (swCount == 50) { // チャタリング対策 数msスイッチ押下で検出
// 前回スイッチ検出から経過した時間を記録
tapTime = timeTemp + timeCount * OCR1A + TCNT1;
TCNT1 = 0; // 経過時間をリセット
timeTemp = 0;
timeCount = 0;
pwmCount = 124; // 増幅最大の点にリセット
if ( 200000 < tapTime && tapTime < 2100000) { // 周期制限 0.2秒~2.1秒
// タップ間隔を電圧値変更間隔時間へ換算(Divスイッチ加味)
OCR1A = tapTime * 0.002 / tapDiv;
// どの周期でも周期表示LEDの点灯時間が同程度になるよう計算
ledOffCount = 125 + ledOnTime / OCR1A;
}
}
}
else swCount = 0;

// ADC --------------------------------------------------------------------------
adcNum++;
if (adcNum == 6) adcNum = 0; // 0〜5 ループ
newAdcValue[adcNum] = analogRead(adcPinArray[adcNum]); // 読取値が4以上変化で変更
if (abs(newAdcValue[adcNum] - oldAdcValue[adcNum]) >= 4) {
if (adcNum <= 2) { // Depth 0〜640 160までは急な変化
if (newAdcValue[adcNum] < 100) dacDepth[adcNum] = 1.6 * newAdcValue[adcNum];
else dacDepth[adcNum] = 0.5201 * newAdcValue[adcNum] + 108;
} else if (adcNum == 3) { // Wave 1 or SQ 矩形波変換増幅率
if (newAdcValue[adcNum] < 500) waveAmp = 1;
else waveAmp = SQ;
} else if (adcNum == 4) { // Div 1〜3 タップ時間分割値
tapDiv = 0.0029 * newAdcValue[adcNum] + 1;
} else if (adcNum == 5) { // Rate 4004〜133 電圧値変更間隔時間
// Rate変更前から経過した時間を記録
timeTemp = timeTemp + timeCount * OCR1A + TCNT1;
TCNT1 = 0; // 経過時間をリセット
timeCount = 0;
// Rate計算 指数カーブ
OCR1A = 73 * exp(0.0039 * (1023 - newAdcValue[adcNum])) + 60;
// どの周期でも周期表示LEDの点灯時間が同程度になるよう計算
ledOffCount = 125 + ledOnTime / OCR1A;
}
oldAdcValue[adcNum] = newAdcValue[adcNum];
}
}

// 電圧値設定 A:増幅側 B:減衰側--------------------------------------------------
void dacA(int w) { // w:waveCount 波形用カウンタ
// w(0〜125)にDepth(0〜640)をかけ128で割ったものが電圧値配列の添字(0〜625)
dacValueA[0] = dacArray[(dacDepth[0] * w) >> 7];
dacValueA[1] = dacArray[(dacDepth[1] * w) >> 7];
dacValueA[2] = dacArray[(dacDepth[2] * w) >> 7];
PORTB &= ~_BV(2); // SS1(PB2) LOW
SPI.transfer((dacValueA[0] >> 8) | 0x30); // 0x30=0b00110000 A出力 バッファなし
SPI.transfer(dacValueA[0] & 0xff); // ゲイン1倍 シャットダウンなし
PORTB |= _BV(2); // SS1(PB2) HIGH
PORTB &= ~_BV(1); // SS2(PB1)
SPI.transfer((dacValueA[1] >> 8) | 0x30);
SPI.transfer(dacValueA[1] & 0xff);
PORTB |= _BV(1);
PORTB &= ~_BV(0); // SS3(PB0)
SPI.transfer((dacValueA[2] >> 8) | 0x30);
SPI.transfer(dacValueA[2] & 0xff);
PORTB |= _BV(0);
}

void dacB(int w) {
dacValueB[0] = dacArray[(dacDepth[0] * w) >> 7];
dacValueB[1] = dacArray[(dacDepth[1] * w) >> 7];
dacValueB[2] = dacArray[(dacDepth[2] * w) >> 7];
PORTB &= ~_BV(2);
SPI.transfer((dacValueB[0] >> 8) | 0xb0); // 0xb0=0b10110000 B出力
SPI.transfer(dacValueB[0] & 0xff);
PORTB |= _BV(2);
PORTB &= ~_BV(1);
SPI.transfer((dacValueB[1] >> 8) | 0xb0);
SPI.transfer(dacValueB[1] & 0xff);
PORTB |= _BV(1);
PORTB &= ~_BV(0);
SPI.transfer((dacValueB[2] >> 8) | 0xb0);
SPI.transfer(dacValueB[2] & 0xff);
PORTB |= _BV(0);
}
過去記事(→ATtiny85 タップテンポ付LFO その2)と同じようなスケッチで、割り込み時の処理がDAコンバータ制御に変わっています。MCP4922の使い方は下記ページを参考にしました。高速処理するため、今回はレジスタを直接変更しています。
きむ茶工房ガレージハウス - DAコンバータ MCP4922(SPI)を利用しD/A変換を行う

音については今までにない効果だと思うので、どう評価されるかわかりません。TREBLEのみ揺らす、MIDのみ揺らさない等、いろんなパターンを試せるので、誰でもきっと「お気に入りのトレモロ」が見つけられるんじゃないかと思います。



以下、採用にならなかった案をメモしておきます。

<他のイコライザ回路>
下記ページのような3バンドイコライザ回路があります。DJミキサーに使われているらしいです。
Equalisers, The Various Types And How They Work - 9 - Frequency 'Isolators'
シミュレーションしてみたのですが、減衰時の特性がいまいち気に入りませんでした。

<デジタルポテンショメータ(以下DPOT)>
フォトカプラの部分にDPOTを使うことを最初検討していました。しかし普通のDPOTは分解能が8ビットなので、1/256ずつ飛び飛びに値を動かすことになり、ノイズが発生してしまうことになります。常にDPOTを動かし続ける今回の用途には向いていないと判断しました。

<フォトカプラのPWM制御>
フォトカプラのLED側をPWMで制御しようと思い、PWM出力が6つ使えるマイコンATmega1284Pを準備しました。しかしPWMが一部8ビットなので、256段階でしか抵抗値を調節できず、スムーズに増幅・減衰の変化をさせることができませんでした。後から考えると、高性能な32ビットマイコンを使えばよかったかもしれません。

タグ : 自作エフェクター 回路図 レイアウト マイコン トレモロ 

管理人

管理人

自己紹介のページ
記事一覧
X(旧Twitter)
Instagram
GitHub
BOOTH

ブログ内検索
カテゴリー
タグ

回路図   歪み   自作エフェクター   周波数特性   市販エフェクター   レイアウト   マイコン   波形・倍音   PureData   RaspberryPi   エレキギター   アンプ   歪率   エレキベース   エフェクター自作方法   コーラス   真空管   ピックアップ   静音ギター   SansAmpBDDI   ヘッドフォンアンプ   擬似ギター出力   ブースター   アコースティックギター   ソロギター   イコライザー   ポールピース   コンデンサ   コンプレッサー   ディレイ   TAB譜   DIY_Layout_Creator   ビブラート   フェイザー   トレモロ   バッファー   ワウ   オートワウ   

最近の記事
最新コメント
RSS
メールフォーム
当ブログに関するお問い合わせはこちらからお願いします。 ※FAQ(よくある質問)もお読みください。

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

アクセスカウンター