The microcontrollers in the STM32G0xx family have dedicated RAM for managing messages in the FDCAN module, for reception and transmission. In the figure below, we can see the areas labeled as buffers and FIFOs, which will store incoming and outgoing messages.

The transmission buffer can store up to 3 elements, ranging from 1 byte to 8 bytes in classic mode or up to 64 bytes in FD mode. The buffers are managed as a FIFO stack and operate as follows:

while( 1u )
{
    /*Colocanmos el mensaje en el buffer de salida y activamos el envio*/
    HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message );
    buffer = HAL_FDCAN_GetLatestTxFifoQRequestBuffer( &CANHandler );
    /*esperamos un segundo*/
    HAL_Delay( 1000u );
}

In the previous code, when we first call the HAL_FDCAN_AddMessageToTxFifoQ function, we save the message to be transmitted in the first FDCAN_TX_BUFFER0. However, in a subsequent iteration, the HAL_FDCAN_AddMessageToTxFifoQ function will load the message to be transmitted into the next buffer, FDCAN_TX_BUFFER1, even if the previous one has already transmitted its message. In a third iteration, the FDCAN_TX_BUFFER2 is used, and then the sequence is repeated. This can be verified using the buffer variable, which, if we place a breakpoint on line 7 and observe its values in each iteration, will be 1, then 2, and 4, before repeating the sequence of values. These values correspond to the buffer definitions found in the stm32g0xx_hal_fdcan.h header.

#define FDCAN_TX_BUFFER0 ((uint32_t)0x00000001U) /*!< Add message to Tx Buffer 0  */
#define FDCAN_TX_BUFFER1 ((uint32_t)0x00000002U) /*!< Add message to Tx Buffer 1  */
#define FDCAN_TX_BUFFER2 ((uint32_t)0x00000004U) /*!< Add message to Tx Buffer 2  */

In the previous example, we transmitted every second, which is more than enough time for the message to be transmitted and the transmitter to become available again. However, it is proper to inquire about its availability. Remember that the function HAL_FDCAN_GetLatestTxFifoQRequestBuffer returns the last buffer you used, and we want to inquire about the availability of the next one.

while( 1u )
{
    /*preguntamos por el buffer que acabamnos de usar*/
    buffer = HAL_FDCAN_GetLatestTxFifoQRequestBuffer( &CANHandler );
    /*preguntamos si el siguiente buffer esta disponible*/
    buffer <<= 1;
    if(buffer==8) buffer = 1;
    if( HAL_FDCAN_IsTxBufferMessagePending( &CANHandler, buffer ) == 0u )
    {
        /*Colocanmos el mensaje en el buffer de salida y activamos el envio*/
        HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message );
    }
      
    /*esperamos un segundo*/
    HAL_Delay( 1000u );
}

Remember that the FIFO stack has three buffers, so we can load up to three messages before waiting for space again. If you want to keep transmitting until the buffers are empty again, you can inquire about the last one. We don't need to inquire about each of them individually since, due to the FIFO behavior, when the last buffer is finished sending, it means the previous ones have already been sent.

while( 1u )
{
    /*preguntamos por el buffer que acabamnos de usar el cual es el ultimo*/
    buffer = HAL_FDCAN_GetLatestTxFifoQRequestBuffer( &CANHandler );
    if( HAL_FDCAN_IsTxBufferMessagePending( &CANHandler, buffer ) == 0u )
    {
        /*Colocanmos el mensaje en los tres buffers de salida y activamos el envio*/
        HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message );
        HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message );
        HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message );
    }
      
    /*esperamos un segundo*/
    HAL_Delay( 1000u );
}

We can also use interrupts to identify the moment when a message has finished sending in any of the buffers. It's necessary to enable the interrupts of the CAN module we're using and call the function HAL_FDCAN_AddMessageToTxFifoQ with the option FDCAN_IT_TX_FIFO_EMPTY. This option indicates that an interrupt will be activated when the CAN Tx FIFO is empty or when all messages have been sent, regardless of which buffer they were placed in.

/*activamos la interrupcion por transmision en el buffer0 cuando este se vacia,
NOTA: con esta opcion el tercer paramtero no es tomado en cuenta*/
HAL_FDCAN_ActivateNotification( &CANHandler, FDCAN_IT_TX_FIFO_EMPTY, 0u );
    
/*Colocanmos el mensaje en el buffer de salida y activamos el envio*/
HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message );

    :
    :
    
/*Este callback se manda llamar cada que el fifo de transmicion se vacia*/
void HAL_FDCAN_TxFifoEmptyCallback(FDCAN_HandleTypeDef *hfdcan)
{
    /*aqui podemo activar una nadera o realizar cualquier cosa que querramos*/
}

Remember that you can use up to three of the buffers, and the interrupt will be triggered when all of these have sent their information.

/*activamos la interrupcion por transmision en el buffer0 cuando este se vacia,
NOTA: con esta opcion el tercer paramtero no es tomado en cuenta*/
HAL_FDCAN_ActivateNotification( &CANHandler, FDCAN_IT_TX_FIFO_EMPTY, 0u );
    
/*Colocanmos el mensaje en el buffer de salida y activamos el envio*/
HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message1 );
HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message2 );
HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message3 );
    :
    :
    
/*Este callback se manda llamar cada que el fifo de transmicion se vacia
los tres mensajes ya se enviaron*/
void HAL_FDCAN_TxFifoEmptyCallback(FDCAN_HandleTypeDef *hfdcan)
{
    /*aqui podemo activar una badera o realizar cualquier cosa que querramos*/
}

Another way to detect that the information has been sent is through the option FDCAN_IT_TX_COMPLETE. This option allows you to specify in which buffer you want to detect that the message has been sent, and it calls the callback function HAL_FDCAN_TxBufferCompleteCallback. However, there is a problem with this option, which is that you need to know exactly which buffer you are using to send the message, and this can be a bit complicated since the HAL_FDCAN_AddMessageToTxFifoQ function does not allow you to choose the buffer to use. For options that don't require more complexity, I suggest using the previous option.

/*activamos la interrupcion por transmision en el buffer0 cuando este se vacia*/
HAL_FDCAN_ActivateNotification( &CANHandler, FDCAN_IT_TX_COMPLETE, FDCAN_TX_BUFFER0 );

/*Colocanmos el mensaje en el buffer de salida y activamos el envio*/
HAL_FDCAN_AddMessageToTxFifoQ( &CANHandler, &CANTxHeader, message );
    :
    :
    
void HAL_FDCAN_TxBufferCompleteCallback(FDCAN_HandleTypeDef *hfdcan, uint32_t BufferIndexes)
{
    /*aqui podemo activar una badera o realizar cualquier cosa que querramos*/
}