I was curious to see if it would be possible to show video on a small and cheap 1" GLCD screen.
I chose the SSD1331, since it was both cheap and had working routines in GC-basic where I could modify some coordinates to get the screen in sequential mode. This way addresses does not have to be sent for each pixel, speeding up the process. In sequential mode, 16bit 565 colourmode, I could achieve 30fps using a PIC18F27Q83 @64MHz. When I also fetch the pixels from picture in a Data Block (or Table), it slows down to 15fps for this demo. My initial goal was 10fps, so my idea of streaming to it via serial-USB seems doable (later on). If anyone has some ideas improving or making modifications, feel free to dig in.
The demo displays a photo that is rotating vertically one line per frame.
6144 pixels are fetched and sent to the GLCD for every frame. 15 frames per second.
Yes, DMA is there somewhere in the future. At this time it is beyond my capabilities.
Actually measuring the fps I was only getting about 13,5 fps.
After sleeping on it I tried spooling it over to an Array and is now getting 17fps!
The reason for choosing PIC18F27Q83 is that is has 13kB RAM, enough to hold a complete frame. I am thinking: Serial streaming (computer -> PIC) , store the whole frame in an array, then transfer it to the GLCD.
Copy the library write operations to your program. Then, in your new method. Set DC high and CS low once per frame, then stream all bytes via raw SPI writes, toggling CS/DC only at the end. Eliminates per-byte overhead.
Replace Do...Loop with burst code using SSPBUF and SSPSTAT. Add defines if needed. Verify pin states in glcd.h.
+50–100% (to 22–30 FPS)
Low
Loop Unrolling or Assembly Inline
Use Repeat...Until with manual increment instead of For...Next.
Replace For...Next with Repeat...Until Offset > 12287+192. Or hand-optimize ASM.
+5–15% (to 16–18 FPS, stacks with burst)
Medium
DMA Transfer (Advanced)
Use DMA channel for auto-transfer to SSPBUF on SPI TX.
Enable DMA in MCC; add init/start/wait code in loop.
+200–500% (to 40–80 FPS)
High
System Clock Tweaks
Ensure 64 MHz stable; switch to internal FRC+PLL if needed.
Add #CONFIG FEXTOSC = OFF, FRCPLL before #CHIP.
+0–5% (if underclocked)
Low
Untested DMA.
Do' Set display to data mode and select chipGLCD_DC=1GLCD_CS=0' Load first byte manually to kick off SPI master clockSSP1BUF=Arrayframe(0)' Set source addr and size (reload for each frame; assumes rolling wraps manually if needed)DMA1SSA=@Arrayframe+1' Start after first byteDMA1SSIZ=12479' Remaining bytes' Enable/start DMA (EN=1, SIRQEN=1)DMA1CON0=0xC0' Wait for DMA complete (poll dest count flag; clears on read)While!DMA1CON0bits.DMA1DCNTIF' Optional: Add timeout or NOPs for stabilityWendDMA1CON0bits.DMA1DCNTIF=0' Clear flag' End transferGLCD_CS=1PORTF.3=!PORTF.3' Frame toggleLoop
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Testing it I get a bunch of errors. Of the things I thought I understood is that it complains about "Do without matching Loop" and it thinks Wend is syntax error. I see the Loop at the end. If I remove the While-Wend part, then it stops complaining about missing Loop. About the missing declares I don't know what to do.
As said, I am not yet skilled enough to follow this. I will save the info for later explorations.
(At this point in time it would be way easier to use two chips to share chores via an 8-bit bus, but no where near as impressive as getting it done as quickly one chip).
Still, I wonder what the compiler means by:
Error: Variable SSP1BUF was not explicitly declared
SSP1BUF is a register, right? On another chip? Is that why the compiler says "was not explicitly declared"?
SSP1BUF is the generic name for the SSP buffer on channel 1. Each chip family may have a different real name. The GCBASIC libraries manage the mapping for you.
In the older chips the hardware SPI the output port was called SSP1BUF. In the modern chips is far more complex ( again this is in the GCBASIC library to make things easy to use ).
Below is a new method for you to try. I have lifted the FASTHWSPITRANSFER code and placed in-line within you solution ( this is the main body, the rest remains as-is).
This shows that SSP1BUF is actually called SPI1TXB and there are other registers needed to send data ( old chips do not have these other additional registers ).
This, if it works, will send the DATA block a lor faster. Wh ? No subs calls.
But, you need to set up the GLCD control lines ( CS and DC )
It wil be interesting to see the fps improvement!
Evan
// Program starts here:DIMiiasWordDIMARRAYframe(12288)asByteDimvalueasbyteDimSPI1RXBasByte=0Forii=0to12287ProgramRead(@BLOCK0+ii,value)Arrayframe(ii)=valuenextDIRPORTF.3OUTDimoffsetasword// Dim value as byteSendCommand_SSD1331(SSD1331_Set_Column_Address)'Columnaddrset//Adds borders for the sequential mode:SendCommand_SSD13310//GLCDX start coordinate (left)SendCommand_SSD133195//GLCDX stop coordinate (right)SendCommand_SSD1331(SSD1331_Set_Row_Address)'RowaddrsetSendCommand_SSD13310//GLCDY start coordinate (up)SendCommand_SSD133163//GLCDY stop coordinate (down)Do// At this point we need to select the chip and tell the glcd data is comingsetSSD1331_CSOFFsetSSD1331_DCONForOffset=0to12287+192'2x96pixels=onelineextratomakeitroll// ProgramRead (@BLOCK0 + Offset, value) '2 bytes = two loop rounds needed (each pixel is 16 bits);Source:hwspi.h(979)// FASTHWSPITRANSFER;Mastermodeonly;OnebytetransfercountSPI1TCNTL=1SPI1TXB=(Arrayframe(offset))waitwhileSPI1RXIF=SPI_RX_IN_PROGRESS;YouMUSTreadtheincomingbytetocompletetheSPIprocess.Donotremove!SPI1RXB=SPI1RXB// SendData_SSD1331 (Arrayframe(offset))nextPORTF.3=!PORTF.3'changesstateonceperframe// Deselect the GLCDsetSSD1331_CSONLoop
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
You have just come across a new feature to be added in the next release!!
Dim SPI1RXB as Byte = 0
In the new release you can DIMension a variable and initialise on one line! You can do this on all variables soon.
This enriches the GCBASIC language, and, it will make code more readable.
An idea by me, implemented by Angel!
Re the one byte. I do not have the datasheet to hand but the logic ( based on your result ) would be toggle the DC.
Do//AtthispointweneedtoselectthechipandtelltheglcddataiscomingsetSSD1331_CSOFFForOffset=0to12287+192'2x96 pixels = one line extra to make it roll // ProgramRead (@BLOCK0 + Offset, value) '2bytes=twolooproundsneeded(eachpixelis16bits)setSSD1331_DCON;Source:hwspi.h(979)//FASTHWSPITRANSFER;Mastermodeonly;OnebytetransfercountSPI1TCNTL=1SPI1TXB=(Arrayframe(offset))waitwhileSPI1RXIF=SPI_RX_IN_PROGRESS;YouMUSTreadtheincomingbytetocompletetheSPIprocess.Donotremove!SPI1RXB=SPI1RXBsetSSD1331_DCOFF//SendData_SSD1331(Arrayframe(offset))nextPORTF.3=!PORTF.3'changesstateonceperframe//DeselecttheGLCDsetSSD1331_CSONLoop
Last edit: Anobium 4 days ago
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
The screeen is now all black.
...
"DIMension a variable and initialise on one line!"
This is great! I have missed this in GCbasic.
My first thought seing you used it:"-Oh, so I can do this after all", but nah. -Well soon, then. -Looking forward to it.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
This may work. Latches the data. Is this does not work.
Then, this maybe a timing issue where the previous logic would work with a delay.
DimSPI1RXBasByte=0Do//AtthispointweneedtotelltheglcddataiscomingsetSSD1331_DCONForOffset=0to12287+192'2x96 pixels = one line extra to make it roll // ProgramRead (@BLOCK0 + Offset, value) '2bytes=twolooproundsneeded(eachpixelis16bits);Source:hwspi.h(979)//FASTHWSPITRANSFER;Mastermodeonly;OnebytetransfercountSPI1TCNTL=1setSSD1331_CSOFFSPI1TXB=(Arrayframe(offset))waitwhileSPI1RXIF=SPI_RX_IN_PROGRESSSPI1RXB=SPI1RXB//Latchthetransmission?setSSD1331_CSOn//SendData_SSD1331(Arrayframe(offset))nextPORTF.3=!PORTF.3'changesstateonceperframe//DeselecttheGLCDsetSSD1331_CSONLoop
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Still black.
If I put Wait 1 ms (or 10 ms) after SPI1RXB = SPI1RXB then I get a bright dot at X0Y0.
The same if I put wait 1 ms between all instructions in your code. One bright dot at X0Y0.
In all cases it updates the PORTF.3 around 30 times per s, so it does not get stuck.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
OK. This feels like a timing issue. I would put a protocol analyser to see what was happening on the wire.
DimSPI1RXBasByte=0DoForOffset=0to12287+192'2x96 pixels = one line extra to make it roll set SSD1331_CS OFF set SSD1331_DC ON ;Source: hwspi.h (979) // FASTHWSPITRANSFER ;Master mode only ;One byte transfer count SPI1TCNTL = 1 SPI1TXB = (Arrayframe(offset)) wait while SPI1RXIF = SPI_RX_IN_PROGRESS SPI1RXB = SPI1RXB // To prevent deselecting the GLCD before the GLCD has physically written the data byte. May need more or less NOPs NOP NOP NOP NOP set SSD1331_CS ON // SendData_SSD1331 (Arrayframe(offset)) next PORTF.3 = !PORTF.3 'changesstateonceperframe//DeselecttheGLCDsetSSD1331_CSONLoop
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Using my old analogue oscilloscope on DataOut PORTC.5:
I went back and ran your first test that I could compile. I didn't see it when I tested it then, but it flickers RF3 at 30fps, but then pauses off briefly every 1.5 seconds and then starts again. The data out pin is stuck high apart from a single downwards spike (that could be a byte) and then goes 0 when it pauses.
Then I took your last code "Black how odd". DO just stays high it seems. A few nop after SPI1RXB = SPI1RXB does not change anything.
Your last code DO just stays high. F.3 flickers at about the previous speed.
Ok, I got the white dot when inserting wait 1ms, but also one frame per 12s. So maybe the white dot is about something timing out. Sorry about that.
Running my last demo (the one with array) there is heavy traffic on DO.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
SendData_SSD1331 (Arrayframe(offset) calls SendData_SSD1331() which then calls FASTHWSPITRANSFER. All the new code is meant to do is to miss the call to SendData_SSD1331() to improve speed. The method SendData_SSD1331() sets the DC and CS then that calls FASTHWSPITRANSFER.
So, check do this work still? If it does then rebuild removing SendData_SSD1331 and replacing with the optimised code.
Do
For Offset = 0 to 12287+192 '2x96 pixels = one line extra to make it roll
SendData_SSD1331 (Arrayframe(offset))
next
PORTF.3 = !PORTF.3 'changes state once per frame
Loop
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Ok. I cannot, at this time, get this GLCD hooked up.
So, time to hack. Edit the SSD1331 library and change the called library to a macro, and, do the same in hwspi library for FASTSPITRANSFER. This will put both methods inline with no calls. So, this will increase code size but it should improve speed.
If this works then we have proven the approach. Best to revert the hacked library's.
Give this a go.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hm. I am sorry, not able to grasp how to do what you are suggesting.
But I found this. Putting some delays in there I could again get the white spot in the upper left corner. So there is some issue with timing also. I deliberately slowed it down to about 1-2 fps.
When doing that I can control that corner pixel by changing the first two bytes of the Data Block. 0xF8 0x00 becomes bright red, which is according to the 565 16bit format and another value in the lower byte changes the colour, so it seems to read and send the two first bytes correctly. Since there is no visible traffic on DO over time, it is not stuck at sending these two bytes over and over.
It appears that something is not incrementing. It just sends one pair of bytes and then does nothing. Weird.
Removing SPI1RXB = SPI1RXB does not seem to have any effect.
Ah, well. 17fps is ok and can maybe be improved later.
........
//waits like this:
Do
// At this point we need to tell the glcd data is coming
set SSD1331_DC ON
wait 10 us
For Offset = 0 to 12287+192 '2x96 pixels = one line extra to make it roll
// ProgramRead (@BLOCK0 + Offset, value) '2 bytes = two loop rounds needed (each pixel is 16 bits)
I was curious to see if it would be possible to show video on a small and cheap 1" GLCD screen.
I chose the SSD1331, since it was both cheap and had working routines in GC-basic where I could modify some coordinates to get the screen in sequential mode. This way addresses does not have to be sent for each pixel, speeding up the process. In sequential mode, 16bit 565 colourmode, I could achieve 30fps using a PIC18F27Q83 @64MHz. When I also fetch the pixels from picture in a Data Block (or Table), it slows down to 15fps for this demo. My initial goal was 10fps, so my idea of streaming to it via serial-USB seems doable (later on). If anyone has some ideas improving or making modifications, feel free to dig in.
The demo displays a photo that is rotating vertically one line per frame.
6144 pixels are fetched and sent to the GLCD for every frame. 15 frames per second.
Use DMA chip. That could improve operations.
The is really good work. Impressed.
Yes, DMA is there somewhere in the future. At this time it is beyond my capabilities.
Actually measuring the fps I was only getting about 13,5 fps.
After sleeping on it I tried spooling it over to an Array and is now getting 17fps!
The reason for choosing PIC18F27Q83 is that is has 13kB RAM, enough to hold a complete frame. I am thinking: Serial streaming (computer -> PIC) , store the whole frame in an array, then transfer it to the GLCD.
I was glad to find out that the array holds a complete frame of 12288 bytes!
(The help says it is limited to 10,000 bytes for 18F)
The max array is the max available RAM or 64k.
Untested DMA.
Testing it I get a bunch of errors. Of the things I thought I understood is that it complains about "Do without matching Loop" and it thinks Wend is syntax error. I see the Loop at the end. If I remove the While-Wend part, then it stops complaining about missing Loop. About the missing declares I don't know what to do.
Last edit: Roger Jönsson 4 days ago
My code was not tested. You should study examples and adapt from
C:\GCstudio\gcbasic\demos\DMA_Solutions\The best place to start would be
C:\GCstudio\gcbasic\demos\DMA_Solutions\18f27q43_send_bigtable_dma_to_serial.gcbthen adapt to SPI.The While-Wend. That should have been WAIT While ...
The real code that send SPI data is:
The real quick way send bytes via SPI could be be adapt that to send many bytes. The datasheet will reveal how to send multiple bytes.
But, you seem to happy having interesting coding going on there!
As said, I am not yet skilled enough to follow this. I will save the info for later explorations.
(At this point in time it would be way easier to use two chips to share chores via an 8-bit bus, but no where near as impressive as getting it done as quickly one chip).
Still, I wonder what the compiler means by:
Error: Variable SSP1BUF was not explicitly declared
SSP1BUF is a register, right? On another chip? Is that why the compiler says "was not explicitly declared"?
SSP1BUFis the generic name for the SSP buffer on channel 1. Each chip family may have a different real name. The GCBASIC libraries manage the mapping for you.In the older chips the hardware SPI the output port was called
SSP1BUF. In the modern chips is far more complex ( again this is in the GCBASIC library to make things easy to use ).Below is a new method for you to try. I have lifted the
FASTHWSPITRANSFERcode and placed in-line within you solution ( this is the main body, the rest remains as-is).This shows that
SSP1BUFis actually calledSPI1TXBand there are other registers needed to send data ( old chips do not have these other additional registers ).This, if it works, will send the DATA block a lor faster. Wh ? No subs calls.
But, you need to set up the GLCD control lines ( CS and DC )
It wil be interesting to see the fps improvement!
Evan
WOW!
We are down to one error, the compiler did not like; Dim SPI1RXB as Byte = 0:
Invalid variable type: BYTE = 0
The compiler didn't like
Dim SPI1RXB as Byte = 0
so I changed it to:
Dim SPI1RXB as Byte
SPI1RXB=0
It updates at 30fps, but only draws one pixel (X0 Y0).
Yes.
You have just come across a new feature to be added in the next release!!
Dim SPI1RXB as Byte = 0In the new release you can DIMension a variable and initialise on one line! You can do this on all variables soon.
This enriches the GCBASIC language, and, it will make code more readable.
An idea by me, implemented by Angel!
Re the one byte. I do not have the datasheet to hand but the logic ( based on your result ) would be toggle the DC.
Last edit: Anobium 4 days ago
The screeen is now all black.
...
"DIMension a variable and initialise on one line!"
This is great! I have missed this in GCbasic.
My first thought seing you used it:"-Oh, so I can do this after all", but nah. -Well soon, then. -Looking forward to it.
Re the next release.
You will be stunned by what is coming!
Black. How odd.
This may work. Latches the data. Is this does not work.
Then, this maybe a timing issue where the previous logic would work with a delay.
Still black.
If I put Wait 1 ms (or 10 ms) after SPI1RXB = SPI1RXB then I get a bright dot at X0Y0.
The same if I put wait 1 ms between all instructions in your code. One bright dot at X0Y0.
In all cases it updates the PORTF.3 around 30 times per s, so it does not get stuck.
OK. This feels like a timing issue. I would put a protocol analyser to see what was happening on the wire.
Using my old analogue oscilloscope on DataOut PORTC.5:
I went back and ran your first test that I could compile. I didn't see it when I tested it then, but it flickers RF3 at 30fps, but then pauses off briefly every 1.5 seconds and then starts again. The data out pin is stuck high apart from a single downwards spike (that could be a byte) and then goes 0 when it pauses.
Then I took your last code "Black how odd". DO just stays high it seems. A few nop after SPI1RXB = SPI1RXB does not change anything.
Your last code DO just stays high. F.3 flickers at about the previous speed.
Ok, I got the white dot when inserting wait 1ms, but also one frame per 12s. So maybe the white dot is about something timing out. Sorry about that.
Running my last demo (the one with array) there is heavy traffic on DO.
This is really odd.
What have I missed?
SendData_SSD1331 (Arrayframe(offset)callsSendData_SSD1331()which then callsFASTHWSPITRANSFER. All the new code is meant to do is to miss the call toSendData_SSD1331()to improve speed. The methodSendData_SSD1331()sets the DC and CS then that callsFASTHWSPITRANSFER.So, check do this work still? If it does then rebuild removing
SendData_SSD1331and replacing with the optimised code.SendData_SSD1331 (Arrayframe(offset)) -Works.
Replacing that line with one of the two later testcodes, then DO goes high and stays high.
F3 flickers as expected.
Ok. I cannot, at this time, get this GLCD hooked up.
So, time to hack. Edit the SSD1331 library and change the called library to a macro, and, do the same in hwspi library for FASTSPITRANSFER. This will put both methods inline with no calls. So, this will increase code size but it should improve speed.
If this works then we have proven the approach. Best to revert the hacked library's.
Give this a go.
Hm. I am sorry, not able to grasp how to do what you are suggesting.
But I found this. Putting some delays in there I could again get the white spot in the upper left corner. So there is some issue with timing also. I deliberately slowed it down to about 1-2 fps.
When doing that I can control that corner pixel by changing the first two bytes of the Data Block. 0xF8 0x00 becomes bright red, which is according to the 565 16bit format and another value in the lower byte changes the colour, so it seems to read and send the two first bytes correctly. Since there is no visible traffic on DO over time, it is not stuck at sending these two bytes over and over.
It appears that something is not incrementing. It just sends one pair of bytes and then does nothing. Weird.
Removing SPI1RXB = SPI1RXB does not seem to have any effect.
Ah, well. 17fps is ok and can maybe be improved later.
........
//waits like this:
Do
// At this point we need to tell the glcd data is coming
set SSD1331_DC ON
wait 10 us
For Offset = 0 to 12287+192 '2x96 pixels = one line extra to make it roll
// ProgramRead (@BLOCK0 + Offset, value) '2 bytes = two loop rounds needed (each pixel is 16 bits)
Loop
OK. Let me send you two adapted files. Tomorrow.