ATtiny10 用プログラム (15)

VCO 回路の「コンパレータ + ワンショット」部分として機能する ATtiny10 のプログラム「scr_tn10.asm」のソースを下に示します。
AVR Studio 4.19 で「Atmel AVR Assembler」プロジェクトとして「scr_tn10」を新規作成し、デフォルトで自動作成される空の「scr_tn10.asm」に下の内容をコピー・アンド・ペーストします。
「scr_tn10.asm」

;
; scr_tn10.asm : software SCR (VCO building block)
;                for Atmel AVR ATtiny10
;
; 2011/01/26 created by pcm1723
; 

;       .include "tn10def.inc"
        .nolist
        .include "tn10def.inc"
        .list
;
; set external sync IN / OUT mode
        .set    SYNC_MODE = 0 ; no sync OUT / IN
;        .set    SYNC_MODE = 1 ; sync OUT mode
;        .set    SYNC_MODE = 2 ; sync IN mode
;
; set sync polarity
;
        .set    SYNC_POL = 0 ; negative going pulse
;        .set    SYNC_POL = 1 ; positive going pulse
;
; set discharge polarity
;
        .set    DISCHG_POL = 1  ; discharge to 'VCC' rail
;        .set    DISCHG_POL = 0  ; discharge to 'GND' rail
;
; set discharge period in terms of CPU clocks
;
        .set    DISCHG_CLKS = 41 ; discharge period = 41 clocks
;                                ; 41 / 8 [MHz] = 5.125 [us]
;
; sync OUT / IN port definition
;
        .set    SYNC_BIT = PORTB2  ; PB2 (pin 4)
;
; discharge port definition
;
        .if     (0 == SYNC_MODE)     ; no sync IN/OUT
         .set    DISCHG_BIT = PORTB2 ; PB2 (pin 4)
        .else                        ; sync IN/OUT mode
         .set    DISCHG_BIT = PORTB1 ; PB1 (pin 3)
        .endif
;        .set    DISCHG_BIT = PORTB3  ; PB3 (pin 6)
;
; register assignment (refer to AVR GCC convention)
;  
        .def    _temp_ = r16  ; temporary register
        .def    _zero_ = r17  ; zero register
;
; macro for sbi @0,@1 / cbi @0,@1 selection by @2
;
        .macro  csbi
         .if     (1 == @2)
          sbi     @0,@1
         .else
          cbi     @0,@1
         .endif
        .endm
;
; reset / interrupt vector table
;
        .cseg
        rjmp    reset_entry
        rjmp    int0_entry
        rjmp    pcint0_entry
        rjmp    tim0_capt_entry
        rjmp    tim0_ovf_entry
        rjmp    tim0_compa_entry
        rjmp    tim0_compb_entry
        rjmp    ana_comp_entry
        rjmp    wdt_entry
        rjmp    vlm_entry
        rjmp    adc_entry
;
reset_entry:
        ldi     _temp_,low(RAMEND)
        out     SPL,_temp_
        ldi     _temp_,high(RAMEND)
        out     SPH,_temp_     ; setup SP to point RAMEND
        clr     _zero_         ; ZERO register
;
; setup clock prescaler to 1/1 (8 MHz)
;
        ldi     _temp_,0xD8    ; protect signature
        out     CCP,_temp_     ; CCP <-- 0xD8
        ldi     _temp_,0x00    ; 0x00 = 1/1 
        out     CLKPSR,_temp_  ; set CLocK PreScaleR
;
; setup port bits
;
        ldi     _temp_,0x0f      ; for all port bits,
        out     DIDR0,_temp_     ; disable digital input
        out     DDRB,_zero_      ; set all port bit to input
        out     PUEB,_zero_      ; disable pull up
; sync IN/OUT
        .if     (1 == SYNC_MODE) ; sync OUT mode?
          csbi    PORTB,SYNC_BIT,(1-SYNC_POL) ; set normally level
          sbi     DDRB,SYNC_BIT  ; set to output direction
        .elif   (2 == SYNC_MODE) ; sync IN mode?
          cbi     DIDR0,SYNC_BIT ; enable digital input
          sbi     PUEB,SYNC_BIT  ; enable pull up
        .endif
; discharge port
        csbi     PORTB,DISCHG_BIT,DISCHG_POL  ; discharge to 'VCC'/'GND' rail
;
; setup analog comparator
;
        sbi    ACSR,ACIS1
        sbi    ACSR,ACIS0       ; positive edge sync
        sbi    ACSR,ACIE        ; interrupt enable
;
; setup Pin Change INTerrupt if external sync IN mode
;
        .if    (2 == SYNC_MODE)
        sbi    PCMSK,SYNC_BIT   ; set Pin Change bit MaSK
        sbi    PCICR,PCIE0      ; Pin Change Interrupt Enable
        .endif
;
        rcall  discharge_25ms   ; initial discharge
;
; enable watchdog timer in interrupt mode
; timeout = 4 sec
;
        ldi    _temp_,(1<<WDIE)|(1<<WDP3) ; WatchDog Interrupt Enable
        out    WDTCSR,_temp_    ; WDP[3:0] = 1000 for 4 sec timeout
;
        sei                     ; enable interrupt globally
;
; setup for sleep
;
sleep_loop:
        ldi    _temp_,0x01      ; idle mode, Sleep Enable
        out    SMCR,_temp_
        sleep                   ; sleep to wait comarator interrupt
        out    SMCR,_zero_      ; clear Sleep Enable bit
        rjmp   sleep_loop       ; sleep again
;
; discharge for approx. 25 ms @ 8 MHz clock
;
discharge_25ms:
        sbi     DDRB,DISCHG_BIT ; start discharge
        .if     (1 == SYNC_MODE) ; sync OUT mode?
          csbi    PORTB,SYNC_BIT,SYNC_POL ; set active level
        .endif
        clr     _temp_          ; loop_count = 256 * 256
d25ms_L0:
        dec     _temp_          ; 
        brne    d25ms_L0        ; inner loop
        dec     _zero_          ; 
        brne    d25ms_L0        ; outer loop
        cbi     DDRB,DISCHG_BIT ; end discharge, go to Hi-Z
        .if     (1 == SYNC_MODE) ; sync OUT mode?
          csbi    PORTB,SYNC_BIT,(1-SYNC_POL) ; set normally level
        .endif
        ret
;
; Pin Change INTerrupt service routine
; for external sync input
;
pcint0_entry:  
        .if     (2 == SYNC_MODE)  ; external sync IN mode
          .if   (1 == SYNC_POL)   ; sync at 0 --> 1 edge
            sbis    PINB,SYNC_BIT ; (1 == SYNC_BIT) ?
            reti                  ; no, immediate return
          .else                   ; sync at 1 --> 0 edge
            sbic    PINB,SYNC_BIT ; (0 == SYNC_BIT) ?
            reti                  ; no, immediate return
          .endif        
        .endif           
;
; sync edge condition met,
; fall into 'ana_comp_entry'
;
; ANAlog COMParator interrupt service routine
; for discharge pulse generation
;
ana_comp_entry:
;                              [#Clocks] 
        sbi     DDRB,DISCHG_BIT ; [1] start discharge
        .if     (1 == SYNC_MODE) ; sync OUT mode
          csbi    PORTB,SYNC_BIT,SYNC_POL ; [1] set active level
        .endif
        push    _temp_          ; [2]
        in      _temp_,SREG     ; [1]
        push    _temp_          ; [2] prologue
;
        ldi     _temp_,(DISCHG_CLKS-11)/3 ; [1] init loop count
ana_comp_L1:
        dec     _temp_          ; [1]
        brne    ana_comp_L1     ; [1/2]
;
        pop     _temp_          ; [2] epilogue
        out     SREG,_temp_     ; [1]
        pop     _temp_          ; [2]
        cbi     DDRB,DISCHG_BIT ; [1] end discharge, go to Hi-Z
        .if     (1 == SYNC_MODE) ; sync OUT mode?
          csbi    PORTB,SYNC_BIT,(1-SYNC_POL) ; set normally level
        .endif
        wdr                     ; watchdog timer reset
        .if     (2 == SYNC_MODE) ; external sync IN mode
          sbi     PCIFR,PCIF0     ; clear Pin Change Interrupt Flag
        .endif
        reti
;
; WatchDog Timer interrupt service routine
; to recover stucked output
;
wdt_entry:
        push    _temp_          ; 
        in      _temp_,SREG     ; 
        push    _temp_          ; prologue
;
        rcall   discharge_25ms  ; discharge for 25 ms
;
        pop     _temp_          ; epilogue
        out     SREG,_temp_     ; 
        pop     _temp_          ; 
        reti
;
; invalid interrupts
;
int0_entry:
tim0_capt_entry:
tim0_ovf_entry:
tim0_compa_entry:
tim0_compb_entry:
vlm_entry:
adc_entry:
        reti
;

これをそのままアセンブルすると、 1 月 26 日付けの記事の HEX ファイルができあがります。
AVR ネイティブのアセンブラなので、不正な 2 ワード lds/sts 命令が生成される可能性はありませんから、HEX ファイルの変換は不要です。
さらには、もともと、このプログラム自体はデータを保持する「変数」としては SRAM を使っていないので、lds/sts 命令そのものを全く使用していません。
プログラム中のシンボル「SYNC_MODE」の設定値、0、1、2 に対して、それぞれ

  • 外部 SYNC 入出力なし (放電用端子は独立、Franco の補償が可能)
  • 外部 SYNC 出力
  • 外部 SYNC 入力

のモードになります。
PB3 (6 番ピン) のリセット機能を活かしたままだと、自由に使える入出力ピンは 3 本だけとなり、外部 SYNC 入出力端子を設けるためには、放電用の端子を電圧センス用の端子と同じピンに割り付ける必要があります。
プログラム中で、「SYNC_MODE」の値が 0 でない場合には、放電用の端子は PB1 (3 番ピン) に割り付け、SYNC 入出力は PB2 (4 番ピン) に割り付けるようにしています。
この場合、Franco の補償のための抵抗を挿入することはできず、SCR を使った回路に、より近づくことになります。
PB3 (6 番ピン) のリセット機能を禁止し、入出力ポートとして使える設定にすれば、放電用端子を独立させられますが、その場合には、チップに HEX ファイルを書き込む際に 12 V を 6 番ピンに加える必要があります。
ソース・プログラム自体は、そのような構成にも対応していますが、「SYNC_MODE」の値で自動的に切り換わるようにはなっていません。 ソース・プログラムに必要な設定を「手動」で行う必要があります。
ソース上の設定を、

    .set    SYNC_MODE   = 1  ; sync OUT mode
    .set    SYNC_POL    = 0  ; negative going pulse
    .set    DISCHG_POL  = 1  ; discharge to 'VCC' rail
    .set    DISCHG_CLKS = 41 ; discharge period = 41 clocks

とした場合のアセンブル結果の HEX ファイルを下に示します。
「scr_osyn.hex」

:100000000AC046C02EC044C043C042C041C029C03F
:1000100037C03EC03DC00FE50DBF00E00EBF112749
:1000200008ED0CBF00E006BF0FE007BB11B913B924
:10003000129A0A9A119AF99AF89AFB9A08D000E64D
:1000400001BF789401E00ABF88951ABFFBCF099AD7
:10005000129800270A95F1F71A95E1F70998129A74
:100060000895099A12980F930FB70F930AE00A9513
:10007000F1F70F910FBF0F910998129AA895189553
:100080000F930FB70F93E3DF0F910FBF0F911895E9
:020090001895C1
:00000001FF


SYNC_MODE = 1 の場合の回路は左のようになります。
「SYNC_POL」の設定で、SYNC パルスが正極性か、負極性かを切り替えられるようになっており、上のソース・ファイルでは「SYNC_POL = 0 」なので負極性の設定です。 
出力波形自体は 1 月 26 日付けの記事に掲載してあります。

ソース上の設定を、

    .set    SYNC_MODE   = 2 ; sync IN mode
    .set    SYNC_POL    = 0  ; negative going pulse
    .set    DISCHG_POL  = 1  ; discharge to 'VCC' rail
    .set    DISCHG_CLKS = 41 ; discharge period = 41 clocks

として SYNC 入力モードにしてアセンブルした結果の HEX ファイルを下に示します。
「scr_isyn.hex」

:100000000AC047C02EC045C044C043C042C02BC038
:1000100038C03FC03EC00FE50DBF00E00EBF112746
:1000200008ED0CBF00E006BF0FE007BB11B913B924
:10003000BA981A9A119AF99AF89AFB9A829A909A0F
:1000400008D000E601BF789401E00ABF88951ABF86
:10005000FBCF099A00270A95F1F71A95E1F709985D
:10006000089502991895099A0F930FB70F930AE014
:100070000A95F1F70F910FBF0F910998A895889AEB
:1000800018950F930FB70F93E4DF0F910FBF0F91E8
:040090001895189512
:00000001FF


回路を左に示します。 
SYNC 出力モードでの出力ピンが入力ピンになっただけです。
「SYNC_POL」で外部 SYNC 入力の、どちら側のエッジで同期が掛かるかを設定します。
SYNC_POL = 0 でネガティブ・エッジ (1 → 0 のエッジ)、SYNC_POL = 1 でポジティブ・エッジ (0 → 1 のエッジ) が有効になります。
「レベル」ではなく、「エッジ」だけを見ているので、SYNC パルス幅には依存しません。
SYNC 入力ピンはプルアップされるので、オープン状態では「H」レベルを保ちます。
SYNC の機能としては、いわゆる「ハード・シンク」で、出力波形の位相にかかわらず、SYNC 入力のアクティブ・エッジで必ずリセットされます。