Computer Graphics 101 With Pi Pico and Colour Display

by tonygo2 in Teachers > Coding

2153 Views, 4 Favorites, 0 Comments

Computer Graphics 101 With Pi Pico and Colour Display

C Graph 101-3.jpg

This Instructable shows you how to quickly produce interactive computer graphics on a small coloured screen with Micropython using an inexpensive microcontroller and a 240x240 pixel display. It is assumed that the reader has already used a Raspberry Pi Pico with the Thonny editor and can use LEDs, buttons and while loops.

Supplies

240 Display.jpg

Raspberry Pi Pico microcontroller

Waveshare 1.3" IPS LCD Display Module for Raspberry Pi Pico (240x240) - with buttons and joystick

USB cable

Thonny Editor - free download


Just plug them together - no soldering - but do not press on the surface of the screen.

Draw Anything You Want, One Dot at a Time

C Graph 101-1.jpg

The display comes with an example program including the screen driver which you can find here:

https://www.waveshare.com/wiki/Pico-LCD-1.3

You do not need to download it as I will provide the necessary code at a later stage, but you may find it interesting. The pinout for the board and the pins used for the buttons and joystick are provided.

The whole computer graphics system depends on being able to daw a coloured dot, or pixel, at a precise point on the screen. With this particular screen we have 240 rows of 240 pixels making a total of 57600 pixels.

We position each pixel by counting from the top left hand corner of the screen using co-ordinate geometry. 0 to 239 for x values moving from left to right across the screen and 0 to 239 for y, moving down the screen. Remember computers count from zero!

This means that the top left position is (0,0) and the bottom right is (239,239).

Each pixel can have 32 intensities, or brightness, of red and blue and 64 degrees of green brightness, making 65536 different possible colours - called the RGB565 colour space. ( Green gets the extra bit because human eyes are more sensitive to green light.) Each colour is given a number, from 0 to 65535. (In hexadecimal from 0x0000 to 0xFFFF - from black to white and all the coloured steps in between.) We can hold such numbers in 16 bits or 2 bytes.

red = 0x07E0, green = 0x001f, blue = 0xf800, white = 0xffff and black = 0x0000

These are difficult values to remember and even harder to mix together to make yellow, orange, cyan and brown!

A common method is to define colours as a mixture of (red, green, blue) each with values in the range 0 to 255.

Red is (255,0,0), Blue is (0,0,255) Yellow is (255,255,0) White is (255,255,255) Cyan is (0,255,255) with Black (0,0,0) and a grey (70,70,70). Remember that we are mixing light and not paint!

I've written a very simple to use routine to convert these 3-byte colours to the 2-byte colours needed for the display.

Here it is:

# Colour Mixing Routine
def colour(R,G,B): # Compact method!
  mix1 = ((R&0xF8)*256) + ((G&0xFC)*8) + ((B&0xF8)>>3)
  return (mix1 & 0xFF) *256 + int((mix1 & 0xFF00) /256) # low nibble first

You do not need to understand how it works, just use it.

c = colour(255,0,255) #  magenta

How the Display Works

Nepal-1.jpg

Here is photograph of a slightly smaller screen (160x80 pixels) displaying a photograph of a temple in Kathmandu but showing each individual pixel. If you look at the pale blue part of the eye you may be able to see that each pixel is made up of a red, a green and a blue part, each of which can be set to a different brightness level.

How do we set these levels? It is a two stage process.

We set up a very long list of bytes called a buffer. It needs to hold two bytes for each pixel so it is 240x240x2 bytes long. This takes up a great deal of space in memory = 115,200 bytes.

When we execute code to set the colour of the pixel

c = colour(255,0,0) # Red
lcd.pixel(10,20,c) # (x,y,c)

we just store 2 bytes in the correct position in the buffer or byte-array. (The 11th pixel from the left and 21st pixel down - think about it!) What we see on the screen does not change until we execute the instruction

lcd.show()

This transfers the numbers in the buffer to the display hardware and updates what we can see. This is a very fast communication between the Pico and the display via SPI.

The most common mistake made by students of computer graphics is to forget this essential instruction and wonder why they cannot see their wonderful creation on the screen.

Building With Pixels

C Graph 101-2.jpg

Here we have some coloured text and a few rather boring grey graphic elements in the lower part of the screen.

There are:

  • a single pixel
  • a horizontal line
  • a pair of slanted lines
  • an outline rectangle
  • a filled rectangle
  • a line of very small text
  • three lines are larger coloured text in a different font

This is the code which produced the grey parts of the image:

c = colour(200,200,200)               # Calculate 2-byte colour code
lcd.pixel(10,120,c)                   # single pixel (x,y,c)
lcd.hline(20,120,200,c)               # horizontal line (x,y,width,c)
lcd.vline(10,130,100,c)               # vertical line  (x,y,height,c)
lcd.rect(20,140,80,20,c)              # hollow rectangle (x,y,w,h,c)
lcd.fill_rect(20,170,80,20,c)         # solid rectangle (x,y,w,h,c)
lcd.line(120,130,230,210,c)           # slanted lines (x0,y0,x1,y1,c)
lcd.line(140,235,235,145,c)
lcd.text("Framebuffer text",20,200,c) # simple text (s,x,y,c)
lcd.show()                            # Draw on the screen

Let us think about how it works.

The pixel is code is used to produce the rest.

We can build a horizontal line by placing pixels side by side with the same y value but with increasing x values.

A vertical line keeps the x value constant while increasing the y value.

The empty rectangle can be produced by two horizonal and two vertical lines and a filled rectangle by a stack of horizonal lines of the same length. (Rather more maths is needed for the sloping lines, but you get the idea. They are all built from single pixels carefully placed by their co-ordinates.)

The text characters are produced in the same way from a stored table with a code for each character.

We do not need to know how they do it. We can just be thankful that someone worked out how to do it and allowed us to use their code with a very simple and easy to understand instruction.

Look at the Video

MVI 8722

The program has nearly 600 lines of code. If you can download it, or get a copy of it from your teacher, look at how it is made up.

The whole program is written in Micropython, including the display driver.

Starting at the top:

  • Import the necessary standard libraries: Lines: 7 - 12
  • Display driver: Lines: 13 to 161
  • Improved Text system - 3 sizes of text: Lines: 162 - 327
  • Start of Graphics routines: Lines: 329 - 458
  • Main program: Lines: 459 onwards

We only need to concentrate on the code from line 459. The rest has been tested and works and we can just use it like the imported libraries.

Triangles and Circles

MVI 8717

You may like to look at the code for the triangles, circles and rings and notice how they use trigonometry and the theorem of Pythagoras. Notice how much harder it is to fill a triangle than a circle.

Starting the Program

CG458-492.jpg

This is the main part of the program

We start the screen and clear it to a black background

lcd = LCD_1inch3() # Start screen 
lcd.fill(colour(0,0,0)) # BLACK 
lcd.show()

Code (currently disabled) shows how to set up the buttons and joystick

It then prints out some coloured text and the basic graphics elements provided within the frame buffer library.

When you want to write your program just copy the lines of code from the top down to line 476, paste into a new window in Thonny and save with a suitable name for your project. You start your instructions at the bottom.

Lines 464 to 475 are currently commented out. Delete the pairs of triple single quotes lines to activate the buttons setup.

Exercise #1

Things to try:

  1. Put a 3 second delay after showing each of the following steps
  2. Set the background colour to dark grey.
  3. Using only vline() and hline() draw a cyan frame 5 pixels from the edge of the screen. [Start at (4,4)]
  4. Draw a large filled red square in the middle of the screen and draw a blue frame round it.
  5. Draw 200 randomly coloured and positioned pixels in the red square.
  6. Draw in the diagonals of the red square with green lines
  7. Place a single white pixel at each of the corners of the screen.
  8. Write your name above the squares in white letters
  9. Clear the screen to black at the end of your program.

This block may help:

lcd = LCD_1inch3()                    # Start the screen 
lcd.fill(colour(0,0,0))               # Black 
lcd.show()                            # Show what you have drawn
c = colour(200,200,200)               # Calculate 2-byte colour code
lcd.pixel(10,120,c)                   # single pixel (x,y,c)
lcd.hline(20,120,200,c)               # horizontal line (x,y,width,c)
lcd.vline(10,130,100,c)               # vertical line  (x,y,height,c)
lcd.rect(20,140,80,20,c)              # hollow rectangle (x,y,w,h,c)
lcd.fill_rect(20,170,80,20,c)         # solid rectangle (x,y,w,h,c)
lcd.line(120,130,230,210,c)           # slanted line (x0,y0,x1,y1,c)
lcd.text("Framebuffer text",20,200,c) # simple text (s,x,y,c)
lcd.show()                            # Draw on the screen
utime.sleep(1)                        # Wait one second
x = random.randint(50,100)            # random number between 50 and 100


Triangles

CG493-520.jpg

This code shows you how to call up the triangle routines

Circles

CG 520-538.jpg

How to call the circle routines

Text in Different Sizes and Button Controlled Loop

CG 540-576.jpg

This is how you write text in different sizes and and centre a text string across the page.

The loop waits for the key to be pressed, lights up the LED on the Pico and simulated screen LED, increments the counter, updates the text, and prints it. The program then waits for the button to be released. It turns off the LEDs and removes the text message from the screen.

To remove or update a message we write over it in the background colour leaving the static parts of the screen intact. Completely re-drawing the screen every time we go round a loop slows things down.

Once we have pressed the button 3 times we fall out of the loop at the bottom and turn off the Pico's LED.

Tidy Up

CG 577-591.jpg

This shows a finishing screen and then turns off the display.

Exercise #2

You may find this helpful.

triangle(40,115,10,180,95,235,c)              # outline triangle (x0,y0,x1,y1,x2,y2,c)
tri_filled(40,115,10,180,95,235,c)            # solid triangle (x0,y0,x1,y1,x2,y2,c)
ring(150,180,25,c)                            # circular ring (cx,xy,r,c)
circle(150,180,25,c)                          # Filled circle (cx,cy,r,c)
printstring("Text",35,40,2,colour(0,255,0))   # (s,x,y,size,c)
centrestring("Centred",190,2,c)               # (s,y,size,c) - x not needed
  1. At the top of the screen write three lines of text in different colours and sizes
  2. Add a large red circle with a green ring round it.
  3. Inside the red circle draw a large solid blue triangle
  4. Inside the blue triangle draw an outline black triangle
  5. Wait 3 seconds and draw a large horizontal green arrow with the word Exit in the middle of the shaft
  6. Wait 4 seconds and clear the screen to mid-blue
  7. Draw a large yellow arrow pointing into the top left corner of the screen - just use triangles.
  8. Wait 3 seconds and clear the screen to black

This one is harder

  1. Steer a small red ball around screen with the joystick - do not leave a trail!
  2. Keep the ball inside of a 3 pixel blue frame round the edge of the screen
  3. Stop the program and clear the screen with button Y

Final Thoughts

Lizard240x240.jpg

I hope you have managed to do the exercises.

If so, you are now a Master of Computer Graphics and should be able to produce some fantastic images of your own design.

Bonus link: Putting photographs on your display - pretty slow and quite a bit of pre-processing!

http://www.penguintutor.com/programming/picodisplayanimations


Enjoy your coding.

Tony Goodhew