Esp32 i2s parallel driver using FreeRtos

Hi,

I am newbie with rtos and with FreeRtos.

I am working in a i2s parallel driver that will use lvgl hmi library, 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.

I am using esp32/esp-idf.

What i 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.

Below folow the code of what i want to do:

// 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 for any help.

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);
}

I’m not fully comprehending the scenario you describe. Would I be correct if I said you have an I2S peripheral you want to write to from two different tasks, and that that peripheral is using a DMA? If so, then you might consider performing all writes using the DMA interrupt. For example, write any data that is to be sent via I2S to a circular buffer (you could use a stream buffer but if you do be aware they are supposed to only have a single writer so you would to protect access with a mutex or other such synchronisation object) - then have the DMA interrupt read from the circular buffer and write to the I2S peripheral. The DMA end interrupt can check to see if there is more data in the buffer, and if so, continue to send it. If there is no data in the buffer then disable the DMA interrupt. When you write to the buffer enable the DMA interrupt so it checks the buffer and writes any data it finds.

Hi,

I would like to post here again, but the following message appears to me:

“Sorry, new users can only put 2 links in a post”.

Thank’s.

The forum moderators can resolve this so that I can continue publishing here ?

Thank’s.

I have attempted (?) to raise your privilege to enable you to post again. Please have a go and report back if you are still having issues.

Hi,

The library lvgl are from third party (https://littlevgl.com/).

I want to integrate the littlevgl lib inside esp32/esp-idf.
To make this integration, i need to write the driver for the display that i will use.
Also i need create the driver for the i2s module of esp32(hardware). And i will use i2s module
together with dma.
My headache is in implementing the i2s driver with dma.

For me to be able to use the littlevgl library i need to allocate memory for two buffers and register the buffers in the lib. The lib will write pixels in this two buffers.
Example:

static lv_color_t buffer_a[ 7680 ];    // lv_color_t are uint16_t.
static lv_color_t buffer_b[ 7680 ];    // 7680 pixels each buffer. 1/20 display full size(320x480).

static lv_disp_buf_t disp_buf;

lv_disp_buf_init ( &disp_buf, buffer_a, buffer_b, 7680 );   
// 7680 pixels = 15360 bytes. pixel size = lv_color_t = 2 bytes per pixel.     

lv_disp_drv_t disp_drv;

lv_disp_drv_init(&disp_drv);

disp_drv.flush_cb = display_flush;    // callback function to send pixels to display.

disp_drv.buffer = &disp_buf;

lv_disp_drv_register(&disp_drv);

The lib only call “display_flush” callback function asynchronously(i think) when the lib need to send data to display.
The lib pass the pointer of the buffer_a or buffer_b to “display_flush” callback function that was writen in the current call of this function.
The lib also passes to this function the coordinates where the pixels will be written on the display( xi, xf, yi, yf ). With this information i know how many pixels to be send to display,
(xf-xi)*(yf-yi). As each pixel are uint16_t and i will send bytes (uint8_t) i need to multiply pixel number by 2( bytes number = pixels * 2 ).

Inside the function “display_flush” i test the pointer like:

if ( drv->buffer->buf_act == p_buffer_a )
{
    i2s_lcd_write_pixels_a ( length * sizeof(lv_color_t) );    // 2 * length = bytes number.
}
else if ( drv->buffer->buf_act == p_buffer_b )
{
    i2s_lcd_write_pixels_b ( length * sizeof(lv_color_t) );  // function inside i2s/dma driver.           
}

The functions “i2s_lcd_write_pixels_a” and “i2s_lcd_write_pixels_b” are in the i2s driver file(i2s_lcd.c for example).
I can only have one function, if necessary, to be able to integrate with freertos( void i2s_lcd_write_pixels(uint32_t length, bool current_buffer) ) , because task notifications only works with a single task.
Example: one single function

static inline void task_send_buffer_to_display( uint32_t address )
{
    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_command );  // start dma transfer.
    while(!I2SX.state.tx_idle);  // wait here. only 11 bytes to send(commands).      

    pixels_flag = 1;    // indicate that i will send only pixels and not commands.
    i2s_dma_trans_start_interrupt( address );    // start another dma transfer.
                                                 // address are "(uint32_t) &dma_desc_buf_a[0]" or "(uint32_t) &dma_desc_buf_b[0]". 
                                                 // Address of two dma descriptors, one for each pixel buffer.
}

The problem are that the dma hardware project of i2s module on esp32 only transfer uint32_t.
And i use only 8 bit parallel fisical port on i2s, so uint8_t.
For this reason i need to copy the pixel buffer(uint16_t) to another 32bit buffer, before starting the dma transfer.
Example:
Separates the 16bit pixels into 2 bytes(low byte and high byte).
Copy each byte(low and high) in a single 32 bit buffer position.

Below follows the code snippet that does this:

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. Buffer declared inside i2s driver.   
    buf_a[ i + 1 ] = ptr[ i + 1 ];        
}        

Espressif informed me that this is the only way it works.
Wasted memory and processing(hardware project bug).

For this reason my goal is that the copy of the buffer and the filling of the dma descriptor be ready when starting the next dma transfer.
While first call of dma transfer are in fisical io transfer progress, the cpu render other buffer inside littlevgl lib, littlevgl call display_flush function, copy the new buffer and fills dma descriptor. When first dma end, occurs a interrupt, then, start new dma transfer, which is already ready to start.

“lv_disp_flush_ready(&disp->driver)” inform the graphics library that you are ready with the flushing(dma/i2s module current transfer finished).
https://docs.littlevgl.com/en/html/porting/display.html

After i inform the lib that the flushing is ready calling “lv_disp_flush_ready(&disp->driver)”, if the lib has another rendered buffer ready to be send, it will call the function display_flush again. This process occurs again and again(switching the buffers on each call), but this call is asynchronous.

The problem are that the buffer copy are after call display_flush. While not calling “lv_disp_flush_ready” the graphical lib stay locked waiting for this call.
After i call “lv_disp_flush_ready”, the lib call display_flush, if the lib has a ready and new rendered buffer to be sent.
For this rason i think that call to “lv_disp_flush_ready(&disp->driver)”, in my single case, should be called after init dma transfer and not at the end of dma transfer
So i need to put a lock here:

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);
}

I dont know if i need create a task or not for this purpose.
I’m really lost.

In this espressif i2s driver, no task was created :
https://github.com/espressif/esp-iot-solution/blob/master/components/i2s_devices/lcd_common/i2s_lcd.c

Thank’s for the help.

There is an awful lot to digest there, but if I understand correctly, it doesn’t sound like you need a task specifically for that, but it will depend on what the rest of your system is doing also.

Hi Richard Barry,

My driver are working now, but not the way i would like.
Without freeRtos support.

In the link below i explain.

May be i would block the task while i wait for a bit change inside interrupt subroutine. But the task switching need to be almost instantly when bit change.

https://www.esp32.com/viewtopic.php?f=12&t=14516#p57704

No one ?!

Any suggestion will be apreciated.

The code below are working, but i would like to use task communication or task notification together with a “mutex”.

//////////////////////////////////////////////////////////////////////////////////////
"BufferA":

int i2s_lcd_write_pixels_a ( uint32_t length )    
{
    uint32_t i;
    uint32_t j;
    uint8_t* ptr;

    ptr = (uint8_t *) &buffer_a;    // buffer_a are uint16_t.

    for ( i = 11, j = 0 ; i < length + 11 ; i = i + 2, j = j + 2 )    // Takes 1,030 ms when length = 15360 bytes.
    {                                                                                                  
        buf_a[ i ]     = ptr[ j ] | 0x000;    // "buf_a[ i ] = ptr[ j ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
        buf_a[ i + 1 ] = ptr[ j + 1 ] | 0x000;    // "buf_a[ i + 1 ] = ptr[ j + 1 ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
    }

    fill_dma_descriptor_a( length + 11 );
 
    // i2s are free ????           
    while( i2s_free == 0 );    //  i2s are not free. Put timeout maybe here ?????           
                               //  Wait for the i2s module to be free( Stay here until dma isr Task Notification ? ).  
       
    i2s_free = 0;    // block i2s/dma resource.
    
    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_a[0] );    // start dma transfers ( commands and pixels ).   


    disp = lv_refr_get_disp_refreshing( );    
    lv_disp_flush_ready(&disp->driver);

    return 1;
}
////////////////////////////////////////////////////////////////////////////////////////
"BufferB":

int i2s_lcd_write_pixels_b ( uint32_t length ) 
{
    uint32_t i;
    uint32_t j;
    uint8_t* ptr;

    ptr = (uint8_t *) &buffer_b;    // buffer_b are uint16_t.

    for ( i = 11, j = 0 ; i < length + 11 ; i = i + 2, j = j + 2 )    // Takes 1,030 ms when length = 15360 bytes.
    {                                                                                                  
        buf_b[ i ]     = ptr[ j ] | 0x000;    // "buf_b[ i ] = ptr[ j ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
        buf_b[ i + 1 ] = ptr[ j + 1 ] | 0x000;    // "buf_b[ i + 1 ] = ptr[ j + 1 ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
    }

    fill_dma_descriptor_b( length + 11 );
 
    // i2s are free ????           
    while( i2s_free == 0 );    //  i2s are not free. Put timeout maybe here ?????           
                               //  Wait for the i2s module to be free( Stay here until dma isr Task Notification ? ).  
       
    i2s_free = 0;    // block i2s/dma resource.
    
    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_b[0] );    // start dma transfers ( commands and pixels ).   


    disp = lv_refr_get_disp_refreshing( );    
    lv_disp_flush_ready(&disp->driver); 

    return 1;
}
//////////////////////////////////////////////////////////////////////////////////////////
Interrupt subroutine:

static void IRAM_ATTR i2s_isr ( )    // All enabled i2s module interrupts points here ???? I think so. 
{   
   
    if ( I2SX.int_st.out_eof )       
    {                             
           
        while(!I2SX.state.tx_idle);    // dma interrupt. i2s still sending bytes through the physical bus, because 64 fifo buffer.
 
        I2SX.conf.tx_start = 0;    
        I2SX.conf.tx_reset = 1;        
        I2SX.conf.tx_reset = 0;    

        i2s_free = 1;    // release the i2s resource.        
    }
    

    if ( I2SX.int_st.out_dscr_err )
    {
        ESP_EARLY_LOGE ( "I2S_TAG", "dma error, interrupt status: 0x%08x", I2SX.int_st.val ); 
    }
  
    I2SX.int_clr.val = I2SX.int_st.val;
}

I think it could be something like this:

//////////////////////////////////////////////////////////////////////////////////////
"BufferA":

int i2s_lcd_write_pixels_a ( uint32_t length ) 
{
    uint32_t i;
    uint32_t j;
    uint8_t* ptr;

    ptr = (uint8_t *) &buffer_a;    // buffer_a are uint16_t.

    for ( i = 11, j = 0 ; i < length + 11 ; i = i + 2, j = j + 2 )    // Takes 1,030 ms when length = 15360 bytes.
    {                                                                                                  
        buf_a[ i ]     = ptr[ j ] | 0x000;    // "buf_a[ i ] = ptr[ j ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
        buf_a[ i + 1 ] = ptr[ j + 1 ] | 0x000;    // "buf_a[ i + 1 ] = ptr[ j + 1 ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
    }

    fill_dma_descriptor_a( length + 11 );
 

    uint32_t ulNotificationValue;
    const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 1 );

    if ( i2s_free == 0 )    // i2s are not free.
    {
        ulNotificationValue = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );  // Wait for the previous transmission to complete( inside isr ).      

        if( ulNotificationValue == 1 )
        {
            /* The previous transmission ended as expected. */
        }
        else
        {
            /* The call to ulTaskNotifyTake() timed out. */
        }   

    }       

    i2s_free = 0;    // block i2s/dma resource.
    
    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_a[0] );    // start dma transfers ( commands and pixels ).   


    disp = lv_refr_get_disp_refreshing( );    
    lv_disp_flush_ready(&disp->driver);

    return 1;
}
////////////////////////////////////////////////////////////////////////////////////////
"BufferB":

int i2s_lcd_write_pixels_b ( uint32_t length )
{
    uint32_t i;
    uint32_t j;
    uint8_t* ptr;
    
    ptr = (uint8_t *) &buffer_b;    // buffer_b are uint16_t.

    for ( i = 11, j = 0 ; i < length + 11 ; i = i + 2, j = j + 2 )    // Takes 1,030 ms when length = 15360 bytes.
    {                                                                                                  
        buf_b[ i ]     = ptr[ j ] | 0x000;    // "buf_b[ i ] = ptr[ j ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
        buf_b[ i + 1 ] = ptr[ j + 1 ] | 0x000;    // "buf_b[ i + 1 ] = ptr[ j + 1 ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
    }

    fill_dma_descriptor_b( length + 11 );
 

    uint32_t ulNotificationValue;
    const TickType_t xMaxBlockTime = pdMS_TO_TICKS( 1 );

    if ( i2s_free == 0 )    // i2s are not free.
    {
        ulNotificationValue = ulTaskNotifyTake( pdFALSE, xMaxBlockTime );  // Wait for the previous transmission to complete ( inside isr ).      

        if( ulNotificationValue == 1 )
        {
            /* The previous transmission ended as expected. */
        }
        else
        {
            /* The call to ulTaskNotifyTake() timed out. */
        }   

    }  
            
    i2s_free = 0;    // block i2s/dma resource.
    
    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_b[0] );    // start dma transfers ( commands and pixels ).   


    disp = lv_refr_get_disp_refreshing( );    
    lv_disp_flush_ready(&disp->driver); 

    return 1;
}
//////////////////////////////////////////////////////////////////////////////////////////
Interrupt subroutine:

static void IRAM_ATTR i2s_isr ( )    // All enabled i2s module interrupts points here ???? I think so. 
{   
   
    if ( I2SX.int_st.out_eof )       
    {                             
           
        while(!I2SX.state.tx_idle);    // dma interrupt. i2s still sending bytes through the physical bus, because 64 fifo buffer.
 
        I2SX.conf.tx_start = 0;    
        I2SX.conf.tx_reset = 1;        
        I2SX.conf.tx_reset = 0;    

        i2s_free = 1;    // release the i2s resource.    

        * Notifies the blocked task ( if it is blocked ).
        * Context switching when leaving the isr.
    
    }
    

    if ( I2SX.int_st.out_dscr_err )
    {
        ESP_EARLY_LOGE ( "I2S_TAG", "dma error, interrupt status: 0x%08x", I2SX.int_st.val ); 
    }
  
    I2SX.int_clr.val = I2SX.int_st.val;
}

Some observations and questions:

  • The context switching need to be immediately after leaving isr if the task is blocked ( it is a driver, so speed are very important ).

  • if the task is not blocked there may be a problem or freertos checks whether the task is blocked or not. if it is blocked it sends the notification and if it is not blocked it simply does not send the notification ?

  • There are two functions that alternate to be notified, i am not sure which strategy to use.

  • In the code that work, i measure the latency in the “while( i2s_free == 0 );” and in function “BufferA” i measure between 280ns - 5us and in “BufferB” i measure between 1,44us - 6,18us.
    So, i dont know if task notification is it advantageous or not, considering 100 to 200 machine cycles in a switching context. My clock are 240 MHz( 4,16 ns ).

    gpio_set_level( (gpio_num_t)12, 1 );   

    // i2s are free ???? 
    while( i2s_free == 0 ); 
   // Wait for the i2s module to be free( Stay here until dma isr Task Notification ? ).  

    gpio_set_level( (gpio_num_t)12, 0 );
  • I also don’t know how freertos behaves when I’m sending pixels to the display.
    The system is very fluid, but i would like to know if the FreeRtos switches tasks in the middle of sending pixels to the display( i think yes ).

Thank’s.

Grateful if you can provide a cut down example that just demonstrated the bare bones of the scenario you have, and what you would like to achieve.

Hi,

I just don’t like to see the processor spend machine cycles in the “while( i2s_free == 0 )” loop doing nothing.

I want to block the task so the processor can do something else.

Thank’s.

It is normally (but not always) best to make the system event driven as much as possible, so there are lots of mechanisms for doing what you want. The first thing you would have to do is make the i2s interrupt driven, then either send all the data to a stream buffer and have the task block on the stream buffer until data arrives, or have the interrupt service routine signal the task (for example, using a direct to task notification) and have the task block on the signal.

OK.

Sorry, i am a newbie in rtos, so i am very lost.
What would it be “event driven” ?

I would like a example with this inside my code that i put above.
I believe not more than 10 lines of code resolve the problem.

I want to use this strategy:
“or have the interrupt service routine signal the task (for example, using a direct to task notification) and have the task block on the signal .”

I am not created the “tasks”:
“int i2s_lcd_write_pixels_a ( uint32_t length )”
and
"int i2s_lcd_write_pixels_b ( uint32_t length ),
so i need to take the handle somehow i think.

There are 2 alternated buffers, a and b, that are copied and fired inside 2 different functions or “tasks”:
“int i2s_lcd_write_pixels_a ( uint32_t length )”
and
“int i2s_lcd_write_pixels_b ( uint32_t length )”,
then only one task could be blocked at a time or none would be blocked if the transfer had ended before the other buffer was ready or the last buffer was sent.

I think that i need to know what task are blocked inside interrupt handler so that i unblock the correct task.
If neither is blocked i simply do nothing within the interrupt service routine.

How to find the handle of a task that i didn’t create ?
How can i raise the priority of that task that i didn’t create ?
I need to raise the priority!!!, so the task context switch will be immediately after leaving the i2s interrupt.

**Buffer A Copy and Fire dma transfer "A":**

**static TaskHandle_t xTaskToNotify_A = NULL;    // global variable i think  ?**


int i2s_lcd_write_pixels_a ( uint32_t length ) 
{
    uint32_t i;
    uint32_t j;
    uint8_t* ptr;

    ptr = (uint8_t *) &buffer_a;    // buffer_a are uint16_t.

    for ( i = 11, j = 0 ; i < length + 11 ; i = i + 2, j = j + 2 )    // Takes 1,030 ms when length = 15360 bytes.
    {                                                                                                  
        buf_a[ i ]     = ptr[ j ] | 0x000;    // "buf_a[ i ] = ptr[ j ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
        buf_a[ i + 1 ] = ptr[ j + 1 ] | 0x000;    // "buf_a[ i + 1 ] = ptr[ j + 1 ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
    }

    fill_dma_descriptor_a( length + 11 );
 

*// This task always have same handle not ?*
*// How can i raise the priority of a task that I didn't create ?*
    **xTaskToNotify_A = xTaskGetCurrentTaskHandle( );** 

    
    if ( i2s_free == 0 )    // i2s are not free.
    {
        **block here and wait notification from i2s interrupt somehow. **
** // Wait ( block this task ) for the previous transmission to complete( notification inside i2s isr ).**      
    }       

    i2s_free = 0;    // block i2s/dma resource.
    
    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_a[0] );    // start dma transfers ( commands and pixels ).   


    disp = lv_refr_get_disp_refreshing( );    
    lv_disp_flush_ready(&disp->driver);

    return 1;
}
**Buffer B Copy and Fire dma transfer "B":**

**static TaskHandle_t xTaskToNotify_B = NULL;    // global variable i think  ?**


int i2s_lcd_write_pixels_b ( uint32_t length ) 
{
    uint32_t i;
    uint32_t j;
    uint8_t* ptr;

    ptr = (uint8_t *) &buffer_b;    // buffer_b are uint16_t.

    for ( i = 11, j = 0 ; i < length + 11 ; i = i + 2, j = j + 2 )    // Takes 1,030 ms when length = 15360 bytes.
    {                                                                                                  
        buf_b[ i ]     = ptr[ j ] | 0x000;    // "buf_b[ i ] = ptr[ j ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
        buf_b[ i + 1 ] = ptr[ j + 1 ] | 0x000;    // "buf_b[ i + 1 ] = ptr[ j + 1 ] | 0x000" to set the bit8 pin (i2s module inverted signal) that is used to control the DC signal.
    }

    fill_dma_descriptor_b( length + 11 );
 

*// This task always have same handle not ?*
*// How can i raise the priority of a task that I didn't create ?*
    **xTaskToNotify_B = xTaskGetCurrentTaskHandle( );** 

    
    if ( i2s_free == 0 )    // i2s are not free.
    {
        **block here and wait notification from i2s interrupt somehow. **
** // Wait ( block this task ) for the previous transmission to complete( notification inside i2s isr ).**      
    }       

    i2s_free = 0;    // block i2s/dma resource.
    
    i2s_dma_trans_start_interrupt( (uint32_t) &dma_desc_buf_b[0] );    // start dma transfers ( commands and pixels ).   


    disp = lv_refr_get_disp_refreshing( );    
    lv_disp_flush_ready(&disp->driver);

    return 1;
}
**// I2s interrupt:**

static void IRAM_ATTR i2s_isr ( )    // All enabled i2s module interrupts points here ???? I think so. 
{   
   **BaseType_t xHigherPriorityTaskWoken = pdFALSE;**


    if ( I2SX.int_st.out_eof )       
    {     
        while(!I2SX.state.tx_idle);    // dma interrupt. i2s still sending bytes through the physical bus, because 64 fifo buffer.
 
        I2SX.conf.tx_start = 0;    
        I2SX.conf.tx_reset = 1;        
        I2SX.conf.tx_reset = 0;    

        i2s_free = 1;    // release the i2s resource.    

        **if ( xTaskToNotify_A == blocked )**
**        {**
**            // unblock taskA immediately after leaving this interrupt. Need to raise task priority**
               **vTaskNotifyGiveFromISR( xTaskToNotify_A, &xHigherPriorityTaskWoken );**
               **portYIELD_FROM_ISR( xHigherPriorityTaskWoken );**
**        }**
**        **
**        if ( xTaskToNotify_B == blocked )**
**        {**
**            // unblock taskB immediately after leaving this interrupt. Need to raise task priority**
               **vTaskNotifyGiveFromISR( xTaskToNotify_B, &xHigherPriorityTaskWoken );**
               **portYIELD_FROM_ISR( xHigherPriorityTaskWoken );**
**        }**
    }
    

    if ( I2SX.int_st.out_dscr_err )
    {
        ESP_EARLY_LOGE ( "I2S_TAG", "dma error, interrupt status: 0x%08x", I2SX.int_st.val ); 
    }
  
    I2SX.int_clr.val = I2SX.int_st.val;    // Clear the interrupt.
}

Thank’s for the help.

Although I don‘t get the whole picture of your application, but isn‘t the busy-polling of the i2s_free state the problem to solve ?
So why not just setting up and start the transfer of the display data and block on a task notification getting signaled by DMA ISR ? I’d propose to poll for the FIFO empty in the now unblocked task to keep the ISR as short as possible.
If the buffer a/b sending happens from different tasks you should use a mutex taken right before i2s_dma_trans_start_interrupt to protect the DMA transfer and given back when done with the display access.
You could add a task handle variable used by the ISR to signal the notification where you simply store the handle of the current task with the mutex being held before starting the DMA.
The current task handle should be known or stored somewhere so you shouldn’t need to call xTaskGetCurrentTaskHandle but that’s not important here.

Edit: I re-read your original post telling it’s a driver called from unknown application code. So you should take the mutexed approach and use xTaskGetCurrentTaskHandle as you already planned.

If the i2s controller is exclusively used for the display you could also consider to move the waiting for the transfer/display update being finished to the beginning of the driver function. This can provide a better performance in the case that the next display update happens anytime later. So the application code can proceed doing something else while the DMA transfer is running and the display is updated. When the next display update needs to be done the previous update has probably already finished and there is no blocking at all when taking the (already signaled) task notification.

1 Like

Hi Hartmut Schaefer,

Here the callback function that i wrote and i registered in littlevgl library.

The littlevgl library call this function when it need send pixels to display.

Notice that i am using double buffering and that the littlevgl library pass to this function the buffer pointer that it has wrote.

/**
 * @brief Fill display for a certain area with data.
 * @param drv  pointer to littlevgl display driver.
 * @param area pointer to littlevgl display area to be updated.
 * @param area members:
 * @param x1 left coordinate of the area.
 * @param y1 top coordinate of the area.
 * @param x2 right coordinate of the area.
 * @param y2 bottom coordinate of the area.
 */

// callback function prototype: void (*flush_cb)(struct _disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p);

void display_flush ( lv_disp_drv_t* drv, lv_area_t* area, lv_color_t * color_map )
{  
    // uint32_t length = (uint32_t) ( area->x2 - area->x1 + 1 ) * ( area->y2 - area->y1 + 1 );    // pixels number. Each pixel = 2 bytes in this case.

    uint32_t length = (uint32_t) ( lv_area_get_width(area) * lv_area_get_height(area) );    // pixels number. Each pixel = 2 bytes in this case.
    
    if ( drv->buffer->buf_act == p_buffer_a )
    {
        commands_to_init_pixels_write_a ( area );    // Set display controller Column addresses, Set Page addresses, Memory write instruction.

        i2s_lcd_write_pixels_a ( length * sizeof(lv_color_t) );    // 2 * length = bytes number.
    }
    else if ( drv->buffer->buf_act == p_buffer_b )
    {
        commands_to_init_pixels_write_b ( area );

        i2s_lcd_write_pixels_b ( length * sizeof(lv_color_t) );  
    }
    else
    {
        printf("bug");
        return;
    }
}

BTW: What do you want to achieve with ptr[ j ] | 0x000; when preparing the display data ?
I guess you want to do & 0x000 :wink:
Although I don’t know enough about the double buffer management implemented (the buffers need to marked as available somehow to provide the right one to littlevgl) I think my proposal should be ok.
But since you‘re implementing a generic display driver you can‘t really rely on task notifications, which might be used by application code. You‘re better off using e.g. a binary semaphore owned by your driver for ISR signaling.
First I’d use 1 i2s_lcd_write_pixels function with the appropriate arguments (buffer a/b, …) which prepares the next DMA data and waits for the previously started DMA ISR (i2s_free) semaphore being signaled.
After getting signaled poll-wait for the FIFO getting empty and start the prepared (next) DMA transfer…

Hi,

i2s port: bit0, bit1, … , bit8.
bit0 - bit7 are data(display pixels).
bit8 are data/command selection pin on display.

The bit8 pin are inverted in i2s hardware module inside esp32.
gpio_matrix_out ( LCD_DC_PIN, data_idx + 8, true, false ); // bit8 is used to control the DC signal !!!

The driver are working, but without display tearing sync for now.

Thank’s.

You can‘t set a bit / bit8 by OR ing 0x000. So I guess it’s just a typo here and the value in real code is 0x100 :wink: