forked from Makuna/NeoPixelBus
Esp32 Hardware support using I2s (#208)
This commit is contained in:
@@ -71,6 +71,7 @@ License along with NeoPixel. If not, see
|
||||
|
||||
#elif defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include "internal/NeoEsp32I2sMethod.h"
|
||||
#include "internal/NeoEspBitBangMethod.h"
|
||||
#include "internal/DotStarGenericMethod.h"
|
||||
|
||||
|
522
src/internal/Esp32_i2s.c
Normal file
522
src/internal/Esp32_i2s.c
Normal file
@@ -0,0 +1,522 @@
|
||||
// WARNING: This file contains code that is more than likely already
|
||||
// exposed from the Esp32 Arduino API. It will be removed once integration is complete.
|
||||
//
|
||||
// Copyright 2015-2016 Espressif Systems (Shanghai) PTE LTD
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#include <string.h>
|
||||
#include <stdio.h>
|
||||
#include "stdlib.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
#include "freertos/semphr.h"
|
||||
#include "freertos/queue.h"
|
||||
|
||||
#include "esp_intr.h"
|
||||
#include "rom/ets_sys.h"
|
||||
#include "soc/gpio_reg.h"
|
||||
#include "soc/gpio_sig_map.h"
|
||||
#include "soc/io_mux_reg.h"
|
||||
#include "soc/rtc_cntl_reg.h"
|
||||
#include "soc/i2s_struct.h"
|
||||
#include "soc/dport_reg.h"
|
||||
#include "soc/sens_reg.h"
|
||||
#include "driver/gpio.h"
|
||||
#include "driver/i2s.h"
|
||||
#include "driver/dac.h"
|
||||
#include "esp32_i2s.h"
|
||||
#include "esp32-hal.h"
|
||||
|
||||
#define I2S_BASE_CLK (160000000L)
|
||||
#define ESP32_REG(addr) (*((volatile uint32_t*)(0x3FF00000+(addr))))
|
||||
|
||||
#define I2S_DMA_QUEUE_SIZE 16
|
||||
#define I2S_DMA_MAX_DATA_LEN 4092// maximum bytes in one dma item
|
||||
#define I2S_DMA_SILENCE_LEN 256 // bytes
|
||||
|
||||
typedef struct i2s_dma_item_s {
|
||||
uint32_t blocksize: 12; // datalen
|
||||
uint32_t datalen : 12; // len*(bits_per_sample/8)*2 => max 2047*8bit/1023*16bit samples
|
||||
uint32_t unused : 5; // 0
|
||||
uint32_t sub_sof : 1; // 0
|
||||
uint32_t eof : 1; // 1 => last?
|
||||
uint32_t owner : 1; // 1
|
||||
|
||||
void* data; // malloc(datalen)
|
||||
struct i2s_dma_item_s* next;
|
||||
|
||||
// if this pointer is not null, it will be freed
|
||||
void* free_ptr;
|
||||
|
||||
// if DMA buffers are preallocated
|
||||
uint8_t* buf;
|
||||
} i2s_dma_item_t;
|
||||
|
||||
typedef struct {
|
||||
i2s_dev_t* bus;
|
||||
int8_t ws;
|
||||
int8_t bck;
|
||||
int8_t out;
|
||||
int8_t in;
|
||||
uint32_t rate;
|
||||
intr_handle_t isr_handle;
|
||||
xQueueHandle tx_queue;
|
||||
|
||||
uint8_t* silence_buf;
|
||||
size_t silence_len;
|
||||
|
||||
i2s_dma_item_t* dma_items;
|
||||
size_t dma_count;
|
||||
uint32_t dma_buf_len :12;
|
||||
uint32_t unused :20;
|
||||
} i2s_bus_t;
|
||||
|
||||
static uint8_t i2s_silence_buf[I2S_DMA_SILENCE_LEN];
|
||||
|
||||
static i2s_bus_t I2S[2] = {
|
||||
{&I2S0, -1, -1, -1, -1, 0, NULL, NULL, i2s_silence_buf, I2S_DMA_SILENCE_LEN, NULL, I2S_DMA_QUEUE_SIZE, 0, 0},
|
||||
{&I2S1, -1, -1, -1, -1, 0, NULL, NULL, i2s_silence_buf, I2S_DMA_SILENCE_LEN, NULL, I2S_DMA_QUEUE_SIZE, 0, 0}
|
||||
};
|
||||
|
||||
void IRAM_ATTR i2sDmaISR(void* arg);
|
||||
bool i2sInitDmaItems(uint8_t bus_num);
|
||||
|
||||
bool i2sInitDmaItems(uint8_t bus_num) {
|
||||
if (bus_num > 1) {
|
||||
return false;
|
||||
}
|
||||
if (I2S[bus_num].tx_queue) {// already set
|
||||
return true;
|
||||
}
|
||||
|
||||
if (I2S[bus_num].dma_items == NULL) {
|
||||
I2S[bus_num].dma_items = (i2s_dma_item_t*)malloc(I2S[bus_num].dma_count* sizeof(i2s_dma_item_t));
|
||||
if (I2S[bus_num].dma_items == NULL) {
|
||||
log_e("MEM ERROR!");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
int i, i2, a;
|
||||
i2s_dma_item_t* item;
|
||||
|
||||
for(i=0; i<I2S[bus_num].dma_count; i++) {
|
||||
i2 = (i+1) % I2S[bus_num].dma_count;
|
||||
item = &I2S[bus_num].dma_items[i];
|
||||
item->eof = 1;
|
||||
item->owner = 1;
|
||||
item->sub_sof = 0;
|
||||
item->unused = 0;
|
||||
item->data = I2S[bus_num].silence_buf;
|
||||
item->blocksize = I2S[bus_num].silence_len;
|
||||
item->datalen = I2S[bus_num].silence_len;
|
||||
item->next = &I2S[bus_num].dma_items[i2];
|
||||
item->free_ptr = NULL;
|
||||
if (I2S[bus_num].dma_buf_len) {
|
||||
item->buf = (uint8_t*)malloc(I2S[bus_num].dma_buf_len);
|
||||
if (item->buf == NULL) {
|
||||
log_e("MEM ERROR!");
|
||||
for(a=0; a<i; a++) {
|
||||
free(I2S[bus_num].dma_items[i].buf);
|
||||
}
|
||||
free(I2S[bus_num].dma_items);
|
||||
I2S[bus_num].dma_items = NULL;
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
item->buf = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
I2S[bus_num].tx_queue = xQueueCreate(I2S[bus_num].dma_count, sizeof(i2s_dma_item_t*));
|
||||
if (I2S[bus_num].tx_queue == NULL) {// memory error
|
||||
log_e("MEM ERROR!");
|
||||
free(I2S[bus_num].dma_items);
|
||||
I2S[bus_num].dma_items = NULL;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void i2sSetSilenceBuf(uint8_t bus_num, uint8_t* data, size_t len) {
|
||||
if (bus_num > 1 || !data || !len) {
|
||||
return;
|
||||
}
|
||||
I2S[bus_num].silence_buf = data;
|
||||
I2S[bus_num].silence_len = len;
|
||||
}
|
||||
|
||||
esp_err_t i2sSetClock(uint8_t bus_num, uint8_t div_num, uint8_t div_b, uint8_t div_a, uint8_t bck, uint8_t bits) {
|
||||
if (bus_num > 1 || div_a > 63 || div_b > 63 || bck > 63) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
i2s_dev_t* i2s = I2S[bus_num].bus;
|
||||
i2s->clkm_conf.clka_en = 0;
|
||||
i2s->clkm_conf.clkm_div_a = div_a;
|
||||
i2s->clkm_conf.clkm_div_b = div_b;
|
||||
i2s->clkm_conf.clkm_div_num = div_num;
|
||||
i2s->sample_rate_conf.tx_bck_div_num = bck;
|
||||
i2s->sample_rate_conf.rx_bck_div_num = bck;
|
||||
i2s->sample_rate_conf.tx_bits_mod = bits;
|
||||
i2s->sample_rate_conf.rx_bits_mod = bits;
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void i2sSetTxDataMode(uint8_t bus_num, i2s_tx_chan_mod_t chan_mod, i2s_tx_fifo_mod_t fifo_mod) {
|
||||
if (bus_num > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
I2S[bus_num].bus->conf_chan.tx_chan_mod = chan_mod; // 0:dual channel; 1:right channel; 2:left channel; 3:left channel constant; 4:right channel constant; (channels flipped if tx_msb_right == 1)
|
||||
I2S[bus_num].bus->fifo_conf.tx_fifo_mod = fifo_mod; // 0:16-bit dual channel; 1:16-bit single channel; 2:32-bit dual channel; 3:32-bit single channel data
|
||||
}
|
||||
|
||||
void i2sSetDac(uint8_t bus_num, bool right, bool left) {
|
||||
if (bus_num > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!right && !left) {
|
||||
dac_output_disable(1);
|
||||
dac_output_disable(2);
|
||||
dac_i2s_disable();
|
||||
I2S[bus_num].bus->conf2.lcd_en = 0;
|
||||
I2S[bus_num].bus->conf.tx_right_first = 0;
|
||||
I2S[bus_num].bus->conf2.camera_en = 0;
|
||||
I2S[bus_num].bus->conf.tx_msb_shift = 1;// I2S signaling
|
||||
return;
|
||||
}
|
||||
|
||||
i2sSetPins(bus_num, -1, -1, -1, -1);
|
||||
I2S[bus_num].bus->conf2.lcd_en = 1;
|
||||
I2S[bus_num].bus->conf.tx_right_first = 0;
|
||||
I2S[bus_num].bus->conf2.camera_en = 0;
|
||||
I2S[bus_num].bus->conf.tx_msb_shift = 0;
|
||||
dac_i2s_enable();
|
||||
|
||||
if (right) {// DAC1, right channel, GPIO25
|
||||
dac_output_enable(1);
|
||||
}
|
||||
if (left) { // DAC2, left channel, GPIO26
|
||||
dac_output_enable(2);
|
||||
}
|
||||
}
|
||||
|
||||
void i2sSetPins(uint8_t bus_num, int8_t out, int8_t ws, int8_t bck, int8_t in) {
|
||||
if (bus_num > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ((ws >= 0 && I2S[bus_num].ws == -1) || (bck >= 0 && I2S[bus_num].bck == -1) || (out >= 0 && I2S[bus_num].out == -1)) {
|
||||
i2sSetDac(bus_num, false, false);
|
||||
}
|
||||
|
||||
if (ws >= 0) {
|
||||
if (I2S[bus_num].ws != ws) {
|
||||
if (I2S[bus_num].ws >= 0) {
|
||||
gpio_matrix_out(I2S[bus_num].ws, 0x100, false, false);
|
||||
}
|
||||
I2S[bus_num].ws = ws;
|
||||
pinMode(ws, OUTPUT);
|
||||
gpio_matrix_out(ws, bus_num?I2S1O_WS_OUT_IDX:I2S0O_WS_OUT_IDX, false, false);
|
||||
}
|
||||
} else if (I2S[bus_num].ws >= 0) {
|
||||
gpio_matrix_out(I2S[bus_num].ws, 0x100, false, false);
|
||||
I2S[bus_num].ws = -1;
|
||||
}
|
||||
|
||||
if (bck >= 0) {
|
||||
if (I2S[bus_num].bck != bck) {
|
||||
if (I2S[bus_num].bck >= 0) {
|
||||
gpio_matrix_out(I2S[bus_num].bck, 0x100, false, false);
|
||||
}
|
||||
I2S[bus_num].bck = bck;
|
||||
pinMode(bck, OUTPUT);
|
||||
gpio_matrix_out(bck, bus_num?I2S1O_BCK_OUT_IDX:I2S0O_BCK_OUT_IDX, false, false);
|
||||
}
|
||||
} else if (I2S[bus_num].bck >= 0) {
|
||||
gpio_matrix_out(I2S[bus_num].bck, 0x100, false, false);
|
||||
I2S[bus_num].bck = -1;
|
||||
}
|
||||
|
||||
if (out >= 0) {
|
||||
if (I2S[bus_num].out != out) {
|
||||
if (I2S[bus_num].out >= 0) {
|
||||
gpio_matrix_out(I2S[bus_num].out, 0x100, false, false);
|
||||
}
|
||||
I2S[bus_num].out = out;
|
||||
pinMode(out, OUTPUT);
|
||||
gpio_matrix_out(out, bus_num?I2S1O_DATA_OUT23_IDX:I2S0O_DATA_OUT23_IDX, false, false);
|
||||
}
|
||||
} else if (I2S[bus_num].out >= 0) {
|
||||
gpio_matrix_out(I2S[bus_num].out, 0x100, false, false);
|
||||
I2S[bus_num].out = -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool i2sWriteDone(uint8_t bus_num) {
|
||||
if (bus_num > 1) {
|
||||
return false;
|
||||
}
|
||||
return (I2S[bus_num].dma_items[0].data == I2S[bus_num].silence_buf && I2S[bus_num].dma_items[1].data == I2S[bus_num].silence_buf);
|
||||
}
|
||||
|
||||
void i2sInit(uint8_t bus_num, uint32_t bits_per_sample, uint32_t sample_rate, i2s_tx_chan_mod_t chan_mod, i2s_tx_fifo_mod_t fifo_mod, size_t dma_count, size_t dma_len) {
|
||||
if (bus_num > 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
I2S[bus_num].dma_count = dma_count;
|
||||
I2S[bus_num].dma_buf_len = dma_len & 0xFFF;
|
||||
|
||||
if (!i2sInitDmaItems(bus_num)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (bus_num) {
|
||||
periph_module_enable(PERIPH_I2S1_MODULE);
|
||||
} else {
|
||||
periph_module_enable(PERIPH_I2S0_MODULE);
|
||||
}
|
||||
|
||||
esp_intr_disable(I2S[bus_num].isr_handle);
|
||||
i2s_dev_t* i2s = I2S[bus_num].bus;
|
||||
i2s->out_link.stop = 1;
|
||||
i2s->conf.tx_start = 0;
|
||||
i2s->int_ena.val = 0;
|
||||
i2s->int_clr.val = 0xFFFFFFFF;
|
||||
i2s->fifo_conf.dscr_en = 0;
|
||||
|
||||
// reset fifo
|
||||
i2s->conf.rx_fifo_reset = 1;
|
||||
i2s->conf.rx_fifo_reset = 0;
|
||||
i2s->conf.tx_fifo_reset = 1;
|
||||
i2s->conf.tx_fifo_reset = 0;
|
||||
|
||||
// reset i2s
|
||||
i2s->conf.tx_reset = 1;
|
||||
i2s->conf.tx_reset = 0;
|
||||
i2s->conf.rx_reset = 1;
|
||||
i2s->conf.rx_reset = 0;
|
||||
|
||||
// reset dma
|
||||
i2s->lc_conf.in_rst = 1;
|
||||
i2s->lc_conf.in_rst = 0;
|
||||
i2s->lc_conf.out_rst = 1;
|
||||
i2s->lc_conf.out_rst = 0;
|
||||
|
||||
// Enable and configure DMA
|
||||
i2s->lc_conf.check_owner = 0;
|
||||
i2s->lc_conf.out_loop_test = 0;
|
||||
i2s->lc_conf.out_auto_wrback = 0;
|
||||
i2s->lc_conf.out_data_burst_en = 0;
|
||||
i2s->lc_conf.outdscr_burst_en = 0;
|
||||
i2s->lc_conf.out_no_restart_clr = 0;
|
||||
i2s->lc_conf.indscr_burst_en = 0;
|
||||
i2s->lc_conf.out_eof_mode = 1;
|
||||
|
||||
i2s->pdm_conf.pcm2pdm_conv_en = 0;
|
||||
i2s->pdm_conf.pdm2pcm_conv_en = 0;
|
||||
// SET_PERI_REG_BITS(RTC_CNTL_CLK_CONF_REG, RTC_CNTL_SOC_CLK_SEL, 0x1, RTC_CNTL_SOC_CLK_SEL_S);
|
||||
|
||||
|
||||
i2s->conf_chan.tx_chan_mod = chan_mod; // 0-two channel;1-right;2-left;3-righ;4-left
|
||||
i2s->conf_chan.rx_chan_mod = chan_mod; // 0-two channel;1-right;2-left;3-righ;4-left
|
||||
i2s->fifo_conf.tx_fifo_mod = fifo_mod; // 0-right&left channel;1-one channel
|
||||
i2s->fifo_conf.rx_fifo_mod = fifo_mod; // 0-right&left channel;1-one channel
|
||||
|
||||
i2s->conf.tx_mono = 0;
|
||||
i2s->conf.rx_mono = 0;
|
||||
|
||||
i2s->conf.tx_start = 0;
|
||||
i2s->conf.rx_start = 0;
|
||||
|
||||
i2s->conf.tx_short_sync = 0;
|
||||
i2s->conf.rx_short_sync = 0;
|
||||
i2s->conf.tx_msb_shift = (bits_per_sample != 8);// 0:DAC/PCM, 1:I2S
|
||||
i2s->conf.rx_msb_shift = 0;
|
||||
|
||||
i2s->conf.tx_slave_mod = 0; // Master
|
||||
|
||||
i2s->conf.tx_msb_right = 0;
|
||||
i2s->conf.tx_right_first = (bits_per_sample == 8);
|
||||
i2s->conf2.lcd_en = (bits_per_sample == 8);
|
||||
i2s->conf2.camera_en = 0;
|
||||
|
||||
i2s->fifo_conf.tx_fifo_mod_force_en = 1;
|
||||
|
||||
i2s->pdm_conf.rx_pdm_en = 0;
|
||||
i2s->pdm_conf.tx_pdm_en = 0;
|
||||
|
||||
i2sSetSampleRate(bus_num, sample_rate, bits_per_sample);
|
||||
|
||||
// enable intr in cpu //
|
||||
esp_intr_alloc(bus_num?ETS_I2S1_INTR_SOURCE:ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_IRAM | ESP_INTR_FLAG_LEVEL1, &i2sDmaISR, &I2S[bus_num], &I2S[bus_num].isr_handle);
|
||||
// enable send intr
|
||||
i2s->int_ena.out_eof = 1;
|
||||
i2s->int_ena.out_dscr_err = 1;
|
||||
|
||||
i2s->fifo_conf.dscr_en = 1;// enable dma
|
||||
i2s->out_link.start = 0;
|
||||
i2s->out_link.addr = (uint32_t)(&I2S[bus_num].dma_items[0]); // loads dma_struct to dma
|
||||
i2s->out_link.start = 1; // starts dma
|
||||
i2s->conf.tx_start = 1;// Start I2s module
|
||||
|
||||
esp_intr_enable(I2S[bus_num].isr_handle);
|
||||
}
|
||||
|
||||
esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t rate, uint8_t bits) {
|
||||
if (bus_num > 1) {
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (I2S[bus_num].rate == rate) {
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
int clkmInteger, clkmDecimals, bck = 0;
|
||||
double denom = (double)1 / 63;
|
||||
int channel = 2;
|
||||
|
||||
double mclk, clkmdiv;
|
||||
int factor;
|
||||
|
||||
if (bits == 8) {
|
||||
factor = 120;
|
||||
} else {
|
||||
factor = (256 % bits) ? 384 : 256;
|
||||
}
|
||||
|
||||
clkmdiv = (double)I2S_BASE_CLK / (rate* factor);
|
||||
if (clkmdiv > 256) {
|
||||
log_e("rate is too low");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
I2S[bus_num].rate = rate;
|
||||
|
||||
clkmInteger = clkmdiv;
|
||||
clkmDecimals = ((clkmdiv - clkmInteger) / denom);
|
||||
|
||||
if (bits == 8) {
|
||||
mclk = rate* factor;
|
||||
bck = 60;
|
||||
bits = 16;
|
||||
} else {
|
||||
mclk = (double)clkmInteger + (denom* clkmDecimals);
|
||||
bck = factor/(bits* channel);
|
||||
}
|
||||
|
||||
i2sSetClock(bus_num, clkmInteger, clkmDecimals, 63, bck, bits);
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void IRAM_ATTR i2sDmaISR(void* arg)
|
||||
{
|
||||
i2s_dma_item_t* dummy = NULL;
|
||||
i2s_bus_t* dev = (i2s_bus_t*)arg;
|
||||
portBASE_TYPE hpTaskAwoken = 0;
|
||||
|
||||
if (dev->bus->int_st.out_dscr_err) {
|
||||
ets_printf("out_dscr_err\n");
|
||||
}
|
||||
while (dev->bus->int_st.out_eof) {
|
||||
i2s_dma_item_t* item = (i2s_dma_item_t*)dev->bus->out_eof_des_addr;
|
||||
dev->bus->int_clr.out_eof = 1;
|
||||
|
||||
if (item->data != dev->silence_buf) {
|
||||
item->data = dev->silence_buf;
|
||||
item->blocksize = dev->silence_len;
|
||||
item->datalen = dev->silence_len;
|
||||
}
|
||||
if (xQueueIsQueueFullFromISR(dev->tx_queue) == pdTRUE && xQueueReceiveFromISR(dev->tx_queue, &dummy, &hpTaskAwoken) == pdTRUE && dummy->free_ptr) {
|
||||
free(dummy->free_ptr);
|
||||
dummy->free_ptr = NULL;
|
||||
}
|
||||
xQueueSendFromISR(dev->tx_queue, (void*)&item, &hpTaskAwoken);
|
||||
}
|
||||
dev->bus->int_clr.val = dev->bus->int_st.val;
|
||||
if (hpTaskAwoken == pdTRUE) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
|
||||
size_t i2sWrite(uint8_t bus_num, uint8_t* data, size_t len, bool copy, bool free_when_sent) {
|
||||
if (bus_num > 1 || !I2S[bus_num].tx_queue) {
|
||||
return 0;
|
||||
}
|
||||
size_t index = 0;
|
||||
size_t toSend = len;
|
||||
size_t limit = I2S_DMA_MAX_DATA_LEN;
|
||||
i2s_dma_item_t* item = NULL;
|
||||
|
||||
if (I2S[bus_num].dma_buf_len) {
|
||||
limit = I2S[bus_num].dma_buf_len;
|
||||
}
|
||||
|
||||
while (len) {
|
||||
if (xQueueReceive(I2S[bus_num].tx_queue, &item, portMAX_DELAY) == pdFALSE) {
|
||||
log_e("xQueueReceive failed\n");
|
||||
break;
|
||||
}
|
||||
if (item->free_ptr) {
|
||||
free(item->free_ptr);
|
||||
item->free_ptr = NULL;
|
||||
}
|
||||
|
||||
toSend = len;
|
||||
|
||||
if (toSend > limit) {
|
||||
toSend = limit;
|
||||
}
|
||||
|
||||
if (!copy) {
|
||||
// data is constant. no need to copy
|
||||
item->data = data+index;
|
||||
item->blocksize = toSend;
|
||||
item->datalen = toSend;
|
||||
if (free_when_sent && (toSend == len)) {
|
||||
// free the full data when done
|
||||
item->free_ptr = data;
|
||||
}
|
||||
} else if (item->buf) {
|
||||
// dma buffers are preallocated
|
||||
memcpy(item->buf, data+index, toSend);
|
||||
item->data = item->buf;
|
||||
item->blocksize = toSend;
|
||||
item->datalen = toSend;
|
||||
} else {
|
||||
// copy data to a new buffer (dma buffers are NULL)
|
||||
uint8_t* buf = (uint8_t*)malloc(toSend);
|
||||
if (!buf) {
|
||||
log_e("buf = NULL; %u/%u (%u/%u)\n", index, len, toSend, esp_get_free_heap_size());
|
||||
break;
|
||||
}
|
||||
memcpy(buf, data+index, toSend);
|
||||
item->free_ptr = buf;
|
||||
item->data = buf;
|
||||
item->blocksize = toSend;
|
||||
item->datalen = toSend;
|
||||
}
|
||||
len -= toSend;
|
||||
index += toSend;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
#endif
|
38
src/internal/Esp32_i2s.h
Normal file
38
src/internal/Esp32_i2s.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(ARDUINO_ARCH_ESP32)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#include "esp_err.h"
|
||||
|
||||
typedef enum {
|
||||
I2S_CHAN_STEREO, I2S_CHAN_RIGHT_TO_LEFT, I2S_CHAN_LEFT_TO_RIGHT, I2S_CHAN_RIGHT_ONLY, I2S_CHAN_LEFT_ONLY
|
||||
} i2s_tx_chan_mod_t;
|
||||
|
||||
typedef enum {
|
||||
I2S_FIFO_16BIT_DUAL, I2S_FIFO_16BIT_SINGLE, I2S_FIFO_32BIT_DUAL, I2S_FIFO_32BIT_SINGLE
|
||||
} i2s_tx_fifo_mod_t;
|
||||
|
||||
void i2sInit(uint8_t bus_num, uint32_t bits_per_sample, uint32_t sample_rate, i2s_tx_chan_mod_t chan_mod, i2s_tx_fifo_mod_t fifo_mod, size_t dma_count, size_t dma_len);
|
||||
|
||||
void i2sSetPins(uint8_t bus_num, int8_t out, int8_t ws, int8_t bck, int8_t in);
|
||||
void i2sSetDac(uint8_t bus_num, bool right, bool left);
|
||||
|
||||
esp_err_t i2sSetClock(uint8_t bus_num, uint8_t div_num, uint8_t div_b, uint8_t div_a, uint8_t bck, uint8_t bits_per_sample);
|
||||
esp_err_t i2sSetSampleRate(uint8_t bus_num, uint32_t sample_rate, uint8_t bits_per_sample);
|
||||
|
||||
void i2sSetTxDataMode(uint8_t bus_num, i2s_tx_chan_mod_t chan_mod, i2s_tx_fifo_mod_t fifo_mod);
|
||||
|
||||
void i2sSetSilenceBuf(uint8_t bus_num, uint8_t* data, size_t len);
|
||||
|
||||
size_t i2sWrite(uint8_t bus_num, uint8_t* data, size_t len, bool copy, bool free_when_sent);
|
||||
bool i2sWriteDone(uint8_t bus_num);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif
|
189
src/internal/NeoEsp32I2sMethod.h
Normal file
189
src/internal/NeoEsp32I2sMethod.h
Normal file
@@ -0,0 +1,189 @@
|
||||
/*-------------------------------------------------------------------------
|
||||
NeoPixel library helper functions for Esp32.
|
||||
|
||||
Written by Michael C. Miller.
|
||||
|
||||
I invest time and resources providing this open source code,
|
||||
please support me by dontating (see https://github.com/Makuna/NeoPixelBus)
|
||||
|
||||
-------------------------------------------------------------------------
|
||||
This file is part of the Makuna/NeoPixelBus library.
|
||||
|
||||
NeoPixelBus is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as
|
||||
published by the Free Software Foundation, either version 3 of
|
||||
the License, or (at your option) any later version.
|
||||
|
||||
NeoPixelBus is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public
|
||||
License along with NeoPixel. If not, see
|
||||
<http://www.gnu.org/licenses/>.
|
||||
-------------------------------------------------------------------------*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
|
||||
extern "C"
|
||||
{
|
||||
#include <Arduino.h>
|
||||
#include "Esp32_i2s.h"
|
||||
}
|
||||
|
||||
const uint16_t c_dmaBytesPerPixelBytes = 4;
|
||||
const uint16_t c_dmaBytesPer50us = 20;
|
||||
const uint32_t c_dmaI2sSampleRate = 100000;
|
||||
|
||||
class NeoEsp32I2sSpeedWs2813
|
||||
{
|
||||
public:
|
||||
const static uint16_t I2sSampleRateDiv = 1;
|
||||
const static uint16_t ResetTimeUs = 250;
|
||||
};
|
||||
|
||||
class NeoEsp32I2sSpeed800Kbps
|
||||
{
|
||||
public:
|
||||
const static uint16_t I2sSampleRateDiv = 1;
|
||||
const static uint16_t ResetTimeUs = 50;
|
||||
};
|
||||
|
||||
class NeoEsp32I2sSpeed400Kbps
|
||||
{
|
||||
public:
|
||||
const static uint16_t I2sSampleRateDiv = 2;
|
||||
const static uint16_t ResetTimeUs = 50;
|
||||
};
|
||||
|
||||
class NeoEsp32I2sBusZero
|
||||
{
|
||||
public:
|
||||
const static uint8_t I2sBusNumber = 0;
|
||||
};
|
||||
|
||||
class NeoEsp32I2sBusOne
|
||||
{
|
||||
public:
|
||||
const static uint8_t I2sBusNumber = 1;
|
||||
};
|
||||
|
||||
template<typename T_SPEED, typename T_BUS> class NeoEsp32I2sMethodBase
|
||||
{
|
||||
public:
|
||||
NeoEsp32I2sMethodBase(uint8_t pin, uint16_t pixelCount, size_t elementSize) :
|
||||
_pin(pin)
|
||||
{
|
||||
uint16_t dmaPixelSize = c_dmaBytesPerPixelBytes * elementSize;
|
||||
uint16_t resetSize = (c_dmaBytesPer50us * T_SPEED::ResetTimeUs / 50 / T_SPEED::I2sSampleRateDiv);
|
||||
|
||||
_pixelsSize = pixelCount * elementSize;
|
||||
_i2sBufferSize = pixelCount * dmaPixelSize + resetSize;
|
||||
|
||||
// must have a 4 byte aligned buffer for i2s
|
||||
uint32_t alignment = _i2sBufferSize % 4;
|
||||
if (alignment)
|
||||
{
|
||||
_i2sBufferSize += 4 - alignment;
|
||||
}
|
||||
|
||||
_pixels = (uint8_t*)malloc(_pixelsSize);
|
||||
memset(_pixels, 0x00, _pixelsSize);
|
||||
|
||||
_i2sBuffer = (uint8_t*)malloc(_i2sBufferSize);
|
||||
memset(_i2sBuffer, 0x00, _i2sBufferSize);
|
||||
}
|
||||
|
||||
~NeoEsp32I2sMethodBase()
|
||||
{
|
||||
while (!IsReadyToUpdate())
|
||||
{
|
||||
yield();
|
||||
}
|
||||
|
||||
pinMode(_pin, INPUT);
|
||||
|
||||
free(_pixels);
|
||||
free(_i2sBuffer);
|
||||
}
|
||||
|
||||
bool IsReadyToUpdate() const
|
||||
{
|
||||
return (i2sWriteDone(T_BUS::I2sBusNumber));
|
||||
}
|
||||
|
||||
void Initialize()
|
||||
{
|
||||
i2sInit(T_BUS::I2sBusNumber, 16, c_dmaI2sSampleRate / T_SPEED::I2sSampleRateDiv, I2S_CHAN_STEREO, I2S_FIFO_16BIT_DUAL, 2, 0);
|
||||
i2sSetPins(T_BUS::I2sBusNumber, _pin, -1, -1, -1);
|
||||
}
|
||||
|
||||
void Update()
|
||||
{
|
||||
// wait for not actively sending data
|
||||
while (!IsReadyToUpdate())
|
||||
{
|
||||
yield();
|
||||
}
|
||||
|
||||
FillBuffers();
|
||||
|
||||
i2sWrite(T_BUS::I2sBusNumber, _i2sBuffer, _i2sBufferSize, false, false);
|
||||
}
|
||||
|
||||
uint8_t* getPixels() const
|
||||
{
|
||||
return _pixels;
|
||||
};
|
||||
|
||||
size_t getPixelsSize() const
|
||||
{
|
||||
return _pixelsSize;
|
||||
}
|
||||
|
||||
private:
|
||||
const uint8_t _pin; // output pin number
|
||||
|
||||
size_t _pixelsSize; // Size of '_pixels' buffer
|
||||
uint8_t* _pixels; // Holds LED color values
|
||||
|
||||
uint32_t _i2sBufferSize; // total size of _i2sBuffer
|
||||
uint8_t* _i2sBuffer; // holds the DMA buffer that is referenced by _i2sBufDesc
|
||||
|
||||
void FillBuffers()
|
||||
{
|
||||
const uint16_t bitpatterns[16] =
|
||||
{
|
||||
0b1000100010001000, 0b1000100010001110, 0b1000100011101000, 0b1000100011101110,
|
||||
0b1000111010001000, 0b1000111010001110, 0b1000111011101000, 0b1000111011101110,
|
||||
0b1110100010001000, 0b1110100010001110, 0b1110100011101000, 0b1110100011101110,
|
||||
0b1110111010001000, 0b1110111010001110, 0b1110111011101000, 0b1110111011101110,
|
||||
};
|
||||
|
||||
uint16_t* pDma = (uint16_t*)_i2sBuffer;
|
||||
uint8_t* pPixelsEnd = _pixels + _pixelsSize;
|
||||
for (uint8_t* pPixel = _pixels; pPixel < pPixelsEnd; pPixel++)
|
||||
{
|
||||
*(pDma++) = bitpatterns[((*pPixel) & 0x0f)];
|
||||
*(pDma++) = bitpatterns[((*pPixel) >> 4) & 0x0f];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedWs2813, NeoEsp32I2sBusZero> NeoEsp32I2s0Ws2813Method;
|
||||
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed800Kbps, NeoEsp32I2sBusZero> NeoEsp32I2s0800KbpsMethod;
|
||||
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed400Kbps, NeoEsp32I2sBusZero> NeoEsp32I2s0400KbpsMethod;
|
||||
|
||||
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeedWs2813, NeoEsp32I2sBusOne> NeoEsp32I2s1Ws2813Method;
|
||||
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed800Kbps, NeoEsp32I2sBusOne> NeoEsp32I2s1800KbpsMethod;
|
||||
typedef NeoEsp32I2sMethodBase<NeoEsp32I2sSpeed400Kbps, NeoEsp32I2sBusOne> NeoEsp32I2s1400KbpsMethod;
|
||||
|
||||
// I2s Bus 1 method is the default method for Esp32
|
||||
typedef NeoEsp32I2s1Ws2813Method NeoWs2813Method;
|
||||
typedef NeoEsp32I2s1800KbpsMethod Neo800KbpsMethod;
|
||||
typedef NeoEsp32I2s1400KbpsMethod Neo400KbpsMethod;
|
||||
|
||||
#endif
|
@@ -162,11 +162,6 @@ typedef NeoEspBitBangMethodBase<NeoEspBitBangSpeedWs2813> NeoEsp32BitBangWs2813M
|
||||
typedef NeoEspBitBangMethodBase<NeoEspBitBangSpeed800Kbps> NeoEsp32BitBang800KbpsMethod;
|
||||
typedef NeoEspBitBangMethodBase<NeoEspBitBangSpeed400Kbps> NeoEsp32BitBang400KbpsMethod;
|
||||
|
||||
// Bitbang method is the default method for Esp32
|
||||
typedef NeoEsp32BitBangWs2813Method NeoWs2813Method;
|
||||
typedef NeoEsp32BitBang800KbpsMethod Neo800KbpsMethod;
|
||||
typedef NeoEsp32BitBang400KbpsMethod Neo400KbpsMethod;
|
||||
|
||||
#else
|
||||
|
||||
typedef NeoEspBitBangMethodBase<NeoEspBitBangSpeedWs2813> NeoEsp8266BitBangWs2813Method;
|
||||
|
Reference in New Issue
Block a user