Last post on this topic. Got it working, requires rx_dma_equal = 1. I'm not super confident of the meaning of this register, but there ya go. Good luck to any that stray here from the future.
Code: Select all
#include "driver/gpio.h"
#include "driver/i2s.h"
#include "driver/ledc.h"
#include "driver/periph_ctrl.h"
#include "esp_err.h"
#include "esp_intr.h"
#include "esp_log.h"
#include "esp_system.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "rom/lldesc.h"
#include "soc/gpio_sig_map.h"
#include "soc/i2s_reg.h"
#include "soc/i2s_struct.h"
#include "soc/io_mux_reg.h"
#include "soc/soc.h"
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_buff( void *p_buffer, uint16_t buffer_size );
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
typedef void ( *parallel_data_ready_cb_t )( const uint8_t *p_data, uint16_t data_len );
void parallel_input_manager_init( parallel_data_ready_cb_t cb );
static void _parallel_data_ready_cb( const uint8_t *p_data, uint16_t data_len );
static uint8_t *p_parallel_data;
static uint32_t parallel_data_len;
static uint32_t isr_cnt = 0;
//-----------------------------------------------------------------------------
void app_main()
{
parallel_input_manager_init( _parallel_data_ready_cb );
while ( 1 )
{
static uint8_t last = 0;
if ( last != isr_cnt )
{
printf( "ISR Cnt: %i\n", isr_cnt );
last = isr_cnt;
}
vTaskDelay( 1000 / portTICK_RATE_MS );
if ( parallel_data_len != 0 )
{
print_buff( p_parallel_data, parallel_data_len );
parallel_data_len = 0;
}
}
}
//-----------------------------------------------------------------------------
static void _parallel_data_ready_cb( const uint8_t *p_data, uint16_t data_len )
{
if ( parallel_data_len == 0 )
{
p_parallel_data = (uint8_t*)p_data;
parallel_data_len = data_len;
}
}
//-----------------------------------------------------------------------------
typedef struct
{
bool initialized;
parallel_data_ready_cb_t data_ready_cb;
} p_i_manager_t;
p_i_manager_t s_pi = { 0 };
// Assumes one word per channel
#define MAX_DMA_TRANSFER_SIZE ( 1024 )
#define NUM_CHAINED_DMAS ( 2 )
DMA_ATTR uint8_t dma_buff[NUM_CHAINED_DMAS][MAX_DMA_TRANSFER_SIZE];
DMA_ATTR lldesc_t dma_descriptor[NUM_CHAINED_DMAS];
static void IRAM_ATTR i2s_isr(void* arg);
static intr_handle_t i2s_intr_handle;
static const uint8_t data0_pin = 17;
static const uint8_t pixel_clk_pin = 15;
static const uint8_t word_width_bits = 8; // Doesn't really matter for this example
static const uint16_t bclk_mhz = 5; // Must divide into 40MHz evenly (i2s clk / 2)
//-----------------------------------------------------------------------------
void parallel_input_manager_init( parallel_data_ready_cb_t cb )
{
s_pi.data_ready_cb = cb;
//---------------------------------------------------------------------------
// Generate a fake data input pattern
ledc_timer_config_t ledc_timer_dclk =
{
.duty_resolution = LEDC_TIMER_2_BIT, // resolution of PWM duty
.freq_hz = bclk_mhz * 1000 * 1000 / 3, // Half the bit clock
.speed_mode = LEDC_LOW_SPEED_MODE, // timer mode
.timer_num = LEDC_TIMER_1, // timer index
.clk_cfg = LEDC_AUTO_CLK, // Auto select the source clock
};
ledc_channel_config_t ledc_channel_dclk =
{
.channel = LEDC_CHANNEL_0,
.duty = 2 << ( ledc_timer_dclk.duty_resolution - LEDC_TIMER_1_BIT - 1 ),
.gpio_num = data0_pin,
.speed_mode = ledc_timer_dclk.speed_mode,
.hpoint = 0,
.timer_sel = ledc_timer_dclk.timer_num,
.intr_type = LEDC_INTR_DISABLE,
.flags.output_invert = false,
};
periph_module_enable( PERIPH_LEDC_MODULE );
ledc_timer_config( &ledc_timer_dclk );
ledc_channel_config( &ledc_channel_dclk );
//---------------------------------------------------------------------------
// Set up our databus and I2S data signals as inputs
gpio_config_t conf =
{
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE
};
// Input the data GPIO pins to the I2S module
conf.mode = GPIO_MODE_INPUT;
conf.pin_bit_mask = 1LL << data0_pin;
gpio_config( &conf );
gpio_matrix_in( data0_pin, I2S0I_DATA_IN8_IDX, false );
gpio_matrix_out( data0_pin, LEDC_LS_SIG_OUT0_IDX, false, false ); // Needs to happen after gpio_config
// Output the pixel clock to the peripherals
conf.mode = GPIO_MODE_OUTPUT;
conf.pin_bit_mask = 1LL << pixel_clk_pin;
gpio_config( &conf );
gpio_matrix_out( pixel_clk_pin, I2S0I_WS_OUT_IDX, false, false );
uint8_t parallel_word_byte_length = 1;
uint16_t bytes_per_sample = parallel_word_byte_length * word_width_bits;
uint16_t samples_per_dma = MAX_DMA_TRANSFER_SIZE / bytes_per_sample;
periph_module_enable( PERIPH_I2S0_MODULE ); // Enable and configure I2S peripheral
I2S0.conf.rx_reset = 1;
I2S0.conf.rx_reset = 0;
I2S0.conf.rx_fifo_reset = 1;
I2S0.conf.rx_fifo_reset = 0;
I2S0.lc_conf.in_rst = 1;
I2S0.lc_conf.in_rst = 0;
I2S0.conf.rx_slave_mod = 0; // Enable master mode, clocking is internally generated
I2S0.conf2.val = 0; // Clear all the bits
I2S0.conf2.lcd_en = 1; // Enable parallel mode
I2S0.conf2.camera_en = 1; // Use HSYNC/VSYNC/HREF to control sampling
I2S0.conf2.cam_clk_loopback = 1; // Use master PCLK as the rx clk
// Equivalent to setting 'pixel_clk_pin' as an input and routing via gpio_matrix_in
I2S0.clkm_conf.clk_sel = 2; // 0: No clock 1: APLL_CLK 2: PLL_160M_CLK 3: No clock
I2S0.clkm_conf.clk_en = 1; // Enable clock gate
static const uint16_t i2s_clk_mhz = 80; // Based on 'I2S0.clkm_conf.clk_sel' setting above
// Configure clock divider
I2S0.clkm_conf.clkm_div_a = 0; // Fractional clock divider denominator value
I2S0.clkm_conf.clkm_div_b = 0; // Fractional clock divider numerator value
I2S0.clkm_conf.clkm_div_num = 2; // Integral I2S clock divider value
I2S0.sample_rate_conf.rx_bck_div_num = (i2s_clk_mhz / ( I2S0.clkm_conf.clkm_div_num * bclk_mhz ) ); // i2s clk is divided before reaches BCK output
printf( "I2S BClk (actual): %i MHz\n", i2s_clk_mhz / ( I2S0.clkm_conf.clkm_div_num * I2S0.sample_rate_conf.rx_bck_div_num ) );
I2S0.conf.rx_right_first = 0; // Receive right channel data first
I2S0.conf.rx_msb_right = 0; // Place right channel data at the MSB in the receive FIFO
I2S0.conf.rx_msb_shift = 0; // Enable receiver in Phillips standard mode
I2S0.conf.rx_mono = 0; // Enable receiver in mono mode
I2S0.conf.rx_short_sync = 0; // Enable receiver in PCM standard mode
I2S0.conf.rx_dma_equal = 1; // TBH: I'm not 100% sure I understand what this does
I2S0.timing.val = 0; // No delays on any of the timing sequences
I2S0.fifo_conf.dscr_en = 1; // Enable I2S DMA mode
I2S0.conf_chan.rx_chan_mod = 1; // Store right channel data only (dependant upon conf.rx_msb_right)
I2S0.sample_rate_conf.rx_bits_mod = 8 * parallel_word_byte_length; // Bit length of I2S receiver channel (word width, must be multiple of 8)
// Value of 0 = 32-bit. When = 8, LSB = I2S0I_DATA_IN8_IDX, else LSB = I2S0I_DATA_IN0_IDX
for ( uint8_t idx = 0; idx < NUM_CHAINED_DMAS; idx++ )
{
dma_descriptor[idx].length = 0; // Number of byte written to the buffer
dma_descriptor[idx].size = samples_per_dma * bytes_per_sample; // In bytes, must be in whole number of words
dma_descriptor[idx].owner = 1; // The allowed operator is the DMA controller
dma_descriptor[idx].sosf = 0; // Start of sub-frame. Also likely not used with I2S
dma_descriptor[idx].buf = ( uint8_t * )dma_buff[idx];
dma_descriptor[idx].offset = 0;
dma_descriptor[idx].empty = 0;
dma_descriptor[idx].eof = 0; // indicates the end of the linked list
dma_descriptor[idx].qe.stqe_next = &dma_descriptor[ ( idx + 1 ) % NUM_CHAINED_DMAS ]; // pointer to the next descriptor
}
I2S0.rx_eof_num = samples_per_dma * bytes_per_sample; // The length of data to be received
I2S0.in_link.addr = ( uint32_t )&dma_descriptor[0];
I2S0.in_link.start = 1; // Start the inlink descriptor
I2S0.int_ena.val = 0; // Disable all interrupts
I2S0.int_clr.val = I2S0.int_raw.val; // Clear interrupt flags
I2S0.int_ena.in_suc_eof = 1; // Trigger interrupt when current descriptor is handled
esp_intr_alloc(ETS_I2S0_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, i2s_isr, (void*)&I2S0, &i2s_intr_handle);
esp_intr_enable(i2s_intr_handle);
I2S0.conf.rx_start = 1; // Start I2S & the DMA
// For I2S in parallel camera input mode, data reception starts with a falling edge on I2S0I_V_SYNC_IDX while H_SYNC = H_ENABLE = 1
gpio_matrix_in( 0x38, I2S0I_H_SYNC_IDX, false ); // 0x3C sets signal low (0), 0x38 sets signal high (1)
gpio_matrix_in( 0x38, I2S0I_H_ENABLE_IDX, false );
gpio_matrix_in( 0x38, I2S0I_V_SYNC_IDX, false );
gpio_matrix_in( 0x3C, I2S0I_V_SYNC_IDX, false );
s_pi.initialized = true;
}
//-----------------------------------------------------------------------------
static void IRAM_ATTR i2s_isr(void* arg)
{
I2S0.int_clr.val = I2S0.int_raw.val;
isr_cnt++;
if ( s_pi.data_ready_cb )
{
lldesc_t *p_dma_descriptor = (lldesc_t *)I2S0.in_eof_des_addr;
s_pi.data_ready_cb( ( const uint8_t *)p_dma_descriptor->buf, p_dma_descriptor->size );
}
}
//-----------------------------------------------------------------------------
void print_buff_custom_format( void *p_buffer, uint16_t buffer_size, uint8_t bytes_per_line, uint32_t base_header_line_cnt,
uint32_t base_header_address_offset )
{
char line_header[20] = "";
char line_text[( bytes_per_line * 3 ) + 1]; // 2 ASCII Characters + space + null
uint16_t line_cnt = 0;
uint8_t line_byte_cnt = 0;
uint8_t *p_next_in = ( uint8_t * )p_buffer;
char *p_next_out = line_text;
if ( buffer_size > bytes_per_line )
{
printf( "\n" );
}
while ( buffer_size )
{
if ( line_byte_cnt == 0 )
{
sprintf( line_header, "[%04i-0x%04x]: 0x", line_cnt + base_header_line_cnt,
( line_cnt * bytes_per_line ) + base_header_address_offset );
line_cnt++;
line_byte_cnt = 0;
p_next_out = line_text;
}
p_next_out += sprintf( p_next_out, "%02x ", *p_next_in );
p_next_in++;
buffer_size--;
line_byte_cnt++;
if ( ( line_byte_cnt == bytes_per_line ) || ( buffer_size == 0 ) )
{
printf( "%s%s\n", line_header, line_text );
line_byte_cnt = 0;
}
}
}
//-----------------------------------------------------------------------------
void print_buff( void *p_buffer, uint16_t buffer_size )
{
print_buff_custom_format( p_buffer, buffer_size, 16, 0, 0 );
}