PIC16F54/7/9 based Video Time Inserter (VTI)
Note - for details of the PAL TV frame timing, I referred to this page. PAL Scan-line timing is 64µs (Active Display 51.95µs, Sync 12.05µs (Front Porch 1.65µs, H-Sync pulse 4.7µs, Back Porch 5.7µs))
After discovering the Pico OSD (which is based on a PIC12F683), I decided to 'have a go' using a 16F54, both because that's what I had available and because some 'extra' pins would be needed to 'receive' a NTP/GPS derived date/time sent via a serial data link
I also took some inspiration from this PIC based video game design
It is not possible to use the PicoOSD 'as is' due to the differences between the PIC12F683 and the PIC16F54.
Unlike the PIC12F683, the 16F54 has a single 'TRIS' command (which copies the contents of the Acc to the Tri-state latch of the target PORT) so it's not possible to directly 'bit set' or 'bit clear' the Tri-state latch nor to 'shift' the contents of the latch itself Further, he 16F54 has no 'Shift Acc' (or 'Rotate Acc') instruction (you can't even add the Acc to itself). You can shift a pattern a Register, but you can't send the reg contents direct to the TRIS latch ! So actually using TRIS will 'cost' at least 3 CLK's to output one bit (Rotate reg, Copy reg to Acc, Copy Acc to TRIS). This would mean 3 CLK's = 12 OSC cycles per bit, which even by overclocking to 25MHz, means each pixel is about 0.48uS 'wide' allowing only 108 pixels per line (or about 18 characters on a PAL 64uS standard line, of which only some 52uS is visible, see below) If we want even half decent resolution, it's plainly not a viable approach to use the tri-state (TRIS) output mode with the 16F54. Instead I will have to leave the PORT enabled and 'buffer' the output with a diode or transistor
The PIC16F54 design
To find the 'insert line', the PIC must be able to detect both the Frame Sync and Line Sync, so it can 'count' to the 'right' line. The problem is keeping the PIC generated pixels 'in step' with the video stream, and this means 'aligning' the pixels to the line sync with an accuracy of better than 1 pixel time
Outputting pixels
The first problem to overcome is the restrictive nature of the 16F5x tri-state (TRIS) command. If the TRIS latch could be 'read', we could rotate the latch contents in a single cycle and achieve a respectable (overclocked) 25MHz/4 = 6.25MHz pixel rate (325 pixels per line). However we can't, so we have to find another way to generate data at 1 CLK per pixel.
The output PORT's are treated as registers. This means the PORT (and thus the state of the pins) can be shifted ('rotated'). However, shift involves reading the existing state of the pins (and not the contents of the PORT latch) - which means we have to leave all the i/o pins on the PORT permanently 'enabled' (so the latch is output on the pins). Taking this approach means we can achieve a single CLK pixel rate
Since the pins will be permanently enabled, the output pin has to be buffered from the existing incoming video feed by using a diode (or, for fast edges, a transistor).
Output pixel resolution
If we can overclock to 25MHz, then we have a 6.25MHz CPU clk = pixel rate, and we get (720* 6.25MHz/13.846) = 325 pixels or about 40 characters using an 8 pixel wide character matrix (5 shape, 3 gap) ! This is (just) acceptable for a VTI (Video Time Inserter) function (although the characters will have to be 'wide and short' to avoid intruding into the picture area too much)
If we use the time/date format "DD mmm YYYY hh:mm:ss.ss" with a 5x7 character map and 2 pixel inter-character gap, this occupies 25x7 = 175 of the 325 available pixels (or just over half the screen width)
Character shapes
For legible characters, we only need a 5x7 'font' (bit map) = which is 5 pixels wide by 7 lines tall.
Ideally, we want a 3 character month (rather than digit month), however the space available for the FONT table is very limited on the low end 16F5x series - for example, the 16F54 can only support a 2 character month (see second ref. below)
(+) DDmmmYYYY character map - (VTI)
(+) 16F54 2char month map - (VTI)
VTI Character string output
Although we need to output 5 bit wide characters, we can still use the 4 bit PORTA, by using Cy as the 5th bit.
Using PORTA b3 as the o/p, we 'pre-shift' the bit map down into Cy before loading 4 bits into PORTA b3-0. After the first bit time, we 'shift up' using the ROTate left with Carry' command. This moves Cy into b0 and, after a total of 4 more bit times the 5th and final bit (from Cy) reaches PORTA b3. For the inter-character gap, all we have to do is 'Bit Clear' PORTA b3.
The PIC will output the bit-mapped characters at maximum possible speed, i.e. 1 CPU CLK per pixel. This is achieved by loading the PORT register during the inter-character gap and then 'shifting' the register once for each pixel
Note that outputting a 'bit map' at maximum speed is much the same as high speed serial link transmission), so similar techniques are used here.
Since each bit map is 5 pixels and the PORTA is only 4 bits wide, we start by shifting b0 into CY (ROTR) and the other 4 bits into the Acc. The Acc is them copied into the PORTA register, where PORT b3 = the video o/p.
When we first ROTL PORTA, the character map is shifted up one and Cy is loaded into PORT bit0.
On each ROTL, PORTA bit7 (which is always 'read' as 0) will be loaded into Cy. Whilst this might hint at the possibility of an early 'end' (when all PORTA bits are '0'), the ROT instructions do not set the Z bit = and we don't have time to waste on 'Jump on Z' anyway). One 'advantage' of 0 to Cy is that we could perform an extra shift for the inter-character gap, however 'CLR PORT' is a better 'guarantee' that b3 will be set to 0.
Whilst the 'obvious' way to output multiple characters of 5 (or 6) bits at a time is by coding it as a 'loop' there is just no time for 'counting' (increment and test). In fact, we can't even use a 'per character' subroutine call (a 'call' costs 2 CLK's and a Return 2 more and we just don't have any CLK's to spare during the inter-character gap).
To maintain the o/p rate, the pixel 'loop' has to be totally 'unpacked' and written out 'long hand'. We also have to 'pre-prepare' all the character maps and load them into a hard-coded set of registers (we don't even have time to increment the INDF pointer) prior to starting pixel output
Further, the 16F54 has only 25 registers, so we need to 're-use' registers where possible (the 16F57 has 72, which means no re-use is necessary)
The character bit map is fed out 'MSB' (left most bit) first. Speed is maintained by using ROTL PORTA (which works because the 5th bit is loaded into the Cy before rotate is started).
Code to output a shape bit map
The 5 pixels of the character bit map are placed into b0-b4 of the bms source register (eg bms10h, bms1h for 10's of hours, units of hours). After the 5 pixels are output, a 2 pixel 'gap' (0 value) is inserted before the next group of 5.
; start the o/p, "hh:mm:ss.ss"
ROTR bms10h,Acc ; get the 10's of hours (pix0 goes to to Cy, pix1-4 to Acc0-3)
COPY Acc,PORTA ; o/p char map pixel 4
ROTL PORTA ; o/p pix3, get pix0 back from Cy into PORTA b0, read PORTA b7 (0) into Cy
ROTL PORTA ; o/p pix2, loads 0 from Cy into PORTA b0
ROTL PORTA ; o/p pix1 (0 in b1)
ROTL PORTA ; o/p pix0 (0 in b2)
CLR PORTA ; o/p 0 (in b3) = 1st pixel of inter-character gap
;
ROTR bms1h,Acc ; get the bit map for unit hours (2nd pixel of inter-character gap)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
Note that if we totally 'unwind' the output code, then the entire 175 pixel VTI stream will require 175 instructions ! Since another 224 locations are to be used for bit-maps (and the 16F54 has only 512 instruction space in total), we will be left with 113 instructions for 'every thing else' (which must include the 'time and date' setting code)
Rather than CLR PORTA, we could also have used an extra 'ROTL PORTA' = on the first ROTL, the top bit of PORTA (which is always read as 0) will be loaded into Cy and, after ROTL instructions PORTAb3 will be loaded with the Cy bit)
ROTL instruction is always via Cy and 'source' is always a register. Destination is either the same register or the Acc. The same restriction applies to NiblSwap (source = reg, dest = same reg or Acc)
Code to output the 'punctuation' ('.' and ':') bit map
The '.' (if required on this scanLine) will be loaded into the MSB of the h units register, m units and seconds units register. Output of the punctuation thus 'follows on' from the unit shape bit map output as per the following code :-
ROTR bm1h,Acc ; The 5 unit hours shape pixels are now aligned with lsb in Cy and other 4 bits in the lo nibble of the Acc
COPY Acc,PORTA ; the low 4 bits are now in PORTA and PORTAb3 = MSB (pix 4) of the character shape
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set 0 for the 1st inter-character gap pixel
NiblSWP bm1h,Acc ;get the top 4 bits of the source (b7-4) into Acc b3-0, second pixel of gap
COPY Acc,PORTA ;set PORTAb3 = now msb of bm1h = punctuation
CLR PORTA ; set 0 for the 1st inter-character gap pixel
; get the next character map to Acc (2nd gap) and then output it ...
Code to re-use Registers
2a) The 16F54 has only 25 GP registers. If we dedicate one per character shape and pack the 'count' into 2 (BCD) digits per byte, we end up with (approximately) none left :-). However if we display the Time before the Date, we can reload two of the time register with the Year 'century' values ('20') and save 2 registers
I actually ended up saving 7 registers by splitting the Date and Time into 2 groups, aligning the Date (DD mmm YYYY) left, the Time (hh:mm:ss.ss) right, and loading the time pixel maps in the gap between the two groups (see Final Code, below). I've left the initial code here because it shows how loading two shapes 'in-line' can reduce the total CLK count to 11 (i.e. less than 6 CLK's per shape)
; Reuse the bms100 and bms10 register for '0' and '2' (year century)
COPY scanLn,Acc ; get the scan line number (0-6) clk (1)
ADD Acc,Acc ; double it, +1 (2)
ADD Acc,Acc ; 4x, +1 (3)
ADD Acc,PCL ; jump to bit map at 4x this scan line +2 (5)
;
LOAD shape0ln1,Acc ; get pixels for line0, scan line +1 (6)
COPY Acc,bms100 ; 1 clk (7)
LOAD shape2ln1,Acc ; 1 clk (8)
JMP done1 ; 2 clk (10)
;
LOAD shape0ln2,Acc
COPY Acc,bms100
LOAD shape2ln2,Acc
JMP done1
LOAD shape0ln3,Acc
COPY Acc,bms100
LOAD shape2ln3,Acc
JMP done1
LOAD shape0ln4,Acc
COPY Acc,bms100
LOAD shape2ln4,Acc
JMP done1
LOAD shape0ln5,Acc
COPY Acc,bms100
LOAD shape2ln5,Acc
JMP done1
LOAD shape0ln6,Acc
COPY Acc,bms100
LOAD shape2ln6,Acc
JMP done1
LOAD shape0ln7,Acc
COPY Acc,bms100
LOAD shape2ln7,Acc
JMP done1
done1: ; arrive here with Acc=shape '2'
COPY Acc,bms10 ; shape '2' loaded to reg bms10, 1 clk (11)
; total for both = 11 CLK's, 34 instructions
Note the above code MUST be contained within the low 256 locations of a page - 'add to PCL' is an 8 bit operation resulting in a 9 bit Program Counter (PC) value with the top bit cleared i.e. the result is always within the first 256 locations of the instruction store (bit 9 of PC is always cleared on any PCL 'write').
The final code
The code below 'evolved' in a number of steps. The major changes were :-
1) added the '.' and ':' pixels to the MSB of the h1,m1,s1 pixel map registers (thus saving the need for a separate 'punctuation register')
2a) display as "hh:mm:ss.ss DD mmm YYYY" so two of the pixel map registers from the 'hms' can be re-used for the '20' year century bit maps (by loading the patterns using the 12 CPU clk's between the two groups (which is just enough time, using an in-line '20' font) thus reducing the reg count
2b) split the display into 2 groups, "DD mmm YYYY" first (left justified) followed by "hh:mm:ss.ss" (right justified), allowing more of the YYYY pixel map registers to be reused
One problem is the length of the 'intergroup gap'. This depends on the PIC OSC frequency, however at the max. guaranteed speed of 20MHz the visible scan line (54uS) is less than 270 CPU clks. The first group ("DD mmm YYYY") is 11x7 = 77 pixels, the second ("hh:mm:ss.ss") is 8x7 + 3x3 = 65 pixels. That means the gap is (less than) 128 CPU clks (and in that time we have to load 8 registers which means no more than 16 clks max for each). Fortunately, the second group consists of numeric values only (which simplifies the look-up process).
Note that output rate is 7 CPU clocks per character (5 shape, 2 space) with only one CLK's between characters. The 'punctuation' mark is added as a single pixel followed by 2 space (one CLK of which is used to load the next character). During "hh:mm:ss.ss" there are zero spare CLK's .
; start of the DD mmm YYYY group (9 registers have been preloaded with the required shapes, including the year century '20')
; 8 of the registers are going to be re-used for the hh:mm:ss.ss group
ROTR Dh10s,Acc ; get 10's od Days, pix0 to Cy, pix1-4 to Acc0-3
COPY Acc,PORTA ; o/p char map pixel 4
ROTL PORTA ; o/p pix3, get pix0 back from Cy into PORTA b0, read PORTA b7 (0) into Cy
ROTL PORTA ; o/p pix2, loads 0 from Cy into PORTA b0
ROTL PORTA ; o/p pix1 (0 in b1)
ROTL PORTA ; o/p pix0 (0 in b2)
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
;
ROTR Dh1s,Acc ; get units Days (whilst 2nd pixel of inter-character gap is output)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
;
CALL slInit: ; Call the first location of the shape pixel Table (immediate RET, total cost 4 CLK)
ROTR mInitial,Acc ; get Initial char of month (whilst 6th pixel of Day/month gap is output)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
;
ROTR m10s,Acc ; get second char of month (whilst 2nd pix of inter-char gap is output)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
;
ROTR m1s,Acc ; get 3rd char of month
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
;
CALL slInit: ; Call the first location of the shape pixel Table (immediate RET, total cost 4 CLK)
ROTR y2s10s,Acc ; get '2' of year (whilst 6th pixel of Day/month gap is output)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
;
ROTR y0s10s,Acc ; get '0' of year
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
;
ROTR y10ss10,Acc ; get 10's of year (1/10s of s)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
;
ROTR y1ss100,Acc ; get 1's of year (1/100s of s)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
CLR PORTA ; set PORTA b3 Lo = 1st pixel of inter-character gap
; 65 Instructions, 71 CLK's.
Next we have to load the character shapes for the hh:mm:ss.ss group (which is complicated by the punctuation that has to be added to the MSB of 3 of the characters)
; get the hh:mm:ss.ss BCD counts, look up the shapes
Registers loaded, now need to insert delay before the start of the output
<
; OK, start of the hh:mm:ss.ss group.
ROTR bm1s,Acc ; get units of a second, pix0 to Cy, pix1-4 to Acc0-3
COPY Acc,PORTA ; o/p char map pixel 4
ROTL PORTA ; o/p pix3, get pix0 back from Cy into PORTA b0, read PORTA b7 (0) into Cy
ROTL PORTA ; o/p pix2, loads 0 from Cy into PORTA b0
ROTL PORTA ; o/p pix1 (0 in b1)
ROTL PORTA ; o/p pix0 (0 in b2)
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
ROTR bm10s,Acc ; get 10s of a second (2nd pixel of inter-character gap)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
ROTR markReg,Acc ; get the bit map for ':' (2nd pixel of inter-character gap)
COPY Acc,PORTA ; o/p '.' pixel to PORTA b3
CLR PORTA ; 0 to PORTA b3 (1st pixel of inter-character gap)
;
ROTR bm1m,Acc ; get units of a minutes, pix0 to Cy, pix1-4 to Acc0-3
COPY Acc,PORTA ; o/p char map pixel 4
ROTL PORTA ; o/p pix3, get pix0 back from Cy into PORTA b0, read PORTA b7 (0) into Cy
ROTL PORTA ; o/p pix2, loads 0 from Cy into PORTA b0
ROTL PORTA ; o/p pix1 (0 in b1)
ROTL PORTA ; o/p pix0 (0 in b2)
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
ROTR bm10m,Acc ; get the bit map for 10s of minutes (2nd pixel of inter-character gap)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
ROTR markReg,Acc ; get the bit map for ':' (2nd pixel of inter-character gap)
COPY Acc,PORTA ; o/p '.' pixel to PORTA b3
CLR PORTA ; 0 to PORTA b3 (1st pixel of inter-character gap)
;
ROTR bm1h,Acc ; get units of hour, pix0 to Cy, pix1-4 to Acc0-3
COPY Acc,PORTA ; o/p char map pixel 4
ROTL PORTA ; o/p pix3, get pix0 back from Cy into PORTA b0, read PORTA b7 (0) into Cy
ROTL PORTA ; o/p pix2, loads 0 from Cy into PORTA b0
ROTL PORTA ; o/p pix1 (0 in b1)
ROTL PORTA ; o/p pix0 (0 in b2)
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
ROTR bm10h,Acc ; get the bit map 10s hours (2nd pixel of inter-character gap)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
OK, "hh:mm:ss.ss" complete, now the gap before Year ("DD mmm YYYY "). This needs to be equivalent to 2 characters, 14 pixels (of which the 1st has already been o/p and the last will be o/p whilst loading the Year units)
Whilst the Year will be 20xx, we still need to o/p the actual character shapes, so still have to pre-load the registers. The next gap == 1 char (approx) = in fact it's going to be 6 pixels (as we use a single CALL delay)
; Year to month gap
CALL charMap0 ;call character shape map which will RET, total delay 4 CPU clk
;
ROTR bm3mth,Acc ; get month 3rd char, pix0 to Cy, pix1-4 to Acc0-3
COPY Acc,PORTA ; o/p char map pixel 4
ROTL PORTA ; o/p pix3, get pix0 back from Cy into PORTA b0, read PORTA b7 (0) into Cy
ROTL PORTA ; o/p pix2, loads 0 from Cy into PORTA b0
ROTL PORTA ; o/p pix1 (0 in b1)
ROTL PORTA ; o/p pix0 (0 in b2)
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
ROTR bm2mth,Acc ; get month 2nd char (2nd pixel of inter-character gap)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
ROTR bm1mth,Acc ; get month 1st char, pix0 to Cy, pix1-4 to Acc0-3
COPY Acc,PORTA ; o/p char map pixel 4
ROTL PORTA ; o/p pix3, get pix0 back from Cy into PORTA b0, read PORTA b7 (0) into Cy
ROTL PORTA ; o/p pix2, loads 0 from Cy into PORTA b0
ROTL PORTA ; o/p pix1 (0 in b1)
ROTL PORTA ; o/p pix0 (0 in b2)
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
Month to day gap
; Month to day gap
CALL charMap0 ;call character shape map which will RET, total delay 4 CPU clk
;
ROTR bm1d,Acc ; get units day (2nd pixel of inter-character gap)
COPY Acc,PORTA ; o/p pix 4
ROTL PORTA ; o/p pix 3, get pix0 back from Cy
ROTL PORTA ; o/p pix 2
ROTL PORTA ; o/p pix 1
ROTL PORTA ; o/p pix 0
ROTL PORTA ; o/p 0 in b3 (1st pixel of inter-character gap)
;
ROTR bm10d,Acc ; get 10s day, pix0 to Cy, pix1-4 to Acc0-3
COPY Acc,PORTA ; o/p char map pixel 4
ROTL PORTA ; o/p pix3, get pix0 back from Cy into PORTA b0, read PORTA b7 (0) into Cy
ROTL PORTA ; o/p pix2, loads 0 from Cy into PORTA b0
ROTL PORTA ; o/p pix1 (0 in b1)
ROTL PORTA ; o/p pix0 (0 in b2)
CLR PORTA ; o/p 0 in b3 = rest of lne = zero
;
That's it ! In the VTI "DD mmm YYYY hh:mm:ss.ss", each of the 17 characters 'cost' 7 instructions (119) however we saved a few in the punctuation and group gap (4 x 3 =12) and the D M Y gaps (2 x 1 =2) for a total 'cost' of 133 total (rather than our first estimate of 175)
The date/time 'count'
To output "DD mmm YYYY hh:mm:ss.ss", 18 registers will be needed for the character shapes. Since the 16F54 has 25 registers, that leaves only 7 for the 'count' = so we will have to 'pack' the count values 2 per byte (as 4 bit BCD digits). The 'count' register set is thus :-
DD = 1 byte (2x BCD = 00-31)
Mmm = 1 byte (binary, 1-12)
YYYY = 1 byte (the first two YY digits will be hard coded to '20' so only need 2x BCD = 15+)
hh = 1 byte (2 x BCD = 00-23)
mm = 1 byte (2 x BCD = 00-59)
ss.ss = 2 bytes (4x BCD = 00.00 - 59.99)
TOTAL 7
When not actually outputting characters, the 18 'shape' registers can be 'reused', however (during output), in addition to the date/time count, we also have to maintain the 'scan line' count (so we can select the correct character shapes). It should be possible to use a combination of the TMR0 register and the 6 'spare' bits in the 'markReg' (there are also 5 (low) bits available in the FSR)
However, 'using up' every GP Register is not a very 'good idea' !
Reducing the pixel map Register count
There are 12 CPU clks 'available' in the 'gap' between the time and date group output. At the very least it should be possible to load the shape maps for '20' (the fixed year century count) into two of the existing registers. It might even be possible to load the Year (units) map into the Acc ready for the start of the year group output
The shape map for '2' and '0' already exists in the shape table, however it takes a minimum of 8 CLK's to get them (1 to set '0' (or 2), 2 Call, 5 Ret with value). This has to be reduced to 6 if we want to re-use two registers.
A Jump 'costs' the same as a 'Call' or 'Ret', however the Ret includes 'Laod Acc with value' which is why a Call/Ret is normally used for data loading. However, when the shape table is Called it then performs a computed Jump before the Ret. This is why loading a shape takes +5 clks.
The 'computed jump' will still be required (we have to get the 'right' scan line map), however there is no need for a 'Call'. This means no 'return', so the data value will have to be loaded 'within' the code
A computed jump (Add Acc,PCL) is an 8 bit operation that results in CLEARING the 9th bit of the Program Counter - i.e. the destination of a computed jump will always ne in the first 256 address locations
The character bit map Data Table
As already noted, the character bit maps are 7x5 and will be held in 7 bytes, 5 bits per byte (bits 4-0, reading left to right). To 'speed up' access, they will be held 'interleaved' i.e. all the 'top' scan lines first, then all the 2nd lines and so on. A register will be used to 'point' at the base of the Table for 'this' scan line.
Computed Call does not exist. So there can only be one 'entry point' into the Data Table (which then computes the required bit map location and executes a 'COPY Acc,PCL' to perform the actual lookup. On entry, the Character code (0-9 in the case of numerals) is in the Acc. To this is added the ScanLine Base reg (this reg is initialised to the first Data Table location and then 32 is added to the register after each line has been processed).
To avoid the need for an additional register (to hold the '.' and ':'), these bit patterns are added into the bit-maps of the preceding characters.. he '.' and ':' bit maps (used in hh:mm:ss.ss) can be 'packed' into 3 bits of a single byte (.::) - and held in the top (unused) 3 bits of any Data Table character bit map (eg '0'). To get the 'mark' pattern for the current scan line, a Call is made with Acc=0. On return the Acc is ROTR to the 'mark' reg which is then nibble-swapped (the mark register thus ends up with the bits '???? ?.::')
Fetching the bit maps
The bit maps are loaded in 2 groups. First the time is loaded, which is held in 4 registers (2 BCD numbers per) = hh:mm:ss.ss and output to the video. Then the date is loaded, held in 3 registers (DD mmm 20YY) and output. The date is a bit more complex - the BCD month number (1-12) has to generate the 3 character 'Mmm' = 3 bit maps (rather than the usual 2). This means the 'loading' process consists of one subroutine (which gets 2 bit maps from a 2 digit BCD) plus 1 'special case' (the Mmm).
With only one INDF 'pointer' (FSR) we have to get 'clever' if we want to use INDF addressing in a loop for both date/time and bit map register addressing. Fortunately, the FSR can be manipulated using INC, DEC (and BSET, BCLR) instructions.
The bit map set uses 8 (or 9) registers which will be stepped through at a rate of two per date/time reg. The reg 'count' can be 'halved' (ROTR) a bit set (or cleared) to 'point' at the date/time set and then the 'count' restored (ROTL) SO LONG AS nothing changes the Cy bit in between
The 16F54 has 25 registers, starting at location 0x07 (00111) and running to 0x1F (11111). If the 4 time registers are 0x07-0x0A and the bit map base is 01110 (0x0E) then a simple ROTR will 'point' at 00111 (0x07, the time base). After 7 INCrements, the bit map pointer will be 'aimed' at 0x15 = 10101 and a ROTR will 'point' at 01010 (0x0A). The 4 date registers can be held at 0x15 - 0x19 (= bit map ROTR + BSET bit4). In both cases, a ROTL will restore the bitmap pointer.
; Arrive here after we have the GPS time and date, plus have reached the 'insert point'
; Initialisation (scan line 0)
LOAD slInit: ; get the start of the DataTable bit maps
COPY Acc,slBase ;
;
LOAD bmBase ; first bit map register (there are 8 for time, 9 for date)
COPY Acc,temp ; save in temp
LOAD timeBase ; start of time register set (2 bytes per)
COPY Acc,FSR ; aim INDF at time set
;
COPY INDF,Acc ; get first time value
CALL getMapN ; get bit map
;
Time keeping
Unless a RTC or GPS is used, the initial time will have to be set by some external means (manually, using buttons, or by serial link transmission from a PC etc) just like any other clock.
The fractional seconds (.ss) can be derived by 'counting' on alternate Frame Sync pulses (to avoid the value changing between 'half frames') but does mean the accuracy is 1/25th s (.04))
Using a GPS chip
The GPS unit will be used to set the initial time (i.e. after power-on) and at some regular interval there-after.
Unless absolute accuracy is required, the time count will be maintained by counting full video frame VSyncs (this avoids the value changing between half frames but means the accuracy is 1/25th s (.04)). If higher accuracy is required, HSync pulses can be counted.
Faster output using an external shift register
The output 'resolution' is limited by the PIC 'ROTL PORT' instruction (and the inter-character gap to the time taken to find the next byte value). If an external pixel shift register is used (eg 74HC166) the PIC would only need to 'feed' it complete bytes .. and this can be done at a rate of 2 CPU CLK's per byte (using absolute Register addressing, COPY reg,Acc, COPY Acc,PORT).
In theory this allows the 16F5x, at max. OSC 20MHz, to output 8 pixels in 2 CPU CLK i.e. 2x OSC/4 which is 20 mega pixels per second. This is actually faster that PAL TV data rate (about 13MHz) In practice,
The drawback is that some sort of external circuit not only has to clock the shift-register, but also has to provide it with a 'load' pulse every 8th clock - and all this has to be kept 'in sync' with the PIC CPU !
Once the cost of all these extra 'counter' chips etc. is added, it becomes cheaper to use a 'high end' 48MHz+ PIC (with it's internal shift register) instead