2021年2月28日日曜日

WaveShaper Saw2Tri ノコギリ波→三角波変換の実験

以前シミュレーションした、NPNトランジスタでノコギリ波を三角波に変換する回路を、ブレッドボードで実験しました。

この回路はAnalog 2.0のVCOをもとに実験させてい頂いております。


シミュレーション回路図

過渡解析

一見トランジスタのただのエミッタ接地のようですが、ベース電流Ibがほとんど流れない活性領域と、Ibが流れ出す飽和領域で動作が切り替わります。

活性領域では普通のエミッタ接地のように(むしろエミッタ接地ではこの領域に収まるようにバイアス電圧などを調節する)出力が反転、飽和領域はトランジスタをスイッチング動作させる場合に使いますがコレクタ電流がベース電流に比例するようになります。トランジスタのIC-VCE特性によるものですがほとんど直線的に切り替わるのはちょっとびっくりします。

R1の値でコレクタ電流を調整しますが、シミュレーションではR1=12kΩ付近で特性が切り替わるポイントが噛み合い、出力が三角波状になります。

ブレッドボード配線図
Tr:2SC1815-GR


入出力波形

トリムを調節して三角波にした状態です。出力は-1.1V~+4.2V程度の振幅になっています。

増幅とバイアス


出力の振幅を±5Vにしたいので後段にアンプを入れます。

シミュレーション回路図

U1周りは非反転増幅回路にVREF(+5V)からR6を通してバイアスを掛けた増幅回路です。

過渡解析

出力はほぼ±5Vの三角波になっています。

ブレッドボード配線図
Tr:2SC1815-GR


入出力波形

出力の振幅がほぼ±5Vになっています。

出力される三角波の波形の対象性はとれていませんが、なかなか興味深い回路だと思います。

2021年2月22日月曜日

Antilog-NPNO Ver.1.2の製作 SawVCOと結合

Antilog-NPNO Ver.1.1の問題点を改善したVer.1.2を製作しました。

回路図

基板図

改善点

  • CV直入力の端子を設けた。
  • BIASとScaleを設定するトリム(RV3、RV4)を横型にしてラックにマウントした状態でも調整しやすくした。
  • SawVCOと接続するコネクタを後ろ側にまとめ、配線しやすくした。
  • トランジスタ差動ペア(Q1、Q2)の間隔を狭め熱結合しやすくした。
  • 基板取付POT(RV1、RV2)のフットプリント(1-2-3)が逆だったのを修正。
  • BIAS値を測定するテストポイント(TP_ADJ)を設けた。
  • 基板上にVIAを開け表裏のベタGNDのインピーダンスを下げた。

などです。

また、R15に温度補償抵抗(3300ppm/℃)を用い、Q1、Q2、R1をエポキシ接着剤(コニシ/クイック5)で熱結合しました。

OPアンプはFET入力のNJM072Dを使用しています。

プリント基板

パネルに取付

SawVCO Ver.1.0(左)とAntilog-NPNO Ver.1.2(右)

ERK01にマウント

ラックにマウントして簡単に調整してCV対出力周波数を測定しました。

CV=0V

CV=1V

CV=2V

CV=3V

CV=4V

CV=5V

CV[V] Code 理論値[Hz} 測定値[Hz] 誤差[Hz] 誤差[%]
0 A0 55 52.1 -2.9 -5.3%
1 A1 110 108.9 -1.1 -1.0%
2 A2 220 222.4 2.4 1.1%
3 A3 440 454.3 14.3 3.3%
4 A4 880 928.8 48.8 5.5%
5 A5 1760 1912.7 152.7 8.7%

Scaleが少し広いようですが、Ver.1.1より調整しやすくなりました。もう少し追い込めるかな?

メモ


RV1、RV2の位置がラックの上面に対して下すぎるのでパネル上面がやや間延びしています。POTの位置を上にずらすか、基板の外形に切り欠けを入れるか。

2021年2月19日金曜日

STM32CubeIDE: I2S DACのTDA1543を使う - OPアンプでI-V変換

TDA1543のデータシートに載っているようにOPアンプを使ってI-V変換を行います。STM32のプログラムは抵抗によるI-V変換の場合と同じです。サイン波とノコギリ波を出力します。

回路図 (I-V変換)

この回路は反転出力になりますが、TDA1543の電流出力が吸い込み型のためI2Sのデータと同相の電圧出力になります。

出力電圧 VO = I * R1 で、抵抗によるI-V変換の場合と同じ振幅になるように R1=2.2kΩとしました。TDA1543の出力電流のフルスケールは2.30mA(typ.)なので、出力電圧VOは

VO = I * R1 = 2.3mA * 2.2kΩ = 5.06V

となります。

今回は生の出力波形を見たいので、LPFの特性は必要ないのですがC1を入れないと発振してしまうのでC1=100pFとしました。

この回路の高域でのカットオフ周波数fcは、

fc = 1 / (2 * π * C * R) = 1 / (2 * π * 100pF * 2.2kΩ) ≒ 724kHz

となります。

R2は出力電流にかけるバイアス電流を設定するためのもので(TDA1543の機能)、この回路の場合1.8kΩにすると出力波形の中心がGND付近になります。きっちりDC成分を除去するにはトリムを入れて微調整するかACカップリングすると良いと思います。

配線


電源電圧は±5Vにしています。TDA1543のVDDの絶対定格はMin=0V Max=+9Vなので要注意。

出力波形



C1:Lch C2:Rch

振幅Vpp=5V、平均値average≒23mVとなりました。抵抗によるI-V変換の場合波形の下側がややつぶれて歪んでいましたが、OPアンプを使うとつぶれは見られません。

スペクトラム


Lch(サイン波) 500kHzレンジ

サンプリング周波数の48kHzの倍数付近にピークが現れています。

Lch(サイン波) 100kHzレンジ

サンプリング周波数以下でも、ナイキスト周波数の24kHzを中心に折返しノイズが見られます。特性の良いLPFを通すとどうなるかいずれ実験してみたいと思います。

Rch(ノコギリ波) 50kHzレンジ

ノコギリ波は基本波の整数倍の高調波で構成されますが、高調波の間のエイリアスが周波数が高くなると徐々に増えていき、サンプリング周波数の48kHz付近で共鳴しているかのようにピークが消えています。数式で求めるとどうなるんでしょうね。 

2021年2月14日日曜日

STM32CubeIDE: I2S DACのTDA1543を使う - サイン波を出力

前回と同じく抵抗でI-V変換を行います。

プログラムを変更して任意の周波数のサイン波を出力してみます。今までAVRやPSoCなどマイコンでの波形生成には計算量の少ない整数演算で済むDDSを使っていましたが、STM32F446REには単精度のFPUが搭載されているので、リアルタイムに浮動小数点数演算して波形生成してみることにします。

TDA1543のPCMデータ・フォーマット


SPIのDACの場合符号なし整数のデータフォーマットが多いですが、I2SのDACはほとんど2の補数の符号付き整数のデータフォーマットです。

16bit値の2の補数の符号付き整数は以下のようになります。

16進数 符号なし10進数 符号付き10進数
0x0000 0 0
0x0001 1 1
0x0002 2 2
: :
0x7FFD 32765 32765
0x7FFE 32766 32766
0x7FFF 32767 32767
0x8000 32768 -32768
0x8001 32769 -32767
0x8002 32770 -32766
: : :
0xFFFD 65533 -3
0xFFFE 65534 -2
0xFFFF 65535 -1

ちょっとややこしそうですが、幸いに(?)C言語のsigned intは2の補数なのでプログラムは難しくありません。

STM32CubeIDE: Version 1.5.1
Target board: Nucleo-F446RE

MXの設定






System Core
  GPIO
    PC5: 
      GPIO mode: Output Push Pull
      User Label: CK_PERIOD
    PC6:
      GPIO mode: Output Push Pull
      User Label: CK_CPLT
    PC8:
      GPIO mode: Output Push Pull
      User Label: CK_HALF_CPLT
Multimedia
  I2S2
    Mode
      Mode: Half-Duplex Master
  Configuration
    Parameter Settings
      Generic Parameters
        Selected Audio Frequency: 48KHz
    DMA Settings
      SPI2_TX
        DMA Request Settings
          Mode: Circular
          Peripheral: Data Width: Half Word
          Memory   : Data Width: Half Word

処理のタイミングを計測するため、GPIOを何本か追加しました。

配線



追加したGPIOをH/Lし、Logic Analyzerで補足します。

main.cにコードを追加

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <math.h>
/* USER CODE END Includes */

浮動小数点数演算を行うため<math.h>をインクルードします。

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
#define PI_F	(3.1415926f)
/* USER CODE END PD */

πの値を単精度で定義します。

/* USER CODE BEGIN PV */
uint32_t sampling_rate = 48000u;
float frequency = 1000.0f;
float phi = 0.0f;
float delta;
uint16_t tx_buffer[2] = { 0, 0 };
/* USER CODE END PV */

サンプリングレート48kHz、出力波形の周波数1kHzと定義しています。

phiは位相角で、サンプリング周期ごとにdeltaだけ増分します。

tx_bufferはDMA転送に使うバッファです。

int main(void)
{
  /* USER CODE BEGIN 1 */
  delta = (2.0f * PI_F * frequency) / sampling_rate;
  /* USER CODE END 1 */

増分deltaを計算します。

  /* USER CODE BEGIN 2 */
  HAL_I2S_Transmit_DMA(&hi2s2, tx_buffer, 2);
  /* USER CODE END 2 */

DMA経由でI2Sを開始します。Ciruclarモードにしているので呼び出しは1回だけで勝手にメモリ→ペリフェラル間の転送が繰り返されます。

/* USER CODE BEGIN 4 */
void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
	HAL_GPIO_WritePin(GPIOC, CK_HALF_CPLT_Pin, GPIO_PIN_SET);

	// Generate Sine wave
	float fv = sinf(phi);
	int16_t v = fv * 0x7fff;
	tx_buffer[0] = (uint16_t)v;

	HAL_GPIO_WritePin(GPIOC, CK_HALF_CPLT_Pin, GPIO_PIN_RESET);
}

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{
	HAL_GPIO_WritePin(GPIOC, CK_CPLT_Pin, GPIO_PIN_SET);

	// Generate Sawtooth wave
	float fv = phi / PI_F;
	int16_t v = fv * 0x7fff;
	tx_buffer[1] = (uint16_t)v;

	// Advance in phase
	phi += delta;
	if (phi > PI_F) {
		phi -= 2.0f * PI_F;
		HAL_GPIO_TogglePin(GPIOC, CK_PERIOD_Pin);
	}

	HAL_GPIO_WritePin(GPIOC, CK_CPLT_Pin, GPIO_PIN_RESET);
}
/* USER CODE END 4 */

割り込みハンドラで、Lchはサイン波、Rchはノコギリ波を生成しています。

// Generate Sine wave
float fv = sinf(phi);
int16_t v = fv * 0x7fff;
tx_buffer[0] = (uint16_t)v;

sinf()の返り値は-1~1なので、0x7fffを乗算するとint16_t型(2の補数の16bit符号付き整数)の最小~最大になります。tx_bufferが符号なしのuint16_t型なので、明示的にキャストしています。キャストしても2進数としては同じ値のままでメモリに格納されます。

処理にかかった時間を計測するため、ハンドラ内の最初と最後でGPIOをH/Lしています。

Analog Discovery 2で出力波形と信号を観測


TDA1543の出力波形

C1:Lch C2:Rch

ノー・オーバーサンプリングで48kHz/16bitで1kHzの波形なので、きっちりガタガタが現れています。

周波数(MeasureのFrequency)を見ると992.7kHzとなっています。STM32CubeIDEのMXではサンプリング周波数の誤差が-0.79%と表示されていて、1,000Hz * 0.79% = 7.9Hz なので仕方ないでしょう。実際の値として見ると少し大きい感じがしますね。

計算処理時間を計測



1ch分転送後、割り込みハンドラに処理が移るまで結構タイムラグがあるようです。

処理にかかった時間は、Quick Measure機能で信号がHighになっている時間を調べると

HalfCplt: 3.51us (284.9kHz)
Cplt: 1.56us (641.0kHz)

でした。Clockが84MHz、DebugプロファイルでBuildした場合の計測なのでもう少し速くできると思います。

2021年2月11日木曜日

STM32CubeIDE: I2S DACのTDA1543を使う - 抵抗でI-V変換

TDA1543はDIP8、5V駆動可、オーバーサンプリングなしという実験に使いやすいI2S DACです。SPI DACだと出回っているものはせいぜい12bit精度ですが、TDA1543はオーディオ用なので16bit精度、サンプリングレートは192kHzまで対応しています。

TDA1543はTDA1543Aというフォーマットが異なるものがあるので要注意です。


前回の「STM32CubeIDE: I2SをDMAで使う」のプログラムをそのまま使ってみます。

配線図

TDA1543は電流出力型です。電圧として取り出すにはI-V変換を行う必要がありますが、簡単にオームの法則(V=IR)を使って抵抗1本で行いました。

データシートを見ると、

  • 電源電圧VDD MAX.=8.0V
  • フルスケール出力電流IFS=2.30mA(typ)
  • 出力電圧コンプライアンスVOC(DC) MIN.=1.8V MAX.=VDD-1.2V

となっています。VDD=8V駆動するとI-V変換抵抗RLは

RL = (VDD - 1.2)[V] / IFS[mA] = (8V - 1.2V) / 2.30mA = 2.96kΩ

となります。RLが大きいほど出力電圧が高くなるので余裕をもって2.2kΩとしました。

TDA1543のVref(7ピン)で出力のバイアス電流を設定できます。

接続する抵抗の値によって出力電流にかかるバイアスの量が変わります。I-V変換後の電圧がコンプライアンス電圧に引っかからないように、抵抗値を設定する必要があります。コンプライアンス電圧を超えると出力波形がクリップしてしまいます。

カットアンドトライの結果、この回路の場合1.5kΩが良いようです。

出力波形とI2S信号


VDD=8V

プログラムではLchをインクリメント、Rchをデクリメントしています。LogicのI2Sの値を見てもそうなっています。

ところが、Scopeの波形を見るとLch(C1:黄)が右肩下がり、Rch(C2:青)が右肩上がりになっており、プログラムとは逆の動作になっています。

これはTDA1543の電流出力が吸い込み(Sink)型のためです。この回路では波形が反転して出力されることになります。

TDA1543のデータシートのブロック図(一部)

TDA1543のデータシートのブロック図を見ると、OPアンプを使った反転型のI-V変換回路が使われています。こちらだと反転されずに電圧が出力されることになります。

また図のようにR=1.2kΩ、C=3.3nFだと、カットオフ周波数fcが

fc = 1 / (2 * π * C * R) = 1 / (2 * π * 3.3nF * 1.2kΩ) = 40.1kHz

の1次LPFの特性を持ちます。

コンプライアンス電圧


同じ回路で電源電圧VDD=5Vにするとコンプライアンス電圧に引っかかって出力がクリップします。

VDD=5V

VDD=5Vの場合、コンプライアンス電圧VOC = (VDD - 1.2) = 5 - 1.2 = 3.8Vとなりますが、実際にはもう少し高い電圧まで出力されています。

2021年2月7日日曜日

STM32CubeIDE: I2SをDMAで使う

DMA経由でI2S通信を行います。48kHz/16bitでLch、Rchそれぞれ16bit値をインクリメント、デクリメントして送信します。DACに入力するとL/Rで逆位相のノコギリ波が出力されます。

STM32CubeIDE: Version 1.5.1
Target board: Nucleo-F446RE

MXの設定



Real Audio Frequencyが47.619kHzとなっていますがI2SのクロックがMCUの内部クロックに依存しているためで、正確に48kHzにするためには元になるClockをうまく調整する必要があります。


Addボタンを押してSPI2_TXを選択するとDMA Request Settingsを設定できるようになります。SPI2_TXとなっているのは、I2SはSPIと同じハードウェアを利用しているためです。

メモリの値を連続して送信するため、ModeはCiruclarにします。Data WidthをHalf Wordに設定します。

Multimedia
  I2S2
    Mode
      Mode: Half-Duplex Master
  Configuration
    Parameter Settings
      Generic Parameters
        Selected Audio Frequency: 48KHz
    DMA Settings
      SPI2_TX
        DMA Request Settings
          Mode: Circular
          Peripheral: Data Width: Half Word
          Memory   : Data Width: Half Word

Pinout



main.cにコードを追加


/* USER CODE BEGIN PV */
uint16_t tx_buffer[2] = { 0, 0 };
/* USER CODE END PV */

割り込みで処理するため、送信するデータを大域変数で定義します。

  /* USER CODE BEGIN 2 */
  HAL_I2S_Transmit_DMA(&hi2s2, tx_buffer, 2);
  /* USER CODE END 2 */

DMAをCircularモードにしているので、1度だけI2S送信を開始します。DMAを使う場合はHAL_I2S_Transmit_DMA()を使います。

/* USER CODE BEGIN 4 */
void HAL_I2S_TxHalfCpltCallback(I2S_HandleTypeDef *hi2s)
{
	tx_buffer[0] ++;
}

void HAL_I2S_TxCpltCallback(I2S_HandleTypeDef *hi2s)
{
	tx_buffer[1] --;
}
/* USER CODE END 4 */

割り込みハンドラを定義します。Lch/Rchそれぞれ送信が完了した時点でインクリメント/デクリメントしています。

I2S出力信号


出力信号をAnalog Discovery 2のScope機能とLogic機能で観測しました。


上側のScopeはC1がWS、C2がBCLKで、WSが47.634kHz、BCKが1.5244MHzとなっています。

下側のLogicを見るとLchがインクリメント、Rchがデクリメントされています。

2021年2月4日木曜日

STM32CubeIDE: I2SをPollingで使う

I2SをPollingで使うことはあまりないと思いますが、動作確認が簡単です。オーソドックスに16bit/48kHzで設定します。

STM32CubeIDE: Version 1.5.1
Target board: Nucleo-F446RE

MXの設定

Multimedia
  I2S2
    Mode
      Mode: Half-Duplex Master
  Configuration
    Parameter Settings
      Generic Parameters
        Selected Audio Frequency: 48KHz

Pinout



main.cにコードを追加


  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  uint16_t data[2] = { 12345, 23456 };
  while (1)
  {
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
	if (HAL_I2S_Transmit(&hi2s2, data, 2, 100) != HAL_OK) {
		Error_Handler();
	}
  }
  /* USER CODE END 3 */

簡単ですね。

I2S出力信号


出力信号をAnalog Discovery 2のLogic機能で観測しました。


Lchに12345、Rchに23456が出力されています。

WSがR->Lに切り替わるところで、一瞬SD(Data)がHighになっています。HAL_I2S_Transmit()は終了後SDをHighにする仕様なのでしょうか?

LABRICOと2x4材で自転車ハンガー用の柱を立てる

ロードバイクは室内保管が基本です。玄関に置いてすぐに乗り出せるようにしたいのですが、設置スペースを小さくするために、2x4材を使って柱を立て宙吊りにました。



使った資材は以下のとおりです。

品名 メーカー 価格 購入先
2x4 8F - 680円 コメリ
2x4アジャスター LABRICO 1100円 コメリ
バイクハンガー4R ミノウラ 2910円 Amazon
ワトコオイル エボニー/ナチュラル ワトコ - 余り物

2x4材とLABRICOは以前リビング用のラックを作った方法を踏襲しました。

設置場所の床から天井の高さを測定し(レーザー距離計があると便利)、-95mmした長さに2x4材を切り出します。私はホームセンターでカットしてもらいました。

生木でもとりあえずは大丈夫だと思いますが、ワトコオイルが余っていたので塗装しました。今回はエボニーとナチュラルを半々で混ぜて薄めの色合いにしてみました。

ミノウラのバイクハンガーは25mmと50mmのビスも付属しています。2x4なら25mmを使います。わざわざ買わなくても良いので親切ですね。



2x4定規で材の中心に墨付け、ハンガーを当ててビス打ちしておしまい。あっという間に完成です。細めのドリルで下穴を開けておいたほうが良いでしょう。


糸に50円玉をぶらさげて柱の垂直を確保しました。

また小物を下げるフックも一緒にビス打ちします。最初つけるのを忘れていてやり直しました。


ちなみにLABRICOは間違えてグリーンのものを買ってしまいましたが、設置してみるとそれほど気にならないようです。パッケージで製品の色がわかりにくいので、お店で買うときは注意しましょう。