音関連のクロックの近似

FPGA で音関連の回路を作ろうとすると master clock が I2S の都合で12.28800 MHz で音源ICが 3.579545 MHz という場合にどう対応するか苦慮する場合がよくある.

12.28800 MHz の clock domain で 3.579545 MHz の device を動かす場合に分周して動かす方法がある. 12.288 / 3.57945 = 3.43... で割り切れないので 3 分周と4分周を混ぜることで平均の周波数を 3.57945 ... に近似することはできる.

分子分母が整数の分数は ruby であれば Rational で作り出すことができる. 3.579... は NTSC の定義では 315.0 / 88 なので下記で精度100%の分数が得られる.

p Rational(315.0/88,12.288)
(8060419787623331/27670116110564328)

こんな桁数が多い値は実用にならないので分子分母ともに3桁ぐらいで総当りで一番の精度を出す.

	ratio = target / master
	best_approx = nil
	min_error = Float::INFINITY

	(2..999).each do |denom|
		numer = (ratio * denom).round
		approx = Rational(numer, denom)
		error = (approx.to_f - ratio).abs

		if error < min_error
			best_approx = approx
			min_error = error
		end
	end
	p best_approx
(67/230)

この近似分数の精度は 1.0000007 倍で音源としては悪くないだろう. *1

67 / 230 が意味することは 3 か 4 で分周した 67 周期のうちで 230 master clock があると約 3.579454 MHz になる. 3 と 4 の分周回数はいくつかは中学2年の連立方程式で解ける.

f1: x + y = p
f2: (d + 1)*x + d * y = q
d: (1/ratio).to_i
p: 近似分数の分子
q: 近似分数の分母

d,p,qの定数を展開すると下記になる.

f1: x + y = 67
f2: 4x + 3y = 230

これは連立方程式の練習問題にいかにもありそうな設定で微積や数列などは一切ないが、周期補正の計算で必要な実用的な連立方程式である. 「4グラムのりんごと3グラムのみかんを数量は合わせて67個,合計の重さは230gにするには何個ずつ買えばいいでしょうか」 そんな練習問題に似すぎていて本当にあっているのか確認してしまった.

x = 29, y = 38. つまり4分周を29回, 3分周を38回すると近似された平均周波数が本当に出る. できるだけ均等に4分周と3分周をまぜるほうがよい.

*1:こういう近似とか平均とかを使うので音関連にはオカルトがつきまとうがそれはご容赦いただきたい