Home
JAQForum Ver 24.01
Log In or Join  
Active Topics
Local Time 21:10 04 Nov 2025 Privacy Policy
Jump to

Notice. New forum software under development. It's going to miss a few functions and look a bit ugly for a while, but I'm working on it full time now as the old forum was too unstable. Couple days, all good. If you notice any issues, please contact me.

Forum Index : Microcontroller and PC projects : One for the PIO gurus

Author Message
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10553
Posted: 07:11pm 01 Nov 2025
Copy link to clipboard 
Print this post

Can anyone help diagnose this?
This code is supposed to create a bitclock, a HSYNC pulse and a VSYNC pulse with VGA timings. It is development in preparation for trying to drive an RGB TFT display which needs a bitclock.
The bitclock runs at 25.1MHz
The HSYNC runs at 31.5 MHz with a 3.8uS sync
The VSYNC runs at 60Hz with a 63.5uS sync
http://www.tinyvga.com/vga-timing/640x480@60Hz
All this works fine except that when you trigger on VSYNC you will see that HSYNC is drifting relative to it whereas given that they should both be triggered nearly simultaneously by the separate irqs.
The problem looks like the hysnc timing is overunning (or visa versa) but I can't see the problem, fiddling with the underrun fudge factor on the vsync numbers changes the drift but it should be locked and the fudge factor of 1000 clocks should not be critical
I've been looking at this too long to see the wood for the trees. Help appreciated.

Option explicit
Option default integer
Dim p1,f1,e1,s1,o1,p2,f2,e2,s2,o2,p3,f3,e3,s3,o3
' set pins that will be used as outputs
SetPin gp4,pio1
SetPin gp16,pio1
SetPin gp17,pio1
'
o1=Pio(next line 1) 'note the start of the first PIO instruction for program 1 - will be 0
Print "compiling pclk"
PIO assemble 1
.program pclk
.side set 1
.line next 'we can always use next unless we specifically want to place the code
   Pull block 'read in the loop counter which will define the timing of the second program
.wrap target
   Mov x,osr side 1 [4] 'we can use osr to keep this and re-use it
.label loop 'loop as specified by the counter
   Nop side 0 [4]
   Jmp x--,loop side 1 [4]
   IRQ 0 side 0 [3] 'set a flag for the second program
   IRQ 4 side 0  'set a flag for the third program
.wrap
.end program list
' create the setting to initialise program 1
p1=Pio(pinctrl 1,,,,gp4)
f1=252000000
e1=Pio(execctrl GP0,Pio(.wrap target),Pio(.wrap))
s1=Pio(shiftctrl 0,0,0,0,0,0)
'
o2=Pio(next line) 'note the start of the second pio program
Print "compiling hsync @ line: ";o2
PIO assemble 1
 .program hsync
 .side set 1
 .line next 'this will pick up o2 automatically
   Pull block side 1 'get the length of the front port
   Mov y,osr side 1 'store the length of front porch in y
   Pull block side 1 'get the length of the sync pulse into osr
 .wrap target
   Mov x,osr side 1 'get the length of the synch
   Wait 1 irq 4 side 1 'this instruction waits for irq 4 to be set and automatically clears it
   ' this happens at the start of each line
.label loop1
   Jmp x--,loop1 side 0
   Mov x,y side 1 'restore the front porch value
.label loop2
   Jmp x--,loop2 side 1
   Nop side 1
'we will trigger the data transfer here
 .wrap
.end program list

' create the setting to initialise program 2
p2=Pio(pinctrl 1,,,,gp16)
f2=252000000
e2=Pio(execctrl GP0,Pio(.wrap target),Pio(.wrap))
s2=Pio(shiftctrl 0,0,0,0,0,0)

o3=Pio(next line) 'note the start of the second pio program
Print "compiling vsync @ line: ";o3
PIO assemble 1
 .program vsync
 .side set 1
 .line next 'this will pick up o3 automatically
   Pull block side 1
   Mov y,osr side 1 'store the length of the front porch in y
   Pull block side 1
   Mov isr,osr side 1 'store the length of the rest of the line in isr
   Pull block side 1 'get the synch length
 .wrap target
   Mov x,osr side 1 'get the synch length
   Wait 1 irq 0 side 1 'this instruction waits a line end which we use to be beginning of frame
.label loop3
   Jmp x--,loop3 side 0 'synch loop
   Mov x,y side 0
.label loop4
   Jmp x--,loop4 side 1 'front porch loop
   Mov x,isr side 1
.label loop5
   Jmp x--,loop5 side 1 'rest of line loop
 .wrap
.end program list

' create the setting to initialise program 3
p3=Pio(pinctrl 1,,,,gp17)
f3=252000000
e3=Pio(execctrl GP0,Pio(.wrap target),Pio(.wrap))
s3=Pio(shiftctrl 0,0,0,0,0,0)

' initialise and start program 1 but note it will block on pull block
PIO init machine 1,0,f1,p1,e1,s1,o1,1,0,0
PIO start 1,0
' initialise and start program 2, it will block waiting for the irq
PIO init machine 1,1,f2,p2,e2,s2,o2,1,0,0
PIO start 1,1
PIO write 1,1,2,48*10-2,96*10-2
' initialise and start program 3, it will block waiting for the irq
PIO init machine 1,2,f3,p3,e3,s3,o3,1,0,0
PIO start 1,2
'rest of line value is reduced to make sure no overun but also don't miss a line
PIO write 1,2,3,8000*33-1,8000*490-1000,8000*2-1
' trigger the whole shebang by writing to program 1 fifo
PIO write 1,0,1,800
' loop, although the PIO will keep running anyway even if the program finishes
Do
Loop

Edited 2025-11-02 05:15 by matherp
 
darthvader
Regular Member

Joined: 31/01/2020
Location: France
Posts: 95
Posted: 10:30pm 01 Nov 2025
Copy link to clipboard 
Print this post

Hi Peter ,
Last time i get problems with VGA , it was the front and back porch that was not as expected.
I'm not a PIO programmer , so , check this if it can help , who knows  

Cheers.
Theory is when we know everything but nothing work ...
Practice is when everything work but no one know why ;)
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10553
Posted: 08:18am 02 Nov 2025
Copy link to clipboard 
Print this post

Solved. Had to clear the accumulated irq before waiting for it in the VSYNC loop. Next step to try and get data in and out
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10553
Posted: 11:17am 02 Nov 2025
Copy link to clipboard 
Print this post

If anyone is interested. Here is the fixed version. I found a couple of commands that weren't implemented in the PIO assemble IRQ NEXT and IRQ PREV. These allow a state machine on one PIO to communicate with a state machine on another PIO. I need these as I have run out of commands on PIO 1 so the data output will have to be on PIO 2. These will be in RC10. I'm using IRQ 3 to tell the data output that it can start a new line and IRQ 4 to synchronise the start of frame.
You can run the program as-is on a standard PicoMite and if you connect PIN 17 to VSYNC on a VGA monitor and pin 16 to Hsync you will find the monitor locks up happily at 640x480 @ 60Hz. The 25.2MHz pixel clock is on GP4.
Option explicit
Option default integer
Dim o
' set pins that will be used as outputs
SetPin gp4,pio1
SetPin gp16,pio1
SetPin gp17,pio1
Const cyclesperpixel=10
Const hwholeline=800
Const hvisible=640*cyclesperpixel
Const hsync=96*cyclesperpixel
Const hfrontporch=16*cyclesperpixel
Const hbackporch=48*cyclesperpixel
Const vwholeframe=525*hwholeline*cyclesperpixel
Const vvisible=480*hwholeline*cyclesperpixel
Const vsync=2*hwholeline*cyclesperpixel
Const vfrontporch=10*hwholeline*cyclesperpixel
Const vbackporch=33*hwholeline*cyclesperpixel
Const clock=Val(MM.Info(cpuspeed))

'
o=Pio(next line 1) 'note the start of the first PIO instruction for program 1 - will be 0
Print "compiling pclk"
PIO assemble 1
.program pclk
.side set 1
.line next 'we can always use next unless we specifically want to place the code
   Pull block 'read in the loop counter which will define the timing of the second program
.wrap target
   Mov x,osr side 1 [4] 'we can use osr to keep this and re-use it
.label loop 'loop as specified by the counter
   Nop side 0 [4]
   Jmp x--,loop side 1 [4]
   IRQ 0 side 0  'set a flag for the second program
   IRQ 1 side 0 [3] 'set a flag for the third program
.wrap
.end program list
'PIO CONFIGURE pio, sm,clock [,startaddress][,sidesetbase] [,sidesetno][,sidesetout][,setbase] [,setno] [,setout]
'[,outbase] [,outno] [,outout][,inbase][,jmppin] [,wraptarget] [,wrap][,sideenable] [,sidepindir][,pushthreshold]
'[,pullthreshold] [,autopush][,autopull] [,inshiftdir][,outshiftdir][,joinrxfifo] [,jointxfifo][,joinrxfifoget] [,joinrxfifoput]
' create the setting to initialise program 1
PIO CONFIGURE 1,0,clock,o,gp4,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)
' create the setting to initialise program 1
'PIO init machine 1,0,f1,p1,e1,s1,o1,1,0,0
'
o=Pio(next line) 'note the start of the second pio program
Print "compiling hsync @ line: ";o
PIO assemble 1
 .program hsync
 .side set 1
 .line next 'this will pick up o2 automatically
   Pull block side 1 'get the length of the sync
   Mov y,osr side 1 'save the length of the sync pulse into y
   Pull block side 1' get the length of the visible data + front porch in osr
 .wrap target
   Mov x,osr side 1 'get the length of the active+frontporch
   Wait 1 irq 1 side 1 'this instruction waits for irq 1 to be set by the pixel clock and automatically clears it
   IRQ NEXT 3 side 1
   ' this happens at the start of each line
.label loop1
   Jmp x--,loop1 side 1
   Mov x,y side 1 'restore the sync value
.label loop2
   Jmp x--,loop2 side 0
   Nop side 1
'we don't need to consider the back porch as we don't trigger again until the start of a new line
 .wrap
.end program list

' create the setting to initialise program 2
PIO CONFIGURE 1,1,clock,o,gp16,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)

o=Pio(next line) 'note the start of the second pio program
Print "compiling vsync @ line: ";o
PIO assemble 1
 .program vsync
 .side set 1
 .line next 'this will pick up o3 automatically
   Pull block side 1
   Mov y,osr side 1 'store the length of the back porch in y
   Pull block side 1
   Mov isr,osr side 1 'store the length of sync in isr
   Pull block side 1 'get the synch length
 .wrap target
   Mov x,osr side 1 'get the length of the active + front porch
   Wait 1 irq 0 side 1 [1]'this instruction waits a line end which we use to be beginning of frame
   IRQ NEXT 4 side 1
.label loop3
   Jmp x--,loop3 side 1 'main data loop
   Mov x,isr side 1 'restore the sync value
.label loop4
   Jmp x--,loop4 side 0 'sync loop
   Mov x,y side 0
.label loop5
   Jmp x--,loop5 side 1 'rest of line loop
   IRQ CLEAR 0 side 1
 .wrap
.end program list

' create the setting to initialise program 3
PIO CONFIGURE 1,2,clock,o,gp17,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)

' initialise and start program 1 but note it will block on pull block
PIO start 1,0
' initialise and start program 2, it will block waiting for the irq
PIO start 1,1
PIO write 1,1,2,hsync,hvisible+hfrontporch-2
' initialise and start program 3, it will block waiting for the irq
PIO start 1,2
'rest of line value is reduced to make sure no overun but also don't miss a line
PIO write 1,2,3,vbackporch-2,vsync,vvisible+vfrontporch-2
' trigger the whole shebang by writing to program 1 fifo
PIO write 1,0,1,hwholeline
' loop, although the PIO will keep running anyway even if the program finishes
Do
Loop
 
PhenixRising
Guru

Joined: 07/11/2023
Location: United Kingdom
Posts: 1599
Posted: 04:43pm 02 Nov 2025
Copy link to clipboard 
Print this post

  Quote  These allow a state machine on one PIO to communicate with a state machine on another PIO


So, for example, The count value of a quad decoder on PIO0 can be shared with the other PIOs?
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10553
Posted: 05:04pm 02 Nov 2025
Copy link to clipboard 
Print this post

No: you just have 8 flags that can be shared
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10553
Posted: 06:20pm 02 Nov 2025
Copy link to clipboard 
Print this post

Here is the final program - VGA running from the standard PicoMite firmware. You will have to wait for RC10 to try it. With RC10 you can load the standard non-VGA firmware onto a Pico RP2350 installed on a VGA board with the usual RGB121 connections. Set the CPU speed to 252MHz then load and run the attached and you will get a very simple 640x480 demo entirely coded in MMbasic. It won't run on an RP2040 as there isn't enough memory for the framebuffer.
Option explicit
Option default integer
Dim o
' set pins that will be used as outputs
SetPin gp4,pio0 'clock pulse
SetPin gp16,pio0 'hsync
SetPin gp17,pio0 'vsync
SetPin gp7,pio1 'DE
SetPin gp18,pio1 'Blue
SetPin gp19,pio1 'low green
SetPin gp20,pio1 'high green
SetPin gp21,pio1 'Red
const hsync=96
const hfrontporch=16
Const cyclesperpixel=10
Const hvisible=640
Const hbackporch=48
const vsync=2
const vbackporch=33
const vfrontporch=10
Const vvisible=480
const pixelsperinteger=16
const pixelsperword=8
const wordstotranster=hvisible*vvisible\pixelsperword
Const vlines=vsync+vbackporch+vvisible+vfrontporch
Const hwholeline=hsync+hfrontporch+hvisible+hbackporch
Const hvisibleclock=hvisible*cyclesperpixel
Const hsyncclock=hsync*cyclesperpixel
Const hfrontporchclock=hfrontporch*cyclesperpixel
Const hbackporchclock=hbackporch*cyclesperpixel
Const vwholeframe=vlines*hwholeline*cyclesperpixel
Const vvisibleclock=vvisible*hwholeline*cyclesperpixel
Const vsyncclock=vsync*hwholeline*cyclesperpixel
Const vfrontporchclock=vfrontporch*hwholeline*cyclesperpixel
Const vbackporchclock=vbackporch*hwholeline*cyclesperpixel
Const clock=Val(MM.Info(cpuspeed))
Dim i,b(hvisible*vvisible\pixelsperinteger-1)
Dim badd=Peek(varaddr b())
'
o=Pio(next line 1) 'note the start of the first PIO instruction for program 1 - will be 0
Print "compiling pclk"
PIO assemble 0
.program pclk
.side set 1
.line next 'we can always use next unless we specifically want to place the code
  Pull block 'read in the loop counter which will define the timing of the second program
.wrap target
  Mov x,osr side 1 [4] 'we can use osr to keep this and re-use it
.label loop 'loop as specified by the counter
  Nop side 0 [4]
  Jmp x--,loop side 1 [4]
  IRQ 0 side 0  'set a flag for the second program
  IRQ PREV 2 side 0
  IRQ 1 side 0 [2]  'set a flag for the third program
.wrap
.end program list
'PIO CONFIGURE pio, sm,clock [,startaddress][,sidesetbase] [,sidesetno][,sidesetout][,setbase] [,setno] [,setout]
'[,outbase] [,outno] [,outout][,inbase][,jmppin] [,wraptarget] [,wrap][,sideenable] [,sidepindir][,pushthreshold]
'[,pullthreshold] [,autopush][,autopull] [,inshiftdir][,outshiftdir][,joinrxfifo] [,jointxfifo][,joinrxfifoget] [,joinrxfifoput]
' create the setting to initialise program 1
PIO CONFIGURE 0,0,clock,o,gp4,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)
' create the setting to initialise program 1
'PIO init machine 1,0,f1,p1,e1,s1,o1,1,0,0
'
o=Pio(next line) 'note the start of the second pio program
Print "compiling hsyncclock @ line: ";o
PIO assemble 0
.program hsyncclock
.side set 1
.line next 'this will pick up o2 automatically
  Pull block side 1 'get the length of the sync
  Mov y,osr side 1 'save the length of the sync pulse into y
  Pull block side 1' get the length of the visible data + front porch in osr
.wrap target
  Mov x,osr side 1 'get the length of the active+frontporch
  Wait 1 irq 1 side 1 'this instruction waits for irq 1 to be set by the pixel clock and automatically clears it
  ' this happens at the start of each line
.label loop1
  Jmp x--,loop1 side 1
  Mov x,y side 1 'restore the sync value
.label loop2
  Jmp x--,loop2 side 0
  Nop side 1
'we don't need to consider the back porch as we don't trigger again until the start of a new line
.wrap
.end program list

' create the setting to initialise program 2
PIO CONFIGURE 0,1,clock,o,gp16,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)

o=Pio(next line) 'note the start of the second pio program
Print "compiling vsyncclock @ line: ";o
PIO assemble 0
.program vsyncclock
.side set 1
.line next 'this will pick up o3 automatically
  Pull block side 1
  Mov y,osr side 1 'store the length of the back porch in y
  Pull block side 1
  Mov isr,osr side 1 'store the length of sync in isr
  Pull block side 1 'get the synch length
.wrap target
  Mov x,osr side 1 'get the length of the active + front porch
  Wait 1 irq 0 side 1 [1]'this instruction waits a line end which we use to be beginning of frame
  IRQ PREV 3 side 1
.label loop3
  Jmp x--,loop3 side 1 'main data loop
  Mov x,isr side 1 'restore the sync value
.label loop4
  Jmp x--,loop4 side 0 'sync loop
  Mov x,y side 0
.label loop5
  Jmp x--,loop5 side 1 'rest of line loop
  IRQ CLEAR 0 side 1
.wrap
.end program list

' create the setting to initialise program 3
PIO CONFIGURE 0,2,clock,o,gp17,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)
o=Pio(next line 0) 'note the start of the first PIO instruction for program 1 - will be 0

Print "compiling datamaster @ line: ";0
PIO assemble 1
.program datamaster
.line 0
Pull block side 0
Mov y,osr 'store the count of active lines
.wrap target 'main loop
Mov x,y 'get the active line count
Wait 1 irq next 3'wait for the start of frame
.label loop6
Wait 1 irq next 2 'wait for the next line to start
IRQ  5 'trigger the output
Jmp x--,loop6
.wrap
.end program list
PIO CONFIGURE 1,0,clock,o,,,,,,,,,,,,Pio(.wrap target),Pio(.wrap)
o= Pio(next line)

Print "compiling data @ line: ";o
PIO assemble 1
.program linedata
.line next
.side set 1
Pull block side 0
Mov y,osr side 0 'store active pixel count divided by the number of pixels per word
.wrap target
Mov x,y side 0'get the active pixel count divided by the number of pixels per word
Wait 1 irq 5 side 0 [9]'wait for next valid line to start
.label loop7
Pull side 1 'get the next data - don't block
Out pins,4 side 1 [9]
Out pins,4 side 1 [9]
Out pins,4 side 1 [9]
Out pins,4 side 1 [9]
Out pins,4 side 1 [9]
Out pins,4 side 1 [9]
Out pins,4 side 1 [9]
Out pins,4 side 1 [9]
Jmp x--,loop7 side 1
Set pins, 0 side 0 [2]
.wrap
.end program list
' create the setting to initialise program 3
PIO CONFIGURE 1,1,clock,o,GP7,1,1,GP18,4,1,GP18,4,1,,,Pio(.wrap target),Pio(.wrap)
PIO SYNC 'this is a new command that syncs the clocks of the PIO. It is needed for IRQ to work between PIO

PIO start 1,0
PIO write 1,0,1,vvisible-1
PIO start 1,1
PIO write 1,1,1,hvisible/pixelsperword-1
' initialise and start program 1 but note it will block on pull block
PIO start 0,0
' initialise and start program 2, it will block waiting for the irq
PIO start 0,1
PIO write 0,1,2,hsyncclock-1,hvisibleclock+hfrontporchclock-1
' initialise and start program 3, it will block waiting for the irq
PIO start 0,2
'rest of line value is reduced to make sure no overun but also don't miss a line
PIO write 0,2,3,vbackporchclock-1,vsyncclock-1,vvisibleclock+vfrontporchclock-1
' loop, although the PIO will keep running anyway even if the program finishes
PIO dma tx 1,1,wordstotranster,b(),redo
' trigger the whole shebang by writing to program 1 fifo
PIO write 0,0,1,hwholeline
PIO sync
Do
mycls 1
Pause 1000
mycls 8
Pause 1000
rectangle 100,100,199,199,&H6
rectangle 0,0,hvisible-1,0,&HF
rectangle 0,vvisible-1,hvisible-1,vvisible-1,&HF
rectangle 0,0,0,vvisible-1,&HF
rectangle hvisible-1,0,hvisible-1,vvisible-1,&HF
Pause 1000
Loop

Sub redo
PIO dma tx 1,1,wordstotranster,b(),redo
End Sub

Sub mycls col
Local c=col
c=c+(c<<4)
c=c+(c<<8)
c=c+(c<<16)
Memory set word badd,c,wordstotranster
End Sub

Sub rectangle x1,y1,x2,y2,c
Local x,y,x1p,x2p,p,q,t,bc=(c<<4) +c
  For y=y1 To y2
      x1p = x1
      x2p = x2
      p = badd + y * (hvisible >> 1) + (x1 >> 1)
      If x1 Mod 2 Then
           t=(PEEK(BYTE p) And &HF) + (c<<4)
           Poke byte p,t
           Inc p
           Inc x1p
      EndIf

      If (x2 Mod 2) = 0 Then
          q = badd + y * (hvisible >> 1) + (x2 >> 1)
          t=(PEEK(BYTE q) And &Hf0) + c
          Poke byte q,t
          Inc x2p,-1
      EndIf
      If x2p>x1p Then Memory set byte p,bc,(x2p-x1p+1)/2
    Next
End Sub

Edited 2025-11-03 22:08 by matherp
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10553
Posted: 10:45pm 03 Nov 2025
Copy link to clipboard 
Print this post

Now working in RGB222 @ 640x480
Is this the next VGA variant?
Option explicit
Option default integer
Dim o
' set pins that will be used as outputs
SetPin gp4,pio0 'clock pulse
SetPin gp16,pio0 'hsync
SetPin gp17,pio0 'vsync
SetPin gp7,pio1 'DE
SetPin gp18,pio1 'low Blue
SetPin gp19,pio1 'high blue
SetPin gp20,pio1 'low green
SetPin gp21,pio1 'high green
SetPin gp22,pio1 'low red
SetPin gp23,pio1 'high red
Const hsync=96
Const hfrontporch=16
Const cyclesperpixel=10
Const hvisible=640
Const hbackporch=48
Const vsync=2
Const vbackporch=33
Const vfrontporch=10
Const vvisible=480
Const pixelsperword=5
Const wordsperline=hvisible\pixelsperword
Const wordstotransfer=wordsperline*vvisible
Const vlines=vsync+vbackporch+vvisible+vfrontporch
Const hwholeline=hsync+hfrontporch+hvisible+hbackporch
Const hvisibleclock=hvisible*cyclesperpixel
Const hsyncclock=hsync*cyclesperpixel
Const hfrontporchclock=hfrontporch*cyclesperpixel
Const hbackporchclock=hbackporch*cyclesperpixel
Const vwholeframe=vlines*hwholeline*cyclesperpixel
Const vvisibleclock=vvisible*hwholeline*cyclesperpixel
Const vsyncclock=vsync*hwholeline*cyclesperpixel
Const vfrontporchclock=vfrontporch*hwholeline*cyclesperpixel
Const vbackporchclock=vbackporch*hwholeline*cyclesperpixel
Const clock=Val(MM.Info(cpuspeed))
Const HRes=hvisible
Const VRes=vvisible
Dim i,b(wordstotransfer\2-1)
Dim mask_table(4)=(&H3F,&HFC0,&H3F000,&HFC0000,&H3F000000)
Dim currentfont=MM.Info(font)
Dim fontadd=MM.Info(font address currentfont)
Dim fontdetail=Peek(word fontadd)
Dim fontbytes=(fontdetail And &HFF) * ((fontdetail>>8) And &HFF)/8
Dim asciioffset=((fontdetail>>16) And &HFF)
Inc fontadd,4 'pont to the actual characters


Dim badd=Peek(varaddr b())
'
o=Pio(next line 1) 'note the start of the first PIO instruction for program 1 - will be 0
Print "compiling pclk"
PIO assemble 0
.program pclk
.side set 1
.line next 'we can always use next unless we specifically want to place the code
  Pull block 'read in the loop counter which will define the timing of the second program
.wrap target
  Mov x,osr side 1 [4] 'we can use osr to keep this and re-use it
.label loop 'loop as specified by the counter
  Nop side 0 [4]
  Jmp x--,loop side 1 [4]
  IRQ 0 side 0  'set a flag for the second program
  IRQ PREV 2 side 0
  IRQ 1 side 0 [2]  'set a flag for the third program
.wrap
.end program list
'PIO CONFIGURE pio, sm,clock [,startaddress][,sidesetbase] [,sidesetno][,sidesetout][,setbase] [,setno] [,setout]
'[,outbase] [,outno] [,outout][,inbase][,jmppin] [,wraptarget] [,wrap][,sideenable] [,sidepindir][,pushthreshold]
'[,pullthreshold] [,autopush][,autopull] [,inshiftdir][,outshiftdir][,joinrxfifo] [,jointxfifo][,joinrxfifoget] [,joinrxfifoput]
' create the setting to initialise program 1
PIO CONFIGURE 0,0,clock,o,gp4,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)
' create the setting to initialise program 1
'PIO init machine 1,0,f1,p1,e1,s1,o1,1,0,0
'
o=Pio(next line) 'note the start of the second pio program
Print "compiling hsyncclock @ line: ";o
PIO assemble 0
.program hsyncclock
.side set 1
.line next 'this will pick up o2 automatically
  Pull block side 1 'get the length of the sync
  Mov y,osr side 1 'save the length of the sync pulse into y
  Pull block side 1' get the length of the visible data + front porch in osr
.wrap target
  Mov x,osr side 1 'get the length of the active+frontporch
  Wait 1 irq 1 side 1 'this instruction waits for irq 1 to be set by the pixel clock and automatically clears it
  ' this happens at the start of each line
.label loop1
  Jmp x--,loop1 side 1
  Mov x,y side 1 'restore the sync value
.label loop2
  Jmp x--,loop2 side 0
  Nop side 1
'we don't need to consider the back porch as we don't trigger again until the start of a new line
.wrap
.end program list

' create the setting to initialise program 2
PIO CONFIGURE 0,1,clock,o,gp16,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)

o=Pio(next line) 'note the start of the second pio program
Print "compiling vsyncclock @ line: ";o
PIO assemble 0
.program vsyncclock
.side set 1
.line next 'this will pick up o3 automatically
  Pull block side 1
  Mov y,osr side 1 'store the length of the back porch in y
  Pull block side 1
  Mov isr,osr side 1 'store the length of sync in isr
  Pull block side 1 'get the synch length
.wrap target
  Mov x,osr side 1 'get the length of the active + front porch
  Wait 1 irq 0 side 1 [1]'this instruction waits a line end which we use to be beginning of frame
  IRQ PREV 3 side 1
.label loop3
  Jmp x--,loop3 side 1 'main data loop
  Mov x,isr side 1 'restore the sync value
.label loop4
  Jmp x--,loop4 side 0 'sync loop
  Mov x,y side 0
.label loop5
  Jmp x--,loop5 side 1 'rest of line loop
  IRQ CLEAR 0 side 1
.wrap
.end program list

' create the setting to initialise program 3
PIO CONFIGURE 0,2,clock,o,gp17,1,1,,,,,,,,,Pio(.wrap target),Pio(.wrap)
o=Pio(next line 0) 'note the start of the first PIO instruction for program 1 - will be 0

Print "compiling datamaster @ line: ";0
PIO assemble 1
.program datamaster
.line 0
Pull block side 0
Mov y,osr 'store the count of active lines
.wrap target 'main loop
Mov x,y 'get the active line count
Wait 1 irq next 3'wait for the start of frame
.label loop6
Wait 1 irq next 2 'wait for the next line to start
IRQ  5 'trigger the output
Jmp x--,loop6
.wrap
.end program list
PIO CONFIGURE 1,0,clock,o,,,,,,,,,,,,Pio(.wrap target),Pio(.wrap)
o= Pio(next line)

Print "compiling data @ line: ";o
PIO assemble 1
.program linedata
.line next
.side set 1
Pull block side 0
Mov y,osr side 0 'store active pixel count divided by the number of pixels per word
.wrap target
Mov x,y side 0'get the active pixel count divided by the number of pixels per word
Wait 1 irq 5 side 0 [9]'wait for next valid line to start
.label loop7
Pull side 1 'get the next data - don't block
Out pins,6 side 1 [9]
Out pins,6 side 1 [9]
Out pins,6 side 1 [9]
Out pins,6 side 1 [9]
Out pins,6 side 1 [7]
Out NULL,2
Jmp x--,loop7 side 1
Mov pins, NULL side 0 [2]
.wrap
.end program list
' create the setting to initialise program 3
PIO CONFIGURE 1,1,clock,o,GP7,1,1,,,,GP18,6,1,,,Pio(.wrap target),Pio(.wrap)
PIO SYNC 'this is a new command that syncs the clocks of the PIO. It is needed for IRQ to work between PIO

PIO start 1,0
PIO write 1,0,1,vvisible-1
PIO start 1,1
PIO write 1,1,1,hvisible\pixelsperword-1
' initialise and start program 1 but note it will block on pull block
PIO start 0,0
' initialise and start program 2, it will block waiting for the irq
PIO start 0,1
PIO write 0,1,2,hsyncclock-1,hvisibleclock+hfrontporchclock-1
' initialise and start program 3, it will block waiting for the irq
PIO start 0,2
'rest of line value is reduced to make sure no overun but also don't miss a line
PIO write 0,2,3,vbackporchclock-1,vsyncclock-1,vvisibleclock+vfrontporchclock-1
' loop, although the PIO will keep running anyway even if the program finishes
PIO dma tx 1,1,wordstotransfer,b(),redo
' trigger the whole shebang by writing to program 1 fifo
PIO write 0,0,1,hwholeline
PIO sync
Do
mycls &H3C
Pause 1000
mycls &H3
Pause 1000
rectangle 100,100,199,199,&H6
rectangle 0,0,hvisible-1,0,&H3F
rectangle 0,vvisible-1,hvisible-1,vvisible-1,&H3F
rectangle 0,0,0,vvisible-1,&H3F
rectangle hvisible-1,0,hvisible-1,vvisible-1,&H3F
printstring "Hello",300,300,3,&H3F,-1
Pause 1000

Loop
Sub printchar s$,x,y,scale,fc,bc
 Local address=(Asc(s$)-asciioffset)*fontbytes+fontadd
 drawbitmap x,y,MM.FONTWIDTH,MM.FONTHEIGHT,scale,fc,bc,address
End Sub

Sub printstring s$,x,y,scale,fc,bc
Local currentx=x,currenty=y
Local j
For j=1 To Len(s$)
printchar Mid$(s$,j,1),currentx,currenty,scale,fc,bc
Inc currentx,MM.FONTWIDTH*scale
Next
End Sub

Sub redo
PIO dma tx 1,1,wordstotransfer,b(),redo
End Sub

Sub mycls col
Local c=col
c=c+(col<<6)
c=c+(col<<12)
c=c+(col<<18)
c=c+(col<<24)
Memory set word badd,c,wordstotransfer
End Sub


Sub rectangle x1,y1,x2,y2,col
Local x,y,p,t,wo,sw,ew,sp,ep,mask,w,i,count
Local c=col
c=c+(col<<6)
c=c+(col<<12)
c=c+(col<<18)
c=c+(col<<24)

' Pre-calculate common masks (major speedup!)

For y=y1 To y2
   wo=y*wordsperline
   sw=x1\pixelsperword
   sp=x1 Mod pixelsperword
   ew=x2\pixelsperword
   ep=x2 Mod pixelsperword

   If sw=ew Then
     ' Single word case
     mask=0
     For i=sp To ep
       mask=mask Or mask_table(i)
     Next
     p=badd+(wo+sw)*4
     t=Peek(word p)
     t=t And INV mask
     t=t Or (c And mask)
     Poke word p,t
   Else
     ' Handle first partial word
     If sp<>0 Then
       mask=0
       For i=sp To 4
         mask=mask Or mask_table(i)
       Next
       p=badd+(wo+sw)*4
       t=Peek(word p)
       t=t And INV mask
       t=t Or (c And mask)
       Poke word p,t
       sw=sw+1
     EndIf

     ' Handle full words in the middle - use MEMORY SET WORD
     count=ew-sw
     If count>0 Then
       p=badd+(wo+sw)*4
       Memory SET WORD p,c,count
     EndIf

     ' Handle last partial word
     If ep<>4 Then
       mask=0
       For i=0 To ep
         mask=mask Or mask_table(i)
       Next
       p=badd+(wo+ew)*4
       t=Peek(word p)
       t=t And INV mask
       t=t Or (c And mask)
       Poke word p,t
     Else
       ' End pixel is exactly at word boundary (pixel 4)
       p=badd+(wo+ew)*4
       Poke word p,c
     EndIf
   EndIf
 Next
End Sub


Sub DrawBitmap x1,y1,width,height,scale,fc,bc,bitmap
Local f,b,i,j,k,x,y,final_width,final_height
Local draw_x_start,draw_y_start,draw_x_end,draw_y_end
Local src_y_start,src_x_start,draw_background
Local bit_index,byte_index,bit_position,pixel_on
Local x_start,x_end
Local p,wo,sw,sp,mask,t,px
Local c_word,mask_table(4)

' Pre-calculate pixel masks
mask_table(0)=&H3F
mask_table(1)=&HFC0
mask_table(2)=&H3F000
mask_table(3)=&HFC0000
mask_table(4)=&H3F000000

' Convert 6-bit colors to full word (5 pixels)
f=fc And &H3F
f=f Or (f<<6) Or (f<<12) Or (f<<18) Or (f<<24)

If bc>=0 Then
  b=bc And &H3F
  b=b Or (b<<6) Or (b<<12) Or (b<<18) Or (b<<24)
  draw_background=1
Else
  draw_background=0
EndIf

' Calculate final dimensions
final_width=width*scale
final_height=height*scale

' Early exit if completely off-screen
If x1>=HRes Or y1>=VRes Or x1+final_width<=0 Or y1+final_height<=0 Then Exit Sub

' Calculate clipping bounds
If x1<0 Then draw_x_start=0 Else draw_x_start=x1
If y1<0 Then draw_y_start=0 Else draw_y_start=y1
If x1+final_width>HRes Then draw_x_end=HRes Else draw_x_end=x1+final_width
If y1+final_height>VRes Then draw_y_end=VRes Else draw_y_end=y1+final_height

' Calculate starting bitmap position (for clipping)
If y1<0 Then src_y_start=(-y1)\scale Else src_y_start=0
If x1<0 Then src_x_start=(-x1)\scale Else src_x_start=0

' Optimize for scale=1 (common case)
If scale=1 Then
  For i=src_y_start To height-1
    y=y1+i
    If y>=VRes Then Exit For
    If y<0 Then Continue For

    wo=y*wordsperline

    For k=src_x_start To width-1
      x=x1+k
      If x>=HRes Then Exit For
      If x<0 Then Continue For

      ' Get bit from bitmap
      bit_index=i*width+k
      byte_index=bit_index>>3
      bit_position=7-(bit_index And 7)
      pixel_on=(PEEK(BYTE bitmap+byte_index)>>bit_position) And 1

      ' Calculate word and pixel position
      sw=x\5
      sp=x Mod 5
      p=badd+(wo+sw)*4

      If pixel_on Then
        ' Draw foreground pixel
        mask=mask_table(sp)
        t=Peek(word p)
        t=t And INV mask
        t=t Or (f And mask)
        Poke word p,t
      ElseIf draw_background Then
        ' Draw background pixel
        mask=mask_table(sp)
        t=Peek(word p)
        t=t And INV mask
        t=t Or (b And mask)
        Poke word p,t
      EndIf
    Next k
  Next i
Else
  ' Scaled version
  For i=src_y_start To height-1
    ' Process scale lines for this source row
    For j=0 To scale-1
      y=y1+i*scale+j
      If y>=VRes Then Exit For
      If y<0 Then Continue For

      wo=y*wordsperline

      For k=src_x_start To width-1
        ' Get bit from bitmap
        bit_index=i*width+k
        byte_index=bit_index>>3
        bit_position=7-(bit_index And 7)
        pixel_on=(PEEK(BYTE bitmap+byte_index)>>bit_position) And 1

        ' Determine color to draw
        If pixel_on Then
          c_word=f
        ElseIf draw_background Then
          c_word=b
        Else
          Continue For ' Skip transparent pixels
        EndIf

        ' Draw scale pixels horizontally
        x_start=x1+k*scale
        x_end=x_start+scale

        ' Clip horizontal span
        If x_start<0 Then x_start=0
        If x_end>HRes Then x_end=HRes

        If x_start<x_end Then
          ' Draw the horizontal run of pixels
          For px=x_start To x_end-1
            sw=px\5
            sp=px Mod 5
            p=badd+(wo+sw)*4
            mask=mask_table(sp)
            t=Peek(word p)
            t=t And INV mask
            t=t Or (c_word And mask)
            Poke word p,t
          Next px
        EndIf
      Next k
    Next j
  Next i
EndIf
End Sub

Edited 2025-11-04 08:46 by matherp
 
Volhout
Guru

Joined: 05/03/2018
Location: Netherlands
Posts: 5413
Posted: 07:35am 04 Nov 2025
Copy link to clipboard 
Print this post

Hi Peter,

I could not find a "spare" pico2 in my parts stock, the pico2 I have are soldered onto a board (GameMite and HDMI board).
I am currently wiring up the GameMite to show VGA video over the keyboard pins (the GameMite does not have successive range of 6 pins on GPIO free, but as long as you do not press any keyboard key you can use GP8..GP15).

The code is running, I see HSYNC and VSYNC, now searching for a DB15 connector.

Volhout
PicomiteVGA PETSCII ROBOTS
 
matherp
Guru

Joined: 11/12/2012
Location: United Kingdom
Posts: 10553
Posted: 08:30am 04 Nov 2025
Copy link to clipboard 
Print this post

For VGA use you can remove the sidesets that drive GP4 and GP7. These create the pixel clock and the DE signals needed for RGB TFT displays
 
Print this page


To reply to this topic, you need to log in.

The Back Shed's forum code is written, and hosted, in Australia.
© JAQ Software 2025