3.3 V ノイズジェネレータ (1)

ノイズジェネレータの「定番」といえば、小信号用トランジスタのベース・エミッタ間に逆バイアスをかけ、ブレークダウンさせて「ツェナダイオード」として使い、発生するノイズを増幅する方式が一般的です。
しかし、小信号用トランジスタの VEBO はスペック上で 5 〜 6 V 程度、実際の値としては 8 〜 9 V 程度になるので、当然、電源電圧 3.3 V では実現できません。
ここでは、アナログ / ディジタルによる 3.3 V 電源で動作可能なノイズジェネレータの方式について検討していきたいと思います。
まずは、3.3 V での動作に何の問題もない、ディジタル方式から始めます。
新鮮味はないのですが、マイコンでソフトウェア的に実現した LFSR (Linear Feedback Shift Register) による m-系列 (最長周期系列) でホワイトノイズを発生させる方式を試してみました。
プログラム・リストは後で示しますが、ATtiny13A マイコンで 31 ビットの生成多項式を使い、ループ一回あたり 13 クロック、内蔵 9.6 MHz クロックで、出力の更新レート (スペクトラム拡散通信の用語で言えば「チップ・レート」) は約 780 kHz、1周期は約 2900 秒というスペックになりました。
ソフトウェアで LFSR を実現する場合、いわゆる「ガロア (Galois) 型」の計算方法が都合がよく、この方式ではプログラムの実行時間およびプログラムサイズに生成多項式 (generator polynomial) の項数 (タップ数) が影響を及ぼさないのを利用し、次の 3 種類の生成多項式を試してみました。

生成多項式 1
g(x) = x31 + x28 + 1

生成多項式 2
g(x) = x31 + x13 + 1

生成多項式 3
g(x) = x31 + x29 + x27 + x25 + x23 + x21 + x19 + x17
+ x15 + x13 + x11 + x9 + x7 + x6 + x4 + x + 1

いわゆる「フィボナッチ (Fibonacci) 型」では、生成多項式の項数が少ないほうが都合がよいため、「生成多項式 1」のような 3 項の多項式が使われます。
しかし、「乱数列」として見た場合の性質は余り良くないことが知られています。
同じ 3 項の生成多項式でも、中間の「タップ位置」をシフトレジスタの幅の中央付近に取ると多少は改善されるそうで、そのような多項式を選んだのが上の「生成多項式 2」です。
「生成多項式 3」は、思い切って、項数を次数の半分程度 (17 項) に選んだもので、生成多項式のビットパターンを 0x55555555 から始めて、m-系列になるパターンを適当に探索したものです。 統計的性質などは調べてありません。
上の3種の生成多項式によるホワイトノイズ出力を WaveSpectra で観測した結果を下に 1、2、3 の順番で示します。



グラフの上の赤い線はピークレベルで、真ん中の黒い線は瞬時値、下の青い線は平均のレベルです。
入力サンプリング周波数は 96 kHz で、40 kHz 付近からレベルが下がっているのは ADC の入力フィルタの影響で、ホワイトノイズ出力自体のレベルが下がっているわけではありません。
「ホワイトノイズ」の言葉通り、各周波数でのレベルは一定で、グラフを見る限り、生成多項式の違いによる差は、特に感じられません。
実際に音を聞くと、特に低域で 1 と 2 は同様で、3 は少し違うように思えますが、はっきりしないので、-3dB/oct のフィルタをかけてピンクノイズにしてから再び聞き比べようと思っています。
最後にプログラム・リストを示します。 生成多項式 3 の設定が有効になっています。

;
; m_noise: white noise generator using LFSR m-sequence
;          (length = 2^31 - 1 = 2147483647)
;          for Atmel AVR ATtiny13A
;
;          chip rate = 783.5 kHz @ CPU_clock = 9.6 MHz
;          period    = 2908 sec  @ CPU_clodk = 9.6 MHz
;
; 2010/08/16 created by pcm1723
; 

        .include "tn13adef.inc"

; genarator polynomial g(x)
; g(x) = x^31 + x^29 + ... x^1 + 1
; g(x) =  [31, 29, 27, 25, 23, 21, 19, 17,
;          15, 13, 11,  9,  7,  6,  4,  1,  0]
        .equ    GXTAP = 0x55555569

; g(x) = x^31 + x^13 + 1
;       .equ    GXTAP = 0x40001000

; g(x) = x^31 + x^28 + 1
;       .equ    GXTAP = 0x48000000

; LFSR inital value
        .equ    SRINIT = 0x00000001

; registers for LFSR
        .def    SR3 = r25
        .def    SR2 = r24
        .def    SR1 = r23
        .def    SR0 = r22

; registers for g(x) tap pattern
        .def    GX3 = r21
        .def    GX2 = r20
        .def    GX1 = r19
        .def    GX0 = r18

        .cseg
        rjmp    reset_entry

reset_entry:
;
; setup GPIO
;
        ldi     SR0,0x3F
        out     PORTB,SR0   ; enable pull up 
        sbi     DDRB,PORTB4 ; PB4 = OUTPUT
;
; load generator polynomial
;
        ldi     GX3,byte4(GXTAP)
        ldi     GX2,byte3(GXTAP)
        ldi     GX1,byte2(GXTAP)
        ldi     GX0,low(GXTAP)
;
; LFSR initial value
;
        ldi     SR3,byte3(SRINIT)
        ldi     SR2,byte2(SRINIT)
        ldi     SR1,byte1(SRINIT)
        ldi     SR0,low(SRINIT)
;
out_zero:
;
; output '0' to port
;                        (clk)
        cbi     PORTB,PORTB4 ; #2, PB4 = 0
        nop             ; #1, timing adjust
        nop             ; #1, timing adjust
        nop             ; #1, timing adjust
        nop             ; #1, timing adjust
        nop             ; #1, timing adjust
;
; 13 CPU clocks per loop
;
loop_entry:
;
; shift right 1 bit
;                       (clk)
        lsr     SR3     ; #1,
        ror     SR2     ; #1,
        ror     SR1     ; #1,
        ror     SR0     ; #1,
        brcc    out_zero ; #1(2), branch if C=0
out_one:
; 
; output '1' to port and apply g(x)
;
        eor     SR3,GX3 ; #1,
        sbi     PORTB,PORTB4 ; #2, PB4 = 1
        eor     SR2,GX2 ; #1, 
        eor     SR1,GX1 ; #1,  
        eor     SR0,GX0 ; #1, 
        rjmp    loop_entry ; #2, loop again