PDA

View Full Version : 16 bit NTSC video Driver



Perry
10-20-2011, 08:27 PM
Thanks to Eric Ball's work with his 8bpp http://forums.parallax.com/showthread.php?134384-NTSC-8bpp-bitmap-display-driver&highlight=8bpp

I am offering for evaluation a 16bit NTSC video driver

This driver only uses 2 pins, on the demo board they would likely be 13 and 15.
pin 13 is the color signals capacitivly coupled to a Duty Dac from pin 15 to deliver luminence.

the demo shows 16 colors and 119 shades


Thanks to any spinner-heads who might try this


Perry

P.S.

having trouble with attachment so here are the two spin files.

first main test program: NTSC_16bit_demo.spin


{{

16bit NTSC video modifications Octber 2011 Perry James Mole pjm@hey-hello.ca

┌───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┐
│ NTSC 8bpp bitmap TV driver demo (C) 2011-08-19 Eric Ball │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│ TERMS OF USE: Parallax Object Exchange License │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┘
}}
CON
' Set up the processor clock in the standard way for 80MHz
' _XINFREQ = 13_500_000 + 0000
' _CLKMODE = XTAL1 + PLL8X

_clkmode = xtal1 + pll16x
_clkfreq = 80_000_000

' Demoboard TV output on pins 13 and 15

TV_PIN = 12

{
Notes from Grey TV driver:

This driver uses PWM to generate output so a low pass filter is recommended:

0.001F
pin 13 ─────┐
pin 15 ──┳─╋── Composit Video output
  value of this cap is small I'm unsure
 
The resistor values (124Ω series, 191Ω to ground) have an output impedance of
75 ohms and will drive a 75-ohm load at 1V P-P. The cap is there to filter the
DUTY doody. - Phil Pilgrim
http://forums.parallax.com/forums/default.aspx?f=25&m=340731&g=342216

However, in my experience, the RC network is not required. I have tested
successfully using any of the Demoboard TV DAC resistors (although the higher
resistance yields darker text) and with no resistors at all (although this
is not recommended).
}
OBJ
tv : "Pixelator_16bit_NTSC" '"NTSC 8bpp"

VAR
' NTSC8bpp input parameters - 7 contiguous longs, reloaded each frame
' Note: NTSC8bpp input parameters not validated, minimum clockspeed 16.364MHz
LONG i_ptr ' pointer to screen (bytes, long aligned), 0 = inactive
LONG i_width ' number of columns, must be divisible by 4 (i_width*i_clocks < 3192-3894MHz/CLKFREQ)
LONG i_height ' number of rows (max i_lines)
LONG i_clocks ' pixel width in 16x colorburst clocks (256 => i_clocks > 515MHz/CLKFREQ)
LONG i_lines ' number of active lines (max 482, does not need to be integer multiple of i_height)
LONG i_pin ' must be divisible by 4, i_pin = 1.1kΩ, i_pin+1 = 560Ω, i_pin+2 = 270Ω
LONG i_frqa ' 0 = calculate FRQA using CLKFREQ=long[0], MSB set = mask of calculated value, MSB clear = value to use
long i_gain ' output video gain Added for Pixelator
' For "square" pixels 3*i_height*i_clocks = 14*i_lines
' i_clocks < 10 and i_lines < 2*i_height will generate color artifacts instead of extra detail
' Due to overscan not all of the screen is visible on all TVs.
' "Action Safe" i_width*i_clocks < 2750, i_lines < 433; "Title Safe" i_width*i_clocks < 2444, i_lines < 385
word bitmap[80*120]

PUB start | color,i,j,ver

tv.start( @i_ptr )

i_width := 80
i_height := 120
i_lines := 480
i_clocks := 36
i_pin := TV_PIN
i_frqa := 0
i_ptr := @bitmap
i_gain := 24

repeat j from 0 to 119 step 1
repeat i from 0 to 79 step 1
bitmap[i+j*80] := ((((i/4 & $F )<<4) + 8) << 8) + j*2



and secondly the actual video driver: Pixelator_16bit_NTSC.spin



{{
┌───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┐
│ NTSC 8bpp bitmap TV driver (C) 2011-06-16 Eric Ball │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤

modifications for 16 bit Octber 2011 Perry James Mole
most changes should be commented with ' PJM

│ The NTSC8bpp TV driver displays a i_width x i_height image on a TV using standard 3pin TV DAC. │
│ The bitmap is padded horizontally and scaled vertically to i_lines. │
│ Pixels are stored in display order (left to right, top to bottom). │
│ │ │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│ TERMS OF USE: Parallax Object Exchange License │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┘
}}

VAR

' long cog

{{
' NTSC8bpp input parameters - 7 contiguous longs, reloaded each frame
' Note: NTSC8bpp input parameters not validated, minimum clockspeed 16.364MHz
LONG i_ptr ' pointer to screen (bytes, long aligned), 0 = inactive
LONG i_width ' number of columns, must be divisible by 4 (i_width*i_clocks < 3192-3894MHz/CLKFREQ)
LONG i_height ' number of rows (max i_lines) square pixel = (14*i_height)/(3*i_lines)
LONG i_clocks ' pixel width in 16x colorburst clocks (256 => i_clocks > 515MHz/CLKFREQ)
LONG i_lines ' number of active lines (max 482, does not need to be integer multiple of i_height)
LONG i_pin ' must be divisible by 4, i_pin = 1.1kΩ, i_pin+1 = 560Ω, i_pin+2 = 270Ω
LONG i_frqa ' 0 = calculate FRQA using CLKFREQ=long[0], MSB set = mask of calculated value, MSB clear = value to use
LONG l_Gain ' PJM added to control greyscale output dynamicaly ie contrast
' For "square" pixels 3*i_height*i_clocks = 14*i_lines
' i_clocks < 10 and i_lines < 2*i_height will generate color artifacts instead of extra detail
' Due to overscan not all of the screen is visible on all TVs.
' "Action Safe" i_width*i_clocks < 2750, i_lines < 433; "Title Safe" i_width*i_clocks < 2444, i_lines < 385
}}

PUB start( parmptr )

stop
RESULT := COGNEW( @cogstart, parmptr)
cog := RESULT + 1
RETURN

PUB stop
COGSTOP( cog~ - 1 )

DAT
{{
The purpose of this code is twofold. First, it can be used to display a
byte per pixel (TV color) bitmap. Second, this code is intended to be a
template which may be used by others to develop Propeller video drivers.
Note: this code creates an 29.97Hz 525 line interlaced display.

Rules for developing video drivers:
1. Start simple. Hardcode values and static display.
2. Add complexity and changes incrementally. Verify at each step.
3. If something doesn't work it's either because you have made an incorrect
assumption or made a coding error.
}}

ORG 0
cogstart RDLONG i_ptr, PAR WZ
IF_Z JMP #cogstart

MOV taskptr, #taskone
MOV numlines, #4
:dotasks JMPRET taskptr, taskptr
DJNZ numlines, #:dotasks

{{
An interlaced NTSC frame is made up of 525 lines:
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
12 lines blank
241 lines active (top field / first line)
half line (with sync pulse)
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
half line (without sync pulse)
12 lines blank
241 lines active (bottom field / last line)

A non-interlaced NTSC frame is made up of 262 lines:
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
12 lines blank
241 lines active
}}

frame MOV numlines, numblank0 WZ ' overscan blank lines
MOV taskptr, #taskone ' reload parameters
IF_NZ CALL #blank

MOV nxtptr, i_ptr
MOV linenum, i_lines
MOV numlines, numodd
CALL #active

MOV numlines, numblank1 WZ ' overscan blank lines
IF_NZ CALL #blank

MOV VSCL, vscl_sync ' half line
WAITVID dblank, #0
MOV FRQB, #0 ' PJM

MOV VSCL, vscl_serr
WAITVID dblank, sblank
MOV FRQB, pwmlut ' PJM

CALL #vsync

MOV VSCL, vscl_half ' extend blank for 1/2 line
WAITVID dblank, sblank
MOV FRQB, #0 ' PJM

{
One of the limitations of TV.spin is it only allows integer vertical
scaling. This calculation (and the matching calculation inside the active
loop) scales a i_height bitmap to i_lines. Note: the first line is
always displayed on the top field.
}
MOV nxtptr, i_ptr
MOV linenum, i_lines
SUB linenum, i_height WZ,WC ' set scaling for 2nd line
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, i_width ' go to next line (single line res)
IF_Z_OR_C ADD nxtptr, i_width ' go to next line (single line res)

MOV numlines, numblank2 WZ ' overscan blank lines
IF_NZ CALL #blank

MOV numlines, numeven
CALL #active

MOV numlines, numblank3 WZ ' overscan blank lines
IF_NZ CALL #blank

CALL #vsync ' moved here to reduce instructions
' from active to equalizing
JMP #frame

{{
As each pixel may be any color, the pixels are effectively the color values.
Therefor the code uses a "reverse WAITVID" which uses the pixel value for the
color (destination) parameter and #%%3210 to output the colors in LSB first
order. The downside of this method is it requires more RAM and the maximum
resolution is clockspeed limited (which isn't a problem for composite NTSC
which is bandwidth limited by the colorburst frequency).
}}
active MOV VSCL, vscl_sync ' 4.7usec @ -40 IRE (sync pulse)
WAITVID dblank, #0
MOV FRQB, #0 ' PJM

MOV pixptr, nxtptr ' precalculate pointer
SUB linenum, i_height WZ,WC ' vertical scaling calculations
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, w_width ' go to next line
SUB linenum, i_height WZ,WC ' do twice for interlace
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, w_width ' go to next line
MOV numpixels, i_width

MOV VSCL, vscl_s2cb ' 0.6usec @ 0 IRE (blank)
WAITVID dblank, sblank
MOV FRQB, pwmlut ' PJM

MOV VSCL, vscl_burst ' 9 cycles of colorburst
WAITVID dblank, sburst
MOV FRQB, burst 'pwmlut ' PJM

MOV VSCL, vscl_edge
WAITVID dblank, sblank
MOV FRQB, black ' PJM

mov vs4,vscl_active ' PJM
shr vs4,#2 ' PJM
MOV VSCL, vs4 'vscl_active ' PJM
' i_clocks PLLA per pixel, 4 pixels per frame

' WAITVID pixel, #%%0
:loop RDword pixel, pixptr ' PJM'RDLONG pixel, pixptr ' 22 CLK
' WAITVID pixel, #%%3210 ' +6 CLK
' ADD pixptr, #4 ' +4 CLK
mov luminance,pixel ' PJM
shl luminance,l_Gain ' PJM
add luminance,black ' PJM

WAITVID pixel, #%%1 ' PJM
MOV FRQb,luminance ' PJM
ADD pixptr, #2 ' PJM

DJNZ numpixels, #:loop ' +4 CLK = 36 CLK

MOV VSCL, vscl_edge
WAITVID dblank, sblank
MOV FRQB, pwmlut ' PJM

DJNZ numlines, #active ' next line
active_ret RET

{{
Video drivers are constrained by WAITVID to WAITVID timing. In the inner
active display loop, this determines the maximum resolution at a given clock
frequency. Other WAITVID to WAITVID intervals (e.g. front porch) determine
the minimum clock frequency.

For example the inner :loop in this code is 36 CLK and displays 4 pixels.
Therefore with an 80MHz clock the minimum VSCL.PixelClocks (i_width) is 7.
36 / 80MHz * 57.272727MHz / 4 = 6.44

Similar calculations should be used between each pair of WAITVIDs to determine
the minimum clock frequency (including initialization tasks).

Note: the first HUBOP after a WAITVID should be counted as 22 CLK as it cannot
be synchronized. WAITVID should be counted as 6 CLK as 5 CLK can cause
intermittent errors with very tight timing.
}}

vsync MOV numlines, #6
:equal1 MOV VSCL, vscl_eqlo ' equalizing pulse (sync/2)
WAITVID dblank, #0
mov frqb,#0 ' PJM

MOV VSCL, vscl_eqhi
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:equal1

MOV numlines, #6
:serration MOV VSCL, vscl_serr ' serration pulse (sync)
WAITVID dblank, #0 ' PJM
mov frqb,#0

MOV VSCL, vscl_sync
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:serration

MOV numlines, #6
:equal2 MOV VSCL, vscl_eqlo ' equalizing pulse (sync/2)
WAITVID dblank, #0
mov frqb,#0 ' PJM

MOV VSCL, vscl_eqhi
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:equal2

vsync_ret RET

blank MOV VSCL, vscl_sync ' 4.7usec @ -40 IRE (sync pulse)
WAITVID dblank, #0 ' PJM
mov frqb,#0

MOV VSCL, vscl_s2cb ' 0.6usec @ 0 IRE (blank)
WAITVID dblank, sblank
mov frqb,pwmlut

MOV VSCL, vscl_burst ' 9 cycles of colorburst
WAITVID dblank, sburst
mov frqb,#0 ' PJM

MOV VSCL, vscl_half
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
JMPRET taskptr, taskptr

MOV VSCL, vscl_blank
WAITVID dblank, sblank
mov frqb,#0 ' PJM

DJNZ numlines, #blank ' next line
blank_ret RET

d1 LONG |<9 ' destination = 1

taskone MOV pixptr, PAR ' load input parameters
MOVD :i_loop, #i_ptr ' 242 CLK
MOV numpixels, #8 ' ' PJM #7 add l_Gain to inputs
:i_loop RDLONG i_ptr, pixptr
ADD pixptr, #4
ADD :i_loop, d1
DJNZ numpixels, #:i_loop

MOV W_width,i_width
shL W_width,#1

CMP i_ptr, #0 WZ ' 0 = blank
IF_Z JMP #standby ' 8 CLK

MOV vscl_active, i_clocks ' vscl_active = i_clocks<<12 + i_clocks*4
SHL vscl_active, #12 ' 16 CLK
ADD vscl_active, i_clocks
SHL vscl_active, #2

MOV vscl_edge, vscl_edge0 ' vscl_edge = 1<<12 + (3192 - i_clocks*i_width)/2
MOV numerator, i_width ' 120 CLK
MOV divisor, i_width
MAX numerator, i_clocks
MIN divisor, i_clocks
:mul SHR divisor, #1 WZ,WC ' maximum 6 iterations
IF_C SUB vscl_edge, numerator
SHL numerator, #1
IF_NZ JMP #:mul
SHR vscl_edge, #1

{
The calculations here accomplish two tasks: ensure the blank lines are
distributed correctly and add on the blank lines to the top of each field.
}
' numeven := rounddown(i_lines / 2)
MOV numeven, i_lines ' 60 CLK
SHR numeven, #1
' numodd := roundup(i_lines / 2)
MOV numodd, i_lines
SUB numodd, numeven
' numblank2 := numblank0 := int( (484-i_lines)/4 ) + 12
MOV numblank0, #484
SUB numblank0, i_lines
SHR numblank0, #2
ADD numblank0, #12
MOV numblank2, numblank0
' numblank1 := int( (482-i_lines)/4 )
MOV numblank1, #482
SUB numblank1, i_lines
SHR numblank1, #2
' numblank3 := numblank1 + i_lines // 2
MOV numblank3, i_lines
AND numblank3, #1
ADD numblank3, numblank1

JMPRET taskptr, taskptr ' 242+8+16+120+60 = 446 CLK

add i_pin,#3 ' ' PJM for Grey duty cycle
MOVS CTRb, i_pin
MOV pixel, #1
SHL pixel, i_pin
mov DIRA, pixel
sub i_pin,#3 ' PJM set pin back
MOVI CTRb, #%0_00110_000 ' PJM use PLL%1 was #%0_00110_111 ' turn on PWM

AND i_pin, #28 ' 16 CLK
MOV pixel, #%0010 'PJM use only color pin '7 summing network output
SHL pixel, i_pin
or DIRA, pixel ' set pin mask

MOV pixel, i_pin ' 36 CLK
SHR pixel, #3
MOVD VCFG, pixel ' set VGroup
AND i_pin, #4 WZ
MOV pixel, #7
SHL pixel, i_pin
MOVS VCFG, pixel ' set VPins
IF_Z MOVI VCFG, #%0_10_1_0_1_000 ' composite baseband on VPins[2..0]
IF_NZ MOVI VCFG, #%0_11_1_0_1_000 ' composite baseband on VPins[6..4]

RDLONG divisor, #0 ' CLKFREQ
MOV quotient, l9090909 ' 306 CLK
MOV numerator, l7159090
:shlmax SHL quotient, #1 WC ' maximize the two values
RCL numerator, #1 ' maximum 17 iterations (based on minimum 16MHz CLK)
SHL divisor, #1 WC
IF_NC JMP #:shlmax
RCR divisor, #1 ' undo overshoot

JMPRET taskptr, taskptr ' 16+36+306 = 358 CLK

MOV numpixels, #16 ' do division (first half)
:div1 CMPSUB numerator, divisor WC ' 264 CLK
RCL quotient, #1
SHR divisor, #1 WZ
DJNZ numpixels, #:div1

JMPRET taskptr, taskptr ' 264 CLK

:div2 CMPSUB numerator, divisor WC ' complete division
RCL quotient, #1 ' 264 CLK
SHR divisor, #1 WZ
IF_NZ JMP #:div2

{
See http://www.linusakesson.net/programming/propeller/pllsync.php
for synchronizing multiple video output cogs
Otherwise just be aware that due to PLL startup time and the initial
value of the internal frame counter being unknown that the timing
and stability of the first frame is not predictable.
}

MOV VSCL, ivscl ' set VSCL to instant reload
MOVI CTRA, #%0_00001_110 ' PLL internal mode, x8 CTRA = 57.2727272MHz (colorburst * 16)
SHL i_frqa, #1 WC,WZ,NR ' test MSB/zero
IF_NZ_AND_NC MOV quotient, i_frqa
IF_C AND quotient, i_frqa
MOV FRQA, quotient ' set CTRA to 7.159MHz, start the counters


lasttask JMPRET taskptr, taskptr ' 256+24 = 280 CLK
JMP #lasttask

standby MOV DIRA, #0
MOV VCFG, #0
MOV CTRA, #0
MOV CTRb, #0
JMP #cogstart

l7159090 LONG $6D3D32 ' 7159090 Hz
l9090909 LONG $E8BA2E8B ' 0.909090909 Hz
ivscl LONG 1<<12+1 ' initial delay

vscl_sync LONG 1<<12+269 ' 4.7usec
vscl_s2cb LONG 1<<12+304-269 ' sync to colorburst
vscl_burst LONG 16<<12+16*9 ' 16 PLLA per cycle, 9 cycles of colorburst
vscl_eqlo LONG 1<<12+135 ' sync/2
vscl_eqhi LONG 1<<12+1685 ' H/2 - sync/2
vscl_serr LONG 1<<12+1551 ' H/2 - sync
vscl_half LONG 1<<12+1820 ' H/2
vscl_blank LONG 1<<12+1372
dblank LONG $8A0200
sblank LONG %%1111_1111_1111_1111 ' 16 pixels color 1
sburst LONG %%2222_2222_2222_2222 ' 16 pixels color 2
vscl_edge0 LONG 1<<13+3192 ' initial value (pre shifted)
pwmlut LONG $4924a249 '$3d249249 ' PJM
black LONG $4a24a249 '$3e93B84A ' PJM
burst LONG $4f24a249 '$3f000000 ' PJM
'color_mask long $FFFFFFF0 ' PJM
cog long 0
vscl_edge RES 1 ' border, 1 PLLA per pixel
vscl_active RES 1 ' 4 pixels @ i_clocks PLLA per pixel

i_ptr RES 1 ' pointer to screen (bytes) 0 = inactive
i_width RES 1 ' number of longs (4 pixels per long)
i_height RES 1 ' number of rows
i_clocks RES 1 ' pixel width
i_lines RES 1 ' number of active lines
i_pin RES 1 ' pin number
i_frqa RES 1 ' FRQA value / mask
l_gain res 1
W_width RES 1 ' number of longs (4 pixels per long)

numeven RES 1 ' number of even active lines
numodd RES 1 ' number of odd active lines
numblank0 RES 1
numblank1 RES 1
numblank2 RES 1
numblank3 RES 1

quotient RES 1
numerator RES 1
divisor RES 1

taskptr RES 1 ' initialization task jmpret pointer
numlines RES 1 ' overall line counter

linenum RES 1 ' line scaling counter
numpixels RES 1 ' pixel counter
pixptr RES 1 ' pointer to pixels
nxtptr RES 1 ' pointer to next line
pixel RES 1 ' pixel value
luminance res 1
vs4 res 1
FIT 496

Baggers
10-23-2011, 12:08 PM
Hi Perry,

I'm surprised I'm the first to respond to this driver!

Fantastic colour range in this driver :D ( although at 5Mhz I get a lot of black flickery lines scrolling up the screen, so I swapped to a 6Mhz crystal on my demo board, and WOW, instant fix! )

Looks brilliant Perry, now to get some free time and figure out some cool stuff to do with it :D, what with this, and PhiPi's Colour Video Grabber!

This looks for interesting times ahead!

Baggers.

Dr_Acula
10-23-2011, 12:35 PM
Yes flickering black lines here too at 5mhz.

No R/C network here, and I see that it TVPin was given a value of 12 and my TV resistors are on pins 16,17,18 so I tried a variety of values from 15 to 18. With 15 I got a nice white to black fade, and with 17 I got some color bars.

Which is the one we are trying to display?

This is a very intriguing piece of code.

Perry
10-23-2011, 05:01 PM
Yes flickering black lines here too at 5mhz.

No R/C network here, and I see that it TVPin was given a value of 12 and my TV resistors are on pins 16,17,18 so I tried a variety of values from 15 to 18. With 15 I got a nice white to black fade, and with 17 I got some color bars.

Which is the one we are trying to display?

This is a very intriguing piece of code.

Thanks for the image, it should show much better than that!

to set pins for 16 use TV_PIN = 16 in the main code, the outputs used are then on pins 17 and 19

17 for chroma .001 cap to resistors
19 for luma

I just left out the duty cap in my test.
and use an alternate RCA connector to connect to TV

Perry
10-23-2011, 05:34 PM
Hi Perry,

I'm surprised I'm the first to respond to this driver!

Fantastic colour range in this driver :D ( although at 5Mhz I get a lot of black flickery lines scrolling up the screen, so I swapped to a 6Mhz crystal on my demo board, and WOW, instant fix! )

Looks brilliant Perry, now to get some free time and figure out some cool stuff to do with it :D, what with this, and PhiPi's Colour Video Grabber!

This looks for interesting times ahead!

Baggers.

Thanks for your comments.

I have using this with "stupid video capture", I was playing again last night with "false color" movies as I was testing the code that I hope to drop Phil's color decoder into.


There seems to be at least one error in the first post
around line 270 in the video driver


MOV VSCL, vscl_burst ' 9 cycles of colorburst
WAITVID dblank, sburst
xxxxxxx mov frqb,#0 ' PJM xxxxxx
mov frqb,#pwmlut ' PJM should be

I normally use 108MHz clock

but this code works at 80Mhz for me except for static horizontal funnys that aren't seen in videos.

I have yet to study the part in the code that seems to time the instruction cycle! taskone?

Baggers
10-24-2011, 10:43 AM
What is the best width you can get? so far I can get 96 width on 6Mhz crystal.

And again I must add, the colour range with this driver is amazing! :) well done!

Perry
10-27-2011, 07:49 PM
What is the best width you can get? so far I can get 96 width on 6Mhz crystal.

And again I must add, the colour range with this driver is amazing! :) well done!

Have not tried very high horz sizes, The vertical bars at 5Mhz are probably "i_clocks" set too small.

My internet has been down lately, am visitinig a free access point!

Perry

Bill Henning
10-28-2011, 12:49 AM
I am going to have to try this out soon... great work!


Thanks to Eric Ball's work with his 8bpp http://forums.parallax.com/showthread.php?134384-NTSC-8bpp-bitmap-display-driver&highlight=8bpp

I am offering for evaluation a 16bit NTSC video driver

This driver only uses 2 pins, on the demo board they would likely be 13 and 15.
pin 13 is the color signals capacitivly coupled to a Duty Dac from pin 15 to deliver luminence.

the demo shows 16 colors and 119 shades


Thanks to any spinner-heads who might try this


Perry

P.S.

having trouble with attachment so here are the two spin files.

first main test program: NTSC_16bit_demo.spin


{{

16bit NTSC video modifications Octber 2011 Perry James Mole pjm@hey-hello.ca

┌───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┐
│ NTSC 8bpp bitmap TV driver demo (C) 2011-08-19 Eric Ball │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│ TERMS OF USE: Parallax Object Exchange License │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┘
}}
CON
' Set up the processor clock in the standard way for 80MHz
' _XINFREQ = 13_500_000 + 0000
' _CLKMODE = XTAL1 + PLL8X

_clkmode = xtal1 + pll16x
_clkfreq = 80_000_000

' Demoboard TV output on pins 13 and 15

TV_PIN = 12

{
Notes from Grey TV driver:

This driver uses PWM to generate output so a low pass filter is recommended:

0.001F
pin 13 ─────┐
pin 15 ──┳─╋── Composit Video output
  value of this cap is small I'm unsure
 
The resistor values (124Ω series, 191Ω to ground) have an output impedance of
75 ohms and will drive a 75-ohm load at 1V P-P. The cap is there to filter the
DUTY doody. - Phil Pilgrim
http://forums.parallax.com/forums/default.aspx?f=25&m=340731&g=342216

However, in my experience, the RC network is not required. I have tested
successfully using any of the Demoboard TV DAC resistors (although the higher
resistance yields darker text) and with no resistors at all (although this
is not recommended).
}
OBJ
tv : "Pixelator_16bit_NTSC" '"NTSC 8bpp"

VAR
' NTSC8bpp input parameters - 7 contiguous longs, reloaded each frame
' Note: NTSC8bpp input parameters not validated, minimum clockspeed 16.364MHz
LONG i_ptr ' pointer to screen (bytes, long aligned), 0 = inactive
LONG i_width ' number of columns, must be divisible by 4 (i_width*i_clocks < 3192-3894MHz/CLKFREQ)
LONG i_height ' number of rows (max i_lines)
LONG i_clocks ' pixel width in 16x colorburst clocks (256 => i_clocks > 515MHz/CLKFREQ)
LONG i_lines ' number of active lines (max 482, does not need to be integer multiple of i_height)
LONG i_pin ' must be divisible by 4, i_pin = 1.1kΩ, i_pin+1 = 560Ω, i_pin+2 = 270Ω
LONG i_frqa ' 0 = calculate FRQA using CLKFREQ=long[0], MSB set = mask of calculated value, MSB clear = value to use
long i_gain ' output video gain Added for Pixelator
' For "square" pixels 3*i_height*i_clocks = 14*i_lines
' i_clocks < 10 and i_lines < 2*i_height will generate color artifacts instead of extra detail
' Due to overscan not all of the screen is visible on all TVs.
' "Action Safe" i_width*i_clocks < 2750, i_lines < 433; "Title Safe" i_width*i_clocks < 2444, i_lines < 385
word bitmap[80*120]

PUB start | color,i,j,ver

tv.start( @i_ptr )

i_width := 80
i_height := 120
i_lines := 480
i_clocks := 36
i_pin := TV_PIN
i_frqa := 0
i_ptr := @bitmap
i_gain := 24

repeat j from 0 to 119 step 1
repeat i from 0 to 79 step 1
bitmap[i+j*80] := ((((i/4 & $F )<<4) + 8) << 8) + j*2



and secondly the actual video driver: Pixelator_16bit_NTSC.spin



{{
┌───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┐
│ NTSC 8bpp bitmap TV driver (C) 2011-06-16 Eric Ball │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤

modifications for 16 bit Octber 2011 Perry James Mole
most changes should be commented with ' PJM

│ The NTSC8bpp TV driver displays a i_width x i_height image on a TV using standard 3pin TV DAC. │
│ The bitmap is padded horizontally and scaled vertically to i_lines. │
│ Pixels are stored in display order (left to right, top to bottom). │
│ │ │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│ TERMS OF USE: Parallax Object Exchange License │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┘
}}

VAR

' long cog

{{
' NTSC8bpp input parameters - 7 contiguous longs, reloaded each frame
' Note: NTSC8bpp input parameters not validated, minimum clockspeed 16.364MHz
LONG i_ptr ' pointer to screen (bytes, long aligned), 0 = inactive
LONG i_width ' number of columns, must be divisible by 4 (i_width*i_clocks < 3192-3894MHz/CLKFREQ)
LONG i_height ' number of rows (max i_lines) square pixel = (14*i_height)/(3*i_lines)
LONG i_clocks ' pixel width in 16x colorburst clocks (256 => i_clocks > 515MHz/CLKFREQ)
LONG i_lines ' number of active lines (max 482, does not need to be integer multiple of i_height)
LONG i_pin ' must be divisible by 4, i_pin = 1.1kΩ, i_pin+1 = 560Ω, i_pin+2 = 270Ω
LONG i_frqa ' 0 = calculate FRQA using CLKFREQ=long[0], MSB set = mask of calculated value, MSB clear = value to use
LONG l_Gain ' PJM added to control greyscale output dynamicaly ie contrast
' For "square" pixels 3*i_height*i_clocks = 14*i_lines
' i_clocks < 10 and i_lines < 2*i_height will generate color artifacts instead of extra detail
' Due to overscan not all of the screen is visible on all TVs.
' "Action Safe" i_width*i_clocks < 2750, i_lines < 433; "Title Safe" i_width*i_clocks < 2444, i_lines < 385
}}

PUB start( parmptr )

stop
RESULT := COGNEW( @cogstart, parmptr)
cog := RESULT + 1
RETURN

PUB stop
COGSTOP( cog~ - 1 )

DAT
{{
The purpose of this code is twofold. First, it can be used to display a
byte per pixel (TV color) bitmap. Second, this code is intended to be a
template which may be used by others to develop Propeller video drivers.
Note: this code creates an 29.97Hz 525 line interlaced display.

Rules for developing video drivers:
1. Start simple. Hardcode values and static display.
2. Add complexity and changes incrementally. Verify at each step.
3. If something doesn't work it's either because you have made an incorrect
assumption or made a coding error.
}}

ORG 0
cogstart RDLONG i_ptr, PAR WZ
IF_Z JMP #cogstart

MOV taskptr, #taskone
MOV numlines, #4
:dotasks JMPRET taskptr, taskptr
DJNZ numlines, #:dotasks

{{
An interlaced NTSC frame is made up of 525 lines:
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
12 lines blank
241 lines active (top field / first line)
half line (with sync pulse)
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
half line (without sync pulse)
12 lines blank
241 lines active (bottom field / last line)

A non-interlaced NTSC frame is made up of 262 lines:
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
12 lines blank
241 lines active
}}

frame MOV numlines, numblank0 WZ ' overscan blank lines
MOV taskptr, #taskone ' reload parameters
IF_NZ CALL #blank

MOV nxtptr, i_ptr
MOV linenum, i_lines
MOV numlines, numodd
CALL #active

MOV numlines, numblank1 WZ ' overscan blank lines
IF_NZ CALL #blank

MOV VSCL, vscl_sync ' half line
WAITVID dblank, #0
MOV FRQB, #0 ' PJM

MOV VSCL, vscl_serr
WAITVID dblank, sblank
MOV FRQB, pwmlut ' PJM

CALL #vsync

MOV VSCL, vscl_half ' extend blank for 1/2 line
WAITVID dblank, sblank
MOV FRQB, #0 ' PJM

{
One of the limitations of TV.spin is it only allows integer vertical
scaling. This calculation (and the matching calculation inside the active
loop) scales a i_height bitmap to i_lines. Note: the first line is
always displayed on the top field.
}
MOV nxtptr, i_ptr
MOV linenum, i_lines
SUB linenum, i_height WZ,WC ' set scaling for 2nd line
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, i_width ' go to next line (single line res)
IF_Z_OR_C ADD nxtptr, i_width ' go to next line (single line res)

MOV numlines, numblank2 WZ ' overscan blank lines
IF_NZ CALL #blank

MOV numlines, numeven
CALL #active

MOV numlines, numblank3 WZ ' overscan blank lines
IF_NZ CALL #blank

CALL #vsync ' moved here to reduce instructions
' from active to equalizing
JMP #frame

{{
As each pixel may be any color, the pixels are effectively the color values.
Therefor the code uses a "reverse WAITVID" which uses the pixel value for the
color (destination) parameter and #%%3210 to output the colors in LSB first
order. The downside of this method is it requires more RAM and the maximum
resolution is clockspeed limited (which isn't a problem for composite NTSC
which is bandwidth limited by the colorburst frequency).
}}
active MOV VSCL, vscl_sync ' 4.7usec @ -40 IRE (sync pulse)
WAITVID dblank, #0
MOV FRQB, #0 ' PJM

MOV pixptr, nxtptr ' precalculate pointer
SUB linenum, i_height WZ,WC ' vertical scaling calculations
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, w_width ' go to next line
SUB linenum, i_height WZ,WC ' do twice for interlace
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, w_width ' go to next line
MOV numpixels, i_width

MOV VSCL, vscl_s2cb ' 0.6usec @ 0 IRE (blank)
WAITVID dblank, sblank
MOV FRQB, pwmlut ' PJM

MOV VSCL, vscl_burst ' 9 cycles of colorburst
WAITVID dblank, sburst
MOV FRQB, burst 'pwmlut ' PJM

MOV VSCL, vscl_edge
WAITVID dblank, sblank
MOV FRQB, black ' PJM

mov vs4,vscl_active ' PJM
shr vs4,#2 ' PJM
MOV VSCL, vs4 'vscl_active ' PJM
' i_clocks PLLA per pixel, 4 pixels per frame

' WAITVID pixel, #%%0
:loop RDword pixel, pixptr ' PJM'RDLONG pixel, pixptr ' 22 CLK
' WAITVID pixel, #%%3210 ' +6 CLK
' ADD pixptr, #4 ' +4 CLK
mov luminance,pixel ' PJM
shl luminance,l_Gain ' PJM
add luminance,black ' PJM

WAITVID pixel, #%%1 ' PJM
MOV FRQb,luminance ' PJM
ADD pixptr, #2 ' PJM

DJNZ numpixels, #:loop ' +4 CLK = 36 CLK

MOV VSCL, vscl_edge
WAITVID dblank, sblank
MOV FRQB, pwmlut ' PJM

DJNZ numlines, #active ' next line
active_ret RET

{{
Video drivers are constrained by WAITVID to WAITVID timing. In the inner
active display loop, this determines the maximum resolution at a given clock
frequency. Other WAITVID to WAITVID intervals (e.g. front porch) determine
the minimum clock frequency.

For example the inner :loop in this code is 36 CLK and displays 4 pixels.
Therefore with an 80MHz clock the minimum VSCL.PixelClocks (i_width) is 7.
36 / 80MHz * 57.272727MHz / 4 = 6.44

Similar calculations should be used between each pair of WAITVIDs to determine
the minimum clock frequency (including initialization tasks).

Note: the first HUBOP after a WAITVID should be counted as 22 CLK as it cannot
be synchronized. WAITVID should be counted as 6 CLK as 5 CLK can cause
intermittent errors with very tight timing.
}}

vsync MOV numlines, #6
:equal1 MOV VSCL, vscl_eqlo ' equalizing pulse (sync/2)
WAITVID dblank, #0
mov frqb,#0 ' PJM

MOV VSCL, vscl_eqhi
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:equal1

MOV numlines, #6
:serration MOV VSCL, vscl_serr ' serration pulse (sync)
WAITVID dblank, #0 ' PJM
mov frqb,#0

MOV VSCL, vscl_sync
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:serration

MOV numlines, #6
:equal2 MOV VSCL, vscl_eqlo ' equalizing pulse (sync/2)
WAITVID dblank, #0
mov frqb,#0 ' PJM

MOV VSCL, vscl_eqhi
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:equal2

vsync_ret RET

blank MOV VSCL, vscl_sync ' 4.7usec @ -40 IRE (sync pulse)
WAITVID dblank, #0 ' PJM
mov frqb,#0

MOV VSCL, vscl_s2cb ' 0.6usec @ 0 IRE (blank)
WAITVID dblank, sblank
mov frqb,pwmlut

MOV VSCL, vscl_burst ' 9 cycles of colorburst
WAITVID dblank, sburst
mov frqb,#0 ' PJM

MOV VSCL, vscl_half
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
JMPRET taskptr, taskptr

MOV VSCL, vscl_blank
WAITVID dblank, sblank
mov frqb,#0 ' PJM

DJNZ numlines, #blank ' next line
blank_ret RET

d1 LONG |<9 ' destination = 1

taskone MOV pixptr, PAR ' load input parameters
MOVD :i_loop, #i_ptr ' 242 CLK
MOV numpixels, #8 ' ' PJM #7 add l_Gain to inputs
:i_loop RDLONG i_ptr, pixptr
ADD pixptr, #4
ADD :i_loop, d1
DJNZ numpixels, #:i_loop

MOV W_width,i_width
shL W_width,#1

CMP i_ptr, #0 WZ ' 0 = blank
IF_Z JMP #standby ' 8 CLK

MOV vscl_active, i_clocks ' vscl_active = i_clocks<<12 + i_clocks*4
SHL vscl_active, #12 ' 16 CLK
ADD vscl_active, i_clocks
SHL vscl_active, #2

MOV vscl_edge, vscl_edge0 ' vscl_edge = 1<<12 + (3192 - i_clocks*i_width)/2
MOV numerator, i_width ' 120 CLK
MOV divisor, i_width
MAX numerator, i_clocks
MIN divisor, i_clocks
:mul SHR divisor, #1 WZ,WC ' maximum 6 iterations
IF_C SUB vscl_edge, numerator
SHL numerator, #1
IF_NZ JMP #:mul
SHR vscl_edge, #1

{
The calculations here accomplish two tasks: ensure the blank lines are
distributed correctly and add on the blank lines to the top of each field.
}
' numeven := rounddown(i_lines / 2)
MOV numeven, i_lines ' 60 CLK
SHR numeven, #1
' numodd := roundup(i_lines / 2)
MOV numodd, i_lines
SUB numodd, numeven
' numblank2 := numblank0 := int( (484-i_lines)/4 ) + 12
MOV numblank0, #484
SUB numblank0, i_lines
SHR numblank0, #2
ADD numblank0, #12
MOV numblank2, numblank0
' numblank1 := int( (482-i_lines)/4 )
MOV numblank1, #482
SUB numblank1, i_lines
SHR numblank1, #2
' numblank3 := numblank1 + i_lines // 2
MOV numblank3, i_lines
AND numblank3, #1
ADD numblank3, numblank1

JMPRET taskptr, taskptr ' 242+8+16+120+60 = 446 CLK

add i_pin,#3 ' ' PJM for Grey duty cycle
MOVS CTRb, i_pin
MOV pixel, #1
SHL pixel, i_pin
mov DIRA, pixel
sub i_pin,#3 ' PJM set pin back
MOVI CTRb, #%0_00110_000 ' PJM use PLL%1 was #%0_00110_111 ' turn on PWM

AND i_pin, #28 ' 16 CLK
MOV pixel, #%0010 'PJM use only color pin '7 summing network output
SHL pixel, i_pin
or DIRA, pixel ' set pin mask

MOV pixel, i_pin ' 36 CLK
SHR pixel, #3
MOVD VCFG, pixel ' set VGroup
AND i_pin, #4 WZ
MOV pixel, #7
SHL pixel, i_pin
MOVS VCFG, pixel ' set VPins
IF_Z MOVI VCFG, #%0_10_1_0_1_000 ' composite baseband on VPins[2..0]
IF_NZ MOVI VCFG, #%0_11_1_0_1_000 ' composite baseband on VPins[6..4]

RDLONG divisor, #0 ' CLKFREQ
MOV quotient, l9090909 ' 306 CLK
MOV numerator, l7159090
:shlmax SHL quotient, #1 WC ' maximize the two values
RCL numerator, #1 ' maximum 17 iterations (based on minimum 16MHz CLK)
SHL divisor, #1 WC
IF_NC JMP #:shlmax
RCR divisor, #1 ' undo overshoot

JMPRET taskptr, taskptr ' 16+36+306 = 358 CLK

MOV numpixels, #16 ' do division (first half)
:div1 CMPSUB numerator, divisor WC ' 264 CLK
RCL quotient, #1
SHR divisor, #1 WZ
DJNZ numpixels, #:div1

JMPRET taskptr, taskptr ' 264 CLK

:div2 CMPSUB numerator, divisor WC ' complete division
RCL quotient, #1 ' 264 CLK
SHR divisor, #1 WZ
IF_NZ JMP #:div2

{
See http://www.linusakesson.net/programming/propeller/pllsync.php
for synchronizing multiple video output cogs
Otherwise just be aware that due to PLL startup time and the initial
value of the internal frame counter being unknown that the timing
and stability of the first frame is not predictable.
}

MOV VSCL, ivscl ' set VSCL to instant reload
MOVI CTRA, #%0_00001_110 ' PLL internal mode, x8 CTRA = 57.2727272MHz (colorburst * 16)
SHL i_frqa, #1 WC,WZ,NR ' test MSB/zero
IF_NZ_AND_NC MOV quotient, i_frqa
IF_C AND quotient, i_frqa
MOV FRQA, quotient ' set CTRA to 7.159MHz, start the counters


lasttask JMPRET taskptr, taskptr ' 256+24 = 280 CLK
JMP #lasttask

standby MOV DIRA, #0
MOV VCFG, #0
MOV CTRA, #0
MOV CTRb, #0
JMP #cogstart

l7159090 LONG $6D3D32 ' 7159090 Hz
l9090909 LONG $E8BA2E8B ' 0.909090909 Hz
ivscl LONG 1<<12+1 ' initial delay

vscl_sync LONG 1<<12+269 ' 4.7usec
vscl_s2cb LONG 1<<12+304-269 ' sync to colorburst
vscl_burst LONG 16<<12+16*9 ' 16 PLLA per cycle, 9 cycles of colorburst
vscl_eqlo LONG 1<<12+135 ' sync/2
vscl_eqhi LONG 1<<12+1685 ' H/2 - sync/2
vscl_serr LONG 1<<12+1551 ' H/2 - sync
vscl_half LONG 1<<12+1820 ' H/2
vscl_blank LONG 1<<12+1372
dblank LONG $8A0200
sblank LONG %%1111_1111_1111_1111 ' 16 pixels color 1
sburst LONG %%2222_2222_2222_2222 ' 16 pixels color 2
vscl_edge0 LONG 1<<13+3192 ' initial value (pre shifted)
pwmlut LONG $4924a249 '$3d249249 ' PJM
black LONG $4a24a249 '$3e93B84A ' PJM
burst LONG $4f24a249 '$3f000000 ' PJM
'color_mask long $FFFFFFF0 ' PJM
cog long 0
vscl_edge RES 1 ' border, 1 PLLA per pixel
vscl_active RES 1 ' 4 pixels @ i_clocks PLLA per pixel

i_ptr RES 1 ' pointer to screen (bytes) 0 = inactive
i_width RES 1 ' number of longs (4 pixels per long)
i_height RES 1 ' number of rows
i_clocks RES 1 ' pixel width
i_lines RES 1 ' number of active lines
i_pin RES 1 ' pin number
i_frqa RES 1 ' FRQA value / mask
l_gain res 1
W_width RES 1 ' number of longs (4 pixels per long)

numeven RES 1 ' number of even active lines
numodd RES 1 ' number of odd active lines
numblank0 RES 1
numblank1 RES 1
numblank2 RES 1
numblank3 RES 1

quotient RES 1
numerator RES 1
divisor RES 1

taskptr RES 1 ' initialization task jmpret pointer
numlines RES 1 ' overall line counter

linenum RES 1 ' line scaling counter
numpixels RES 1 ' pixel counter
pixptr RES 1 ' pointer to pixels
nxtptr RES 1 ' pointer to next line
pixel RES 1 ' pixel value
luminance res 1
vs4 res 1
FIT 496

ericball
10-28-2011, 01:27 AM
What is the best width you can get? so far I can get 96 width on 6Mhz crystal.

Maximum resolution is a function of the inner loop:

:loop RDword pixel, pixptr ' PJM'RDLONG pixel, pixptr ' 22 CLK
mov luminance,pixel ' PJM ' +4 CLK
shl luminance,l_Gain ' PJM ' +4 CLK
add luminance,black ' PJM ' +4 CLK

WAITVID pixel, #%%1 ' PJM +5 CLK
MOV FRQb,luminance ' PJM +4 CLK
ADD pixptr, #2 ' PJM +4 CLK

DJNZ numpixels, #:loop ' +4 CLK = 51 CLK

So that's 51 CLK per pixel at 96MHz, or a minimum of 31 PLLA (i_clocks) per pixel, which works out to an i_width of around 89 pixels.

potatohead
10-30-2011, 08:37 AM
I've been acting as product manager on a Internet startup. Big time sink. :( Caught a nice break, and just can't resist this.

Having a great play with this right now. VERY impressive range of color. This is just a brilliant idea Perry. Very well done!

Would anybody be interested in a higher resolution luma only overlay?

I'm thinking of firing off another COG running another, simple 4 color tile driver to run right on the same pins. Been a while since I tried that. I think I can do it this time, particularly if it's luma only.

Another alternative would be to run the luma channel on this one at the current 80 or so pixels, but open up the waitvid so that a few pixels fit in there... Both methods could be combined. Too bad I don't have some kind of simple latch, where I could do "AND" with the pins... More could be done, but RAM would be at issue. Calling Dr_A. :)

This one works a pixel at a time, using waitvid for the color, then using the PWM for the luma. Why not just run the waitvid for a few pixels at a time? Since there are three pins, it's possible to "OR" the two outputs right? Different colors and a luma change for the brighter could be done. Run 4 pixels at a clip, keeping the PWM the same. I'm deffo gonna try that.

There also appeared to be some bright luma values in the border region. When I configured Eric's driver to be 80x96 with borders, they appeared bright. Modifying a value or two in the TV driver fixed it, I think. Will post that shortly. Anyone else experience this?

Baggers
10-30-2011, 04:43 PM
Nice Idea Potatohead, would be ace with a better resolution :) deffo worth the extra cog to do it!

potatohead
10-30-2011, 06:47 PM
Hmmm... I can't seem to attach anything either. Wanted to upload my wip archive from last night.

(attachment problems resolved... wierd)

See post below for WIP code posting and sample image.

potatohead
10-31-2011, 03:19 AM
In this post, I've created a quick bit of spin code that plots a dot, and does a few ramps, both intensity and color. Took a while to get that sorted out. Also took the opportunity to run this on my captures, and various TV's. Range of values for a good color space was the goal.

As originally configured, the driver was running full screen, and IMHO the darker color ranges were well below "black". These value displayed are black and brighter. The Sony didn't like the below black signal at all, though the other devices seemed ok with it. Just a FYI. Well, mostly. The low color values dip a shade below black, but won't be at issue on any device I've ever seen.

The code here is configured for a 80x96 display, centered in the NTSC "safe area", leaving a black border. It can run wider, but not too much narrower. I'm using a 100Mhz Propeller.

My older, portable TV renders the graphics fairly well. Roll off the sharpness control a little, and it's basically useful. On my HD set, and the Sony WEGA, the PWM noise is very visible. IMHO, using the cap is recommended. I've gotta set one up for sure.

Edit: The little lower resolution LCD displays should render this nicely. Just thought I would add that. I have one smaller one, and it looks great.

The capture devices were not happy with this signal at all. One of the better captures attached. (and it's not very good, but does give you a sense of what the image looks like)

potatohead
10-31-2011, 08:59 AM
Progress!

Got the color spaces sorted nicely. This archive contains a nice color map showing the range of values, basic dot plotter, clipped to the screen, and range constrained color definition.

Hues range from 0 to 15, all the basic hues a Propeller does with the built in video generator.
Each HUE has 128 intensity values.

2048 displayable colors are possible! Nice one Perry! :)

If the HUE is set to a value greater than 15, it's interpreted as INTENSITY only. Monochrome.
Each INTENSITY has 250 values.

250 displayable grey scales are also possible!

Resolution on this one is 80x96. It's not too big of a deal to run it at greater vertical resolutions, if desired. One page of graphics takes a little under 8k, leaving room for a higher resolution overlay. I'm thinking this may well be very useful for communicating levels, graphs and such, if some detail text can be included. Thinking on that...

I'm currently working on a line command, and am going to run this on a better circuit, also S-video. The PWM raw from the demo board is kind of noisy, which actually limits the color resolution to about 80 pixels. Some color combinations are better than that, but dot crawl and fringing gets significant on most combinations. Load the archive code to see a sample display demonstrating color limits on your display. Tweak your sharpness, contrast and such, changing background colors to see what I mean.

I'll post up S-video stuff shortly, once I get the PPDB wired properly.

Right now, it's possible to plot out basic data, suppose somebody could do a nice ball 'n bat game, or display pictures. Use the setcolor method included to figure out graphics palette. Somebody needs to combine this with Phil's video capture. It's ready to go!

Baggers
10-31-2011, 10:10 AM
You know what would be a great use for this?
.
.
.
A graphic adventure, top two thirds of the screen, using a bitmap, lower third using a charmap, maybe even half height rom text to save space?

Just a thought!

Great work though Perry and Potatohead :D kudos!

potatohead
10-31-2011, 10:36 AM
Yeah, but there is more!

I'm still puzzling over hues. Looks like there are 32 distinct hues. If so, that's ~4k colors... Found the extra set while tinkering around with an attempt to do S-video on this one.

Got a more reasonable capture this time. Drawing darker color blocks interleaved with the bright palette samples cuts way down on the overall noise.

Hues range from 0 to 31, all other values from post above apply. Values above 31 are seen as INTENSITY only, with the 250 value limit.

I need some help understanding exactly how this one works. When I modified the TV driver to do S-video, I found colors and intensities combined. That was unexpected. I thought intensity was running on one pin, and color on the other... is that true?

Got a fairly good handle on what it DOES however. :)

potatohead
10-31-2011, 11:12 AM
@baggers:

Yeah, like the old Apple ][ games sometimes did. Wouldn't be a big deal to do text. Could grab lots of pictures off the SD card.

Until I get a better understanding of this one, I'm kind of limited though. It's totally possible to run more pixels in the waitvid, limiting intensity to the current 80 pixel resolution. Or... using another COG, with overlay video, or maybe just optimizing the PWM, better resolution could go that way too.

Noise is the biggest hassle. I need some small value caps. Some values of intensity are decidedly "sparkly", and the color dot-crawl is being exacerbated, due to that, I believe. Color resolution right now is low. 80 pixels is actually pushing it some.

@Perry

What is needed is chroma only on the high pin, PWM running through another pin, on the standard DAC. The Demo Board has both the high pin and the DAC connected together, which actually displays most anything you throw at it, or it can do the broadcast carrier, as intended.

I'm having trouble separating these two. Would love it if I could just get the LUMA signal out the composite connector, when chroma is routed to the high pin. I;m not quite seeing what you did here. Then again, it's late too. Gonna get sleep. Had a lot of fun though. Thanks for posting this!

Dr_Acula
10-31-2011, 12:16 PM
Is it possible to mix a color with gray? Or in more technical terms, for a color with a fixed hue (say, blue) and a fixed lightness (say 50%), produce a color bar that goes from gray through to the full color?

The reason I ask is that with the limited propeller palette, if you run that through a dithering algorithm, many colors need to be artificially created by mixing a color, then a gray pixel, then a color pixel, and these pixels do not blend particularly well and at higher pixel density start to cause artifacts.

If you could produce a true mix of gray and a color then the lower number of pixels per line may not matter so much.

I'm not sure how you would do this in one byte though, as you need to store HSL values and might need more than one byte per pixel.

Cluso99
10-31-2011, 12:59 PM
wow! I totally missed this thread because I have been interested in text only with color and low pin count.

Baggers
10-31-2011, 02:56 PM
and to think, all these latest progresses would have probably been missed if PropII had already been out! another reason the Prop ( 1 ) is such a fantastic device!

potatohead
10-31-2011, 08:34 PM
Dr_A, with this driver, that's not possible. What one could do though, is run it at 80x192, and simply interleave grey and the color vertically. Color pixel on top, with approximate grey level, and a grey pixel on bottom, for example, maybe checkerboard them. That works reasonably well to simulate what you are wanting to do. Would be acceptable for pictures, movies and simple data graphs and such.

You should take a look with the code I have above. Very easy to do a coupla ramps to evaluate what that would look like. Not often we get to talk about a 3 - 4K color, any color anywhere type driver! :)

Something like:



repeat i from 0 to 31 step 2
repeat j from 0 to 60 step 4
setcolor(j/4, i*4)
cplot(i+10, j+20)
setcolor(32, i*4)
cplot(i+10, j+21)


Should draw a few pixels and or ramps for you to look at. Tweak the steps and or add cplot commands. (cplot = color plot, acts on last setcolor statement)

Increase the graphics buffer to 16K


word bitmap[80*96] 'The graphics screen, currently about 8k (should be 192)


Then adjust the screen driver parameters:



i_height := 96 (should be 192)
i_lines := 96*4 (I think this can just change to 96*2)

And the dot plotter:



PUB cplot (x, y)
'80 x 96 graphics screen
x //= 79
y //= 95 (SB 192)
bitmap[y*80+x] := current_color


IMHO, we need another video circuit to do what you are asking. Low resolution PWM could be done like this, using three COGs, maybe 4 pins total, outputting to YCbCr component video, or maybe RGB. The YCbCr format is Y = Intensity, Cb and Cr are blue and red color difference signals. Say you've got 50 percent grey on the Y, you could output a modest level difference on the Cb and or Cr channels to get saturation control, I think.

(goes off to look at that more closely)

I really like that format, and I have a display or two that will take it at up to HD resolutions. But... that input is not found on lower end displays, and isn't found at all, or much at all in various parts of the world. Why, I have no idea. I've done a quick test or two, then dropped it because it's a three cog solution to really be effective, and it's gonna take the RAM, and displays might not be all that easy, though I do believe there are converters out there. Maybe that makes sense? (goes off to look at those too)

Could also run the Y channel at a higher resolution than the difference channels too, allowing for intensity detail, but lower resolution color. Been thinking about doing that, just because it's fun. Need to order up some resistors and a cap or two, it seems. PWM noise is too great without the caps for display on anything with any real capability.

With RGB, we could do a similar thing, but without the ability to have intensity detail. Just fire off three of the PWM drivers, and figure out sync.

Is three COGs worth it?

Our lack of saturation control boils down to the color phase signal output by the prop video generator is just one fixed level. In composite land, we need to vary that, or move to a output form that gives us more direct control over the colors produced. Or... use clever dither techniques, like what I mentioned, or flicker it, alternating stuff across frames. (never did like that one, unless it's done on PAL, which actually renders the in-between color very nicely --one of the things I have always wanted to demonstrate on Prop, but we never got a good enough PAL signal for it to be worth doing)

One other option in NTSC land, given RAM isn't too much of a consideration, is to simply crank up the resolution, using words or longs per pixel. Eric has written a stable artifacting driver, which means we can do mixing, like I showed here: http://propeller.wikispaces.com/Colors That one ended up being unstable, because it's hard to know the state of the phase counter with respect to the pixel clock. There are hue shifts on propeller start that occur, meaning one can't always count on a given value boiling down to a given color on screen, without a lot of fiddling. That works because the composite signal breaks higher frequency content into color. Crank up the pixel clock, so that the pixels fall inside the colorburst clock, and make sure they are consistently timed, and the pixels literally become color info. This was the method used in the old Apple ][ for color graphics. It quite literally just had small pixels. Where a person places one determines the color. This idea is just expanding on that with lots of pixels to get lots of colors.

Did that one a long time ago, and didn't come back to it for that reason. Eric's recent NTSC efforts, inspired by the Apple, have the timings resolved, meaning that technique is on the table now. With more resolution, that method can produce more low saturation colors. Some of what that looks like can be seen on that driver screen capture.

I used 320 pixels in that one, with one half of a 160 horizontal resolution pixel being a standard propeller color, and the other half being one of the intensities. With artifacting, the horizontal resolution is fixed to the period of the color burst, which is 160 pixels in the "safe area" inside the borders, just FYI. Can be lower multiples too, or the active part of the screen can be changed. Possibilities are 40, 64, 80, 128, 160, etc... horizontally. Vertically, it's all about RAM, or tiles and RAM, whichever. Could be that we only need 640, if the standard waitvid hues are incorporated. Haven't tried it.

basically outputting color info, and luma info at a high rate, leaving the TV to average things out. A 640 pixel display will yield about 230 colors, a 1280 pixel one will output a lot! I never did the 1280 display trick, because it eats all the RAM... In C, maybe you don't care?

1280 pixels @ 4 colors = 80 waitvids / line, one word per pixel, for a total of 160 pixels horizontal. 320 bytes per scan line... That means a 30k buffer would yield a 160x90 or so pixel display. Would look fab though! Maybe I can knock up a sample of that, running at a modest resolution, or just using part of the screen. Would be NTSC, composite only though. Edit: There are a LOT of possibilities here. Some tinkering would be needed. Might not need 1280. Lots of ways to "mix" and "artifact" in the display. (on the to-do list now, because I love that kind of thing)

Prop video always comes down to RAM or pins, or both. Perry boosted what we can do though. I like this driver a lot. Opens the door to a lot of possibilities, and with the reference circuit too!

@Baggers: I love Prop I for precisely that reason. It's a great chip! So many possibilities too. Seems the more we think about it, the more it will do. Love that. What other device can support the kinds of conversations we have here regularly? Video on Prop is fun! (too damn much fun, I'm tired today!)

LOL!! Just thought about Perry and him getting Internet sorted. Bet he's gonna enjoy this thread!

Dr_Acula
11-01-2011, 12:07 AM
This is a fascinating discussion. I'm still digesting that last posting and it will take a while longer yet.

I need to find some time to push the ram chip to its limits - at the moment it is half the speed of the video scan line but I think it can go faster. If you can pull data off an external chip it opens up more possibilities.

Researching NTSC is not as easy as I thought. I'm not even sure if some websites are correct - eg this wikipedia site http://en.wikipedia.org/wiki/Saturation_(color_theory) talks about colorfulness, chroma and saturation. They describe 'colorfulness' as the scale from gray to a full color. Then they talk about saturation and link to the HSL page where 'saturation' describes the scale from gray to full color. So what is 'saturation'?

Whatever color format you pick, there are always 3 values, not two. eg http://en.wikipedia.org/wiki/File:Hsl-hsv-colorpickers.svg looking at the picture on the top right with a fixed hue, there are still two dimensions there to describe all the colors.

The problem with some palettes is they are just 'too pretty', ie too colorful, ie, they only contain all the fully saturated colors and none of the ones with subtle mixes of gray.

Thinking about it in terms of the full color palette, you can have very fine graduations between various hues but if the graduation between 'gray' and 'color' is only binary, ie one graduation, it leaves out a lot of colors.

I'm not sure how to put this information back in? Is it the amplitude of the signal? I'm still not sure - I think amplitude is 'lightness', not 'grayness'.

Maybe there is another pwm you could run to alter things further? I wonder what you could do if you considered using all 8 cogs. And maybe even had two ram chips working independently and you could pull data off those ram chips in parallel using separate cogs.

I'd be interested to see what you could do with just a small tile in the middle of the screen.

Phil Pilgrim (PhiPi)
11-01-2011, 01:44 AM
Dr_A,

Maybe this illustration will help:

http://forums.parallax.com/attachment.php?attachmentid=86503&d=1320108138

The brightness is the signal level above "black" after lowpass filtering out the 3.57 MHz color component. The saturation is the amplitude of the 3.57 MHz chroma component, and the hue is the phase of the chroma component relative to the extrapolated phase of the color burst that occurs at the beginning of each line.

A signal that's totally gray will have no chroma component. IOW the 3.57 MHz subcarrier will be flat. A signal that's totally saturated will have a very high chroma subcarrier riding on the brightness signal.

Here's an image the illustrates the difference between saturation and brightness:

http://forums.parallax.com/attachment.php?attachmentid=86504&d=1320108138

You can see that saturation is a measure of the "purity" of the color, whereas brightness is just a measure of how much light is emitted (or reflected).

-Phil

Dr_Acula
11-01-2011, 01:54 AM
Brilliant - thanks PhiPi.

So you want to adjust the amplitude to adjust the saturation. If there are clever tricks being done with pwm to adjust things, can we do more clever tricks? Run another cog to generate a pwm and use that to control a transconductance amplifier like the CA3080? Hmm - need to track the average level for the lightness so this sets the virtual earth of the op amp, then adjust the gain. That would be a low pass filter with a time constant about the same as for displaying a pixel. Though I see the open loop bandwidth is only 2Mhz - possibly not fast enough? There are other faster chips I see. (I'm searching for wide bandwidth transconductance).

Ok, let me see. The Hue waveform is absolutely critical for timing and looking at the phase difference, would be too fast to store in an external ram (thinking sample-and-hold).

But the Brightness and Saturation could be. And that information need not even go through a prop.

Store Hue values in hub (could get up to 256x224). Store Saturation and Brightness in external ram (one chip and clock out alternate bytes, or two separate chips or one byte per pixel with 16 levels each of Sat/Brightness). The brightness goes to a level shifting op amp circuit and the saturation controls gain. Probably easier to do it in the other order because the earth is constant - so run gain first, then level shift.

These are old-school circuits that I understand. Simple R/2R D to A, maybe start with 16 levels of saturation and 16 levels of brightness. Can store that in one byte per pixel. 16 levels of gain for the transconductance op amp and then 16 levels of shift by using an op-amp adder.

Just need fast enough op amps. CA3280 transconductance amp has a 9Mhz bandwidth.

potatohead
11-01-2011, 02:55 AM
Great chart Phil.

This can also be done with just pixels. Draw a grey pixel, for example, then put a really bright, smaller pixel right in the middle of it. The result will be a moderate saturation-ish pixel as the timing of the small pixel and it's high frequency nature triggers the color circuits, it's intensity impacts the purity of the color, as does it's size, while the grey pixel tends to "fill-in" where there would normally be a more fully developed color signal.

Frankly, we should fire off a thread about a NEW video circuit. The reference one has been, and will continue to be hacked all over the place. Very cool, but...

It might be a great discussion to have. Say we target two COGs. It's not like people would have to write for two, but design the circuit so that two could really be put to use. Get pins sorted, and discuss various modulation techniques we have available to us. With monochrome NTSC / PAL, we can do overlay, where two COGs write to the same pins, with VGA, and things like component, we could do that too, and do so in color, and make choices, like running color at a lower bitrate than monochrome. Or... if we had a gate, where a logical AND operation could happen electrically, the output of one COG could gate another one. This has it's uses in TV land, many of which apply in VGA land too.

One use would be to gate off a low resolution color signal, for example, allowing a dark intensity pixel to exist amidst a bright color. We can do the opposite now, as the pins are OR'ed together. Maybe that's not AND, but some other op. Something to be discussed.

Dr_Acula
11-02-2011, 03:47 AM
I'm bursting with ideas at the moment thanks to post #23 and this whole thread. I think adjusting Sat and Brightness could be done using data clocked out from external ram chip(s).

So I thought I might come back to Hue. On the propeller, Hue comes in 16 discrete steps. Let's say we took a signal with full saturation and zero brightness. Add saturation and brightness later, but internal to the prop you have 16 Hues. Can you get more Hues by tweaking the phase?

The frequency is constant (3.57Mhz right?) and I was reading about phase shifts in R/C filters. There is a nice description about 1/3 of the way down this page http://www.geofex.com/article_folders/phasers/phase.html.

So if you have a constant frequency and and RC network and you adjust the resistor, then does that mean you can adjust the phase. I still need to test out whether the prop can read bytes off memory fast enough, but let's say it can, you read a byte and use 3 bits to work out the Hue. The other bits are set for full saturation. At the same time another byte is read off another ram chip and that contains data fed into a programmable resistor (?? a 4066 fast enough?) and then that alters the R in a RC filter and thus tweaks the phase.

Crazy ideas I know. I need to do more experiments on a breadboard. brb

Dr_Acula
11-02-2011, 12:33 PM
Many experiments later...

It comes down to this loop


rdloop mov t2, ina ' read byte from SRAM \ ignores upper bits
wrbyte t2, hubaddr ' copy byte to hub /
add hubaddr, #1 ' inc hub pointer
add outa, #(1 << 8) ' inc sram address
djnz len, #rdloop ' loop for xxx bytes


This is pulling the data off an sram as fast as possible and it is about 5/8 of the speed required. So even with one cog pulling data off a sram, the time it takes to do a wrbyte to move to hub is too long.

Maybe maybe one could have two srams running in parallel, each with their own pins and cogs but it gets complex. You just can't pull the data off an sram and pass it through a propeller fast enough.

I'm probably hijacking this thread too much, but I thought I might just throw the AD725 RGB to NTSC chip in the mix. http://www.analog.com/en/digital-to-analog-converters/video-encoders/ad725/products/product.html

Get the prop to generate the hsync and vsync signals. Use a Word rather than a Byte and you can have 5 bits for red, 5 for green and 5 for blue (two sram chips, or even sram with 16 data lines)

If you took that loop above and removed the wrbyte and one of the add instructions, could the propeller produce the timing clock fast enough?

In an ideal world, I think a pixel is 80 nanoseconds. The SRAM chip is 55ns so that works.

Even if that loop is fast enough, it may not divide in the right ratio. eg one loop might run too fast and you finish the line 3/4 of the way across the screen, and if you add a NOP it finishes off the screen and is too wide. The level of control is too coarse.

Maybe it is better to use the propeller counters and tell it to give you 256 pulses at a period of x? (Can the prop do that). Or use HC161 counters and clock out using a separate osc, and use the prop to do clever things like detect when the 161 has counted up to 256 and then stop the clock.

More silly thoughts. But if it gave the propeller a display of 32768 colors...

kuroneko
11-02-2011, 02:34 PM
This is pulling the data off an sram as fast as possible and it is about 5/8 of the speed required. So even with one cog pulling data off a sram, the time it takes to do a wrbyte to move to hub is too long.
You can write ina to hub at clkfreq/4 MB/s. Because you write a byte at a time that's still clkfreq/16 MB/s. Only drawback is that you'd have to put the address handling (add outa, #256) into a 2nd cog. Would that be OK?

Dr_Acula
11-03-2011, 08:08 AM
Hi kuroneko, can you explain that a bit more? What would you change on the code in post #27? And how much faster would it run?

For this demo, one cog is running spin and one is running the TV driver and all the rest are free. And the only pins being used are the serial download, eeprom and 3 pins for TV. So there is a lot of flexibility.

kuroneko
11-03-2011, 08:33 AM
What would you change on the code in post #27? And how much faster would it run?
Currently you occupy 2 hub windows for transferring a byte which gives you clkfreq/32 MB/s (2.5MB/s @80MHz). This can be done twice as fast (one byte per hub window or 5MB/s @80MHz). As I mentioned, address handling has to be done in a second cog, addresses will run from high to low. From your loop I'd remove the 2 add insns (so that it fits into 16 cycles). And I'd have to add some support ...

Dr_Acula
11-03-2011, 09:30 AM
Very cunning!

The TV video driver cog is already updating a hub variable to say which line it is displaying, and the ram driver cog is using the change in this variable to sychronise the two, so having a third cog synch to that variable should be possible.

Phil Pilgrim (PhiPi)
11-03-2011, 07:26 PM
You can do it in one cog. The trick is to start an NCO counter that outputs on P8 to toggle the LSB of the RAM's address. That way, the program has to increment the rest of address only once for every two fetches. The other trick is to combine the bytes read into a long, so four bytes can be written to the hub at once:



' Read ina: * P8: 0 1

:loop movi pix,ina '* |
shr pix,#8 ' |
movi pix,ina '* |
shr pix,#8 ' |
add outa,:incr ' |
movi pix,ina '* |
shl pix,#1 ' |
mov pixl,ina '* |
shl pixl,#24 ' |
add outa,:incr ' |
shr pix,#8 ' |
or pix,pixl ' |
wrlong pix,hubaddr ' |
' |
add hubaddr,#4 ' |
djnz reps,:loop ' |

:incr long $200

Because of the additional instruction between the second and third read, the NCO timing is critical. You want the output on P8 to be settled when ina is fetched during the source read cycle.

-Phil

Dr_Acula
11-03-2011, 11:08 PM
Ah PhipPi, now that is clever.

Well that has me thinking about two more things (which may have applications for cache drivers for C as well). In addition to your counter trick -

1) read values into cog variables rather than to hub, and then do a burst write to hub using longs rather than bytes. ? 16 or 32 byte blocks
2) unroll the loop to, say, 16 or 32 bytes, and that saves the djnz.

kuroneko
11-04-2011, 12:45 AM
Because of the additional instruction between the second and third read, the NCO timing is critical. You want the output on P8 to be settled when ina is fetched during the source read cycle.
In that case why not throw another counter at it? :) If anything we have enough of them.

DAT
' Read ina: * P8: 0 1 P9: 0 1

:loop movi pix,ina '* | | %00
shr pix,#8 ' | |
movi pix,ina '* | | %01
shr pix,#8 ' | |
movi pix,ina '* | | %10
shl pix,#1 ' | |
mov pixl,ina '* | | %11
shl pixl,#24 ' | |
nop ' | |
add outa,:incr ' | |
shr pix,#8 ' | |
or pix,pixl ' | |
wrlong pix,hubaddr ' | |
' | |
add hubaddr,#4 ' | |
djnz reps,:loop ' | |

:incr long $400

While not strictly necessary let's make some more room for whatever may be required. The version below does what I know as "pulling a lonesock". I'm sure other clever people came up with the same thing, point is I know it under that name.

DAT
' Read ina: * P8: 0 1 P9: 0 1

:loop movs pix, ina '* | | %00
ror pix, #8 wc ' | |
movs pix, ina '* | | %01
ror pix, #8 ' | |
movs pix, ina '* | | %10
rol pix, #15 ' | |
movi pix, ina '* | | %11
rcl pix, #1 ' | |
nop ' | |
nop ' | |
nop ' | |
add outa, :incr ' | |
wrlong pix,hubaddr ' | |
' | |
add hubaddr, #4 ' | |
djnz reps, :loop ' | |

:incr long $400

Perry
11-23-2011, 08:56 PM
OK! my internet is back !

Thanks for all the comments guys.

I am now looking into adding "saturation" to this circuit. With just a few more parts and no code changes this setup should produce 4 levels of saturation.

Please take a look at the schematic in the first spin file, did I get the diodes right !

P.S. I can now upload files so zip archive is included.



{{

16bit NTSC video modifications November 23 2011 Perry James Mole pjm@hey-hello.ca

┌───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┐
│ NTSC 8bpp bitmap TV driver demo (C) 2011-08-19 Eric Ball │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│ TERMS OF USE: Parallax Object Exchange License │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┘
}}
CON
' Set up the processor clock in the standard way for 80MHz
_XINFREQ = 13_500_000 + 0000
_CLKMODE = XTAL1 + PLL8X

' _clkmode = xtal1 + pll16x
' _clkfreq = 80_000_000

' Demoboard TV output on pins 13 and 15

TV_PIN = 12
biWidth = 64 '92 '92 '98
biHeight = 128 '72

{


This driver's concept is to produce an HSL video circuit

hue ,saturation, luminance

hue 16 values
saturation 4 values
luminance 128 values

┌───────────────┐
│ 470Ω │
└────────┐ │
saturation0..... pin 12 ─────────────┘ │
470Ω │
hue............. pin 13 ──────────────┫
┌───────────────┫
│ 240Ω │
└────────┐ │
saturation1..... pin 14 ─────────────┘ │
 0.001F
124Ω │
luminance....... pin 15 ──────────────┻──────┳───┳───── Composite Video output
191Ω   470pf
 

Notes from Grey TV driver:
This driver uses PWM to generate output so a low pass filter is recommended:

The resistor values (124Ω series, 191Ω to ground) have an output impedance of
75 ohms and will drive a 75-ohm load at 1V P-P. The cap is there to filter the
DUTY doody. - Phil Pilgrim
http://forums.parallax.com/forums/default.aspx?f=25&m=340731&g=342216

However, in my experience, the RC network is not required. I have tested
successfully using any of the Demoboard TV DAC resistors (although the higher
resistance yields darker text) and with no resistors at all (although this
is not recommended).
}
OBJ
tv : "Pixelator_II_NTSC" '"NTSC 8bpp"

VAR
' NTSC8bpp input parameters - 7 contiguous longs, reloaded each frame
' Note: NTSC8bpp input parameters not validated, minimum clockspeed 16.364MHz
LONG i_ptr ' pointer to screen (bytes, long aligned), 0 = inactive
LONG i_width ' number of columns, must be divisible by 4 (i_width*i_clocks < 3192-3894MHz/CLKFREQ)
LONG i_height ' number of rows (max i_lines)
LONG i_clocks ' pixel width in 16x colorburst clocks (256 => i_clocks > 515MHz/CLKFREQ)
LONG i_lines ' number of active lines (max 482, does not need to be integer multiple of i_height)
LONG i_pin ' must be divisible by 4, i_pin = 1.1kΩ, i_pin+1 = 560Ω, i_pin+2 = 270Ω
LONG i_frqa ' 0 = calculate FRQA using CLKFREQ=long[0], MSB set = mask of calculated value, MSB clear = value to use
long i_gain ' output video gain Added for Pixelator
long i_colors ' color video flags Added for Pixelator
long ix_colors
' For "square" pixels 3*i_height*i_clocks = 14*i_lines
' i_clocks < 10 and i_lines < 2*i_height will generate color artifacts instead of extra detail
' Due to overscan not all of the screen is visible on all TVs.
' "Action Safe" i_width*i_clocks < 2750, i_lines < 433; "Title Safe" i_width*i_clocks < 2444, i_lines < 385

word bitmap[biWidth*biHeight]

word hue, saturation, intensity

PUB start | color,i,j,ver

tv.start( @i_ptr )

i_width := biWidth
i_height := biHeight
i_lines := 480
i_clocks := 33 '34
i_pin := TV_PIN
i_frqa := 0
i_ptr := @bitmap
i_gain := 24
i_colors := $0_00

repeat j from 0 to biHeight-1 step 1
repeat i from 0 to biWidth-1 step 1

intensity := (j*2) & $7F
bitmap[i+j*biWidth] := intensity

case intensity&3

0: saturation := $0

1: saturation := $1

2: saturation := $4

3: saturation := $5

hue := (i/4 & $F )<<4

if j > 64
bitmap[i+j*biWidth] |= (hue | saturation | 8 ) << 8
' if j == 64
' bitmap[i+j*biWidth] := $0
if j > 118
bitmap[i+j*biWidth] := $d830



and the TV driver updated



{{
┌───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┐
│ NTSC 8bpp bitmap TV driver (C) 2011-06-16 Eric Ball │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤

modifications for 16 bit November 23 Perry James Mole
most changes should be commented with ' PJM
added concept for saturation

│ The NTSC8bpp TV driver displays a i_width x i_height image on a TV using standard 3pin TV DAC. │
│ The bitmap is padded horizontally and scaled vertically to i_lines. │
│ Pixels are stored in display order (left to right, top to bottom). │
│ │ │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│ TERMS OF USE: Parallax Object Exchange License │
├───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┤
│Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation │
│files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, │
│modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software│
│is furnished to do so, subject to the following conditions: │
│ │
│The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.│
│ │
│THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE │
│WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR │
│COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, │
│ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. │
└───────────────────────────────────────────────── ────────────────────────────────────────────────── ───────────────────────────┘
}}

VAR

' long cog

{{
' NTSC8bpp input parameters - 7 contiguous longs, reloaded each frame
' Note: NTSC8bpp input parameters not validated, minimum clockspeed 16.364MHz
LONG i_ptr ' pointer to screen (bytes, long aligned), 0 = inactive
LONG i_width ' number of columns, must be divisible by 4 (i_width*i_clocks < 3192-3894MHz/CLKFREQ)
LONG i_height ' number of rows (max i_lines) square pixel = (14*i_height)/(3*i_lines)
LONG i_clocks ' pixel width in 16x colorburst clocks (256 => i_clocks > 515MHz/CLKFREQ)
LONG i_lines ' number of active lines (max 482, does not need to be integer multiple of i_height)
LONG i_pin ' must be divisible by 4, i_pin = 1.1kΩ, i_pin+1 = 560Ω, i_pin+2 = 270Ω
LONG i_frqa ' 0 = calculate FRQA using CLKFREQ=long[0], MSB set = mask of calculated value, MSB clear = value to use
LONG l_Gain ' PJM added to control greyscale output dynamicaly ie contrast
' For "square" pixels 3*i_height*i_clocks = 14*i_lines
' i_clocks < 10 and i_lines < 2*i_height will generate color artifacts instead of extra detail
' Due to overscan not all of the screen is visible on all TVs.
' "Action Safe" i_width*i_clocks < 2750, i_lines < 433; "Title Safe" i_width*i_clocks < 2444, i_lines < 385
}}

PUB start( parmptr )

stop
RESULT := COGNEW( @cogstart, parmptr)
cog := RESULT + 1
RETURN

PUB stop
COGSTOP( cog~ - 1 )

DAT
{{
The purpose of this code is twofold. First, it can be used to display a
byte per pixel (TV color) bitmap. Second, this code is intended to be a
template which may be used by others to develop Propeller video drivers.
Note: this code creates an 29.97Hz 525 line interlaced display.

Rules for developing video drivers:
1. Start simple. Hardcode values and static display.
2. Add complexity and changes incrementally. Verify at each step.
3. If something doesn't work it's either because you have made an incorrect
assumption or made a coding error.
}}

ORG 0
cogstart RDLONG i_ptr, PAR WZ
IF_Z JMP #cogstart

MOV taskptr, #taskone
MOV numlines, #4
:dotasks JMPRET taskptr, taskptr
DJNZ numlines, #:dotasks

{{
An interlaced NTSC frame is made up of 525 lines:
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
12 lines blank
241 lines active (top field / first line)
half line (with sync pulse)
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
half line (without sync pulse)
12 lines blank
241 lines active (bottom field / last line)

A non-interlaced NTSC frame is made up of 262 lines:
9 lines vertical sync (6 equalizing pulses, 6 serration pulses, 6 equalizing pulses)
12 lines blank
241 lines active
}}

frame MOV numlines, numblank0 WZ ' overscan blank lines
MOV taskptr, #taskone ' reload parameters
IF_NZ CALL #blank

MOV nxtptr, i_ptr
MOV linenum, i_lines
MOV numlines, numodd
CALL #active

MOV numlines, numblank1 WZ ' overscan blank lines
IF_NZ CALL #blank

MOV VSCL, vscl_sync ' half line
WAITVID dblank, #0
MOV FRQB, #0 ' PJM

MOV VSCL, vscl_serr
WAITVID dblank, sblank
MOV FRQB, pwmlut ' PJM

CALL #vsync

MOV VSCL, vscl_half ' extend blank for 1/2 line
WAITVID dblank, sblank
MOV FRQB, #0 ' PJM

{
One of the limitations of TV.spin is it only allows integer vertical
scaling. This calculation (and the matching calculation inside the active
loop) scales a i_height bitmap to i_lines. Note: the first line is
always displayed on the top field.
}
MOV nxtptr, i_ptr
MOV linenum, i_lines
SUB linenum, i_height WZ,WC ' set scaling for 2nd line
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, i_width ' go to next line (single line res)
IF_Z_OR_C ADD nxtptr, i_width ' go to next line (single line res)

MOV numlines, numblank2 WZ ' overscan blank lines
IF_NZ CALL #blank

MOV numlines, numeven
CALL #active

MOV numlines, numblank3 WZ ' overscan blank lines
IF_NZ CALL #blank

CALL #vsync ' moved here to reduce instructions
' from active to equalizing
JMP #frame

{{
As each pixel may be any color, the pixels are effectively the color values.
Therefor the code uses a "reverse WAITVID" which uses the pixel value for the
color (destination) parameter and #%%3210 to output the colors in LSB first
order. The downside of this method is it requires more RAM and the maximum
resolution is clockspeed limited (which isn't a problem for composite NTSC
which is bandwidth limited by the colorburst frequency).
}}
active MOV VSCL, vscl_sync ' 4.7usec @ -40 IRE (sync pulse)
WAITVID dblank, #0
MOV FRQB, #0 ' PJM

MOV pixptr, nxtptr ' precalculate pointer
SUB linenum, i_height WZ,WC ' vertical scaling calculations
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, w_width ' go to next line
SUB linenum, i_height WZ,WC ' do twice for interlace
IF_Z_OR_C ADD linenum, i_lines
IF_Z_OR_C ADD nxtptr, w_width ' go to next line
MOV numpixels, i_width

MOV VSCL, vscl_s2cb ' 0.6usec @ 0 IRE (blank)
WAITVID dblank, sblank
MOV FRQB, pwmlut ' PJM

MOV VSCL, vscl_burst ' 9 cycles of colorburst
WAITVID dblank, sburst
MOV FRQB, pwmlut ' PJM

MOV VSCL, vscl_edge
WAITVID dblank, sblank
MOV FRQB, black ' PJM

mov vs4,vscl_active ' PJM
shr vs4,#2 ' PJM
MOV VSCL, vs4 'vscl_active ' PJM
' i_clocks PLLA per pixel, 4 pixels per frame

' WAITVID pixel, #%%0
:loop RDword pixel, pixptr ' PJM'RDLONG pixel, pixptr 7 - 22 ' 22 CLK
' WAITVID pixel, #%%3210
' ADD pixptr, #4
mov luminance,pixel ' PJM ' +4 CLK 26
rol luminance,l_Gain ' PJM ' +4 CLK 30
add luminance,black ' PJM ' +4 CLK 34

WAITVID pixel, #%%1 ' PJM ' +5 CLK 39
MOV FRQb,luminance ' PJM ' +4 CLK 43
ADD pixptr, #2 ' PJM ' +4 CLK 47

DJNZ numpixels, #:loop ' +4 CLK = 51 to 44 CLKs

MOV VSCL, vscl_edge
WAITVID dblank, sblank
MOV FRQB, black ' PJM

DJNZ numlines, #active ' next line
active_ret RET

{{
Video drivers are constrained by WAITVID to WAITVID timing. In the inner
active display loop, this determines the maximum resolution at a given clock
frequency. Other WAITVID to WAITVID intervals (e.g. front porch) determine
the minimum clock frequency.

For example the inner :loop in this code is 36 CLK and displays 4 pixels.
Therefore with an 80MHz clock the minimum VSCL.PixelClocks (i_width) is 7.
36 / 80MHz * 57.272727MHz / 4 = 6.44

Similar calculations should be used between each pair of WAITVIDs to determine
the minimum clock frequency (including initialization tasks).

Note: the first HUBOP after a WAITVID should be counted as 22 CLK as it cannot
be synchronized. WAITVID should be counted as 6 CLK as 5 CLK can cause
intermittent errors with very tight timing.
}}

vsync MOV numlines, #6
:equal1 MOV VSCL, vscl_eqlo ' equalizing pulse (sync/2)
WAITVID dblank, #0
mov frqb,#0 ' PJM

MOV VSCL, vscl_eqhi
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:equal1

MOV numlines, #6
:serration MOV VSCL, vscl_serr ' serration pulse (sync)
WAITVID dblank, #0 ' PJM
mov frqb,#0

MOV VSCL, vscl_sync
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:serration

MOV numlines, #6
:equal2 MOV VSCL, vscl_eqlo ' equalizing pulse (sync/2)
WAITVID dblank, #0
mov frqb,#0 ' PJM

MOV VSCL, vscl_eqhi
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
DJNZ numlines, #:equal2

vsync_ret RET

blank MOV VSCL, vscl_sync ' 4.7usec @ -40 IRE (sync pulse)
WAITVID dblank, #0 ' PJM
mov frqb,#0

MOV VSCL, vscl_s2cb ' 0.6usec @ 0 IRE (blank)
WAITVID dblank, sblank
mov frqb,pwmlut

MOV VSCL, vscl_burst ' 9 cycles of colorburst
WAITVID dblank, sburst
mov frqb,pwmlut ' PJM

MOV VSCL, vscl_half
WAITVID dblank, sblank
mov frqb,pwmlut ' PJM
JMPRET taskptr, taskptr

MOV VSCL, vscl_blank
WAITVID dblank, sblank
mov frqb,#0 ' PJM

DJNZ numlines, #blank ' next line
blank_ret RET

d1 LONG |<9 ' destination = 1

taskone MOV pixptr, PAR ' load input parameters
MOVD :i_loop, #i_ptr ' 242 CLK
MOV numpixels, #8 ' ' PJM #7 add l_Gain to inputs
:i_loop RDLONG i_ptr, pixptr
ADD pixptr, #4
ADD :i_loop, d1
DJNZ numpixels, #:i_loop

MOV W_width,i_width ' PJM now using words for pixel depth
shL W_width,#1

CMP i_ptr, #0 WZ ' 0 = blank
IF_Z JMP #standby ' 8 CLK

MOV vscl_active, i_clocks ' vscl_active = i_clocks<<12 + i_clocks*4
SHL vscl_active, #12 ' 16 CLK
ADD vscl_active, i_clocks
SHL vscl_active, #2

MOV vscl_edge, vscl_edge0 ' vscl_edge = 1<<12 + (3192 - i_clocks*i_width)/2
MOV numerator, i_width ' 120 CLK
MOV divisor, i_width
MAX numerator, i_clocks
MIN divisor, i_clocks
:mul SHR divisor, #1 WZ,WC ' maximum 6 iterations
IF_C SUB vscl_edge, numerator
SHL numerator, #1
IF_NZ JMP #:mul
SHR vscl_edge, #1

{
The calculations here accomplish two tasks: ensure the blank lines are
distributed correctly and add on the blank lines to the top of each field.
}
' numeven := rounddown(i_lines / 2)
MOV numeven, i_lines ' 60 CLK
SHR numeven, #1
' numodd := roundup(i_lines / 2)
MOV numodd, i_lines
SUB numodd, numeven
' numblank2 := numblank0 := int( (484-i_lines)/4 ) + 12
MOV numblank0, #484
SUB numblank0, i_lines
SHR numblank0, #2
ADD numblank0, #12
MOV numblank2, numblank0
' numblank1 := int( (482-i_lines)/4 )
MOV numblank1, #482
SUB numblank1, i_lines
SHR numblank1, #2
' numblank3 := numblank1 + i_lines // 2
MOV numblank3, i_lines
AND numblank3, #1
ADD numblank3, numblank1

JMPRET taskptr, taskptr ' 242+8+16+120+60 = 446 CLK

add i_pin,#3 ' ' PJM for Grey duty cycle
MOVS CTRb, i_pin
MOV pixel, #1
SHL pixel, i_pin
mov DIRA, pixel
sub i_pin,#3 ' PJM set pin back
MOVI CTRb, #%0_00110_111 ' PJM use PLL%1 was #%0_00110_000 ' turn on PWM

AND i_pin, #28 ' 16 CLK
MOV pixel, #%0010 'PJM use only color pin '7 summing network output
' PJM use origional value when saturation circuit is in place
SHL pixel, i_pin
or DIRA, pixel ' set pin mask

MOV pixel, i_pin ' 36 CLK
SHR pixel, #3
MOVD VCFG, pixel ' set VGroup
AND i_pin, #4 WZ
MOV pixel, #7
SHL pixel, i_pin
MOVS VCFG, pixel ' set VPins
IF_Z MOVI VCFG, #%0_10_1_0_1_000 ' composite baseband on VPins[2..0]
IF_NZ MOVI VCFG, #%0_11_1_0_1_000 ' composite baseband on VPins[6..4]

RDLONG divisor, #0 ' CLKFREQ
MOV quotient, l9090909 ' 306 CLK
MOV numerator, l7159090
:shlmax SHL quotient, #1 WC ' maximize the two values
RCL numerator, #1 ' maximum 17 iterations (based on minimum 16MHz CLK)
SHL divisor, #1 WC
IF_NC JMP #:shlmax
RCR divisor, #1 ' undo overshoot

JMPRET taskptr, taskptr ' 16+36+306 = 358 CLK

MOV numpixels, #16 ' do division (first half)
:div1 CMPSUB numerator, divisor WC ' 264 CLK
RCL quotient, #1
SHR divisor, #1 WZ
DJNZ numpixels, #:div1

JMPRET taskptr, taskptr ' 264 CLK

:div2 CMPSUB numerator, divisor WC ' complete division
RCL quotient, #1 ' 264 CLK
SHR divisor, #1 WZ
IF_NZ JMP #:div2

{
See http://www.linusakesson.net/programming/propeller/pllsync.php
for synchronizing multiple video output cogs
Otherwise just be aware that due to PLL startup time and the initial
value of the internal frame counter being unknown that the timing
and stability of the first frame is not predictable.
}

MOV VSCL, ivscl ' set VSCL to instant reload
MOVI CTRA, #%0_00001_110 ' PLL internal mode, x8 CTRA = 57.2727272MHz (colorburst * 16)
SHL i_frqa, #1 WC,WZ,NR ' test MSB/zero
IF_NZ_AND_NC MOV quotient, i_frqa
IF_C AND quotient, i_frqa
MOV FRQA, quotient ' set CTRA to 7.159MHz, start the counters


lasttask JMPRET taskptr, taskptr ' 256+24 = 280 CLK
JMP #lasttask

standby MOV DIRA, #0
MOV VCFG, #0
MOV CTRA, #0
MOV CTRb, #0
JMP #cogstart

l7159090 LONG $6D3D32 ' 7159090 Hz
l9090909 LONG $E8BA2E8B ' 0.909090909 Hz
ivscl LONG 1<<12+1 ' initial delay

vscl_sync LONG 1<<12+269 ' 4.7usec
vscl_s2cb LONG 1<<12+304-269 ' sync to colorburst
vscl_burst LONG 16<<12+16*9 ' 16 PLLA per cycle, 9 cycles of colorburst
vscl_eqlo LONG 1<<12+135 ' sync/2
vscl_eqhi LONG 1<<12+1685 ' H/2 - sync/2
vscl_serr LONG 1<<12+1551 ' H/2 - sync
vscl_half LONG 1<<12+1820 ' H/2
vscl_blank LONG 1<<12+1372
dblank LONG $8A0200
sblank LONG %%1111_1111_1111_1111 ' 16 pixels color 1
sburst LONG %%2222_2222_2222_2222 ' 16 pixels color 2
vscl_edge0 LONG 1<<13+3192 ' initial value (pre shifted)
pwmlut LONG $3fff_ffff '$3824_4924 '$45555555 '$3d249249 ' PJM
black LONG $4fff_ffff '$3daaaaaa '$3e93B84A ' PJM
'burst LONG $4e44ffff '$3f000000 ' PJM
'color_mask long $FFFFFFF0 ' PJM
cog long 0
luminance long
lum0 byte 0
lum1 byte 0
lum2 byte 0
lum3 byte 0
vscl_edge RES 1 ' border, 1 PLLA per pixel
vscl_active RES 1 ' 4 pixels @ i_clocks PLLA per pixel

i_ptr RES 1 ' pointer to screen (bytes) 0 = inactive
i_width RES 1 ' number of longs (4 pixels per long)
i_height RES 1 ' number of rows
i_clocks RES 1 ' pixel width
i_lines RES 1 ' number of active lines
i_pin RES 1 ' pin number
i_frqa RES 1 ' FRQA value / mask
l_gain res 1
W_width RES 1 ' number of longs (4 pixels per long)

numeven RES 1 ' number of even active lines
numodd RES 1 ' number of odd active lines
numblank0 RES 1
numblank1 RES 1
numblank2 RES 1
numblank3 RES 1

quotient RES 1
numerator RES 1
divisor RES 1

taskptr RES 1 ' initialization task jmpret pointer
numlines RES 1 ' overall line counter

linenum RES 1 ' line scaling counter
numpixels RES 1 ' pixel counter
pixptr RES 1 ' pointer to pixels
nxtptr RES 1 ' pointer to next line
pixel RES 1 ' pixel value

vs4 res 1
FIT 496