De0‐Nano - Maverick-Shark/retroGuru GitHub Wiki
Source: https://sites.google.com/site/fpgaandco/de0-nano-psp-screen

The PSP screen (Sharp LQ043) is a 4.3" true color TFT display. It is very cheap, easy to drive and I've long wanted to use it for something. So I hooked it up to my DE0-Nano board.
There are a few things about this display that make it not quite as straight forward to use as other alternatives. One is the backlight that requires about 20V to fully light up (7 LEDs in series). The other is the 2.5V logic level of the digital signals.
As far as I know the IO banks on the DE0-Nano board are hardwired to 3.3V and the datasheet suggests that the display can handle this voltage. So instead of a VCC of 2.5V I am driving the screen with 3.3V. So far it seems to work okay.
To be on the safe side would require an additional voltage regulator and resistors on all data lines.
Maybe next time.
That leaves the backlight which is driven by the tiny SOT-23 sized LT1932. It's a constant current boost driver that can drive up to 8 LEDs from 3V.
Here's the schematic and the resulting board:


A little care has to be taken to ensure a clean etch. Those traces are a little under 10mil.
By far the trickiest part is soldering the backlight connector onto the board. But with a little caution it's actually quite easy.
I did it the hard way with a small tipped soldering iron. When I ordered the parts I had anticipated some trouble with these connectors so I ordered 3 of them to be safe and I did in fact destroy one of them in the process.
Don't use super glue to hold it in place! The connector is a ridiculously delicate part mechanical part and you don't want the glue to get sucked into it. Solder paste is slightly sticky and its enough to fix the part in place long enough to solder in on.
If you are using a needle to maneuver the part around or hold it down while soldering be very careful where you place the needle. In relation to the little forks that later connect to the ribbon cable a needle is huge. They interlock with the black cover and are easily bent. In the second to last microscope pic below you can see the tip of a standard sewing needle I used to clean up some leftover solder beads.
What worked best for me was to cover the pins generously with solder paste, align the part and then just heat up the trace leading to the pins one by one, never actually touching the pins.
Make sure to clean the board thoroughly after you're done. Those little balls of solder that make up the paste are getting everywhere.
Don't forget to solder the exposed metal parts on the side of the connector. You'll need that added mechanical strength.
Ready for a test drive.
Note: There are exposed components on the underside of the FPC cable! Insulation has to be added between the PCB and the FPC. In this case I used two small Post-Its.
Also, the backlight cable needs to be twisted 180 degrees in this version of the board to match the polarity of the LEDs which doesn't show in this pic.
A few lines of Verilog are enough to generate a rudimentary test pattern.
Test patterns are easily generated but to get the most use out of my display I wanted to be able use a chunk of memory as a frame buffer so an MPU could actually draw text, graphs or display images. The data transfer from memory to the display should happen automatically and in the background. Otherwise, since the screen requires a 9 MHz pixel clock an MPU would be fairly busy with that task alone.
Fortunately, all of this can be plugged together into a NiosII with the standard Altera IP cores.
I used the SG DMA controller in combination with the Video Sync Generator to constantly copy contents from the SDRAM frame buffer to the screen. The DMA controller does this with almost no intervention from the cpu which, in this case, is free to calculate and draw a Mandelbrot fractal (see picture at the top).
More info about this process can be found in the tutorial NiosII LCD Driver.
I also recently managed to connect it to my Beaglebone running Android.
When I mentioned that this build is cheap I'd say it comes in around $40-45 depending on the stash of components you are already hording.
Component | Price | Description |
---|---|---|
Sharp TFT LCD (LQ043 K3146) | $24.50 | SKU3217 (Dealextreme) |
40 pin ribbon cable connector | $2.10 | SKU42223 (Dealextreme) |
4 pin backlight connector | $1.50 | SKU42130 (Dealextreme) |
LED driver chip LT1932 | $4.20 | LT1932ES6#TRMPBFCT-ND (Digikey) |
Inductor 6uH | $0.35 | 587-2101-1-ND (Digikey) |
2x20 female header | $2.20 | S9200-ND (Digikey) |
*resistors, *caps, *diodes, etc | ~$5.00 |
(* make sure everything on the LED side of the inductor is rated for at least 30V)
Here are my Eagle files in case they might be useful:
(I already flipped the polarity of the backlight socket so the cable doesn't need twisting)
PSP_screen_breakout.brd
PSP_screen_breakout.sch
LCD Pin assignments
# system clock input from board
set_location_assignment PIN_R8 -to clk_50
# LEDs
set_location_assignment PIN_F3 -to out_port_from_the_pio_led[5]
set_location_assignment PIN_D1 -to out_port_from_the_pio_led[4]
set_location_assignment PIN_A11 -to out_port_from_the_pio_led[3]
set_location_assignment PIN_B13 -to out_port_from_the_pio_led[2]
set_location_assignment PIN_A13 -to out_port_from_the_pio_led[1]
set_location_assignment PIN_A15 -to out_port_from_the_pio_led[0]
# SDRAM
set_location_assignment PIN_P2 -to zs_addr_from_the_sdram[0]
set_location_assignment PIN_N5 -to zs_addr_from_the_sdram[1]
set_location_assignment PIN_N6 -to zs_addr_from_the_sdram[2]
set_location_assignment PIN_M8 -to zs_addr_from_the_sdram[3]
set_location_assignment PIN_P8 -to zs_addr_from_the_sdram[4]
set_location_assignment PIN_T7 -to zs_addr_from_the_sdram[5]
set_location_assignment PIN_N8 -to zs_addr_from_the_sdram[6]
set_location_assignment PIN_T6 -to zs_addr_from_the_sdram[7]
set_location_assignment PIN_R1 -to zs_addr_from_the_sdram[8]
set_location_assignment PIN_P1 -to zs_addr_from_the_sdram[9]
set_location_assignment PIN_N2 -to zs_addr_from_the_sdram[10]
set_location_assignment PIN_N1 -to zs_addr_from_the_sdram[11]
set_location_assignment PIN_L4 -to zs_addr_from_the_sdram[12]
set_location_assignment PIN_M7 -to zs_ba_from_the_sdram[0]
set_location_assignment PIN_M6 -to zs_ba_from_the_sdram[1]
set_location_assignment PIN_K1 -to zs_dq_to_and_from_the_sdram[15]
set_location_assignment PIN_N3 -to zs_dq_to_and_from_the_sdram[14]
set_location_assignment PIN_P3 -to zs_dq_to_and_from_the_sdram[13]
set_location_assignment PIN_R5 -to zs_dq_to_and_from_the_sdram[12]
set_location_assignment PIN_R3 -to zs_dq_to_and_from_the_sdram[11]
set_location_assignment PIN_T3 -to zs_dq_to_and_from_the_sdram[10]
set_location_assignment PIN_T2 -to zs_dq_to_and_from_the_sdram[9]
set_location_assignment PIN_T4 -to zs_dq_to_and_from_the_sdram[8]
set_location_assignment PIN_R7 -to zs_dq_to_and_from_the_sdram[7]
set_location_assignment PIN_J1 -to zs_dq_to_and_from_the_sdram[6]
set_location_assignment PIN_J2 -to zs_dq_to_and_from_the_sdram[5]
set_location_assignment PIN_K2 -to zs_dq_to_and_from_the_sdram[4]
set_location_assignment PIN_K5 -to zs_dq_to_and_from_the_sdram[3]
set_location_assignment PIN_L8 -to zs_dq_to_and_from_the_sdram[2]
set_location_assignment PIN_G1 -to zs_dq_to_and_from_the_sdram[1]
set_location_assignment PIN_G2 -to zs_dq_to_and_from_the_sdram[0]
set_location_assignment PIN_R6 -to zs_dqm_from_the_sdram[0]
set_location_assignment PIN_T5 -to zs_dqm_from_the_sdram[1]
set_location_assignment PIN_L1 -to zs_cas_n_from_the_sdram
set_location_assignment PIN_L7 -to zs_cke_from_the_sdram
set_location_assignment PIN_P6 -to zs_cs_n_from_the_sdram
set_location_assignment PIN_L2 -to zs_ras_n_from_the_sdram
set_location_assignment PIN_C2 -to zs_we_n_from_the_sdram
set_location_assignment PIN_R4 -to shift_clk_c0_out
# Display
set_location_assignment PIN_E10 -to out_port_from_the_pio_led[7]
set_location_assignment PIN_B12 -to out_port_from_the_pio_led[6]
set_location_assignment PIN_D3 -to RGB_OUT_from_the_video_sync_generator[1]
set_location_assignment PIN_C3 -to RGB_OUT_from_the_video_sync_generator[3]
set_location_assignment PIN_A2 -to RGB_OUT_from_the_video_sync_generator[4]
set_location_assignment PIN_A3 -to RGB_OUT_from_the_video_sync_generator[5]
set_location_assignment PIN_B3 -to RGB_OUT_from_the_video_sync_generator[6]
set_location_assignment PIN_B4 -to RGB_OUT_from_the_video_sync_generator[7]
set_location_assignment PIN_A5 -to RGB_OUT_from_the_video_sync_generator[8]
set_location_assignment PIN_D5 -to RGB_OUT_from_the_video_sync_generator[9]
set_location_assignment PIN_B6 -to RGB_OUT_from_the_video_sync_generator[10]
set_location_assignment PIN_A6 -to RGB_OUT_from_the_video_sync_generator[11]
set_location_assignment PIN_B7 -to RGB_OUT_from_the_video_sync_generator[12]
set_location_assignment PIN_D6 -to RGB_OUT_from_the_video_sync_generator[13]
set_location_assignment PIN_A7 -to RGB_OUT_from_the_video_sync_generator[14]
set_location_assignment PIN_C6 -to RGB_OUT_from_the_video_sync_generator[15]
set_location_assignment PIN_C8 -to RGB_OUT_from_the_video_sync_generator[16]
set_location_assignment PIN_E6 -to RGB_OUT_from_the_video_sync_generator[17]
set_location_assignment PIN_E7 -to RGB_OUT_from_the_video_sync_generator[18]
set_location_assignment PIN_D8 -to RGB_OUT_from_the_video_sync_generator[19]
set_location_assignment PIN_E8 -to RGB_OUT_from_the_video_sync_generator[20]
set_location_assignment PIN_F8 -to RGB_OUT_from_the_video_sync_generator[21]
set_location_assignment PIN_F9 -to RGB_OUT_from_the_video_sync_generator[22]
set_location_assignment PIN_E9 -to RGB_OUT_from_the_video_sync_generator[23]
set_location_assignment PIN_D9 -to shift_clk_c1_out
set_location_assignment PIN_B11 -to HD_from_the_video_sync_generator
set_location_assignment PIN_D11 -to VD_from_the_video_sync_generator
Display Driver - A Test
// uses DMA to display sections of SDRAM //
#include <stdio.h>
#include "altera_avalon_pio_regs.h"
#include <altera_avalon_sgdma.h>
#include <altera_avalon_sgdma_descriptor.h>
#include <altera_avalon_sgdma_regs.h>
// DMA descriptors //
alt_sgdma_descriptor dmaDescA[8];
alt_sgdma_descriptor dmaDescEND;
// we locate our first framebuffer at the beginning of the SDRAM //
alt_u32* frameBufferA = (alt_u32*)SDRAM_BASE;
// this subroutine initializes a chain of descriptors //
void init_framebuffer(alt_sgdma_dev *dma) {
// 480*272 lines * 4 bytes = 522240 bytes //
// 65532 (0xfffc) bytes * 7 = 458724 //
// +63516 (0xf81c) bytes //
// frame buffer A //
alt_u8* buff = (alt_u8*)frameBufferA;
int i;
for(i = 0; i < 8; ++i) {
alt_u16 size = (i<7)?0xfffc:0xf81c;
alt_avalon_sgdma_construct_mem_to_stream_desc( &dmaDescA[i], (i<7) ? (&dmaDescA[i+1]) : &dmaDescEND, (alt_u32*)buff, size, 0, i==0, i==7, 0);
buff+= size;
}
}
int main() {
// switch on the backlight //
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, 64);
// initialize the DMA and get a device handle //
alt_sgdma_dev *dma = alt_avalon_sgdma_open("/dev/sgdma");
printf("open dma returned %ld\n", (alt_u32)dma);
printf("framebuffer 1 at %lx\n", (alt_u32)frameBufferA);
// assert the DISP signal //
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, 128+64);
// now we just continuously copy framebufferA to the Video Sync Generator //
int count = 1;
while(1) {
// blink an led to see the program running //
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, (count & 0x0001)+128+64);
// init the descriptors //
init_framebuffer(dma);
// transfer //
alt_avalon_sgdma_do_sync_transfer(dma, dmaDescA);
count++;
}
return 0;
}
Display Driver - B Test
#include <stdio.h>
#include "altera_avalon_pio_regs.h"
#include <altera_avalon_sgdma.h>
#include <altera_avalon_sgdma_descriptor.h>
#include <altera_avalon_sgdma_regs.h>
// some useful structs ---------------------------- //
// a pixel in 32 bit //
typedef union {
alt_u32 color32;
struct allColor {
alt_u8 b;
alt_u8 g;
alt_u8 r;
alt_u8 blank;
}
color8;
}
Color;
// a particle, just for funsies //
typedef struct {
int x, y;
int vx, vy;
}
Particle;
// global variables ----------------------------- //
// DMA descriptors //
alt_sgdma_descriptor dmaDescA[8];
alt_sgdma_descriptor dmaDescEND;
// we locate our first framebuffer at the beginning of the SDRAM //
alt_u32* frameBufferA = (alt_u32*)SDRAM_BASE;
// DMA ------------------------------------- //
// The InterruptService Routine (actually a callback function called by the ISR) //
void my_dma_callback(void *data) {
// reset the OWNED_BY_HW bit in the descriptors to reuse the chain
int i;
for(i = 0; i < 8;++i)
dmaDescA[i].control |= 1<<ALTERA_AVALON_SGDMA_DESCRIPTOR_CONTROL_OWNED_BY_HW_OFST;
// trigger another transfer all over again //
alt_avalon_sgdma_do_async_transfer((alt_sgdma_dev*)data, dmaDescA);
}
// this subroutine initializes a chain of descriptors, registers the //
// interrupt service routine and starts the first asynchronous transfer//
void init_and_start_framebuffer(alt_sgdma_dev *dma) {
// 480*272 lines * 4 bytes = 522240 bytes //
// 65532 (0xfffc) bytes * 7 = 458724 //
// +63516 (0xf81c) bytes //
// frame buffer A //
alt_u8* buff = (alt_u8*) frameBufferA;
int i;
for(i = 0; i < 8; ++i) {
alt_u16 size = (i<7)?0xfffc:0xf81c;
alt_avalon_sgdma_construct_mem_to_stream_desc( &dmaDescA[i], (i<7) ? (&dmaDescA[i+1]) : &dmaDescEND, (alt_u32*)buff, size, 0, i==0, i==7, 0);
buff+= size;
}
alt_avalon_sgdma_register_callback( dma, my_dma_callback,
ALTERA_AVALON_SGDMA_CONTROL_IE_CHAIN_COMPLETED_MSK
| ALTERA_AVALON_SGDMA_CONTROL_IE_GLOBAL_MSK, (void*)dma );
alt_avalon_sgdma_do_async_transfer(dma, dmaDescA);
}
// basic drawing routines --------------------------------------//
// draw a pixel //
inline void setPix(const int x, const int y, const Color col, alt_u32* buffer) {
buffer[x + y * 480] = col.color32;
}
// bresenham line drawing //
void line(alt_u32* buffer, int x0, int y0, int x1, int y1, Color color) {
int dx = abs(x1-x0), sx = x0<x1 ? 1 : -1;
int dy = abs(y1-y0), sy = y0<y1 ? 1 : -1;
int err = (dx>dy ? dx : -dy)/2, e2;
for(;;) {
setPix(x0, y0, color, buffer);
if (x0==x1 && y0==y1) break;
e2 = err;
if (e2 >-dx) { err -= dy; x0 += sx; }
if (e2 < dy) { err += dx; y0 += sy; }
}
}
// fill the screen with a fractal (quite slow) //
void MandelBrot(alt_u32* buffer) {
alt_32 x,xx,y,cx,cy;
int iteration,hx,hy;
int itermax = 64;
/* how many iterations to do */
int px, py;
Color ramp[67];
memset(ramp, 0, sizeof(ramp));
// lets prepare some pretty colors //
for(x = 0; x < 32; x++) {
ramp[x].color8.r = x*8;
ramp[x].color8.g = 255- x*8;
ramp[x].color8.b = 0;
}
for(x = 0; x < 32; x++) {
ramp[32+x].color8.r = 255 - x*8; ramp[32+x].color8.g = 0;
ramp[32+x].color8.b = x*8;
}
py = 0;
for (hy=-1500;py<272; hy+=6, ++py) {
cy = hy;
px=0;
for (hx=-2200;px<480; hx+=6, ++px) {
cx = hx;
x = y = 0;
for (iteration=0; iteration < itermax; iteration++) {
x/=8; y/=8;
xx = x*x/16-y*y/16+cx;
y = 2*x*y/16+cy;
x = xx;
if ((x/32)*(x/32)+(y/32)*(y/32)>1000000) break;
}
setPix(px, py, ramp[iteration], buffer);
}
}
}
// useful to fill parts of the screen without blocking the SDRAM for too long //
// use this instead of memset when doing frame buffer fills //
void nonburst_memset(alt_u32* trg, alt_u32 val, alt_u32 size) {
const int chunkSize = 12;
// <-size of burst, lower this if the display gets corrupted. //
alt_u32 s;
while(size) {
s = (size>chunkSize)?chunkSize:size;
memset(trg, val, s*4);
trg+=s;
size-=s;
}
}
int main() {
// switch on the backlight //
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, 64);
// initialize the DMA and get a device handle //
alt_sgdma_dev *dma = alt_avalon_sgdma_open("/dev/sgdma");
printf("open dma returned %ld\n", (alt_u32)dma);
printf("framebuffer 1 at %lx\n", (alt_u32)frameBufferA);
// assert the DISP signal //
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, 128+64); init_and_start_framebuffer(dma);
// now we actually have some time to draw something //
Particle particles[2] = { {100,0, -1, -1}, {0, 120, -1, -1} };
Color col;
int count;
for(count=1;;count++) {
// blink an led to see the program running //
IOWR_ALTERA_AVALON_PIO_DATA(PIO_LED_BASE, (count & 0x0001)+128+64);
// draw a line in a random color //
col.color8.r = rand()%256;
col.color8.g = rand()%256;
col.color8.b = rand()%256;
line(frameBufferA, particles[0].x, particles[0].y, particles[1].x, particles[1].y, col);
// bounce the particles around //
int i;
for(i = 0; i < 2; ++i) {
if(particles[i].x == 0 || particles[i].x == 479) particles[i].vx *= -1;
particles[i].x += particles[i].vx;
if(particles[i].y == 0 || particles[i].y == 271) particles[i].vy *= -1;
particles[i].y += particles[i].vy;
}
}
return 0;
}