Introduction: Computer Graphics 101 With Pi Pico and Colour Display
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
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.
Step 1: Draw Anything You Want, One Dot at a Time
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
Step 2: How the Display Works
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.
Step 3: Building With Pixels
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.
Step 4: Look at the Video
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.
Attachments
Step 5: Triangles and Circles
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.
Step 6: Starting the Program
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:
- Put a 3 second delay after showing each of the following steps
- Set the background colour to dark grey.
- Using only vline() and hline() draw a cyan frame 5 pixels from the edge of the screen. [Start at (4,4)]
- Draw a large filled red square in the middle of the screen and draw a blue frame round it.
- Draw 200 randomly coloured and positioned pixels in the red square.
- Draw in the diagonals of the red square with green lines
- Place a single white pixel at each of the corners of the screen.
- Write your name above the squares in white letters
- 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
Step 7: Triangles
This code shows you how to call up the triangle routines
Step 8: Circles
How to call the circle routines
Step 9: Text in Different Sizes and Button Controlled Loop
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.
Step 10: Tidy Up
This shows a finishing screen and then turns off the display.
Step 11: 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
- At the top of the screen write three lines of text in different colours and sizes
- Add a large red circle with a green ring round it.
- Inside the red circle draw a large solid blue triangle
- Inside the blue triangle draw an outline black triangle
- Wait 3 seconds and draw a large horizontal green arrow with the word Exit in the middle of the shaft
- Wait 4 seconds and clear the screen to mid-blue
- Draw a large yellow arrow pointing into the top left corner of the screen - just use triangles.
- Wait 3 seconds and clear the screen to black
This one is harder
- Steer a small red ball around screen with the joystick - do not leave a trail!
- Keep the ball inside of a 3 pixel blue frame round the edge of the screen
- Stop the program and clear the screen with button Y
Step 12: Final Thoughts
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