i2s DMA pixel data / clock input
Posted: Fri Jun 26, 2020 7:41 pm
Hello, I am attempting to use the esp32 to decode frames from a pinball DMD display. The i2s peripheral on the esp32 seems like it should work. I am using camera slave receiving mode, but am having trouble configuring the DMA/fifo to pull the data in correctly. The display data comes in rows of pixels; 128 bits per row. It is likely I am going about the wrong way but..
I am using VS Code, PlatformIO, and Arduino Framework
Now using 128bytes to represent 128bits seem very inefficient but perhaps that how to do it?
Also, examples I've seen for camera slave mode don't use the I2S0I_BCK_IN_IDX config and instead use the I2S0I_WS_IN_IDX.. this seems odd to me.
Also, I am not using any of the HSYNC/VSYNC/or HREF, but I have tried changing these without success. The esp32 datasheet doesn't give much details into how those signals are used to interpret the pulse stream. I suspect I need to use these...
The buffer full Interrupt get fired and I have a breakpoint in the ISR via JTAG, but the buffer I receive never seems to look even close to what I see on the logic analyzer.
Sorry for the messy code I've patched together! Any advice on how this should be configured is much appreciated!
I am using VS Code, PlatformIO, and Arduino Framework
Code: Select all
#include "soc/soc.h"
#include "soc/gpio_sig_map.h"
#include "soc/i2s_reg.h"
#include "soc/i2s_struct.h"
#include "soc/io_mux_reg.h"
#include "driver/gpio.h"
#include "driver/periph_ctrl.h"
#include "driver/i2s.h"
#include "rom/lldesc.h"
#include <TFT_eSPI.h>
TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library
#ifndef TFT_DISPOFF
#define TFT_DISPOFF 0x28
#endif
#ifndef TFT_SLPIN
#define TFT_SLPIN 0x10
#endif
#define TFT_MOSI 19
#define TFT_SCLK 18
#define TFT_CS 5
#define TFT_DC 16
#define TFT_RST 23
#define TFT_BL 21 // Display backlight control pin
DMA_ATTR uint16_t dma_buff[2][64]; // I2S_RX_FIFO_MOD = 0; 16-bit dual channel data, 128 bytes of these represents a row of dmd data
DMA_ATTR lldesc_t dma_descriptor[2]; // cyclical buffer of 2
uint16_t picture[128][240];
// DMD display size
#define ROW_COUNT 32
#define COL_COUNT 128
uint8_t dots[ROW_COUNT][COL_COUNT]; //test image that is being generated on Teensy
#define DMD_DE GPIO_NUM_36
#define DMD_ROW_DATA GPIO_NUM_37
#define DMD_ROW_CLK GPIO_NUM_38
#define DMD_COL_LATCH GPIO_NUM_39
#define DMD_PIX_CLK GPIO_NUM_32
#define DMD_SDATA GPIO_NUM_33
unsigned long startTime;
unsigned long endTime;
unsigned int frames = 1;
static intr_handle_t i2s_intr_handle;
static void i2s_isr(void* arg);
static void dma_reset();
static void fifo_reset();
void setup() {
Serial.begin(115200);
Serial.println(ESP.getSdkVersion());
tft.init();
tft.setRotation(3); // landscape upside-down
tft.fillScreen(TFT_BLACK);
tft.setTextSize(2);
tft.setTextColor(TFT_WHITE);
tft.setCursor(0, 0);
tft.setTextDatum(MC_DATUM);
tft.setTextSize(1);
if (TFT_BL > 0) {
pinMode(TFT_BL, OUTPUT);
digitalWrite(TFT_BL, HIGH);
}
tft.setSwapBytes(true);
// create test image
for (int row = 0; row < ROW_COUNT; row++) {
for (int col = 0; col < 64; col++) {
if (((row + col) % 8) == 0) {
dots[row][col] = 15;
}
else {
dots[row][col] = 0;
}
if (row < 16)
{
dots[row][col + 64] = col >> 2;
}
else
{
dots[row][col + 64] = 15 - (col >> 2);
}
if (col < 16)
{
dots[row][col] = col;
}
}
}
// display test image in console and convert to on/off
for (int row = 0; row < ROW_COUNT; row++) {
for (int col = 0; col < COL_COUNT; col++) {
if (dots[row][col] & 0x08) {
dots[row][col] = 1;
}
else {
dots[row][col] = 0;
}
Serial.print(dots[row][col]);
}
Serial.println(" ");
}
Serial.println(" ");
// display test image on LCD
tft.pushImage(0, 32, 128, 32, (uint8_t*)dots);
//buff 0
dma_descriptor[0].length = 0; //number of byte written to the buffer
dma_descriptor[0].size = sizeof(dma_buff[0]); //max size of the buffer in bytes
dma_descriptor[0].owner = 1;
dma_descriptor[0].sosf = 1;
dma_descriptor[0].buf = (uint8_t*)&dma_buff[0][0];
dma_descriptor[0].offset = 0;
dma_descriptor[0].empty = 0;
dma_descriptor[0].eof = 0; // was 0
//pointer to the next descriptor
dma_descriptor[0].qe.stqe_next = &dma_descriptor[1];
//buff 1
dma_descriptor[1].length = 0; //number of byte written to the buffer
dma_descriptor[1].size = sizeof(dma_buff[1]); //max size of the buffer in bytes
dma_descriptor[1].owner = 1;
dma_descriptor[1].sosf = 1;
dma_descriptor[1].buf = (uint8_t*)&dma_buff[1][0];
dma_descriptor[1].offset = 0;
dma_descriptor[1].empty = 0;
dma_descriptor[1].eof = 1; // was 0
//pointer to the next descriptor
dma_descriptor[1].qe.stqe_next = &dma_descriptor[0];
//data inputs
pinMode(DMD_DE, INPUT); // H-SYNC ?
pinMode(DMD_ROW_DATA, INPUT); // V-SYNC ?
pinMode(DMD_ROW_CLK, INPUT);
pinMode(DMD_COL_LATCH, INPUT);
pinMode(DMD_PIX_CLK, INPUT); // CLK
pinMode(DMD_SDATA, INPUT); // DATA
gpio_matrix_in(DMD_SDATA, I2S0I_DATA_IN0_IDX, false);
gpio_matrix_in(DMD_PIX_CLK, I2S0I_WS_IN_IDX, false); // word select as clk?
//gpio_matrix_in(DMD_PIX_CLK, I2S0I_BCK_IN_IDX, false); //clk
//for i2s in parallel camera input mode data is receiver only when V_SYNC = H_SYNC = H_ENABLE = 1. We don't use these inputs so simply set them High
gpio_matrix_in(0x38, I2S0I_V_SYNC_IDX, false);
gpio_matrix_in(0x38, I2S0I_H_SYNC_IDX, false); //0x30 sends 0, 0x38 sends 1
gpio_matrix_in(0x38 /*DMD_DE*/, I2S0I_H_ENABLE_IDX, false);
// Enable and configure I2S peripheral
periph_module_enable(PERIPH_I2S0_MODULE);
// Enable slave mode (sampling clock is external)
I2S0.conf.rx_slave_mod = 1;
// Enable parallel mode
I2S0.conf2.lcd_en = 1;
// Use HSYNC/VSYNC/HREF to control sampling
I2S0.conf2.camera_en = 1;
I2S0.clkm_conf.val=0;
I2S0.clkm_conf.clka_en=0;
I2S0.clkm_conf.clkm_div_a=0;
I2S0.clkm_conf.clkm_div_b=0;
//We ignore the possibility for fractional division here.
I2S0.clkm_conf.clkm_div_num=1;
// FIFO will sink data to DMA
I2S0.fifo_conf.dscr_en = 1;
I2S0.fifo_conf.tx_fifo_mod_force_en = 1;
I2S0.fifo_conf.rx_fifo_mod_force_en = 1;
// FIFO configuration
I2S0.fifo_conf.rx_data_num=64; //Thresholds.
I2S0.fifo_conf.tx_data_num=0;
I2S0.fifo_conf.rx_fifo_mod = 0;
I2S0.conf_chan.rx_chan_mod = 0;
// Clear flags which are used in I2S serial mode
I2S0.sample_rate_conf.val=0;
I2S0.conf.rx_right_first = 0;
I2S0.conf.rx_msb_right = 0;
I2S0.conf.rx_msb_shift = 0;
I2S0.conf.rx_mono = 0;
I2S0.conf.rx_short_sync = 0;
I2S0.timing.val = 0;
//start i2s
I2S0.rx_eof_num = 64; //0xFFFFFFFF;
I2S0.in_link.addr = (uint32_t)&dma_descriptor[0];
I2S0.in_link.start = 1;
I2S0.int_clr.val = I2S0.int_raw.val;
I2S0.int_ena.val = 0; // was 0
I2S0.int_ena.in_suc_eof = 1; // I2S_IN_SUC_EOF_INT: Triggered when all data have been received; interrupt
//Set int handler
esp_err_t err = esp_intr_alloc(ETS_I2S0_INTR_SOURCE, 0, &i2s_isr, (void*)&I2S0, NULL);
if (err != ESP_OK) ESP_LOGE(TAG, "Error esp_intr_alloc %s", esp_err_to_name(err));
if (err == ESP_OK) Serial.println("esp_intr_alloc returned ESP_OK");
//err = esp_intr_enable(i2s_intr_handle);
//if (err != ESP_OK) ESP_LOGE(TAG, "error in esp_intr_enable: %s", esp_err_to_name(err));
//if (err == ESP_OK) Serial.println("esp_intr_enable returned ESP_OK");
//start i2s + dma
I2S0.conf.rx_start = 1;
}
static void IRAM_ATTR i2s_isr(void* arg) {
if (I2S0.int_st.in_suc_eof) {
I2S0.int_clr.val = I2S0.int_st.val;
lldesc_t *finish_desc = (lldesc_t*)I2S0.in_eof_des_addr;
// TODO: take buffer data and add the row to the growing frame
} else {
I2S0.int_clr.val = I2S0.int_st.val;
Serial.print("unexpected interrupt: ");
Serial.println(I2S0.int_st.val);
}
}
static void dma_reset() {
I2S0.lc_conf.in_rst=1; I2S0.lc_conf.in_rst=0;
I2S0.lc_conf.out_rst=1; I2S0.lc_conf.out_rst=0;
}
static void fifo_reset() {
I2S0.conf.rx_fifo_reset=1; I2S0.conf.rx_fifo_reset=0;
I2S0.conf.tx_fifo_reset=1; I2S0.conf.tx_fifo_reset=0;
}
void loop() {
}
Also, examples I've seen for camera slave mode don't use the I2S0I_BCK_IN_IDX config and instead use the I2S0I_WS_IN_IDX.. this seems odd to me.
Also, I am not using any of the HSYNC/VSYNC/or HREF, but I have tried changing these without success. The esp32 datasheet doesn't give much details into how those signals are used to interpret the pulse stream. I suspect I need to use these...
The buffer full Interrupt get fired and I have a breakpoint in the ISR via JTAG, but the buffer I receive never seems to look even close to what I see on the logic analyzer.
Sorry for the messy code I've patched together! Any advice on how this should be configured is much appreciated!