Esp32 i2s parallel driver with FreeRtos

Hi,

Description

I dont know if this forum is the right place to do this question or are in FreeRtos forum.

I am working in a i2s parallel driver that will use lvgl in 2 buffer mode.
Each buffer can store 7680 pixels(1/20 display size).

The i2s parallel driver at firt is working, but i want to put 2 buffer to work synchronously with dma interrupt ie: enable first buffer transfer with dma, while the second buffer are rendered by cpu.
If the cpu finish render second buffer while dma are transfer first buffer yet, the new dma transfer need to wait for dma finish first transfer.
I dont want to polling a flag to know if dma are free or not.
This use cpu for nothing.

The problem are that littlevgl “display_flush” function happens asynchronously and the two buffer need be send synchronously by the driver.

I want to use FreeRtos in some way, but i have many doubts.

I think that my problem can be solved in some way with this:
https://www.freertos.org/RTOS-task-notifications.html

I also need to know if the “dma / i2s io” resource is free or not, but mutex cannot be used within the interrupt subroutine.

What MCU/Processor/Board and compiler are you using?

ESP32/esp-idf

What do you want to achieve?

Using FreeRtos to sync two buffers to be sent by “i2s dma io” driver.
In the future i also want to synchronize the sending buffer with the tearing signal of the display.

What have you tried so far?

Code below.

Code to reproduce

//  Main function: Test only without littlevgl yet.
void main( )
{

    lv_area_t area;

    area.x1 = 0x0010;
    area.x2 = 0x013f;    
    area.y1 = 0x0005;    
    area.y2 = 0x01df;   
    
    while(1)
    {
        display_flush ( NULL, &area );   

        // vTaskDelay( 1000 / portTICK_PERIOD_MS );  
    }
}	

void display_flush ( lv_disp_drv_t* drv, lv_area_t* area ) 
{  
    static bool flag = 1;
    flag = !flag;
  
        
    if ( flag == 0 )
    {        
        commands_to_init_pixels_write ( area );    // fills command buffer with some display commands.

        i2s_lcd_write_pixels_a ( (uint32_t) 15360 );          
    }
    else
    {     
        commands_to_init_pixels_write ( area ); 

        i2s_lcd_write_pixels_b ( (uint32_t) 15360 );           
    }
}    


int i2s_lcd_write_pixels_a ( uint32_t length )   // length = bytes number. length <= 15360. 2 bytes per pixel. 
{
    uint32_t i;
    uint8_t* ptr;  

    if ( length > pixels_size_in_bytes )     
    {
        printf( "error: length > pixels_size_in_bytes.\n\n" );
        return -1;
    }     

    ptr = (uint8_t *) &buffer_a;    // buffer_a are lv_color_t(uint16_t) the buffer registered to littlevgl driver.

    for ( i = 0 ; i < length ; i = i + 2 )   
    {        
        buf_a[ i ] = ptr[ i ];                  // buf_a are uint32_t buffer.    
        buf_a[ i + 1 ] = ptr[ i + 1 ];        
    }        
              
    fill_dma_descriptor_a( length );               
    fill_dma_descriptor_command ( 11 );  
    

Are the shared resource i2s0 free ?

If i2s0 is free, blocks access to the i2s peripheral and calls the task "task_send_buffer_a_to_display ()"
to fire the dma transfer. After the dma ends, it releases access to the i2s peripheral within the dma interrupt (i2s is still sending data through the physical port, because of the 64 byte fifo memory).

If i2s0 is not free, the task need waiting here until "dma/i2s" isr subroutine send message(unlock). 
Here the function did not init dma transfer, but need return from this function i think ( confuse ).

    return 2019;
}

////////////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
// tasks can be static inline ?


static inline void task_send_buffer_a_to_display( )   
{
    
    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_command );
    while(!I2SX.state.tx_idle);  // Wait the peripheral i2s stay free. It is not worth switching tasks on FreeRtos here because sending 11 bytes takes = ~ 1 us and the TICKRATE is around 1 ms - 10 ms.
    
    pixels_flag = 1;   // Informs that it will transfer pixels and not commands. Reset inside "i2s/dma" isr.

    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_a[0] );   
}


Same for "int i2s_lcd_write_pixels_b ( uint32_t length )" and for "static inline void task_send_buffer_b_to_display( )" .


static void IRAM_ATTR i2s_isr ( ) 
{   

    if ( I2SX.int_st.out_eof )
    {

        if ( pixels_flag )  // send pixels flag indication.
        {    
            pixels_flag = 0;          
            
            lv_disp_t * disp = lv_refr_get_disp_refreshing();
            lv_disp_flush_ready(&disp->driver);  


            // Release i2s/dma resource. Cannot use mutex inside isr.
            // xTaskNotifyFromISR();  // Notify the task to init dma transfer immediately if the other buffer is already ready to send.  
        }          

    }
   
    I2SX.int_clr.val = I2SX.int_st.val;
}  
` ``` `   


Thank's.

Now i saw that “lv_disp_flush_ready(&disp->driver)”, should not be called inside the isr because my goal is that the copy of the buffer and the filling of the dma descriptor are ready when starting the next dma transfer.

I think that i must call “lv_disp_flush_ready(&disp->driver)” here:

    //copy of the buffer
    for ( i = 0 ; i < length ; i = i + 2 )   
    {        
        buf_a[ i ] = ptr[ i ];                  // buf_a are uint32_t buffer.    
        buf_a[ i + 1 ] = ptr[ i + 1 ];        
    }        
              
    fill_dma_descriptor_a( length );       // filling of the dma descriptor for pixels write.               
    fill_dma_descriptor_command ( 11 );    // filling of the dma descriptor for commands write.


    lv_disp_t * disp = lv_refr_get_disp_refreshing();

    if ( i2s_free )    //  i2s free = 1. i2s not free = 0.
    {
        Blocks access to i2s.

        task_send_buffer_a_to_display( );    // start dma transfer   
   
        lv_disp_flush_ready(&disp->driver); 
    }
    else
    {
        Wait for the i2s module to be free( Stay here until dma isr Task Notification ? ).  
        
        Blocks access to i2s.   

        task_send_buffer_a_to_display( );    // start dma transfer

        lv_disp_flush_ready(&disp->driver);
    }

No one.

Some suggestion ?

Thank’s.

I think you’re using a wrong approach, I don’t think lvgl works as you try to use it. What version of lvgl are you using? Are you using the Arduino port of esp32 or esp-idf framework?

Hi,

I am using littlevgl version 6.0.

I am using esp-idf.

Es correcto si asumo que hablas español? En github.com/littlevgl/lv_port_esp32 estoy trabajando con el esp-idf, puedes darle un vistazo y revisar si lo puedes adaptar a tus necesidades…

Hola,

hablo portugués.

Gracias.

Estas trabajando con un controlador de display en particular?

Yes.

But for now i am only working in the i2s driver and in learn rtos. In this case learn FreeRtos.

After i2s driver are working “perfect”, i can connect anything that need a 8 bit parallel bus.

And if you change the driver a little bit to other bus lengths like 16 bits, etc.

Hi,

Someone with experience in FreeRtos to give me some tips/direction ?

Thank’s.