JM9 XRCU Tutorials 0.0.1a
Learn how to program XRCU
Loading...
Searching...
No Matches
Monitor

XRCU has a small colour screen on the front. We call it the monitor. Your robot can use it to show words, numbers and pictures. For example, it can show the distance from an ultrasonic sensor, or a menu to choose a program, without a computer!

The monitor is a 1.8 inch screen with a chip called ST7735. You plugged it onto XRCU in the Hardware Setup chapter, so no extra wiring is needed.

In this chapter, we will learn how to print text and draw shapes on the monitor.

Pixels and Coordinates

A screen is made of many tiny dots of light, called pixels. Each pixel can show one colour at a time. Our monitor is 160 pixels wide and 128 pixels tall.

To tell XRCU which pixel we mean, we use 2 numbers, called coordinates:

  • x tells how far the pixel is from the left edge.
  • y tells how far the pixel is from the top edge.

We write them together as (x, y). The top-left pixel is (0, 0). We start counting from 0, so the last pixel on the right has x = 159, not 160. In the same way, the last pixel at the bottom has y = 127.

Warning
In maths class, y gets bigger when you go up. On the monitor, y gets bigger when you go down! This is because a screen is drawn from the top line to the bottom line, like how we write on paper.

Hello, Monitor!

To use the monitor, write this line at the very top of your program:

#include <Monitor.h>

After that, we can use a ready-made object called display. display is our monitor. Just like button1, XRCU gets it ready for you before setup() runs, so you do not need to call begin().

Here is our first program:

#include <Monitor.h>
void setup() {
// Paint the whole screen black.
// This also clears the start-up message on the screen.
display.fillScreen(ST7735_BLACK);
// Move the text cursor to x = 10, y = 20.
display.setCursor(10, 20);
// Make the text 2 times bigger than normal.
display.setTextSize(2);
// Choose yellow for the text.
display.setTextColor(ST7735_YELLOW);
// Print the text.
display.print("Hello!");
}
void loop() {
// Nothing to do here.
}

Let's look at each line:

Code What it does
display.fillScreen(ST7735_BLACK) Paints every pixel black.
display.setCursor(10, 20) The next text starts here. (10, 20) is the top-left corner of the first letter.
display.setTextSize(2) Makes the text 2 times bigger. Size 1 is the smallest.
display.setTextColor(ST7735_YELLOW) Chooses the colour of the text.
display.print("Hello!") Prints the text, starting at the cursor.
Note
When your program starts, the monitor still shows the message "Running program:" with the date, time and name of your program. XRCU does not clear it for you. That is why we start with display.fillScreen(ST7735_BLACK); in setup().

How big is a letter?

At text size 1, every letter takes a box of 6 pixels wide and 8 pixels tall (including a small gap on the right, so the letters do not touch). At size 2, every letter is 2 times bigger: 12 pixels wide and 16 pixels tall. At size 3, it is 18 pixels wide and 24 pixels tall, and so on.

So at size 1, one line can fit 160 / 6 = 26 letters. At size 2, one line can only fit 13 letters.

If the text is too long for one line, the rest of the text goes on to the next line.

print and println

Just like Serial, the monitor has both print(...) and println(...). They can also print numbers, like display.print(25);. After println(...), the next text starts on a new line.

Note
The new line always starts at the left edge of the screen (x = 0), even if your cursor started at x = 10. If you want the next line to start at x = 10 again, use setCursor(...) again.

Checkpoint 11.1

Change the program above, so that the monitor shows:

  1. Your name in yellow, at text size 2, starting at (10, 20).
  2. Your class (for example, 6B) in cyan (ST7735_CYAN), at text size 1, starting at (10, 50).
  3. Then use display.println(...) to print 2 more words, one on each line, in white (ST7735_WHITE).

Upload and run it. Where does each word from println(...) start? Is it at x = 10 or at x = 0? Why?

Take a photo of the monitor. Submit this photo to your assignment portal at checkpoint 11.1.

Colours

Here are the colours that are ready for you to use:

Code Colour
ST7735_BLACK black
ST7735_WHITE white
ST7735_RED red
ST7735_GREEN green
ST7735_BLUE blue
ST7735_CYAN cyan (light blue)
ST7735_MAGENTA magenta (pink-purple)
ST7735_YELLOW yellow
ST7735_ORANGE orange
Remarks
Want another colour? Mix red, green and blue light, each from 0 (none) to 255 (full), with display.color565(red, green, blue). For example, display.fillScreen(display.color565(255, 105, 180)); paints the screen pink. (565 is the name of the way the monitor stores colours. It cannot show every colour exactly, so some very close colours look the same.)

Changing Numbers: Words on Top of Words

A robot often needs to show a number that keeps changing, like a distance or a counter. Let's try:

#include <Monitor.h>
int counter;
void setup() {
display.fillScreen(ST7735_BLACK);
display.setTextSize(3);
display.setTextColor(ST7735_WHITE);
counter = 0;
}
void loop() {
// Always print the number at the same place.
display.setCursor(20, 50);
display.print(counter);
// Count up by 1 every second.
counter = counter + 1;
delay(1000);
}

You may expect to see 0, then 1, then 2... But after a few seconds, the number turns into a white block!

This is because print(...) only paints the pixels of the letters. It does not clean the pixels around them. So the old number stays on the screen, and the new number is painted on top of it, like writing on the same spot of paper again and again. This is what the monitor shows after the numbers 0 to 12 are printed at the same place:

Fix 1: Clean Up First

Before printing a new number, we paint a black rectangle over the old number. fillRect(...) paints a filled rectangle:

// x, y, width, height, colour
display.fillRect(20, 50, 120, 24, ST7735_BLACK);

(20, 50) is the top-left corner of the rectangle. It is 120 pixels wide and 24 pixels tall: 24 is the height of a size 3 letter, and 120 is wide enough for a few digits.

So the new loop() becomes:

void loop() {
// Clean the old number by painting a black rectangle over it.
display.fillRect(20, 50, 120, 24, ST7735_BLACK);
// Print the new number at the same place.
display.setCursor(20, 50);
display.print(counter);
counter = counter + 1;
delay(1000);
}
Remarks
Only clean the small part of the screen that changes. Do not use fillScreen(...) again and again in loop(). Painting the whole screen takes time, and the screen will flicker (flash on and off).

Checkpoint 11.2

Upload the counter program without the fix, and run it. What happens to the number after 10 seconds?

Then add the black fillRect(...) before print(...), and upload again. Now change delay(1000); to delay(100);. Can you still read the number clearly? Does it flicker?

Record a video of the monitor in both tests. Submit this video to your assignment portal at checkpoint 11.2.

Fix 2: Text with a Background Colour

There is a second way to fix the problem. setTextColor(...) can take 2 colours:

// text colour, background colour
display.setTextColor(ST7735_WHITE, ST7735_BLACK);

Now print(...) paints the whole box of each letter: white for the letter, and black for the empty pixels around it. So each new letter covers up the old letter in the same place.

With only 1 colour, like setTextColor(ST7735_WHITE), the empty pixels are not painted. That is why the numbers in checkpoint 11.2 piled up.

But there is still a small problem. Imagine the screen shows 12, and then we print 0 at the same place. 0 only covers the box of the 1. The 2 is still there! So the screen shows 02.

To fix this, print a few spaces after the number. A space is a blank letter, so it paints its whole box with the background colour:

display.setCursor(20, 50);
display.print(counter);
// Cover up any old digits that are left on the right.
display.print(" ");
Note
Fix 2 only cleans the boxes of the letters that you print. Fix 1 (fillRect(...)) cleans any area you choose. Both are fine. Use the one you find easier.

Checkpoint 11.3

Make a click counter on the monitor:

  1. Show the word Clicks: at the top of the screen, at text size 2. Print it only once, in setup().
  2. Show the number of clicks below it, at text size 3.
  3. Each click of SW1 makes the number go up by 1.
  4. Each click of SW2 sets the number back to 0.
  5. Use Fix 2: setTextColor(...) with a text colour and a background colour. Do not use fillRect(...) or fillScreen(...) in loop().
  6. Only print the number when it changes.

(Hint: #include <Buttons.h> and button1.isClicked() from the Buttons chapter.)

Upload and run it. First, try it without printing spaces after the number. Click SW1 12 times, then click SW2. What does the screen show? Why? Then add the spaces, and try again.

Record a video showing your fingers and the monitor during both tests. Submit this video to your assignment portal at checkpoint 11.3.

Drawing Shapes

Now let's draw some pictures! There are 2 kinds of shapes:

  • draw shapes only paint the outline, like a picture in a colouring book.
  • fill shapes paint the outline and everything inside it.
Code What it paints
display.drawPixel(x, y, colour) One pixel at (x, y).
display.drawLine(x1, y1, x2, y2, colour) A straight line from (x1, y1) to (x2, y2).
display.drawRect(x, y, width, height, colour) The outline of a rectangle. (x, y) is its top-left corner.
display.fillRect(x, y, width, height, colour) A filled rectangle.
display.drawCircle(x, y, radius, colour) The outline of a circle. (x, y) is its centre.
display.fillCircle(x, y, radius, colour) A filled circle.
display.drawTriangle(x1, y1, x2, y2, x3, y3, colour) The outline of a triangle with corners (x1, y1), (x2, y2) and (x3, y3).
display.fillTriangle(x1, y1, x2, y2, x3, y3, colour) A filled triangle.
display.fillScreen(colour) Paints the whole screen.

Be careful with the 2 different "corners": a rectangle starts from its top-left corner, but a circle starts from its centre.

Here is a program that draws a small house with the sun in the sky:

#include <Monitor.h>
void setup() {
// Blue sky.
display.fillScreen(ST7735_BLUE);
// Green grass: a filled rectangle at the bottom of the screen.
// x, y, width, height, colour
display.fillRect(0, 100, 160, 28, ST7735_GREEN);
// The sun: a filled circle in the top-right corner.
// centre x, centre y, radius, colour
display.fillCircle(135, 25, 15, ST7735_YELLOW);
// The walls of the house: a white filled rectangle.
display.fillRect(30, 60, 60, 40, ST7735_WHITE);
// The roof: a red filled triangle on top of the walls.
// left, top, right, colour
display.fillTriangle(25, 60, 60, 30, 95, 60, ST7735_RED);
// The door: an orange filled rectangle.
display.fillRect(52, 75, 16, 25, ST7735_ORANGE);
// A window: a black outline of a rectangle, with a cross in it.
display.drawRect(35, 67, 13, 13, ST7735_BLACK);
display.drawLine(41, 67, 41, 79, ST7735_BLACK);
display.drawLine(35, 73, 47, 73, ST7735_BLACK);
}
void loop() {
// Nothing to do here.
}

Notice the order: later shapes are painted on top of earlier shapes. The walls are painted first, then the door is painted on top of the walls. Here is what the program draws:

Remarks
Plan your picture on squared paper first. Draw a box 160 squares wide and 128 squares tall (or 16 by 12.8, with 1 square = 10 pixels), then read the coordinates of each corner from the paper.

If part of a shape is outside the screen, that part is simply not painted. Nothing breaks.

Checkpoint 11.4

Draw a robot face on the monitor. Your picture must have:

  1. A head made with drawRect(...) or fillRect(...).
  2. 2 eyes made with fillCircle(...).
  3. A nose made with fillTriangle(...) or drawTriangle(...).
  4. A mouth made with drawLine(...).
  5. At least 1 draw... shape and at least 1 fill... shape.
  6. Your name printed somewhere on the screen.

Upload and run it. Then swap the order of 2 lines in your code, for example draw the eyes before the head. What happens? Why?

Take a photo of your robot face, and another photo of the picture after you swapped the 2 lines. Submit both photos to your assignment portal at checkpoint 11.4.

A Bar that Grows

Numbers are easy to read, but sometimes a picture is even faster to read. Think of the battery bar on a phone: one look, and you know how much battery is left.

Let's draw a bar that shows a number from 0 to 100. First, we draw a white frame once, in setup():

// The frame: 142 x 22 pixels, so the inside is 140 x 20 pixels.
display.drawRect(9, 59, 142, 22, ST7735_WHITE);

The inside of the frame starts at (10, 60). It is 140 pixels wide and 20 pixels tall. When the number is 100, the bar should fill all 140 pixels. When the number is 50, the bar should fill half of it: 70 pixels. So the width of the bar is:

int barWidth = value * 140 / 100;
Note
Multiply first, then divide. value / 100 would give 0 for every value smaller than 100, because an int throws away everything after the decimal point.

Then we paint 2 rectangles inside the frame: the bar itself in green, and the empty part on the right in black. The black part cleans up the old, longer bar when the number goes down.

// The bar, from the left side of the frame.
display.fillRect(10, 60, barWidth, 20, ST7735_GREEN);
// The empty part, from the end of the bar to the right side of the frame.
display.fillRect(10 + barWidth, 60, 140 - barWidth, 20, ST7735_BLACK);

Painting only these 2 rectangles is much faster than painting the whole screen, so the bar does not flicker.

Checkpoint 11.5

Remember the "volume control" from the Buttons chapter (checkpoint 6.4)? Let's show it on the monitor!

Write a program where:

  1. The volume starts at 50.
  2. Each click of SW2 makes the volume go up by 1, and each click of SW1 makes it go down by 1. The volume must never go above 100 or below 0.
  3. Turn on pulse clicks for both buttons, so you can hold a button to change the volume quickly.
  4. The monitor shows the volume as a number and as a bar inside a white frame.
  5. The bar is green when the volume is 80 or less, and red when the volume is more than 80.
  6. Only redraw the number and the bar when the volume changes.

(Hint: a colour is just a number, so you can keep it in an int variable, like int barColour = ST7735_GREEN;, and change it with an if.)

Upload and run it. Hold SW2 until the volume reaches 100, then hold SW1 until the volume reaches 0. Does the bar change colour at the right place? Does the bar flicker?

Record a video showing your fingers and the monitor during the test above. Submit this video to your assignment portal at checkpoint 11.5.

Summary

Code What it does
#include <Monitor.h> Lets us use the monitor, called display. It is ready before setup().
display.fillScreen(ST7735_BLACK) Paints the whole screen. Use it in setup() to clear the start-up message.
display.setCursor(x, y) The next text starts here (top-left corner of the first letter).
display.setTextSize(2) Text size. Size 1 letters are 6 x 8 pixels, size 2 letters are 12 x 16 pixels.
display.setTextColor(ST7735_WHITE) Text colour. Only the letters are painted.
display.setTextColor(ST7735_WHITE, ST7735_BLACK) Text colour and background colour. The whole box of each letter is painted.
display.print(...) / display.println(...) Prints text or a number. A new line starts at x = 0.
display.drawPixel(x, y, colour) Paints one pixel.
display.drawLine(x1, y1, x2, y2, colour) Draws a line.
display.drawRect(x, y, w, h, colour) / fillRect(...) Draws / fills a rectangle. (x, y) is the top-left corner.
display.drawCircle(x, y, r, colour) / fillCircle(...) Draws / fills a circle. (x, y) is the centre.
display.drawTriangle(x1, y1, x2, y2, x3, y3, colour) / fillTriangle(...) Draws / fills a triangle with 3 corners.
display.color565(red, green, blue) Makes your own colour. Each number is from 0 to 255.

Remember:

  • The monitor is 160 pixels wide and 128 pixels tall. (0, 0) is the top-left corner, and y gets bigger when you go down.
  • print(...) does not clean the old text. Clean it with fillRect(...), or use a background colour in setTextColor(...) and print spaces after the number.
  • Only redraw the parts that change. Do not use fillScreen(...) in loop().
  • Shapes painted later cover shapes painted earlier.