ITU-T G.227 にあるフィルタを実装して、無線機の特性試験の試験方法に登場する「疑似音声信号発生器」を WebAudio で作ってみる。

ITU-T G.227

Conventional Telephone Signal という名前の勧告。伝達関数が書いてある。600Hz ぐらいにピークのあるフィルタになっている。

ホワイトノイズをこのフィルタに通すことによって疑似音声となる。なので、この周波数特性を持つフィルタを設計する。

フィルタの設計方法

フィルタの知識がないので、試行錯誤しながら以下のようにすすめていった。

  • 周波数特性をそのままFIRフィルタにする
  • 最初から実数 FIR フィルタとして設計してみる
  • 双一次変換を使って IIR フィルタにしてみる

結論からいえば、今回の場合はアナログフィルタの伝達関数が示されているので、そこからデジタル IIR フィルタに変換するのが一番良かった (と思う)

求めるのは Jupyter Notebook 上で行なった。

周波数特性をそのままFIRフィルタにする

FIR フィルタは周波数特性がわかっていれば IFFT で作ることができる。ほぼ何も考えずに作れる。

最初は対称をつくらず IFFT して、最後に実数だけとるのを作った。

実数 FIR フィルタとして設計する

フィルタを左右対称にすればほぼ実数フィルタになるはずだが、低域の減衰が急峻すぎるせいかこれでも虚数が出てきてしまう。しかたないのでこのまま虚数を捨てて実数フィルタとして評価した。

まぁまぁうまくいくけど、かなり重くなってしまう。そして、それなりに G.227 の特性から誤差が発生して気持ちがわるい。FIR なので次数を増やせばそれだけ近似はできるが、どんどん重くなっていく。

なお FIR フィルタを WebAudio で使いたい場合は、ConvolverNode を使えば良い。

アナログフィルタを双一次変換 (bilinear transform) してIIRフィルタを得て FIR フィルタで補正する

↑補正なし↓補正

アナログフィルタ(周波数連続という意味)からデジタルフィルタ(離散という意味)をつくる方法の一つに双一次変換がある。scipy で簡単にできるので、これを試す。

今回の場合、アナログの伝達関数はわかっているので、この規格に書いてある伝達関数を双一次変換してIIRフィルタの係数を得る。つまり以下を変換してデジタルフィルタを得たい。


なぜ微妙に括弧がついてるのかわからないが、結局これは次数ごとに書きなおすと見なれた以下の形になる。ほぼ scipy.signal.freqs などが受けとる形になっている。4次。

ただ、p が なので、これを角周波数 をとる関数に変えたい。

python で上記通り係数計算して、signal.bilinear() に渡してあげると、デジタルフィルタの係数になる。ただ、双一次変換は周波数歪みが起こるので、完全にアナログフィルタの特性と一致するわけではない。

こういう場合どうするのかよくわからなかったが、次数の少ない FIR で容易に補正できそうだったため、アナログフィルタの周波数特性との差分をとって補正する FIR フィルタを追加で設計する。

IIR フィルタを WebAudio で使う場合は IIRFilterNode を使えば良い。feedForward に分子 feedBack に分母の係数を渡せば動いてくれる。

  1. トップ
  2. tech
  3. ブラウザで動く擬似音声発生器 (ITU-T G.227 フィルタ)

  • Type-C コネクタ
  • 25MHz / 32.768 kHz 水晶つき
  • NRST / BOOT ボタン

裏面に WeAct と書いてあった。

何もせずそのまま USB に繋ぐとシリアルポートとして認識される。

Bus 020 Device 025: ID 0483:5740 STMicroelectronics STM32 Virtual ComPort 
00:01:10,04012019
VrefintAdc: 1.2133V
TempSensorAdc: 24.75C
FlashID:0 ERROR!

こういう出力がずっと流れている。

プログラム方法

この MCU は System Memory という領域を持っており、ブートローダーが最初から書きこまれている。詳細は AN2606

USB 経由で DFU するには、基板上の BOOT0 を押しながら、NRST を押せば良い。

Bus 020 Device 008: ID 0483:df11 STMicroelectronics STM32  BOOTLOADER  Serial

として認識される。

platformio

https://docs.platformio.org/en/latest/boards/ststm32/blackpill_f401cc.html

blackpill_f401cc というのがある。たぶんこれなので、一旦これで試す。

platformio init --board=blackpill_f401cc

残念ながら mbed framework が使えないみたいなので、一旦 arduino でいく src/main.cpp

#include "Arduino.h"

#ifndef LED_BUILTIN
#define LED_BUILTIN 13
#endif

void setup() {
	pinMode(LED_BUILTIN, OUTPUT);
}

void loop() {
	digitalWrite(LED_BUILTIN, HIGH);
	delay(500);
	digitalWrite(LED_BUILTIN, LOW);
	delay(500);
}
pio run

.pio/build/blackpill_f401cc/firmware.bin ができるので、BOOT0 を押しながら NRST を押し、USB DFU モードにする。この状態で dfu-util で書きこめる。

dfu-util -d 0483:df11 -a 0 -s 0x08000000:leave -D .pio/build/blackpill_f401cc/firmware.bin 

mbed のカスタムボード定義

とりあえず mbed のほうが好みなので mbed を使えるようにしてみる。こういうことするとハマりやすいのであまりよくないが…

403 Forbidden に従いながらカスタムボードを定義する。

platformi.ini

[env:f401cc]
platform = ststm32
board = f401cc
framework = mbed
build_flags = -I$PROJECTSRC_DIR/TARGET_F401CC

boards/f401cc.json

# https://github.com/platformio/platform-ststm32/blob/develop/boards/blackpill_f401cc.json をコピペ

custom_targets.json https://github.com/ARMmbed/mbed-os/blob/e1c3de649dd9c16d9548f73b4bbb71858af904f7/targets/targets.json#L4834 から抜き出してコピペ

{
	"F401CC": {
		"inherits": ["FAMILY_STM32"],
		"core": "Cortex-M4F",
		"default_toolchain": "GCC_ARM",
		"extra_labels_add": [
			"STM32F4",
			"STM32F401",
			"STM32F401xC",
			"STM32F401VC"
		],
		"supported_toolchains": ["GCC_ARM"],
		"device_has_add": ["MPU"],
		"device_name": "STM32F401CC"
	}
}

cp -r ~/.platformio/packages/framework-mbed/targets/TARGET_STM/TARGET_STM32F4/TARGET_STM32F401xC/TARGET_DISCO_F401VC src/TARGET_F401CC

#include "mbed.h"

DigitalOut led(PC_13);

int main() {
	for (;;) {
		led = 1;
		wait(0.2);
		led = 0;
		wait(0.2);
	}
}

これで pio run が走って .pio/build/f401cc/firmware.bin ができるはず。

time dfu-util -d 0483:df11 -a 0 -s 0x08000000:leave -D .pio/build/f401cc/firmware.bin 

で書きこみ。

  1. トップ
  2. tech
  3. STM32F401CC の安いボード

別表第35とかに出てくる「標準符号化試験信号」の生成について調べた。

擬似信号発生器は、標準符号化試験信号(ITU-T勧告O.150による9段PN符号)を発
生させる。
と書いてある。

ITU-T O.150 は n 段(stage)のシフトレジスタで構成されるテスト用の疑似乱数列について定義している。n段の符号はnビットのシフトレジスタに対応する。

具体的な生成方法は wikipedia の LFSR を読むのがわかりやすい。

9段のPN符号

「4.1 511-bit pseudo-random test pattern」となっているところが9段のもの。

「The pattern may be generated in a nine-stage shift register whose 5th and 9th stage outputs are added in a modulo-two addition stage, and the result is fed back to the input of the first stage. The pattern begins with the first ONE of 9 consecutive ONEs.」と文章で書いてある。

5th and 9th と書いてあるところが帰還多項式に対応しており、この場合

になる。また初期状態も定められており、全ビット1から初めるとしている。

JavaScript での実装

以下のような任意のビット数(ただし32ビット以下)の LFSR のコードを書いた。全ビットが0でない限りは最大周期で全ての状態が出現する。

/**
 * Linear-feedback shift register
 *
 * reg: n bits register state
 * n: Bits(n) (up to 32bits)
 * taps: feedback polynomial (eg. x^16 + x^14 + x^13 + x^11 + 1 => [16, 14, 13, 11])
 * ref. https://en.wikipedia.org/wiki/Linear-feedback_shift_register
 */
function LFSR(reg, n, taps) {
	const mask = n === 32 ? (-1>>>0) : (1 << n) - 1;
	reg &= mask;

	const bit = taps.
		map( (tap) => reg >> (n - tap) ).
		reduce( (r, i) => r ^ i ) & 1;

	return ( (reg >>> 1) | ( bit <<  (n-1) ) ) & mask;
}

const start = 511;
let lfsr = start;
let period = 0;
do {
	lfsr = LFSR(lfsr, 9, [9, 5]);
	console.log('output:', lfsr>>(9-1), 'internal:', (lfsr | (1<<9)).toString(2).slice(1));
	period++;
} while(lfsr !== start);
console.log('this feedback polynomial period:', period);
  1. トップ
  2. tech
  3. 標準符号化試験信号 PN符号 LFSR