OPHoperHPO / lilygo-ttgo-twatch-2020-micropython

:snake:MicroPython for LilyGO TTGO T-Watch-2020:snake:
MIT License
57 stars 12 forks source link

Add support for i2s audio #5

Open AngainorDev opened 3 years ago

AngainorDev commented 3 years ago

@miketeachman published an i2s module targeting i2s audio on esp32, see https://github.com/micropython/micropython/pull/4471 and related examples: https://github.com/miketeachman/micropython-esp32-i2s-examples

This would make a perfect addition to this nice T-Watch 2020 Micropython fork.

OPHoperHPO commented 3 years ago

Currently in progress

amirgon commented 3 years ago

I2S is already supported here, as part of espidf module. Try import espidf "espidf" is an additional library that comes as part of LVGL.

It includes I2S, SPI master, ADC, Heap functions, PCNT, MDNS. Latest version (which is not yet integrated here) also includes HTTP client.

miketeachman commented 3 years ago

I2S is already supported here, as part of espidf module

It would be interesting to try this approach. At this point I don't know if anyone has attempted using the espidf module to implement I2S. It's certainly worth checking out. The way to do this is to look at the C code in the PR and duplicate that in MicroPython.

If developer time efficiency and certainty of operation are a concern, then integrating the I2S PR is likely the way to go. It's been used by 100's of people and has documentation. https://github.com/miketeachman/micropython-esp32-i2s-examples

The PR integration consists of adding one C file, and one-line edits to 3 others.

amirgon commented 3 years ago

It would be interesting to try this approach. At this point I don't know if anyone has attempted using the espidf module to implement I2S. It's certainly worth checking out. The way to do this is to look at the C code in the PR and duplicate that in MicroPython.

@miketeachman - I have used it.
I'm using it with SPH0645 microphone and it works well.
It's currently on a private repo as part of an internal project but I could probably send an example if anyone would be interested.

If developer time efficiency and certainty of operation are a concern, then integrating the I2S PR is likely the way to go. It's been used by 100's of people and has documentation.

It's great that there are alternatives!
If your I2S PR works well and tested by 100's of people, by all means I would recommend anyone to use it.

My alternative is just a transparent translation of esp-idf API to Micropython, and in this case it's already part of this ttgo twatch repo so anyone can already use it with an existing prebuilt FW. I2S is just part of it. The module provides many other esp-idf APIs.

jmiskovic commented 3 years ago

@amirgon the example of using esp-idf I2S binding would be much appreciated!

amirgon commented 3 years ago

@jmiskovic Here is a simplified example, extracted from a private project with some irrelevant parts removed:

import gc
import espidf as esp
from espidf import I2S_MODE
from espidf import I2S_COMM_FORMAT
from espidf import I2S_CHANNEL_FMT
from espidf import I2S_BITS_PER_SAMPLE
from espidf import I2S_CHANNEL
from espidf import GPIO_MODE

class Recorder:
    I2S_PIN_NO_CHANGE = const(-1)
    BYTES_PER_SAMPLE = const(4)
    BYTES_PER_OUTPUT_SAMPLE = const(2)
    OVERSAMPLE_MULTIPLIER = const(1)

    def __init__(self, bclk = 2, dout = 36, ws = 22, freq = 16000, i2s_num = esp.I2S_NUM._0):
        self.bclk = bclk
        self.dout = dout
        self.ws = ws
        self.freq = freq
        self.sample_rate = freq * OVERSAMPLE_MULTIPLIER
        self.i2s_num = i2s_num
        self.bits_per_sample = self.BYTES_PER_SAMPLE * 8
        self.dma_buf_count = 6
        self.dma_buf_len = 1024
        self.bytes_to_read = (self.dma_buf_count * self.dma_buf_len)

        # Init I2S

        self.i2s_config = esp.i2s_config_t({
            'mode': I2S_MODE.MASTER | I2S_MODE.RX,
            'sample_rate': self.sample_rate,
            'bits_per_sample': self.bits_per_sample,
            'communication_format': I2S_COMM_FORMAT.I2S | I2S_COMM_FORMAT.I2S_MSB,
            'channel_format': I2S_CHANNEL_FMT.ONLY_RIGHT,
            'intr_alloc_flags': (1<<1), # ESP_INTR_FLAG_LEVEL1
            'dma_buf_count': self.dma_buf_count,
            'dma_buf_len': self.dma_buf_len,
            'use_apll':0
        })

        self.i2s_pin_config = esp.i2s_pin_config_t({
            'bck_io_num': self.bclk,
            'ws_io_num': self.ws,
            'data_out_num': self.I2S_PIN_NO_CHANGE,
            'data_in_num': self.dout,    
        })

        esp.i2s_driver_install(self.i2s_num, self.i2s_config, 0, None)
        esp.i2s_set_pin(self.i2s_num, self.i2s_pin_config)
        esp.SPH0645_WORKAROUND(self.i2s_num)
        esp.i2s_stop(self.i2s_num)

    def close(self):
        esp.i2s_driver_uninstall(self.i2s_num)

    def record(self):
        input_buf = bytearray(self.bytes_to_read)
        self.input_view = memoryview(input_buf)
        bytes_read_ptr = esp.C_Pointer()
        gc.disable()
        esp.i2s_start(self.i2s_num)

        while self.recording:
            res = esp.i2s_read(self.i2s_num, self.input_view, self.bytes_to_read, bytes_read_ptr, 0)
            if res != 0: raise RuntimeError('i2s_read completed with error %d' % res)
            bytes_read = bytes_read_ptr.uint_val
            if bytes_read == 0: continue

            # Do something with input_buf

        esp.i2s_stop(self.i2s_num)
        gc.endale()
jmiskovic commented 3 years ago

Thank you!

Because I also wanted to play sounds, I searched around some more. There is a fork of this repo that already integrated I2S.

It should be useful to someone.