ESP32S3 (MicroPython) + MAX98357 I2S Audio Amplifier to play tones
This post exercise on ESP32-S3-DevKitC-1 running MicroPython v1.24.1, to
play tones using MAX98357 I2S Audio Amplifier.
mpy_s3_max98357.py, a simple MicroPython to play single tone on MAX98357 I2S Audio Amplifier.
With 3.2" 320x240 IPS LCD (ILI9341 SPI), user interface added.
Connection:
mpy_s3_max98357_touchLCD.py
It's found that sometimes false touched on LCD, so I add a simple debounce on touch.
mpy_s3_max98357_touchLCD_debounce.py
Connection between MAX98357 and ESP32-S3-DevKitC-1:
MAX98357 ESP32-S3-DevKitC-1
==================================
VCC 3V3
GND GND
MAX98357_LRC GPIO17
MAX98357_BCLK GPIO16
MAX98357_DIN GPIO15
MAX98357_GAIN GND
mpy_s3_max98357.py, a simple MicroPython to play single tone on MAX98357 I2S Audio Amplifier.
"""
ESP32-S3-DevKitC-1 running MicroPython v1.24.1
Play single tone output to MAX98357.
"""
import math
import array
import time
from machine import I2S, Pin
print("Start")
MAX98357_LRC = 17
MAX98357_BCLK = 16
MAX98357_DIN =15
i2s = I2S(0,
sck=Pin(MAX98357_BCLK),
ws=Pin(MAX98357_LRC),
sd=Pin(MAX98357_DIN),
mode=I2S.TX,
bits=16,
format=I2S.MONO,
rate=44100,
ibuf=44100,
)
print("i2s:", i2s)
def generate_tone(sample_rate, frequency, duration):
tick_ms_generate_tone_start = time.ticks_ms()
sample_count = int(sample_rate * duration)
amplitude = 14000 #32767
wave = array.array("h", (0 for _ in range(sample_count)))
for i in range(sample_count):
wave[i] = int(amplitude * math.sin(2 * math.pi * frequency * i / sample_rate))
tick_ms_generate_tone_end = time.ticks_ms()
print("Time to generate Tone", frequency, "Hz for", duration,
"s with sample_rate", sample_rate, "=", "ms",
time.ticks_diff(tick_ms_generate_tone_end, tick_ms_generate_tone_start))
return wave
duration = 1
tone_1k5 = generate_tone(44100, 1500, duration)
def play_tone():
print("Play single tone")
tick_ms_i2s_write_start = time.ticks_ms()
i2s.write(tone_1k5)
tick_ms_i2s_write_end = time.ticks_ms()
print("Time for i2s.write()", ":",
time.ticks_diff(tick_ms_i2s_write_end, tick_ms_i2s_write_start), "ms")
time.sleep(duration)
print("Playing stop")
play_tone()
while True:
k = input("'q' to quit, others re-play tone.")
if k == 'q':
break
play_tone()
print("~ bye ~")
i2s.deinit()
With 3.2" 320x240 IPS LCD (ILI9341 SPI), user interface added.
Connection:
mpy_s3_max98357_touchLCD.py
"""
ESP32-S3-DevKitC-1 running MicroPython v1.24.1
3.2" 320x240 IPS LCD (ILI9341 SPI) with Cap. Touch (FT6336) and Micro SD Slot
Touch LCD to play tone output to MAX98357.
Libraries needed, install to /lib:
- ili9341.py
- ft6x36.py
"""
import os, sys
from time import sleep
from machine import Pin, SPI, I2C, I2S
from ili9341 import Display, color565
from ft6x36 import FT6x36
import math
import array
print("====================================")
print(sys.implementation[0], os.uname()[3],
"\nrun on", os.uname()[4])
print("====================================")
LCD_CS=14 # low to select
LCD_RST=13
LCD_RS=12
LCD_SDI=11 #MOSI
LCD_SCK=10
LCD_LED=9
LCD_SDO=8 #MISO
CTP_SCL=7
CTP_RST=6
CTP_SDA=5
CTP_INT=4 #Not used
SD_CS=18 # low to select
MAX98357_LRC = 17
MAX98357_BCLK = 16
MAX98357_DIN =15
# un-select both LCD and SD in power-up,
# to prevent un-used cs unstable and false selected.
pin_cs_lcd = Pin(LCD_CS, Pin.OUT)
pin_cs_lcd.value(1)
pin_cs_sd = Pin(SD_CS, Pin.OUT)
pin_cs_sd.value(1)
pin_backlight=Pin(LCD_LED, Pin.OUT)
pin_backlight.value(0)
# output a negative pulse to CTP_RST pin, to reset FT6336U
pin_CTP_RST=Pin(CTP_RST, Pin.OUT)
pin_CTP_RST.value(1)
sleep(0.1)
pin_CTP_RST.value(0)
sleep(0.1)
pin_CTP_RST.value(1)
sleep(0.1)
# rot and rot_set
# are used to better manager rotation/width/height of display and touch
rot=1 # 0 rotation = 0
# 1 rotation = 90
# 2 rotation = 180
# 3 rotation = 270
# rot_set[x][0] : ili9341 Display rotation
# rot_set[x][1] : ili9341 Display width
# rot_set[x][2] : ili9341 Display height
# rot_set[x][3] : ft6336U Touch rotation
# rot_set[x][4] : ft6336U Touch width
# rot_set[x][5] : ft6336U Touch height
rot_set = [[0, 240, 320, FT6x36.PORTRAIT_INVERTED, 240, 320],
[90, 320, 240, FT6x36.LANDSCAPE_INVERTED, 240, 320],
[180, 240, 320, FT6x36.PORTRAIT, 240, 320],
[270, 320, 240, FT6x36.LANDSCAPE, 240, 320]]
# spi share by LCD and SD
spi = SPI(1, baudrate=40000000, sck=LCD_SCK, mosi=LCD_SDI, miso=LCD_SDO)
display = Display(spi,
rotation=rot_set[rot][0],
width=rot_set[rot][1], height=rot_set[rot][2],
dc=Pin(LCD_RS), cs=Pin(LCD_CS), rst=Pin(LCD_RST))
display.invert(enable=True)
# i2c
i2c = I2C(0, scl=Pin(CTP_SCL), sda=Pin(CTP_SDA))
touch = FT6x36(i2c,
width=rot_set[rot][4],
height=rot_set[rot][5],
rotation=rot_set[rot][3],)
i2s = I2S(0,
sck=Pin(MAX98357_BCLK),
ws=Pin(MAX98357_LRC),
sd=Pin(MAX98357_DIN),
mode=I2S.TX,
bits=16,
format=I2S.MONO,
rate=44100,
ibuf=44100,
)
#print("i2s:", i2s)
pin_backlight.value(1)
display.clear()
# draw a rectangle show the boundary of the screen
display.draw_rectangle(0, 0, display.width, display.height, color565(255, 255, 255))
def generate_tone(sample_rate, frequency, duration):
sample_count = int(sample_rate * duration)
amplitude = 14000 #32767
wave = array.array("h", (0 for _ in range(sample_count)))
for i in range(sample_count):
wave[i] = int(amplitude * math.sin(2 * math.pi * frequency * i / sample_rate))
return wave
num_of_tone = 7
tone_list = [[261.63],
[293.66],
[329.63],
[349.23],
[392.00],
[440.00],
[493.88]]
button_width = display.width // num_of_tone
button_height = 180
button_margin_x = 5
button_top = 50
button_bottom = button_top+button_height
tone_sample_rate = 44100
tone_duration = 0.5
display.draw_text8x8(5, 5,
"Generating tone, please wait!",
color565(255, 255, 255),
background=color565(0, 0, 0)
)
for i in range(num_of_tone):
x = i * button_width + button_margin_x
y = button_top
w = button_width - (2 * button_margin_x)
h = button_bottom - button_top
display.fill_rectangle(x, y,
w, h,
color565(50, 50, 50))
display.draw_rectangle(x, y,
w, h,
color565(150, 150, 150))
tone_list[i].append([x, y, x+w, y+h])
# generate tone
tone_list[i].append(generate_tone(tone_sample_rate,
tone_list[i][0], #freq
tone_duration))
print("Tone", i, ":", tone_list[i][0], "generated.",
"len() =", len(tone_list[i][2]))
display.fill_rectangle(x, y,
w, h,
color565(150, 150, 150))
display.draw_rectangle(x, y,
w, h,
color565(255, 255, 255))
str_freq = "{:.2f} Hz".format(tone_list[i][0])
display.draw_text8x8(x+1, y+1,
str_freq,
color565(255, 255, 255),
background=color565(150, 150, 150),
rotate=90)
i2s.write(tone_list[i][2])
display.draw_text8x8(5, 5,
" ",
color565(255, 255, 255),
background=color565(0, 0, 0)
)
touched = False
while True:
p = touch.get_positions()
if len(p) > 0:
x = p[0][0]
y = p[0][1]
if touched == False:
for i in range(len(tone_list)):
if x >= tone_list[i][1][0] and \
x <= tone_list[i][1][2] and \
y >= tone_list[i][1][1] and \
y <= tone_list[i][1][3]:
i2s.write(tone_list[i][2])
print("touched within button", i, tone_list[i][0])
touched = True
break
else:
touched = False
sleep(0.1)
It's found that sometimes false touched on LCD, so I add a simple debounce on touch.
mpy_s3_max98357_touchLCD_debounce.py
"""
ESP32-S3-DevKitC-1 running MicroPython v1.24.1
3.2" 320x240 IPS LCD (ILI9341 SPI) with Cap. Touch (FT6336) and Micro SD Slot
Touch LCD to play tone output to MAX98357, with debounce.
Libraries needed, install to /lib:
- ili9341.py
- ft6x36.py
"""
import os, sys
from time import sleep
from machine import Pin, SPI, I2C, I2S
from ili9341 import Display, color565
from ft6x36 import FT6x36
import math
import array
from micropython import const
print("====================================")
print(sys.implementation[0], os.uname()[3],
"\nrun on", os.uname()[4])
print("====================================")
LCD_CS=14 # low to select
LCD_RST=13
LCD_RS=12
LCD_SDI=11 #MOSI
LCD_SCK=10
LCD_LED=9
LCD_SDO=8 #MISO
CTP_SCL=7
CTP_RST=6
CTP_SDA=5
CTP_INT=4 #Not used
SD_CS=18 # low to select
MAX98357_LRC = 17
MAX98357_BCLK = 16
MAX98357_DIN =15
#MAX98357_GAIN connect to GND
# un-select both LCD and SD in power-up,
# to prevent un-used cs unstable and false selected.
pin_cs_lcd = Pin(LCD_CS, Pin.OUT)
pin_cs_lcd.value(1)
pin_cs_sd = Pin(SD_CS, Pin.OUT)
pin_cs_sd.value(1)
pin_backlight=Pin(LCD_LED, Pin.OUT)
pin_backlight.value(0)
# output a negative pulse to CTP_RST pin, to reset FT6336U
pin_CTP_RST=Pin(CTP_RST, Pin.OUT)
pin_CTP_RST.value(1)
sleep(0.1)
pin_CTP_RST.value(0)
sleep(0.1)
pin_CTP_RST.value(1)
sleep(0.1)
# rot and rot_set
# are used to better manager rotation/width/height of display and touch
rot=1 # 0 rotation = 0
# 1 rotation = 90
# 2 rotation = 180
# 3 rotation = 270
# rot_set[x][0] : ili9341 Display rotation
# rot_set[x][1] : ili9341 Display width
# rot_set[x][2] : ili9341 Display height
# rot_set[x][3] : ft6336U Touch rotation
# rot_set[x][4] : ft6336U Touch width
# rot_set[x][5] : ft6336U Touch height
rot_set = [[0, 240, 320, FT6x36.PORTRAIT_INVERTED, 240, 320],
[90, 320, 240, FT6x36.LANDSCAPE_INVERTED, 240, 320],
[180, 240, 320, FT6x36.PORTRAIT, 240, 320],
[270, 320, 240, FT6x36.LANDSCAPE, 240, 320]]
# spi share by LCD and SD
spi = SPI(1, baudrate=40000000, sck=LCD_SCK, mosi=LCD_SDI, miso=LCD_SDO)
display = Display(spi,
rotation=rot_set[rot][0],
width=rot_set[rot][1], height=rot_set[rot][2],
dc=Pin(LCD_RS), cs=Pin(LCD_CS), rst=Pin(LCD_RST))
display.invert(enable=True)
# i2c
i2c = I2C(0, scl=Pin(CTP_SCL), sda=Pin(CTP_SDA))
touch = FT6x36(i2c,
width=rot_set[rot][4],
height=rot_set[rot][5],
rotation=rot_set[rot][3],)
i2s = I2S(0,
sck=Pin(MAX98357_BCLK),
ws=Pin(MAX98357_LRC),
sd=Pin(MAX98357_DIN),
mode=I2S.TX,
bits=16,
format=I2S.MONO,
rate=44100,
ibuf=44100,
)
#print("i2s:", i2s)
pin_backlight.value(1)
display.clear()
# draw a rectangle show the boundary of the screen
display.draw_rectangle(0, 0, display.width, display.height, color565(255, 255, 255))
def generate_tone(sample_rate, frequency, duration):
sample_count = int(sample_rate * duration)
amplitude = 14000 #32767
wave = array.array("h", (0 for _ in range(sample_count)))
for i in range(sample_count):
wave[i] = int(amplitude * math.sin(2 * math.pi * frequency * i / sample_rate))
return wave
num_of_tone = 7
tone_list = [[261.63],
[293.66],
[329.63],
[349.23],
[392.00],
[440.00],
[493.88]]
button_width = display.width // num_of_tone
button_height = 180
button_margin_x = 5
button_top = 50
button_bottom = button_top+button_height
button_pad_width = button_width - (2 * button_margin_x)
BUTTON_BACKGROUND_INIT = color565(40, 40, 40)
BUTTON_BORDER_INIT = color565(100, 100, 100)
BUTTON_BACKGROUND = color565(100, 100, 100)
BUTTON_BORDER_RELEASED = color565(150, 150, 150)
BUTTON_BORDER_PRESSED = color565(255, 255, 255)
tone_sample_rate = 44100
tone_duration = 0.5
display.draw_text8x8(5, 5,
"Generating tone, please wait!",
color565(255, 255, 255),
background=color565(0, 0, 0)
)
for i in range(num_of_tone):
x = i * button_width + button_margin_x
y = button_top
w = button_pad_width
h = button_height
display.fill_rectangle(x, y,
w, h,
BUTTON_BACKGROUND_INIT)
display.draw_rectangle(x, y,
w, h,
BUTTON_BORDER_INIT)
tone_list[i].append([x, y, x+w, y+h])
# generate tone
tone_list[i].append(generate_tone(tone_sample_rate,
tone_list[i][0], #freq
tone_duration))
print("Tone", i, ":", tone_list[i][0], "generated.",
"len() =", len(tone_list[i][2]))
display.fill_rectangle(x, y,
w, h,
BUTTON_BACKGROUND)
display.draw_rectangle(x, y,
w, h,
BUTTON_BORDER_RELEASED)
str_freq = "{:.2f} Hz".format(tone_list[i][0])
display.draw_text8x8(x+1, y+1,
str_freq,
color565(255, 255, 255),
BUTTON_BACKGROUND,
rotate=90)
i2s.write(tone_list[i][2])
display.draw_text8x8(5, 5,
" ",
color565(255, 255, 255),
background=color565(0, 0, 0)
)
"""
Touch Debouncing
Here I assign touch_st as a state machine, in states of:
TOUCH_ST_0_idle
TOUCH_ST_1_touch_debouncing
TOUCH_ST_2_touched
TOUCH_ST_3_untouch_debouncing
"""
TOUCH_ST_0_idle = const(0)
TOUCH_ST_1_touch_debouncing = const(1)
TOUCH_ST_2_touched = const(2)
TOUCH_ST_3_untouch_debouncing = const(3)
def doState_0():
#print("TOUCH_ST_0_idle")
global touch_st
if current_touched:
# un-touched -> touched, go to debounce
touch_st = TOUCH_ST_1_touch_debouncing
def doState_1():
#print("TOUCH_ST_1_touch_debouncing")
global touch_st
global last_pressed_button
if current_touched:
# touched -> touched, within debouncing
# Two consecutive touch will change from TOUCH_ST_0_idle to TOUCH_ST_2_touched,
# otherwise back to TOUCH_ST_0_idle
touch_x = touch_p[0][0]
touch_y = touch_p[0][1]
#print("Touch-Down:", touch_x, "-", touch_y)
touch_st = TOUCH_ST_2_touched
#check if touched on button
for i in range(len(tone_list)):
if touch_x >= tone_list[i][1][0] and \
touch_x <= tone_list[i][1][2] and \
touch_y >= tone_list[i][1][1] and \
touch_y <= tone_list[i][1][3]:
i2s.write(tone_list[i][2])
print("touched within button", i, tone_list[i][0])
display.draw_rectangle(tone_list[i][1][0], tone_list[i][1][1],
button_pad_width, button_height,
BUTTON_BORDER_PRESSED)
last_pressed_button = i
break
else:
touch_st = TOUCH_ST_0_idle
def doState_2():
#print("TOUCH_ST_2_touched")
global touch_st
if current_touched:
# touched -> touched,
pass
else:
# touched - > un-touched
touch_st = TOUCH_ST_3_untouch_debouncing
def doState_3():
#print("TOUCH_ST_3_untouch_debouncing")
global touch_st
global last_pressed_button
if current_touched:
touch_st =TOUCH_ST_2_touched
else:
#print("Touch-Up:")
touch_st = TOUCH_ST_0_idle
if last_pressed_button != None:
display.draw_rectangle(tone_list[last_pressed_button][1][0], tone_list[last_pressed_button][1][1],
button_pad_width, button_height,
BUTTON_BORDER_RELEASED)
last_pressed_button = None
touch_actions = [doState_0, doState_1, doState_2, doState_3]
touch_st = TOUCH_ST_0_idle
last_pressed_button = None
while True:
touch_p = touch.get_positions()
if len(touch_p) > 0:
current_touched = True
else:
current_touched = False
touch_actions[touch_st]()
sleep(0.05)
Comments
Post a Comment