The Versatile RP2040: a Look at the Waveshare Circular Display with Embedded RP2040

This l324446101_1348645385897513_7627990848468519069_nittle gem, which cost around six dollars, US, features a gyroscope, accelerometer, on board clock, temperature sensor (a feature of the RP2040) and the built in circular display at 1.28 inches. With a resolution of 240 x 240, there’s quite a bit you can do with the board.   Due to its size and shape, and with a creative 3d print, you could make a nice little smart watch, though it does not have WiFi or Bluetooth built in. However, it’s capabilities can more than make up for this lack of connectivity.  One thing to keep in mind is that most of the I/O pins are exposed, so you could add both via an 8266 module that is small enough fit on the back of the device.  I have not yet pursued that, however, I have been experimenting with the board and will share some of what I have learned.

As this is a Waveshare product, they have a wiki page for it:

Wiki: www.waveshare.com/wiki/RP2040-LCD-1.28

PROGRAMMING

The device can be programmed via MicroPython or C++.  I am going to focus on the MicroPython side of things.

The first you will need to do is copy the firmware to the device to prepare it for use with MicroPython. 

Here’s a link to the image:

RP2040-LCD-1.28-image

Download the image and unzip it. Look for the file RP2040-LCD-1.28-image.uf2 in the directory where you unzipped the image.

Hold down the reset button on the device and plug it into your computer.  If running Windows, a directory will open that is the device page.  Drag the file into the device page. 

You should now be able to use MicroPython with the device.

I used Thonny, a free MicroPython IDE that works nearly seamlessly with many different devices, including RP2040 based boards.  I will post links at the end of this post to take you to a few good pages on how to setup your RP2040 device with Thonny.

CODE

Have a look at the following code:

from machine import Pin,I2C,SPI,PWM,ADC
from LCD1inch28 import LCD_1inch28,QMI8658


import machine
import utime
import time


Vbat_Pin          = 29
sensor_temp       = machine.ADC(4)
conversion_factor = 3.3 / (65535)


if __name__==’__main__’:
  
     LCD = LCD_1inch28()
     LCD.set_bl_pwm(65535)
     qmi8658 = QMI8658()
     Vbat    = ADC(Pin(Vbat_Pin))   
     rtc     = machine.RTC()
     ax=0
     ay=0
     az=0
     x=0
     y=0
     startx=50
     starty=100
     dotColor = LCD.black
     LCD.pixel(startx,starty, dotColor)
    
     def drawBox(sx,sy,ex,ey, colors):
         LCD.fill_rect(sx,sy, ex, ey, LCD.black)
         LCD.fill_rect(sx-2,sy-2, ex-2, ey-2, colors)
         LCD.rect(sx-2,sy-2, ex-2, ey-2, LCD.black)
        
     def getAcc():  #Read accelerometer and return either a positive or negative 1 for the x,y readings. Return Z for actual z axis
         abc = qmi8658.Read_XYZ()
         ax=abc[1]
         if ax<0:
             ax=-1
         if ax>0:
             ax=1
         ay=abc[2]
         if ay<0:
             ay=-1
         if ay>0:
             ay=1
         az=abc[3]
         print(ax,ay,az)
         return ax,ay,az
    
     def dPrint(text,x,y):
         LCD.text(text,x+1,y+1,LCD.black)
         LCD.text(text,x,y,LCD.white)
   
     LCD.fill(LCD.steel)
     dPrint(“Half-Byte Clock”,60,25)
     dPrint(“RP2040 LCD”,80,57)  
    
     LCD.show()
    
     while (True):
         # Get the date and time
         timestamp = rtc.datetime()
        
         # Split out date and time so they can be displayed separately
         datestring = “%04d/%02d/%02d “%(timestamp[0:3])
         timestring = “%02d:%02d:%02d “%(timestamp[4:7])
        
         # Draw the box to display the date and time (box has drop shadow)
         drawBox(60,106,137,29,LCD.darkblue)
        
         # Show the date and time in the box we just drew
         dPrint(“Date:” + datestring, 64, 110)
         dPrint(“Time:” + timestring, 64, 120)
        
         # Get the battery voltage and display it
         sx=78
         sy=213
         ex=83
         ey=18
         drawBox(sx,sy,ex,ey,LCD.red)
         reading = Vbat.read_u16()*3.3/65535*2
         LCD.text(“Vbat={:.2f}”.format(reading),80,215,LCD.black)
        
         # Get the temperature, then display it
         reading     = sensor_temp.read_u16() * conversion_factor       
         temperature = (27 – (reading – 0.706)/0.001721)
         temperature = (temperature * 1.8) + 8
         theTemp     = str(round(temperature,0))
        
         drawBox(70,87,115,19,LCD.red)
         dPrint(“Temp: ” + theTemp , 72,89)
        
         LCD.show()

   This code more or less matches what you see in the first picture posted above.  It shows you how to:

  • Draw boxes, text and achieve 3d like effects
  • Read the temperature sensor
  • Read the battery status
  • Get and display the time
  • Setup the LCD
  • Setup the board
  • Read the accelerometer

Lets look at this, section by section:

Top:

from machine import Pin,I2C,SPI,PWM,ADC
from LCD1inch28 import LCD_1inch28,QMI8658

import machine
import utime
import time

Vbat_Pin          = 29
sensor_temp       = machine.ADC(4)
conversion_factor = 3.3 / (65535)

First, in order to use most of the board’s features, you have to setup MicroPython so it can talk to the hardware.  To do that, we need to use ‘machine’, or, rather, specific parts of ‘machine’ like Pin, I2C, SPI and so forth. 

To use the LCD, we need to import LCD_1inch28 and QMI8658. These are part of the demo code found here: Demo code

Download that file, unzip it and go into the Python folder. There, you should find the Demo code.  Open that in Thonny and run it. Provided you have your device connected, it will run the code on the device. From that point on, you can use those libraries in other scripts.

Note that the battery pin is 29 (VBAT in the section above.)

The temperature sensor is accessed via ADC pin 4.

Next, refer to:

if __name__==’__main__’:
  
     LCD = LCD_1inch28()
     LCD.set_bl_pwm(65535)
     qmi8658 = QMI8658()
     Vbat    = ADC(Pin(Vbat_Pin))   
     rtc     = machine.RTC()

     ax=0
     ay=0
     az=0
     x=0
     y=0
     startx=50
     starty=100
     dotColor = LCD.black
     LCD.pixel(startx,starty, dotColor)
    
     def drawBox(sx,sy,ex,ey, colors):
         LCD.fill_rect(sx,sy, ex, ey, LCD.black)
         LCD.fill_rect(sx-2,sy-2, ex-2, ey-2, colors)
         LCD.rect(sx-2,sy-2, ex-2, ey-2, LCD.black)
        
     def getAcc():  #Read accelerometer and return either a positive or negative 1 for the x,y readings. Return Z for actual z axis
         abc = qmi8658.Read_XYZ()
         ax=abc[1]
         if ax<0:
             ax=-1
         if ax>0:
             ax=1
         ay=abc[2]
         if ay<0:
             ay=-1
         if ay>0:
             ay=1
         az=abc[3]
         print(ax,ay,az)
         return ax,ay,az
    
    def dPrint(text,x,y):
         LCD.text(text,x+1,y+1,LCD.black)
         LCD.text(text,x,y,LCD.white)
   
    LCD.fill(LCD.steel)
    dPrint(“Half-Byte Clock”,60,25)
     dPrint(“RP2040 LCD”,80,57)  
    
    LCD.show()

The first five, bolded lines above set up the LCD, and prepare the real time clock and grab the battery voltage.

Next, a handful of variables are initialized. 

The lines

    startx=50
    starty=100
    dotColor = LCD.black
     LCD.pixel(startx,starty, dotColor)
    

Tell the LCD interface where to start and what the color will be.

DrawBox is a function that, when given start and end coordinates and color, will draw two boxes: one black, which is the shadow and one that is filled with the supplied color. The offset for the shadow is –2 pixels, so be sure to not allow the shadow box to draw offscreen.  There is not error checking as of yet (I was lazy.) 

getACC gets accelerometer values.

dPrint does for text what drawbox does for boxes: It prints text with a shadow.

Finally, the last four lines of this snippet will clear the screen, print two lines of text and then refresh the screen.  In order for anything to show on the screen, you must first send the info to the screen and then you have to the LCD.Show() to force the refresh.

The rest of the clock demo is as follows:

while (True):
         # Get the date and time
         timestamp = rtc.datetime()
        
         # Split out date and time so they can be displayed separately
         datestring = “%04d/%02d/%02d “%(timestamp[0:3])
         timestring = “%02d:%02d:%02d “%(timestamp[4:7])
        
         # Draw the box to display the date and time (box has drop shadow)
         drawBox(60,106,137,29,LCD.darkblue)
        
         # Show the date and time in the box we just drew
         dPrint(“Date:” + datestring, 64, 110)
         dPrint(“Time:” + timestring, 64, 120)
        
         # Get the battery voltage and display it
         sx=78
         sy=213
         ex=83
         ey=18
         drawBox(sx,sy,ex,ey,LCD.red)
         reading = Vbat.read_u16()*3.3/65535*2
         LCD.text(“Vbat={:.2f}”.format(reading),80,215,LCD.black)
        
         # Get the temperature, then display it
         reading     = sensor_temp.read_u16() * conversion_factor       
         temperature = (27 – (reading – 0.706)/0.001721)
         temperature = (temperature * 1.8) + 8
        theTemp     = str(round(temperature,0))
        
         drawBox(70,87,115,19,LCD.red)
         dPrint(“Temp: ” + theTemp , 72,89)
        
        LCD.show()

The While sets up the loop to run indefinitely (it will always evaluate to true) and, thus, update the info on the screen.

        # Get the date and time
        timestamp = rtc.datetime()
       
        # Split out date and time so they can be displayed separately
        datestring = “%04d/%02d/%02d “%(timestamp[0:3])
         timestring = “%02d:%02d:%02d “%(timestamp[4:7])
        
         # Draw the box to display the date and time (box has drop shadow)
         drawBox(60,106,137,29,LCD.darkblue)
        
        # Show the date and time in the box we just drew
         dPrint(“Date:” + datestring, 64, 110)
         dPrint(“Time:” + timestring, 64, 120)

This code grabs the current system date and time.  It splits them into date and time variables.  It uses standard formatting characters to format the time and the date.  We draw our three d box and make it dark blue.  Next, we shadow print the date and time on different lines inside the box.

Reading the battery voltage is fairly straight forward as well:

        # Get the battery voltage and display it
         sx=78
         sy=213
         ex=83
        ey=18
        drawBox(sx,sy,ex,ey,LCD.red)
        reading = Vbat.read_u16()*3.3/65535*2
        LCD.text(“Vbat={:.2f}”.format(reading),80,215,LCD.black)

First, we set the location variables, then draw the box with a red background.  We read the battery voltage using the conversion factor (reading * 3.3/65535 * 2) and, finally, display it rounded to two decimal places.

Next, we get and display the temperature:

        # Get the temperature, then display it
         reading     = sensor_temp.read_u16() * conversion_factor       
         temperature = (27 – (reading – 0.706)/0.001721)
         temperature = (temperature * 1.8) + 8
        theTemp     = str(round(temperature,0))
        
        drawBox(70,87,115,19,LCD.red)
         dPrint(“Temp: ” + theTemp , 72,89)

Reading the temperature value also involves the conversion factor we used in the battery reading.  We then convert from celsius (because…) and then round the value for display.

The very last line updates the screen and we then repeat.

There’s a lot of value in this little device.  I am thinking of even writing a tiny game using the accelerometer, something like moving the marble through the maze and dropping it into the hole at the end of the maze.

Below are some tricks and tables containing useful bits like color values, and how to get at the hardware.

Reading the accelerometer
and gyroscope

get the values for

xyz=qmi8658.Read_XYZ()

        #
Play with the accelerometer

        ax=getAcc()[0]  # Call our
function and get the x,y and z values

        ay=getAcc()[1]

        az=getAcc()[2]

        x=x+ax

        y=y+ay

        if
y<0:   # stay on the
screen

            y=starty

        if
y>239:

            y=starty

           

        if
x<0:   # stay on the
screen

            x=startx

        if
x>239:

            x=startx

           

        print (startx+x,y)

        LCD.pixel(x+startx,y+starty,LCD.white)  # Plot our point

Initialize the qmi object

qmi8658=QMI8658()

Accelerometer X axis

xyz[0]

Accelerometer Y axis

xyz[1]

Accelerometer Z axis

xyz[2]

Gyroscope X

xyz[3]

Gyroscope y

xyz[4]

Gyroscope z

xyz[5]

Color
Table

WHITE        

0xFFFF

BLACK        

0x0000

BLUE          

0x001F

BRED          

0XF81F

GRED          

0XFFE0

GBLUE         

0X07FF

RED           

0xF800

MAGENTA     

0xF81F

GREEN         

0x07E0

CYAN          

0x7FFF

YELLOW        

0xFFE0

BROWN         

0XBC40

BRRED         

0XFC07

GRAY         

0X8430

STEEL

0x1805

LCYAN

0x180f

Circular screen
specific

Clear screen to a color:

LCD.fill(LCD.white)

Draw a rectangle and fill with color:

LCD.fill_rect(0,0,240,40,LCD.red)

Put text on screen at x,y and color

LCD.text(“RP2040-LCD-1.28”,60,25,LCD.white)

Update the screen

LCD.show()

Initialize screen object

LCD = LCD_1inch28()

LCD.set_bl_pwm(65535)

Set pixel

LCD.pixel(x,y,LCD.black)

Draw rectangle

LCD.rect(sx,sy,ex,ey,color)       

Draw line

LCD.line(sx,sy,ex,ey,color)       

Temperature
read
:

# Source:
Electrocredible.com, Language: MicroPython


from machine import ADC


import time adc = machine.ADC(4)



while True:


ADC_voltage = adc.read_u16() *
(3.3 / (65535))


temperature_celcius = 27 –
(ADC_voltage – 0.706)/0.001721


temp_fahrenheit=32+(1.8*temperature_celcius)


print(“Temperature: {}°C
{}°F”.


format(temperature_celcius,temp_fahrenheit))


time.sleep_ms(500)

Pinout:

rp2040Circular


Links:

RP2040-LCD-1.28 – Waveshare Wiki

Demo

Drawing

Datasheet

Official Raspberry Pi Documents

Raspberry Pi Demo

Developing Software

Thonny


Leave a comment