Generic ESP32 Examples

These examples run on a ESP32 board with a 320x240 display. They are were tested using a SparkFun ESP32 Thing and a Waveshare 2 inch LCD ST7789 Module. You may need to modify the pin use for your device.

esp32_320x240/lines.py

 1"""
 2lines.py
 3
 4    Draws lines and rectangles in random colors at random locations on the
 5    display.
 6
 7"""
 8import random
 9from machine import Pin, SPI
10import st7789py as st7789
11
12
13def main():
14    # configure display
15
16    spi = SPI(1, baudrate=31250000, sck=Pin(18), mosi=Pin(19))
17
18    tft = st7789.ST7789(
19        spi,
20        320,
21        240,
22        reset=Pin(4, Pin.OUT),
23        cs=Pin(13, Pin.OUT),
24        dc=Pin(12, Pin.OUT),
25        backlight=Pin(15, Pin.OUT),
26        rotation=0)
27
28    tft.fill(st7789.BLUE)
29
30    while True:
31        tft.line(
32            random.randint(0, tft.width),
33            random.randint(0, tft.height),
34            random.randint(0, tft.width),
35            random.randint(0, tft.height),
36            st7789.color565(
37                random.getrandbits(8),
38                random.getrandbits(8),
39                random.getrandbits(8)
40                )
41            )
42
43        width = random.randint(0, tft.width // 2)
44        height = random.randint(0, tft.height // 2)
45        col = random.randint(0, tft.width - width)
46        row = random.randint(0, tft.height - height)
47        tft.fill_rect(
48            col,
49            row,
50            width,
51            height,
52            st7789.color565(
53                random.getrandbits(8),
54                random.getrandbits(8),
55                random.getrandbits(8)
56            )
57        )
58
59
60main()

esp32_320x240/hello.py

 1"""
 2hello.py
 3
 4    Writes "Hello!" in random colors at random locations on the display.
 5
 6"""
 7import random
 8from machine import Pin, SPI
 9import st7789py as st7789
10
11# Choose a font
12
13# from romfonts import vga1_8x8 as font
14# from romfonts import vga2_8x8 as font
15# from romfonts import vga1_8x16 as font
16# from romfonts import vga2_8x16 as font
17# from romfonts import vga1_16x16 as font
18# from romfonts import vga1_bold_16x16 as font
19# from romfonts import vga2_16x16 as font
20# from romfonts import vga2_bold_16x16 as font
21# from romfonts import vga1_16x32 as font
22# from romfonts import vga1_bold_16x32 as font
23# from romfonts import vga2_16x32 as font
24from romfonts import vga2_bold_16x32 as font
25
26
27def main():
28    spi = SPI(1, baudrate=31250000, sck=Pin(18), mosi=Pin(19))
29
30    tft = st7789.ST7789(
31        spi,
32        320,
33        240,
34        reset=Pin(4, Pin.OUT),
35        cs=Pin(13, Pin.OUT),
36        dc=Pin(12, Pin.OUT),
37        backlight=Pin(15, Pin.OUT),
38        rotation=0)
39
40    while True:
41        for rotation in range(4):
42            tft.rotation(rotation)
43            tft.fill(0)
44            col_max = tft.width - font.WIDTH*6
45            row_max = tft.height - font.HEIGHT
46
47            for _ in range(100):
48                tft.text(
49                    font,
50                    "Hello!",
51                    random.randint(0, col_max),
52                    random.randint(0, row_max),
53                    st7789.color565(
54                        random.getrandbits(8),
55                        random.getrandbits(8),
56                        random.getrandbits(8)),
57                    st7789.color565(
58                        random.getrandbits(8),
59                        random.getrandbits(8),
60                        random.getrandbits(8))
61                )
62
63
64main()

esp32_320x240/feathers.py

  1"""
  2feathers.py
  3
  4    Smoothly scroll mirrored rainbow colored random curves across the display.
  5
  6"""
  7
  8import random
  9import math
 10import utime
 11from machine import Pin, SPI
 12import st7789py as st7789
 13
 14
 15def between(left, right, along):
 16    """returns a point along the curve from left to right"""
 17    dist = (1 - math.cos(along * math.pi)) / 2
 18    return left * (1 - dist) + right * dist
 19
 20
 21def color_wheel(position):
 22    """returns a 565 color from the given position of the color wheel"""
 23    position = (255 - position) % 255
 24
 25    if position < 85:
 26        return st7789.color565(255 - position * 3, 0, position * 3)
 27
 28    if position < 170:
 29        position -= 85
 30        return st7789.color565(0, position * 3, 255 - position * 3)
 31
 32    position -= 170
 33    return st7789.color565(position * 3, 255 - position * 3, 0)
 34
 35
 36def main():
 37    '''
 38    The big show!
 39    '''
 40    #enable display and clear screen
 41
 42    spi = SPI(1, baudrate=31250000, sck=Pin(18), mosi=Pin(19))
 43
 44    tft = st7789.ST7789(
 45        spi,
 46        320,
 47        240,
 48        reset=Pin(4, Pin.OUT),
 49        cs=Pin(13, Pin.OUT),
 50        dc=Pin(12, Pin.OUT),
 51        backlight=Pin(15, Pin.OUT),
 52        rotation=1)
 53
 54    tft.fill(st7789.BLACK)      # clear screen
 55
 56    height = tft.height         # height of display in pixels
 57    width = tft.width           # width if display in pixels
 58
 59    tfa = 0                     # top free area when scrolling
 60    bfa = 0         	        # bottom free area when scrolling
 61
 62    scroll = 0                  # scroll position
 63    wheel = 0                   # color wheel position
 64
 65    tft.vscrdef(tfa, width, bfa)    # set scroll area
 66    tft.vscsad(scroll + tfa)        # set scroll position
 67    tft.fill(st7789.BLACK)          # clear screen
 68
 69    half = (height >> 1) - 1    # half the height of the dislay
 70    interval = 0                # steps between new points
 71    increment = 0               # increment per step
 72    counter = 1                 # step counter, overflow to start
 73    current_y = 0               # current_y value (right point)
 74    last_y = 0                  # last_y value (left point)
 75
 76    # segment offsets
 77    x_offsets = [x * (width // 8) -1 for x in range(2,9)]
 78
 79    while True:
 80        # when the counter exceeds the interval, save current_y to last_y,
 81        # choose a new random value for current_y between 0 and 1/2 the
 82        # height of the display, choose a new random interval then reset
 83        # the counter to 0
 84
 85        if counter > interval:
 86            last_y = current_y
 87            current_y = random.randint(0, half)
 88            counter = 0
 89            interval = random.randint(10, 100)
 90            increment = 1/interval      # increment per step
 91
 92        # clear the first column of the display and scroll it
 93        tft.vline(scroll, 0, height, st7789.BLACK)
 94        tft.vscsad(scroll + tfa)
 95
 96        # get the next point between last_y and current_y
 97        tween = int(between(last_y, current_y, counter * increment))
 98
 99        # draw mirrored pixels across the display at the offsets using the color_wheel effect
100        for i, x_offset in enumerate(x_offsets):
101            tft.pixel((scroll + x_offset) % width, half + tween, color_wheel(wheel+(i<<2)))
102            tft.pixel((scroll + x_offset) % width, half - tween, color_wheel(wheel+(i<<2)))
103
104        # increment scroll, counter, and wheel
105        scroll = (scroll + 1) % width
106        wheel = (wheel + 1) % 256
107        counter += 1
108
109
110main()

esp32_320x240/fonts.py

 1"""
 2fonts.py
 3
 4    Pages through all characters of four fonts on the display.
 5
 6"""
 7import utime
 8from machine import Pin, SPI
 9import st7789py as st7789
10
11# Choose fonts
12
13# from romfonts import vga1_8x8 as font
14from romfonts import vga2_8x8 as font1
15# from romfonts import vga1_8x16 as font
16from romfonts import vga2_8x16 as font2
17# from romfonts import vga1_16x16 as font
18# from romfonts import vga1_bold_16x16 as font
19# from romfonts import vga2_16x16 as font
20from romfonts import vga2_bold_16x16 as font3
21# from romfonts import vga1_16x32 as font
22# from romfonts import vga1_bold_16x32 as font
23# from romfonts import vga2_16x32 as font
24from romfonts import vga2_bold_16x32 as font4
25
26
27def main():
28    spi = SPI(1, baudrate=31250000, sck=Pin(18), mosi=Pin(19))
29
30    tft = st7789.ST7789(
31        spi,
32        320,
33        240,
34        reset=Pin(4, Pin.OUT),
35        cs=Pin(13, Pin.OUT),
36        dc=Pin(12, Pin.OUT),
37        backlight=Pin(15, Pin.OUT),
38        rotation=0)
39
40    tft.vscrdef(40, 240, 40)
41
42    while True:
43        for font in (font1, font2, font3, font4):
44            tft.fill(st7789.BLUE)
45            line = 0
46            col = 0
47
48            for char in range(font.FIRST, font.LAST):
49                tft.text(font, chr(char), col, line, st7789.WHITE, st7789.BLUE)
50                col += font.WIDTH
51                if col > tft.width - font.WIDTH:
52                    col = 0
53                    line += font.HEIGHT
54
55                    if line > tft.height-font.HEIGHT:
56                        utime.sleep(3)
57                        tft.fill(st7789.BLUE)
58                        line = 0
59                        col = 0
60
61            utime.sleep(3)
62
63
64main()

esp32_320x240/scroll.py

 1"""
 2fonts.py
 3
 4    Smoothly scrolls all font characters up the screen on the display.
 5    Only works with fonts with heights that are even multiples of
 6    the screen height, (i.e. 8 or 16 pixels high)
 7
 8"""
 9import utime
10import random
11from machine import Pin, SPI
12import st7789py as st7789
13
14# choose a font
15
16# from romfonts import vga1_8x8 as font
17# from romfonts import vga2_8x8 as font
18# from romfonts import vga1_8x16 as font
19# from romfonts import vga2_8x16 as font
20# from romfonts import vga1_16x16 as font
21# from romfonts import vga1_bold_16x16 as font
22# from romfonts import vga2_16x16 as font
23from romfonts import vga2_bold_16x16 as font
24
25
26def main():
27    spi = SPI(1, baudrate=31250000, sck=Pin(18), mosi=Pin(19))
28
29    tft = st7789.ST7789(
30        spi,
31        320,
32        240,
33        reset=Pin(4, Pin.OUT),
34        cs=Pin(13, Pin.OUT),
35        dc=Pin(12, Pin.OUT),
36        backlight=Pin(15, Pin.OUT),
37        rotation=0)
38
39    last_line = tft.height - font.HEIGHT
40    tfa = 0
41    tfb = 0
42    tft.vscrdef(tfa, 240, tfb)
43
44    tft.fill(st7789.BLUE)
45    scroll = 0
46    character = 0
47    while True:
48        tft.fill_rect(0, scroll, tft.width, 1, st7789.BLUE)
49
50        if scroll % font.HEIGHT == 0:
51            tft.text(
52                font,
53                '\\x{:02x}= {:s} '.format(character, chr(character)),
54                0,
55                (scroll + last_line) % tft.height,
56                st7789.WHITE,
57                st7789.BLUE)
58
59            character = character + 1 if character < 256 else 0
60
61        tft.vscsad(scroll + tfa)
62        scroll += 1
63
64        if scroll == tft.height:
65            scroll = 0
66
67        utime.sleep(0.01)
68
69
70main()

esp32_320x240/toasters.py

Flying toasters sprite demo using bitmaps created from spritesheet using the sprites2bitmap.py utility. See the maketoast shell script for the command line used to create the toast_bitmaps.py from the toasters.bmp image.

  1'''
  2toasters.py - Flying Toasters(ish) an ESP-32 and ST7789 240x320 display.
  3
  4    Uses spritesheet from CircuitPython_Flying_Toasters pendant project
  5    https://learn.adafruit.com/circuitpython-sprite-animation-pendant-mario-clouds-flying-toasters
  6
  7    Convert spritesheet bmp to tft.bitmap() method compatible python module using:
  8        python3 ./sprites2bitmap.py toasters.bmp 64 64 4 > toast_bitmaps.py
  9
 10'''
 11
 12import gc
 13import time
 14import random
 15from machine import Pin, SPI
 16import st7789
 17import toast_bitmaps
 18
 19TOASTER_FRAMES = [0, 1, 2, 3]
 20TOAST_FRAMES = [4]
 21
 22def collide(a_col, a_row, a_width, a_height, b_col, b_row, b_width, b_height):
 23    '''return true if two rectangles overlap'''
 24    return (a_col + a_width >= b_col and a_col <= b_col + b_width
 25            and a_row + a_height >= b_row and a_row <= b_row + b_height)
 26
 27def random_start(tft, sprites, bitmaps, num):
 28    '''
 29    Return a random location along the top or right of the screen, if that location would overlaps
 30    with another sprite return (0,0). This allows the other sprites to keep moving giving the next
 31    random_start a better chance to avoid a collision.
 32
 33    '''
 34    # 50/50 chance to try along the top/right half or along the right/top half of the screen
 35    if random.getrandbits(1):
 36        row = 1
 37        col = random.randint(bitmaps.WIDTH//2, tft.width()-bitmaps.WIDTH)
 38    else:
 39        col = tft.width() - bitmaps.WIDTH
 40        row = random.randint(1, tft.height() // 2)
 41
 42    if any(collide(
 43        col, row, bitmaps.WIDTH, bitmaps.HEIGHT,
 44        sprite.col, sprite.row, sprite.width, sprite.height)
 45        for sprite in sprites if num != sprite.num):
 46
 47        col = 0
 48        row = 0
 49
 50    return (col, row)
 51
 52def main():
 53
 54    class Toast():
 55        '''
 56        Toast class to keep track of toaster and toast sprites
 57        '''
 58        def __init__(self, sprites, bitmaps, frames):
 59            '''create new sprite in random location that does not overlap other sprites'''
 60            self.num = len(sprites)
 61            self.bitmaps = bitmaps
 62            self.frames = frames
 63            self.steps = len(frames)
 64            self.col, self.row  = random_start(tft, sprites, bitmaps, self.num)
 65            self.width = bitmaps.WIDTH
 66            self.height = bitmaps.HEIGHT
 67            self.last_col = self.col
 68            self.last_row = self.row
 69            self.step = random.randint(0, self.steps)
 70            self.dir_col = -random.randint(2, 5)
 71            self.dir_row = 2
 72            self.prev_dir_col = self.dir_col
 73            self.prev_dir_row = self.dir_row
 74            self.iceberg = 0
 75
 76        def clear(self):
 77            '''clear above and behind sprite'''
 78            tft.fill_rect(
 79                self.col, self.row-1, self.width, self.dir_row+1,
 80                st7789.BLACK)
 81
 82            tft.fill_rect(
 83                self.col+self.width+self.dir_col, self.row,
 84                -self.dir_col, self.height, st7789.BLACK)
 85
 86        def erase(self):
 87            '''erase last postion of sprite'''
 88            tft.fill_rect(
 89                self.last_col, self.last_row, self.width, self.height, st7789.BLACK)
 90
 91        def move(self, sprites):
 92            '''step frame and move sprite'''
 93
 94            if self.steps:
 95                self.step = (self.step + 1) % self.steps
 96
 97            self.last_col = self.col
 98            self.last_row = self.row
 99            new_col = self.col + self.dir_col
100            new_row = self.row + self.dir_row
101
102            # if new location collides with another sprite, change direction for 32 frames
103
104            for sprite in sprites:
105                if (
106                    self.num != sprite.num
107                    and collide(
108                        new_col, new_row, self.width, self.height,
109                        sprite.col, sprite.row, sprite.width, sprite.height,
110                    )
111                    and (self.col > sprite.col)):
112
113                    self.iceberg = 32
114                    self.dir_col = -1
115                    self.dir_row = 3
116                    new_col = self.col + self.dir_col
117                    new_row = self.row + self.dir_row
118
119            self.col = new_col
120            self.row = new_row
121
122            # if new location touches edge of screen, erase then set new start location
123            if self.col <= 0 or self.row > tft.height() - self.height:
124                self.erase()
125                self.dir_col = -random.randint(2, 5)
126                self.dir_row = 2
127                self.col, self.row  = random_start(tft, sprites, self.bitmaps, self.num)
128
129            # Track post collision direction change
130            if self.iceberg:
131                self.iceberg -= 1
132                if self.iceberg == 1:
133                    self.dir_col = self.prev_dir_col
134                    self.dir_row = self.prev_dir_row
135
136        def draw(self):
137            '''if the location is not 0,0 draw current frame of sprite at it's location'''
138            if self.col and self.row:
139                tft.bitmap(self.bitmaps, self.col, self.row, self.frames[self.step])
140
141    # configure spi interface
142    spi = SPI(1, baudrate=31250000, sck=Pin(18), mosi=Pin(19))
143
144    # configure display
145    tft = st7789.ST7789(
146        spi,
147        240,
148        320,
149        reset=Pin(4, Pin.OUT),
150        cs=Pin(13, Pin.OUT),
151        dc=Pin(12, Pin.OUT),
152        backlight=Pin(15, Pin.OUT),
153        rotation=1,
154        buffer_size=64*62*2)
155
156    # init and clear screen
157    tft.init()
158    tft.fill(st7789.BLACK)
159
160    # create toast spites and set animation frames
161    sprites = []
162    sprites.append(Toast(sprites, toast_bitmaps, TOAST_FRAMES))
163    sprites.append(Toast(sprites, toast_bitmaps, TOASTER_FRAMES))
164    sprites.append(Toast(sprites, toast_bitmaps, TOASTER_FRAMES))
165
166    # move and draw sprites
167
168    while True:
169        for sprite in sprites:
170            sprite.clear()
171            sprite.move(sprites)
172            sprite.draw()
173
174        gc.collect()
175        time.sleep(0.01)
176
177main()