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!