Adsense HTML/JavaScript

Wednesday, December 16, 2020

ESP32-S2/CircuitPython: display on IPS screen (ST7789/SPI) using adafruit_st7789/displayio lib


This post show how to program in CircuitPython run on ESP32-S2(nanoESP32-S2 dev. board) to display on 1.14" 135x240 IPS Screen with ST7789V driver/SPI interface, using adafruit_st7789/displayio library.

The display is 1.14" 135x240 (RGB) IPS (SKU: MSP1141), details refer to : LCDwiki - 1.14inch IPS Module

Connection:


Library:

In this exercise, adafruit_st7789 and adafruit_display_text of Adafruit CircuitPython Library Bundle is needed.

Visit https://circuitpython.org/libraries, download the appropriate bundle for your version of CircuitPython.

Unzip the file, copy adafruit_st7789.mpy and adafruit_display_text folder to the lib folder on your CIRCUITPY drive.

Example code:

Copy below examples code to code.py on CIRCUITPY drive to run.

spiST7789

"""
Example of CircuitPython/ESP32-S2 (run on nanoESP32-S2)
to display on 1.14" 135x240 (RGB) IPS screen
with ST7789 driver via SPI interface.

Connection between nanoESP32 and
the IPS screen, with ST7789 SPI interface.
3V3  - BLK (backlight, always on)
IO37 - CS
IO38 - DC
IO39 - RES
IO40 - SDA
IO41 - SCL
3V3  - VCC
GND  - GND
"""

import os
import board
import time
import terminalio
import displayio
import busio
from adafruit_display_text import label
#from adafruit_st7789 import ST7789
import adafruit_st7789

print("==============================")
print(os.uname())
print("Hello nanoESP32-S2/CircuitPython ST7789 SPI IPS Display")
print(adafruit_st7789.__name__ + " version: " + adafruit_st7789.__version__)
print()

# Release any resources currently in use for the displays
displayio.release_displays()

tft_cs = board.IO37
tft_dc = board.IO38
tft_res = board.IO39
spi_mosi = board.IO40
spi_clk = board.IO41

"""
classbusio.SPI(clock: microcontroller.Pin,
                MOSI: Optional[microcontroller.Pin] = None,
                MISO: Optional[microcontroller.Pin] = None)
"""
spi = busio.SPI(spi_clk, MOSI=spi_mosi)

display_bus = displayio.FourWire(
    spi, command=tft_dc, chip_select=tft_cs, reset=tft_res
)

#I get the parameters by guessing and trying
#display = ST7789(display_bus, width=135, height=240, rowstart=40, colstart=53)
display = adafruit_st7789.ST7789(display_bus,
                    width=135, height=240,
                    rowstart=40, colstart=53)

# Make the display context
splash = displayio.Group(max_size=10)
display.show(splash)

color_bitmap = displayio.Bitmap(135, 240, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x00FF00

bg_sprite = displayio.TileGrid(color_bitmap, pixel_shader=color_palette, x=0, y=0)
splash.append(bg_sprite)

# Draw a smaller inner rectangle
inner_bitmap = displayio.Bitmap(133, 238, 1)
inner_palette = displayio.Palette(1)
inner_palette[0] = 0x0000FF
inner_sprite = displayio.TileGrid(inner_bitmap, pixel_shader=inner_palette, x=1, y=1)
splash.append(inner_sprite)

# Draw a label
text_group1 = displayio.Group(max_size=10, scale=2, x=20, y=40)
text1 = "ESP32-S2"
text_area1 = label.Label(terminalio.FONT, text=text1, color=0xFF0000)
text_group1.append(text_area1)  # Subgroup for text scaling
# Draw a label
text_group2 = displayio.Group(max_size=10, scale=1, x=20, y=60)
text2 = "CircuitPython"
text_area2 = label.Label(terminalio.FONT, text=text2, color=0xFFFFFF)
text_group2.append(text_area2)  # Subgroup for text scaling

# Draw a label
text_group3 = displayio.Group(max_size=10, scale=1, x=20, y=100)
text3 = adafruit_st7789.__name__
text_area3 = label.Label(terminalio.FONT, text=text3, color=0x0000000)
text_group3.append(text_area3)  # Subgroup for text scaling
# Draw a label
text_group4 = displayio.Group(max_size=10, scale=2, x=20, y=120)
text4 = adafruit_st7789.__version__
text_area4 = label.Label(terminalio.FONT, text=text4, color=0x000000)
text_group4.append(text_area4)  # Subgroup for text scaling

splash.append(text_group1)
splash.append(text_group2)
splash.append(text_group3)
splash.append(text_group4)

time.sleep(3.0)

rot = 0
while True:
    time.sleep(5.0)
    rot = rot + 90
    if (rot>=360):
        rot =0
    display.rotation = rot
    

spiST7789_bitmap
"""
Example of CircuitPython/ESP32-S2 (run on nanoESP32-S2)
to display on 1.14" 135x240 (RGB) IPS screen
with ST7789 driver via SPI interface.

Connection between nanoESP32 and
the IPS screen, with ST7789 SPI interface.
3V3  - BLK (backlight, always on)
IO37 - CS
IO38 - DC
IO39 - RES
IO40 - SDA
IO41 - SCL
3V3  - VCC
GND  - GND
"""

import os
import board
import time
import terminalio
import displayio
import busio
from adafruit_display_text import label
import adafruit_st7789

print("==============================")
print(os.uname())
print("Hello nanoESP32-S2/CircuitPython ST7789 SPI IPS Display")
print(adafruit_st7789.__name__ + " version: " + adafruit_st7789.__version__)
print()

# Release any resources currently in use for the displays
displayio.release_displays()

tft_cs = board.IO37
tft_dc = board.IO38
tft_res = board.IO39
spi_mosi = board.IO40
spi_clk = board.IO41

"""
classbusio.SPI(clock: microcontroller.Pin,
                MOSI: Optional[microcontroller.Pin] = None,
                MISO: Optional[microcontroller.Pin] = None)
"""
spi = busio.SPI(spi_clk, MOSI=spi_mosi)

display_bus = displayio.FourWire(
    spi, command=tft_dc, chip_select=tft_cs, reset=tft_res
)

display = adafruit_st7789.ST7789(display_bus,
                    width=135, height=240,
                    rowstart=40, colstart=53)
display.rotation = 270

group = displayio.Group(max_size=10)
display.show(group)

bitmap = displayio.Bitmap(240, 135, 135)

palette = displayio.Palette(135)
for p in range(135):
    palette[p] = (0x010000*p) + (0x0100*p) + p

for y in range(135):
    for x in range(240):
        bitmap[x,y] = y
        
tileGrid = displayio.TileGrid(bitmap, pixel_shader=palette, x=0, y=0)
group.append(tileGrid)

time.sleep(3.0)

while True:
    for p in range(135):
        palette[p] = p
    time.sleep(3.0)

    for p in range(135):
        palette[p] = 0x0100 * p
    time.sleep(3.0)

    for p in range(135):
        palette[p] = 0x010000 * p
    time.sleep(3.0)

spiST7789_terminal
"""
Example of CircuitPython/ESP32-S2 (run on nanoESP32-S2)
to display on 1.14" 135x240 (RGB) IPS screen
with ST7789 driver via SPI interface.

Connection between nanoESP32 and
the IPS screen, with ST7789 SPI interface.
3V3  - BLK (backlight, always on)
IO37 - CS
IO38 - DC
IO39 - RES
IO40 - SDA
IO41 - SCL
3V3  - VCC
GND  - GND
"""

import os
import board
import time
import terminalio
import displayio
import busio
from adafruit_display_text import label
#from adafruit_st7789 import ST7789
import adafruit_st7789

print("==============================")
print(os.uname())
print("Hello nanoESP32-S2/CircuitPython ST7789 SPI IPS Display")
print(adafruit_st7789.__name__ + " version: " + adafruit_st7789.__version__)
print()

# Release any resources currently in use for the displays
displayio.release_displays()

tft_cs = board.IO37
tft_dc = board.IO38
tft_res = board.IO39
spi_mosi = board.IO40
spi_clk = board.IO41

"""
classbusio.SPI(clock: microcontroller.Pin,
                MOSI: Optional[microcontroller.Pin] = None,
                MISO: Optional[microcontroller.Pin] = None)
"""
spi = busio.SPI(spi_clk, MOSI=spi_mosi)

display_bus = displayio.FourWire(
    spi, command=tft_dc, chip_select=tft_cs, reset=tft_res
)

#I get the parameters by guessing and trying
#display = ST7789(display_bus, width=135, height=240, rowstart=40, colstart=53)
display = adafruit_st7789.ST7789(display_bus,
                    width=135, height=240,
                    rowstart=40, colstart=53)
display.rotation = 270

"""
Now click on to activate Serial Console,
Press any key to enter the REPL.
You would have a terminal that you could type at and have the screen update.
"""


~ More example of ESP32-S2/CircuitPython

Saturday, December 12, 2020

nanoESP32-S2/CircuitPython: I2C and 128x64 SSD1306 OLED

Trying I2C on nanoESP32-S2/CircuitPython, and display on 128x64 OLED with I2C SSD1306 driver (3.3V), using adafruit_ssd1306.mpy and adafruit_framebuf.mpy of Adafruit CircuitPython Library Bundle.

Connection between nanoESP32-S2 and 128x64 I2C SSD1306 OLED:

ESP32-S2 - OLED
3V3 VCC
GND GND
IO40           SDA
IO41           SCL 

Exercise to 128x64 OLED with I2C SSD1306 driver:

import time
import os
import board
import busio
import adafruit_ssd1306
# lib needed:
# adafruit_ssd1306.mpy
# adafruit_framebuf.mpy

# Helper function to draw a circle from a given position with a given radius
# This is an implementation of the midpoint circle algorithm,
# see https://en.wikipedia.org/wiki/Midpoint_circle_algorithm#C_example for details
def draw_circle(xpos0, ypos0, rad, col=1):
    x = rad - 1
    y = 0
    dx = 1
    dy = 1
    err = dx - (rad << 1)
    while x >= y:
        display.pixel(xpos0 + x, ypos0 + y, col)
        display.pixel(xpos0 + y, ypos0 + x, col)
        display.pixel(xpos0 - y, ypos0 + x, col)
        display.pixel(xpos0 - x, ypos0 + y, col)
        display.pixel(xpos0 - x, ypos0 - y, col)
        display.pixel(xpos0 - y, ypos0 - x, col)
        display.pixel(xpos0 + y, ypos0 - x, col)
        display.pixel(xpos0 + x, ypos0 - y, col)
        if err <= 0:
            y += 1
            err += dy
            dy += 2
        if err > 0:
            x -= 1
            dx += 2
            err += dx - (rad << 1)
    display.show()

print("==============================")
print(os.uname())
print("Hello nanoESP32-S2/CircuitPython ssd1306 OLED example")
print(adafruit_ssd1306.__name__ + "version: " + adafruit_ssd1306.__version__)
print()

WIDTH = 128
HEIGHT = 64
CENTER_X = int(WIDTH/2)
CENTER_Y = int(HEIGHT/2)

SDA = board.IO40
SCL = board.IO41
i2c = busio.I2C(SCL, SDA)

if(i2c.try_lock()):
    print("i2c.scan(): " + str(i2c.scan()))
    i2c.unlock()

display = adafruit_ssd1306.SSD1306_I2C(WIDTH, HEIGHT, i2c)

display.fill(0)
display.show()
time.sleep(1.0)
display.fill(1)
display.show()
time.sleep(1.0)
display.fill(0)
display.show()
time.sleep(1.0)

for y in range(HEIGHT):
    for x in range(WIDTH):
        display.pixel(x, y, 1)
    display.show()
    
draw_circle(CENTER_X, CENTER_Y, 25, 0)

print("- bye -\n")

~ more exercises on nanoESP32-S2/CircuitPython.



Remark about ESP32-S2 pull-up resistor for I2C:

As mentioned in Espressif document ESP-IDF Programming Guide :  API Reference > Peripherals API > I2C DriverESP32-S2’s internal pull-ups are in the range of tens of kOhm, which is, in most cases, insufficient for use as I2C pull-ups. Users are advised to use external pull-ups with values described in the I2C specification.

As I understand, most I2C SSD1306 OLED module have pull-up resistors. It's no external resistor needed in this exercise.

Next:






Monday, December 7, 2020

nanoESP32-S2/CircuitPython exercise: to control external NeoPixel

Previous exercise show how to install Adafruit CircuitPython Library Bundle to nanoESP32-S2 dev. board, and drive the onboard NeoPixel. This post show how to drive external NeoPixel (Ring and Square).

IO45 is used to drive the 8X NeoPixel Ring. connect to its DI.
IO42 is used to drive the 4X4 NeoPixel Square, connect to its DI.
IO18 is used to drive the onboard NeoPixel.
In the exercise of ADCNeoPixel, IO1 is used as analog input, convert to digital and display on 8X NeoPixel Ring.

All the NeoPixels have color order of  GRB, so have to set pixel_order=neopixel.GRB.

Just copy the code to code.py on your nanoESP32-S2 board, save to run.

ADCNeoPixel.py
import time
import os
import microcontroller
import board
import analogio
import neopixel

adc01=analogio.AnalogIn(board.IO1)

NumOfPixel_ring = 8

def offAllPixel():
    pixel_ring.fill((0, 0, 0))
    onboard_pixel[0] = (0, 0, 0)
    pixel_ring.show()
    onboard_pixel.show()

print("==============================")
print(os.uname())
print("Hello nanoESP32-S2/CircuitPython NeoPixel/ADC example")
print("cpu.temperature: " + str(microcontroller.cpu.temperature))
print("neopixel version: " + neopixel.__version__)
print()

ref = adc01.reference_voltage
print("reference voltage: " + str(ref))

# Create the NeoPixel objects
# onboard NeoPixel = IO18
onboard_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, 
                    auto_write=False,
                    brightness=0.02,
                    pixel_order=neopixel.GRB)
                    
pixel_ring = neopixel.NeoPixel(board.IO45, NumOfPixel_ring, 
                    auto_write=False,
                    brightness=0.3,
                    pixel_order=neopixel.GRB)

offAllPixel()
#blink neopixels to start
time.sleep(0.5)
onboard_pixel[0] = (255, 0, 0)
onboard_pixel.show()
time.sleep(0.5)
onboard_pixel[0] = (0, 255, 0)
onboard_pixel.show()
time.sleep(0.5)
onboard_pixel[0] = (20, 0, 255)
onboard_pixel.show()
time.sleep(0.5)
onboard_pixel[0] = (0, 0, 0)
onboard_pixel.show()

pixel_ring.fill((0, 0, 0))
pixel_ring.show()
time.sleep(1.0)

while True:
    value01=adc01.value
    step = int(value01/8192)
    offAllPixel()
    for i in range(step):
        pixel_ring[i] = (255, 255, 255)
    pixel_ring.show()
    
    onboard_pixel.brightness = step/10
    onboard_pixel[0] = (0, 0, 255)
    onboard_pixel.show()

    time.sleep(0.1)

print("- bye -\n")


testNeopixel.py
import time
import os
import microcontroller
import neopixel
import board
import random

NumOfPixel_ring = 8
NumOfPixel_sq = 16

def offAllPixel():
    fillAllPixel(0, 0, 0)
    
def fillAllPixel(r, g, b):
    pixel_ring.fill((r, g, b))
    pixel_sq.fill((r, g, b))
    onboard_pixel[0] = (r, g, b)


print("==============================")
print(os.uname())
print("Hello nanoESP32-S2/CircuitPython NeoPixel example")
print("Test on:")
print("Onboard NEOPIXEL")
print("8X NeoPixel Ring")
print("4X4 NeoPixel Square")
print("cpu.temperature: " + str(microcontroller.cpu.temperature))
print()
print("neopixel version: " + neopixel.__version__)
print()

# Create the NeoPixel objects
# onboard NeoPixel = IO18
onboard_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, 
                    brightness=0.1,
                    pixel_order=neopixel.GRB)
                    
pixel_ring = neopixel.NeoPixel(board.IO45, NumOfPixel_ring, 
                    brightness=0.1,
                    pixel_order=neopixel.GRB)
                    
pixel_sq = neopixel.NeoPixel(board.IO42, NumOfPixel_sq, 
                    brightness=0.1, pixel_order=neopixel.GRB)

offAllPixel()

#blink neopixels to start
time.sleep(0.5)
onboard_pixel[0] = (255, 0, 0)
time.sleep(0.5)
onboard_pixel[0] = (0, 255, 0)
time.sleep(0.5)
onboard_pixel[0] = (20, 0, 255)
time.sleep(0.5)
onboard_pixel[0] = (0, 0, 0)

for i in range(NumOfPixel_ring):
    pixel_ring.fill((0, 0, 0))
    pixel_ring[i] = (255, 255, 255)
    time.sleep(0.5)
pixel_ring.fill((0, 0, 0))

for i in range(NumOfPixel_sq):
    pixel_sq.fill((0, 0, 0))
    pixel_sq[i] = (255, 255, 255)
    time.sleep(0.5)
pixel_sq.fill((0, 0, 0))
time.sleep(0.5)

fillAllPixel(255, 0, 0)
time.sleep(1.0)
fillAllPixel(0, 255, 0)
time.sleep(1.0)
fillAllPixel(0, 0, 255)
time.sleep(1.0)

offAllPixel()
time.sleep(2.0)

print("- bye -\n")

randomNeopixel.py
import time
import os
import microcontroller
import neopixel
import board
import random

NumOfPixel_ring = 8
NumOfPixel_sq = 16

def offAllPixel():
    fillAllPixel(0, 0, 0)
    
def fillAllPixel(r, g, b):
    pixel_ring.fill((r, g, b))
    pixel_sq.fill((r, g, b))
    onboard_pixel[0] = (r, g, b)
    
def randomNeopixel():

    for r in range(2000):
        r = random.randint(0, 255)
        g = random.randint(0, 255)
        b = random.randint(0, 255)
        
        onboard_pixel[0] = (r, g, b)
        i = random.randint(0, NumOfPixel_ring-1)
        pixel_ring[i] = (r, g, b)
        i = random.randint(0, NumOfPixel_sq-1)
        pixel_sq[i] = (r, g, b)
        
        time.sleep(0.2)


print("==============================")
print(os.uname())
print("Hello nanoESP32-S2/CircuitPython NeoPixel example")
print("Generate random color on:")
print("Onboard NEOPIXEL")
print("8X NeoPixel Ring")
print("4X4 NeoPixel Square")
print("cpu.temperature: " + str(microcontroller.cpu.temperature))
print()
print("neopixel version: " + neopixel.__version__)
print()

# Create the NeoPixel objects
# onboard NeoPixel = IO18
onboard_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1, 
                    brightness=0.05,
                    pixel_order=neopixel.GRB)
                    
pixel_ring = neopixel.NeoPixel(board.IO45, NumOfPixel_ring, 
                    brightness=0.05,
                    pixel_order=neopixel.GRB)
                    
pixel_sq = neopixel.NeoPixel(board.IO42, NumOfPixel_sq, 
                    brightness=0.05, pixel_order=neopixel.GRB)

offAllPixel()

#blink neopixels to start
time.sleep(0.5)
onboard_pixel[0] = (255, 0, 0)
time.sleep(0.5)
onboard_pixel[0] = (0, 255, 0)
time.sleep(0.5)
onboard_pixel[0] = (20, 0, 255)
time.sleep(0.5)
onboard_pixel[0] = (0, 0, 0)

randomNeopixel()

offAllPixel()
time.sleep(2.0)

print("- bye -\n")


Related:

Sunday, December 6, 2020

nanoESP32-S2/CircuitPython exercise: ADC (Analog to Digital Converter)

This exercise run on nanoESP32-S2/CircuitPython, read analog input from IO1, and convert to voltage. Then display on Mu editor's Plotter.

import time
import os
import microcontroller
import board
import analogio

adc01=analogio.AnalogIn(board.IO1)

print("==============================")
print(os.uname())
print("Hello nanoESP32-S2/CircuitPython ADC example")
print("cpu.temperature: " + str(microcontroller.cpu.temperature))
print()

ref = adc01.reference_voltage
print("reference voltage: " + str(ref))

while True:
    value01=adc01.value
    voltage = ref * value01/65535
    print((ref, voltage))
    time.sleep(0.5)

print("- bye -\n")


remark:
As shown in the video, the ADC measured voltage cannot reached 3.3V.
 
I checked the voltage on IO1, is 3.3V. But the returned value of adc01 (analogio.AnalogIn(board.IO1)) is 51871, correspond 2.6V.
 

To get the reference voltage used by the ADC, e can read the property analogio.AnalogIn.reference_voltage.

For ADC values in CircuitPython you’ll find they’re all put into the range of 16-bit unsigned values. This means the possible values you’ll read from the ADC fall within the range of 0 to 65535 (or 2^16 - 1).

(ref: CircuitPython Basics: Analog Inputs & Outputs > Analog to Digital Converter (Inputs))


Next:

Saturday, December 5, 2020

TinyML: Using Machine Learning on Microcontroller to recognize speech@Remoticon 2020


Running a neural network on a micro-controller might seem absurd, but it’s possible (and has some great uses!). In this workshop, we’ll train a neural network to recognize one of several spoken words, convert it to a TensorFlow Lite model, and load it onto an ARM micro-controller (STM32 Nucleo-L476RG board), where it will listen for and respond to the wake word in real-time. Prior knowledge of machine learning is not necessary for this workshop, but it can be helpful to understand how the neural network operates.

Instructor: Shawn Hymel
Shawn is an electrical and embedded engineer, freelance content creator, and instructor. He and Harris Kenny host the podcast, Hello Blink Show, where they discuss various aspects of starting a business from sales to marketing to hiring. From 2013 to 2018, Shawn worked for SparkFun Electronics designing open-source PCBs, writing firmware, teaching concepts on video, and creating tutorials. Shawn started his own company, Skal Risa, LLC, to help companies create compelling technical content in electronics and embedded systems. He is also an advocate for enriching education through STEM and believes that the best marketing comes from teaching. He can be found giving talks, running workshops, and swing dancing in his free time.

Read the article on Hackaday:
https://hackaday.com/?p=450862

All example code and slides:
https://github.com/ShawnHymel/ei-keyword-spotting

Workshop project page:
https://hackaday.io/project/175078-remoticon-tiny-ml

Music by Rich Hogben



Friday, December 4, 2020

Mu editor Plotter

The Plotter of Mu editor is a graph tool to visualise numeric data which your code may create.

To activate the plotter in those modes that support it, simply toggle it on or off by clicking the “Plotter” button.

When the plotter is active it begins to listen for a valid source of data. This could be your Python program, something in the REPL or serial data coming from a connected device.

In all instances the pattern of valid data is the same: a Python tuple containing only numeric data printed to a new line. In case you’re wondering, a tuple in Python is a collection of data enclosed by parentheis, and looks like this: (1, 2, 3).

Each item in the tuple will be plotted on a separate line in the line chart. So, if you have three sources of data, each time you emit a tuple it will have three numbers in it, one for each source of data.


CircuitPython 6.1.0 Beta 2 released

CircuitPython 6.1.0 Beta 2 is released (2020-12-03). The downloads page is here, select the correct file and language for your board. 

As mentioned in the Release Notes for 6.1.0-beta.2, Please use 6.0.x if you need a stable version of CircuitPython on all ports except ESP32-S2. Please use this release or newer for the ESP32-S2

To Install CircuitPython to nanoESP32-S2, refer here.

For more exercise of using ESP32-S2, refer here.