5. Implementación final - alejandromz/AudioPlayer-ESP32 GitHub Wiki

Implementación final del Software

Utilizando las pruebas individuales que se expusieron previamente, se hizo la construcción del código final que va a manejar el dispositivo. En general este código se creó con el objetivo de que sea más legible, creando un código basado en funciones que haga que el loop principal sea fácil de entender. A continuación se puede ver todo el código desarrollado.

Inicialmente se hacen los comentarios iniciales y se importan los paquetes necesarios.


## ---------------------------------------------
# 
# Project: ESP32 Audio Player
# Universidad Nacional de Colombia - Sede Bogotá
# Departamento de Ingeniería Eléctrica y Electrónica
#
# Sistemas Embebidos 2022-1
# Professor: Johnny German Cubides Castro
# 
## ---------------------------------------------

# Import required libraries
from time import sleep_ms, ticks_ms
from machine import I2C, I2S, Pin, ADC, SoftSPI
from i2c_lcd import I2cLcd
from sdcard import SDCard
import os

A continuación, se crean las variables del buffer necesarias para el protocolo I2C y se empiezan a definir las funciones. Algunas de estas funciones se van a explicar más a fondo, pero la mayoría de las funciones están fuertemente ligadas a las pruebas de módulos individuales que se mostraron previamente, por lo que se obviará su funcionamiento.

## Allocate buffer variables
wav_samples = bytearray(1024)
wav_samples_mv = memoryview(wav_samples)


## Define functions
# Display the files of the current directory on the LCD
def display_menu(directorio, pos):
    lcd.clear()
    if pos == len(directorio)-1:
        lcd.putstr('{}\n>{}'.format(directorio_dsp[pos-1],directorio_dsp[pos]))
    else:
        lcd.putstr('>{}\n{}'.format(directorio_dsp[pos],directorio_dsp[pos+1]))
    sleep_ms(300)
    

# Change the position of the cursor
def navigate(pos):
    y_val = y.read()
    if y_val < threshold:
        pos = pos-1
        if pos<0:
            pos=0
    elif y_val > 4095-threshold:
        pos = pos+1
        if pos>len(directorio)-1:
            pos = len(directorio)-1
    return pos
        

# Change directory to the selected one if possible
def select_dir(directorio, old_dir, pos, state):
    wav = None
    if directorio[pos] == 'Return':
        try:
            lst = old_dir.split('/')
            old_dir=''
            
            if (len(lst) > 2 and lst[0] != '') or len(lst) > 3:
                for item in lst[0:-2]:
                    ## print(item)
                    if item != '':
                        old_dir=old_dir+'/'+item
            else:
                old_dir=owd
        except:
            print('Error')
                
        os.chdir(old_dir)
        directorio = os.listdir()
        if old_dir != owd:
            directorio.append('Return')
        pos = 0
    elif directorio[pos][-4:] == '.wav':
        state = 'play'
        wav_file = old_dir+directorio[pos]
        wav = open(wav_file,'rb')
    else:
        try:
            os.chdir(directorio[pos])
            old_dir=old_dir+directorio[pos]+'/'
            directorio = os.listdir()
            directorio.append('Return')
            pos = 0
        except:
            print('No es un directorio.')
    return directorio, old_dir, pos, state, wav


# Play the selected audiofile until it ends
def play_wav(wav, w_pos, wav_len, state):
    global wav_samples_mv
    if  w_pos < wav_len:
        wav.seek(w_pos)
        wav.readinto(wav_samples_mv)
        num_written = audio_out.write(wav_samples_mv)
        w_pos += num_written
    else:
        state = 'pause'
        w_pos = wav.seek(44)
    return w_pos, state
            
            
# Display the current audiofile and its state
def display_wav(directorio, pos, w_pos, wav_len, state):
    lcd.clear()
    lcd.putstr('{}\n{}'.format(directorio[pos][:12], state))
    
 
# Change the state of the audiofile   
def play_pause(state):
    if sw.value()==0:
        if state == 'play':
            state = 'pause'
        elif state == 'pause':
            state='play'
        sleep_ms(200)
        
    x_val = x.read()
    if x_val < threshold:
        state = 'menu'
        wav.close()
        
    return state
  

Una vez se definen las funciones, que contienen el grueso del funcionamiento del programa, se empiezan a inicializar las clases que manejan los módulos del dispositivo.

## Initialize class instances

# Initialize SDCard object
spisd = SoftSPI(-1,
                miso=Pin(21),
                mosi=Pin(19),
                sck=Pin(18))
sd = SDCard(spisd, Pin(5))

# Initialize I2S object
# sck=Pin(16), ws=Pin(17), sd=Pin(4),
audio_out = I2S(1,
                sck=Pin(25), ws=Pin(26), sd=Pin(4),
                mode=I2S.TX,
                bits=16, 
                format=I2S.MONO,
                rate=16000,
                ibuf=2000)

# Initialize I2C object
DEFAULT_I2C_ADDR = 0X27
# i2c=I2C(scl=Pin(22), sda=Pin(21), freq=400000)
i2c=I2C(scl=Pin(22), sda=Pin(15), freq=400000)
lcd = I2cLcd(i2c, DEFAULT_I2C_ADDR,2,16)

# Initialize joystick
threshold = 500
x = ADC(Pin(32))
y = ADC(Pin(33))
x.atten(ADC.ATTN_11DB)
y.atten(ADC.ATTN_11DB)
sw = Pin(14, Pin.IN, Pin.PULL_UP)


## Initialize SD storage 
vfs=os.VfsFat(sd)
os.mount(vfs, '/sd')
owd = os.getcwd()
old_dir=owd


## Create list of the sd directories
directorio = os.listdir()
directorio_dsp = [st[0:12] for st in directorio]
pos = 0
display_menu(directorio, pos)

Luego, se definen las variables del estado inicial del programa.


## Define initial state
state = 'menu'
wav = None
wav_len = 0
w_pos = 0

Por último, se da inicio al funcionamiento del programa a través de un while. Aquí el funcionamiento se asemeja a una máquina de estados que se puede manejar a través de la interfaz que se desarrolló para el proyecto.

## Main loop
while True:
    if state == 'menu':
        old_pos = pos
        pos = navigate(old_pos)
        if pos != old_pos:
            display_menu(directorio, pos)
            sleep_ms(100)
            
        if sw.value()==0:
            directorio, old_dir, pos, state, wav = select_dir(directorio, old_dir, pos, state)
            directorio_dsp = [st[0:12] for st in directorio]
            if state == 'play':
                wav_len = wav.seek(0, 2)
                w_pos = wav.seek(44)
                display_wav(directorio, pos, w_pos, wav_len, state)
            else:
                display_menu(directorio, pos)
            sleep_ms(200)
    elif state == 'play':
        w_pos, state = play_wav(wav, w_pos, wav_len, state)
        state = play_pause(state)
        if state == 'pause':
            display_wav(directorio, pos, w_pos, wav_len, state)
        elif state == 'menu':
            display_menu(directorio, pos)
    elif state == 'pause':
        state = play_pause(state)
        if state == 'play':
            display_wav(directorio, pos, w_pos, wav_len, state)
        elif state == 'menu':
            display_menu(directorio, pos)
    else:
        print('State errror.')

Implementación final del Hardware

Una vez se obtuvo la PCB, se hizo el montaje de todos los módulos descritos en la sección previa. El resultado de esto se puede ver en la siguiente imagen.

image

Sin embargo, en este punto del proceso se debieron hacer modificaciones de último momento a la placa. Primero, notamos que los botones de enabled y boot de la ESP 32 habían sido mal diseñados, por lo que la ESP32 se estaba reiniciando constantemente. Para resolver este problema se hicieron algunos cortes en la capa de ground para conectarlo a los pines de los switch y se hizo un corto del pin de enabled a uno de los capacitores. Por otro lado, en el proceso de ruteo se hicieron algunos cambios a los pines (a algunos que no se probaron) para facilitar este proceso. Esto llevó a un problema en el cual usamos el pin IO02 en la ESP32, un puerto prioritario y a usar dos pines de conexión al joystick que no tenían capacidades ADC. Debido a esto, se debieron cortar estas pistas y hacer un corto externo a la placa con algunos cables. Los resultados de todos estos arreglos se pueden ver en las siguientes imagenes.

image image

Una vez que se realizaron estos arreglos, finalmente se pudo instalar Micropython en la ESP32 y se pudo confirmar el funcionamiento de todos los módulos mediante las pruebas individuales descritas previamente. Una vez hecho esto, se realizó el montaje parcial en la carcasa y se volvió a comprobar que todo funcionara bien con el código final del proyecto.

image

Por último, se finalizó el montaje del cirtuito en la carcasa y se comprobó el funcionamiento, como se ve en los siguientes videos.

https://user-images.githubusercontent.com/37418752/176674115-8dd3e817-492c-451c-b91d-cf0d89e85242.mp4

https://user-images.githubusercontent.com/37418752/176674149-e4eb0ba0-d4c0-4b56-b6d7-30a73aeb7efd.mp4

https://user-images.githubusercontent.com/37418752/176674187-b7bf8088-5610-4962-b13d-1ea3a3ed9690.mp4