I2S (LCD/parallel-mode + DMA) sends output buffer twice?

p-rimes
Posts: 89
Joined: Thu Jun 08, 2017 6:20 pm

I2S (LCD/parallel-mode + DMA) sends output buffer twice?

Postby p-rimes » Thu Jun 08, 2017 6:42 pm

I seem to be experiencing a bug (or configuration error) that leads to incorrect/duplicate I2S data being transmitted. My DMA buffer is transmitted correctly (e.g. in my case 32 clocks edges == 32 16-bit samples in parallel mode) -- however the buffer is being transmitted twice (64 clock edges total, samples [0..32),[0..32)). This bug appears in the following configurations / memory layouts:
- 16-bit stereo (my preferred configuration)
- 16-bit single-channel
- 32-bit single-channel (simply ignoring the LSB and using the upper 16 bits)
- 32-bit stereo (not able to get this working -- clocks ~5-6 cycles for each new transmission then stops)

I have tried nearly all possible combinations of transmit/FIFO settings (everything I could find in the TRM and a few more undocumented bits), specifically the following relevant bits:

Code: Select all

I2S1.conf2.lcd_en = 1;			// (need this for parallel mode)
I2S1.conf1.tx_pcm_bypass = 1;		// (doesn't seem to make a difference in parallel mode)
I2S1.conf_chan.tx_chan_mod = 0x0;	// (also tried 0x1, 0x2, 0x3 -- not too much changed)
I2S1.fifo_conf.tx_fifo_mod = 0x0;	// (also tried 0x1, 0x2, 0x3)
I2S1.conf2.lcd_tx_wrx2_en = 0;		// (also tried 1, no change)
I2S1.conf2.lcd_tx_sdx2_en = 0;		// (also tried 1, this sends each sample twice as documented: not what I want)
I2S1.fifo_conf.tx_data_num = 32;	// (doesn't seem to make a difference most of the time, occasional garbage transmitted if set wrong e.g. [16, 0, 63...])
I2S1.sample_rate_conf.tx_bits_mod = 16; // (tried with 32, requires memory layout change to get the data correct, but still sends the data out twice)
I2S1.sample_rate_conf.tx_bck_div_num = 1; // (also tried with 2)
dma_desc.length = 32 * sizeof(uint16_t);	// (buffer size in bytes. I expected 32 clock cycles/samples from this, but got 64. If I set it to 32, then I expect 16 samples but I see 32, the second half are duplicated. This is the weirdness.)
dma_desc.size = 32 * sizeof(uint16_t);	// (ditto)
I have been implementing an LED matrix driver, using the I2S1 peripheral in the LCD mode, with tx_stop_en=1, and a periodic timer ISR to restart the transmission with a new DMA buffer.

What is going on here? Is this a real bug, or is this a mis-configuration? I am stopping the out_link.start/conf.tx_start transmission in the I2S isr, and then using out_link.restart with a new buffer address in the timer ISR to begin a new transmission. Maybe I am not resetting properly between transmissions? (or, am resetting something that should NOT be reset)

Please let me know if I should add waveforms or additional code to help understand the issue.

ESP_Sprite
Posts: 9759
Joined: Thu Nov 26, 2015 4:08 am

Re: I2S (LCD/parallel-mode + DMA) sends output buffer twice?

Postby ESP_Sprite » Fri Jun 09, 2017 7:46 am

What do you use as clock (=WE) for your LCD? Bclk or WS? In the code I have here, I use WS for the write strobe.

(Also, will what you do be open-source? I had the idea to turn I2S into a LED matrix controller for a while now, but never got around to it... got a nice 64x32 RGB matrix to play with and all.)

p-rimes
Posts: 89
Joined: Thu Jun 08, 2017 6:20 pm

Re: I2S (LCD/parallel-mode + DMA) sends output buffer twice?

Postby p-rimes » Sat Jun 10, 2017 3:27 am

Well I had tried both WS for clock, as well as using BCK directly. I prefer the waveforms from using BCK, but it doesn't seem to make a significant difference (still seeing 64 samples instead of 32 -- a new sample on each clock level change). When using WS signal in LCD mode, it seems as though the clock signal is lagging the samples by a full cycle: that definitely confuses my logic analyzer and I don't think the LED panels would accept it, so I'm just using BCK (not WS) so far.

Also, what seems so weird about the current behaviour (and why I think it may not be BCK vs WS), is that it does finish sending out the whole buffer before the samples start repeating, so the duplicate samples are actually N clock edges apart -- if the samples were next to each other (eg. similar to what the sdx2 bit appears to do), then I would suspect something like BCK vs WS could be the issue... but here the duplicate data begins many FIFO entries (!) after the valid data begins.

I wonder if it might be because I am using the I2S/DMA peripheral in an unusual way, since I am using binary code modulation for dimming the display (instead of PWM) to save on framebuffer RAM (I am aiming for 12-bits-per-channel sRGB). So each frame, for each of the 12 bits, I am running a timer (with prescaler at 2, 4, 8, etc) after each I2S transmission to display the LED data, then stopping/restarting the I2S peripheral to load new data for each row (so the MSB is "on" for twice as long as MSB-1, etc). I'm trying to reset as few things as possible in between, IMO only the DMA memory address is changing, but I have found that I need to at least stop + reset + start the I2S peripheral to get a new transmission going. I'm also stop/starting the DMA out_link (seems sane?), but I'm not resetting the FIFO. Maybe this specific info helps?

In general, if there was some info on what bits to reset and when, and in what order (peripheral, DMA, FIFO), that would be so helpful. My approach is to override as few register fields as possible (so e.g. I'm not setting any fields related to RX -- they are all still at defaults).

Also a confirmation on the difference between DMA size vs length?

Thanks for your help so far, I'm really liking the ESP32/esp-idf (even the TRM!), and I can see it being a really great platform for future work. I would consider open-sourcing the LED matrix driver up to 8-bits per channel, but I'm hoping to get 12-bits sRGB for my use case so I might leave that part private (assuming I get it working!)

p-rimes
Posts: 89
Joined: Thu Jun 08, 2017 6:20 pm

Re: I2S (LCD/parallel-mode + DMA) sends output buffer twice?

Postby p-rimes » Tue Jun 13, 2017 4:06 am

I think I solved it!

I am now using the following settings:
I2S1.conf_chan.tx_chan_mod = 0x1; (NOT 0x0)
I2S1.fifo_conf.tx_fifo_mod = 0x1; (NOT 0x0)
I2S1.sample_rate_conf.tx_bck_div_num = 2; (NOT 1)
I2S1.int_ena.out_eof = 1; (NOT out_done)
Only using I2S1.out_link.start = 1; I2S1.conf.tx_start = 1; NOT using I2S1.out_link.restart = 1;
Using portENTER_CRITICAL/portENTER_CRITICAL_ISR around the sections where are modifying I2S registers
The only remaining issue I have is one of speed -- I want the fastest possible clock (40MHz ideally), but I seem to be limited by:

Code: Select all

I2S1.clkm_conf.clkm_div_num = 8;
to around 40MHz / 8 (analyzer says around ~4.8MHz)?

My clock settings are as follows:

Code: Select all

// TRM p186: Set this bit to enable clk_apll.
I2S1.clkm_conf.clka_en = 0;
// TRM p186: Fractional clock divider’s denominator value.
I2S1.clkm_conf.clkm_div_a = 1;
// TRM p186: Fractional clock divider’s numerator value.
I2S1.clkm_conf.clkm_div_b = 1;
// TRM p186: I2S clock divider’s integral value.
I2S1.clkm_conf.clkm_div_num = 8;

// TRM p187: Set the bits to configure the bit length of I2S transmitter channel.
I2S1.sample_rate_conf.tx_bits_mod = 16;
// TRM p187: Bit clock configuration bit in transmitter mode.
I2S1.sample_rate_conf.tx_bck_div_num = 2;

Who is online

Users browsing this forum: No registered users and 140 guests