SX Video IO

Untested SX code for detecting video sync with the onboard comparator

Nikolai Golovchenko says:

Sync detection is sure possible with a SX. I made a quick test on the evaluation board (50 MHz SX28), just for horizontal sync, using the comparator to catch the sync pulses. It works in most cases, but sometimes, when video is bad quality (noisy, but screen is stable) lines are missed with that setup.

There are a few problems:

1) Comparator lowest input voltage level is 0.4V. With a 0 to 1V video, sync threshold is at about 0.15V. So it is out of specs, though seems to be working.

2) Noise. I wonder how screen remains stable even when signal is noisy. There must be some filtering in a TV. I'll try to dig the sync separator circuit from an old TV.

3) SX counts the pulse width in a loop like:

        .
        .
        .
measure_ov2
        mov w, #5
        mov dx, w
measure3
        mov w, #128-100
        mov RTCC, w
        mov w, #compMode
measure3a
        mov !RB, w
        mov temp, w
        snb temp.0
         jmp measure_ok
        sb RTCC.7            ;or any other reg my be used for
                             ;counting
         jmp measure3a
        decsz dx
         jmp measure3
;too long - repeat pulse catch
        jmp wait_1
        .
        .
        .

The inner loop takes 8 cycles (without comparator that would be 6 cycles). So the comparator is sampled every 8 cycles, and maximum jitter in sync detection is 8 cycles. That can be visible if SX frequency is too low. Probably, it will be better to build an external circuit on a transistor and not use the comparator altogether.

Nikolai Golovchenko says:

Okay, synchronizing to horizontal pulses video in software works now. The trickiest part was to lock to sync pulses with the free running internal oscillator. Even the smallest errors (under 0.01 us per line) build up very quickly and synchronization fails...

The program implements only horizontal sync locking, but vertical sync pulses detection should be a bit easier since they coincide with horizontal ones. Here is the details:

I used the built-in comparator and a DC level shifter to separate sync pulses. Here is the circuit in ascii 'art'.

                     -------*----------> +5V
                     |      |  R2
                     |      |  4k
               VT1   |      --/\/\/--
               n-p-n C\|            |       _
                      B|----*-/\/\/-*   RB1| \
     R1       C2     E/|    |  R3   -------|- \   RB0
     1k       0.22u  |      |  30       RB2|   >----o
 o--/\/\/--*--||-----*------|--------------|+ /  Composite
 Video     |         |      |              |_/   sync out
 In       --- C1     \ R5   --|>|---|
          --- 330p   / 2.2M   VD1   |     Comparator
           |         \              |
          === GND   ===            === GND

The resistors R2,R3 and diode VD1 set about a 60 mV threshold above the sync pulses lower level (sync pulses are negative going). Surprisingly, the comparator negative input reference should have been taken *above* the transistor base (as it is in the circuit). One would assume that BE voltage drop is about 0.6V and the negative input of comparator is already 0.6V above the sync 'floor'. But it is not so. The capacitor C2 has almost no way to discharge and consequently it is charged with very low current and the BE drop is about 0 V. Therefore, the sync floor potential is equal to the voltage set by divider at the transistor base. When the charging current rises, which happens when a new sync pulse arrives, so does the difference between comparator inputs, so the pulse at this moment is not missed.

The circuit takes 3 pins: 2 comparator inputs and 1 output. Comparator output could be checked by polling its configuration register, but it takes 3 instructions to check it this way:

waitHigh mov !RB, w     ;exchange comparator conwfiguration
                        ;with w (mode = $08)
        mov temp, w
        sb temp.0
         jmp waitHigh   ;6 cycles in loop

while it takes just 1 instruction to check the port pin

waitHigh sb RB.0
         jmp waitHigh   ;4 cycles in loop

So I chose to sacrifice the pin as it is better to sample the comparator output a little faster (and smaller code size too).

Locking to the HSync frequency is the most difficult part here. As Simon Nield suggested I use two PLL modes:

  1. ) Crash PLL. It is used after chip reset and when too many sync pulses are missed. On each external pulse (longer than 2 us) SX resets RTCC and calculates error on the next pulse. Errors are summed up for 256 samples and the timer load value is corrected with the average error value. The timer load value is 16 bit, higher byte adds directly to RTCC and lower one to phase accumulator.

    This mode works until average error goes low enough. After that the SX enters the fine PLL mode.

  2. ) Fine PLL. This is the hardest part. In this mode, the RTCC load value is not corrected (that is the line is assumed to have a fixed length), only RTCC is corrected. The correction value is produced by a low pass filter, which averages the errors. This works like an integral term in a PI regulator. I found out that proportional term (subtracting current error from RTCC) doesn't help really, because the error signal is so noisy that subtracting it from RTCC results in passing the noise further.

It works pretty well for a wide range of video sources, maybe just a bit worse than the TV circuit. Another possible issue may be that a line can have slightly different length for a different video source. That may result in a different position of overlaid image on the screen, because the clock is not adjusted, only its period is adjusted by PLL.

By the way, this was tested on SECAM, but I think NTSC and PAL are very close to SECAM in the horizontal sync parameters. In SECAM horisontal period is 64 us, sync pulse is 4.7 us, and equalizing pulses are 2.35 us.

Next step is vertical sync...

; PLL Syncronized signal over video (horizontal sync)
; SX28-52 demo board
; 
; Crystal: 50 MHz
; Connections:
; RB3 - button (active low)
; RA0 - output --\/\/\---|>|--- to video
;               R1 220  VD1
; RB0 - comparator output (sync active low)
;
;                     -------*----------> +5V
;                     |      |  R2
;                     |      |  4k
;               VT1   |      --/\/\/--
;               n-p-n C\|            |       _
;                      B|----*-/\/\/-*   RB1| \
;     R1       C2     E/|    |  R3   -------|- \   RB0
;     1k       0.22u  |      |  30       RB2|   >----o
; o--/\/\/--*--||-----*------|--------------|+ /  Composite
; Video     |         |      |              |_/   sync out
; In       --- C1     \ R5   --|>|---|
;          --- 330p   / 2.2M   VD1   |     Comparator
;           |         \              |
;          === GND   ===            === GND
;
; The program locks to horizontal sync pulses and outputs 
; two vertical lines when the button is pressed. 
;
; RTCC is used without prescaler as a time base. A full line
; takes 64us*50=3200 cycles or 12.5 periods (overflows) of timer.
; At the start of each line a phase variable (default is 128) is
; added to RTCC, so RTCC overflows 13 times per line.
;
	DEVICE	SX28AC, BANKS8, PAGES4
	DEVICE  OSCHS3, TURBO, OPTIONX
	RESET start

	ORG 8
;global bank
	ORG $10
;bank 0
temp		DS 1
AVEHI		DS 1	;filter/averager output
AVELO		DS 1	;
counter		DS 1	;averaging counter
flags		DS 1
missedPulses	DS 1	;missed pulses counter
phase		DS 2	;16 bit phase
RTCClow		DS 1	;phase accumulator
RTCCsample	DS 1	;error sample
	ORG $30
;bank 1
	ORG $50
;bank 2
	ORG $70
;bank 3
	ORG $90
;bank 4
	ORG $B0
;bank 5
	ORG $D0
;bank 6
	ORG $F0
;bank 7

ledRun		EQU RC.7
keyGo		EQU RB.3
videoOut 	EQU RA.0
videoIn 	EQU RB.0	;comparator output (sync is negative going)
initRTCC 	EQU 128		;default RTCC initial value
crashPLL	EQU flags.0
pulseBad	EQU flags.1
compMode	EQU %00000000
;                    ||\____/
;Comparator enabled _||  |
;Comp. RB0 out enabl._|  |
;reserved bits __________|

        ORG 1
start
; Set up pins (mode = $0F - port direction access)
	clr RA
	clr RB
	clr RC
	mov w, #$00
	mov !RA, w	;RA - output
	mov !RC, w	;RC - output
	mov w, #$0E
	mov !RB, w	;RB - output/input
;set up RTCC
	mov w, #%11001000
;                |||||\_/
;                ||||| | 
;Reg 0 is RTCC __||||| | 
;RTCC int is dis__|||| | 
;RTCC clock int.___||| | 
;RTCC edge 0/1______|| |  
;Prescaler to WDT ___| |
;Prescaler (WDT) 1:2 __|
	mov !OPTION, w
;256 RTCC counts is 256/50=5.12 us

;set up comparator
	mov M, #$08		;comparator access
	mov w, #compMode
	mov !RB, w		;exchange comparator reg and w
        mov M, #$0F		;restore mode
        
;Catch a 4us pulse
crashStart
	mov w, #initRTCC	;initialize phase value
	mov phase+1, w
	clr phase
	clr RTCClow             ;clear phase accumulator
	setb crashPLL		;enter 'crash' PLL mode
	clr AVEHI		;clear averager
	clr AVELO      	        ;
	clr counter          	;
	clr missedPulses        ;clear missed pulses counter

waitHSync
	sb videoIn      ;wait for 1
	 jmp $-1
	snb videoIn	;wait for 0
	 jmp $-1
	 
	clr RTCC	;reset timer
	
			;input must be 0 for at least 4 us
			;4us = 4 * 50 = 200 cycles
	mov w, #33	;33*6=198
	mov temp, w
measureHSync
	snb videoIn
	 jmp waitHSync	;pulse too short
	decsz temp
	 jmp measureHSync ;6 cycles in a loop

;We don't detect if a pulse is too long since this would be a vertical
;sync which is synchronized to the horizontal pulses.
;By rejecting pulses less than 4 us we reject also equalizing pulses
;which appear at vertical sync at double rate of horizontal sync pulses.
;This is done to start crash PLL mode somewhere from a normal HSync 
;to not get into the middle of lines. But in normal PLL mode the equalizing
;pulses are detected.

;At this point about 4 us passed since HSYNC pulse first front and RTCC reset
;4us = 200 cycles, so timer didn't overflow yet
	snb RTCC.7	;wait for timer overflow
	 jmp $-1

updateTimer
	mov w, phase		;add phase to 
	add RTCClow, w          ;phase accumulator
	mov w, phase+1          ;
	snc                     ;
	 mov w, ++phase+1       ;
	add RTCC, w             ;and timer

;Now we have about 128+-60 cycles before 2nd RTCC overflow

;Wait 4 overflows 4 * 256/50 = 20.48 us
	mov w, #4
	mov temp, w
wait5th
	sb RTCC.7
	 jmp $-1
	snb RTCC.7
	 jmp $-1
	decsz temp
	 jmp  wait5th

; Send a 5.12 us pulse (one RTCC overflow) over video if button pressed
	snb keyGo	;don't set videoOut if no button pressed
	 setb videoOut
	snb keyGo
	 setb ledRun

	sb RTCC.7	;wait for transition from 1 to 0 in RTCC.7
	 jmp $-1
	snb RTCC.7
	 jmp $-1
			;end pulse
	clrb videoOut
	clrb ledRun

;Wait 6 overflows (12 total so far)
	mov w, #6
	mov temp, w
wait12th
	sb RTCC.7	;wait for transition from 1 to 0 in RTCC.7
	 jmp $-1
	snb RTCC.7
	 jmp $-1
	decsz temp
	 jmp  wait12th

; Send a 2.56 us pulse (half RTCC period) over video if button pressed
	snb keyGo	
	 setb videoOut
	snb keyGo
	 setb ledRun

        sb RTCC.7
         jmp $-1

	clrb videoOut   ;end pulse
	clrb ledRun

        sb RTCC.5	;wait until 96 counts just before 13th overflow,
         jmp $-1        ;which should coincide with the next HSync pulse

;Start detecting HSync. Wait for positive to negative transition
;and measure the pulse width.

        clrb pulseBad
	mov w, #32	;32*6=96*2 (+-96 cycles window)
	mov temp, w
HSyncWaitHigh
	snb videoIn
	 jmp HSyncWaitLow
	decsz temp
	 jmp HSyncWaitHigh
	setb pulseBad
	jmp PulseNotCaught
HSyncWaitLow
	sb videoIn
	 jmp PulseCaught
	decsz temp
	 jmp HSyncWaitLow
	setb pulseBad
	jmp PulseNotCaught


PulseCaught
	mov w, RTCC	;sample RTCC, which is a distance from
			;pulse to overflow.
	mov RTCCsample, w
        
			;input must be 0 for at least 2 us
			;2us = 2 * 50 = 100 cycles
	mov w, #16	;16*6=96
	mov temp, w
PulseCaught1
	snb videoIn
	 setb pulseBad ;pulse too short
	decsz temp
	 jmp PulseCaught1   ;6 cycles in a loop

;At this point, 98 cycles after RTCC sample
;Don't check if the pulse is too long. VSync pulse coincides
;with HSync.
PulseNotCaught
;wait for overflow (RTCC=98+-96=2..194)
        sb RTCC.7
         jmp $-1
        snb RTCC.7	
         jmp $-1
        
;now we have about 60 cycles here to do PLL (default RTCC increment
;is 128 and it can change about +-60 (+-2% variation in line length)
;and we have to catch timer's first overflow)

;now check pulse measurement results. If pulse bad, clock on
;RTCC and increment missed pulses counter
	sb pulseBad
         clr missedPulses ;clear missed pulses counter
	sb pulseBad
	 jmp PLLstart
        
;pulse is missed.
;increment missed counter and resynchronize if too many missed.
        inc missedPulses
        snb missedPulses.5	;threshold = 32 missed pulses
         jmp crashStart
        clr RTCCsample  ;assume zero RTCC sample and
                        ;run PLL anyway
                        
PLLstart
	snb crashPLL
	 jmp crashPLLbranch
;Fine PLL mode.
;Integral control:
;  	RTCC -= H(z)*error*gain
;
;   H(z)=b0/(1-a1*1/z), or y = u*b0 + y[-1]*a1
;   
;   b0 = 1/256, a1 = 255/256
;   gain = 1/256

;Run low-pass filter
	mov w, AVEHI	;AVE *= 1-1/256
	sub AVELO, w
	snb AVEHI.7
	 inc AVEHI
	sc
	 dec AVEHI

	mov w, RTCCsample ;AVE += RTCCsample/256
	add AVELO, w
	snc
	 inc AVEHI
	snb RTCCsample.7
	 dec AVEHI

;correct RTCC,
;RTCC -= AVE/256
	mov w, AVEHI
	sub RTCClow, w
	clr temp
	snb AVEHI.7
	 not temp
	mov w, temp
	sc
	 mov w, ++temp 
	sub RTCC, w

	
        jmp updateTimer
	
crashPLLbranch
 
	mov w, RTCCsample ;accumulate error to determine new phase
	add AVELO, w
	snc
	 inc AVEHI
	snb RTCCsample.7
	 dec AVEHI
                	;reset RTCC (RTCC should be zero at HSync pulse)
	sub RTCC, w
        decsz counter
         jmp updateTimer
        		;error is ready in AVE
        		;subtract it from RTCC load value
        mov w, AVELO
        sub phase, w
        mov w, AVEHI
        sc
         mov w, ++AVEHI
        sub phase+1, w
        		;if error is small
        		;end the crash PLL
        mov w, AVEHI
        snb AVEHI.7
         xor w, #$FF
        sz
         jmp PLL_ErrorTooBig

	mov w, --AVELO
	xor w, #$FF
	sb AVEHI.7
	 mov w, AVELO
	and w, #$F0
	snz
         clrb crashPLL	;end crash PLL phase

PLL_ErrorTooBig
        clr AVELO
        clr AVEHI
        jmp updateTimer




Questions: