Adsense HTML/JavaScript

Sunday, March 6, 2022

ESP32-C3/CircuitPython 7.2.0 + ST7735 TFT, display bmp in slideshow, and using displayio.OnDiskBitmap/adafruit_imageload.

Exercise of using CircuitPython 7.2.0 on  ESP32-C3-DevKitM-1 with 1.44" 128x128 ST7735 SPI TFT (KMR1441_SPI V2):
- display bmp in slideshow
- display bmp using OnDiskBitmap/adafruit_imageload

The display module used in this exercise is a with 1.44" 128x128 SPI TFT marked "KMR1441_SPI V2".


Connection:

Firstly, connect the display to ESP32-C3-DevKitM-1.

	ST7735		ESP32-C3-DevKitM-1
	------------------------------
	VCC		3V3
	GND		GND
	CS		IO10
	RESET		IO1
	A0		IO0
	SDA		IO3
	SCK		IO2
	LED		3V3

Library:

Visit CircuitPython Library page to download Bundle for Version 7.x, and extract it.

Copy the libraries to lib folder in Circuit device.
- adafruit_st7735r.mpy
- adafruit_display_text folder
- adafruit_slideshow.mpy
- adafruit_imageload folder

Exercise code:

cpyESP32C3_st7735_128x128.py, functional testing.

"""
CircuitPython 7.2.0 exercise run on  ESP32-C3-DevKitM-1
with 1.44" 128x128 (KMR1441_SPI V2)

ref:
adafruit/Adafruit_CircuitPython_ST7735R
https://github.com/adafruit/Adafruit_CircuitPython_ST7735R
"""

from sys import implementation as sysImplementation
import time
import os
import board
import busio
import displayio
import terminalio

from adafruit_st7735r import ST7735R as TFT_ST7735
from adafruit_st7735r import __name__ as ST7735_NAME
from adafruit_st7735r import __version__ as ST7735_VERSION

from adafruit_display_text import label

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

#Connection between ESP32-C3 and SPI ST7735 display
                        #marking on display
tft_sck = board.IO2     #SCK
tft_mosi = board.IO3    #SDA
tft_dc = board.IO0      #A0
tft_reset = board.IO1   #RESET
tft_cs = board.IO10     #CS
#Backlight (LED) connect to ESP32-C3 3V3
#TFT VCC - ESP32-C3 3V3
#TFT GND - ESP32-C3 GND

tft_spi = busio.SPI(clock=tft_sck, MOSI=tft_mosi)
display_bus = displayio.FourWire(
    tft_spi, command=tft_dc, chip_select=tft_cs, reset=tft_reset)

display = TFT_ST7735(display_bus, width=128, height=128,
                     rotation=90,
                     bgr=True)

strSys = sysImplementation[0] + ' ' + \
         str(sysImplementation[1][0]) +'.'+ \
         str(sysImplementation[1][1]) +'.'+ \
         str(sysImplementation[1][2])

print("==========================================")

print(strSys)
print('run on ' + os.uname()[4])
print('using', ST7735_NAME, ST7735_VERSION)
print("==========================================")

print(type(display))
print("display.width:  ", display.width)
print("display.height: ", display.height)

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

color_bitmap = displayio.Bitmap(display.width, display.height, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x000000
time.sleep(1)

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

for c in [["RED", 0xFF0000],
          ["GREEN", 0x00FF00],
          ["BLUE", 0x0000FF]]:
    print(c[0], " : ", hex(c[1]))
    color_palette[0] = c[1]
    time.sleep(2)

splash.remove(bg_sprite)
#---

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

color_bitmap = displayio.Bitmap(display.width, display.height, 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(display.width-2, display.height-2, 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(scale=1, x=5, y=10)
text1 = "ESP32-C3"
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(scale=1, x=5, y=25)
text2 = strSys
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(scale=1, x=5, y=40)
text3 = ST7735_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(scale=1, x=5, y=55)
text4 = ST7735_VERSION
text_area4 = label.Label(terminalio.FONT, text=text4, color=0x000000)
text_group4.append(text_area4)  # Subgroup for text scaling

text_group5 = displayio.Group(scale=1, x=5, y=70)
text5 = str(display.width) + " x " + str(display.height)
text_area5 = label.Label(terminalio.FONT, text=text5, color=0x000000)
text_group5.append(text_area5)  # Subgroup for text scaling

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

time.sleep(3.0)

rot = 90
while True:
    time.sleep(5.0)
    rot = rot + 90
    if (rot>=360):
        rot = 0
    display.rotation = rot
cpyESP32C3_st7735_slideshow.py, Test with exercise in "Creating Slideshows in CircuitPython".
"""
CircuitPython 7.2.0 exercise run on  ESP32-C3-DevKitM-1
with 1.44" 128x128 (KMR1441_SPI V2)
- slideshow

ref:
adafruit/Adafruit_CircuitPython_ST7735R
https://github.com/adafruit/Adafruit_CircuitPython_ST7735R
"""

from sys import implementation as sysImplementation
import time
import os
import board
import busio
import displayio
import terminalio

from adafruit_st7735r import ST7735R as TFT_ST7735
from adafruit_st7735r import __name__ as ST7735_NAME
from adafruit_st7735r import __version__ as ST7735_VERSION

from adafruit_display_text import label

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

#Connection between ESP32-C3 and SPI ST7735 display
                        #marking on display
tft_sck = board.IO2     #SCK
tft_mosi = board.IO3    #SDA
tft_dc = board.IO0      #A0
tft_reset = board.IO1   #RESET
tft_cs = board.IO10     #CS
#Backlight (LED) connect to ESP32-C3 3V3
#TFT VCC - ESP32-C3 3V3
#TFT GND - ESP32-C3 GND

tft_spi = busio.SPI(clock=tft_sck, MOSI=tft_mosi)
display_bus = displayio.FourWire(
    tft_spi, command=tft_dc, chip_select=tft_cs, reset=tft_reset)

display = TFT_ST7735(display_bus, width=128, height=128,
                     rotation=90,
                     bgr=True)

strSys = sysImplementation[0] + ' ' + \
         str(sysImplementation[1][0]) +'.'+ \
         str(sysImplementation[1][1]) +'.'+ \
         str(sysImplementation[1][2])

print("==========================================")

print(strSys)
print('run on ' + os.uname()[4])
print('using', ST7735_NAME, ST7735_VERSION)
print("==========================================")

print(type(display))
print("display.width:  ", display.width)
print("display.height: ", display.height)

#===============================
# SPDX-FileCopyrightText: 2019 Anne Barela for Adafruit Industries
#
# SPDX-License-Identifier: MIT

# CircuitPython Slideshow - uses the adafruit_slideshow.mpy library
#import board
from adafruit_slideshow import PlayBackOrder, SlideShow

# Create the slideshow object that plays through once alphabetically.
slideshow = SlideShow(display,
                      folder="/images",
                      loop=True,
                      order=PlayBackOrder.ALPHABETICAL,
                      dwell=5)

while slideshow.update():
    pass
cpyESP32C3_st7735_OnDiskBitmap.py, test with OnDiskBitmap example in "Display a Bitmap".
"""
CircuitPython 7.2.0 exercise run on  ESP32-C3-DevKitM-1
with 1.44" 128x128 (KMR1441_SPI V2)
- OnDiskBitmap

ref:
adafruit/Adafruit_CircuitPython_ST7735R
https://github.com/adafruit/Adafruit_CircuitPython_ST7735R
"""

from sys import implementation as sysImplementation
import time
import os
import board
import busio
import displayio
import terminalio

from adafruit_st7735r import ST7735R as TFT_ST7735
from adafruit_st7735r import __name__ as ST7735_NAME
from adafruit_st7735r import __version__ as ST7735_VERSION

from adafruit_display_text import label

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

#Connection between ESP32-C3 and SPI ST7735 display
                        #marking on display
tft_sck = board.IO2     #SCK
tft_mosi = board.IO3    #SDA
tft_dc = board.IO0      #A0
tft_reset = board.IO1   #RESET
tft_cs = board.IO10     #CS
#Backlight (LED) connect to ESP32-C3 3V3
#TFT VCC - ESP32-C3 3V3
#TFT GND - ESP32-C3 GND

tft_spi = busio.SPI(clock=tft_sck, MOSI=tft_mosi)
display_bus = displayio.FourWire(
    tft_spi, command=tft_dc, chip_select=tft_cs, reset=tft_reset)

display = TFT_ST7735(display_bus, width=128, height=128,
                     rotation=90,
                     bgr=True)

strSys = sysImplementation[0] + ' ' + \
         str(sysImplementation[1][0]) +'.'+ \
         str(sysImplementation[1][1]) +'.'+ \
         str(sysImplementation[1][2])

print("==========================================")

print(strSys)
print('run on ' + os.uname()[4])
print('using', ST7735_NAME, ST7735_VERSION)
print("==========================================")

print(type(display))
print("display.width:  ", display.width)
print("display.height: ", display.height)

#===============================
# SPDX-FileCopyrightText: 2019 Carter Nelson for Adafruit Industries
#
# SPDX-License-Identifier: MIT

#import board
#import displayio

#display = board.DISPLAY

# Future method for CircuitPython 7 onwards

# Setup the file as the bitmap data source
bitmap = displayio.OnDiskBitmap("images/002.bmp")

# Create a TileGrid to hold the bitmap
tile_grid = displayio.TileGrid(bitmap, pixel_shader=bitmap.pixel_shader)

# Create a Group to hold the TileGrid
group = displayio.Group()

# Add the TileGrid to the Group
group.append(tile_grid)

# Add the Group to the Display
display.show(group)

# Loop forever so you can enjoy your image
while True:
    pass
cpyESP32C3_st7735_ImageLoad.py, test with example of ImageLoad in "Display a Bitmap"
"""
CircuitPython 7.2.0 exercise run on  ESP32-C3-DevKitM-1
with 1.44" 128x128 (KMR1441_SPI V2)
- ImageLoad

ref:
adafruit/Adafruit_CircuitPython_ST7735R
https://github.com/adafruit/Adafruit_CircuitPython_ST7735R
"""

from sys import implementation as sysImplementation
import time
import os
import board
import busio
import displayio
import terminalio

from adafruit_st7735r import ST7735R as TFT_ST7735
from adafruit_st7735r import __name__ as ST7735_NAME
from adafruit_st7735r import __version__ as ST7735_VERSION

from adafruit_display_text import label

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

#Connection between ESP32-C3 and SPI ST7735 display
                        #marking on display
tft_sck = board.IO2     #SCK
tft_mosi = board.IO3    #SDA
tft_dc = board.IO0      #A0
tft_reset = board.IO1   #RESET
tft_cs = board.IO10     #CS
#Backlight (LED) connect to ESP32-C3 3V3
#TFT VCC - ESP32-C3 3V3
#TFT GND - ESP32-C3 GND

tft_spi = busio.SPI(clock=tft_sck, MOSI=tft_mosi)
display_bus = displayio.FourWire(
    tft_spi, command=tft_dc, chip_select=tft_cs, reset=tft_reset)

display = TFT_ST7735(display_bus, width=128, height=128,
                     rotation=90,
                     bgr=True)

strSys = sysImplementation[0] + ' ' + \
         str(sysImplementation[1][0]) +'.'+ \
         str(sysImplementation[1][1]) +'.'+ \
         str(sysImplementation[1][2])

print("==========================================")

print(strSys)
print('run on ' + os.uname()[4])
print('using', ST7735_NAME, ST7735_VERSION)
print("==========================================")

print(type(display))
print("display.width:  ", display.width)
print("display.height: ", display.height)

#===============================
# SPDX-FileCopyrightText: 2019 Carter Nelson for Adafruit Industries
#
# SPDX-License-Identifier: MIT

#import board
#import displayio
import adafruit_imageload

#display = board.DISPLAY

bitmap, palette = adafruit_imageload.load("images/003i.bmp",
                                          bitmap=displayio.Bitmap,
                                          palette=displayio.Palette)

# Create a TileGrid to hold the bitmap
tile_grid = displayio.TileGrid(bitmap, pixel_shader=palette)

# Create a Group to hold the TileGrid
group = displayio.Group()

# Add the TileGrid to the Group
group.append(tile_grid)

# Add the Group to the Display
display.show(group)

# Loop forever so you can enjoy your image
while True:
    pass


Sunday, February 27, 2022

Install CircuitPython 7.2.0 on ESP32-C3 (ESP32-C3-DevKitM-1/NodeMCU ESP-C3-32S-Kit)

CircuitPython 7.2.0 was released. With espressif ESP32-S3 and ESP32-C3 supported (considered alpha and will have bugs and missing functionality).


This post show how to install CircuitPython 7.2.0 on ESP32-C3-DevKitM-1 (ESP32-C3-MINI-1), using Raspberry Pi 4B running Raspberry Pi OS 32-bit (buster). Then test with exercise to read system info, control onboard RGB (NEOPIXEL), and 0.96" 80x160 IPS.



Download Firmware:

Visit CircuitPython Download page, search C3.

I can't find exact board named "ESP32-C3-DevKitM-1", so I try "ESP32-C3-DevKitC-1-N4 by Espressif". DOWNLOAD .BIN NOW under CircuitPython 7.2.0, it's adafruit-circuitpython-espressif_esp32c3_devkitm_1_n4-en_US-7.2.0.bin.

Identify USB port:

Before connect the board to USB.
Run the command to clear dmesg buffer -
$ sudo dmesg -c

Connect the ESP32-C3-DevKitM-1 board to USB.
Run dmesg again, the connected port will be shown -
$ dmesg

Install CircuitPython firmware using esptool:

esptool is needed to flash firmware on ESP devices.
~ Install esptool on Raspberry Pi OS (32 bit)

With esptool installed, you can check the ESP chip ID and Flash using commands:
$ esptool.py --chip auto --port /dev/ttyUSB0 chip_id
$ esptool.py --chip auto --port /dev/ttyUSB0 flash_id

To erase the flash, enter:
$ esptool.py --port /dev/ttyUSB0 erase_flash

To flash the firmware, I follow the command in MicroPython document > Getting started with MicroPython on the ESP32 > Deploying the firmware.
replace:
- chip to esp32c3
- port
- address start from 0x0
- file name
$ esptool.py --chip esp32c3 --port /dev/ttyUSB0 write_flash \-z 0x0 \
adafruit-circuitpython-espressif_esp32c3_devkitm_1_n4-en_US-7.2.0.bin
Exercise code:

cpyESP32C3_info.py, get CircuitPython info
"""
CircuitPython 7.2.0 exercise run on ESP32-C3,
get system info.
"""
import board
import sys
import os
"""
ref:
The entire table of ANSI color codes working in C:
https://gist.github.com/RabaDabaDoba/145049536f815903c79944599c6f952a
"""
class color:
   RED = '\033[1;31;48m'
   BLUE = '\033[1;34;48m'
   BLACK = '\033[1;30;48m'
   END = '\033[1;37;0m'

print(board.board_id)
print(sys.implementation[0] + ' ' +
      str(sys.implementation[1][0]) +'.'+
      str(sys.implementation[1][1]) +'.'+
      str(sys.implementation[1][2]))
print("==========================================")
info = color.RED + \
       sys.implementation[0] + ' ' + \
       os.uname()[3] + color.END + '\n' + \
       'run on ' + color.BLUE + os.uname()[4] + color.END
print(info)
print("==========================================")

print()

cpyESP32C3_NEOPIXEL.py, control onboard RGB (Neopixel).
import time
import os
import microcontroller
import neopixel
import board

def cycleNeopixel(wait):
    for r in range(255):
        pixel[0] = (r, 0, 0)
        time.sleep(wait)
    for r in range(255, 0, -1):
        pixel[0] = (r, 0, 0)
        time.sleep(wait)
        
    for g in range(255):
        pixel[0] = (0, g, 0)
        time.sleep(wait)
    for g in range(255, 0, -1):
        pixel[0] = (0, g, 0)
        time.sleep(wait)
        
    for b in range(255):
        pixel[0] = (0, 0, b)
        time.sleep(wait)
    for b in range(255, 0, -1):
        pixel[0] = (0, 0, b)
        time.sleep(wait)
        
print("==============================")
print("Hello ESP32-C3/CircuitPython NeoPixel exercise")
#print(os.uname())
for u in os.uname():
    print(u)
print()
print("neopixel version: " + neopixel.__version__)
print()

# Create the NeoPixel object
pixel = neopixel.NeoPixel(board.NEOPIXEL,
                          1,
                          pixel_order=neopixel.RGB)
pixel[0] = (0, 0, 0)
time.sleep(2.0)

cycleNeopixel(0.01)

pixel[0] = (0, 0, 0)
time.sleep(2.0)

print("- bye -\n")

cpyESP32C3_st7735_80x160.py, test with 0.96" 80x160 IPS.
"""
CircuitPython 7.2.0 exercise run on ESP32-C3
with unknown brand 0.96 inch 80x160 SPI ST7735 IPS

ref:
adafruit/Adafruit_CircuitPython_ST7735R
https://github.com/adafruit/Adafruit_CircuitPython_ST7735R
"""

from sys import implementation as sysImplementation
import time
import board
import busio
import displayio
import terminalio

from adafruit_st7735r import ST7735R as TFT_ST7735
from adafruit_st7735r import __name__ as ST7735_NAME
from adafruit_st7735r import __version__ as ST7735_VERSION

from adafruit_display_text import label

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

#Connection between ESP32-C3 and SPI ST7735 display
                        #marking on display
tft_sck = board.IO2     #SCL
tft_mosi = board.IO3    #SDA
tft_reset = board.IO0   #RES
tft_dc = board.IO1      #DC
tft_cs = board.IO10     #CS
#Backlight (BLK) connect to ESP32-C3 3V3
#TFT VCC - ESP32-C31 3V3
#TFT GND - ESP32-C3 GND

tft_spi = busio.SPI(clock=tft_sck, MOSI=tft_mosi)
display_bus = displayio.FourWire(
    tft_spi, command=tft_dc, chip_select=tft_cs, reset=tft_reset
)

# I find out colrstart/rowstart by try/error and retry
display = TFT_ST7735(display_bus, width=160, height=80,
                     colstart=26, rowstart=1,
                     rotation=90,
                     invert=True
                     )

print(type(display))
print("display.width:  ", display.width)
print("display.height: ", display.height)

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

color_bitmap = displayio.Bitmap(display.width, display.height, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0x000000
time.sleep(1)

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

for c in [["RED", 0xFF0000],
          ["GREEN", 0x00FF00],
          ["BLUE", 0x0000FF]]:
    print(c[0], " : ", hex(c[1]))
    color_palette[0] = c[1]
    time.sleep(2)

splash.remove(bg_sprite)
#---

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

color_bitmap = displayio.Bitmap(display.width, display.height, 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(display.width-2, display.height-2, 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(scale=1, x=5, y=10)
text1 = "ESP32-C3"
text_area1 = label.Label(terminalio.FONT, text=text1, color=0xFF0000)
text_group1.append(text_area1)  # Subgroup for text scaling

# Draw a label
strSys = sysImplementation[0] + ' ' + \
         str(sysImplementation[1][0]) +'.'+ \
         str(sysImplementation[1][1]) +'.'+ \
         str(sysImplementation[1][2])
text_group2 = displayio.Group(scale=1, x=5, y=25)
text2 = strSys
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(scale=1, x=5, y=40)
text3 = ST7735_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(scale=1, x=5, y=55)
text4 = ST7735_VERSION
text_area4 = label.Label(terminalio.FONT, text=text4, color=0x000000)
text_group4.append(text_area4)  # Subgroup for text scaling

text_group5 = displayio.Group(scale=1, x=5, y=70)
text5 = str(display.width) + " x " + str(display.height)
text_area5 = label.Label(terminalio.FONT, text=text5, color=0x000000)
text_group5.append(text_area5)  # Subgroup for text scaling

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

time.sleep(3.0)

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

Download firmware for "ESP-C3-32S by Ai-Thinker",  adafruit-circuitpython-ai_thinker_esp32-c3s-en_US-7.2.0.bin.



Replace the file name in flashing command:
$ esptool.py --chip esp32c3 --port /dev/ttyUSB0 write_flash \-z 0x0
adafruit-circuitpython-ai_thinker_esp32-c3s-en_US-7.2.0.bin
cpyESP32C3_info.py run on NodeMCU ESP-C3-32S-Kit:


check the board assignment:





next:
ESP32-C3/CircuitPython 7.2.0 + ST7735 TFT, display bmp in slideshow, and using displayio.OnDiskBitmap/adafruit_imageload.

Wednesday, February 16, 2022

ESP32-C3/arduino-esp32 + 0.96" 80x160 IPS, create custom class extends Adafruit ST7735 library.

 This exercise run on ESP32-C3-DevKitM-1 in arduino-esp32 2.0.2, display on unknown brand 0.96" 80x160 IPS. 

Library used:
- Adafruit ST7735 and ST7789 Library
- Adafruit GFX Library


Connect:

	TFT_ST7735 ESP32-C3
	-------------------
	VCC        3V3
	GND        GND
	CS         10
	RESET      9
	A0(DC)     8
	SDA        6
	SCK        4
	LED        3V3
	
In my test, if Adafruit_ST7735 is used directly (as in another exercise "ESP32-C3/arduino-esp32 to display on ST7735 and ST7789 SPI LCDs") with option INITR_MINI160x80, the drawing area is shifted and REG and BLUE is swapped.


In this exercise, I create a custom class (MyST7735) extend Adafruit_ST7735:
- call setColRowStart(26, 1) in init() to correct the shifting.
- override setRotation() function, to use ST7735_MADCTL_BGR instead of ST77xx_MADCTL_RGB.

Exercise code:

ESP32C3_ST7735_MINI160x80.ino
/*
 * arduino-esp32 exercise run on ESP32-C3-DevKitM-1,
 * display on 0.96" 80x160 TFT with SPI ST7735,
 * using 'Adafruit ST7735 and ST7789 Library'.
 * 
 * Base on Adafruit ST7735 and ST7789 Library 1.9.1
 * 
 * Fix offset and color with
 * custom class (MyST7735) extending Adafruit_ST7735,
 * to call protected function setColRowStart(),
 * and override setRotation().
 * 
 * ref:
 * Adafruit-ST7735-Library:
 * https://github.com/adafruit/Adafruit-ST7735-Library
 * Adafruit-GFX-Library:
 * https://github.com/adafruit/Adafruit-GFX-Library
 *
 */

#include <Adafruit_GFX.h>    // Core graphics library

//#include <Adafruit_ST7735.h> // Hardware-specific library for ST7735
#include "MyST7735.h"

#define TFT_CS_ST7735   10
#define TFT_RST_ST7735  9 // Or set to -1 and connect to Arduino RESET pin
#define TFT_CS_ST7789   2
#define TFT_RST_ST7789  3   
#define TFT_DC          8

/*
 * Erik:
 * on ESP32-C3-DevKitM-1
 * SS:   7  - not used
 * MOSI: 6
 * MISO: 5  - not used
 * SCK:  4
 * ===================
 * Conection:
 * 
 * TFT_ST7735 ESP32-C3
 * -------------------
 * VCC        3V3
 * GND        GND
 * CS         10
 * RESET      9
 * A0(DC)     8
 * SDA        6
 * SCK        4
 * LED        3V3
 *
 */

//Adafruit_ST7735 tft_ST7735 = Adafruit_ST7735(TFT_CS_ST7735, TFT_DC, TFT_RST_ST7735);
MyST7735 tft_ST7735 = MyST7735(TFT_CS_ST7735, TFT_DC, TFT_RST_ST7735);

void setup(void) {
  delay(500);
  Serial.begin(115200);
  delay(500);
  Serial.print(F("Hello!\n"));
  Serial.print(F("0.96 80X160 (RGB) IPS Test\n\n"));

  //tft_ST7735.initR(INITR_MINI160x80); // Init ST7735S mini display
  tft_ST7735.init();
  
  tft_ST7735.invertDisplay(true);
  tft_ST7735.setRotation(3);
  
  // SPI speed defaults to SPI_DEFAULT_FREQ defined in the library, you can override it here
  // Note that speed allowable depends on chip and quality of wiring, if you go too fast,
  // you may end up with a black screen some times, or all the time.
  //tft.setSPISpeed(40000000);

  Serial.println(F("Initialized"));


  // large block of text
  tft_ST7735.fillScreen(ST77XX_BLACK);
  tft_ST7735.setTextWrap(true);
  tft_ST7735.setTextColor(ST77XX_WHITE);

  drawBorderline();

  delay(1000);
  
  tft_ST7735.setCursor(0, 0);
  tft_ST7735.print("Hello ESP32C3");

  tft_ST7735.setCursor(0, 20);
  tft_ST7735.print("Chip Model: " + String(ESP.getChipModel()));
  tft_ST7735.setCursor(0, 30);
  tft_ST7735.print("rotation: " + String(tft_ST7735.getRotation()));
  tft_ST7735.setCursor(0, 40);
  tft_ST7735.print(String(tft_ST7735.width()) + " x " + String(tft_ST7735.height()));

  delay(1000);
  colorTest();

  delay(1000);
  for(int offset=0; offset<tft_ST7735.height()/2-10; offset++){
    int col;
    if(offset%4 == 0)
      col = ST77XX_WHITE;
    else
      col = ST77XX_BLACK;
      
    tft_ST7735.drawRect(offset, offset, 
                 tft_ST7735.width()-1-2*offset, tft_ST7735.height()-1-2*offset,
                 col);
    delay(100);
  }

  delay(2000);

  tft_ST7735.setRotation(0);

  tft_ST7735.fillScreen(ST77XX_BLACK);
  tft_ST7735.setTextWrap(true);
  tft_ST7735.setTextColor(ST77XX_WHITE);

  tft_ST7735.drawRect(0, 0, 
                 tft_ST7735.width()-1, tft_ST7735.height()-1,
                 ST77XX_WHITE);

  delay(1000);
  colorTest();

  drawBorderline();

  Serial.println("\n\n- setup() end -\n");
  delay(1000);
}

void drawBorderline(){
  tft_ST7735.fillScreen(ST77XX_BLACK);
  tft_ST7735.drawRect(0, 0, 
                 tft_ST7735.width()-1, tft_ST7735.height()-1,
                 ST77XX_WHITE);
  Serial.println();
  Serial.printf("\nrotation %d", tft_ST7735.getRotation());
  Serial.printf("\nwidth %d", tft_ST7735.width());
  Serial.printf("\nheight %d", tft_ST7735.height());
}

void colorTest(){
  tft_ST7735.fillScreen(ST77XX_RED);
  tft_ST7735.setCursor(50, 50);
  tft_ST7735.print("RED");
  delay(1000);
  tft_ST7735.fillScreen(ST77XX_GREEN);
  tft_ST7735.setCursor(50, 50);
  tft_ST7735.print("GREEN");
  delay(1000);
  tft_ST7735.fillScreen(ST77XX_BLUE);
  tft_ST7735.setCursor(50, 50);
  tft_ST7735.print("BLUE");
  delay(1000);
}

/*
 * Cnvert R, G, B (in uint8_t)
 * to color (in uint16_t) in 565 format for Adafruit GFX library
 * rrrrrggg gggbbbbb
 */
uint16_t convertRGBtoColor(uint8_t r, uint8_t g, uint8_t b){
  return (((r & 0xF8) << 8) |
          ((g & 0xFC) << 3) |
          (b >> 3));
}

void loop() {
  
  tft_ST7735.setRotation(1);  
  for (int x = 0; x < tft_ST7735.width(); x++){
    int c =255 *  x/tft_ST7735.width();
    uint16_t color = convertRGBtoColor(c, c, c);
    tft_ST7735.drawLine(x, 0, x, tft_ST7735.height()-1, color);
  }
  delay(1000);
  
  tft_ST7735.setRotation(2);
  for (int x = 0; x < tft_ST7735.width(); x++){
    int r =255 *  x/tft_ST7735.width();
    uint16_t color = convertRGBtoColor(r, 0, 0);
    tft_ST7735.drawLine(x, 0, x, tft_ST7735.height()-1, color);
  }
  delay(1000);
  
  tft_ST7735.setRotation(3);
  for (int x = 0; x < tft_ST7735.width(); x++){
    int g =255 *  x/tft_ST7735.width();
    uint16_t color = convertRGBtoColor(0, g, 0);
    tft_ST7735.drawLine(x, 0, x, tft_ST7735.height()-1, color);
  }
  delay(1000);
  
  tft_ST7735.setRotation(4);
  for (int x = 0; x < tft_ST7735.width(); x++){
    int b =255 *  x/tft_ST7735.width();
    uint16_t color = convertRGBtoColor(0, 0, b);
    tft_ST7735.drawLine(x, 0, x, tft_ST7735.height()-1, color);
  }
  delay(1000);
}


MyST7735.cpp
#include "MyST7735.h"
#include "Adafruit_ST7735.h"
#include "Adafruit_ST77xx.h"

MyST7735::MyST7735(int8_t cs, int8_t dc, int8_t rst)
    : Adafruit_ST7735(cs, dc, rst) {}

/*************************************************************************
    init() to fix offset
    by calling protected function setColRowStart().
*************************************************************************/

void MyST7735::init(void) {
  initR(INITR_MINI160x80);
  setColRowStart(26, 1);
}

/*************************************************************************
    override setRotation() to fix color order using ST7735_MADCTL_BGR.
    To make it simple, option INITR_MINI160x80 is assumed and handled only.
*************************************************************************/
void MyST7735::setRotation(uint8_t m) {
  uint8_t madctl = 0;

  rotation = m & 3; // can't be higher than 3

  switch (rotation) {
  case 0:

    madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MY | ST7735_MADCTL_BGR;
    _height = ST7735_TFTHEIGHT_160;
    _width = ST7735_TFTWIDTH_80;
    _xstart = _colstart;
    _ystart = _rowstart;
    break;
  case 1:

    madctl = ST77XX_MADCTL_MY | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR;
    _width = ST7735_TFTHEIGHT_160;
    _height = ST7735_TFTWIDTH_80;
    _ystart = _colstart;
    _xstart = _rowstart;
    break;
  case 2:
    madctl = ST7735_MADCTL_BGR;
    _height = ST7735_TFTHEIGHT_160;
    _width = ST7735_TFTWIDTH_80;
    _xstart = _colstart;
    _ystart = _rowstart;
    break;
  case 3:

    madctl = ST77XX_MADCTL_MX | ST77XX_MADCTL_MV | ST7735_MADCTL_BGR;
    _width = ST7735_TFTHEIGHT_160;
    _height = ST7735_TFTWIDTH_80;
    _ystart = _colstart;
    _xstart = _rowstart;
    break;
  }

  sendCommand(ST77XX_MADCTL, &madctl, 1);
}


MyST7735.h
#ifndef _MyST7735_
#define _MyST7735_

#include "Adafruit_ST77xx.h"
#include "Adafruit_ST7735.h"

#define ST7735_MADCTL_BGR 0x08
#define ST77XX_MADCTL_RGB 0x00

class MyST7735 : public Adafruit_ST7735 {

  public:
  MyST7735(int8_t cs, int8_t dc, int8_t rst);
  void init(void);
  void setRotation(uint8_t m);
  
};

#endif // _MyST7735_


Wednesday, December 15, 2021

BLE between ESP32/ESP32C3 (arduino-esp32), notify DHT11 reading of temperature & humidity.

This exercise run on ESP32/ESP32-C3 to perform BLE communication, to notify data update. Both run on arduino-esp32 framework.


The BLE server run on ESP32-DevKitC V4, read DHT11 temperature & humidity, display on ST7789 SPI TFT, and notify connected client. The BLE client run on ESP32-C3-DevKitM-1, connect to BLE server, update SSD1306 I2C OLED once notified data updated.

Basically, the server copy from my former exercise "ESP32 + DHT11 temperature & humidity sensor with display on ST7789 and BLE function", the client copy from BLE_client example. 

But with my original setting (follow BLE_notify example) in server side:

  pAdvertising->setScanResponse(false);
  pAdvertising->setMinPreferred(0x0);
both advertisedDevice.haveServiceUUID() and advertisedDevice.isAdvertisingService(serviceUUID) in client return false. So the client cannot find the server.

To solve it, I change the setting (follow BLE_server example):
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);
  pAdvertising->setMinPreferred(0x12);
then both advertisedDevice.haveServiceUUID() and advertisedDevice.isAdvertisingService(serviceUUID) in client return true, such that the server can be found.

ESP32_DHT_ST789_graphic_BLE_2021-12-14.ino, server side run on ESP32-DevKitC V4.
/*
   Execise run on ESP32 (ESP32-DevKitC V4) with arduino-esp32 2.0.1,
   read DHT11 Humidity & Temperature Sensor,
   and display on ST7789 SPI TFT, 2" IPS 240x320, with graph.
   BLE function added.

   Library needed:
   - DHT sensor library for ESPx by beegee_tokyo
   - Adafruit ST7735 and ST7789 Library by Adafruit
   - Adafruit GFX Library by Adafruit

    Modify from examples DHT_ESP32 of DHT sensor library for ESPx

    Connection between DHT11 and ESP32 (GPIO#)
    -----------------------------------------------
    DHT11         ESP32
    -----         -----
    VCC*          3V3
    DATA**        32
    NC
    GND           GND

 *  * - depends on module, my DHT11 module is 3V3~5V operate.

 *  ** - depends on your module, maybe you have to add a
    pull-up resistor (~10K Ohm) betwee DATA and VCC.

    Connection between ST7789 SPI and ESP32 (GPIO#)
    -----------------------------------------------
    ST7789 SPI    ESP32
    ----------    -----
    GND           GND
    VCC           3V3
    SCL           18
    SDA           23
    RES           26
    DC            25
    CS            33
    BLK           3V3

*/
#include "DHTesp.h"
#include <Ticker.h>
#include <Adafruit_GFX.h>    // Core graphics library
#include <Adafruit_ST7789.h> // Hardware-specific library for ST7789
#include <Fonts/FreeMonoBold12pt7b.h>
#include <SPI.h>

#include <BLEDevice.h>
#include <BLEServer.h>
#include <BLEUtils.h>
#include <BLE2902.h>

#ifndef ESP32
#pragma message(THIS EXAMPLE IS FOR ESP32 ONLY!)
#error Select ESP32 board.
#endif

DHTesp dht;

void tempTask(void *pvParameters);
bool getTemperature();
void triggerGetTemp();

/** Task handle for the light value read task */
TaskHandle_t tempTaskHandle = NULL;
/** Ticker for temperature reading */
Ticker tempTicker;
/** Comfort profile */
ComfortState cf;
/** Flag if task should run */
bool tasksEnabled = false;
/** Pin number for DHT11 data pin */
int dhtPin = 32;  //17;

//hardware SPI MOSI   23
//hardware SPI SCK    18
#define TFT_CS        33
#define TFT_RST       26
#define TFT_DC        25
Adafruit_ST7789 tft = Adafruit_ST7789(TFT_CS, TFT_DC, TFT_RST);

bool rqsUpdate = false;
TempAndHumidity updateValues;

unsigned long prvUpdateMillis;

#define FRAME_TOPX    0
#define FRAME_TOPY    200
#define FRAME_WIDTH   240
#define FRAME_HEIGHT  100
#define FRAME_BOTTOMY FRAME_TOPY + FRAME_HEIGHT
#define SCR_HEIGHT    320

int idx = 0;
#define IDX_MAX     240

BLEServer* pServer = NULL;
BLECharacteristic* pCharacteristic = NULL;
BLECharacteristic* pChar_temp = NULL;
BLECharacteristic* pChar_humi = NULL;
bool deviceConnected = false;
bool oldDeviceConnected = false;

// See the following for generating UUIDs:
// https://www.uuidgenerator.net/

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

class MyServerCallbacks: public BLEServerCallbacks {
    void onConnect(BLEServer* pServer) {
      deviceConnected = true;
      BLEDevice::startAdvertising();
      Serial.println("MyServerCallbacks.onConnect");
    };

    void onDisconnect(BLEServer* pServer) {
      deviceConnected = false;
      Serial.println("MyServerCallbacks.onDisconnect");
    }
};

/**
   initTemp
   Setup DHT library
   Setup task and timer for repeated measurement
   @return bool
      true if task and timer are started
      false if task or timer couldn't be started
*/
bool initTemp() {
  byte resultValue = 0;
  // Initialize temperature sensor
  dht.setup(dhtPin, DHTesp::DHT11);
  Serial.println("DHT initiated");

  // Start task to get temperature
  xTaskCreatePinnedToCore(
    tempTask,                       /* Function to implement the task */
    "tempTask ",                    /* Name of the task */
    4000,                           /* Stack size in words */
    NULL,                           /* Task input parameter */
    5,                              /* Priority of the task */
    &tempTaskHandle,                /* Task handle. */
    1);                             /* Core where the task should run */

  if (tempTaskHandle == NULL) {
    Serial.println("Failed to start task for temperature update");
    return false;
  } else {
    // Start update of environment data every XX seconds
    tempTicker.attach(2, triggerGetTemp);
  }
  return true;
}

/**
   triggerGetTemp
   Sets flag dhtUpdated to true for handling in loop()
   called by Ticker getTempTimer
*/
void triggerGetTemp() {
  if (tempTaskHandle != NULL) {
    xTaskResumeFromISR(tempTaskHandle);
  }
}

/**
   Task to reads temperature from DHT11 sensor
   @param pvParameters
      pointer to task parameters
*/
void tempTask(void *pvParameters) {
  Serial.println("tempTask loop started");
  while (1) // tempTask loop
  {
    if (tasksEnabled) {
      // Get temperature values
      getTemperature();
    }
    // Got sleep again
    vTaskSuspend(NULL);
  }
}

/**
   getTemperature
   Reads temperature from DHT11 sensor
   @return bool
      true if temperature could be aquired
      false if aquisition failed
*/
bool getTemperature() {
  // Reading temperature for humidity takes about 250 milliseconds!
  // Sensor readings may also be up to 2 seconds 'old' (it's a very slow sensor)
  TempAndHumidity newValues = dht.getTempAndHumidity();
  // Check if any reads failed and exit early (to try again).
  if (dht.getStatus() != 0) {
    Serial.println("DHT11 error status: " + String(dht.getStatusString()));
    return false;
  }

  rqsUpdate = true;
  updateValues = newValues;
  return true;
}

void setup()
{
  Serial.begin(115200);
  Serial.println();
  Serial.println("DHT ESP32 example with tasks");

  //init DHT
  initTemp();
  // Signal end of setup() to tasks
  tasksEnabled = true;

  //init BLE
  // Create the BLE Device
  BLEDevice::init("ESP32-DHT11");

  // Create the BLE Server
  pServer = BLEDevice::createServer();

  pServer->setCallbacks(new MyServerCallbacks());

  // Create the BLE Service
  BLEService *pService = pServer->createService(SERVICE_UUID);

  // Create a BLE Characteristic for temp and humi
  pChar_temp = pService->createCharacteristic(
                 CHAR_UUID_TEMP,
                 BLECharacteristic::PROPERTY_READ   |
                 BLECharacteristic::PROPERTY_WRITE  |
                 BLECharacteristic::PROPERTY_NOTIFY |
                 BLECharacteristic::PROPERTY_INDICATE
               );
  pChar_humi = pService->createCharacteristic(
                 CHAR_UUID_HUMI,
                 BLECharacteristic::PROPERTY_READ   |
                 BLECharacteristic::PROPERTY_WRITE  |
                 BLECharacteristic::PROPERTY_NOTIFY |
                 BLECharacteristic::PROPERTY_INDICATE
               );

  pChar_temp->addDescriptor(new BLE2902());
  pChar_humi->addDescriptor(new BLE2902());

  // Start the service
  pService->start();

  // Start advertising
  BLEAdvertising *pAdvertising = BLEDevice::getAdvertising();
  pAdvertising->addServiceUUID(SERVICE_UUID);
  
  // Erik updated@2021-12-14
  //pAdvertising->setScanResponse(false);
  //pAdvertising->setMinPreferred(0x0);  // set value to 0x00 to not advertise this parameter
  
  pAdvertising->setScanResponse(true);
  pAdvertising->setMinPreferred(0x06);  // functions that help with iPhone connections issue
  pAdvertising->setMinPreferred(0x12);
  //print for information only
  Serial.println("pAdvertising->setScanResponse(true)");
  Serial.println("pAdvertising->setMinPreferred(0x06)");
  Serial.println("pAdvertising->setMinPreferred(0x12)");
  
  BLEDevice::startAdvertising();
  Serial.println("Waiting a client connection to notify...");


  //init ST7789
  tft.init(240, 320);           // Init ST7789 320x240
  tft.setRotation(2);
  tft.setFont(&FreeMonoBold12pt7b);
  tft.setTextWrap(true);

  tft.fillScreen(ST77XX_RED);
  delay(300);
  tft.fillScreen(ST77XX_GREEN);
  delay(300);
  tft.fillScreen(ST77XX_BLUE);
  delay(300);

  tft.setCursor(0, 0);
  tft.setTextColor(ST77XX_RED);
  tft.print("\n");
  tft.print("ESP32 + DHT11 + ST7789\n");

  prvUpdateMillis = millis();

}

void loop() {
  if (!tasksEnabled) {
    // Wait 2 seconds to let system settle down
    delay(2000);
    // Enable task that will read values from the DHT sensor
    tasksEnabled = true;
    if (tempTaskHandle != NULL) {
      vTaskResume(tempTaskHandle);
    }
  }

  if (rqsUpdate) {

    unsigned long curUpdateMillis = millis();

    tft.fillRect(0, 53, 240, 75, ST77XX_BLUE );

    tft.setCursor(0, 70);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" temp.: " + String(updateValues.temperature));
    tft.setCursor(0, 95);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" humi.: " + String(updateValues.humidity));
    tft.setCursor(0, 115);
    tft.setTextColor(ST77XX_WHITE);
    tft.print(" mills.: " + String(curUpdateMillis - prvUpdateMillis));
    prvUpdateMillis = curUpdateMillis;


    if (idx == 0) {
      tft.fillRect(FRAME_TOPX, FRAME_TOPY,
                   FRAME_WIDTH, SCR_HEIGHT - FRAME_TOPY,
                   ST77XX_BLUE);
    }

    tft.drawLine(
      FRAME_TOPX + idx, FRAME_BOTTOMY,
      FRAME_TOPX + idx, FRAME_BOTTOMY - (int)updateValues.temperature,
      ST77XX_WHITE);

    idx++;
    if (idx >= IDX_MAX)
      idx = 0;

    char bufTemp[5];
    char bufHumi[5];
    //convert floating point value to String
    dtostrf(updateValues.temperature, 0, 2, bufTemp);
    dtostrf(updateValues.humidity, 0, 2, bufHumi);

    pChar_temp->setValue((uint8_t*)bufTemp, 5);
    pChar_temp->notify();
    pChar_humi->setValue((uint8_t*)bufHumi, 5);
    pChar_humi->notify();

    //Serial.println(" T:" + String(updateValues.temperature) + " H:" + String(updateValues.humidity));
    rqsUpdate = false;


  }

  // disconnecting
  if (!deviceConnected && oldDeviceConnected) {
    Serial.println("disconnecting");
    delay(500); // give the bluetooth stack the chance to get things ready
    pServer->startAdvertising(); // restart advertising
    Serial.println("start advertising");
    oldDeviceConnected = deviceConnected;
  }
  // connecting
  if (deviceConnected && !oldDeviceConnected) {
    Serial.println("connecting");
    // do stuff here on connecting
    oldDeviceConnected = deviceConnected;
  }

  yield();
}


ESP32C3_BLE_client.ino, client side run on ESP32-C3-DevKitM-1. Copy from BLE_client example, with UUID updated.
/**
 * A BLE client example that is rich in capabilities.
 * There is a lot new capabilities implemented.
 * author unknown
 * updated by chegewara
 */

#include "BLEDevice.h"
//#include "BLEScan.h"

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

// The remote service we wish to connect to.
static BLEUUID serviceUUID(SERVICE_UUID);
// The characteristic of the remote service we are interested in.
static BLEUUID    charUUID(CHAR_UUID_TEMP);

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteCharacteristic;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {
    Serial.print("Notify callback for characteristic ");
    Serial.print(pBLERemoteCharacteristic->getUUID().toString().c_str());
    Serial.print(" of data length ");
    Serial.println(length);
    Serial.print("data: ");
    Serial.println((char*)pData);
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    pClient->connect(myDevice);  // if you pass BLEAdvertisedDevice instead of address, it will be recognized type of peer device address (public or private)
    Serial.println(" - Connected to server");
    pClient->setMTU(517); //set client to request maximum MTU from server (default is 23 otherwise)
  
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service of the remote BLE server.
    pRemoteCharacteristic = pRemoteService->getCharacteristic(charUUID);
    if (pRemoteCharacteristic == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our characteristic");

    // Read the value of the characteristic.
    if(pRemoteCharacteristic->canRead()) {
      std::string value = pRemoteCharacteristic->readValue();
      Serial.print("The characteristic value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteCharacteristic->canNotify())
      pRemoteCharacteristic->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    if(advertisedDevice.haveServiceUUID()){
      Serial.println("- advertisedDevice.haveServiceUUID()");
    }
    if(advertisedDevice.isAdvertisingService(serviceUUID)){
      Serial.println("- advertisedDevice.isAdvertisingService(serviceUUID)");
    }

    // We have found a device, let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

      Serial.println("*** device found ***");

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server; there is nothin more we will do.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {
    String newValue = "Time since boot: " + String(millis()/1000);
    Serial.println("Setting new characteristic value to \"" + newValue + "\"");
    
    // Set the characteristic's value to be the array of bytes that is actually a string.
    pRemoteCharacteristic->writeValue(newValue.c_str(), newValue.length());
  }else if(doScan){
    BLEDevice::getScan()->start(0);  // this is just example to start scan after disconnect, most likely there is better way to do it in arduino
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop




ESP32C3_BLE_DHT11_client.ino, updated to recognize temperature & humidity.
/**
 * modified from BLE_client
 * to work with ESP32_DHT_ST789_graphic_BLE.ino,
 * to monitor temp/humi.
 */

#include "BLEDevice.h"
//#include "BLEScan.h"

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

// The remote service we wish to connect to.
static BLEUUID serviceUUID(SERVICE_UUID);
// The characteristic of the remote service we are interested in.
static BLEUUID  charUUID_TEMP(CHAR_UUID_TEMP);
static BLEUUID  charUUID_HUMI(CHAR_UUID_HUMI);

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteChar_temp;
static BLERemoteCharacteristic* pRemoteChar_humi;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {

    String strCharUUID = pBLERemoteCharacteristic->getUUID().toString().c_str();
    Serial.printf("Notify callback for characteristic: ");
    strCharUUID.toUpperCase();
    Serial.println(strCharUUID);

    if(strCharUUID.equals(CHAR_UUID_TEMP)){
      Serial.print("temp: ");
      for (int i=0; i<length; i++)
          Serial.print((char) pData[i]);
    }else if(strCharUUID.equals(CHAR_UUID_HUMI)){
      Serial.print("humi: ");
      for (int i=0; i<length; i++)
          Serial.print((char) pData[i]);
    }

    Serial.println();
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    // if you pass BLEAdvertisedDevice instead of address, 
    // it will be recognized type of peer device address (public or private)
    pClient->connect(myDevice);  
    Serial.println(" - Connected to server");
    //set client to request maximum MTU from server (default is 23 otherwise)
    pClient->setMTU(517); 
  
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service 
    // of the remote BLE server.
    pRemoteChar_temp = pRemoteService->getCharacteristic(charUUID_TEMP);
    if (pRemoteChar_temp == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TEMP.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found temp characteristic");

    pRemoteChar_humi = pRemoteService->getCharacteristic(charUUID_HUMI);
    if (pRemoteChar_humi == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TEMP.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found humi characteristic");

    // Read the value of the characteristic.
    if(pRemoteChar_temp->canRead()) {
      std::string value = pRemoteChar_temp->readValue();
      Serial.print("The characteristic temp value was: ");
      Serial.println(value.c_str());
    }
    if(pRemoteChar_humi->canRead()) {
      std::string value = pRemoteChar_humi->readValue();
      Serial.print("The characteristic humi value was: ");
      Serial.println(value.c_str());
    }

    if(pRemoteChar_temp->canNotify())
      pRemoteChar_temp->registerForNotify(notifyCallback);
    if(pRemoteChar_humi->canNotify())
      pRemoteChar_humi->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and 
 * find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    if(advertisedDevice.haveServiceUUID()){
      Serial.println("- haveServiceUUID()");
    }
    if(advertisedDevice.isAdvertisingService(serviceUUID)){
      Serial.println("- isAdvertisingService(serviceUUID)");
    }

    // We have found a device, 
    // let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && 
      advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

      Serial.println("*** device found ***");

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, 
  // update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {

  }else if(doScan){
    // this is just example to start scan after disconnect, 
    // most likely there is better way to do it in arduino
    BLEDevice::getScan()->start(0);  
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop




ESP32C3_BLE_DHT11_SSD1306_client.ino, with display on SSD1306 I2C OLED. For the SSD1306 part, refer to last exercise "ESP32-C3-DevKitM-1 display on ssd1306 I2C OLED using Adafruit SSD1306 library".
/**
 * modified from BLE_client
 * to work with ESP32_DHT_ST789_graphic_BLE.ino,
 * to monitor temp/humi.
 * and display on SSD1306 I2C OLED
 */

#include "BLEDevice.h"
//#include "BLEScan.h"
#include <SPI.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define SDA_pin 3
#define SCL_pin 2
#define OLED_RESET     -1
#define SCREEN_ADDRESS 0x3C //0x3D ///
Adafruit_SSD1306 display;

#define SERVICE_UUID "FC8601FC-7829-407B-9C2E-4D3F117DFF2D"
#define CHAR_UUID_TEMP "0FD31907-35AE-4BB0-8AB1-51F98C05B326"
#define CHAR_UUID_HUMI "D01D3AF7-818D-4A90-AECF-0E1EC48AA5F0"

// The remote service we wish to connect to.
static BLEUUID serviceUUID(SERVICE_UUID);
// The characteristic of the remote service we are interested in.
static BLEUUID  charUUID_TEMP(CHAR_UUID_TEMP);
static BLEUUID  charUUID_HUMI(CHAR_UUID_HUMI);

static boolean doConnect = false;
static boolean connected = false;
static boolean doScan = false;
static BLERemoteCharacteristic* pRemoteChar_temp;
static BLERemoteCharacteristic* pRemoteChar_humi;
static BLEAdvertisedDevice* myDevice;

static void notifyCallback(
  BLERemoteCharacteristic* pBLERemoteCharacteristic,
  uint8_t* pData,
  size_t length,
  bool isNotify) {

    String strCharUUID = pBLERemoteCharacteristic->getUUID().toString().c_str();
    Serial.printf("Notify callback for characteristic: ");
    strCharUUID.toUpperCase();
    Serial.println(strCharUUID);

    if(strCharUUID.equals(CHAR_UUID_TEMP)){
      Serial.print("temp: ");

      String strTemp = (char*)pData;
      Serial.print(strTemp);
      displayTemp(strTemp);
      
    }else if(strCharUUID.equals(CHAR_UUID_HUMI)){
      Serial.print("humi: ");

      String strHumi = (char*)pData;
      Serial.print(strHumi);
      displayHumi(strHumi);
    }

    Serial.println();
}

class MyClientCallback : public BLEClientCallbacks {
  void onConnect(BLEClient* pclient) {
    displayPrompt("onConnect");
  }

  void onDisconnect(BLEClient* pclient) {
    connected = false;
    Serial.println("onDisconnect");
    displayPrompt("onDisconnect");
  }
};

bool connectToServer() {
    Serial.print("Forming a connection to ");
    Serial.println(myDevice->getAddress().toString().c_str());
    
    BLEClient*  pClient  = BLEDevice::createClient();
    Serial.println(" - Created client");

    pClient->setClientCallbacks(new MyClientCallback());

    // Connect to the remove BLE Server.
    // if you pass BLEAdvertisedDevice instead of address, 
    // it will be recognized type of peer device address (public or private)
    pClient->connect(myDevice);  
    Serial.println(" - Connected to server");
    //set client to request maximum MTU from server (default is 23 otherwise)
    pClient->setMTU(517); 
  
    // Obtain a reference to the service we are after in the remote BLE server.
    BLERemoteService* pRemoteService = pClient->getService(serviceUUID);
    if (pRemoteService == nullptr) {
      Serial.print("Failed to find our service UUID: ");
      Serial.println(serviceUUID.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found our service");


    // Obtain a reference to the characteristic in the service 
    // of the remote BLE server.
    pRemoteChar_temp = pRemoteService->getCharacteristic(charUUID_TEMP);
    if (pRemoteChar_temp == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TEMP.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found temp characteristic");

    pRemoteChar_humi = pRemoteService->getCharacteristic(charUUID_HUMI);
    if (pRemoteChar_humi == nullptr) {
      Serial.print("Failed to find our characteristic UUID: ");
      Serial.println(charUUID_TEMP.toString().c_str());
      pClient->disconnect();
      return false;
    }
    Serial.println(" - Found humi characteristic");

    // Read the value of the characteristic.
    if(pRemoteChar_temp->canRead()) {
      std::string value = pRemoteChar_temp->readValue();
      Serial.print("The characteristic temp value was: ");
      Serial.println(value.c_str());
      displayTemp(value.c_str());
    }
    if(pRemoteChar_humi->canRead()) {
      std::string value = pRemoteChar_humi->readValue();
      Serial.print("The characteristic humi value was: ");
      Serial.println(value.c_str());
      displayHumi(value.c_str());
    }

    if(pRemoteChar_temp->canNotify())
      pRemoteChar_temp->registerForNotify(notifyCallback);
    if(pRemoteChar_humi->canNotify())
      pRemoteChar_humi->registerForNotify(notifyCallback);

    connected = true;
    return true;
}
/**
 * Scan for BLE servers and 
 * find the first one that advertises the service we are looking for.
 */
class MyAdvertisedDeviceCallbacks: public BLEAdvertisedDeviceCallbacks {
 /**
   * Called for each advertising BLE server.
   */
  void onResult(BLEAdvertisedDevice advertisedDevice) {
    Serial.print("BLE Advertised Device found: ");
    Serial.println(advertisedDevice.toString().c_str());

    if(advertisedDevice.haveServiceUUID()){
      Serial.println("- haveServiceUUID()");
    }
    if(advertisedDevice.isAdvertisingService(serviceUUID)){
      Serial.println("- isAdvertisingService(serviceUUID)");
    }

    // We have found a device, 
    // let us now see if it contains the service we are looking for.
    if (advertisedDevice.haveServiceUUID() && 
      advertisedDevice.isAdvertisingService(serviceUUID)) {

      BLEDevice::getScan()->stop();
      myDevice = new BLEAdvertisedDevice(advertisedDevice);
      doConnect = true;
      doScan = true;

      Serial.println("*** device found ***");
      displayPrompt("found");

    } // Found our server
  } // onResult
}; // MyAdvertisedDeviceCallbacks

void initScreen(){

  Wire.setPins(SDA_pin,SCL_pin);
  display = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
  
  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally
  if(!display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS)) {
    Serial.println(F("SSD1306 allocation failed"));
    for(;;); // Don't proceed, loop forever
  }

  display.clearDisplay();
  display.display();
  delay(500);

  display.setTextSize(2);
  display.setTextColor(SSD1306_WHITE); // Draw white text
  display.cp437(true);         // Use full 256 char 'Code Page 437' font

  display.setCursor(10, 10);
  display.printf("ESP32C3");
  display.setCursor(10, 28);
  display.printf("BLE");

  display.fillRect(0, 0, display.width()-1, display.height()-1, SSD1306_INVERSE);
  display.display();
  delay(3000);

  // Clear the buffer
  display.clearDisplay();
  display.display();

}


void displayTemp(String temp){

  //erase display of old temp
  display.fillRect(40, 10, 80, 18, SSD1306_BLACK);
  
  display.setCursor(10, 10);
  display.setTextSize(1);
  display.print("temp");
  display.setCursor(40, 10);
  display.setTextSize(2);
  display.print(temp);
  display.display();
}

void displayHumi(String humi){

  //erase display of old humi
  display.fillRect(40, 30, 80, 18, SSD1306_BLACK);
  
  display.setCursor(10, 30);
  display.setTextSize(1);
  display.print("humi");
  display.setCursor(40, 30);
  display.setTextSize(2);
  display.print(humi);
  display.display();
}

void displayPrompt(String prompt){

  //erase display of old temp
  display.fillRect(10, 10, 110, 40, SSD1306_BLACK);

  display.setCursor(40, 10);
  display.setTextSize(1);
  display.print(prompt);
  display.display();
}


void setup() {
  Serial.begin(115200);
  Serial.println("Starting Arduino BLE Client application...");

  initScreen();
  
  BLEDevice::init("");

  // Retrieve a Scanner and set the callback we want to use to be informed when we
  // have detected a new device.  Specify that we want active scanning and start the
  // scan to run for 5 seconds.
  BLEScan* pBLEScan = BLEDevice::getScan();
  pBLEScan->setAdvertisedDeviceCallbacks(new MyAdvertisedDeviceCallbacks());
  pBLEScan->setInterval(1349);
  pBLEScan->setWindow(449);
  pBLEScan->setActiveScan(true);
  pBLEScan->start(5, false);
  
  displayPrompt("Scan");
  Serial.println("Scan");
} // End of setup.


// This is the Arduino main loop function.
void loop() {

  // If the flag "doConnect" is true then we have scanned for and found the desired
  // BLE Server with which we wish to connect.  Now we connect to it.  Once we are 
  // connected we set the connected flag to be true.
  if (doConnect == true) {
    if (connectToServer()) {
      Serial.println("We are now connected to the BLE Server.");
    } else {
      Serial.println("We have failed to connect to the server.");
    }
    doConnect = false;
  }

  // If we are connected to a peer BLE Server, 
  // update the characteristic each time we are reached
  // with the current time since boot.
  if (connected) {

  }else if(doScan){
    // this is just example to start scan after disconnect, 
    // most likely there is better way to do it in arduino
    displayPrompt("Scan");
    Serial.println("Scan");
    BLEDevice::getScan()->start(0);  
  }
  
  delay(1000); // Delay a second between loops.
} // End of loop


Sunday, December 12, 2021

arduino-esp32, ESP32-C3-DevKitM-1 display on ssd1306 I2C OLED using Adafruit SSD1306 library

This post show how to driver 128x64 ssd1306 I2C OLED on ESP32-C3-DevKitM-1 (arduino-esp32) using Adafruit SSD1306 library.


In Arduino IDE, install Adafruit SSD1306 library.


Open ssd1306_128x64_i2c example.


This exercise run on ESP32-C3-DevKitM-1, but the default I2C SDA pin (GPIO8) is connected to on-board RGB LED. (ref: arduino-esp32 (ESP32-C3) scan i2c device address, using custom SDA and SCL) In my exercise SDA/SCL are re-allocated to GPIO3 and GPIO2.

Here list the modification I apply on ssd1306_128x64_i2c example,marked in RED:
		.
		.
		.
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
// The pins for I2C are defined by the Wire-library. 
// On an arduino UNO:       A4(SDA), A5(SCL)
// On an arduino MEGA 2560: 20(SDA), 21(SCL)
// On an arduino LEONARDO:   2(SDA),  3(SCL), ...

/* remarked by Erik 2021-12-13
 * On my SSD1306 128x64 OLED
 * - No RESET pin
 * - I2C Address = 0x3C
 * - I have to re-allocate I2C SDA/SCL, due to conflict with onboard RGB LED.
 * - SDA = 3
 * - SCL = 2
 */
#define SDA_pin 3
#define SCL_pin 2
#define OLED_RESET     -1 //4 // Reset pin # (or -1 if sharing Arduino reset pin)
#define SCREEN_ADDRESS 0x3C //0x3D /// ...
//Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_SSD1306 display;

#define NUMFLAKES     10 // Number of snowflakes in the animation example
		.
		.
		.
void setup() {
  Serial.begin(9600);

  // by Erik 2021-12-13
  // Call Wire.setPins to assign SDA/SCL pins
  Wire.setPins(SDA_pin,SCL_pin);
  display = Adafruit_SSD1306(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

		.
		.
		.
		


arduino-esp32 (ESP32-C3) scan i2c device address, using custom SDA and SCL.

i2c_scanner is a simple sketch scans the I2C-bus for devices. If a device is found, it is reported to the Arduino serial monitor.

With arduino-esp32, the default I2C of "ESP32C3 Dev Modul" is assigned on:
- SDA: GPIO8
- SCL: GPIO9
(ref: arduino-esp32, list the pre-defined function pins of ESP32C3/S2 Dev Module)

But on ESP32-C3-DevKitM-1,GPIO8 is connected to on-board RGB LED (WS2812).
(ref: ESP32-C3-DevKitM-1 User Guide)

So I have to assign I2C to other custom SDA/SCL pins, by calling   Wire.setPins() before Wire.begin().

  Wire.setPins(SDA_pin, SCL_pin);
  Wire.begin();
How it run on ESP32-C3-DevKitM-1, with SSD1306 conected. The detected I2C address is 0x3C.



Next:
~ how it applied to work with ssd1306 I2C OLED.